OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Compare Revisions

Regard whitespace Rev 180 → Rev 181

/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/ReferenceProcessor.java
18,18 → 18,18
 
public class ReferenceProcessor extends JPanel implements BatchProcessor {
 
private final SQLField field;
private final BatchField field;
final SQLElement element;
private ElementComboBox combo;
 
public ReferenceProcessor(SQLField field) {
public ReferenceProcessor(BatchField field) {
this.field = field;
this.element = ComptaPropsConfiguration.getInstanceCompta().getDirectory().getElement(field.getForeignTable());
this.element = ComptaPropsConfiguration.getInstanceCompta().getDirectory().getElement(field.getField().getForeignTable());
 
if (element != null) {
this.setLayout(new BorderLayout());
this.add(new JLabel("remplacer par "), BorderLayout.WEST);
combo = new ElementComboBox(true, 200);
combo = new ElementComboBox(true, 10);
combo.setMinimal();
combo.setAddIconVisible(false);
combo.init(element);
36,7 → 36,7
this.add(combo, BorderLayout.CENTER);
} else {
this.setLayout(new FlowLayout());
this.add(new JLabelWarning("No element for table " + field.getTable().getName()));
this.add(new JLabelWarning("No element for table " + field.getField().getTable().getName()));
}
}
 
45,7 → 45,7
 
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
rowValues.put(field.getName(), combo.getSelectedId());
rowValues.put(field.getField().getName(), combo.getSelectedId());
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/product/PurchaseProcessor.java
2,14 → 2,14
 
import java.math.BigDecimal;
 
import org.openconcerto.modules.common.batchprocessing.BatchField;
import org.openconcerto.modules.common.batchprocessing.NumberProcessor;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
 
public class PurchaseProcessor extends NumberProcessor {
 
public PurchaseProcessor(SQLField field) {
public PurchaseProcessor(BatchField field) {
super(field);
}
 
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/product/TTCProcessor.java
4,8 → 4,8
 
import org.openconcerto.erp.core.finance.tax.model.TaxeCache;
import org.openconcerto.erp.utils.ConvertDevise;
import org.openconcerto.modules.common.batchprocessing.BatchField;
import org.openconcerto.modules.common.batchprocessing.NumberProcessor;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
12,7 → 12,7
 
public class TTCProcessor extends NumberProcessor {
 
public TTCProcessor(SQLField field) {
public TTCProcessor(BatchField field) {
super(field);
}
 
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/product/TVAProcessor.java
4,8 → 4,8
 
import org.openconcerto.erp.core.finance.tax.model.TaxeCache;
import org.openconcerto.erp.utils.ConvertDevise;
import org.openconcerto.modules.common.batchprocessing.BatchField;
import org.openconcerto.modules.common.batchprocessing.ReferenceProcessor;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
12,7 → 12,7
 
public class TVAProcessor extends ReferenceProcessor {
 
public TVAProcessor(SQLField field) {
public TVAProcessor(BatchField field) {
super(field);
}
 
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/product/HTProcessor.java
4,8 → 4,8
 
import org.openconcerto.erp.core.finance.tax.model.TaxeCache;
import org.openconcerto.erp.utils.ConvertDevise;
import org.openconcerto.modules.common.batchprocessing.BatchField;
import org.openconcerto.modules.common.batchprocessing.NumberProcessor;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
12,7 → 12,7
 
public class HTProcessor extends NumberProcessor {
 
public HTProcessor(SQLField field) {
public HTProcessor(BatchField field) {
super(field);
}
 
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/StringProcessor.java
29,8 → 29,8
private JRadioButton bLower;
private JRadioButton bUpper;
 
public StringProcessor(SQLField field) {
this.field = field;
public StringProcessor(BatchField field) {
this.field = field.getField();
 
this.setLayout(new GridBagLayout());
bReplace = new JRadioButton("remplacer par");
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/BatchEditorPanel.java
24,10 → 24,13
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.PropsConfiguration;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowListRSH;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.JLabelBold;
36,24 → 39,34
 
public class BatchEditorPanel extends JPanel {
 
public BatchEditorPanel(final List<SQLRowValues> rows, FieldFilter filter) {
Configuration conf = PropsConfiguration.getInstance();
final SQLFieldTranslator translator = conf.getTranslator();
public BatchEditorPanel(final SQLElementDirectory dir, final List<SQLRowValues> rows, FieldFilter filter) {
SQLFieldTranslator translator = dir.getTranslator();
Set<SQLField> fields = rows.get(0).getTable().getFields();
List<SQLField> f = new ArrayList<SQLField>();
List<BatchField> f = new ArrayList<BatchField>();
for (SQLField sqlField : fields) {
if (ForbiddenFieldName.isAllowed(sqlField.getName()) && translator.getLabelFor(sqlField) != null) {
if (filter == null || !filter.isFiltered(sqlField)) {
f.add(sqlField);
f.add(new BatchField(dir, sqlField, null));
}
}
}
SQLTable tableTarif = rows.get(0).getTable().getTable("TARIF");
SQLTable tableArticleTarif = rows.get(0).getTable().getTable("ARTICLE_TARIF");
SQLSelect sel = new SQLSelect();
sel.addSelectStar(tableTarif);
List<SQLRow> rowTarif = SQLRowListRSH.execute(sel);
for (SQLRow sqlRow : rowTarif) {
f.add(new BatchField(dir, tableArticleTarif.getField("PV_HT"), sqlRow));
if (tableArticleTarif.contains("POURCENT_REMISE")) {
f.add(new BatchField(dir, tableArticleTarif.getField("POURCENT_REMISE"), sqlRow));
}
}
 
Collections.sort(f, new Comparator<SQLField>() {
Collections.sort(f, new Comparator<BatchField>() {
 
@Override
public int compare(SQLField o1, SQLField o2) {
return translator.getLabelFor(o1).compareToIgnoreCase(translator.getLabelFor(o2));
public int compare(BatchField o1, BatchField o2) {
return o1.getComboName().compareToIgnoreCase(o2.getComboName());
}
});
this.setLayout(new GridBagLayout());
60,11 → 73,11
GridBagConstraints c = new DefaultGridBagConstraints();
this.add(new JLabel("Champ"), c);
 
final JComboBox<SQLField> combo = new JComboBox<SQLField>(f.toArray(new SQLField[1]));
final JComboBox<BatchField> combo = new JComboBox<BatchField>(f.toArray(new BatchField[1]));
combo.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
String label = translator.getLabelFor(((SQLField) value));
String label = ((BatchField) value).getComboName();
return super.getListCellRendererComponent(list, label, index, isSelected, cellHasFocus);
}
});
86,7 → 99,7
c.gridy++;
c.anchor = GridBagConstraints.NORTHWEST;
final BatchDetailPanel comp = new BatchDetailPanel();
comp.setField((SQLField) combo.getSelectedItem());
comp.setField((BatchField) combo.getSelectedItem());
this.add(comp, c);
 
JPanel actions = new JPanel();
108,7 → 121,7
 
@Override
public void itemStateChanged(ItemEvent e) {
comp.setField((SQLField) combo.getSelectedItem());
comp.setField((BatchField) combo.getSelectedItem());
 
}
});
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/DateProcessor.java
17,8 → 17,8
private final JDate d = new JDate(true);
private final SQLField field;
 
public DateProcessor(SQLField field) {
this.field = field;
public DateProcessor(BatchField field) {
this.field = field.getField();
this.setLayout(new FlowLayout());
this.add(new JLabel("forcer la date au "));
this.add(d);
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/BooleanProcessor.java
13,13 → 13,13
import org.openconcerto.ui.VFlowLayout;
 
public class BooleanProcessor extends JPanel implements BatchProcessor {
private final SQLField field;
private final BatchField field;
 
private JRadioButton bTrue;
private JRadioButton bFalse;
private JRadioButton bInvert;
 
public BooleanProcessor(SQLField field) {
public BooleanProcessor(BatchField field) {
this.field = field;
this.setLayout(new VFlowLayout());
bTrue = new JRadioButton("forcer à vrai");
41,7 → 41,7
if (bTrue.isSelected()) {
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
rowValues.put(field.getName(), Boolean.TRUE);
rowValues.put(field.getField().getName(), Boolean.TRUE);
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
48,7 → 48,7
} else if (bFalse.isSelected()) {
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
rowValues.put(field.getName(), Boolean.FALSE);
rowValues.put(field.getField().getName(), Boolean.FALSE);
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
55,9 → 55,9
} else if (bInvert.isSelected()) {
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
final Boolean boolean1 = sqlRowAccessor.asRow().getBoolean(field.getName());
final Boolean boolean1 = sqlRowAccessor.asRow().getBoolean(field.getField().getName());
if (boolean1 != null) {
rowValues.put(field.getName(), boolean1.equals(Boolean.FALSE));
rowValues.put(field.getField().getName(), boolean1.equals(Boolean.FALSE));
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/BatchDetailPanel.java
22,46 → 22,47
this.setLayout(new VFlowLayout());
}
 
public void setField(SQLField field) {
public void setField(BatchField batchField) {
this.removeAll();
 
SQLField field = batchField.getField();
final SQLType type = field.getType();
final Class<?> javaType = type.getJavaType();
final String fName = field.getName();
if (fName.equals("PV_TTC")) {
final NumberProcessor p = new TTCProcessor(field);
final NumberProcessor p = new TTCProcessor(batchField);
this.add(p);
this.processor = p;
} else if (fName.equals("PV_HT")) {
final NumberProcessor p = new HTProcessor(field);
final NumberProcessor p = new HTProcessor(batchField);
this.add(p);
this.processor = p;
} else if (fName.equals("ID_TAXE")) {
final ReferenceProcessor p = new TVAProcessor(field);
final ReferenceProcessor p = new TVAProcessor(batchField);
this.add(p);
this.processor = p;
} else if (fName.equals("PA_HT")) {
final NumberProcessor p = new PurchaseProcessor(field);
final NumberProcessor p = new PurchaseProcessor(batchField);
this.add(p);
this.processor = p;
} else if (javaType.equals(Boolean.class)) {
final BooleanProcessor p = new BooleanProcessor(field);
final BooleanProcessor p = new BooleanProcessor(batchField);
this.add(p);
this.processor = p;
} else if (field.isKey()) {
final ReferenceProcessor p = new ReferenceProcessor(field);
final ReferenceProcessor p = new ReferenceProcessor(batchField);
this.add(p);
this.processor = p;
} else if (javaType.equals(String.class)) {
final StringProcessor p = new StringProcessor(field);
final StringProcessor p = new StringProcessor(batchField);
this.add(p);
this.processor = p;
} else if (javaType.equals(Date.class)) {
final DateProcessor p = new DateProcessor(field);
final DateProcessor p = new DateProcessor(batchField);
this.add(p);
this.processor = p;
} else if (javaType.equals(BigDecimal.class) || javaType.equals(Float.class) || javaType.equals(Double.class) || javaType.equals(Integer.class) || javaType.equals(Long.class)) {
final NumberProcessor p = new NumberProcessor(field);
final NumberProcessor p = new NumberProcessor(batchField);
this.add(p);
this.processor = p;
}
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/BatchField.java
New file
0,0 → 1,58
package org.openconcerto.modules.common.batchprocessing;
 
import java.util.List;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowListRSH;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.SQLFieldTranslator;
 
public class BatchField {
 
private final SQLField field;
private final SQLRowAccessor foreignLinkRow;
private final SQLFieldTranslator translator;
private final SQLElement elementLink;
 
public BatchField(SQLElementDirectory dir, SQLField field, SQLRowAccessor foreignLinkRow) {
this.field = field;
this.foreignLinkRow = foreignLinkRow;
 
this.translator = dir.getTranslator();
if (foreignLinkRow == null) {
this.elementLink = null;
} else {
this.elementLink = dir.getElement(foreignLinkRow.getTable());
}
}
 
public SQLField getField() {
return field;
}
 
public SQLRowAccessor getForeignLinkRow() {
return foreignLinkRow;
}
 
public String getComboName() {
if (this.foreignLinkRow == null) {
return this.translator.getLabelFor(this.field);
} else {
return this.elementLink.getPluralName() + " " + this.foreignLinkRow.getString("NOM") + " " + this.translator.getLabelFor(this.field);
}
}
 
public List<SQLRow> getReferentRows(SQLRowAccessor rowOrigin) {
SQLSelect sel = new SQLSelect();
sel.addSelectStar(this.field.getTable());
final Where w = new Where(this.field.getTable().getField("ID_" + rowOrigin.getTable().getName()), "=", rowOrigin.getID());
sel.setWhere(w.and(new Where(this.field.getTable().getField("ID_" + foreignLinkRow.getTable().getName()), "=", foreignLinkRow.getID())));
return SQLRowListRSH.execute(sel);
}
 
}
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/Module.java
2,7 → 2,6
 
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.List;
 
9,18 → 8,12
import javax.swing.AbstractAction;
import javax.swing.JFrame;
 
import org.openconcerto.erp.config.Gestion;
import org.openconcerto.erp.modules.AbstractModule;
import org.openconcerto.erp.modules.ComponentsContext;
import org.openconcerto.erp.modules.ModuleFactory;
import org.openconcerto.erp.modules.ModuleManager;
import org.openconcerto.erp.modules.ModulePackager;
import org.openconcerto.erp.modules.RuntimeModuleFactory;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRequestLog;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.ui.ConnexionPanel;
import org.openconcerto.sql.view.list.IListe;
import org.openconcerto.sql.view.list.IListeAction.IListeEvent;
import org.openconcerto.sql.view.list.RowAction;
33,7 → 26,7
}
 
@Override
protected void setupComponents(ComponentsContext ctxt) {
protected void setupComponents(final ComponentsContext ctxt) {
 
super.setupComponents(ctxt);
final SQLElement element = ctxt.getElement("ARTICLE");
60,7 → 53,7
 
};
 
f.setContentPane(new BatchEditorPanel(rows, filter));
f.setContentPane(new BatchEditorPanel(ctxt.getElement("ARTICLE").getDirectory(), rows, filter));
f.pack();
f.setMinimumSize(new Dimension(400, 300));
f.setLocationRelativeTo(IListe.get(e));
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/NumberProcessor.java
13,7 → 13,7
import javax.swing.JRadioButton;
import javax.swing.JTextField;
 
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.ui.DefaultGridBagConstraints;
20,7 → 20,7
 
public class NumberProcessor extends JPanel implements BatchProcessor {
 
private final SQLField field;
private final BatchField batchfield;
// Editors
final JTextField tReplace = new JTextField();
private JRadioButton bReplace;
30,8 → 30,8
final JTextField tRemove = new JTextField();
private JRadioButton bRemove;
 
public NumberProcessor(SQLField field) {
this.field = field;
public NumberProcessor(BatchField field) {
this.batchfield = field;
 
this.setLayout(new GridBagLayout());
bReplace = new JRadioButton("remplacer par");
110,11 → 110,23
if (bReplace.isSelected()) {
BigDecimal v = new BigDecimal(this.tReplace.getText().trim());
for (SQLRowAccessor sqlRowAccessor : r) {
 
if (batchfield.getForeignLinkRow() != null) {
 
final List<SQLRow> referentRow = batchfield.getReferentRows(sqlRowAccessor);
for (SQLRow sqlRowT : referentRow) {
SQLRowValues rowValues = sqlRowT.createEmptyUpdateRow();
rowValues.put(batchfield.getField().getName(), decimalToFieldType(v));
processBeforeUpdate(sqlRowT, rowValues);
rowValues.update();
}
} else {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
rowValues.put(field.getName(), decimalToFieldType(v));
rowValues.put(batchfield.getField().getName(), decimalToFieldType(v));
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
}
} else if (bAdd.isSelected()) {
 
String t = this.tAdd.getText().trim();
127,18 → 139,44
BigDecimal v = new BigDecimal(t);
 
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
final BigDecimal value = sqlRowAccessor.asRow().getBigDecimal(field.getName());
 
if (batchfield.getForeignLinkRow() != null) {
 
final List<SQLRow> referentRow = batchfield.getReferentRows(sqlRowAccessor);
for (SQLRow sqlRowT : referentRow) {
 
SQLRowValues rowValues = sqlRowT.createEmptyUpdateRow();
BigDecimal value = sqlRowT.asRow().getBigDecimal(batchfield.getField().getName());
 
if (value != null) {
if (isPercent) {
rowValues.put(field.getName(), decimalToFieldType(value.multiply(v.divide(new BigDecimal(100)).add(BigDecimal.ONE))));
rowValues.put(batchfield.getField().getName(), decimalToFieldType(value.multiply(v.divide(new BigDecimal(100)).add(BigDecimal.ONE))));
} else {
rowValues.put(field.getName(), decimalToFieldType(value.add(v)));
rowValues.put(batchfield.getField().getName(), decimalToFieldType(value.add(v)));
}
processBeforeUpdate(sqlRowT, rowValues);
rowValues.update();
}
}
} else {
 
final SQLRowValues rowValues;
final BigDecimal value;
 
rowValues = sqlRowAccessor.createEmptyUpdateRow();
value = sqlRowAccessor.asRow().getBigDecimal(batchfield.getField().getName());
 
if (value != null) {
if (isPercent) {
rowValues.put(batchfield.getField().getName(), decimalToFieldType(value.multiply(v.divide(new BigDecimal(100)).add(BigDecimal.ONE))));
} else {
rowValues.put(batchfield.getField().getName(), decimalToFieldType(value.add(v)));
}
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
}
}
} else if (bRemove.isSelected()) {
String t = this.tRemove.getText().trim();
boolean isPercent = false;
149,15 → 187,35
 
BigDecimal v = new BigDecimal(t);
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
if (batchfield.getForeignLinkRow() != null) {
final List<SQLRow> referentRow = batchfield.getReferentRows(sqlRowAccessor);
if (referentRow != null && !referentRow.isEmpty()) {
for (SQLRow sqlRowT : referentRow) {
 
final BigDecimal value = sqlRowAccessor.asRow().getBigDecimal(field.getName());
SQLRowValues rowValues = sqlRowT.createEmptyUpdateRow();
final BigDecimal value = sqlRowT.getBigDecimal(batchfield.getField().getName());
if (value != null) {
if (isPercent) {
rowValues.put(field.getName(), decimalToFieldType(value.multiply(v.divide(new BigDecimal(-100)).add(BigDecimal.ONE))));
rowValues.put(batchfield.getField().getName(), decimalToFieldType(value.multiply(v.divide(new BigDecimal(-100)).add(BigDecimal.ONE))));
} else {
rowValues.put(field.getName(), decimalToFieldType(value.add(v)));
rowValues.put(batchfield.getField().getName(), decimalToFieldType(value.add(v)));
}
processBeforeUpdate(sqlRowT, rowValues);
rowValues.update();
}
}
}
} else {
 
SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
final BigDecimal value = sqlRowAccessor.asRow().getBigDecimal(batchfield.getField().getName());
 
if (value != null) {
if (isPercent) {
rowValues.put(batchfield.getField().getName(), decimalToFieldType(value.multiply(v.divide(new BigDecimal(-100)).add(BigDecimal.ONE))));
} else {
rowValues.put(batchfield.getField().getName(), decimalToFieldType(value.add(v)));
}
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
164,9 → 222,10
}
}
}
}
 
private Object decimalToFieldType(BigDecimal v) {
final Class<?> javaType = field.getType().getJavaType();
final Class<?> javaType = batchfield.getField().getType().getJavaType();
if (javaType.equals(BigDecimal.class)) {
return v;
} else if (javaType.equals(Float.class)) {
/trunk/Modules/Module Label/.classpath
1,7 → 1,7
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry combineaccessrules="false" kind="src" path="/OpenConcerto"/>
<classpathentry kind="output" path="bin"/>
</classpath>
/trunk/Modules/Module Label/lib/barcode4j-2.1.0.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/Modules/Module Label/lib/barcode4j-2.1.0.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/Modules/Module Label/lib/jbarcode-0.2.8.jar
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/Modules/Module Label/lib/jbarcode-0.2.8.jar
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/48.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/48.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/LabelPanel.java
15,11 → 15,9
 
import javax.swing.JPanel;
 
import org.openconcerto.sql.model.SQLRowAccessor;
 
public class LabelPanel extends JPanel implements Printable {
 
private List<? extends SQLRowAccessor> list;
private List<? extends Label> list;
private int lines;
private int columns;
private Boolean[][] mask;
29,7 → 27,7
private int topMargin;
private int leftMargin;
 
public LabelPanel(List<? extends SQLRowAccessor> list, int l, int c, LabelRenderer labelRenderer) {
public LabelPanel(List<? extends Label> list, int l, int c, LabelRenderer labelRenderer) {
this.list = list;
this.renderer = labelRenderer;
setSizeLayoutSize(l, c);
48,6 → 46,11
});
}
 
public void setList(List<? extends Label> list) {
this.list = list;
repaint();
}
 
private void setSizeLayoutSize(int l, int c) {
this.lines = l;
this.columns = c;
102,7 → 105,7
g.drawRect(x, y, w, h);
// Label
if (rowIndex < list.size()) {
SQLRowAccessor row = list.get(rowIndex);
Label row = list.get(rowIndex);
renderer.paintLabel(g, row, x, y, w, h, 10f);
}
rowIndex++;
169,7 → 172,7
for (int j = 0; j < this.columns; j++) {
boolean mask = this.mask[i][j];
if (!mask && rowIndex < list.size()) {
SQLRowAccessor row = list.get(rowIndex);
Label row = list.get(rowIndex);
renderer.paintLabel(g, row, j * w + imageableX, i * h + imageableY, w, h, 10f);
}
rowIndex++;
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/96.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/96.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/logo.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/logo.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/GS1Frame.java
New file
0,0 → 1,1049
package org.openconcerto.modules.label;
 
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
 
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.PrintService;
import javax.print.SimpleDoc;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.PlainDocument;
 
import org.openconcerto.modules.label.filter.BatchDocumentFilter;
import org.openconcerto.modules.label.filter.EANDocumentFilter;
import org.openconcerto.modules.label.filter.LimitDocumentFilter;
import org.openconcerto.modules.label.filter.NumberOfProductDocumentFilter;
import org.openconcerto.modules.label.gs1.GS1AIElements;
import org.openconcerto.modules.label.gs1.GS1Util;
import org.openconcerto.ui.JDate;
 
import uk.org.okapibarcode.backend.Code128;
import uk.org.okapibarcode.backend.DataMatrix;
import uk.org.okapibarcode.backend.DataMatrix.ForceMode;
import uk.org.okapibarcode.backend.Ean;
import uk.org.okapibarcode.backend.Symbol;
import uk.org.okapibarcode.output.Java2DRenderer;
 
public class GS1Frame extends JFrame {
 
private final SimpleDateFormat dfDate = new SimpleDateFormat("yyMMdd");
private final JTextField textSupplier = new JTextField(20);
private final JTextField textName = new JTextField(20);
private final JTextField textEAN = new JTextField(14);
private final JTextField textBatch = new JTextField(20);
private final JSpinner portZPL = new JSpinner(new SpinnerNumberModel(9100, 24, 10000, 1));
private final JLabel labelEan = new JLabel("EAN 13/14", SwingConstants.RIGHT);
private final JTextField textNumberOfProducts = new JTextField(12);
private final JSpinner labelsSpinner = new JSpinner(new SpinnerNumberModel(5, 0, 9000, 1));
private final JTextField labelBarCode = new JTextField(64);
private final JDate dateDLUODLC = new JDate();
private final JRadioButton b1 = new JRadioButton("DDM/DLUO");
private final JCheckBox checkIgnoreMargins = new JCheckBox("Ignorer les marges de l'imprimante");
private final JRadioButton twoPerPage = new JRadioButton("2");
private final JRadioButton fourPerPage = new JRadioButton("4");
private final JRadioButton radioZPLUSB = new JRadioButton("imprimante locale");
private final JTextField textIP = new JTextField(20);
private final JTabbedPane tabs = new JTabbedPane();
private LabelPanel labelPanel;
private BufferedImage barcodeImage = null;
private BufferedImage barcodeImageBatch = null;
private BufferedImage barcodeDatamatrix = null;
private final Properties properties = new Properties();
 
public GS1Frame() {
super("Code à barres GS1-128");
 
final File file = new File("openconcerto-gs1.properties");
if (file.exists()) {
try {
properties.load(new FileInputStream(file));
} catch (IOException e) {
e.printStackTrace();
}
}
 
final JSplitPane split = new JSplitPane();
final JPanel left = createLeftContent();
final JComponent right = createRightContent();
split.setLeftComponent(left);
split.setRightComponent(right);
this.setContentPane(split);
split.setEnabled(false);
try {
updateList();
} catch (Exception e) {
e.printStackTrace();
}
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
try {
saveProperties();
} catch (IOException e1) {
e1.printStackTrace();
}
dispose();
}
});
}
 
public void saveProperties() throws FileNotFoundException, IOException {
properties.setProperty("supplier", this.textSupplier.getText());
properties.setProperty("product", this.textName.getText());
properties.setProperty("ean", this.textEAN.getText());
properties.setProperty("nbOfProdcuts", this.textNumberOfProducts.getText());
properties.setProperty("batch", this.textBatch.getText());
final Date value = this.dateDLUODLC.getValue();
if (value != null) {
properties.setProperty("sellByDate", String.valueOf(value.getTime()));
} else {
properties.setProperty("sellByDate", "");
}
properties.setProperty("sellBy", b1.isSelected() ? "true" : "false");
properties.setProperty("nbLabels", this.labelsSpinner.getValue().toString());
properties.setProperty("labelsPerPage", fourPerPage.isSelected() ? "4" : "2");
properties.setProperty("ignoreMargin", checkIgnoreMargins.isSelected() ? "true" : "false");
properties.setProperty("usbPrinter", this.radioZPLUSB.isSelected() ? "true" : "false");
properties.setProperty("zplIP", this.textIP.getText());
properties.setProperty("zplPort", this.portZPL.getValue().toString());
properties.setProperty("tab", String.valueOf(tabs.getSelectedIndex()));
 
final FileOutputStream out = new FileOutputStream("openconcerto-gs1.properties");
properties.store(out, "");
out.flush();
out.close();
}
 
boolean isEANValid() {
final String ean = this.textEAN.getText().trim();
if (ean.length() < 13) {
return false;
}
final char cd1 = Ean.calcDigit(ean.substring(0, ean.length() - 1));
final char cd2 = ean.charAt(ean.length() - 1);
return (cd1 == cd2);
}
 
private void updateList() throws Exception {
this.barcodeImage = null;
this.barcodeImageBatch = null;
this.barcodeDatamatrix = null;
this.labelPanel.setList(new ArrayList<>());
this.labelBarCode.setText("");
// Check EAN
String ean = this.textEAN.getText().trim();
if (!isEANValid()) {
this.labelEan.setForeground(Color.RED);
} else {
this.labelEan.setForeground(Color.BLACK);
}
if (ean.length() == 13) {
this.labelEan.setText("EAN 13");
} else if (ean.length() == 14) {
this.labelEan.setText("EAN 14");
} else {
this.labelEan.setText("EAN invalide");
this.labelEan.setForeground(Color.RED);
}
 
if (!isEANValid()) {
return;
}
 
int n = ((Number) this.labelsSpinner.getValue()).intValue();
this.labelBarCode.setText(this.getGS1().formatHumanReadable());
this.labelBarCode.setEditable(false);
GS1Label l = new GS1Label(this.textSupplier.getText(), this.textName.getText(), this.getGS1());
List<GS1Label> list = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
list.add(l);
}
this.labelPanel.setList(list);
 
// Configure the barcode generator
// adjust barcode width here
// 32mm de hatu minimum
// 0,495mm et 1,016mm pour largeur de barre
 
GS1Util util = new GS1Util();
 
{
final GS1AIElements gs128 = getGS128();
Code128 dataMatrix = new Code128();
dataMatrix.setDataType(Symbol.DataType.GS1);
dataMatrix.setBarHeight(70);
dataMatrix.setContent(util.formatDataMatrix(gs128));
int magnification = 5;
int borderSize = 3;
this.barcodeImage = new BufferedImage((dataMatrix.getWidth() * magnification) + (2 * borderSize), (dataMatrix.getHeight() * magnification) + (2 * borderSize), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = this.barcodeImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, (dataMatrix.getWidth() * magnification) + (2 * borderSize), (dataMatrix.getHeight() * magnification) + (2 * borderSize));
Java2DRenderer renderer = new Java2DRenderer(g2d, magnification, Color.WHITE, Color.BLACK);
renderer.render(dataMatrix);
}
final GS1AIElements gs128b = getGS128Batch();
if (!gs128b.isEmpty()) {
Code128 dataMatrix = new Code128();
dataMatrix.setDataType(Symbol.DataType.GS1);
dataMatrix.setBarHeight(90);
dataMatrix.setContent(util.formatDataMatrix(gs128b));
int magnification = 4;
int borderSize = 3;
this.barcodeImageBatch = new BufferedImage((dataMatrix.getWidth() * magnification) + (2 * borderSize), (int) (dataMatrix.getHeight() * magnification) + (2 * borderSize),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = this.barcodeImageBatch.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, (dataMatrix.getWidth() * magnification) + (2 * borderSize), (dataMatrix.getHeight() * magnification) + (2 * borderSize));
Java2DRenderer renderer = new Java2DRenderer(g2d, magnification, Color.WHITE, Color.BLACK);
renderer.render(dataMatrix);
}
 
{
String s = util.formatDataMatrix(getGS1());
DataMatrix dataMatrix = new DataMatrix();
dataMatrix.setDataType(Symbol.DataType.GS1);
dataMatrix.setForceMode(ForceMode.SQUARE);
dataMatrix.setContent(s);
int magnification = 30;
int borderSize = 3;
this.barcodeDatamatrix = new BufferedImage((dataMatrix.getWidth() * magnification) + (2 * borderSize), (dataMatrix.getHeight() * magnification) + (2 * borderSize),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = this.barcodeDatamatrix.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, (dataMatrix.getWidth() * magnification) + (2 * borderSize), (dataMatrix.getHeight() * magnification) + (2 * borderSize));
Java2DRenderer renderer = new Java2DRenderer(g2d, magnification, Color.WHITE, Color.BLACK);
renderer.render(dataMatrix);
}
 
// ImageIO.write(barcodeImage, "PNG", new File("barcode.png"));
// ImageIO.write(barcodeImageBatch, "PNG", new File("barcode2.png"));
// ImageIO.write(barcodeDatamatrix, "PNG", new File("datamatrix.png"));
 
}
 
private JPanel createLeftContent() {
final JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.gridy = 0;
c.gridx = 0;
c.gridwidth = 3;
p.add(createLogo(), c);
c.fill = GridBagConstraints.HORIZONTAL;
c.gridwidth = 1;
c.gridy++;
c.anchor = GridBagConstraints.WEST;
c.insets = new Insets(2, 3, 2, 2);
//
c.weightx = 0;
c.gridwidth = 1;
p.add(new JLabel("Fournisseur / fabricant", SwingConstants.RIGHT), c);
c.gridx++;
c.weightx = 1;
c.gridwidth = 2;
this.textSupplier.setText(properties.getProperty("supplier", ""));
((AbstractDocument) this.textSupplier.getDocument()).setDocumentFilter(new LimitDocumentFilter(22));
p.add(this.textSupplier, c);
 
//
c.gridx = 0;
c.gridy++;
c.weightx = 0;
c.gridwidth = 1;
p.add(new JLabel("Désignation du produit", SwingConstants.RIGHT), c);
c.gridx++;
c.weightx = 1;
c.gridwidth = 2;
this.textName.setText(properties.getProperty("product", "Biscuit Extra"));
((AbstractDocument) this.textName.getDocument()).setDocumentFilter(new LimitDocumentFilter(22));
p.add(this.textName, c);
 
// EAN 13/14
c.gridx = 0;
c.gridy++;
c.weightx = 0;
c.gridwidth = 1;
p.add(this.labelEan, c);
c.gridx++;
c.weightx = 1;
c.gridwidth = 2;
this.textEAN.setText(properties.getProperty("ean", "7612345678900"));
((AbstractDocument) this.textEAN.getDocument()).setDocumentFilter(new EANDocumentFilter());
p.add(this.textEAN, c);
// nb de produit
c.gridx = 0;
c.gridy++;
c.weightx = 0;
c.gridwidth = 1;
p.add(new JLabel("Nombre par carton", SwingConstants.RIGHT), c);
c.gridx++;
c.weightx = 1;
c.gridwidth = 2;
c.fill = GridBagConstraints.NONE;
PlainDocument doc = (PlainDocument) this.textNumberOfProducts.getDocument();
doc.setDocumentFilter(new NumberOfProductDocumentFilter());
this.textNumberOfProducts.setText(properties.getProperty("nbOfProdcuts", "10"));
p.add(this.textNumberOfProducts, c);
// Numéro de lot
c.gridx = 0;
c.gridy++;
c.weightx = 0;
c.gridwidth = 1;
c.fill = GridBagConstraints.HORIZONTAL;
p.add(new JLabel("Lot", SwingConstants.RIGHT), c);
c.gridx++;
c.weightx = 1;
c.gridwidth = 2;
((AbstractDocument) this.textBatch.getDocument()).setDocumentFilter(new BatchDocumentFilter());
this.textBatch.setText(properties.getProperty("batch", ""));
p.add(this.textBatch, c);
// DLC
c.gridx = 1;
c.gridy++;
c.weightx = 0;
c.gridwidth = 1;
 
p.add(this.b1, c);
c.gridx++;
c.weightx = 1;
this.dateDLUODLC.setFormat(new SimpleDateFormat("dd/MM/yyyy"));
if (!properties.getProperty("sellByDate", "").isEmpty()) {
this.dateDLUODLC.setDateInMillis(Long.parseLong(properties.getProperty("sellByDate")));
}
p.add(this.dateDLUODLC, c);
// DLUO
c.gridx = 1;
c.gridy++;
c.weightx = 0;
JRadioButton b2 = new JRadioButton("DLC");
p.add(b2, c);
c.gridx++;
c.weightx = 1;
 
this.b1.setSelected(properties.getProperty("sellBy", "true").equals("true"));
b2.setSelected(!properties.getProperty("sellBy", "true").equals("true"));
// Barcode
c.gridx = 0;
c.gridy++;
c.weightx = 0;
c.gridwidth = 3;
p.add(new JLabel("Code GS1 complet de l'étiquette", SwingConstants.LEFT), c);
c.gridy++;
c.weightx = 1;
this.labelBarCode.setFont(this.textBatch.getFont());
this.labelBarCode.setEnabled(false);
p.add(this.labelBarCode, c);
 
// Nombre d'étiquettes
c.gridx = 0;
c.gridy++;
c.weightx = 0;
c.gridwidth = 1;
p.add(new JLabel("Nombre d'étiquettes", SwingConstants.RIGHT), c);
c.gridx++;
c.weightx = 0;
c.fill = GridBagConstraints.NONE;
 
this.labelsSpinner.setValue(Integer.parseInt(properties.getProperty("nbLabels", "4")));
 
p.add(this.labelsSpinner, c);
 
final JPanel spacer = new JPanel();
spacer.setOpaque(false);
c.gridy++;
c.weighty = 1;
p.add(spacer, c);
 
final ButtonGroup gr = new ButtonGroup();
gr.add(this.b1);
gr.add(b2);
 
// Listeners
 
final ChangeListener changeListener = new ChangeListener() {
 
@Override
public void stateChanged(ChangeEvent e) {
try {
updateList();
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
this.labelsSpinner.addChangeListener(changeListener);
 
final DocumentListener dListener = new DocumentListener() {
 
@Override
public void removeUpdate(DocumentEvent e) {
changedUpdate(e);
}
 
@Override
public void insertUpdate(DocumentEvent e) {
changedUpdate(e);
 
}
 
@Override
public void changedUpdate(DocumentEvent e) {
try {
updateList();
} catch (Exception ex) {
ex.printStackTrace();
}
}
};
this.textSupplier.getDocument().addDocumentListener(dListener);
this.textName.getDocument().addDocumentListener(dListener);
this.textEAN.getDocument().addDocumentListener(dListener);
this.textNumberOfProducts.getDocument().addDocumentListener(dListener);
this.textBatch.getDocument().addDocumentListener(dListener);
 
this.dateDLUODLC.addValueListener(new PropertyChangeListener() {
 
@Override
public void propertyChange(PropertyChangeEvent evt) {
try {
updateList();
} catch (Exception e) {
e.printStackTrace();
}
 
}
});
this.b1.addChangeListener(changeListener);
b2.addChangeListener(changeListener);
return p;
}
 
private Component createLogo() {
final Image i1 = new ImageIcon(this.getClass().getResource("logo.png")).getImage();
final Image i2 = new ImageIcon(this.getClass().getResource("oc-qrcode.png")).getImage();
return new JComponent() {
@Override
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(i1, 0, 3, null);
g.drawImage(i2, getWidth() - i2.getWidth(null), 0, null);
g.setColor(Color.GRAY);
g.drawLine(0, 58, getWidth(), 58);
}
 
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 59);
}
};
}
 
private JComponent createRightContent() {
 
final JPanel panelA4 = new JPanel();
panelA4.setOpaque(false);
panelA4.setLayout(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.insets = new Insets(2, 3, 2, 2);
c.gridx = 0;
c.gridy = 0;
panelA4.add(createToolBar(), c);
c.gridy++;
// panelA4.add(createToolBar2(), c);
// c.gridy++;
c.insets = new Insets(0, 0, 0, 0);
panelA4.add(new JSeparator(JSeparator.HORIZONTAL), c);
c.gridy++;
c.weightx = 1;
c.weighty = 1;
c.fill = GridBagConstraints.BOTH;
final int columnCount = fourPerPage.isSelected() ? 2 : 1;
this.labelPanel = new LabelPanel(new ArrayList<Label>(), 2, columnCount, createRenderer()) {
@Override
public Dimension getPreferredSize() {
return new Dimension(190 * 3, 280 * 3);
}
};
this.labelPanel.setIgnoreMargins(checkIgnoreMargins.isSelected());
panelA4.add(this.labelPanel, c);
tabs.addTab("Feuilles A4", panelA4);
tabs.addTab("Imprimante ZPL", createZPLPanel());
tabs.setSelectedIndex(Integer.parseInt(properties.getProperty("tab", "0")));
return tabs;
}
 
private Component createZPLPanel() {
final JPanel panelZPL = new JPanel();
panelZPL.setOpaque(false);
panelZPL.setLayout(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.insets = new Insets(2, 3, 2, 2);
c.gridx = 0;
c.gridy = 0;
c.weightx = 0;
c.gridwidth = 1;
c.anchor = GridBagConstraints.EAST;
 
radioZPLUSB.setOpaque(false);
panelZPL.add(radioZPLUSB, c);
c.gridx += 5;
c.weightx = 1;
c.fill = GridBagConstraints.NONE;
c.insets = new Insets(4, 3, 2, 4);
final JButton buttonPrintZPL = new JButton("Imprimer");
buttonPrintZPL.setOpaque(false);
panelZPL.add(buttonPrintZPL, c);
c.insets = new Insets(2, 3, 2, 2);
c.gridx = 0;
c.gridy++;
c.weightx = 0;
c.fill = GridBagConstraints.HORIZONTAL;
final JRadioButton radioZPLNetwork = new JRadioButton("imprimante réseau");
radioZPLNetwork.setOpaque(false);
panelZPL.add(radioZPLNetwork, c);
c.gridx++;
panelZPL.add(new JLabel("Adresse"), c);
c.gridx++;
 
textIP.setText(properties.getProperty("zplIP", "192.168.1.50"));
panelZPL.add(textIP, c);
c.gridx++;
panelZPL.add(new JLabel("Port"), c);
this.portZPL.setValue(Integer.parseInt(properties.getProperty("zplPort", "9100")));
c.gridx++;
panelZPL.add(this.portZPL, c);
c.gridx = 0;
c.gridy++;
c.gridwidth = 6;
 
radioZPLUSB.setSelected(properties.getProperty("usbPrinter", "true").equals("true"));
radioZPLNetwork.setSelected(!properties.getProperty("usbPrinter", "true").equals("true"));
 
final ButtonGroup g = new ButtonGroup();
g.add(radioZPLUSB);
g.add(radioZPLNetwork);
 
c.anchor = GridBagConstraints.NORTHWEST;
panelZPL.add(new JLabel("L'impression nécessite une imprimante d'étiquettes compatible ZPL (Zebra ou autre)."), c);
c.gridy++;
c.weighty = 1;
panelZPL.add(new JLabel("Vous pouvez paramétrer l'impression par défaut (largeur 6\" à 203 dpi) dans le fichier zpl.txt ."), c);
buttonPrintZPL.addActionListener(new ActionListener() {
 
@Override
public void actionPerformed(ActionEvent e) {
if (!isEANValid()) {
JOptionPane.showMessageDialog(GS1Frame.this, "EAN invalide");
return;
}
 
final String code = createZPLCode();
System.out.println("ZPL:");
System.out.println(code);
byte[] data = code.getBytes(StandardCharsets.US_ASCII);
if (radioZPLNetwork.isSelected()) {
Socket socket = null;
try {
socket = new Socket(textIP.getText(), ((Number) GS1Frame.this.portZPL.getValue()).intValue());
final DataOutputStream out = new DataOutputStream(socket.getOutputStream());
final int nb = ((Number) labelsSpinner.getValue()).intValue();
for (int i = 0; i < nb; i++) {
out.write(data);
}
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(buttonPrintZPL, "Erreur d'impression réseau : " + ex.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
 
}
} else {
try {
final PrinterJob pj1 = PrinterJob.getPrinterJob();
if (pj1.printDialog()) {
final PrintService ps = pj1.getPrintService();
final DocPrintJob pj = ps.createPrintJob();
final SimpleDoc doc = new SimpleDoc(data, DocFlavor.BYTE_ARRAY.AUTOSENSE, null);
final int nb = ((Number) labelsSpinner.getValue()).intValue();
for (int i = 0; i < nb; i++) {
pj.print(doc, null);
}
}
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(buttonPrintZPL, "Erreur d'impression locale : " + ex.getMessage());
}
}
}
 
});
 
return panelZPL;
 
}
 
private LabelRenderer createRenderer() {
return new LabelRenderer() {
 
@Override
public void paintLabel(Graphics g, Label label, int x, int y, int gridWith, int gridHeight, float fontSize) {
final Graphics2D g2d = (Graphics2D) g;
g.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(3));
String str1 = GS1Frame.this.textSupplier.getText().trim();
String str2 = GS1Frame.this.textName.getText();
int pos1 = 26;
if (str1.isEmpty()) {
str1 = GS1Frame.this.textName.getText();
str2 = "";
pos1 += 16;
}
 
final AffineTransform defaultAt = g2d.getTransform();
final AffineTransform at = ((AffineTransform) (g2d.getTransform().clone()));
at.rotate(-Math.PI / 2);
g2d.setTransform(at);
g.setFont(getFont().deriveFont(15f));
final int d1 = 60;
 
final int d2 = 60;
g.drawString(str1, -y - gridHeight + d1, x + pos1);
g.drawString(str2, -y - gridHeight + d2, x + 58);
if (GS1Frame.this.barcodeDatamatrix != null) {
float ratioDatamatrix = 15f;
final int wd = (int) (GS1Frame.this.barcodeDatamatrix.getWidth() / ratioDatamatrix);
final int hd = (int) (GS1Frame.this.barcodeDatamatrix.getHeight() / ratioDatamatrix);
g.drawImage(GS1Frame.this.barcodeDatamatrix, -y - 10 - wd, x + 10, wd, hd, null);
final int w1 = GS1Frame.this.barcodeImage.getWidth() / 4;
final int h1 = GS1Frame.this.barcodeImage.getHeight() / 4;
g.drawImage(GS1Frame.this.barcodeImage, -y + -gridHeight + 60, x + 70, w1, h1, null);
 
}
if (GS1Frame.this.barcodeImageBatch != null) {
// GS1-128 numero de lot
final int w2 = GS1Frame.this.barcodeImageBatch.getWidth() / 4;
final int h2 = GS1Frame.this.barcodeImageBatch.getHeight() / 4;
g.drawImage(GS1Frame.this.barcodeImageBatch, -y - gridHeight + 60, x + 180, w2, h2, null);
}
g2d.setTransform(defaultAt);
 
}
};
 
}
 
public JPanel createToolBar() {
final JPanel toolbar = new JPanel();
toolbar.setOpaque(false);
toolbar.setLayout(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.insets = new Insets(2, 3, 2, 2);
c.gridx = 0;
c.gridy = 0;
c.weightx = 0;
c.gridwidth = 1;
toolbar.add(new JLabel("Etiquettes par page"), c);
c.gridx++;
 
twoPerPage.setOpaque(false);
fourPerPage.setOpaque(false);
final ButtonGroup g = new ButtonGroup();
g.add(twoPerPage);
g.add(fourPerPage);
 
if (properties.getProperty("labelsPerPage", "4").equals("4")) {
fourPerPage.setSelected(true);
} else {
twoPerPage.setSelected(true);
}
 
toolbar.add(twoPerPage, c);
c.gridx++;
toolbar.add(fourPerPage, c);
c.gridx++;
checkIgnoreMargins.setOpaque(false);
checkIgnoreMargins.setSelected(properties.getProperty("ignoreMargin", "false").equals("true"));
c.gridx++;
toolbar.add(checkIgnoreMargins, c);
c.gridx++;
c.weightx = 1;
c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.NORTHEAST;
final JButton printButton = new JButton("Imprimer");
printButton.setOpaque(false);
toolbar.add(printButton, c);
c.gridx++;
 
twoPerPage.addActionListener(new ActionListener() {
 
@Override
public void actionPerformed(ActionEvent e) {
if (twoPerPage.isSelected()) {
GS1Frame.this.labelPanel.setColumnCount(1);
} else {
GS1Frame.this.labelPanel.setColumnCount(2);
}
 
}
});
 
fourPerPage.addActionListener(new ActionListener() {
 
@Override
public void actionPerformed(ActionEvent e) {
if (twoPerPage.isSelected()) {
GS1Frame.this.labelPanel.setColumnCount(1);
} else {
GS1Frame.this.labelPanel.setColumnCount(2);
}
 
}
});
 
printButton.addActionListener(new ActionListener() {
 
@Override
public void actionPerformed(ActionEvent e) {
if (!isEANValid()) {
JOptionPane.showMessageDialog(GS1Frame.this, "EAN invalide");
return;
}
 
final PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintable(GS1Frame.this.labelPanel);
boolean ok = job.printDialog();
if (ok) {
try {
job.print();
} catch (PrinterException ex) {
JOptionPane.showMessageDialog(GS1Frame.this, "Print error :" + ex.getMessage());
}
}
 
}
});
checkIgnoreMargins.addActionListener(new ActionListener() {
 
@Override
public void actionPerformed(ActionEvent e) {
GS1Frame.this.labelPanel.setIgnoreMargins(checkIgnoreMargins.isSelected());
 
}
});
return toolbar;
}
 
public JPanel createToolBar2() {
final JPanel toolbar = new JPanel();
toolbar.setOpaque(false);
toolbar.setLayout(new GridBagLayout());
final GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(2, 3, 2, 2);
c.gridx = 0;
c.gridy = 0;
c.weightx = 0;
c.gridwidth = 1;
c.fill = GridBagConstraints.NONE;
toolbar.add(new JLabel("Marge en haut (mm)"), c);
c.gridx++;
final JSpinner sLines = new JSpinner(new SpinnerNumberModel(0, 0, 200, 1));
toolbar.add(sLines, c);
c.gridx++;
toolbar.add(new JLabel("Marge à gauche (mm)"), c);
c.gridx++;
final JSpinner sColums = new JSpinner(new SpinnerNumberModel(0, 0, 200, 1));
c.weightx = 1;
toolbar.add(sColums, c);
c.gridx++;
 
sLines.addChangeListener(new ChangeListener() {
 
@Override
public void stateChanged(ChangeEvent e) {
final Number n = (Number) sLines.getValue();
if (n != null) {
GS1Frame.this.labelPanel.setTopMargin(n.intValue());
}
}
});
sColums.addChangeListener(new ChangeListener() {
 
@Override
public void stateChanged(ChangeEvent e) {
final Number n = (Number) sColums.getValue();
if (n != null) {
GS1Frame.this.labelPanel.setLeftMargin(n.intValue());
}
}
});
 
return toolbar;
}
 
public String readZPLScript() {
if (new File("zpl.txt").exists()) {
try (InputStream in = new FileInputStream(new File("zpl.txt"))) {
return readAscii(in);
} catch (Exception e) {
e.printStackTrace();
}
} else {
try (InputStream in = GS1Frame.class.getResourceAsStream("default-zpl.txt")) {
return readAscii(in);
} catch (Exception e) {
e.printStackTrace();
}
 
}
return "";
}
 
protected String createZPLCode() {
final String script = readZPLScript();
final BufferedReader reader = new BufferedReader(new StringReader(script));
final StringBuilder builder = new StringBuilder();
final GS1Util u = new GS1Util();
try {
String line = reader.readLine();
while (line != null) {
if (line.contains("XXXXXXXXXX")) {
if (!this.textSupplier.getText().isEmpty()) {
line = line.replace("XXXXXXXXXX", this.textSupplier.getText());
builder.append(line);
builder.append("\r\n");
}
} else if (line.contains("YYYYYYYYYY")) {
line = line.replace("YYYYYYYYYY", this.textName.getText());
builder.append(line);
builder.append("\r\n");
} else if (line.contains("(02)7612345678900(15)201218(37)9999")) {
line = line.replace("(02)7612345678900(15)201218(37)9999", getGS128().formatHumanReadable());
builder.append(line);
builder.append("\r\n");
 
} else if (line.contains("(10)LLLLLLLLL")) {
GS1AIElements batch = getGS128Batch();
if (!batch.isEmpty()) {
line = line.replace("(10)LLLLLLLLL", batch.formatHumanReadable());
builder.append(line);
builder.append("\r\n");
}
} else if (line.contains("OPENCONCERTO")) {
GS1AIElements all = getGS1();
line = line.replace("OPENCONCERTO", u.formatZPL(all));
builder.append(line);
builder.append("\r\n");
 
} else {
builder.append(line);
builder.append("\r\n");
}
 
line = reader.readLine();
}
} catch (Exception e) {
e.printStackTrace();
}
 
return builder.toString();
}
 
private GS1AIElements getGS1() {
final GS1AIElements gs1 = new GS1AIElements();
final String ean = this.textEAN.getText().trim();
if (!ean.isEmpty()) {
if (ean.length() == 13 || (ean.length() == 14 && ean.charAt(0) == '0')) {
if (ean.length() == 13) {
gs1.put("02", '0' + ean);
} else {
gs1.put("02", ean);
}
} else {
gs1.put("01", ean);
}
}
 
if (this.dateDLUODLC.getValue() != null) {
String date = this.dfDate.format(this.dateDLUODLC.getValue());
if (this.b1.isSelected()) {
gs1.put("15", date);
} else {
gs1.put("17", date);
}
}
 
final String nbProducts = this.textNumberOfProducts.getText();
if (!nbProducts.trim().isEmpty()) {
int n = Integer.parseInt(this.textNumberOfProducts.getText());
gs1.put("37", String.valueOf(n));
 
}
final String batch = this.textBatch.getText().trim();
if (!batch.isEmpty()) {
gs1.put("10", batch);
}
 
return gs1;
}
 
private GS1AIElements getGS128Batch() {
final GS1AIElements gs1 = new GS1AIElements();
final String batch = this.textBatch.getText().trim();
if (!batch.isEmpty()) {
gs1.put("10", batch);
}
return gs1;
}
 
private GS1AIElements getGS128() {
final GS1AIElements gs1 = getGS1();
gs1.remove("10");
return gs1;
}
 
public String readAscii(final InputStream in) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final byte[] buf = new byte[8192];
int length;
while ((length = in.read(buf)) > 0) {
out.write(buf, 0, length);
}
out.flush();
out.close();
return new String(out.toByteArray(), StandardCharsets.US_ASCII);
}
 
static List<Image> frameIcon;
 
public static synchronized List<Image> getFrameIcon() {
if (frameIcon == null) {
frameIcon = new ArrayList<>();
final int[] sizes = { 16, 32, 48, 96 };
for (int i = 0; i < sizes.length; i++) {
int v = sizes[i];
try {
frameIcon.add(new ImageIcon(GS1Frame.class.getResource(v + ".png")).getImage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
return frameIcon;
}
 
public static void main(String[] args) {
 
if (!new File("zpl.txt").exists()) {
final InputStream in = GS1Frame.class.getResourceAsStream("default-zpl.txt");
try (FileOutputStream fOut = new FileOutputStream(new File("zpl.txt"))) {
byte[] buf = new byte[8192];
int length;
while ((length = in.read(buf)) > 0) {
fOut.write(buf, 0, length);
}
fOut.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
SwingUtilities.invokeLater(new Runnable() {
 
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e1) {
e1.printStackTrace();
}
 
final GS1Frame f = new GS1Frame();
f.setIconImages(getFrameIcon());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
f.setLocationRelativeTo(null);
f.setResizable(false);
 
}
});
 
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/filter/LimitDocumentFilter.java
New file
0,0 → 1,30
package org.openconcerto.modules.label.filter;
 
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
 
public class LimitDocumentFilter extends DocumentFilter {
 
private int limit;
 
public LimitDocumentFilter(int limit) {
if (limit <= 0) {
throw new IllegalArgumentException("Limit can not be <= 0");
}
this.limit = limit;
}
 
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
int currentLength = fb.getDocument().getLength();
int overLimit = (currentLength + text.length()) - limit - length;
if (overLimit > 0) {
text = text.substring(0, text.length() - overLimit);
}
if (text.length() > 0) {
super.replace(fb, offset, length, text, attrs);
}
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/filter/NumberOfProductDocumentFilter.java
New file
0,0 → 1,47
package org.openconcerto.modules.label.filter;
 
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
 
public class NumberOfProductDocumentFilter extends DocumentFilter {
private boolean isValid(String testText) {
if (testText.length() > 8) {
return false;
}
if (testText.isEmpty()) {
return true;
}
int intValue = 0;
try {
intValue = Integer.parseInt(testText.trim());
} catch (NumberFormatException e) {
return false;
}
if (intValue < 1 || intValue > 99999999) {
return false;
}
return true;
}
 
@Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
StringBuilder sb = new StringBuilder();
sb.append(fb.getDocument().getText(0, fb.getDocument().getLength()));
sb.insert(offset, text);
if (isValid(sb.toString())) {
super.insertString(fb, offset, text, attr);
}
}
 
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
StringBuilder sb = new StringBuilder();
sb.append(fb.getDocument().getText(0, fb.getDocument().getLength()));
int end = offset + length;
sb.replace(offset, end, text);
if (isValid(sb.toString())) {
super.replace(fb, offset, length, text, attrs);
}
}
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/filter/BatchDocumentFilter.java
New file
0,0 → 1,23
package org.openconcerto.modules.label.filter;
 
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
 
import org.openconcerto.modules.label.ISO646;
 
public class BatchDocumentFilter extends LimitDocumentFilter {
 
public BatchDocumentFilter() {
super(20);
}
 
@Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
super.insertString(fb, offset, ISO646.clean(text), attr);
}
 
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
super.replace(fb, offset, length, ISO646.clean(text), attrs);
}
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/filter/EANDocumentFilter.java
New file
0,0 → 1,36
package org.openconcerto.modules.label.filter;
 
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
 
public class EANDocumentFilter extends LimitDocumentFilter {
 
public EANDocumentFilter() {
super(14);
}
 
@Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
super.insertString(fb, offset, clean(text), attr);
}
 
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
super.replace(fb, offset, length, clean(text), attrs);
}
 
public static String clean(String s) {
final int length = s.length();
final StringBuilder b = new StringBuilder(length);
for (int i = 0; i < length; i++) {
final char charAt = s.charAt(i);
if (Character.isDigit(charAt)) {
b.append(s.charAt(i));
} else {
b.append('0');
}
}
return b.toString();
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/filter/ISO646DocumentFilter.java
New file
0,0 → 1,19
package org.openconcerto.modules.label.filter;
 
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
 
import org.openconcerto.modules.label.ISO646;
 
public class ISO646DocumentFilter extends DocumentFilter {
@Override
public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException {
super.insertString(fb, offset, ISO646.clean(text), attr);
}
 
@Override
public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
super.replace(fb, offset, length, ISO646.clean(text), attrs);
}
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/LabelRenderer.java
2,8 → 2,6
 
import java.awt.Graphics;
 
import org.openconcerto.sql.model.SQLRowAccessor;
 
public interface LabelRenderer {
public void paintLabel(Graphics g, SQLRowAccessor row, int x, int y, int gridWith, int gridHeight, float fontSize);
public void paintLabel(Graphics g, Label label, int x, int y, int gridWith, int gridHeight, float fontSize);
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/gs1/GS1Util.java
New file
0,0 → 1,243
package org.openconcerto.modules.label.gs1;
 
import org.openconcerto.modules.label.ISO646;
 
public class GS1Util {
 
// ISO/IEC 15424 symbology identifiers
// ]E0 : EAN-13, UPC-A, or UPC-E : 13 digits
// ]E1 : Two-digit add-on symbol : 2 digits
// ]E2 : Five-digit add-on symbol : 5 digits
// ]E3 : EAN-13, UPC-A, or UPC-E with add-on symbol : 15 or 18 digits
// ]E4 : EAN-8 : 8 digits
// ]I1 : ITF-14 : 14 digits
// ]C1 : GS1-128 : Standard AI element strings
private static final String GS1_128_SCANNER_PREFIX = "]C1";
// ]e0 : GS1 DataBar : Standard AI element strings
private static final String GS1_DATABAR_SCANNER_PREFIX = "]e0";
// ]e1 : GS1 Composite : Data packet containing the data + an encoded symbol separator
// character.
// ]e2 : GS1 Composite : Data packet containing the data following an escape mechanism
// character.
// ]d2 : GS1 DataMatrix : Standard AI element strings
private static final String GS1_DATAMATRIX_SCANNER_PREFIX = "]d2";
// ]Q3 : GS1 QR Code : Standard AI element strings
private static final String GS1_QRCODE_SCANNER_PREFIX = "]Q3";
// ]J1 : GS1 DotCode : Standard AI element strings
 
static final int ERROR_CODE_INVALID_GS1_SCAN = 0;
static final int ERROR_CODE_UNKNOWN_AI = 1;
static final int ERROR_CODE_INCOMPLETE_AI = 2;
static final int ERROR_CODE_NOT_FOUND_SEPARATOR = 3;
static final int ERROR_CODE_INSUFFICIENT_VALUE_LENGTH = 4;
static final int ERROR_CODE_EXCEEDED_VALUE_LENGTH = 5;
static final int ERROR_CODE_CONVERT_DECIMAL_POINT = 5;
static final int ERROR_CODE_WRONG_DECIMAL_POINT = 6;
static final int ERROR_CODE_CONVERT_DECIMAL_VALUE = 7;
 
public static final char GS_SEPARATOR = '\u001D';
// public static final char FNC1_SEPARATOR = 232;
 
static final int SPACE_SEPARATOR = ' ';
 
int separator;
 
public GS1Util() {
this(GS_SEPARATOR);
}
 
public GS1Util(int separator) {
this.separator = separator;
}
 
public GS1AIElements parseFromScanner(String scan) throws GS1ParseException {
if (scan.startsWith(GS1_DATAMATRIX_SCANNER_PREFIX) || scan.startsWith(GS1_128_SCANNER_PREFIX) || scan.startsWith(GS1_DATABAR_SCANNER_PREFIX) || scan.startsWith(GS1_QRCODE_SCANNER_PREFIX)) {
return parse(scan.substring(3));
}
return parse(scan);
}
 
public GS1AIElements parse(String barcode) throws GS1ParseException {
if (barcode.length() < 3) {
throw new GS1ParseException("", ERROR_CODE_INVALID_GS1_SCAN, "code too short");
}
 
System.err.println("GS1Util.parse()" + barcode);
GS1AIElements attr = new GS1AIElements();
StringBuilder ai = new StringBuilder();
 
int length = barcode.length();
for (int i = 0; i < length; ++i) {
int aiLength = ai.length();
if (aiLength > 1) {
GS1ApplicationIdentifier aii = GS1AIElements.getApplicationIdentifier(ai.toString());
if (aii == null) {
if (aiLength < 4)
ai.append(barcode.charAt(i));
else
throw new GS1ParseException(ai.toString(), ERROR_CODE_UNKNOWN_AI, "Unknown AI");
} else {
int decimalPoint = 0;
if (aii.decimalPoint) {
try {
decimalPoint = Integer.valueOf(String.valueOf(barcode.charAt(i)));
} catch (NumberFormatException e) {
throw new GS1ParseException(ai.toString(), ERROR_CODE_CONVERT_DECIMAL_POINT, "Errow convert to decimal point");
}
 
if (++i >= length)
throw new GS1ParseException(ai.toString(), ERROR_CODE_INSUFFICIENT_VALUE_LENGTH, "Insufficient value length");
}
 
String value;
 
if (aii.variableLength) {
int separatorIndex = barcode.indexOf(this.separator, i);
 
if (separatorIndex < 0) {
if (length - i > aii.length)
throw new GS1ParseException(ai.toString(), ERROR_CODE_NOT_FOUND_SEPARATOR, "Not found separator");
else if (length - i < aii.minLength)
throw new GS1ParseException(ai.toString(), ERROR_CODE_INSUFFICIENT_VALUE_LENGTH, "Insufficient value length");
else {
value = barcode.substring(i);
i = length;
}
} else if (separatorIndex - i > aii.length)
throw new GS1ParseException(ai.toString(), ERROR_CODE_EXCEEDED_VALUE_LENGTH, "Exceeded value length");
else if (separatorIndex - i < aii.minLength)
throw new GS1ParseException(ai.toString(), ERROR_CODE_INSUFFICIENT_VALUE_LENGTH, "Insufficient value length");
else {
value = barcode.substring(i, separatorIndex);
i = separatorIndex;
}
} else {
if (i + aii.length > length) {
throw new GS1ParseException(ai.toString(), ERROR_CODE_INSUFFICIENT_VALUE_LENGTH, "Insufficient value length");
}
value = barcode.substring(i, i + aii.length);
i += aii.length - 1;
}
 
if (aii.decimalPoint && decimalPoint > 0) {
if (decimalPoint >= value.length())
throw new GS1ParseException(ai.toString(), ERROR_CODE_WRONG_DECIMAL_POINT, "Decimal point more then value length");
 
try {
value = String.valueOf(Double.valueOf(value.substring(0, value.length() - decimalPoint) + "." + value.substring(value.length() - decimalPoint)));
} catch (NumberFormatException e) {
throw new GS1ParseException(ai.toString(), ERROR_CODE_CONVERT_DECIMAL_VALUE, "Error convert decimal point value");
}
}
 
attr.put(ai.toString(), value);
 
ai.setLength(0);
}
} else
ai.append(barcode.charAt(i));
}
 
if (ai.length() > 0)
throw new GS1ParseException(ai.toString(), ERROR_CODE_INCOMPLETE_AI, "Incomplete AI");
 
return attr;
}
 
public String format(GS1AIElements values) {
 
StringBuilder b = new StringBuilder();
int size = values.size();
for (int i = 0; i < size; i++) {
String k = values.getKey(i);
GS1ApplicationIdentifier ai = GS1AIElements.getApplicationIdentifier(k);
final String value = values.getValue(i);
b.append(k);
b.append(value);
if (ai.variableLength && i < size - 1) {
b.append((char) this.separator);
}
}
return b.toString();
}
 
public String formatZPL(GS1AIElements values) {
 
StringBuilder b = new StringBuilder();
int size = values.size();
b.append("@1");
for (int i = 0; i < size; i++) {
String k = values.getKey(i);
GS1ApplicationIdentifier ai = GS1AIElements.getApplicationIdentifier(k);
final String value = values.getValue(i);
b.append(k);
b.append(value);
if (ai.variableLength && i < size - 1) {
b.append("@d029");
}
}
return b.toString();
}
 
public String formatDataMatrix(GS1AIElements values) {
 
StringBuilder b = new StringBuilder();
int size = values.size();
// b.append(FNC1_SEPARATOR);
for (int i = 0; i < size; i++) {
String k = values.getKey(i);
GS1ApplicationIdentifier ai = GS1AIElements.getApplicationIdentifier(k);
final String value = values.getValue(i);
b.append('[');
b.append(k);
b.append(']');
b.append(value);
if (ai.variableLength && i < size - 1) {
// b.append(GS_SEPARATOR);
}
}
return b.toString();
}
 
public static void main(String[] args) throws GS1ParseException {
GS1Util p = new GS1Util('_');
System.out.println("GS1Util.main()" + (char) p.separator);
String barcode = "0104607018700852111806051718062910180605_211";
// barcode = "01088888931021461712031510W1040190";
GS1AIElements values = p.parse(barcode);
values.dump(System.out);
 
values.put("391", "kookkk");
System.out.println(p.format(values));
System.out.println(values.formatHumanReadable());
 
GS1AIElements valuesMax = new GS1AIElements();
valuesMax.put("02", "01234567891234");
valuesMax.put("15", "201202");
valuesMax.put("37", "12345678");
valuesMax.put("10", "12345678901234567890");
GS1Util pStd = new GS1Util();
System.out.println(p.format(valuesMax));
System.out.println(p.format(valuesMax).length());
System.out.println(pStd.format(valuesMax));
System.out.println(pStd.format(valuesMax).length());
System.err.println("GS1Util.main() GS128 from barcode reader");
String gs1128 = "]C10207612345678900152012153745646578";
GS1Util p2 = new GS1Util();
values = p2.parseFromScanner(gs1128);
values.dump(System.out);
}
 
public static String showAllChars(String s) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (ISO646.isValid(s.charAt(i))) {
b.append(s.charAt(i));
} else {
b.append("[" + (int) s.charAt(i) + "]");
}
}
return b.toString();
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/gs1/GS1AIElements.java
New file
0,0 → 1,259
package org.openconcerto.modules.label.gs1;
 
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
 
public class GS1AIElements {
static final HashMap<String, GS1ApplicationIdentifier> GS1_128_AI = new HashMap<>();
static {
GS1_128_AI.put("00", new GS1ApplicationIdentifier(0, 18, false, false));
GS1_128_AI.put("01", new GS1ApplicationIdentifier(0, 14, false, false));
GS1_128_AI.put("02", new GS1ApplicationIdentifier(0, 14, false, false));
GS1_128_AI.put("10", new GS1ApplicationIdentifier(0, 20, true, false));
GS1_128_AI.put("11", new GS1ApplicationIdentifier(0, 6, false, false));
GS1_128_AI.put("12", new GS1ApplicationIdentifier(0, 6, false, false));
GS1_128_AI.put("13", new GS1ApplicationIdentifier(0, 6, false, false));
GS1_128_AI.put("14", new GS1ApplicationIdentifier(0, 6, false, false));
GS1_128_AI.put("15", new GS1ApplicationIdentifier(0, 6, false, false));
GS1_128_AI.put("17", new GS1ApplicationIdentifier(0, 6, false, false));
GS1_128_AI.put("20", new GS1ApplicationIdentifier(0, 2, false, false));
GS1_128_AI.put("21", new GS1ApplicationIdentifier(0, 20, true, false));
GS1_128_AI.put("240", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("241", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("242", new GS1ApplicationIdentifier(0, 6, true, false));
GS1_128_AI.put("250", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("251", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("253", new GS1ApplicationIdentifier(13, 30, true, false));
GS1_128_AI.put("254", new GS1ApplicationIdentifier(0, 20, true, false));
GS1_128_AI.put("255", new GS1ApplicationIdentifier(13, 25, true, false));
GS1_128_AI.put("30", new GS1ApplicationIdentifier(0, 8, true, false));
GS1_128_AI.put("310", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("311", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("312", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("313", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("314", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("315", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("316", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("320", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("321", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("322", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("323", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("324", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("325", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("326", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("327", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("328", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("329", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("330", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("331", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("332", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("333", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("334", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("335", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("336", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("340", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("341", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("342", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("343", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("344", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("345", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("346", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("347", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("348", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("349", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("350", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("351", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("352", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("353", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("354", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("355", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("356", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("357", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("360", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("361", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("362", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("363", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("364", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("365", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("366", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("367", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("368", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("369", new GS1ApplicationIdentifier(0, 6, false, true));
GS1_128_AI.put("37", new GS1ApplicationIdentifier(0, 8, true, false));
GS1_128_AI.put("390", new GS1ApplicationIdentifier(0, 15, true, true));
GS1_128_AI.put("391", new GS1ApplicationIdentifier(3, 18, true, true));
GS1_128_AI.put("392", new GS1ApplicationIdentifier(0, 15, true, true));
GS1_128_AI.put("393", new GS1ApplicationIdentifier(3, 18, true, true));
GS1_128_AI.put("400", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("401", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("402", new GS1ApplicationIdentifier(0, 17, false, false));
GS1_128_AI.put("403", new GS1ApplicationIdentifier(3, 30, true, false));
GS1_128_AI.put("410", new GS1ApplicationIdentifier(0, 17, false, false));
GS1_128_AI.put("411", new GS1ApplicationIdentifier(0, 17, false, false));
GS1_128_AI.put("412", new GS1ApplicationIdentifier(0, 17, false, false));
GS1_128_AI.put("413", new GS1ApplicationIdentifier(0, 17, false, false));
GS1_128_AI.put("414", new GS1ApplicationIdentifier(0, 17, false, false));
GS1_128_AI.put("420", new GS1ApplicationIdentifier(0, 20, true, false));
GS1_128_AI.put("421", new GS1ApplicationIdentifier(0, 0, true, false));
GS1_128_AI.put("422", new GS1ApplicationIdentifier(0, 3, false, false));
GS1_128_AI.put("423", new GS1ApplicationIdentifier(3, 15, true, false));
GS1_128_AI.put("424", new GS1ApplicationIdentifier(0, 3, false, false));
GS1_128_AI.put("425", new GS1ApplicationIdentifier(0, 3, false, false));
GS1_128_AI.put("426", new GS1ApplicationIdentifier(0, 3, false, false));
GS1_128_AI.put("7001", new GS1ApplicationIdentifier(0, 13, false, false));
GS1_128_AI.put("7002", new GS1ApplicationIdentifier(0, 30, false, false));
GS1_128_AI.put("7003", new GS1ApplicationIdentifier(0, 10, false, false));
GS1_128_AI.put("7004", new GS1ApplicationIdentifier(0, 4, true, false));
GS1_128_AI.put("8001", new GS1ApplicationIdentifier(0, 14, false, false));
GS1_128_AI.put("8002", new GS1ApplicationIdentifier(0, 20, true, false));
GS1_128_AI.put("8003", new GS1ApplicationIdentifier(14, 30, true, false));
GS1_128_AI.put("8004", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("8005", new GS1ApplicationIdentifier(0, 6, false, false));
GS1_128_AI.put("8006", new GS1ApplicationIdentifier(0, 18, false, false));
GS1_128_AI.put("8007", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("8008", new GS1ApplicationIdentifier(8, 12, true, false));
GS1_128_AI.put("8018", new GS1ApplicationIdentifier(0, 18, false, false));
GS1_128_AI.put("8020", new GS1ApplicationIdentifier(0, 25, true, false));
GS1_128_AI.put("8100", new GS1ApplicationIdentifier(0, 6, false, false));
GS1_128_AI.put("8101", new GS1ApplicationIdentifier(0, 10, false, false));
GS1_128_AI.put("8102", new GS1ApplicationIdentifier(0, 2, false, false));
GS1_128_AI.put("8110", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("8200", new GS1ApplicationIdentifier(0, 70, true, false));
GS1_128_AI.put("90", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("91", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("92", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("93", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("94", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("95", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("96", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("97", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("98", new GS1ApplicationIdentifier(0, 30, true, false));
GS1_128_AI.put("99", new GS1ApplicationIdentifier(0, 30, true, false));
}
 
private final ArrayList<String> keysAndValues = new ArrayList<>();
 
public GS1AIElements() {
 
}
 
public boolean isEmpty() {
return this.keysAndValues.isEmpty();
}
 
public boolean containsKey(String key) {
final int size = this.keysAndValues.size();
for (int i = 0; i < size; i += 2) {
if (this.keysAndValues.get(i).equals(key)) {
return true;
}
}
return false;
}
 
public boolean containsValue(String value) {
final int size = this.keysAndValues.size();
for (int i = 1; i < size; i += 2) {
if (this.keysAndValues.get(i).equals(value)) {
return true;
}
}
return false;
}
 
public String getKey(int index) {
return this.keysAndValues.get(index * 2);
}
 
public String getValue(int index) {
return this.keysAndValues.get(1 + index * 2);
}
 
public String get(String key) {
final int size = this.keysAndValues.size();
for (int i = 0; i < size; i += 2) {
if (this.keysAndValues.get(i).equals(key)) {
 
return this.keysAndValues.get(i + 1);
}
}
return null;
}
 
public String put(String key, String value) {
GS1ApplicationIdentifier ai = GS1_128_AI.get(key);
if (ai == null) {
throw new IllegalArgumentException("AI " + key + " unknown");
}
if (ai.variableLength) {
if (value.length() < ai.minLength) {
throw new IllegalArgumentException("AI " + key + " value length must >= " + ai.minLength + " but is " + value.length() + " for value " + value);
}
if (value.length() > ai.length) {
throw new IllegalArgumentException("AI " + key + " value length must be <= " + ai.length + " but is " + value.length() + " for value " + value);
}
} else {
if (value.length() != ai.length) {
throw new IllegalArgumentException("AI " + key + " value length must be " + ai.length + " but is " + value.length() + " for value " + value);
}
}
 
final int size = this.keysAndValues.size();
for (int i = 0; i < size; i += 2) {
if (this.keysAndValues.get(i).equals(key)) {
final String old = this.keysAndValues.get(i + 1);
this.keysAndValues.set(i + 1, value);
return old;
}
}
this.keysAndValues.add(key);
this.keysAndValues.add(value);
return null;
}
 
public String remove(String key) {
final int size = this.keysAndValues.size();
for (int i = 0; i < size; i += 2) {
if (this.keysAndValues.get(i).equals(key)) {
this.keysAndValues.remove(i);
return this.keysAndValues.remove(i);
}
}
return null;
}
 
public void clear() {
this.keysAndValues.clear();
}
 
public int size() {
return this.keysAndValues.size() / 2;
}
 
public void dump(PrintStream out) {
for (int i = 0; i < size(); i++) {
out.print("(");
out.print(getKey(i));
out.print(")");
out.println(getValue(i));
}
out.flush();
}
 
public static GS1ApplicationIdentifier getApplicationIdentifier(String k) {
return GS1_128_AI.get(k);
}
 
public String formatHumanReadable() {
StringBuilder b = new StringBuilder();
int size = size();
for (int i = 0; i < size; i++) {
b.append('(');
b.append(this.getKey(i));
b.append(')');
b.append(this.getValue(i));
}
return b.toString();
}
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/gs1/GS1ApplicationIdentifier.java
New file
0,0 → 1,17
package org.openconcerto.modules.label.gs1;
 
public class GS1ApplicationIdentifier {
 
public final int minLength;
public final int length;
public final boolean variableLength;
public final boolean decimalPoint;
 
public GS1ApplicationIdentifier(int minLength, int length, boolean variableLength, boolean decimalPoint) {
this.minLength = minLength;
this.length = length;
this.variableLength = variableLength;
this.decimalPoint = decimalPoint;
}
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/gs1/GS1ParseException.java
New file
0,0 → 1,18
package org.openconcerto.modules.label.gs1;
 
public class GS1ParseException extends Exception {
final String ai;
final int errorCode;
final String errorMessage;
 
public GS1ParseException(final String ai, final int errorCode, final String errorMessage) {
this.ai = ai;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
 
@Override
public String toString() {
return "AI: " + ai + ", errorCode: " + errorCode + ", errorMessage: " + errorMessage;
}
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/graphicspl/GPLRenderer.java
New file
0,0 → 1,166
package org.openconcerto.modules.label.graphicspl;
 
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
 
import javax.imageio.ImageIO;
 
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
 
public abstract class GPLRenderer {
public static final int ALIGN_LEFT = 0;
public static final int ALIGN_RIGHT = 1;
public static final int ALIGN_CENTER = 2;
 
public static final int BARCODE_EAN8 = 0;
public static final int BARCODE_EAN13 = 1;
public static final int BARCODE_CODE128 = 2;
public static final int BARCODE_CODE128_GS1 = 3;
public static final int BARCODE_DATAMATRIX = 4;
public static final int BARCODE_QRCODE = 5;
private final float ratio;
 
public GPLRenderer(float ratio) {
this.ratio = ratio;
}
 
public GPLRenderer() {
this.ratio = 1.0f;
}
 
public float getRatio() {
return ratio;
}
 
public void render(GraphicsPL graphicsPL) throws IOException {
 
Document doc = graphicsPL.getDocument();
final int width = Integer.parseInt(doc.getDocumentElement().getAttribute("width"));
NodeList nodeList = doc.getFirstChild().getChildNodes();
int size = nodeList.getLength();
for (int i = 0; i < size; i++) {
 
if (nodeList.item(i).getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) nodeList.item(i);
String name = element.getNodeName();
if (name.equals("text")) {
int x = Math.round(ratio * Integer.parseInt(element.getAttribute("x")));
int y = Math.round(ratio * Integer.parseInt(element.getAttribute("y")));
String txt = element.getTextContent();
Color color = Color.BLACK;
if (element.hasAttribute("color")) {
color = Color.decode(element.getAttribute("color"));
}
int fontSize = Math.round(ratio * Integer.parseInt(element.getAttribute("fontsize")));
String fontName = element.getAttribute("font");
int maxWidth = width - x;
boolean wrap = false;
int align = ALIGN_LEFT;
if (element.hasAttribute("align")) {
if (element.getAttribute("align").equals("right")) {
align = ALIGN_RIGHT;
} else if (element.getAttribute("align").equals("center")) {
align = ALIGN_CENTER;
}
}
if (!element.hasAttribute("visible") || element.getAttribute("visible").equals("true")) {
drawText(x, y, txt, align, fontName, fontSize, color, maxWidth, wrap);
}
 
} else if (name.equals("rectangle")) {
int x = Math.round(ratio * Integer.parseInt(element.getAttribute("x")));
int y = Math.round(ratio * Integer.parseInt(element.getAttribute("y")));
int w = Math.round(ratio * Integer.parseInt(element.getAttribute("width")));
int h = Math.round(ratio * Integer.parseInt(element.getAttribute("height")));
Color color = Color.BLACK;
if (element.hasAttribute("color")) {
color = Color.decode(element.getAttribute("color"));
}
 
if (element.hasAttribute("fill") && element.getAttribute("fill").equals("true")) {
fillRectangle(x, y, w, h, color);
} else {
int lineWidth = Math.round(ratio);
if (element.hasAttribute("linewidth")) {
lineWidth = Math.round(ratio * Integer.parseInt(element.getAttribute("linewidth")));
}
drawRectangle(x, y, w, h, color, lineWidth);
}
 
} else if (name.equals("image")) {
String fileName = element.getAttribute("file");
int x = Math.round(ratio * Integer.parseInt(element.getAttribute("x")));
int y = Math.round(ratio * Integer.parseInt(element.getAttribute("y")));
BufferedImage img = ImageIO.read(new File(graphicsPL.getImageDir(), fileName));
int w = Math.round(ratio * img.getWidth());
int h = Math.round(ratio * img.getHeight());
if (element.hasAttribute("width")) {
w = Math.round(ratio * Integer.parseInt(element.getAttribute("width")));
}
if (element.hasAttribute("height")) {
h = Math.round(ratio * Integer.parseInt(element.getAttribute("height")));
}
drawImage(x, y, w, h, img);
 
} else if (name.equals("barcode")) {
int x = Math.round(ratio * Integer.parseInt(element.getAttribute("x")));
int y = Math.round(ratio * Integer.parseInt(element.getAttribute("y")));
int h = 0;
 
if (element.hasAttribute("height")) {
h = Math.round(ratio * Integer.parseInt(element.getAttribute("height")));
}
String type = element.getAttribute("type");
String code = element.getTextContent();
int t;
if (type.equals("ean8")) {
t = BARCODE_EAN8;
} else if (type.equals("ean13")) {
t = BARCODE_EAN13;
} else if (type.equals("ean128")) {
t = BARCODE_CODE128;
} else if (type.equals("gs1")) {
t = BARCODE_CODE128_GS1;
} else if (type.equals("datamatrix")) {
t = BARCODE_DATAMATRIX;
if (h != 0) {
System.err.println("ignoring datamatrix height attribute");
}
} else if (type.equals("qrcode")) {
t = BARCODE_QRCODE;
} else {
throw new IllegalArgumentException("unsupported barcode type : " + type);
}
 
int fontSize = Math.round(ratio * 8);
if (element.hasAttribute("fontsize")) {
fontSize = Math.round(ratio * Integer.parseInt(element.getAttribute("fontsize")));
}
int moduleWidth = Math.round(ratio);
if (element.hasAttribute("modulewidth")) {
moduleWidth = Math.round(ratio * Integer.parseInt(element.getAttribute("modulewidth")));
}
drawBarcode(x, y, h, t, code, moduleWidth, fontSize);
} else {
throw new IllegalStateException("unsupported primitive : " + name);
}
}
}
}
 
public abstract void drawText(int x, int y, String text, int align, String fontName, int fontSize, Color color, int maxWidth, boolean wrap);
 
public abstract void drawImage(int x, int y, int w, int h, BufferedImage img);
 
public abstract void fillRectangle(int x, int y, int w, int h, Color color);
 
public abstract void drawRectangle(int x, int y, int w, int h, Color color, int lineWidth);
 
public abstract void drawBarcode(int x, int y, int h, int type, String code, int moduleWidth, int fontSize);
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/graphicspl/GraphicsPL.java
New file
0,0 → 1,128
package org.openconcerto.modules.label.graphicspl;
 
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
 
import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
 
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
 
public class GraphicsPL {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><graphicspl height=\"400\" width=\"600\"/>";
Document doc;
private File imgDir;
 
public static void main(String[] args) throws Exception {
GraphicsPL g = new GraphicsPL();
g.load(new File("Template/Labels/test.graphicspl"));
BufferedImage img = g.createImage(10);
ImageIO.write(img, "png", new File("gpl.png"));
String zpl = g.getZPL();
System.out.println(zpl);
Printable p = g.createPrintable();
PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintable(p);
boolean ok = job.printDialog();
if (ok) {
job.print();
}
 
}
 
public Printable createPrintable() {
final Element root = this.doc.getDocumentElement();
int dpi = 300;
if (root.hasAttribute("dpi")) {
dpi = Integer.parseInt(root.getAttribute("dpi"));
}
float printRatio = 1f;
if (root.hasAttribute("printratio")) {
printRatio = Float.parseFloat(root.getAttribute("printratio"));
}
return createPrintable(dpi, printRatio);
}
 
public Printable createPrintable(int dpi, float printRatio) {
return new Printable() {
 
@Override
public int print(Graphics graphics, PageFormat pf, int pageIndex) throws PrinterException {
if (pageIndex > 0) {
return NO_SUCH_PAGE;
}
final Element root = GraphicsPL.this.doc.getDocumentElement();
final int width = Math.round(printRatio * Integer.parseInt(root.getAttribute("width")));
final int height = Math.round(printRatio * Integer.parseInt(root.getAttribute("height")));
final Graphics2D g2d = (Graphics2D) graphics;
float ratio = (printRatio * dpi) / 72f;
try {
final BufferedImage img = createImage(ratio);
g2d.drawImage(img, (int) Math.round(pf.getImageableX()), (int) Math.round(pf.getImageableY()), width, height, null);
} catch (ParserConfigurationException | SAXException | IOException e) {
throw new PrinterException(e.getMessage());
}
return PAGE_EXISTS;
}
};
}
 
private String getZPL() throws IOException {
final ZPLRenderer renderer = new ZPLRenderer();
renderer.render(this);
return renderer.getZPL();
}
 
private BufferedImage createImage(float ratio) throws ParserConfigurationException, SAXException, IOException {
final Element root = this.doc.getDocumentElement();
final int width = Math.round(ratio * Integer.parseInt(root.getAttribute("width")));
final int height = Math.round(ratio * Integer.parseInt(root.getAttribute("height")));
final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
final Graphics2D graphics = (Graphics2D) img.getGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
final Graphics2DRenderer renderer = new Graphics2DRenderer(graphics, ratio);
renderer.render(this);
graphics.dispose();
return img;
}
 
public Document getDocument() {
return this.doc;
}
 
private void load(File file) throws ParserConfigurationException, SAXException, IOException {
this.xml = new String(Files.readAllBytes(file.toPath()));
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
final DocumentBuilder builder = factory.newDocumentBuilder();
final ByteArrayInputStream input = new ByteArrayInputStream(this.xml.getBytes(StandardCharsets.UTF_8));
this.doc = builder.parse(input);
this.doc.getDocumentElement().normalize();
this.imgDir = file.getParentFile();
}
 
public File getImageDir() {
return this.imgDir;
}
 
public void setImageDir(File dir) {
this.imgDir = dir;
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/graphicspl/ZPLRenderer.java
New file
0,0 → 1,234
package org.openconcerto.modules.label.graphicspl;
 
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
 
import org.openconcerto.utils.ImageUtils;
import org.openconcerto.utils.StringUtils;
 
public class ZPLRenderer extends GPLRenderer {
 
private StringBuilder sb = new StringBuilder();
 
public ZPLRenderer() {
sb.append("^XA\n^CI28\n");
}
 
@Override
public void drawText(int x, int y, String text, int align, String fontName, int fontSize, Color color, int maxWidth, boolean wrap) {
sb.append("^CF0,");
sb.append(fontSize);
//
 
if (align == ALIGN_RIGHT) {
sb.append("^FO");
sb.append('0');
sb.append(',');
sb.append(y);
sb.append("^FB");
sb.append(x);
sb.append(",9999,0");
 
sb.append(",R,0");
} else if (align == ALIGN_CENTER) {
sb.append("^FO");
sb.append('0');
sb.append(',');
sb.append(y);
sb.append("^FB");
sb.append(x * 2);
sb.append(",9999,0");
 
sb.append(",C,0");
} else {
sb.append("^FO");
sb.append(x);
sb.append(',');
sb.append(y);
 
//
sb.append("^FB");
sb.append(maxWidth);
sb.append(",9999,0");
 
sb.append(",L,0");
}
//
 
sb.append("^FD");
sb.append(text);
if (align == ALIGN_CENTER) {
sb.append("\\&");
}
 
sb.append("^FS\n");
}
 
@Override
public void drawImage(int x, int y, int w, int h, BufferedImage img) {
try {
if (w != img.getWidth() || h != img.getHeight()) {
img = ImageUtils.createQualityResizedImage(img, w, h);
}
 
final int bytesPerRow = (img.getWidth() + 7) / 8;
int size = bytesPerRow * img.getHeight();
byte[] data = getData(img);
sb.append("^FO");
sb.append(x);
sb.append(",");
sb.append(y);
sb.append("^GFA");
sb.append(",");
sb.append(size);
sb.append(",");
sb.append(size);
sb.append(",");
sb.append(bytesPerRow);
sb.append(",");
sb.append(StringUtils.bytesToHexString(data));
sb.append("^FS\n");
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
 
byte[] getData(BufferedImage img) throws IOException {
 
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
int width = img.getWidth();
 
int height = img.getHeight();
// Lines
for (int i = 0; i < height; i++) {
int[] pixels = new int[width];
img.getRGB(0, i, width, 1, pixels, 0, 4);
final byte[] encodedLine = encodeLine(pixels);
out.write(encodedLine);
}
return out.toByteArray();
}
 
private byte[] encodeLine(int[] pixels) throws IOException {
final int bytesPerRow = (pixels.length + 7) / 8;
byte[] bytesToEncode = new byte[bytesPerRow];
int index = 0;
for (int i = 0; i < bytesToEncode.length; i++) {
int points = 0;
for (int j = 0; j < 8; j++) {
 
int c = 0;
if (index < pixels.length) {
c = pixels[index];
int a = (c & 0xff000000) >> 24;
int r = (c & 0x00ff0000) >> 16;
int g = (c & 0x0000ff00) >> 8;
int b = c & 0x000000ff;
int grayScale = (int) (21.2671 * r + 71.5160 * g + 7.2169 * b);
boolean isBlack = grayScale < 12000;
points = points * 2;
if (isBlack && a < 0) {
points++;
}
 
} else {
points = points * 2;
}
 
index++;
}
bytesToEncode[i] = (byte) points;
 
}
return bytesToEncode;
}
 
@Override
public void fillRectangle(int x, int y, int w, int h, Color color) {
sb.append("^FO");
sb.append(x);
sb.append(',');
sb.append(y);
 
sb.append("^GB");
sb.append(w);
sb.append(',');
sb.append(h);
sb.append(',');
sb.append(Math.min(w, h));
sb.append("^FS\n");
 
}
 
@Override
public void drawRectangle(int x, int y, int w, int h, Color color, int lineWidth) {
sb.append("^FO");
sb.append(x);
sb.append(',');
sb.append(y);
 
sb.append("^GB");
sb.append(w);
sb.append(',');
sb.append(h);
sb.append(',');
sb.append(lineWidth);
sb.append("^FS\n");
}
 
@Override
public void drawBarcode(int x, int y, int h, int type, String code, int moduleWidth, int fontSize) {
code = code.trim();
sb.append("^FO");
sb.append(x);
sb.append(',');
sb.append(y);
sb.append("^CF0,");
sb.append(fontSize);
if (type == BARCODE_EAN13) {
sb.append("^BY");
sb.append(moduleWidth);
 
sb.append("^BEN,");
sb.append(h);
sb.append(",Y,N");
 
} else if (type == BARCODE_EAN8) {
sb.append("^BY");
sb.append(moduleWidth);
 
sb.append("^B8N,");
sb.append(h);
sb.append(",Y,N");
 
} else if (type == BARCODE_DATAMATRIX) {
sb.append("^BXN,");
sb.append(moduleWidth);
sb.append(",200");
} else if (type == BARCODE_CODE128) {
sb.append("^BY");
sb.append(moduleWidth - 1);
sb.append("^BCN,");
sb.append(h);
sb.append(",Y,N,Y,D");
 
} else if (type == BARCODE_CODE128_GS1) {
sb.append("^BY");
sb.append(moduleWidth - 1);
sb.append("^BCN,");
sb.append(h);
sb.append(",Y,N,Y,N");
 
}
sb.append("^FD");
sb.append(code);
sb.append("^FS\n");
}
 
public String getZPL() {
return sb.toString() + "\n^XZ";
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/graphicspl/Graphics2DRenderer.java
New file
0,0 → 1,116
package org.openconcerto.modules.label.graphicspl;
 
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
 
import uk.org.okapibarcode.backend.Code128;
import uk.org.okapibarcode.backend.DataMatrix;
import uk.org.okapibarcode.backend.Ean;
import uk.org.okapibarcode.backend.QrCode;
import uk.org.okapibarcode.backend.Symbol;
import uk.org.okapibarcode.output.Java2DRenderer;
 
public class Graphics2DRenderer extends GPLRenderer {
 
private final Graphics2D g;
 
public Graphics2DRenderer(Graphics2D g, float ratio) {
super(ratio);
this.g = g;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
 
@Override
public void drawText(int x, int y, String text, int align, String fontName, int fontSize, Color color, int maxWidth, boolean wrap) {
final Font font = new Font(fontName, Font.PLAIN, fontSize);
g.setFont(font.deriveFont(fontSize));
g.setColor(color);
y += g.getFontMetrics().getAscent() - g.getFontMetrics().getDescent();
if (align == ALIGN_RIGHT) {
int w = (int) g.getFontMetrics().getStringBounds(text, g).getWidth();
g.drawString(text, x - w, y);
} else if (align == ALIGN_CENTER) {
int w = (int) (g.getFontMetrics().getStringBounds(text, g).getWidth() / 2D);
g.drawString(text, x - w, y);
} else {
g.drawString(text, x, y);
}
}
 
@Override
public void drawImage(int x, int y, int w, int h, BufferedImage img) {
g.drawImage(img, x, y, x + w, y + h, 0, 0, img.getWidth(), img.getHeight(), null);
}
 
@Override
public void fillRectangle(int x, int y, int w, int h, Color color) {
g.setColor(color);
g.fillRect(x, y, w, h);
}
 
@Override
public void drawRectangle(int x, int y, int w, int h, Color color, int lineWidth) {
Stroke s = g.getStroke();
g.setColor(color);
if (lineWidth != 1) {
g.setStroke(new BasicStroke(lineWidth));
}
g.drawRect(x, y, w, h);
g.setStroke(s);
}
 
@Override
public void drawBarcode(int x, int y, int h, int type, String code, int moduleWidth, int fontSize) {
 
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
 
Symbol symbol = null;
if (type == BARCODE_EAN8) {
symbol = new Ean();
((Ean) symbol).setMode(Ean.Mode.EAN8);
symbol.setBarHeight(h);
} else if (type == BARCODE_EAN13) {
symbol = new Ean();
((Ean) symbol).setMode(Ean.Mode.EAN13);
symbol.setBarHeight(h);
} else if (type == BARCODE_CODE128) {
symbol = new Code128();
symbol.setDataType(Symbol.DataType.GS1);
symbol.setBarHeight(h);
} else if (type == BARCODE_CODE128_GS1) {
symbol = new Code128();
symbol.setBarHeight(h);
} else if (type == BARCODE_DATAMATRIX) {
symbol = new DataMatrix();
} else if (type == BARCODE_QRCODE) {
symbol = new QrCode();
}
 
if (symbol == null) {
return;
}
symbol.setModuleWidth(moduleWidth);
symbol.setFontSize(fontSize);
symbol.setContent(code.trim());
 
AffineTransform aT = g.getTransform();
 
g.setTransform(AffineTransform.getTranslateInstance(x, y));
 
Java2DRenderer renderer = new Java2DRenderer(g, 1, Color.WHITE, Color.BLACK);
renderer.render(symbol);
 
g.setTransform(aT);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
 
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/oc-qrcode.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/oc-qrcode.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/Label.java
New file
0,0 → 1,5
package org.openconcerto.modules.label;
 
public class Label {
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/default-zpl.txt
New file
0,0 → 1,20
^XA
 
^FX Titre
^CF0,42^FO150,43^FB750,1,0,L^FDXXXXXXXXXX^FS
^FX DataMatrix
^FO1010,20^BXN,7,200,,,,@^FDOPENCONCERTO^FS
 
^FX Nom du produit
^CF0,40
^FO150,140^FB700,1,0,L^FDYYYYYYYYYY^FS
 
^FX Code barre
^FO150,220
^BY3,2,300
^BCN,300,Y,N,Y,D
^FD(02)7612345678900(15)201218(37)9999^FS
 
^FO150,620^BY3,2,300^BCN,300,Y,N,Y,D^FD(10)LLLLLLLLL^FS
 
^XZ
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/ModuleLabel.java
4,10 → 4,17
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;
 
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.SwingWorker;
 
import org.openconcerto.erp.generationDoc.provider.AdresseFullClientValueProvider;
14,6 → 21,7
import org.openconcerto.erp.modules.AbstractModule;
import org.openconcerto.erp.modules.ComponentsContext;
import org.openconcerto.erp.modules.ModuleFactory;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
23,10 → 31,12
import org.openconcerto.sql.view.list.IListeAction.IListeEvent;
import org.openconcerto.sql.view.list.RowAction.PredicateRowAction;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.GestionDevise;
import org.openconcerto.utils.StringUtils;
 
public final class ModuleLabel extends AbstractModule {
final LinkedHashMap<String, String> zplTemplates = new LinkedHashMap<String, String>();
 
public ModuleLabel(ModuleFactory f) throws IOException {
super(f);
34,6 → 44,9
 
@Override
protected void setupComponents(ComponentsContext ctxt) {
readTemplates(new File("Template/Labels"));
readTemplates(new File("Configuration/Template/Labels"));
 
final String actionName = "Imprimer les étiquettes";
final PredicateRowAction aArticle = new PredicateRowAction(new AbstractAction(actionName) {
 
41,26 → 54,31
public void actionPerformed(ActionEvent arg0) {
final IListe list = IListe.get(arg0);
final List<Integer> selectedIDs = list.getSelection().getSelectedIDs();
final SwingWorker<List<SQLRowValues>, String> wworker = new SwingWorker<List<SQLRowValues>, String>() {
final SQLTable tArticle = list.getSelectedRows().get(0).getTable();
final SwingWorker<List<RowValuesLabel>, String> wworker = new SwingWorker<List<RowValuesLabel>, String>() {
 
@Override
protected List<SQLRowValues> doInBackground() throws Exception {
final SQLTable tArticle = list.getSelectedRows().get(0).getTable();
protected List<RowValuesLabel> doInBackground() throws Exception {
final SQLRowValues graph = new SQLRowValues(tArticle);
graph.putNulls("NOM", "PV_TTC");
final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(graph);
final List<SQLRowValues> values = fetcher.fetch(new Where(tArticle.getKey(), selectedIDs));
return values;
final List<SQLRowValues> rows = fetcher.fetch(new Where(tArticle.getKey(), selectedIDs));
final List<RowValuesLabel> list = new ArrayList<>(rows.size());
for (SQLRowValues row : rows) {
list.add(new RowValuesLabel(row));
}
return list;
}
 
@Override
protected void done() {
try {
final List<SQLRowValues> values = get();
final List<RowValuesLabel> values = get();
 
final LabelFrame f = new LabelFrame(values, new LabelRenderer() {
 
@Override
public void paintLabel(Graphics g, SQLRowAccessor row, int x, int y, int gridWith, int gridHeight, float fontSize) {
public void paintLabel(Graphics g, Label label, int x, int y, int gridWith, int gridHeight, float fontSize) {
g.setColor(Color.BLACK);
g.setFont(g.getFont().deriveFont(fontSize));
// Labels borders
67,6 → 85,7
final int hBorder = 12;
final int vBorder = 8;
// Product name
SQLRowValues row = ((RowValuesLabel) label).getSQLRowValues();
final String text = row.getString("NOM");
final List<String> l = StringUtils.wrap(text, g.getFontMetrics(), gridWith - 2 * hBorder);
final int lineHeight = g.getFontMetrics().getHeight();
73,7 → 92,7
int lineY = y;
final int margin = gridHeight - l.size() * lineHeight;
if (margin > 0) {
lineY += (int) (margin / 2);
lineY += margin / 2;
}
for (String line : l) {
g.drawString(line, x + hBorder, lineY);
95,8 → 114,8
} catch (Exception e) {
ExceptionHandler.handle("Erreur d'impression", e);
}
}
};
};
wworker.execute();
 
}
107,11 → 126,11
public void actionPerformed(ActionEvent arg0) {
final IListe list = IListe.get(arg0);
final List<Integer> selectedIDs = list.getSelection().getSelectedIDs();
final SwingWorker<List<SQLRowValues>, String> wworker = new SwingWorker<List<SQLRowValues>, String>() {
final SQLTable tClient = list.getSelectedRows().get(0).getTable();
final SwingWorker<List<RowValuesLabel>, String> wworker = new SwingWorker<List<RowValuesLabel>, String>() {
 
@Override
protected List<SQLRowValues> doInBackground() throws Exception {
final SQLTable tClient = list.getSelectedRows().get(0).getTable();
protected List<RowValuesLabel> doInBackground() throws Exception {
final SQLRowValues graph = new SQLRowValues(tClient);
graph.putNulls("NOM");
final SQLRowValues a1 = graph.putRowValues("ID_ADRESSE");
119,17 → 138,24
final SQLRowValues a2 = graph.putRowValues("ID_ADRESSE_L");
a2.putNulls(a2.getTable().getFieldsName());
final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(graph);
final List<SQLRowValues> values = fetcher.fetch(new Where(tClient.getKey(), selectedIDs));
return values;
final List<SQLRowValues> rows = fetcher.fetch(new Where(tClient.getKey(), selectedIDs));
 
final List<RowValuesLabel> list = new ArrayList<>(rows.size());
for (SQLRowValues row : rows) {
list.add(new RowValuesLabel(row));
}
return list;
}
 
@Override
protected void done() {
try {
final List<SQLRowValues> values = get();
final List<RowValuesLabel> values = get();
final LabelFrame f = new LabelFrame(values, new LabelRenderer() {
 
@Override
public void paintLabel(Graphics g, SQLRowAccessor row, int x, int y, int gridWith, int gridHeight, float fontSize) {
public void paintLabel(Graphics g, Label label, int x, int y, int gridWith, int gridHeight, float fontSize) {
SQLRowValues row = ((RowValuesLabel) label).getSQLRowValues();
SQLRowAccessor rAddr = row.getForeign("ID_ADRESSE_L");
if (rAddr == null || rAddr.isUndefined()) {
rAddr = row.getForeign("ID_ADRESSE");
165,8 → 191,8
} catch (Exception e) {
ExceptionHandler.handle("Erreur d'impression", e);
}
}
};
};
wworker.execute();
 
}
176,13 → 202,94
aClient.setPredicate(IListeEvent.createSelectionCountPredicate(1, Integer.MAX_VALUE));
ctxt.getElement("ARTICLE").getRowActions().add(aArticle);
ctxt.getElement("CLIENT").getRowActions().add(aClient);
 
if (!this.zplTemplates.isEmpty()) {
for (final Entry<String, String> entry : this.zplTemplates.entrySet()) {
final String zpl = entry.getValue();
final PredicateRowAction action = new PredicateRowAction(new AbstractAction("Imprimer l'étiquette " + entry.getKey()) {
 
@Override
public void actionPerformed(ActionEvent arg0) {
final ZPLPrinterPanel p = new ZPLPrinterPanel(zpl);
final JFrame f = new JFrame();
final IListe list = IListe.get(arg0);
final int idProduct = list.getSelection().getSelectedID();
final SQLTable tArticle = list.getSelectedRows().get(0).getTable();
 
final SwingWorker<SQLRowValues, String> wworker = new SwingWorker<SQLRowValues, String>() {
 
@Override
protected SQLRowValues doInBackground() throws Exception {
final SQLRow row = tArticle.getRow(idProduct);
row.fetchValues();
return row.asRowValues();
}
 
@Override
protected void done() {
try {
final SQLRowValues values = get();
p.initUI(values);
f.setTitle(entry.getKey());
f.setContentPane(p);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
 
} catch (Exception e) {
ExceptionHandler.handle("Erreur d'impression", e);
}
}
};
wworker.execute();
 
}
}, true, false);
 
action.setPredicate(IListeEvent.createSelectionCountPredicate(1, 1));
ctxt.getElement("ARTICLE").getRowActions().add(action);
}
}
 
}
 
@Override
protected void start() {
 
}
 
private void readTemplates(File templatesDir) {
System.out.println("ModuleLabel.readTemplates() " + templatesDir.getAbsolutePath());
if (templatesDir.exists() && templatesDir.isDirectory()) {
System.err.println("ModuleLabel.readTemplates() " + templatesDir.getAbsolutePath());
File[] files = templatesDir.listFiles();
if (files != null) {
LinkedHashMap<String, String> map = new LinkedHashMap<>();
for (File f : files) {
if (f.getName().endsWith(".zpl")) {
try {
String zpl = FileUtils.read(f, StandardCharsets.UTF_8);
String name = f.getName().substring(0, f.getName().length() - 4).trim();
map.put(name, zpl);
System.err.println("ModuleLabel.readTemplates() add " + name);
} catch (Exception e) {
System.err.println(this.getClass().getCanonicalName() + "start() cannot read zpl template : " + f.getAbsolutePath() + " : " + e.getMessage());
}
}
}
// Tri de la map par clef
final TreeMap<String, String> copy = new TreeMap<>(map);
this.zplTemplates.clear();
this.zplTemplates.putAll(copy);
}
 
} else {
System.err.println("ModuleLabel.readTemplates() " + templatesDir.getAbsolutePath() + " missing");
}
}
 
@Override
protected void stop() {
// nothing
}
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/32.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/32.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/RowValuesLabel.java
New file
0,0 → 1,16
package org.openconcerto.modules.label;
 
import org.openconcerto.sql.model.SQLRowValues;
 
public class RowValuesLabel extends Label {
private SQLRowValues row;
 
public RowValuesLabel(SQLRowValues row) {
this.row = row;
}
 
public SQLRowValues getSQLRowValues() {
return row;
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/16.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/16.png
New file
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/ZPLPrinterPanel.java
New file
0,0 → 1,409
package org.openconcerto.modules.label;
 
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.print.PrinterJob;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
 
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.PrintService;
import javax.print.SimpleDoc;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
 
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.JLabelBold;
import org.openconcerto.utils.BaseDirs;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.ProductInfo;
 
public class ZPLPrinterPanel extends JPanel {
private final HashMap<String, String> mapName = new HashMap<>();
private final List<String> variables;
private final List<String> knownVariables = new ArrayList<>();
private Map<String, JTextField> editorMap = new HashMap<>();
private String zpl;
private final Properties properties = new Properties();
 
public static void main(String[] args) throws IOException {
// final File f = new File("Templates/Labels", "50x50.zpl");
final File file = new File("Template/Labels", "57x32.zpl");
String zpl = FileUtils.read(file);
SwingUtilities.invokeLater(new Runnable() {
 
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
ZPLPrinterPanel p = new ZPLPrinterPanel(zpl);
p.initUI(null);
JFrame f = new JFrame();
f.setTitle(file.getName());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(p);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
 
}
 
public ZPLPrinterPanel(String zpl) {
this.zpl = zpl;
this.variables = getVariables(zpl);
knownVariables.add("product.code");
knownVariables.add("product.name");
knownVariables.add("product.material");
 
knownVariables.add("product.ean13");
knownVariables.add("product.price");
knownVariables.add("product.pricewithtax");
//
mapName.put("product.name", "Nom");
mapName.put("product.code", "Code");
mapName.put("product.ean13", "Code à barres");
mapName.put("product.price", "Prix HT");
mapName.put("product.pricewithtax", "Prix TTC");
mapName.put("product.treatment", "Traitement");
mapName.put("product.origin", "Origine");
mapName.put("product.batch", "Lot");
mapName.put("product.size", "Taille");
mapName.put("product.color", "Couleur");
mapName.put("product.material", "Matière");
}
 
private final File getPrefFile() {
final File prefsFolder = BaseDirs.create(ProductInfo.getInstance()).getPreferencesFolder();
if (!prefsFolder.exists()) {
prefsFolder.mkdirs();
}
return new File(prefsFolder, "labels.properties");
}
 
protected void initUI(SQLRowAccessor row) {
final File prefsFolder = BaseDirs.create(ProductInfo.getInstance()).getPreferencesFolder();
if (!prefsFolder.exists()) {
prefsFolder.mkdirs();
}
 
final File file = getPrefFile();
System.out.println(file.getAbsolutePath());
if (file.exists()) {
try {
properties.load(new FileInputStream(file));
} catch (IOException e) {
e.printStackTrace();
}
}
 
this.setLayout(new GridBagLayout());
GridBagConstraints c = new DefaultGridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
this.removeAll();
 
// Fields
 
Set<String> added = new HashSet<>();
for (String v : this.knownVariables) {
if (variables.contains(v)) {
// Non editable
String label = getName(v);
c.gridx = 0;
c.weightx = 0;
this.add(new JLabel(label, SwingConstants.RIGHT), c);
c.gridx++;
c.weightx = 1;
 
String value = getValueAsString(row, v);
JTextField txt = new JTextField(20);
if (value != null) {
txt.setText(value);
txt.setEditable(false);
}
editorMap.put(v, txt);
this.add(txt, c);
added.add(v);
c.gridy++;
}
}
for (String v : this.variables) {
if (!added.contains(v)) {
// Editable
String label = getName(v);
c.gridx = 0;
c.weightx = 0;
this.add(new JLabel(label, SwingConstants.RIGHT), c);
c.gridx++;
c.weightx = 1;
JTextField txt = new JTextField(20);
editorMap.put(v, txt);
this.add(txt, c);
added.add(v);
c.gridy++;
}
 
}
 
c.gridwidth = 2;
c.gridx = 0;
this.add(new JLabelBold("Paramètres d'impression"), c);
// Printer selector
c.gridx = 0;
c.gridy++;
final JPanel l1 = new JPanel();
l1.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0));
 
l1.add(new JLabel("Nombre d'étiquettes"));
final JSpinner nbLabels = new JSpinner(new SpinnerNumberModel(1, 1, 1000, 10));
l1.add(nbLabels);
this.add(l1, c);
// Delay
l1.add(new JLabel(" Pause entre chaque impression"));
final JSpinner delayLabels = new JSpinner(new SpinnerNumberModel(800, 100, 10000, 100));
l1.add(delayLabels);
this.add(l1, c);
l1.add(new JLabel("ms"));
 
c.gridy++;
 
final JPanel lPrintNetwork = new JPanel();
lPrintNetwork.setLayout(new FlowLayout(FlowLayout.LEFT, 2, 0));
JRadioButton radioNetworkPrinter = new JRadioButton("imprimante réseau IP :");
lPrintNetwork.add(radioNetworkPrinter);
JTextField textPrinterIP = new JTextField(16);
lPrintNetwork.add(textPrinterIP);
lPrintNetwork.add(new JLabel(" Port :"));
JSpinner portZPL = new JSpinner(new SpinnerNumberModel(9100, 24, 10000, 1));
lPrintNetwork.add(portZPL);
this.add(lPrintNetwork, c);
 
c.gridy++;
final JPanel lPrintLocal = new JPanel();
lPrintLocal.setLayout(new FlowLayout(FlowLayout.LEFT, 2, 0));
JRadioButton radioLocalPrinter = new JRadioButton("imprimante locale");
lPrintLocal.add(radioLocalPrinter);
radioLocalPrinter.setSelected(true);
this.add(lPrintLocal, c);
 
final ButtonGroup gr = new ButtonGroup();
gr.add(radioLocalPrinter);
gr.add(radioNetworkPrinter);
c.gridy++;
c.weighty = 1;
c.gridx = 1;
c.fill = GridBagConstraints.NONE;
c.anchor = GridBagConstraints.SOUTHEAST;
 
// Restore state from properties
if (properties.getOrDefault("printerType", "local").equals("local")) {
radioLocalPrinter.setSelected(true);
} else {
radioNetworkPrinter.setSelected(true);
}
textPrinterIP.setText(properties.getOrDefault("printerIp", "").toString());
portZPL.setValue(Long.parseLong(properties.getOrDefault("printerPort", "9100").toString()));
nbLabels.setValue(Long.parseLong(properties.getOrDefault("nbLabels", "1").toString()));
delayLabels.setValue(Long.parseLong(properties.getOrDefault("delay", "800").toString()));
// Print
 
JButton printButton = new JButton("Imprimer");
this.add(printButton, c);
printButton.addActionListener(new ActionListener() {
 
@Override
public void actionPerformed(ActionEvent e) {
try {
if (radioLocalPrinter.isSelected()) {
properties.put("printerType", "local");
} else {
properties.put("printerType", "network");
properties.put("printerIp", textPrinterIP.getText());
properties.put("printerPort", portZPL.getValue().toString());
}
properties.put("nbLabels", nbLabels.getValue().toString());
properties.put("delay", delayLabels.getValue().toString());
// Save Prefs
properties.store(new FileOutputStream(getPrefFile()), "");
} catch (Exception e1) {
ExceptionHandler.handle("Erreur de sauvegarde de " + getPrefFile().getAbsolutePath(), e1);
}
final String code = createZPLCode();
System.out.println("ZPL:");
System.out.println(code);
byte[] data = code.getBytes(StandardCharsets.UTF_8);
if (radioNetworkPrinter.isSelected()) {
Socket socket = null;
try {
socket = new Socket(textPrinterIP.getText(), ((Number) portZPL.getValue()).intValue());
final DataOutputStream out = new DataOutputStream(socket.getOutputStream());
final int nb = ((Number) nbLabels.getValue()).intValue();
for (int i = 0; i < nb; i++) {
out.write(data);
}
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(printButton, "Erreur d'impression réseau : " + ex.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
 
}
} else {
try {
final PrinterJob pj1 = PrinterJob.getPrinterJob();
if (pj1.printDialog()) {
final PrintService ps = pj1.getPrintService();
final DocPrintJob pj = ps.createPrintJob();
final SimpleDoc doc = new SimpleDoc(data, DocFlavor.BYTE_ARRAY.AUTOSENSE, null);
final int nb = ((Number) nbLabels.getValue()).intValue();
for (int i = 0; i < nb; i++) {
pj.print(doc, null);
Thread.sleep(((Number) delayLabels.getValue()).intValue());
}
}
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(printButton, "Erreur d'impression locale : " + ex.getMessage());
}
}
 
}
});
 
}
 
private String createZPLCode() {
 
final BufferedReader reader = new BufferedReader(new StringReader(this.zpl));
final StringBuilder builder = new StringBuilder();
 
try {
String line = reader.readLine();
while (line != null) {
if (line.contains("${")) {
boolean add = false;
for (String v : this.editorMap.keySet()) {
if (line.contains("${" + v + "}")) {
final String value = this.editorMap.get(v).getText();
line = line.replace("${" + v + "}", value);
if (!value.trim().isEmpty()) {
add = true;
}
}
}
if (add) {
builder.append(line);
builder.append("\n");
}
 
} else {
builder.append(line);
builder.append("\n");
}
line = reader.readLine();
}
 
} catch (Exception e) {
e.printStackTrace();
}
 
return builder.toString();
}
 
public String getValueAsString(SQLRowAccessor row, String variableName) {
if (row == null) {
return null;
}
if (variableName.equals("product.code")) {
return row.getString("CODE");
} else if (variableName.equals("product.name")) {
return row.getString("NOM");
} else if (variableName.equals("product.ean13")) {
return row.getString("CODE_BARRE");
} else if (variableName.equals("product.price")) {
return new DecimalFormat("#0.00").format(row.getBigDecimal("PV_HT"));
} else if (variableName.equals("product.pricewithtax")) {
return new DecimalFormat("#0.00").format(row.getBigDecimal("PV_TTC"));
} else if (variableName.equals("product.material")) {
return row.getString("MATIERE");
}
return "";
}
 
public String getName(String variableName) {
String n = mapName.get(variableName);
if (n == null) {
return variableName;
}
return n;
}
 
public List<String> getVariables(String str) {
final List<String> result = new ArrayList<>();
if (str == null || str.length() < 4) {
return result;
}
final int l = str.length() - 1;
int start = 0;
boolean inName = false;
for (int i = 0; i < l; i++) {
char c1 = str.charAt(i);
char c2 = str.charAt(i + 1);
if (!inName) {
if (c1 == '$' && c2 == '{') {
start = i + 2;
inName = true;
}
} else if (c2 == '}') {
final int stop = i + 1;
String v = str.substring(start, stop);
result.add(v);
inName = false;
}
}
return result;
}
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/GS1Label.java
New file
0,0 → 1,16
package org.openconcerto.modules.label;
 
import org.openconcerto.modules.label.gs1.GS1AIElements;
 
public class GS1Label extends Label {
String text;
String text2;
GS1AIElements gs1;
 
public GS1Label(String text, String text2, GS1AIElements gs1) {
this.text = text;
this.text2 = text2;
this.gs1 = gs1;
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/ISO646.java
New file
0,0 → 1,30
package org.openconcerto.modules.label;
 
public class ISO646 {
public static final String ALLOWED_CHARS = "!\"%&'()*+,-./01234567989:;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
 
public static boolean isValid(char c) {
final int length = ALLOWED_CHARS.length();
for (int i = 0; i < length; i++) {
if (c == ALLOWED_CHARS.charAt(i)) {
return true;
}
}
return false;
}
 
public static String clean(String s) {
final int length = s.length();
final StringBuilder b = new StringBuilder(length);
for (int i = 0; i < length; i++) {
final char charAt = s.charAt(i);
if (isValid(charAt)) {
b.append(s.charAt(i));
} else {
b.append('_');
}
}
return b.toString();
}
 
}
/trunk/Modules/Module Label/src/org/openconcerto/modules/label/LabelFrame.java
20,7 → 20,6
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
 
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.utils.ExceptionHandler;
 
30,7 → 29,7
private static final int DEFAULT_COLS = 4;
final LabelPanel labelPanel;
 
public LabelFrame(List<? extends SQLRowAccessor> list, LabelRenderer labelRenderer) {
public LabelFrame(List<? extends Label> list, LabelRenderer labelRenderer) {
final JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
final GridBagConstraints c = new DefaultGridBagConstraints();
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/AztecCode.java
New file
0,0 → 1,1824
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import static java.nio.charset.StandardCharsets.US_ASCII;
import static uk.org.okapibarcode.util.Arrays.insertArray;
 
/**
* <p>
* Implements Aztec Code bar code symbology According to ISO/IEC 24778:2008.
*
* <p>
* Aztec Code can encode 8-bit ISO 8859-1 (Latin-1) data (except 0x00 Null characters) up to a
* maximum length of approximately 3800 numeric characters, 3000 alphabetic characters or 1900 bytes
* of data in a two-dimensional matrix symbol.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class AztecCode extends Symbol {
 
/* 27 x 27 data grid */
private static final int[] COMPACT_AZTEC_MAP = { 609, 608, 411, 413, 415, 417, 419, 421, 423, 425, 427, 429, 431, 433, 435, 437, 439, 441, 443, 445, 447, 449, 451, 453, 455, 457, 459, 607, 606,
410, 412, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 605, 604, 409, 408, 243, 245, 247, 249, 251, 253, 255, 257,
259, 261, 263, 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 460, 461, 603, 602, 407, 406, 242, 244, 246, 248, 250, 252, 254, 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276,
278, 280, 282, 462, 463, 601, 600, 405, 404, 241, 240, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127, 129, 131, 133, 135, 137, 139, 284, 285, 464, 465, 599, 598, 403, 402, 239,
238, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 286, 287, 466, 467, 597, 596, 401, 400, 237, 236, 105, 104, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21,
23, 25, 27, 140, 141, 288, 289, 468, 469, 595, 594, 399, 398, 235, 234, 103, 102, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 142, 143, 290, 291, 470, 471, 593, 592, 397, 396, 233,
232, 101, 100, 1, 1, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 0, 1, 28, 29, 144, 145, 292, 293, 472, 473, 591, 590, 395, 394, 231, 230, 99, 98, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 30, 31,
146, 147, 294, 295, 474, 475, 589, 588, 393, 392, 229, 228, 97, 96, 2027, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2007, 32, 33, 148, 149, 296, 297, 476, 477, 587, 586, 391, 390, 227, 226, 95, 94, 2026,
1, 0, 1, 1, 1, 1, 1, 0, 1, 2008, 34, 35, 150, 151, 298, 299, 478, 479, 585, 584, 389, 388, 225, 224, 93, 92, 2025, 1, 0, 1, 0, 0, 0, 1, 0, 1, 2009, 36, 37, 152, 153, 300, 301, 480, 481,
583, 582, 387, 386, 223, 222, 91, 90, 2024, 1, 0, 1, 0, 1, 0, 1, 0, 1, 2010, 38, 39, 154, 155, 302, 303, 482, 483, 581, 580, 385, 384, 221, 220, 89, 88, 2023, 1, 0, 1, 0, 0, 0, 1, 0, 1,
2011, 40, 41, 156, 157, 304, 305, 484, 485, 579, 578, 383, 382, 219, 218, 87, 86, 2022, 1, 0, 1, 1, 1, 1, 1, 0, 1, 2012, 42, 43, 158, 159, 306, 307, 486, 487, 577, 576, 381, 380, 217, 216,
85, 84, 2021, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2013, 44, 45, 160, 161, 308, 309, 488, 489, 575, 574, 379, 378, 215, 214, 83, 82, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 46, 47, 162, 163, 310, 311, 490,
491, 573, 572, 377, 376, 213, 212, 81, 80, 0, 0, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 0, 0, 48, 49, 164, 165, 312, 313, 492, 493, 571, 570, 375, 374, 211, 210, 78, 76, 74, 72, 70, 68,
66, 64, 62, 60, 58, 56, 54, 50, 51, 166, 167, 314, 315, 494, 495, 569, 568, 373, 372, 209, 208, 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59, 57, 55, 52, 53, 168, 169, 316, 317, 496, 497,
567, 566, 371, 370, 206, 204, 202, 200, 198, 196, 194, 192, 190, 188, 186, 184, 182, 180, 178, 176, 174, 170, 171, 318, 319, 498, 499, 565, 564, 369, 368, 207, 205, 203, 201, 199, 197,
195, 193, 191, 189, 187, 185, 183, 181, 179, 177, 175, 172, 173, 320, 321, 500, 501, 563, 562, 366, 364, 362, 360, 358, 356, 354, 352, 350, 348, 346, 344, 342, 340, 338, 336, 334, 332,
330, 328, 326, 322, 323, 502, 503, 561, 560, 367, 365, 363, 361, 359, 357, 355, 353, 351, 349, 347, 345, 343, 341, 339, 337, 335, 333, 331, 329, 327, 324, 325, 504, 505, 558, 556, 554,
552, 550, 548, 546, 544, 542, 540, 538, 536, 534, 532, 530, 528, 526, 524, 522, 520, 518, 516, 514, 512, 510, 506, 507, 559, 557, 555, 553, 551, 549, 547, 545, 543, 541, 539, 537, 535,
533, 531, 529, 527, 525, 523, 521, 519, 517, 515, 513, 511, 508, 509 };
 
private static final int[][] AZTEC_MAP = new int[151][151];
 
/*
* From Table 2:
*
* 1 = upper 2 = lower 4 = mixed 8 = punctuation 16 = digits 32 = binary
*
* Values can be OR'ed, so e.g. 12 = 4 | 8, and 23 = 1 | 2 | 4 | 16
*/
private static final int[] AZTEC_CODE_SET = { 32, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 12, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 4, 4, 4, 4, 4, 23, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
24, 8, 24, 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 4, 8, 4, 4, 4, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 8, 4, 8, 4, 4 };
 
/* From Table 2 */
private static final int[] AZTEC_SYMBOL_CHAR = { 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 300, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 15, 16, 17, 18, 19, 1, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 301, 18, 302, 20, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 21, 22, 23, 24, 25, 26, 20, 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, 27, 21, 28, 22, 23, 24, 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, 29, 25, 30, 26, 27 };
 
/*
* Problem characters are: 300: Carriage Return (ASCII 13) 301: Comma (ASCII 44) 302: Full Stop
* (ASCII 46)
*/
private static final String[] PENTBIT = { "00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111", "01000", "01001", "01010", "01011", "01100", "01101", "01110", "01111", "10000",
"10001", "10010", "10011", "10100", "10101", "10110", "10111", "11000", "11001", "11010", "11011", "11100", "11101", "11110", "11111" };
 
private static final String[] QUADBIT = { "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111" };
 
private static final String[] TRIBIT = { "000", "001", "010", "011", "100", "101", "110", "111" };
 
/* Codewords per symbol */
private static final int[] AZTEC_SIZES = { 21, 48, 60, 88, 120, 156, 196, 240, 230, 272, 316, 364, 416, 470, 528, 588, 652, 720, 790, 864, 940, 1020, 920, 992, 1066, 1144, 1224, 1306, 1392, 1480,
1570, 1664 };
 
private static final int[] AZTEC_COMPACT_SIZES = { 17, 40, 51, 76 };
 
/* Data bits per symbol maximum with 10% error correction */
private static final int[] AZTEC_10_DATA_SIZES = { 96, 246, 408, 616, 840, 1104, 1392, 1704, 2040, 2420, 2820, 3250, 3720, 4200, 4730, 5270, 5840, 6450, 7080, 7750, 8430, 9150, 9900, 10680, 11484,
12324, 13188, 14076, 15000, 15948, 16920, 17940 };
 
/* Data bits per symbol maximum with 23% error correction */
private static final int[] AZTEC_23_DATA_SIZES = { 84, 204, 352, 520, 720, 944, 1184, 1456, 1750, 2070, 2410, 2780, 3180, 3590, 4040, 4500, 5000, 5520, 6060, 6630, 7210, 7830, 8472, 9132, 9816,
10536, 11280, 12036, 12828, 13644, 14472, 15348 };
 
/* Data bits per symbol maximum with 36% error correction */
private static final int[] AZTEC_36_DATA_SIZES = { 66, 168, 288, 432, 592, 776, 984, 1208, 1450, 1720, 2000, 2300, 2640, 2980, 3350, 3740, 4150, 4580, 5030, 5500, 5990, 6500, 7032, 7584, 8160,
8760, 9372, 9996, 10656, 11340, 12024, 12744 };
 
/* Data bits per symbol maximum with 50% error correction */
private static final int[] AZTEC_50_DATA_SIZES = { 48, 126, 216, 328, 456, 600, 760, 936, 1120, 1330, 1550, 1790, 2050, 2320, 2610, 2910, 3230, 3570, 3920, 4290, 4670, 5070, 5484, 5916, 6360,
6828, 7308, 7800, 8316, 8844, 9384, 9948 };
 
private static final int[] AZTEC_COMPACT_10_DATA_SIZES = { 78, 198, 336, 520 };
private static final int[] AZTEC_COMPACT_23_DATA_SIZES = { 66, 168, 288, 440 };
private static final int[] AZTEC_COMPACT_36_DATA_SIZES = { 48, 138, 232, 360 };
private static final int[] AZTEC_COMPACT_50_DATA_SIZES = { 36, 102, 176, 280 };
 
private static final int[] AZTEC_OFFSET = { 66, 64, 62, 60, 57, 55, 53, 51, 49, 47, 45, 42, 40, 38, 36, 34, 32, 30, 28, 25, 23, 21, 19, 17, 15, 13, 10, 8, 6, 4, 2, 0 };
 
private static final int[] AZTEC_COMPACT_OFFSET = { 6, 4, 2, 0 };
 
/* Initialize AZTEC_MAP */
static {
 
int layer, start, length, n, i;
int x, y;
 
for (x = 0; x < 151; x++) {
for (y = 0; y < 151; y++) {
AZTEC_MAP[x][y] = 0;
}
}
 
for (layer = 1; layer < 33; layer++) {
start = 112 * (layer - 1) + 16 * (layer - 1) * (layer - 1) + 2;
length = 28 + (layer - 1) * 4 + layer * 4;
/* Top */
i = 0;
x = 64 - (layer - 1) * 2;
y = 63 - (layer - 1) * 2;
for (n = start; n < start + length; n += 2) {
AZTEC_MAP[avoidReferenceGrid(x + i)][avoidReferenceGrid(y)] = n;
AZTEC_MAP[avoidReferenceGrid(x + i)][avoidReferenceGrid(y - 1)] = n + 1;
i++;
}
/* Right */
i = 0;
x = 78 + (layer - 1) * 2;
y = 64 - (layer - 1) * 2;
for (n = start + length; n < start + length * 2; n += 2) {
AZTEC_MAP[avoidReferenceGrid(x)][avoidReferenceGrid(y + i)] = n;
AZTEC_MAP[avoidReferenceGrid(x + 1)][avoidReferenceGrid(y + i)] = n + 1;
i++;
}
/* Bottom */
i = 0;
x = 77 + (layer - 1) * 2;
y = 78 + (layer - 1) * 2;
for (n = start + length * 2; n < start + length * 3; n += 2) {
AZTEC_MAP[avoidReferenceGrid(x - i)][avoidReferenceGrid(y)] = n;
AZTEC_MAP[avoidReferenceGrid(x - i)][avoidReferenceGrid(y + 1)] = n + 1;
i++;
}
/* Left */
i = 0;
x = 63 - (layer - 1) * 2;
y = 77 + (layer - 1) * 2;
for (n = start + length * 3; n < start + length * 4; n += 2) {
AZTEC_MAP[avoidReferenceGrid(x)][avoidReferenceGrid(y - i)] = n;
AZTEC_MAP[avoidReferenceGrid(x - 1)][avoidReferenceGrid(y - i)] = n + 1;
i++;
}
}
 
/* Central finder pattern */
for (y = 69; y <= 81; y++) {
for (x = 69; x <= 81; x++) {
AZTEC_MAP[x][y] = 1;
}
}
for (y = 70; y <= 80; y++) {
for (x = 70; x <= 80; x++) {
AZTEC_MAP[x][y] = 0;
}
}
for (y = 71; y <= 79; y++) {
for (x = 71; x <= 79; x++) {
AZTEC_MAP[x][y] = 1;
}
}
for (y = 72; y <= 78; y++) {
for (x = 72; x <= 78; x++) {
AZTEC_MAP[x][y] = 0;
}
}
for (y = 73; y <= 77; y++) {
for (x = 73; x <= 77; x++) {
AZTEC_MAP[x][y] = 1;
}
}
for (y = 74; y <= 76; y++) {
for (x = 74; x <= 76; x++) {
AZTEC_MAP[x][y] = 0;
}
}
 
/* Guide bars */
for (y = 11; y < 151; y += 16) {
for (x = 1; x < 151; x += 2) {
AZTEC_MAP[x][y] = 1;
AZTEC_MAP[y][x] = 1;
}
}
 
/* Descriptor */
for (i = 0; i < 10; i++) { /* Top */
AZTEC_MAP[avoidReferenceGrid(66 + i)][avoidReferenceGrid(64)] = 20000 + i;
}
for (i = 0; i < 10; i++) { /* Right */
AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(66 + i)] = 20010 + i;
}
for (i = 0; i < 10; i++) { /* Bottom */
AZTEC_MAP[avoidReferenceGrid(75 - i)][avoidReferenceGrid(77)] = 20020 + i;
}
for (i = 0; i < 10; i++) { /* Left */
AZTEC_MAP[avoidReferenceGrid(64)][avoidReferenceGrid(75 - i)] = 20030 + i;
}
 
/* Orientation */
AZTEC_MAP[avoidReferenceGrid(64)][avoidReferenceGrid(64)] = 1;
AZTEC_MAP[avoidReferenceGrid(65)][avoidReferenceGrid(64)] = 1;
AZTEC_MAP[avoidReferenceGrid(64)][avoidReferenceGrid(65)] = 1;
AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(64)] = 1;
AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(65)] = 1;
AZTEC_MAP[avoidReferenceGrid(77)][avoidReferenceGrid(76)] = 1;
}
 
private static int avoidReferenceGrid(final int input) {
int output = input;
if (output > 10) {
output++;
}
if (output > 26) {
output++;
}
if (output > 42) {
output++;
}
if (output > 58) {
output++;
}
if (output > 74) {
output++;
}
if (output > 90) {
output++;
}
if (output > 106) {
output++;
}
if (output > 122) {
output++;
}
if (output > 138) {
output++;
}
return output;
}
 
private int preferredSize = 0;
private int preferredEccLevel = 2;
private String structuredAppendMessageId;
private int structuredAppendPosition = 1;
private int structuredAppendTotal = 1;
 
/**
* <p>
* Sets a preferred symbol size. This value may be ignored if data string is too large to fit in
* the specified symbol size. Values correspond to symbol sizes as shown in the following table:
*
* <table summary="Available Aztec Code symbol sizes">
* <tbody>
* <tr>
* <th>Input</th>
* <th>Symbol Size</th>
* <th>Input</th>
* <th>Symbol Size</th>
* </tr>
* <tr>
* <td>1</td>
* <td>15 x 15</td>
* <td>19</td>
* <td>79 x 79</td>
* </tr>
* <tr>
* <td>2</td>
* <td>19 x 19</td>
* <td>20</td>
* <td>83 x 83</td>
* </tr>
* <tr>
* <td>3</td>
* <td>23 x 23</td>
* <td>21</td>
* <td>87 x 87</td>
* </tr>
* <tr>
* <td>4</td>
* <td>27 x 27</td>
* <td>22</td>
* <td>91 x 91</td>
* </tr>
* <tr>
* <td>5</td>
* <td>19 x 19</td>
* <td>23</td>
* <td>95 x 95</td>
* </tr>
* <tr>
* <td>6</td>
* <td>23 x 23</td>
* <td>24</td>
* <td>101 x 101</td>
* </tr>
* <tr>
* <td>7</td>
* <td>27 x 27</td>
* <td>25</td>
* <td>105 x 105</td>
* </tr>
* <tr>
* <td>8</td>
* <td>31 x 31</td>
* <td>26</td>
* <td>109 x 109</td>
* </tr>
* <tr>
* <td>9</td>
* <td>37 x 37</td>
* <td>27</td>
* <td>113 x 113</td>
* </tr>
* <tr>
* <td>10</td>
* <td>41 x 41</td>
* <td>28</td>
* <td>117 x 117</td>
* </tr>
* <tr>
* <td>11</td>
* <td>45 x 45</td>
* <td>29</td>
* <td>121 x 121</td>
* </tr>
* <tr>
* <td>12</td>
* <td>49 x 49</td>
* <td>30</td>
* <td>125 x 125</td>
* </tr>
* <tr>
* <td>13</td>
* <td>53 x 53</td>
* <td>31</td>
* <td>131 x 131</td>
* </tr>
* <tr>
* <td>14</td>
* <td>57 x 57</td>
* <td>32</td>
* <td>135 x 135</td>
* </tr>
* <tr>
* <td>15</td>
* <td>61 x 61</td>
* <td>33</td>
* <td>139 x 139</td>
* </tr>
* <tr>
* <td>16</td>
* <td>67 x 67</td>
* <td>34</td>
* <td>143 x 143</td>
* </tr>
* <tr>
* <td>17</td>
* <td>71 x 71</td>
* <td>35</td>
* <td>147 x 147</td>
* </tr>
* <tr>
* <td>18</td>
* <td>75 x 75</td>
* <td>36</td>
* <td>151 x 151</td>
* </tr>
* </tbody>
* </table>
*
* <p>
* Note that sizes 1 to 4 are the "compact" Aztec Code symbols; sizes 5 to 36 are the
* "full-range" Aztec Code symbols.
*
* @param size an integer in the range 1 - 36
*/
public void setPreferredSize(final int size) {
if (size < 1 || size > 36) {
throw new IllegalArgumentException("Invalid size: " + size);
}
this.preferredSize = size;
}
 
/**
* Returns the preferred symbol size.
*
* @return the preferred symbol size
*/
public int getPreferredSize() {
return this.preferredSize;
}
 
/**
* Sets the preferred minimum amount of symbol space dedicated to error correction. This value
* will be ignored if a symbol size has been set by <code>setPreferredSize</code>. Valid options
* are:
*
* <table summary="Error correction options">
* <tbody>
* <tr>
* <th>Mode</th>
* <th>Error Correction Capacity</th>
* </tr>
* <tr>
* <td>1</td>
* <td>&gt; 10% + 3 codewords</td>
* </tr>
* <tr>
* <td>2</td>
* <td>&gt; 23% + 3 codewords</td>
* </tr>
* <tr>
* <td>3</td>
* <td>&gt; 36% + 3 codewords</td>
* </tr>
* <tr>
* <td>4</td>
* <td>&gt; 50% + 3 codewords</td>
* </tr>
* </tbody>
* </table>
*
* @param eccLevel an integer in the range 1 - 4
*/
public void setPreferredEccLevel(final int eccLevel) {
if (eccLevel < 1 || eccLevel > 4) {
throw new IllegalArgumentException("Invalid ECC level: " + eccLevel);
}
this.preferredEccLevel = eccLevel;
}
 
/**
* Returns the preferred error correction level.
*
* @return the preferred error correction level
*/
public int getPreferredEccLevel() {
return this.preferredEccLevel;
}
 
/**
* If this Aztec Code symbol is part of a series of Aztec Code symbols appended in a structured
* format, this method sets the position of this symbol in the series. Valid values are 1
* through 26 inclusive.
*
* @param position the position of this Aztec Code symbol in the structured append series
*/
public void setStructuredAppendPosition(final int position) {
if (position < 1 || position > 26) {
throw new IllegalArgumentException("Invalid Aztec Code structured append position: " + position);
}
this.structuredAppendPosition = position;
}
 
/**
* Returns the position of this Aztec Code symbol in a series of symbols using structured
* append. If this symbol is not part of such a series, this method will return <code>1</code>.
*
* @return the position of this Aztec Code symbol in a series of symbols using structured append
*/
public int getStructuredAppendPosition() {
return this.structuredAppendPosition;
}
 
/**
* If this Aztec Code symbol is part of a series of Aztec Code symbols appended in a structured
* format, this method sets the total number of symbols in the series. Valid values are 1
* through 26 inclusive. A value of 1 indicates that this symbol is not part of a structured
* append series.
*
* @param total the total number of Aztec Code symbols in the structured append series
*/
public void setStructuredAppendTotal(final int total) {
if (total < 1 || total > 26) {
throw new IllegalArgumentException("Invalid Aztec Code structured append total: " + total);
}
this.structuredAppendTotal = total;
}
 
/**
* Returns the size of the series of Aztec Code symbols using structured append that this symbol
* is part of. If this symbol is not part of a structured append series, this method will return
* <code>1</code>.
*
* @return size of the series that this symbol is part of
*/
public int getStructuredAppendTotal() {
return this.structuredAppendTotal;
}
 
/**
* If this Aztec Code symbol is part of a series of Aztec Code symbols appended in a structured
* format, this method sets the unique message ID for the series. Values may not contain spaces
* and must contain only printable ASCII characters. Message IDs are optional.
*
* @param messageId the unique message ID for the series that this symbol is part of
*/
public void setStructuredAppendMessageId(final String messageId) {
if (messageId != null && !messageId.matches("^[\\x21-\\x7F]+$")) {
throw new IllegalArgumentException("Invalid Aztec Code structured append message ID: " + messageId);
}
this.structuredAppendMessageId = messageId;
}
 
/**
* Returns the unique message ID of the series of Aztec Code symbols using structured append
* that this symbol is part of. If this symbol is not part of a structured append series, this
* method will return <code>null</code>.
*
* @return the unique message ID for the series that this symbol is part of
*/
public String getStructuredAppendMessageId() {
return this.structuredAppendMessageId;
}
 
@Override
protected boolean gs1Supported() {
return true;
}
 
@Override
protected void encode() {
 
int layers;
boolean compact;
StringBuilder adjustedString;
 
if (this.inputDataType == DataType.GS1 && this.readerInit) {
throw new OkapiException("Cannot encode in GS1 and Reader Initialisation mode at the same time");
}
 
eciProcess(); // Get ECI mode
 
/* Optional structured append (Section 8 of spec) */
/* ML + UL start flag handled later, not part of data */
if (this.structuredAppendTotal != 1) {
final StringBuilder prefix = new StringBuilder();
if (this.structuredAppendMessageId != null) {
prefix.append(' ').append(this.structuredAppendMessageId).append(' ');
}
prefix.append((char) (this.structuredAppendPosition + 64)); // 1-26 as A-Z
prefix.append((char) (this.structuredAppendTotal + 64)); // 1-26 as A-Z
final int[] prefixArray = toBytes(prefix.toString(), US_ASCII);
this.inputData = insertArray(this.inputData, 0, prefixArray);
}
 
final String binaryString = generateAztecBinary();
int dataLength = binaryString.length();
 
if (this.preferredSize == 0) {
 
/* The size of the symbol can be determined by Okapi */
 
int dataMaxSize = 0;
final int compLoop = this.readerInit ? 1 : 4;
 
do {
/* Decide what size symbol to use - the smallest that fits the data */
 
int[] dataSizes;
int[] compactDataSizes;
 
switch (this.preferredEccLevel) {
/*
* For each level of error correction work out the smallest symbol which the data
* will fit in
*/
case 1:
dataSizes = AZTEC_10_DATA_SIZES;
compactDataSizes = AZTEC_COMPACT_10_DATA_SIZES;
break;
case 2:
dataSizes = AZTEC_23_DATA_SIZES;
compactDataSizes = AZTEC_COMPACT_23_DATA_SIZES;
break;
case 3:
dataSizes = AZTEC_36_DATA_SIZES;
compactDataSizes = AZTEC_COMPACT_36_DATA_SIZES;
break;
case 4:
dataSizes = AZTEC_50_DATA_SIZES;
compactDataSizes = AZTEC_COMPACT_50_DATA_SIZES;
break;
default:
throw new OkapiException("Unrecognized ECC level: " + this.preferredEccLevel);
}
 
layers = 0;
compact = false;
 
for (int i = 32; i > 0; i--) {
if (dataLength < dataSizes[i - 1]) {
layers = i;
compact = false;
dataMaxSize = dataSizes[i - 1];
}
}
 
for (int i = compLoop; i > 0; i--) {
if (dataLength < compactDataSizes[i - 1]) {
layers = i;
compact = true;
dataMaxSize = compactDataSizes[i - 1];
}
}
 
if (layers == 0) {
/* Couldn't find a symbol which fits the data */
throw new OkapiException("Input too long (too many bits for selected ECC)");
}
 
adjustedString = adjustBinaryString(binaryString, compact, layers);
dataLength = adjustedString.length();
 
} while (dataLength > dataMaxSize);
/*
* This loop will only repeat on the rare occasions when the rule about not having all
* 1s or all 0s means that the binary string has had to be lengthened beyond the maximum
* number of bits that can be encoded in a symbol of the selected size
*/
 
} else {
 
/* The size of the symbol has been specified by the user */
 
if (this.preferredSize >= 1 && this.preferredSize <= 4) {
compact = true;
layers = this.preferredSize;
} else {
compact = false;
layers = this.preferredSize - 4;
}
 
adjustedString = adjustBinaryString(binaryString, compact, layers);
 
/* Check if the data actually fits into the selected symbol size */
final int codewordSize = getCodewordSize(layers);
final int[] sizes = compact ? AZTEC_COMPACT_SIZES : AZTEC_SIZES;
final int dataMaxSize = codewordSize * (sizes[layers - 1] - 3);
if (adjustedString.length() > dataMaxSize) {
throw new OkapiException("Data too long for specified Aztec Code symbol size");
}
}
 
if (this.readerInit && compact && layers > 1) {
throw new OkapiException("Symbol is too large for reader initialization");
}
 
if (this.readerInit && layers > 22) {
throw new OkapiException("Symbol is too large for reader initialization");
}
 
final int codewordSize = getCodewordSize(layers);
final int dataBlocks = adjustedString.length() / codewordSize;
 
int eccBlocks;
if (compact) {
eccBlocks = AZTEC_COMPACT_SIZES[layers - 1] - dataBlocks;
} else {
eccBlocks = AZTEC_SIZES[layers - 1] - dataBlocks;
}
 
infoLine("Compact Mode: " + compact);
infoLine("Layers: " + layers);
infoLine("Codeword Length: " + codewordSize + " bits");
infoLine("Data Codewords: " + dataBlocks);
infoLine("ECC Codewords: " + eccBlocks);
 
/* Add ECC data to the adjusted string */
addErrorCorrection(adjustedString, codewordSize, dataBlocks, eccBlocks);
 
/* Invert the data so that actual data is on the outside and reed-solomon on the inside */
for (int i = 0; i < adjustedString.length() / 2; i++) {
final int mirror = adjustedString.length() - i - 1;
final char c = adjustedString.charAt(i);
adjustedString.setCharAt(i, adjustedString.charAt(mirror));
adjustedString.setCharAt(mirror, c);
}
 
/* Create the descriptor / mode message */
final String descriptor = createDescriptor(compact, layers, dataBlocks);
 
/* Plot all of the data into the symbol in pre-defined spiral pattern */
if (compact) {
 
this.readable = "";
this.row_count = 27 - 2 * AZTEC_COMPACT_OFFSET[layers - 1];
this.row_height = new int[this.row_count];
this.row_height[0] = -1;
this.pattern = new String[this.row_count];
for (int y = AZTEC_COMPACT_OFFSET[layers - 1]; y < 27 - AZTEC_COMPACT_OFFSET[layers - 1]; y++) {
final StringBuilder bin = new StringBuilder(27);
for (int x = AZTEC_COMPACT_OFFSET[layers - 1]; x < 27 - AZTEC_COMPACT_OFFSET[layers - 1]; x++) {
final int j = COMPACT_AZTEC_MAP[y * 27 + x];
if (j == 0) {
bin.append('0');
}
if (j == 1) {
bin.append('1');
}
if (j >= 2) {
if (j - 2 < adjustedString.length()) {
bin.append(adjustedString.charAt(j - 2));
} else {
if (j >= 2000) {
bin.append(descriptor.charAt(j - 2000));
} else {
bin.append('0');
}
}
}
}
this.row_height[y - AZTEC_COMPACT_OFFSET[layers - 1]] = 1;
this.pattern[y - AZTEC_COMPACT_OFFSET[layers - 1]] = bin2pat(bin);
}
 
} else {
 
this.readable = "";
this.row_count = 151 - 2 * AZTEC_OFFSET[layers - 1];
this.row_height = new int[this.row_count];
this.row_height[0] = -1;
this.pattern = new String[this.row_count];
for (int y = AZTEC_OFFSET[layers - 1]; y < 151 - AZTEC_OFFSET[layers - 1]; y++) {
final StringBuilder bin = new StringBuilder(151);
for (int x = AZTEC_OFFSET[layers - 1]; x < 151 - AZTEC_OFFSET[layers - 1]; x++) {
final int j = AZTEC_MAP[x][y];
if (j == 1) {
bin.append('1');
}
if (j == 0) {
bin.append('0');
}
if (j >= 2) {
if (j - 2 < adjustedString.length()) {
bin.append(adjustedString.charAt(j - 2));
} else {
if (j >= 20000) {
bin.append(descriptor.charAt(j - 20000));
} else {
bin.append('0');
}
}
}
}
this.row_height[y - AZTEC_OFFSET[layers - 1]] = 1;
this.pattern[y - AZTEC_OFFSET[layers - 1]] = bin2pat(bin);
}
}
}
 
private String generateAztecBinary() {
 
/* Encode input data into a binary string */
int i, j, k, bytes;
int curtable, newtable, lasttable, chartype, maplength, blocks;
final int[] charmap = new int[2 * this.inputData.length];
final int[] typemap = new int[2 * this.inputData.length];
final int[] blockType = new int[this.inputData.length + 1];
final int[] blockLength = new int[this.inputData.length + 1];
 
/* Lookup input string in encoding table */
maplength = 0;
 
/* Add FNC1 to beginning of GS1 messages */
if (this.inputDataType == DataType.GS1) {
charmap[maplength] = 0; // FLG
typemap[maplength++] = 8; // PUNC
charmap[maplength] = 400; // (0)
typemap[maplength++] = 8; // PUNC
}
 
if (this.eciMode != 3) {
int flagNumber;
 
charmap[maplength] = 0; // FLG
typemap[maplength++] = 8; // PUNC
 
flagNumber = 6;
 
if (this.eciMode < 100000) {
flagNumber = 5;
}
 
if (this.eciMode < 10000) {
flagNumber = 4;
}
 
if (this.eciMode < 1000) {
flagNumber = 3;
}
 
if (this.eciMode < 100) {
flagNumber = 2;
}
 
if (this.eciMode < 10) {
flagNumber = 1;
}
 
charmap[maplength] = 400 + flagNumber;
typemap[maplength++] = 8; // PUNC
}
 
for (i = 0; i < this.inputData.length; i++) {
if (this.inputData[i] == FNC1) {
/* FNC1 represented by FLG(0) */
charmap[maplength] = 0; // FLG
typemap[maplength++] = 8; // PUNC
charmap[maplength] = 400; // (0)
typemap[maplength++] = 8; // PUNC
} else {
if (this.inputData[i] > 0x7F || this.inputData[i] == 0x00) {
charmap[maplength] = this.inputData[i];
typemap[maplength++] = 32; // BINARY
} else {
charmap[maplength] = AZTEC_SYMBOL_CHAR[this.inputData[i]];
typemap[maplength++] = AZTEC_CODE_SET[this.inputData[i]];
}
}
}
 
/* Look for double character encoding possibilities */
for (i = 0; i < maplength - 1; i++) {
if (charmap[i] == 300 && charmap[i + 1] == 11 && typemap[i] == 12 && typemap[i + 1] == 4) {
/* CR LF combination */
charmap[i] = 2;
typemap[i] = 8; // PUNC
if (i + 1 != maplength) {
for (j = i + 1; j < maplength; j++) {
charmap[j] = charmap[j + 1];
typemap[j] = typemap[j + 1];
}
}
maplength--;
}
 
if (charmap[i] == 302 && charmap[i + 1] == 1 && typemap[i] == 24 && typemap[i + 1] == 23) {
/* . SP combination */
charmap[i] = 3;
typemap[i] = 8; // PUNC;
if (i + 1 != maplength) {
for (j = i + 1; j < maplength; j++) {
charmap[j] = charmap[j + 1];
typemap[j] = typemap[j + 1];
}
}
maplength--;
}
 
if (charmap[i] == 301 && charmap[i + 1] == 1 && typemap[i] == 24 && typemap[i + 1] == 23) {
/* , SP combination */
charmap[i] = 4;
typemap[i] = 8; // PUNC;
if (i + 1 != maplength) {
for (j = i + 1; j < maplength; j++) {
charmap[j] = charmap[j + 1];
typemap[j] = typemap[j + 1];
}
}
maplength--;
}
 
if (charmap[i] == 21 && charmap[i + 1] == 1 && typemap[i] == 8 && typemap[i + 1] == 23) {
/* : SP combination */
charmap[i] = 5;
typemap[i] = 8; // PUNC;
if (i + 1 != maplength) {
for (j = i + 1; j < maplength; j++) {
charmap[j] = charmap[j + 1];
typemap[j] = typemap[j + 1];
}
}
maplength--;
}
}
 
/* look for blocks of characters which use the same table */
blocks = 0;
for (i = 0; i < maplength; i++) {
if (i > 0 && typemap[i] == typemap[i - 1]) {
blockLength[blocks - 1]++;
} else {
blocks++;
blockType[blocks - 1] = typemap[i];
blockLength[blocks - 1] = 1;
}
}
 
if ((blockType[0] & 1) != 0) {
blockType[0] = 1;
}
if ((blockType[0] & 2) != 0) {
blockType[0] = 2;
}
if ((blockType[0] & 4) != 0) {
blockType[0] = 4;
}
if ((blockType[0] & 8) != 0) {
blockType[0] = 8;
}
 
if (blocks > 1) {
 
/* look for adjacent blocks which can use the same table (left to right search) */
for (i = 1; i < blocks; i++) {
if ((blockType[i] & blockType[i - 1]) != 0) {
blockType[i] = blockType[i] & blockType[i - 1];
}
}
 
if ((blockType[blocks - 1] & 1) != 0) {
blockType[blocks - 1] = 1;
}
if ((blockType[blocks - 1] & 2) != 0) {
blockType[blocks - 1] = 2;
}
if ((blockType[blocks - 1] & 4) != 0) {
blockType[blocks - 1] = 4;
}
if ((blockType[blocks - 1] & 8) != 0) {
blockType[blocks - 1] = 8;
}
 
/* look for adjacent blocks which can use the same table (right to left search) */
for (i = blocks - 2; i > 0; i--) {
if ((blockType[i] & blockType[i + 1]) != 0) {
blockType[i] = blockType[i] & blockType[i + 1];
}
}
 
/* determine the encoding table for characters which do not fit with adjacent blocks */
for (i = 1; i < blocks; i++) {
if ((blockType[i] & 8) != 0) {
blockType[i] = 8;
}
if ((blockType[i] & 4) != 0) {
blockType[i] = 4;
}
if ((blockType[i] & 2) != 0) {
blockType[i] = 2;
}
if ((blockType[i] & 1) != 0) {
blockType[i] = 1;
}
}
 
/*
* if less than 4 characters are preceded and followed by binary blocks then it is more
* efficient to also encode these in binary
*/
 
// for (i = 1; i < blocks - 1; i++) {
// if ((blockType[i - 1] == 32) && (blockLength[i] < 4)) {
// int nonBinaryLength = blockLength[i];
// for (int l = i; ((l < blocks) && (blockType[l] != 32)); l++) {
// nonBinaryLength += blockLength[l];
// }
// if (nonBinaryLength < 4) {
// blockType[i] = 32;
// }
// }
// }
 
/* Combine blocks of the same type */
i = 0;
do {
if (blockType[i] == blockType[i + 1]) {
blockLength[i] += blockLength[i + 1];
for (j = i + 1; j < blocks - 1; j++) {
blockType[j] = blockType[j + 1];
blockLength[j] = blockLength[j + 1];
}
blocks--;
} else {
i++;
}
} while (i < blocks - 1);
}
 
/* Put the adjusted block data back into typemap */
j = 0;
for (i = 0; i < blocks; i++) {
if (blockLength[i] < 3 && blockType[i] != 32) { /* Shift character(s) needed */
 
for (k = 0; k < blockLength[i]; k++) {
typemap[j + k] = blockType[i] + 64;
}
} else { /* Latch character (or byte mode) needed */
 
for (k = 0; k < blockLength[i]; k++) {
typemap[j + k] = blockType[i];
}
}
j += blockLength[i];
}
 
/* Don't shift an initial capital letter */
if (maplength > 0 && typemap[0] == 65) {
typemap[0] = 1;
}
 
/*
* Problem characters (those that appear in different tables with different values) can now
* be resolved into their tables
*/
for (i = 0; i < maplength; i++) {
if (charmap[i] >= 300 && charmap[i] < 400) {
curtable = typemap[i];
if (curtable > 64) {
curtable -= 64;
}
switch (charmap[i]) {
case 300:
/* Carriage Return */
switch (curtable) {
case 8:
charmap[i] = 1;
break; // PUNC
case 4:
charmap[i] = 14;
break; // PUNC
}
break;
case 301:
/* Comma */
switch (curtable) {
case 8:
charmap[i] = 17;
break; // PUNC
case 16:
charmap[i] = 12;
break; // DIGIT
}
break;
case 302:
/* Full Stop */
switch (curtable) {
case 8:
charmap[i] = 19;
break; // PUNC
case 16:
charmap[i] = 13;
break; // DIGIT
}
break;
}
}
}
 
final StringBuilder binaryString = new StringBuilder();
info("Encoding: ");
curtable = 1; /* start with 1 table */
lasttable = 1;
 
/* Optional structured append start flag (Section 8 of spec) */
if (this.structuredAppendTotal != 1) {
binaryString.append(PENTBIT[29]);
info("ML ");
binaryString.append(PENTBIT[29]);
info("UL ");
}
 
for (i = 0; i < maplength; i++) {
newtable = curtable;
if (typemap[i] != curtable && charmap[i] < 400) {
/* Change table */
if (curtable == 32) {
/*
* If ending binary mode the current table is the same as when entering binary
* mode
*/
curtable = lasttable;
newtable = lasttable;
}
if (typemap[i] > 64) {
/* Shift character */
switch (typemap[i]) {
case 64 + 1:
/* To UPPER */
switch (curtable) {
case 2:
/* US */
binaryString.append(PENTBIT[28]);
info("US ");
break;
case 4:
/* UL */
binaryString.append(PENTBIT[29]);
info("UL ");
newtable = 1;
break;
case 8:
/* UL */
binaryString.append(PENTBIT[31]);
info("UL ");
newtable = 1;
break;
case 16:
/* US */
binaryString.append(QUADBIT[15]);
info("US ");
break;
}
break;
case 64 + 2:
/* To LOWER */
switch (curtable) {
case 1:
/* LL */
binaryString.append(PENTBIT[28]);
info("LL ");
newtable = 2;
break;
case 4:
/* LL */
binaryString.append(PENTBIT[28]);
info("LL ");
newtable = 2;
break;
case 8:
/* UL LL */
binaryString.append(PENTBIT[31]);
info("UL ");
binaryString.append(PENTBIT[28]);
info("LL ");
newtable = 2;
break;
case 16:
/* UL LL */
binaryString.append(QUADBIT[14]);
info("UL ");
binaryString.append(PENTBIT[28]);
info("LL ");
newtable = 2;
break;
}
break;
case 64 + 4:
/* To MIXED */
switch (curtable) {
case 1:
/* ML */
binaryString.append(PENTBIT[29]);
info("ML ");
newtable = 4;
break;
case 2:
/* ML */
binaryString.append(PENTBIT[29]);
info("ML ");
newtable = 4;
break;
case 8:
/* UL ML */
binaryString.append(PENTBIT[31]);
info("UL ");
binaryString.append(PENTBIT[29]);
info("ML ");
newtable = 4;
break;
case 16:
/* UL ML */
binaryString.append(QUADBIT[14]);
info("UL ");
binaryString.append(PENTBIT[29]);
info("ML ");
newtable = 4;
break;
}
break;
case 64 + 8:
/* To PUNC */
switch (curtable) {
case 1:
/* PS */
binaryString.append(PENTBIT[0]);
info("PS ");
break;
case 2:
/* PS */
binaryString.append(PENTBIT[0]);
info("PS ");
break;
case 4:
/* PS */
binaryString.append(PENTBIT[0]);
info("PS ");
break;
case 16:
/* PS */
binaryString.append(QUADBIT[0]);
info("PS ");
break;
}
break;
case 64 + 16:
/* To DIGIT */
switch (curtable) {
case 1:
/* DL */
binaryString.append(PENTBIT[30]);
info("DL ");
newtable = 16;
break;
case 2:
/* DL */
binaryString.append(PENTBIT[30]);
info("DL ");
newtable = 16;
break;
case 4:
/* UL DL */
binaryString.append(PENTBIT[29]);
info("UL ");
binaryString.append(PENTBIT[30]);
info("DL ");
newtable = 16;
break;
case 8:
/* UL DL */
binaryString.append(PENTBIT[31]);
info("UL ");
binaryString.append(PENTBIT[30]);
info("DL ");
newtable = 16;
break;
}
break;
}
} else {
/* Latch character */
switch (typemap[i]) {
case 1:
/* To UPPER */
switch (curtable) {
case 2:
/* ML UL */
binaryString.append(PENTBIT[29]);
info("ML ");
binaryString.append(PENTBIT[29]);
info("UL ");
newtable = 1;
break;
case 4:
/* UL */
binaryString.append(PENTBIT[29]);
info("UL ");
newtable = 1;
break;
case 8:
/* UL */
binaryString.append(PENTBIT[31]);
info("UL ");
newtable = 1;
break;
case 16:
/* UL */
binaryString.append(QUADBIT[14]);
info("UL ");
newtable = 1;
break;
}
break;
case 2:
/* To LOWER */
switch (curtable) {
case 1:
/* LL */
binaryString.append(PENTBIT[28]);
info("LL ");
newtable = 2;
break;
case 4:
/* LL */
binaryString.append(PENTBIT[28]);
info("LL ");
newtable = 2;
break;
case 8:
/* UL LL */
binaryString.append(PENTBIT[31]);
info("UL ");
binaryString.append(PENTBIT[28]);
info("LL ");
newtable = 2;
break;
case 16:
/* UL LL */
binaryString.append(QUADBIT[14]);
info("UL ");
binaryString.append(PENTBIT[28]);
info("LL ");
newtable = 2;
break;
}
break;
case 4:
/* To MIXED */
switch (curtable) {
case 1:
/* ML */
binaryString.append(PENTBIT[29]);
info("ML ");
newtable = 4;
break;
case 2:
/* ML */
binaryString.append(PENTBIT[29]);
info("ML ");
newtable = 4;
break;
case 8:
/* UL ML */
binaryString.append(PENTBIT[31]);
info("UL ");
binaryString.append(PENTBIT[29]);
info("ML ");
newtable = 4;
break;
case 16:
/* UL ML */
binaryString.append(QUADBIT[14]);
info("UL ");
binaryString.append(PENTBIT[29]);
info("ML ");
newtable = 4;
break;
}
break;
case 8:
/* To PUNC */
switch (curtable) {
case 1:
/* ML PL */
binaryString.append(PENTBIT[29]);
info("ML ");
binaryString.append(PENTBIT[30]);
info("PL ");
newtable = 8;
break;
case 2:
/* ML PL */
binaryString.append(PENTBIT[29]);
info("ML ");
binaryString.append(PENTBIT[30]);
info("PL ");
newtable = 8;
break;
case 4:
/* PL */
binaryString.append(PENTBIT[30]);
info("PL ");
newtable = 8;
break;
case 16:
/* UL ML PL */
binaryString.append(QUADBIT[14]);
info("UL ");
binaryString.append(PENTBIT[29]);
info("ML ");
binaryString.append(PENTBIT[30]);
info("PL ");
newtable = 8;
break;
}
break;
case 16:
/* To DIGIT */
switch (curtable) {
case 1:
/* DL */
binaryString.append(PENTBIT[30]);
info("DL ");
newtable = 16;
break;
case 2:
/* DL */
binaryString.append(PENTBIT[30]);
info("DL ");
newtable = 16;
break;
case 4:
/* UL DL */
binaryString.append(PENTBIT[29]);
info("UL ");
binaryString.append(PENTBIT[30]);
info("DL ");
newtable = 16;
break;
case 8:
/* UL DL */
binaryString.append(PENTBIT[31]);
info("UL ");
binaryString.append(PENTBIT[30]);
info("DL ");
newtable = 16;
break;
}
break;
case 32:
/* To BINARY */
lasttable = curtable;
switch (curtable) {
case 1:
/* BS */
binaryString.append(PENTBIT[31]);
info("BS ");
newtable = 32;
break;
case 2:
/* BS */
binaryString.append(PENTBIT[31]);
info("BS ");
newtable = 32;
break;
case 4:
/* BS */
binaryString.append(PENTBIT[31]);
info("BS ");
newtable = 32;
break;
case 8:
/* UL BS */
binaryString.append(PENTBIT[31]);
info("UL ");
binaryString.append(PENTBIT[31]);
info("BS ");
lasttable = 1;
newtable = 32;
break;
case 16:
/* UL BS */
binaryString.append(QUADBIT[14]);
info("UL ");
binaryString.append(PENTBIT[31]);
info("BS ");
lasttable = 1;
newtable = 32;
break;
}
 
bytes = 0;
do {
bytes++;
} while (typemap[i + bytes - 1] == 32);
bytes--;
 
if (bytes > 2079) {
throw new OkapiException("Input too long");
}
 
if (bytes > 31) {
/* Put 00000 followed by 11-bit number of bytes less 31 */
binaryString.append("00000");
for (int weight = 0x400; weight > 0; weight = weight >> 1) {
if ((bytes - 31 & weight) != 0) {
binaryString.append('1');
} else {
binaryString.append('0');
}
}
} else {
/* Put 5-bit number of bytes */
for (int weight = 0x10; weight > 0; weight = weight >> 1) {
if ((bytes & weight) != 0) {
binaryString.append('1');
} else {
binaryString.append('0');
}
}
}
 
break;
}
}
}
/* Add data to the binary string */
curtable = newtable;
chartype = typemap[i];
if (chartype > 64) {
chartype -= 64;
}
switch (chartype) {
case 1:
case 2:
case 4:
case 8:
if (charmap[i] >= 400) {
info("FLG(" + (charmap[i] - 400) + ") ");
binaryString.append(TRIBIT[charmap[i] - 400]);
if (charmap[i] != 400) {
/* ECI */
binaryString.append(eciToBinary());
}
} else {
binaryString.append(PENTBIT[charmap[i]]);
infoSpace(charmap[i]);
}
break;
case 16:
binaryString.append(QUADBIT[charmap[i]]);
infoSpace(charmap[i]);
break;
case 32:
for (int weight = 0x80; weight > 0; weight = weight >> 1) {
if ((charmap[i] & weight) != 0) {
binaryString.append('1');
} else {
binaryString.append('0');
}
}
infoSpace(charmap[i]);
break;
}
}
 
infoLine();
 
return binaryString.toString();
}
 
/** Adjusts bit stream so that no codewords are all 0s or all 1s, per Section 7.3.1.2 */
private StringBuilder adjustBinaryString(final String binaryString, final boolean compact, final int layers) {
 
final StringBuilder adjustedString = new StringBuilder();
final int codewordSize = getCodewordSize(layers);
int ones = 0;
 
/* Insert dummy digits needed to prevent codewords of all 0s or all 1s */
for (int i = 0; i < binaryString.length(); i++) {
if ((adjustedString.length() + 1) % codewordSize == 0) {
if (ones == codewordSize - 1) {
// codeword of B-1 1s, add dummy 0
adjustedString.append('0');
i--;
} else if (ones == 0) {
// codeword of B-1 0s, add dummy 1
adjustedString.append('1');
i--;
} else {
// no dummy value needed
adjustedString.append(binaryString.charAt(i));
}
ones = 0;
} else {
adjustedString.append(binaryString.charAt(i));
if (binaryString.charAt(i) == '1') {
ones++;
}
}
}
 
/* Add padding */
int adjustedLength = adjustedString.length();
final int remainder = adjustedLength % codewordSize;
int padBits = codewordSize - remainder;
if (padBits == codewordSize) {
padBits = 0;
}
for (int i = 0; i < padBits; i++) {
adjustedString.append('1');
}
adjustedLength = adjustedString.length();
 
/* Make sure padding didn't create an invalid (all 1s) codeword */
ones = 0;
for (int i = adjustedLength - codewordSize; i < adjustedLength && i >= 0; i++) {
if (adjustedString.charAt(i) == '1') {
ones++;
}
}
if (ones == codewordSize) {
adjustedString.setCharAt(adjustedLength - 1, '0');
}
 
/* Log the codewords */
info("Codewords: ");
for (int i = 0; i < adjustedLength / codewordSize; i++) {
int l = 0, m = 1 << codewordSize - 1;
for (int j = 0; j < codewordSize; j++) {
if (adjustedString.charAt(i * codewordSize + j) == '1') {
l += m;
}
m = m >> 1;
}
infoSpace(l);
}
infoLine();
 
/* Return the adjusted bit string */
return adjustedString;
}
 
private String eciToBinary() {
final String eciNumber = Integer.toString(this.eciMode);
final StringBuilder binary = new StringBuilder(4 * eciNumber.length());
for (int i = 0; i < eciNumber.length(); i++) {
binary.append(QUADBIT[eciNumber.charAt(i) - '0' + 2]);
infoSpace(eciNumber.charAt(i));
}
return binary.toString();
}
 
/** Creates the descriptor / mode message, per Section 7.2 */
private String createDescriptor(final boolean compact, final int layers, final int dataBlocks) {
 
final StringBuilder descriptor = new StringBuilder();
int descDataSize;
 
if (compact) {
/* The first 2 bits represent the number of layers minus 1 */
if ((layers - 1 & 0x02) != 0) {
descriptor.append('1');
} else {
descriptor.append('0');
}
if ((layers - 1 & 0x01) != 0) {
descriptor.append('1');
} else {
descriptor.append('0');
}
/* The next 6 bits represent the number of data blocks minus 1 */
if (this.readerInit) {
descriptor.append('1');
} else {
if ((dataBlocks - 1 & 0x20) != 0) {
descriptor.append('1');
} else {
descriptor.append('0');
}
}
for (int i = 0x10; i > 0; i = i >> 1) {
if ((dataBlocks - 1 & i) != 0) {
descriptor.append('1');
} else {
descriptor.append('0');
}
}
descDataSize = 2;
} else {
/* The first 5 bits represent the number of layers minus 1 */
for (int i = 0x10; i > 0; i = i >> 1) {
if ((layers - 1 & i) != 0) {
descriptor.append('1');
} else {
descriptor.append('0');
}
}
 
/* The next 11 bits represent the number of data blocks minus 1 */
if (this.readerInit) {
descriptor.append('1');
} else {
if ((dataBlocks - 1 & 0x400) != 0) {
descriptor.append('1');
} else {
descriptor.append('0');
}
}
for (int i = 0x200; i > 0; i = i >> 1) {
if ((dataBlocks - 1 & i) != 0) {
descriptor.append('1');
} else {
descriptor.append('0');
}
}
descDataSize = 4;
}
 
infoLine("Mode Message: " + descriptor);
 
/* Split into 4-bit codewords */
final int[] desc_data = new int[descDataSize];
for (int i = 0; i < descDataSize; i++) {
for (int weight = 0; weight < 4; weight++) {
if (descriptor.charAt(i * 4 + weight) == '1') {
desc_data[i] += 8 >> weight;
}
}
}
 
/*
* Add Reed-Solomon error correction with Galois Field GF(16) and prime modulus x^4 + x + 1
* (Section 7.2.3)
*/
final ReedSolomon rs = new ReedSolomon();
rs.init_gf(0x13);
if (compact) {
rs.init_code(5, 1);
rs.encode(2, desc_data);
final int[] desc_ecc = new int[6];
for (int i = 0; i < 5; i++) {
desc_ecc[i] = rs.getResult(i);
}
for (int i = 0; i < 5; i++) {
for (int weight = 0x08; weight > 0; weight = weight >> 1) {
if ((desc_ecc[4 - i] & weight) != 0) {
descriptor.append('1');
} else {
descriptor.append('0');
}
}
}
} else {
rs.init_code(6, 1);
rs.encode(4, desc_data);
final int[] desc_ecc = new int[6];
for (int i = 0; i < 6; i++) {
desc_ecc[i] = rs.getResult(i);
}
for (int i = 0; i < 6; i++) {
for (int weight = 0x08; weight > 0; weight = weight >> 1) {
if ((desc_ecc[5 - i] & weight) != 0) {
descriptor.append('1');
} else {
descriptor.append('0');
}
}
}
}
 
return descriptor.toString();
}
 
/**
* Adds error correction data to the specified binary string, which already contains the primary
* data
*/
private void addErrorCorrection(final StringBuilder adjustedString, final int codewordSize, final int dataBlocks, final int eccBlocks) {
 
int x, poly, startWeight;
 
/* Split into codewords and calculate Reed-Solomon error correction codes */
switch (codewordSize) {
case 6:
x = 32;
poly = 0x43;
startWeight = 0x20;
break;
case 8:
x = 128;
poly = 0x12d;
startWeight = 0x80;
break;
case 10:
x = 512;
poly = 0x409;
startWeight = 0x200;
break;
case 12:
x = 2048;
poly = 0x1069;
startWeight = 0x800;
break;
default:
throw new OkapiException("Unrecognized codeword size: " + codewordSize);
}
 
final ReedSolomon rs = new ReedSolomon();
final int[] data = new int[dataBlocks + 3];
final int[] ecc = new int[eccBlocks + 3];
 
for (int i = 0; i < dataBlocks; i++) {
for (int weight = 0; weight < codewordSize; weight++) {
if (adjustedString.charAt(i * codewordSize + weight) == '1') {
data[i] += x >> weight;
}
}
}
 
rs.init_gf(poly);
rs.init_code(eccBlocks, 1);
rs.encode(dataBlocks, data);
 
for (int i = 0; i < eccBlocks; i++) {
ecc[i] = rs.getResult(i);
}
 
for (int i = eccBlocks - 1; i >= 0; i--) {
for (int weight = startWeight; weight > 0; weight = weight >> 1) {
if ((ecc[i] & weight) != 0) {
adjustedString.append('1');
} else {
adjustedString.append('0');
}
}
}
}
 
/** Determines codeword bit length - Table 3 */
private static int getCodewordSize(final int layers) {
if (layers >= 23) {
return 12;
} else if (layers >= 9 && layers <= 22) {
return 10;
} else if (layers >= 3 && layers <= 8) {
return 8;
} else {
assert layers <= 2;
return 6;
}
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Nve18.java
New file
0,0 → 1,88
/*
* Copyright 2015 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
/**
* <p>
* Calculate NVE-18 (Nummer der Versandeinheit), also known as SSCC-18 (Serial Shipping Container
* Code).
*
* <p>
* Encodes a 17-digit number, adding a modulo-10 check digit.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class Nve18 extends Symbol {
 
@Override
protected void encode() {
 
String gs1Equivalent = "";
int zeroes;
int count = 0;
int c, cdigit;
int p = 0;
 
if (this.content.length() > 17) {
throw new OkapiException("Input data too long");
}
 
if (!this.content.matches("[0-9]+")) {
throw new OkapiException("Invalid characters in input");
}
 
// Add leading zeroes
zeroes = 17 - this.content.length();
for (int i = 0; i < zeroes; i++) {
gs1Equivalent += "0";
}
 
gs1Equivalent += this.content;
 
// Add Modulus-10 check digit
for (int i = gs1Equivalent.length() - 1; i >= 0; i--) {
c = Character.getNumericValue(gs1Equivalent.charAt(i));
if (p % 2 == 0) {
c = c * 3;
}
count += c;
p++;
}
cdigit = 10 - count % 10;
if (cdigit == 10) {
cdigit = 0;
}
 
infoLine("NVE Check Digit: " + cdigit);
 
this.content = "[00]" + gs1Equivalent + cdigit;
 
// Defer to Code 128
final Code128 code128 = new Code128();
code128.setDataType(DataType.GS1);
code128.setHumanReadableLocation(this.humanReadableLocation);
code128.setContent(this.content);
 
this.readable = code128.readable;
this.pattern = code128.pattern;
this.row_count = code128.row_count;
this.row_height = code128.row_height;
this.symbol_height = code128.symbol_height;
this.symbol_width = code128.symbol_width;
this.rectangles = code128.rectangles;
this.texts = code128.texts;
 
info(code128.encodeInfo);
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Ean.java
New file
0,0 → 1,352
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import static uk.org.okapibarcode.backend.HumanReadableLocation.BOTTOM;
import static uk.org.okapibarcode.backend.HumanReadableLocation.NONE;
import static uk.org.okapibarcode.backend.HumanReadableLocation.TOP;
 
import java.awt.geom.Rectangle2D;
 
/**
* <p>
* Implements EAN bar code symbology according to BS EN 797:1996.
*
* <p>
* European Article Number data can be encoded in EAN-8 or EAN-13 format requiring a 7-digit or
* 12-digit input respectively. EAN-13 numbers map to Global Trade Identification Numbers (GTIN)
* whereas EAN-8 symbols are generally for internal use only. Check digit is calculated and should
* not be in input data. Leading zeroes are added as required.
*
* <p>
* Add-on content can be appended to the main symbol content by adding a <tt>'+'</tt> character,
* followed by the add-on content (up to 5 digits).
*
* @author <a href="mailto:jakel2006@me.com">Robert Elliott</a>
*/
public class Ean extends Symbol {
 
public enum Mode {
EAN8, EAN13
};
 
private static final String[] EAN13_PARITY = { "AAAAAA", "AABABB", "AABBAB", "AABBBA", "ABAABB", "ABBAAB", "ABBBAA", "ABABAB", "ABABBA", "ABBABA" };
 
private static final String[] EAN_SET_A = { "3211", "2221", "2122", "1411", "1132", "1231", "1114", "1312", "1213", "3112" };
 
private static final String[] EAN_SET_B = { "1123", "1222", "2212", "1141", "2311", "1321", "4111", "2131", "3121", "2113" };
 
private Mode mode = Mode.EAN13;
private int guardPatternExtraHeight = 5;
private boolean linkageFlag;
private EanUpcAddOn addOn;
 
/** Creates a new instance. */
public Ean() {
this.humanReadableAlignment = HumanReadableAlignment.JUSTIFY;
}
 
/**
* Sets the EAN mode (EAN-8 or EAN-13). The default is EAN-13.
*
* @param mode the EAN mode (EAN-8 or EAN-13)
*/
public void setMode(final Mode mode) {
this.mode = mode;
}
 
/**
* Returns the EAN mode (EAN-8 or EAN-13).
*
* @return the EAN mode (EAN-8 or EAN-13)
*/
public Mode getMode() {
return this.mode;
}
 
/**
* Sets the extra height used for the guard patterns. The default value is <code>5</code>.
*
* @param guardPatternExtraHeight the extra height used for the guard patterns
*/
public void setGuardPatternExtraHeight(final int guardPatternExtraHeight) {
this.guardPatternExtraHeight = guardPatternExtraHeight;
}
 
/**
* Returns the extra height used for the guard patterns.
*
* @return the extra height used for the guard patterns
*/
public int getGuardPatternExtraHeight() {
return this.guardPatternExtraHeight;
}
 
/**
* Sets the linkage flag. If set to <code>true</code>, this symbol is part of a composite
* symbol.
*
* @param linkageFlag the linkage flag
*/
protected void setLinkageFlag(final boolean linkageFlag) {
this.linkageFlag = linkageFlag;
}
 
@Override
protected void encode() {
 
separateContent();
 
if (this.content.isEmpty()) {
throw new OkapiException("Missing EAN data");
}
 
if (this.mode == Mode.EAN8) {
ean8();
} else {
ean13();
}
}
 
private void separateContent() {
final int splitPoint = this.content.indexOf('+');
if (splitPoint == -1) {
// there is no add-on data
this.addOn = null;
} else if (splitPoint == this.content.length() - 1) {
// we found the add-on separator, but no add-on data
throw new OkapiException("Invalid add-on data");
} else {
// there is a '+' in the input data, use an add-on EAN2 or EAN5
this.addOn = new EanUpcAddOn();
this.addOn.font = this.font;
this.addOn.fontName = this.fontName;
this.addOn.fontSize = this.fontSize;
this.addOn.humanReadableLocation = this.humanReadableLocation == NONE ? NONE : TOP;
this.addOn.moduleWidth = this.moduleWidth;
this.addOn.default_height = this.default_height + this.guardPatternExtraHeight - 8;
this.addOn.setContent(this.content.substring(splitPoint + 1));
this.content = this.content.substring(0, splitPoint);
}
}
 
private void ean13() {
 
this.content = validateAndPad(this.content, 12);
 
final char check = calcDigit(this.content);
infoLine("Check Digit: " + check);
 
final String hrt = this.content + check;
final char parityChar = hrt.charAt(0);
final String parity = EAN13_PARITY[parityChar - '0'];
infoLine("Parity Digit: " + parityChar);
 
final StringBuilder dest = new StringBuilder("111");
for (int i = 1; i < 13; i++) {
if (i == 7) {
dest.append("11111");
}
if (i <= 6) {
if (parity.charAt(i - 1) == 'B') {
dest.append(EAN_SET_B[hrt.charAt(i) - '0']);
} else {
dest.append(EAN_SET_A[hrt.charAt(i) - '0']);
}
} else {
dest.append(EAN_SET_A[hrt.charAt(i) - '0']);
}
}
dest.append("111");
 
this.readable = hrt;
this.pattern = new String[] { dest.toString() };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private void ean8() {
 
this.content = validateAndPad(this.content, 7);
 
final char check = calcDigit(this.content);
infoLine("Check Digit: " + check);
 
final String hrt = this.content + check;
 
final StringBuilder dest = new StringBuilder("111");
for (int i = 0; i < 8; i++) {
if (i == 4) {
dest.append("11111");
}
dest.append(EAN_SET_A[hrt.charAt(i) - '0']);
}
dest.append("111");
 
this.readable = hrt;
this.pattern = new String[] { dest.toString() };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
protected static String validateAndPad(String s, final int targetLength) {
 
if (!s.matches("[0-9]+")) {
throw new OkapiException("Invalid characters in input");
}
 
if (s.length() > targetLength) {
throw new OkapiException("Input data too long");
}
 
if (s.length() < targetLength) {
for (int i = s.length(); i < targetLength; i++) {
s = '0' + s;
}
}
 
return s;
}
 
public static char calcDigit(final String s) {
 
int count = 0;
int p = 0;
 
for (int i = s.length() - 1; i >= 0; i--) {
int c = Character.getNumericValue(s.charAt(i));
if (p % 2 == 0) {
c = c * 3;
}
count += c;
p++;
}
 
int cdigit = 10 - count % 10;
if (cdigit == 10) {
cdigit = 0;
}
 
return (char) (cdigit + '0');
}
 
@Override
protected void plotSymbol() {
 
int xBlock;
int x, y, w, h;
boolean black = true;
final int compositeOffset = this.linkageFlag ? 6 : 0; // space for composite separator above
final int hrtOffset = this.humanReadableLocation == TOP ? getTheoreticalHumanReadableHeight() : 0; // space
// for
// HRT
// above
 
this.rectangles.clear();
this.texts.clear();
x = 0;
 
/* Draw the bars in the symbology */
for (xBlock = 0; xBlock < this.pattern[0].length(); xBlock++) {
 
w = this.pattern[0].charAt(xBlock) - '0';
 
if (black) {
y = 0;
h = this.default_height;
/* Add extension to guide bars */
if (this.mode == Mode.EAN13) {
if (x < 3 || x > 91 || x > 45 && x < 49) {
h += this.guardPatternExtraHeight;
}
if (this.linkageFlag && (x == 0 || x == 94)) {
h += 2;
y -= 2;
}
} else {
if (x < 3 || x > 62 || x > 30 && x < 35) {
h += this.guardPatternExtraHeight;
}
if (this.linkageFlag && (x == 0 || x == 66)) {
h += 2;
y -= 2;
}
}
final Rectangle2D.Double rect = new Rectangle2D.Double(scale(x), y + compositeOffset + hrtOffset, scale(w), h);
this.rectangles.add(rect);
this.symbol_width = Math.max(this.symbol_width, (int) rect.getMaxX());
this.symbol_height = Math.max(this.symbol_height, (int) rect.getHeight());
}
 
black = !black;
x += w;
}
 
/* Add separator for composite symbology, if necessary */
if (this.linkageFlag) {
if (this.mode == Mode.EAN13) {
this.rectangles.add(new Rectangle2D.Double(scale(0), 0, scale(1), 2));
this.rectangles.add(new Rectangle2D.Double(scale(94), 0, scale(1), 2));
this.rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
this.rectangles.add(new Rectangle2D.Double(scale(95), 2, scale(1), 2));
} else { // EAN8
this.rectangles.add(new Rectangle2D.Double(scale(0), 0, scale(1), 2));
this.rectangles.add(new Rectangle2D.Double(scale(66), 0, scale(1), 2));
this.rectangles.add(new Rectangle2D.Double(scale(-1), 2, scale(1), 2));
this.rectangles.add(new Rectangle2D.Double(scale(67), 2, scale(1), 2));
}
this.symbol_height += 4;
}
 
/* Now add the text */
if (this.humanReadableLocation == BOTTOM) {
this.symbol_height -= this.guardPatternExtraHeight;
final double baseline = this.symbol_height + this.fontSize;
if (this.mode == Mode.EAN13) {
this.texts.add(new TextBox(scale(-9), baseline, scale(4), this.readable.substring(0, 1), HumanReadableAlignment.RIGHT));
this.texts.add(new TextBox(scale(5), baseline, scale(39), this.readable.substring(1, 7), this.humanReadableAlignment));
this.texts.add(new TextBox(scale(51), baseline, scale(39), this.readable.substring(7, 13), this.humanReadableAlignment));
} else { // EAN8
this.texts.add(new TextBox(scale(5), baseline, scale(25), this.readable.substring(0, 4), this.humanReadableAlignment));
this.texts.add(new TextBox(scale(37), baseline, scale(25), this.readable.substring(4, 8), this.humanReadableAlignment));
}
} else if (this.humanReadableLocation == TOP) {
final double baseline = this.fontSize;
final int width = this.mode == Mode.EAN13 ? 94 : 66;
this.texts.add(new TextBox(scale(0), baseline, scale(width), this.readable, this.humanReadableAlignment));
}
 
/* Now add the add-on symbol, if necessary */
if (this.addOn != null) {
final int gap = 9;
final int baseX = this.symbol_width + scale(gap);
final Rectangle2D.Double r1 = this.rectangles.get(0);
final Rectangle2D.Double ar1 = this.addOn.rectangles.get(0);
final int baseY = (int) (r1.y + r1.getHeight() - ar1.y - ar1.getHeight());
for (final TextBox t : this.addOn.getTexts()) {
this.texts.add(new TextBox(baseX + t.x, baseY + t.y, t.width, t.text, t.alignment));
}
for (final Rectangle2D.Double r : this.addOn.getRectangles()) {
this.rectangles.add(new Rectangle2D.Double(baseX + r.x, baseY + r.y, r.width, r.height));
}
this.symbol_width += scale(gap) + this.addOn.symbol_width;
this.pattern[0] = this.pattern[0] + gap + this.addOn.pattern[0];
}
}
 
/** Scales the specified width or x-dimension according to the current module width. */
private int scale(final int w) {
return this.moduleWidth * w;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/DataBar14.java
New file
0,0 → 1,616
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import static uk.org.okapibarcode.backend.DataBarLimited.getWidths;
 
import java.math.BigInteger;
 
/**
* <p>
* Implements GS1 DataBar Omnidirectional and GS1 DataBar Truncated according to ISO/IEC 24724:2011.
*
* <p>
* Input data should be a 13-digit Global Trade Identification Number (GTIN) without check digit or
* Application Identifier [01].
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class DataBar14 extends Symbol {
 
public enum Mode {
/** DataBar-14 */
LINEAR,
/** DataBar-14 Omnidirectional */
OMNI,
/** DataBar-14 Omnidirectional Stacked */
STACKED
}
 
private static final int[] G_SUM_TABLE = { 0, 161, 961, 2015, 2715, 0, 336, 1036, 1516 };
 
private static final int[] T_TABLE = { 1, 10, 34, 70, 126, 4, 20, 48, 81 };
 
private static final int[] MODULES_ODD = { 12, 10, 8, 6, 4, 5, 7, 9, 11 };
 
private static final int[] MODULES_EVEN = { 4, 6, 8, 10, 12, 10, 8, 6, 4 };
 
private static final int[] WIDEST_ODD = { 8, 6, 4, 3, 1, 2, 4, 6, 8 };
 
private static final int[] WIDEST_EVEN = { 1, 3, 5, 6, 8, 7, 5, 3, 1 };
 
private static final int[] CHECKSUM_WEIGHT = { /* Table 5 */
1, 3, 9, 27, 2, 6, 18, 54, 4, 12, 36, 29, 8, 24, 72, 58, 16, 48, 65, 37, 32, 17, 51, 74, 64, 34, 23, 69, 49, 68, 46, 59 };
 
private static final int[] FINDER_PATTERN = { 3, 8, 2, 1, 1, 3, 5, 5, 1, 1, 3, 3, 7, 1, 1, 3, 1, 9, 1, 1, 2, 7, 4, 1, 1, 2, 5, 6, 1, 1, 2, 3, 8, 1, 1, 1, 5, 7, 1, 1, 1, 3, 9, 1, 1 };
 
private boolean linkageFlag;
private Mode mode = Mode.LINEAR;
 
@Override
public void setDataType(final DataType dummy) {
// Do nothing!
}
 
/**
* Although this is a GS1 symbology, input data is expected to omit the [01] Application
* Identifier, as well as the check digit. Thus, the input data is not considered GS1-format
* data.
*/
@Override
protected boolean gs1Supported() {
return false;
}
 
protected void setLinkageFlag(final boolean linkageFlag) {
this.linkageFlag = linkageFlag;
}
 
protected boolean getLinkageFlag() {
return this.linkageFlag;
}
 
/**
* Sets the symbol mode. The default is {@link Mode#LINEAR}.
*
* @param mode the symbol mode
*/
public void setMode(final Mode mode) {
this.mode = mode;
}
 
/**
* Returns the symbol mode.
*
* @return the symbol mode
*/
public Mode getMode() {
return this.mode;
}
 
@Override
protected void encode() {
 
final boolean[][] grid = new boolean[5][100];
BigInteger accum;
BigInteger left_reg;
BigInteger right_reg;
final int[] data_character = new int[4];
final int[] data_group = new int[4];
final int[] v_odd = new int[4];
final int[] v_even = new int[4];
int i;
final int[][] data_widths = new int[8][4];
int checksum;
int c_left;
int c_right;
final int[] total_widths = new int[46];
int writer;
char latch;
int j;
int count;
int check_digit;
final StringBuilder bin = new StringBuilder();
int compositeOffset = 0;
 
if (this.content.length() > 13) {
throw new OkapiException("Input too long");
}
 
if (!this.content.matches("[0-9]+?")) {
throw new OkapiException("Invalid characters in input");
}
 
accum = new BigInteger(this.content);
if (this.linkageFlag) {
accum = accum.add(new BigInteger("10000000000000"));
compositeOffset = 1;
}
 
/* Calculate left and right pair values */
left_reg = accum.divide(new BigInteger("4537077"));
right_reg = accum.mod(new BigInteger("4537077"));
 
/* Calculate four data characters */
accum = left_reg.divide(new BigInteger("1597"));
data_character[0] = accum.intValue();
accum = left_reg.mod(new BigInteger("1597"));
data_character[1] = accum.intValue();
accum = right_reg.divide(new BigInteger("1597"));
data_character[2] = accum.intValue();
accum = right_reg.mod(new BigInteger("1597"));
data_character[3] = accum.intValue();
 
info("Data Characters: ");
for (i = 0; i < 4; i++) {
infoSpace(data_character[i]);
}
infoLine();
 
/* Calculate odd and even subset values */
if (data_character[0] >= 0 && data_character[0] <= 160) {
data_group[0] = 0;
}
if (data_character[0] >= 161 && data_character[0] <= 960) {
data_group[0] = 1;
}
if (data_character[0] >= 961 && data_character[0] <= 2014) {
data_group[0] = 2;
}
if (data_character[0] >= 2015 && data_character[0] <= 2714) {
data_group[0] = 3;
}
if (data_character[0] >= 2715 && data_character[0] <= 2840) {
data_group[0] = 4;
}
if (data_character[1] >= 0 && data_character[1] <= 335) {
data_group[1] = 5;
}
if (data_character[1] >= 336 && data_character[1] <= 1035) {
data_group[1] = 6;
}
if (data_character[1] >= 1036 && data_character[1] <= 1515) {
data_group[1] = 7;
}
if (data_character[1] >= 1516 && data_character[1] <= 1596) {
data_group[1] = 8;
}
if (data_character[3] >= 0 && data_character[3] <= 335) {
data_group[3] = 5;
}
if (data_character[3] >= 336 && data_character[3] <= 1035) {
data_group[3] = 6;
}
if (data_character[3] >= 1036 && data_character[3] <= 1515) {
data_group[3] = 7;
}
if (data_character[3] >= 1516 && data_character[3] <= 1596) {
data_group[3] = 8;
}
if (data_character[2] >= 0 && data_character[2] <= 160) {
data_group[2] = 0;
}
if (data_character[2] >= 161 && data_character[2] <= 960) {
data_group[2] = 1;
}
if (data_character[2] >= 961 && data_character[2] <= 2014) {
data_group[2] = 2;
}
if (data_character[2] >= 2015 && data_character[2] <= 2714) {
data_group[2] = 3;
}
if (data_character[2] >= 2715 && data_character[2] <= 2840) {
data_group[2] = 4;
}
 
v_odd[0] = (data_character[0] - G_SUM_TABLE[data_group[0]]) / T_TABLE[data_group[0]];
v_even[0] = (data_character[0] - G_SUM_TABLE[data_group[0]]) % T_TABLE[data_group[0]];
v_odd[1] = (data_character[1] - G_SUM_TABLE[data_group[1]]) % T_TABLE[data_group[1]];
v_even[1] = (data_character[1] - G_SUM_TABLE[data_group[1]]) / T_TABLE[data_group[1]];
v_odd[3] = (data_character[3] - G_SUM_TABLE[data_group[3]]) % T_TABLE[data_group[3]];
v_even[3] = (data_character[3] - G_SUM_TABLE[data_group[3]]) / T_TABLE[data_group[3]];
v_odd[2] = (data_character[2] - G_SUM_TABLE[data_group[2]]) / T_TABLE[data_group[2]];
v_even[2] = (data_character[2] - G_SUM_TABLE[data_group[2]]) % T_TABLE[data_group[2]];
 
/* Use RSS subset width algorithm */
for (i = 0; i < 4; i++) {
if (i == 0 || i == 2) {
int[] widths = getWidths(v_odd[i], MODULES_ODD[data_group[i]], 4, WIDEST_ODD[data_group[i]], 1);
data_widths[0][i] = widths[0];
data_widths[2][i] = widths[1];
data_widths[4][i] = widths[2];
data_widths[6][i] = widths[3];
widths = getWidths(v_even[i], MODULES_EVEN[data_group[i]], 4, WIDEST_EVEN[data_group[i]], 0);
data_widths[1][i] = widths[0];
data_widths[3][i] = widths[1];
data_widths[5][i] = widths[2];
data_widths[7][i] = widths[3];
} else {
int[] widths = getWidths(v_odd[i], MODULES_ODD[data_group[i]], 4, WIDEST_ODD[data_group[i]], 0);
data_widths[0][i] = widths[0];
data_widths[2][i] = widths[1];
data_widths[4][i] = widths[2];
data_widths[6][i] = widths[3];
widths = getWidths(v_even[i], MODULES_EVEN[data_group[i]], 4, WIDEST_EVEN[data_group[i]], 1);
data_widths[1][i] = widths[0];
data_widths[3][i] = widths[1];
data_widths[5][i] = widths[2];
data_widths[7][i] = widths[3];
}
}
 
/* Calculate the checksum */
checksum = 0;
for (i = 0; i < 8; i++) {
checksum += CHECKSUM_WEIGHT[i] * data_widths[i][0];
checksum += CHECKSUM_WEIGHT[i + 8] * data_widths[i][1];
checksum += CHECKSUM_WEIGHT[i + 16] * data_widths[i][2];
checksum += CHECKSUM_WEIGHT[i + 24] * data_widths[i][3];
}
checksum %= 79;
 
/* Calculate the two check characters */
if (checksum >= 8) {
checksum++;
}
if (checksum >= 72) {
checksum++;
}
c_left = checksum / 9;
c_right = checksum % 9;
 
infoLine("Checksum: " + checksum);
 
/* Put element widths together */
total_widths[0] = 1;
total_widths[1] = 1;
total_widths[44] = 1;
total_widths[45] = 1;
for (i = 0; i < 8; i++) {
total_widths[i + 2] = data_widths[i][0];
total_widths[i + 15] = data_widths[7 - i][1];
total_widths[i + 23] = data_widths[i][3];
total_widths[i + 36] = data_widths[7 - i][2];
}
for (i = 0; i < 5; i++) {
total_widths[i + 10] = FINDER_PATTERN[i + 5 * c_left];
total_widths[i + 31] = FINDER_PATTERN[4 - i + 5 * c_right];
}
 
this.row_count = 0;
 
final boolean[] separator = new boolean[100];
for (i = 0; i < separator.length; i++) {
separator[i] = false;
}
 
/* Put this data into the symbol */
if (this.mode == Mode.LINEAR) {
writer = 0;
latch = '0';
for (i = 0; i < 46; i++) {
for (j = 0; j < total_widths[i]; j++) {
if (latch == '1') {
grid[this.row_count][writer] = true;
}
writer++;
}
if (latch == '1') {
latch = '0';
} else {
latch = '1';
}
}
if (this.symbol_width < writer) {
this.symbol_width = writer;
}
 
if (this.linkageFlag) {
/* separator pattern for composite symbol */
for (i = 4; i < 92; i++) {
separator[i] = !grid[0][i];
}
latch = '1';
for (i = 16; i < 32; i++) {
if (!grid[0][i]) {
if (latch == '1') {
separator[i] = true;
latch = '0';
} else {
separator[i] = false;
latch = '1';
}
} else {
separator[i] = false;
latch = '1';
}
}
latch = '1';
for (i = 63; i < 78; i++) {
if (!grid[0][i]) {
if (latch == '1') {
separator[i] = true;
latch = '0';
} else {
separator[i] = false;
latch = '1';
}
} else {
separator[i] = false;
latch = '1';
}
}
}
this.row_count = this.row_count + 1;
 
count = 0;
check_digit = 0;
 
/* Calculate check digit from Annex A and place human readable text */
final StringBuilder hrt = new StringBuilder(14);
for (i = this.content.length(); i < 13; i++) {
hrt.append('0');
}
hrt.append(this.content);
for (i = 0; i < 13; i++) {
count += hrt.charAt(i) - '0';
if ((i & 1) == 0) {
count += 2 * (hrt.charAt(i) - '0');
}
}
check_digit = 10 - count % 10;
if (check_digit == 10) {
check_digit = 0;
}
infoLine("Check Digit: " + check_digit);
hrt.append((char) (check_digit + '0'));
this.readable = "(01)" + hrt;
}
 
if (this.mode == Mode.STACKED) {
/* top row */
writer = 0;
latch = '0';
for (i = 0; i < 23; i++) {
for (j = 0; j < total_widths[i]; j++) {
grid[this.row_count][writer] = latch == '1';
writer++;
}
if (latch == '1') {
latch = '0';
} else {
latch = '1';
}
}
grid[this.row_count][writer] = true;
grid[this.row_count][writer + 1] = false;
 
/* bottom row */
this.row_count = this.row_count + 2;
grid[this.row_count][0] = true;
grid[this.row_count][1] = false;
writer = 0;
latch = '1';
for (i = 23; i < 46; i++) {
for (j = 0; j < total_widths[i]; j++) {
grid[this.row_count][writer + 2] = latch == '1';
writer++;
}
if (latch == '1') {
latch = '0';
} else {
latch = '1';
}
}
 
/* separator pattern */
for (i = 1; i < 46; i++) {
if (grid[this.row_count - 2][i] == grid[this.row_count][i]) {
if (!grid[this.row_count - 2][i]) {
grid[this.row_count - 1][i] = true;
}
} else {
if (!grid[this.row_count - 1][i - 1]) {
grid[this.row_count - 1][i] = true;
}
}
}
for (i = 0; i < 4; i++) {
grid[this.row_count - 1][i] = false;
}
 
if (this.linkageFlag) {
/* separator pattern for composite symbol */
for (i = 4; i < 46; i++) {
separator[i] = !grid[0][i];
}
latch = '1';
for (i = 16; i < 32; i++) {
if (!grid[0][i]) {
if (latch == '1') {
separator[i] = true;
latch = '0';
} else {
separator[i] = false;
latch = '1';
}
} else {
separator[i] = false;
latch = '1';
}
}
}
this.row_count = this.row_count + 1;
if (this.symbol_width < 50) {
this.symbol_width = 50;
}
}
 
if (this.mode == Mode.OMNI) {
/* top row */
writer = 0;
latch = '0';
for (i = 0; i < 23; i++) {
for (j = 0; j < total_widths[i]; j++) {
grid[this.row_count][writer] = latch == '1';
writer++;
}
latch = latch == '1' ? '0' : '1';
}
grid[this.row_count][writer] = true;
grid[this.row_count][writer + 1] = false;
 
/* bottom row */
this.row_count = this.row_count + 4;
grid[this.row_count][0] = true;
grid[this.row_count][1] = false;
writer = 0;
latch = '1';
for (i = 23; i < 46; i++) {
for (j = 0; j < total_widths[i]; j++) {
grid[this.row_count][writer + 2] = latch == '1';
writer++;
}
if (latch == '1') {
latch = '0';
} else {
latch = '1';
}
}
 
/* middle separator */
for (i = 5; i < 46; i += 2) {
grid[this.row_count - 2][i] = true;
}
 
/* top separator */
for (i = 4; i < 46; i++) {
if (!grid[this.row_count - 4][i]) {
grid[this.row_count - 3][i] = true;
}
}
latch = '1';
for (i = 17; i < 33; i++) {
if (!grid[this.row_count - 4][i]) {
if (latch == '1') {
grid[this.row_count - 3][i] = true;
latch = '0';
} else {
grid[this.row_count - 3][i] = false;
latch = '1';
}
} else {
grid[this.row_count - 3][i] = false;
latch = '1';
}
}
 
/* bottom separator */
for (i = 4; i < 46; i++) {
if (!grid[this.row_count][i]) {
grid[this.row_count - 1][i] = true;
}
}
latch = '1';
for (i = 16; i < 32; i++) {
if (!grid[this.row_count][i]) {
if (latch == '1') {
grid[this.row_count - 1][i] = true;
latch = '0';
} else {
grid[this.row_count - 1][i] = false;
latch = '1';
}
} else {
grid[this.row_count - 1][i] = false;
latch = '1';
}
}
 
if (this.symbol_width < 50) {
this.symbol_width = 50;
}
if (this.linkageFlag) {
/* separator pattern for composite symbol */
for (i = 4; i < 46; i++) {
separator[i] = !grid[0][i];
}
latch = '1';
for (i = 16; i < 32; i++) {
if (!grid[0][i]) {
if (latch == '1') {
separator[i] = true;
latch = '0';
} else {
separator[i] = false;
latch = '1';
}
} else {
separator[i] = false;
latch = '1';
}
}
}
this.row_count = this.row_count + 1;
}
 
this.pattern = new String[this.row_count + compositeOffset];
this.row_height = new int[this.row_count + compositeOffset];
 
if (this.linkageFlag) {
bin.setLength(0);
for (j = 0; j < this.symbol_width; j++) {
if (separator[j]) {
bin.append('1');
} else {
bin.append('0');
}
}
this.pattern[0] = bin2pat(bin);
this.row_height[0] = 1;
}
 
for (i = 0; i < this.row_count; i++) {
bin.setLength(0);
for (j = 0; j < this.symbol_width; j++) {
if (grid[i][j]) {
bin.append('1');
} else {
bin.append('0');
}
}
this.pattern[i + compositeOffset] = bin2pat(bin);
}
 
if (this.mode == Mode.LINEAR) {
this.row_height[0 + compositeOffset] = -1;
}
if (this.mode == Mode.STACKED) {
this.row_height[0 + compositeOffset] = 5;
this.row_height[1 + compositeOffset] = 1;
this.row_height[2 + compositeOffset] = 7;
}
if (this.mode == Mode.OMNI) {
this.row_height[0 + compositeOffset] = -1;
this.row_height[1 + compositeOffset] = 1;
this.row_height[2 + compositeOffset] = 1;
this.row_height[3 + compositeOffset] = 1;
this.row_height[4 + compositeOffset] = -1;
}
 
if (this.linkageFlag) {
this.row_count++;
}
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/ChannelCode.java
New file
0,0 → 1,167
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
/**
* <p>
* Implements Channel Code according to ANSI/AIM BC12-1998.
*
* <p>
* Channel Code encodes whole integer values between 0 and 7,742,862.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class ChannelCode extends Symbol {
 
private int preferredNumberOfChannels;
 
private final int[] space = new int[11];
private final int[] bar = new int[11];
private double currentValue;
private double targetValue;
 
/**
* Sets the preferred number of channels used to encode data. This setting will be ignored if
* the value to be encoded requires more channels.
*
* @param channels the preferred number of channels (3 to 8, inclusive)
*/
public void setPreferredNumberOfChannels(final int channels) {
if (channels < 3 || channels > 8) {
throw new IllegalArgumentException("Invalid Channel Code number of channels: " + channels);
}
this.preferredNumberOfChannels = channels;
}
 
/**
* Returns the preferred number of channels used to encode data.
*
* @return the preferred number of channels used to encode data
*/
public int getPreferredNumberOfChannels() {
return this.preferredNumberOfChannels;
}
 
@Override
protected void encode() {
 
int channels;
int i;
int leadingZeroCount;
 
if (this.content.length() > 7) {
throw new OkapiException("Input too long");
}
 
if (!this.content.matches("[0-9]+")) {
throw new OkapiException("Invalid characters in input");
}
 
if (this.preferredNumberOfChannels <= 2 || this.preferredNumberOfChannels > 8) {
channels = 3;
} else {
channels = this.preferredNumberOfChannels;
}
 
this.targetValue = Integer.parseInt(this.content);
 
switch (channels) {
case 3:
if (this.targetValue > 26) {
channels++;
}
case 4:
if (this.targetValue > 292) {
channels++;
}
case 5:
if (this.targetValue > 3493) {
channels++;
}
case 6:
if (this.targetValue > 44072) {
channels++;
}
case 7:
if (this.targetValue > 576688) {
channels++;
}
case 8:
if (this.targetValue > 7742862) {
channels++;
}
}
 
if (channels == 9) {
throw new OkapiException("Value out of range");
}
 
infoLine("Channels Used: " + channels);
 
for (i = 0; i < 11; i++) {
this.bar[i] = 0;
this.space[i] = 0;
}
 
this.bar[0] = this.space[1] = this.bar[1] = this.space[2] = this.bar[2] = 1;
this.currentValue = 0;
this.pattern = new String[1];
nextSpace(channels, 3, channels, channels);
 
leadingZeroCount = channels - 1 - this.content.length();
 
this.readable = "";
for (i = 0; i < leadingZeroCount; i++) {
this.readable += "0";
}
this.readable += this.content;
 
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private void nextSpace(final int channels, final int i, final int maxSpace, final int maxBar) {
for (int s = i < channels + 2 ? 1 : maxSpace; s <= maxSpace; s++) {
this.space[i] = s;
nextBar(channels, i, maxBar, maxSpace + 1 - s);
}
}
 
private void nextBar(final int channels, final int i, final int maxBar, final int maxSpace) {
int b = this.space[i] + this.bar[i - 1] + this.space[i - 1] + this.bar[i - 2] > 4 ? 1 : 2;
if (i < channels + 2) {
for (; b <= maxBar; b++) {
this.bar[i] = b;
nextSpace(channels, i + 1, maxSpace, maxBar + 1 - b);
}
} else if (b <= maxBar) {
this.bar[i] = maxBar;
checkIfDone();
this.currentValue++;
}
}
 
private void checkIfDone() {
if (this.currentValue == this.targetValue) {
/* Target reached - save the generated pattern */
final StringBuilder sb = new StringBuilder();
sb.append("11110");
for (int i = 0; i < 11; i++) {
sb.append((char) (this.space[i] + '0'));
sb.append((char) (this.bar[i] + '0'));
}
this.pattern[0] = sb.toString();
}
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Pharmacode.java
New file
0,0 → 1,77
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
 
package uk.org.okapibarcode.backend;
 
/**
* Implements the <a href="http://en.wikipedia.org/wiki/Pharmacode">Pharmacode</a> bar code
* symbology. <br>
* Pharmacode is used for the identification of pharmaceuticals. The symbology is able to encode
* whole numbers between 3 and 131070.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class Pharmacode extends Symbol {
 
@Override
protected void encode() {
int tester = 0;
int i;
 
String inter = "";
String dest = "";
 
if (this.content.length() > 6) {
throw new OkapiException("Input too long");
}
 
if (!this.content.matches("[0-9]+")) {
throw new OkapiException("Invalid characters in data");
}
 
for (i = 0; i < this.content.length(); i++) {
tester *= 10;
tester += Character.getNumericValue(this.content.charAt(i));
}
 
if (tester < 3 || tester > 131070) {
throw new OkapiException("Data out of range");
}
 
do {
if ((tester & 1) == 0) {
inter += "W";
tester = (tester - 2) / 2;
} else {
inter += "N";
tester = (tester - 1) / 2;
}
} while (tester != 0);
 
for (i = inter.length() - 1; i >= 0; i--) {
if (inter.charAt(i) == 'W') {
dest += "32";
} else {
dest += "12";
}
}
 
this.readable = "";
this.pattern = new String[1];
this.pattern[0] = dest;
this.row_count = 1;
this.row_height = new int[1];
this.row_height[0] = -1;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Logmars.java
New file
0,0 → 1,98
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import static uk.org.okapibarcode.util.Arrays.positionOf;
 
/**
* Implements the LOGMARS (Logistics Applications of Automated Marking and Reading Symbols) standard
* used by the US Department of Defense. Input data can be of any length and supports the characters
* 0-9, A-Z, dash (-), full stop (.), space, dollar ($), slash (/), plus (+) and percent (%). A
* Modulo-43 check digit is calculated and added, and should not form part of the input data.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class Logmars extends Symbol {
 
private static final String[] CODE39LM = { "1113313111", "3113111131", "1133111131", "3133111111", "1113311131", "3113311111", "1133311111", "1113113131", "3113113111", "1133113111", "3111131131",
"1131131131", "3131131111", "1111331131", "3111331111", "1131331111", "1111133131", "3111133111", "1131133111", "1111333111", "3111111331", "1131111331", "3131111311", "1111311331",
"3111311311", "1131311311", "1111113331", "3111113311", "1131113311", "1111313311", "3311111131", "1331111131", "3331111111", "1311311131", "3311311111", "1331311111", "1311113131",
"3311113111", "1331113111", "1313131111", "1313111311", "1311131311", "1113131311" };
 
private static final char[] LOOKUP = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', '%' };
 
/** Ratio of wide bar width to narrow bar width. */
private double moduleWidthRatio = 3;
 
/**
* Sets the ratio of wide bar width to narrow bar width. Valid values are usually between
* {@code 2} and {@code 3}. The default value is {@code 3}.
*
* @param moduleWidthRatio the ratio of wide bar width to narrow bar width
*/
public void setModuleWidthRatio(final double moduleWidthRatio) {
this.moduleWidthRatio = moduleWidthRatio;
}
 
/**
* Returns the ratio of wide bar width to narrow bar width.
*
* @return the ratio of wide bar width to narrow bar width
*/
public double getModuleWidthRatio() {
return this.moduleWidthRatio;
}
 
/** {@inheritDoc} */
@Override
protected double getModuleWidth(final int originalWidth) {
if (originalWidth == 1) {
return 1;
} else {
return this.moduleWidthRatio;
}
}
 
/** {@inheritDoc} */
@Override
protected void encode() {
 
if (!this.content.matches("[0-9A-Z\\. \\-$/+%]*")) {
throw new OkapiException("Invalid characters in input");
}
 
String p = "";
final int l = this.content.length();
int charval, counter = 0;
char thischar;
char checkDigit;
for (int i = 0; i < l; i++) {
thischar = this.content.charAt(i);
charval = positionOf(thischar, LOOKUP);
counter += charval;
p += CODE39LM[charval];
}
 
counter = counter % 43;
checkDigit = LOOKUP[counter];
infoLine("Check Digit: " + checkDigit);
p += CODE39LM[counter];
 
this.readable = this.content + checkDigit;
this.pattern = new String[] { "1311313111" + p + "131131311" };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Pdf417.java
New file
0,0 → 1,1531
/*
* Copyright 2014-2017 Robin Stuart, Daniel Gredler
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
 
package uk.org.okapibarcode.backend;
 
import static uk.org.okapibarcode.util.Arrays.positionOf;
 
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
 
/**
* <p>
* Implements PDF417 bar code symbology and MicroPDF417 bar code symbology according to ISO/IEC
* 15438:2006 and ISO/IEC 24728:2006 respectively.
*
* <p>
* PDF417 supports encoding up to the ISO standard maximum symbol size of 925 codewords which (at
* error correction level 0) allows a maximum data size of 1850 text characters, or 2710 digits. The
* maximum size MicroPDF417 symbol can hold 250 alphanumeric characters or 366 digits.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
* @author Daniel Gredler
*/
public class Pdf417 extends Symbol {
 
public enum Mode {
/** Normal PDF417. */
NORMAL,
/** Truncated PDF417. */
TRUNCATED,
/** MicroPDF417. */
MICRO
}
 
private enum EncodingMode {
FALSE, TEX, BYT, NUM
}
 
private final int[] codeWords = new int[2700];
private int codeWordCount;
private Mode symbolMode = Mode.NORMAL;
private Integer columns;
private Integer rows;
private int preferredEccLevel = -1;
private int structuredAppendFileId = 0;
private int structuredAppendPosition = 1;
private int structuredAppendTotal = 1;
 
private static final int MAX_NUMERIC_COMPACTION_BLOCK_SIZE = 44;
 
private static final int[] COEFRS = {
/* k = 2 */
27, 917,
 
/* k = 4 */
522, 568, 723, 809,
 
/* k = 8 */
237, 308, 436, 284, 646, 653, 428, 379,
 
/* k = 16 */
274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65,
 
/* k = 32 */
361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517, 273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410,
 
/* k = 64 */
539, 422, 6, 93, 862, 771, 453, 106, 610, 287, 107, 505, 733, 877, 381, 612, 723, 476, 462, 172, 430, 609, 858, 822, 543, 376, 511, 400, 672, 762, 283, 184, 440, 35, 519, 31, 460, 594,
225, 535, 517, 352, 605, 158, 651, 201, 488, 502, 648, 733, 717, 83, 404, 97, 280, 771, 840, 629, 4, 381, 843, 623, 264, 543,
 
/* k = 128 */
521, 310, 864, 547, 858, 580, 296, 379, 53, 779, 897, 444, 400, 925, 749, 415, 822, 93, 217, 208, 928, 244, 583, 620, 246, 148, 447, 631, 292, 908, 490, 704, 516, 258, 457, 907, 594, 723,
674, 292, 272, 96, 684, 432, 686, 606, 860, 569, 193, 219, 129, 186, 236, 287, 192, 775, 278, 173, 40, 379, 712, 463, 646, 776, 171, 491, 297, 763, 156, 732, 95, 270, 447, 90, 507, 48,
228, 821, 808, 898, 784, 663, 627, 378, 382, 262, 380, 602, 754, 336, 89, 614, 87, 432, 670, 616, 157, 374, 242, 726, 600, 269, 375, 898, 845, 454, 354, 130, 814, 587, 804, 34, 211, 330,
539, 297, 827, 865, 37, 517, 834, 315, 550, 86, 801, 4, 108, 539,
 
/* k = 256 */
524, 894, 75, 766, 882, 857, 74, 204, 82, 586, 708, 250, 905, 786, 138, 720, 858, 194, 311, 913, 275, 190, 375, 850, 438, 733, 194, 280, 201, 280, 828, 757, 710, 814, 919, 89, 68, 569, 11,
204, 796, 605, 540, 913, 801, 700, 799, 137, 439, 418, 592, 668, 353, 859, 370, 694, 325, 240, 216, 257, 284, 549, 209, 884, 315, 70, 329, 793, 490, 274, 877, 162, 749, 812, 684, 461, 334,
376, 849, 521, 307, 291, 803, 712, 19, 358, 399, 908, 103, 511, 51, 8, 517, 225, 289, 470, 637, 731, 66, 255, 917, 269, 463, 830, 730, 433, 848, 585, 136, 538, 906, 90, 2, 290, 743, 199,
655, 903, 329, 49, 802, 580, 355, 588, 188, 462, 10, 134, 628, 320, 479, 130, 739, 71, 263, 318, 374, 601, 192, 605, 142, 673, 687, 234, 722, 384, 177, 752, 607, 640, 455, 193, 689, 707,
805, 641, 48, 60, 732, 621, 895, 544, 261, 852, 655, 309, 697, 755, 756, 60, 231, 773, 434, 421, 726, 528, 503, 118, 49, 795, 32, 144, 500, 238, 836, 394, 280, 566, 319, 9, 647, 550, 73,
914, 342, 126, 32, 681, 331, 792, 620, 60, 609, 441, 180, 791, 893, 754, 605, 383, 228, 749, 760, 213, 54, 297, 134, 54, 834, 299, 922, 191, 910, 532, 609, 829, 189, 20, 167, 29, 872, 449,
83, 402, 41, 656, 505, 579, 481, 173, 404, 251, 688, 95, 497, 555, 642, 543, 307, 159, 924, 558, 648, 55, 497, 10,
 
/* k = 512 */
352, 77, 373, 504, 35, 599, 428, 207, 409, 574, 118, 498, 285, 380, 350, 492, 197, 265, 920, 155, 914, 299, 229, 643, 294, 871, 306, 88, 87, 193, 352, 781, 846, 75, 327, 520, 435, 543,
203, 666, 249, 346, 781, 621, 640, 268, 794, 534, 539, 781, 408, 390, 644, 102, 476, 499, 290, 632, 545, 37, 858, 916, 552, 41, 542, 289, 122, 272, 383, 800, 485, 98, 752, 472, 761, 107,
784, 860, 658, 741, 290, 204, 681, 407, 855, 85, 99, 62, 482, 180, 20, 297, 451, 593, 913, 142, 808, 684, 287, 536, 561, 76, 653, 899, 729, 567, 744, 390, 513, 192, 516, 258, 240, 518,
794, 395, 768, 848, 51, 610, 384, 168, 190, 826, 328, 596, 786, 303, 570, 381, 415, 641, 156, 237, 151, 429, 531, 207, 676, 710, 89, 168, 304, 402, 40, 708, 575, 162, 864, 229, 65, 861,
841, 512, 164, 477, 221, 92, 358, 785, 288, 357, 850, 836, 827, 736, 707, 94, 8, 494, 114, 521, 2, 499, 851, 543, 152, 729, 771, 95, 248, 361, 578, 323, 856, 797, 289, 51, 684, 466, 533,
820, 669, 45, 902, 452, 167, 342, 244, 173, 35, 463, 651, 51, 699, 591, 452, 578, 37, 124, 298, 332, 552, 43, 427, 119, 662, 777, 475, 850, 764, 364, 578, 911, 283, 711, 472, 420, 245,
288, 594, 394, 511, 327, 589, 777, 699, 688, 43, 408, 842, 383, 721, 521, 560, 644, 714, 559, 62, 145, 873, 663, 713, 159, 672, 729, 624, 59, 193, 417, 158, 209, 563, 564, 343, 693, 109,
608, 563, 365, 181, 772, 677, 310, 248, 353, 708, 410, 579, 870, 617, 841, 632, 860, 289, 536, 35, 777, 618, 586, 424, 833, 77, 597, 346, 269, 757, 632, 695, 751, 331, 247, 184, 45, 787,
680, 18, 66, 407, 369, 54, 492, 228, 613, 830, 922, 437, 519, 644, 905, 789, 420, 305, 441, 207, 300, 892, 827, 141, 537, 381, 662, 513, 56, 252, 341, 242, 797, 838, 837, 720, 224, 307,
631, 61, 87, 560, 310, 756, 665, 397, 808, 851, 309, 473, 795, 378, 31, 647, 915, 459, 806, 590, 731, 425, 216, 548, 249, 321, 881, 699, 535, 673, 782, 210, 815, 905, 303, 843, 922, 281,
73, 469, 791, 660, 162, 498, 308, 155, 422, 907, 817, 187, 62, 16, 425, 535, 336, 286, 437, 375, 273, 610, 296, 183, 923, 116, 667, 751, 353, 62, 366, 691, 379, 687, 842, 37, 357, 720,
742, 330, 5, 39, 923, 311, 424, 242, 749, 321, 54, 669, 316, 342, 299, 534, 105, 667, 488, 640, 672, 576, 540, 316, 486, 721, 610, 46, 656, 447, 171, 616, 464, 190, 531, 297, 321, 762,
752, 533, 175, 134, 14, 381, 433, 717, 45, 111, 20, 596, 284, 736, 138, 646, 411, 877, 669, 141, 919, 45, 780, 407, 164, 332, 899, 165, 726, 600, 325, 498, 655, 357, 752, 768, 223, 849,
647, 63, 310, 863, 251, 366, 304, 282, 738, 675, 410, 389, 244, 31, 121, 303, 263 };
 
private static final String[] CODAGEMC = { "urA", "xfs", "ypy", "unk", "xdw", "yoz", "pDA", "uls", "pBk", "eBA", "pAs", "eAk", "prA", "uvs", "xhy", "pnk", "utw", "xgz", "fDA", "pls", "fBk", "frA",
"pvs", "uxy", "fnk", "ptw", "uwz", "fls", "psy", "fvs", "pxy", "ftw", "pwz", "fxy", "yrx", "ufk", "xFw", "ymz", "onA", "uds", "xEy", "olk", "ucw", "dBA", "oks", "uci", "dAk", "okg", "dAc",
"ovk", "uhw", "xaz", "dnA", "ots", "ugy", "dlk", "osw", "ugj", "dks", "osi", "dvk", "oxw", "uiz", "dts", "owy", "dsw", "owj", "dxw", "oyz", "dwy", "dwj", "ofA", "uFs", "xCy", "odk", "uEw",
"xCj", "clA", "ocs", "uEi", "ckk", "ocg", "ckc", "ckE", "cvA", "ohs", "uay", "ctk", "ogw", "uaj", "css", "ogi", "csg", "csa", "cxs", "oiy", "cww", "oij", "cwi", "cyy", "oFk", "uCw", "xBj",
"cdA", "oEs", "uCi", "cck", "oEg", "uCb", "ccc", "oEa", "ccE", "oED", "chk", "oaw", "uDj", "cgs", "oai", "cgg", "oab", "cga", "cgD", "obj", "cib", "cFA", "oCs", "uBi", "cEk", "oCg", "uBb",
"cEc", "oCa", "cEE", "oCD", "cEC", "cas", "cag", "caa", "cCk", "uAr", "oBa", "oBD", "cCB", "tfk", "wpw", "yez", "mnA", "tds", "woy", "mlk", "tcw", "woj", "FBA", "mks", "FAk", "mvk", "thw",
"wqz", "FnA", "mts", "tgy", "Flk", "msw", "Fks", "Fkg", "Fvk", "mxw", "tiz", "Fts", "mwy", "Fsw", "Fsi", "Fxw", "myz", "Fwy", "Fyz", "vfA", "xps", "yuy", "vdk", "xow", "yuj", "qlA", "vcs",
"xoi", "qkk", "vcg", "xob", "qkc", "vca", "mfA", "tFs", "wmy", "qvA", "mdk", "tEw", "wmj", "qtk", "vgw", "xqj", "hlA", "Ekk", "mcg", "tEb", "hkk", "qsg", "hkc", "EvA", "mhs", "tay", "hvA",
"Etk", "mgw", "taj", "htk", "qww", "vij", "hss", "Esg", "hsg", "Exs", "miy", "hxs", "Eww", "mij", "hww", "qyj", "hwi", "Eyy", "hyy", "Eyj", "hyj", "vFk", "xmw", "ytj", "qdA", "vEs", "xmi",
"qck", "vEg", "xmb", "qcc", "vEa", "qcE", "qcC", "mFk", "tCw", "wlj", "qhk", "mEs", "tCi", "gtA", "Eck", "vai", "tCb", "gsk", "Ecc", "mEa", "gsc", "qga", "mED", "EcC", "Ehk", "maw", "tDj",
"gxk", "Egs", "mai", "gws", "qii", "mab", "gwg", "Ega", "EgD", "Eiw", "mbj", "gyw", "Eii", "gyi", "Eib", "gyb", "gzj", "qFA", "vCs", "xli", "qEk", "vCg", "xlb", "qEc", "vCa", "qEE", "vCD",
"qEC", "qEB", "EFA", "mCs", "tBi", "ghA", "EEk", "mCg", "tBb", "ggk", "qag", "vDb", "ggc", "EEE", "mCD", "ggE", "qaD", "ggC", "Eas", "mDi", "gis", "Eag", "mDb", "gig", "qbb", "gia", "EaD",
"giD", "gji", "gjb", "qCk", "vBg", "xkr", "qCc", "vBa", "qCE", "vBD", "qCC", "qCB", "ECk", "mBg", "tAr", "gak", "ECc", "mBa", "gac", "qDa", "mBD", "gaE", "ECC", "gaC", "ECB", "EDg", "gbg",
"gba", "gbD", "vAq", "vAn", "qBB", "mAq", "EBE", "gDE", "gDC", "gDB", "lfA", "sps", "wey", "ldk", "sow", "ClA", "lcs", "soi", "Ckk", "lcg", "Ckc", "CkE", "CvA", "lhs", "sqy", "Ctk", "lgw",
"sqj", "Css", "lgi", "Csg", "Csa", "Cxs", "liy", "Cww", "lij", "Cwi", "Cyy", "Cyj", "tpk", "wuw", "yhj", "ndA", "tos", "wui", "nck", "tog", "wub", "ncc", "toa", "ncE", "toD", "lFk", "smw",
"wdj", "nhk", "lEs", "smi", "atA", "Cck", "tqi", "smb", "ask", "ngg", "lEa", "asc", "CcE", "asE", "Chk", "law", "snj", "axk", "Cgs", "trj", "aws", "nii", "lab", "awg", "Cga", "awa", "Ciw",
"lbj", "ayw", "Cii", "ayi", "Cib", "Cjj", "azj", "vpA", "xus", "yxi", "vok", "xug", "yxb", "voc", "xua", "voE", "xuD", "voC", "nFA", "tms", "wti", "rhA", "nEk", "xvi", "wtb", "rgk", "vqg",
"xvb", "rgc", "nEE", "tmD", "rgE", "vqD", "nEB", "CFA", "lCs", "sli", "ahA", "CEk", "lCg", "slb", "ixA", "agk", "nag", "tnb", "iwk", "rig", "vrb", "lCD", "iwc", "agE", "naD", "iwE", "CEB",
"Cas", "lDi", "ais", "Cag", "lDb", "iys", "aig", "nbb", "iyg", "rjb", "CaD", "aiD", "Cbi", "aji", "Cbb", "izi", "ajb", "vmk", "xtg", "ywr", "vmc", "xta", "vmE", "xtD", "vmC", "vmB", "nCk",
"tlg", "wsr", "rak", "nCc", "xtr", "rac", "vna", "tlD", "raE", "nCC", "raC", "nCB", "raB", "CCk", "lBg", "skr", "aak", "CCc", "lBa", "iik", "aac", "nDa", "lBD", "iic", "rba", "CCC", "iiE",
"aaC", "CCB", "aaB", "CDg", "lBr", "abg", "CDa", "ijg", "aba", "CDD", "ija", "abD", "CDr", "ijr", "vlc", "xsq", "vlE", "xsn", "vlC", "vlB", "nBc", "tkq", "rDc", "nBE", "tkn", "rDE", "vln",
"rDC", "nBB", "rDB", "CBc", "lAq", "aDc", "CBE", "lAn", "ibc", "aDE", "nBn", "ibE", "rDn", "CBB", "ibC", "aDB", "ibB", "aDq", "ibq", "ibn", "xsf", "vkl", "tkf", "nAm", "nAl", "CAo", "aBo",
"iDo", "CAl", "aBl", "kpk", "BdA", "kos", "Bck", "kog", "seb", "Bcc", "koa", "BcE", "koD", "Bhk", "kqw", "sfj", "Bgs", "kqi", "Bgg", "kqb", "Bga", "BgD", "Biw", "krj", "Bii", "Bib", "Bjj",
"lpA", "sus", "whi", "lok", "sug", "loc", "sua", "loE", "suD", "loC", "BFA", "kms", "sdi", "DhA", "BEk", "svi", "sdb", "Dgk", "lqg", "svb", "Dgc", "BEE", "kmD", "DgE", "lqD", "BEB", "Bas",
"kni", "Dis", "Bag", "knb", "Dig", "lrb", "Dia", "BaD", "Bbi", "Dji", "Bbb", "Djb", "tuk", "wxg", "yir", "tuc", "wxa", "tuE", "wxD", "tuC", "tuB", "lmk", "stg", "nqk", "lmc", "sta", "nqc",
"tva", "stD", "nqE", "lmC", "nqC", "lmB", "nqB", "BCk", "klg", "Dak", "BCc", "str", "bik", "Dac", "lna", "klD", "bic", "nra", "BCC", "biE", "DaC", "BCB", "DaB", "BDg", "klr", "Dbg", "BDa",
"bjg", "Dba", "BDD", "bja", "DbD", "BDr", "Dbr", "bjr", "xxc", "yyq", "xxE", "yyn", "xxC", "xxB", "ttc", "wwq", "vvc", "xxq", "wwn", "vvE", "xxn", "vvC", "ttB", "vvB", "llc", "ssq", "nnc",
"llE", "ssn", "rrc", "nnE", "ttn", "rrE", "vvn", "llB", "rrC", "nnB", "rrB", "BBc", "kkq", "DDc", "BBE", "kkn", "bbc", "DDE", "lln", "jjc", "bbE", "nnn", "BBB", "jjE", "rrn", "DDB", "jjC",
"BBq", "DDq", "BBn", "bbq", "DDn", "jjq", "bbn", "jjn", "xwo", "yyf", "xwm", "xwl", "tso", "wwf", "vto", "xwv", "vtm", "tsl", "vtl", "lko", "ssf", "nlo", "lkm", "rno", "nlm", "lkl", "rnm",
"nll", "rnl", "BAo", "kkf", "DBo", "lkv", "bDo", "DBm", "BAl", "jbo", "bDm", "DBl", "jbm", "bDl", "jbl", "DBv", "jbv", "xwd", "vsu", "vst", "nku", "rlu", "rlt", "DAu", "bBu", "jDu", "jDt",
"ApA", "Aok", "keg", "Aoc", "AoE", "AoC", "Aqs", "Aqg", "Aqa", "AqD", "Ari", "Arb", "kuk", "kuc", "sha", "kuE", "shD", "kuC", "kuB", "Amk", "kdg", "Bqk", "kvg", "kda", "Bqc", "kva", "BqE",
"kvD", "BqC", "AmB", "BqB", "Ang", "kdr", "Brg", "kvr", "Bra", "AnD", "BrD", "Anr", "Brr", "sxc", "sxE", "sxC", "sxB", "ktc", "lvc", "sxq", "sgn", "lvE", "sxn", "lvC", "ktB", "lvB", "Alc",
"Bnc", "AlE", "kcn", "Drc", "BnE", "AlC", "DrE", "BnC", "AlB", "DrC", "BnB", "Alq", "Bnq", "Aln", "Drq", "Bnn", "Drn", "wyo", "wym", "wyl", "swo", "txo", "wyv", "txm", "swl", "txl", "kso",
"sgf", "lto", "swv", "nvo", "ltm", "ksl", "nvm", "ltl", "nvl", "Ako", "kcf", "Blo", "ksv", "Dno", "Blm", "Akl", "bro", "Dnm", "Bll", "brm", "Dnl", "Akv", "Blv", "Dnv", "brv", "yze", "yzd",
"wye", "xyu", "wyd", "xyt", "swe", "twu", "swd", "vxu", "twt", "vxt", "kse", "lsu", "ksd", "ntu", "lst", "rvu", "ypk", "zew", "xdA", "yos", "zei", "xck", "yog", "zeb", "xcc", "yoa", "xcE",
"yoD", "xcC", "xhk", "yqw", "zfj", "utA", "xgs", "yqi", "usk", "xgg", "yqb", "usc", "xga", "usE", "xgD", "usC", "uxk", "xiw", "yrj", "ptA", "uws", "xii", "psk", "uwg", "xib", "psc", "uwa",
"psE", "uwD", "psC", "pxk", "uyw", "xjj", "ftA", "pws", "uyi", "fsk", "pwg", "uyb", "fsc", "pwa", "fsE", "pwD", "fxk", "pyw", "uzj", "fws", "pyi", "fwg", "pyb", "fwa", "fyw", "pzj", "fyi",
"fyb", "xFA", "yms", "zdi", "xEk", "ymg", "zdb", "xEc", "yma", "xEE", "ymD", "xEC", "xEB", "uhA", "xas", "yni", "ugk", "xag", "ynb", "ugc", "xaa", "ugE", "xaD", "ugC", "ugB", "oxA", "uis",
"xbi", "owk", "uig", "xbb", "owc", "uia", "owE", "uiD", "owC", "owB", "dxA", "oys", "uji", "dwk", "oyg", "ujb", "dwc", "oya", "dwE", "oyD", "dwC", "dys", "ozi", "dyg", "ozb", "dya", "dyD",
"dzi", "dzb", "xCk", "ylg", "zcr", "xCc", "yla", "xCE", "ylD", "xCC", "xCB", "uak", "xDg", "ylr", "uac", "xDa", "uaE", "xDD", "uaC", "uaB", "oik", "ubg", "xDr", "oic", "uba", "oiE", "ubD",
"oiC", "oiB", "cyk", "ojg", "ubr", "cyc", "oja", "cyE", "ojD", "cyC", "cyB", "czg", "ojr", "cza", "czD", "czr", "xBc", "ykq", "xBE", "ykn", "xBC", "xBB", "uDc", "xBq", "uDE", "xBn", "uDC",
"uDB", "obc", "uDq", "obE", "uDn", "obC", "obB", "cjc", "obq", "cjE", "obn", "cjC", "cjB", "cjq", "cjn", "xAo", "ykf", "xAm", "xAl", "uBo", "xAv", "uBm", "uBl", "oDo", "uBv", "oDm", "oDl",
"cbo", "oDv", "cbm", "cbl", "xAe", "xAd", "uAu", "uAt", "oBu", "oBt", "wpA", "yes", "zFi", "wok", "yeg", "zFb", "woc", "yea", "woE", "yeD", "woC", "woB", "thA", "wqs", "yfi", "tgk", "wqg",
"yfb", "tgc", "wqa", "tgE", "wqD", "tgC", "tgB", "mxA", "tis", "wri", "mwk", "tig", "wrb", "mwc", "tia", "mwE", "tiD", "mwC", "mwB", "FxA", "mys", "tji", "Fwk", "myg", "tjb", "Fwc", "mya",
"FwE", "myD", "FwC", "Fys", "mzi", "Fyg", "mzb", "Fya", "FyD", "Fzi", "Fzb", "yuk", "zhg", "hjs", "yuc", "zha", "hbw", "yuE", "zhD", "hDy", "yuC", "yuB", "wmk", "ydg", "zEr", "xqk", "wmc",
"zhr", "xqc", "yva", "ydD", "xqE", "wmC", "xqC", "wmB", "xqB", "tak", "wng", "ydr", "vik", "tac", "wna", "vic", "xra", "wnD", "viE", "taC", "viC", "taB", "viB", "mik", "tbg", "wnr", "qyk",
"mic", "tba", "qyc", "vja", "tbD", "qyE", "miC", "qyC", "miB", "qyB", "Eyk", "mjg", "tbr", "hyk", "Eyc", "mja", "hyc", "qza", "mjD", "hyE", "EyC", "hyC", "EyB", "Ezg", "mjr", "hzg", "Eza",
"hza", "EzD", "hzD", "Ezr", "ytc", "zgq", "grw", "ytE", "zgn", "gny", "ytC", "glz", "ytB", "wlc", "ycq", "xnc", "wlE", "ycn", "xnE", "ytn", "xnC", "wlB", "xnB", "tDc", "wlq", "vbc", "tDE",
"wln", "vbE", "xnn", "vbC", "tDB", "vbB", "mbc", "tDq", "qjc", "mbE", "tDn", "qjE", "vbn", "qjC", "mbB", "qjB", "Ejc", "mbq", "gzc", "EjE", "mbn", "gzE", "qjn", "gzC", "EjB", "gzB", "Ejq",
"gzq", "Ejn", "gzn", "yso", "zgf", "gfy", "ysm", "gdz", "ysl", "wko", "ycf", "xlo", "ysv", "xlm", "wkl", "xll", "tBo", "wkv", "vDo", "tBm", "vDm", "tBl", "vDl", "mDo", "tBv", "qbo", "vDv",
"qbm", "mDl", "qbl", "Ebo", "mDv", "gjo", "Ebm", "gjm", "Ebl", "gjl", "Ebv", "gjv", "yse", "gFz", "ysd", "wke", "xku", "wkd", "xkt", "tAu", "vBu", "tAt", "vBt", "mBu", "qDu", "mBt", "qDt",
"EDu", "gbu", "EDt", "gbt", "ysF", "wkF", "xkh", "tAh", "vAx", "mAx", "qBx", "wek", "yFg", "zCr", "wec", "yFa", "weE", "yFD", "weC", "weB", "sqk", "wfg", "yFr", "sqc", "wfa", "sqE", "wfD",
"sqC", "sqB", "lik", "srg", "wfr", "lic", "sra", "liE", "srD", "liC", "liB", "Cyk", "ljg", "srr", "Cyc", "lja", "CyE", "ljD", "CyC", "CyB", "Czg", "ljr", "Cza", "CzD", "Czr", "yhc", "zaq",
"arw", "yhE", "zan", "any", "yhC", "alz", "yhB", "wdc", "yEq", "wvc", "wdE", "yEn", "wvE", "yhn", "wvC", "wdB", "wvB", "snc", "wdq", "trc", "snE", "wdn", "trE", "wvn", "trC", "snB", "trB",
"lbc", "snq", "njc", "lbE", "snn", "njE", "trn", "njC", "lbB", "njB", "Cjc", "lbq", "azc", "CjE", "lbn", "azE", "njn", "azC", "CjB", "azB", "Cjq", "azq", "Cjn", "azn", "zio", "irs", "rfy",
"zim", "inw", "rdz", "zil", "ily", "ikz", "ygo", "zaf", "afy", "yxo", "ziv", "ivy", "adz", "yxm", "ygl", "itz", "yxl", "wco", "yEf", "wto", "wcm", "xvo", "yxv", "wcl", "xvm", "wtl", "xvl",
"slo", "wcv", "tno", "slm", "vro", "tnm", "sll", "vrm", "tnl", "vrl", "lDo", "slv", "nbo", "lDm", "rjo", "nbm", "lDl", "rjm", "nbl", "rjl", "Cbo", "lDv", "ajo", "Cbm", "izo", "ajm", "Cbl",
"izm", "ajl", "izl", "Cbv", "ajv", "zie", "ifw", "rFz", "zid", "idy", "icz", "yge", "aFz", "ywu", "ygd", "ihz", "ywt", "wce", "wsu", "wcd", "xtu", "wst", "xtt", "sku", "tlu", "skt", "vnu",
"tlt", "vnt", "lBu", "nDu", "lBt", "rbu", "nDt", "rbt", "CDu", "abu", "CDt", "iju", "abt", "ijt", "ziF", "iFy", "iEz", "ygF", "ywh", "wcF", "wsh", "xsx", "skh", "tkx", "vlx", "lAx", "nBx",
"rDx", "CBx", "aDx", "ibx", "iCz", "wFc", "yCq", "wFE", "yCn", "wFC", "wFB", "sfc", "wFq", "sfE", "wFn", "sfC", "sfB", "krc", "sfq", "krE", "sfn", "krC", "krB", "Bjc", "krq", "BjE", "krn",
"BjC", "BjB", "Bjq", "Bjn", "yao", "zDf", "Dfy", "yam", "Ddz", "yal", "wEo", "yCf", "who", "wEm", "whm", "wEl", "whl", "sdo", "wEv", "svo", "sdm", "svm", "sdl", "svl", "kno", "sdv", "lro",
"knm", "lrm", "knl", "lrl", "Bbo", "knv", "Djo", "Bbm", "Djm", "Bbl", "Djl", "Bbv", "Djv", "zbe", "bfw", "npz", "zbd", "bdy", "bcz", "yae", "DFz", "yiu", "yad", "bhz", "yit", "wEe", "wgu",
"wEd", "wxu", "wgt", "wxt", "scu", "stu", "sct", "tvu", "stt", "tvt", "klu", "lnu", "klt", "nru", "lnt", "nrt", "BDu", "Dbu", "BDt", "bju", "Dbt", "bjt", "jfs", "rpy", "jdw", "roz", "jcy",
"jcj", "zbF", "bFy", "zjh", "jhy", "bEz", "jgz", "yaF", "yih", "yyx", "wEF", "wgh", "wwx", "xxx", "sch", "ssx", "ttx", "vvx", "kkx", "llx", "nnx", "rrx", "BBx", "DDx", "bbx", "jFw", "rmz",
"jEy", "jEj", "bCz", "jaz", "jCy", "jCj", "jBj", "wCo", "wCm", "wCl", "sFo", "wCv", "sFm", "sFl", "kfo", "sFv", "kfm", "kfl", "Aro", "kfv", "Arm", "Arl", "Arv", "yDe", "Bpz", "yDd", "wCe",
"wau", "wCd", "wat", "sEu", "shu", "sEt", "sht", "kdu", "kvu", "kdt", "kvt", "Anu", "Bru", "Ant", "Brt", "zDp", "Dpy", "Doz", "yDF", "ybh", "wCF", "wah", "wix", "sEh", "sgx", "sxx", "kcx",
"ktx", "lvx", "Alx", "Bnx", "Drx", "bpw", "nuz", "boy", "boj", "Dmz", "bqz", "jps", "ruy", "jow", "ruj", "joi", "job", "bmy", "jqy", "bmj", "jqj", "jmw", "rtj", "jmi", "jmb", "blj", "jnj",
"jli", "jlb", "jkr", "sCu", "sCt", "kFu", "kFt", "Afu", "Aft", "wDh", "sCh", "sax", "kEx", "khx", "Adx", "Avx", "Buz", "Duy", "Duj", "buw", "nxj", "bui", "bub", "Dtj", "bvj", "jus", "rxi",
"jug", "rxb", "jua", "juD", "bti", "jvi", "btb", "jvb", "jtg", "rwr", "jta", "jtD", "bsr", "jtr", "jsq", "jsn", "Bxj", "Dxi", "Dxb", "bxg", "nyr", "bxa", "bxD", "Dwr", "bxr", "bwq", "bwn",
"pjk", "urw", "ejA", "pbs", "uny", "ebk", "pDw", "ulz", "eDs", "pBy", "eBw", "zfc", "fjk", "prw", "zfE", "fbs", "pny", "zfC", "fDw", "plz", "zfB", "fBy", "yrc", "zfq", "frw", "yrE", "zfn",
"fny", "yrC", "flz", "yrB", "xjc", "yrq", "xjE", "yrn", "xjC", "xjB", "uzc", "xjq", "uzE", "xjn", "uzC", "uzB", "pzc", "uzq", "pzE", "uzn", "pzC", "djA", "ors", "ufy", "dbk", "onw", "udz",
"dDs", "oly", "dBw", "okz", "dAy", "zdo", "drs", "ovy", "zdm", "dnw", "otz", "zdl", "dly", "dkz", "yno", "zdv", "dvy", "ynm", "dtz", "ynl", "xbo", "ynv", "xbm", "xbl", "ujo", "xbv", "ujm",
"ujl", "ozo", "ujv", "ozm", "ozl", "crk", "ofw", "uFz", "cns", "ody", "clw", "ocz", "cky", "ckj", "zcu", "cvw", "ohz", "zct", "cty", "csz", "ylu", "cxz", "ylt", "xDu", "xDt", "ubu", "ubt",
"oju", "ojt", "cfs", "oFy", "cdw", "oEz", "ccy", "ccj", "zch", "chy", "cgz", "ykx", "xBx", "uDx", "cFw", "oCz", "cEy", "cEj", "caz", "cCy", "cCj", "FjA", "mrs", "tfy", "Fbk", "mnw", "tdz",
"FDs", "mly", "FBw", "mkz", "FAy", "zFo", "Frs", "mvy", "zFm", "Fnw", "mtz", "zFl", "Fly", "Fkz", "yfo", "zFv", "Fvy", "yfm", "Ftz", "yfl", "wro", "yfv", "wrm", "wrl", "tjo", "wrv", "tjm",
"tjl", "mzo", "tjv", "mzm", "mzl", "qrk", "vfw", "xpz", "hbA", "qns", "vdy", "hDk", "qlw", "vcz", "hBs", "qky", "hAw", "qkj", "hAi", "Erk", "mfw", "tFz", "hrk", "Ens", "mdy", "hns", "qty",
"mcz", "hlw", "Eky", "hky", "Ekj", "hkj", "zEu", "Evw", "mhz", "zhu", "zEt", "hvw", "Ety", "zht", "hty", "Esz", "hsz", "ydu", "Exz", "yvu", "ydt", "hxz", "yvt", "wnu", "xru", "wnt", "xrt",
"tbu", "vju", "tbt", "vjt", "mju", "mjt", "grA", "qfs", "vFy", "gnk", "qdw", "vEz", "gls", "qcy", "gkw", "qcj", "gki", "gkb", "Efs", "mFy", "gvs", "Edw", "mEz", "gtw", "qgz", "gsy", "Ecj",
"gsj", "zEh", "Ehy", "zgx", "gxy", "Egz", "gwz", "ycx", "ytx", "wlx", "xnx", "tDx", "vbx", "mbx", "gfk", "qFw", "vCz", "gds", "qEy", "gcw", "qEj", "gci", "gcb", "EFw", "mCz", "ghw", "EEy",
"ggy", "EEj", "ggj", "Eaz", "giz", "gFs", "qCy", "gEw", "qCj", "gEi", "gEb", "ECy", "gay", "ECj", "gaj", "gCw", "qBj", "gCi", "gCb", "EBj", "gDj", "gBi", "gBb", "Crk", "lfw", "spz", "Cns",
"ldy", "Clw", "lcz", "Cky", "Ckj", "zCu", "Cvw", "lhz", "zCt", "Cty", "Csz", "yFu", "Cxz", "yFt", "wfu", "wft", "sru", "srt", "lju", "ljt", "arA", "nfs", "tpy", "ank", "ndw", "toz", "als",
"ncy", "akw", "ncj", "aki", "akb", "Cfs", "lFy", "avs", "Cdw", "lEz", "atw", "ngz", "asy", "Ccj", "asj", "zCh", "Chy", "zax", "axy", "Cgz", "awz", "yEx", "yhx", "wdx", "wvx", "snx", "trx",
"lbx", "rfk", "vpw", "xuz", "inA", "rds", "voy", "ilk", "rcw", "voj", "iks", "rci", "ikg", "rcb", "ika", "afk", "nFw", "tmz", "ivk", "ads", "nEy", "its", "rgy", "nEj", "isw", "aci", "isi",
"acb", "isb", "CFw", "lCz", "ahw", "CEy", "ixw", "agy", "CEj", "iwy", "agj", "iwj", "Caz", "aiz", "iyz", "ifA", "rFs", "vmy", "idk", "rEw", "vmj", "ics", "rEi", "icg", "rEb", "ica", "icD",
"aFs", "nCy", "ihs", "aEw", "nCj", "igw", "raj", "igi", "aEb", "igb", "CCy", "aay", "CCj", "iiy", "aaj", "iij", "iFk", "rCw", "vlj", "iEs", "rCi", "iEg", "rCb", "iEa", "iED", "aCw", "nBj",
"iaw", "aCi", "iai", "aCb", "iab", "CBj", "aDj", "ibj", "iCs", "rBi", "iCg", "rBb", "iCa", "iCD", "aBi", "iDi", "aBb", "iDb", "iBg", "rAr", "iBa", "iBD", "aAr", "iBr", "iAq", "iAn", "Bfs",
"kpy", "Bdw", "koz", "Bcy", "Bcj", "Bhy", "Bgz", "yCx", "wFx", "sfx", "krx", "Dfk", "lpw", "suz", "Dds", "loy", "Dcw", "loj", "Dci", "Dcb", "BFw", "kmz", "Dhw", "BEy", "Dgy", "BEj", "Dgj",
"Baz", "Diz", "bfA", "nps", "tuy", "bdk", "now", "tuj", "bcs", "noi", "bcg", "nob", "bca", "bcD", "DFs", "lmy", "bhs", "DEw", "lmj", "bgw", "DEi", "bgi", "DEb", "bgb", "BCy", "Day", "BCj",
"biy", "Daj", "bij", "rpk", "vuw", "xxj", "jdA", "ros", "vui", "jck", "rog", "vub", "jcc", "roa", "jcE", "roD", "jcC", "bFk", "nmw", "ttj", "jhk", "bEs", "nmi", "jgs", "rqi", "nmb", "jgg",
"bEa", "jga", "bED", "jgD", "DCw", "llj", "baw", "DCi", "jiw", "bai", "DCb", "jii", "bab", "jib", "BBj", "DDj", "bbj", "jjj", "jFA", "rms", "vti", "jEk", "rmg", "vtb", "jEc", "rma", "jEE",
"rmD", "jEC", "jEB", "bCs", "nli", "jas", "bCg", "nlb", "jag", "rnb", "jaa", "bCD", "jaD", "DBi", "bDi", "DBb", "jbi", "bDb", "jbb", "jCk", "rlg", "vsr", "jCc", "rla", "jCE", "rlD", "jCC",
"jCB", "bBg", "nkr", "jDg", "bBa", "jDa", "bBD", "jDD", "DAr", "bBr", "jDr", "jBc", "rkq", "jBE", "rkn", "jBC", "jBB", "bAq", "jBq", "bAn", "jBn", "jAo", "rkf", "jAm", "jAl", "bAf", "jAv",
"Apw", "kez", "Aoy", "Aoj", "Aqz", "Bps", "kuy", "Bow", "kuj", "Boi", "Bob", "Amy", "Bqy", "Amj", "Bqj", "Dpk", "luw", "sxj", "Dos", "lui", "Dog", "lub", "Doa", "DoD", "Bmw", "ktj", "Dqw",
"Bmi", "Dqi", "Bmb", "Dqb", "Alj", "Bnj", "Drj", "bpA", "nus", "txi", "bok", "nug", "txb", "boc", "nua", "boE", "nuD", "boC", "boB", "Dms", "lti", "bqs", "Dmg", "ltb", "bqg", "nvb", "bqa",
"DmD", "bqD", "Bli", "Dni", "Blb", "bri", "Dnb", "brb", "ruk", "vxg", "xyr", "ruc", "vxa", "ruE", "vxD", "ruC", "ruB", "bmk", "ntg", "twr", "jqk", "bmc", "nta", "jqc", "rva", "ntD", "jqE",
"bmC", "jqC", "bmB", "jqB", "Dlg", "lsr", "bng", "Dla", "jrg", "bna", "DlD", "jra", "bnD", "jrD", "Bkr", "Dlr", "bnr", "jrr", "rtc", "vwq", "rtE", "vwn", "rtC", "rtB", "blc", "nsq", "jnc",
"blE", "nsn", "jnE", "rtn", "jnC", "blB", "jnB", "Dkq", "blq", "Dkn", "jnq", "bln", "jnn", "rso", "vwf", "rsm", "rsl", "bko", "nsf", "jlo", "bkm", "jlm", "bkl", "jll", "Dkf", "bkv", "jlv",
"rse", "rsd", "bke", "jku", "bkd", "jkt", "Aey", "Aej", "Auw", "khj", "Aui", "Aub", "Adj", "Avj", "Bus", "kxi", "Bug", "kxb", "Bua", "BuD", "Ati", "Bvi", "Atb", "Bvb", "Duk", "lxg", "syr",
"Duc", "lxa", "DuE", "lxD", "DuC", "DuB", "Btg", "kwr", "Dvg", "lxr", "Dva", "BtD", "DvD", "Asr", "Btr", "Dvr", "nxc", "tyq", "nxE", "tyn", "nxC", "nxB", "Dtc", "lwq", "bvc", "nxq", "lwn",
"bvE", "DtC", "bvC", "DtB", "bvB", "Bsq", "Dtq", "Bsn", "bvq", "Dtn", "bvn", "vyo", "xzf", "vym", "vyl", "nwo", "tyf", "rxo", "nwm", "rxm", "nwl", "rxl", "Dso", "lwf", "bto", "Dsm", "jvo",
"btm", "Dsl", "jvm", "btl", "jvl", "Bsf", "Dsv", "btv", "jvv", "vye", "vyd", "nwe", "rwu", "nwd", "rwt", "Dse", "bsu", "Dsd", "jtu", "bst", "jtt", "vyF", "nwF", "rwh", "DsF", "bsh", "jsx",
"Ahi", "Ahb", "Axg", "kir", "Axa", "AxD", "Agr", "Axr", "Bxc", "kyq", "BxE", "kyn", "BxC", "BxB", "Awq", "Bxq", "Awn", "Bxn", "lyo", "szf", "lym", "lyl", "Bwo", "kyf", "Dxo", "lyv", "Dxm",
"Bwl", "Dxl", "Awf", "Bwv", "Dxv", "tze", "tzd", "lye", "nyu", "lyd", "nyt", "Bwe", "Dwu", "Bwd", "bxu", "Dwt", "bxt", "tzF", "lyF", "nyh", "BwF", "Dwh", "bwx", "Aiq", "Ain", "Ayo", "kjf",
"Aym", "Ayl", "Aif", "Ayv", "kze", "kzd", "Aye", "Byu", "Ayd", "Byt", "szp" };
 
private static final char[] BR_SET = { 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
'z', '*', '+', '-' };
 
private static final String[] PDF_TTF = { "00000", "00001", "00010", "00011", "00100", "00101", "00110", "00111", "01000", "01001", "01010", "01011", "01100", "01101", "01110", "01111", "10000",
"10001", "10010", "10011", "10100", "10101", "10110", "10111", "11000", "11001", "11010", "11011", "11100", "11101", "11110", "11111", "01", "1111111101010100", "11111101000101001" };
 
private static final int[] ASCII_X = { 7, 8, 8, 4, 12, 4, 4, 8, 8, 8, 12, 4, 12, 12, 12, 12, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 12, 8, 8, 4, 8, 8, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 4, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 8, 8, 8, 8 };
 
private static final int[] ASCII_Y = { 26, 10, 20, 15, 18, 21, 10, 28, 23, 24, 22, 20, 13, 16, 17, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 14, 0, 1, 23, 2, 25, 3, 0, 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, 4, 5, 6, 24, 7, 8, 0, 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, 21, 27, 9 };
 
private static final int[] MICRO_AUTOSIZE = { 4, 6, 7, 8, 8, 10, 10, 12, 12, 13, 14, 16, 18, 18, 19, 20, 24, 24, 24, 29, 30, 33, 34, 37, 39, 46, 54, 58, 70, 72, 82, 90, 108, 126, // max
// codeword
// counts
1, 14, 2, 7, 24, 3, 15, 25, 4, 8, 16, 5, 17, 26, 9, 6, 10, 18, 27, 11, 28, 12, 19, 13, 29, 20, 30, 21, 22, 31, 23, 32, 33, 34 // corresponding
// variant
};
 
/*
* Rows, columns, error codewords, k-offset of valid MicroPDF417 sizes from ISO/IEC 24728:2006
*/
private static final int[] MICRO_VARIANTS = { 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // columns
11, 14, 17, 20, 24, 28, 8, 11, 14, 17, 20, 23, 26, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44, 4, 6, 8, 10, 12, 15, 20, 26, 32, 38, 44, // rows
7, 7, 7, 8, 8, 8, 8, 9, 9, 10, 11, 13, 15, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50, 8, 12, 14, 16, 18, 21, 26, 32, 38, 44, 50, // k
// (EC
// codewords)
0, 0, 0, 7, 7, 7, 7, 15, 15, 24, 34, 57, 84, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294, 7, 45, 70, 99, 115, 133, 154, 180, 212, 250, 294 // offset
};
 
/*
* Following is Left RAP, Centre RAP, Right RAP and Start Cluster from ISO/IEC 24728:2006 tables
* 10, 11 and 12
*/
private static final int[] RAP_TABLE = { 1, 8, 36, 19, 9, 25, 1, 1, 8, 36, 19, 9, 27, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1, 47, 1, 7, 15, 25, 37, 1, 1, 21, 15, 1, // left
// RAP
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25, 19, 1, 7, 15, 25, 37, 17, 9, 29, 31, 25, // centre
// RAP
9, 8, 36, 19, 17, 33, 1, 9, 8, 36, 19, 17, 35, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, 43, 1, 7, 15, 25, 37, 33, 17, 37, 47, 49, // right
// RAP
0, 3, 6, 0, 6, 0, 0, 0, 3, 6, 0, 6, 6, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0, 3, 0, 0, 6, 0, 0, 0, 0, 6, 6, 0 // start
// cluster
};
 
/* Left and Right Row Address Pattern from Table 2 */
private static final String[] RAPLR = { "", "221311", "311311", "312211", "222211", "213211", "214111", "223111", "313111", "322111", "412111", "421111", "331111", "241111", "232111", "231211",
"321211", "411211", "411121", "411112", "321112", "312112", "311212", "311221", "311131", "311122", "311113", "221113", "221122", "221131", "221221", "222121", "312121", "321121",
"231121", "231112", "222112", "213112", "212212", "212221", "212131", "212122", "212113", "211213", "211123", "211132", "211141", "211231", "211222", "211312", "211321", "211411",
"212311" };
 
/* Centre Row Address Pattern from Table 2 */
private static final String[] RAPC = { "", "112231", "121231", "122131", "131131", "131221", "132121", "141121", "141211", "142111", "133111", "132211", "131311", "122311", "123211", "124111",
"115111", "114211", "114121", "123121", "123112", "122212", "122221", "121321", "121411", "112411", "113311", "113221", "113212", "113122", "122122", "131122", "131113", "122113",
"113113", "112213", "112222", "112312", "112321", "111421", "111331", "111322", "111232", "111223", "111133", "111124", "111214", "112114", "121114", "121123", "121132", "112132",
"112141" };
 
/* MicroPDF417 coefficients from ISO/IEC 24728:2006 Annex F */
private static final int[] MICRO_COEFFS = {
/* k = 7 */
76, 925, 537, 597, 784, 691, 437,
 
/* k = 8 */
237, 308, 436, 284, 646, 653, 428, 379,
 
/* k = 9 */
567, 527, 622, 257, 289, 362, 501, 441, 205,
 
/* k = 10 */
377, 457, 64, 244, 826, 841, 818, 691, 266, 612,
 
/* k = 11 */
462, 45, 565, 708, 825, 213, 15, 68, 327, 602, 904,
 
/* k = 12 */
597, 864, 757, 201, 646, 684, 347, 127, 388, 7, 69, 851,
 
/* k = 13 */
764, 713, 342, 384, 606, 583, 322, 592, 678, 204, 184, 394, 692,
 
/* k = 14 */
669, 677, 154, 187, 241, 286, 274, 354, 478, 915, 691, 833, 105, 215,
 
/* k = 15 */
460, 829, 476, 109, 904, 664, 230, 5, 80, 74, 550, 575, 147, 868, 642,
 
/* k = 16 */
274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42, 176, 65,
 
/* k = 18 */
279, 577, 315, 624, 37, 855, 275, 739, 120, 297, 312, 202, 560, 321, 233, 756, 760, 573,
 
/* k = 21 */
108, 519, 781, 534, 129, 425, 681, 553, 422, 716, 763, 693, 624, 610, 310, 691, 347, 165, 193, 259, 568,
 
/* k = 26 */
443, 284, 887, 544, 788, 93, 477, 760, 331, 608, 269, 121, 159, 830, 446, 893, 699, 245, 441, 454, 325, 858, 131, 847, 764, 169,
 
/* k = 32 */
361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687, 284, 193, 517, 273, 494, 263, 147, 593, 800, 571, 320, 803, 133, 231, 390, 685, 330, 63, 410,
 
/* k = 38 */
234, 228, 438, 848, 133, 703, 529, 721, 788, 322, 280, 159, 738, 586, 388, 684, 445, 680, 245, 595, 614, 233, 812, 32, 284, 658, 745, 229, 95, 689, 920, 771, 554, 289, 231, 125, 117, 518,
 
/* k = 44 */
476, 36, 659, 848, 678, 64, 764, 840, 157, 915, 470, 876, 109, 25, 632, 405, 417, 436, 714, 60, 376, 97, 413, 706, 446, 21, 3, 773, 569, 267, 272, 213, 31, 560, 231, 758, 103, 271, 572,
436, 339, 730, 82, 285,
 
/* k = 50 */
923, 797, 576, 875, 156, 706, 63, 81, 257, 874, 411, 416, 778, 50, 205, 303, 188, 535, 909, 155, 637, 230, 534, 96, 575, 102, 264, 233, 919, 593, 865, 26, 579, 623, 766, 146, 10, 739, 246,
127, 71, 244, 211, 477, 920, 876, 427, 820, 718, 435 };
 
/**
* Creates a new PDF417 symbol instance.
*/
public Pdf417() {
setBarHeight(3);
}
 
/**
* Sets the default bar height (height of a single row) for this symbol (default value is
* <code>3</code>).
*
* @param barHeight the default bar height for this symbol
*/
@Override
public void setBarHeight(final int barHeight) {
super.setBarHeight(barHeight);
}
 
/**
* Sets the width of the symbol by specifying the number of columns of data codewords. Valid
* values are 1-30 for PDF417 and 1-4 for MicroPDF417.
*
* @param columns the number of data columns in the symbol
*/
public void setDataColumns(final int columns) {
this.columns = columns;
}
 
/**
* Returns the number of data columns used by this symbol, or {@code null} if the number of data
* columns has not been set.
*
* @return the number of data columns used by this symbol
*/
public Integer getDataColumns() {
return this.columns;
}
 
/**
* Sets the height of the symbol by specifying the number of rows of data codewords. Valid
* values are 3-90 for PDF417 and 4-44 for MicroPDF417.
*
* @param rows the number of rows in the symbol
*/
public void setRows(final int rows) {
this.rows = rows;
}
 
/**
* Returns the number of rows used by this symbol, or {@code null} if the number of rows has not
* been set.
*
* @return the number of rows used by this symbol
*/
public Integer getRows() {
return this.rows;
}
 
/**
* Set the amount of the symbol which is dedicated to error correction codewords. The number of
* codewords of error correction data is determined by 2<sup>(eccLevel + 1)</sup>. This
* attribute is ignored when using {@link Mode#MICRO micro} mode.
*
* @param eccLevel level of error correction (0-8)
*/
public void setPreferredEccLevel(final int eccLevel) {
if (eccLevel < 0 || eccLevel > 8) {
throw new IllegalArgumentException("ECC level must be between 0 and 8.");
}
this.preferredEccLevel = eccLevel;
}
 
/**
* Returns the preferred error correction level.
*
* @return the preferred error correction level
*/
public int getPreferredEccLevel() {
return this.preferredEccLevel;
}
 
/**
* Forces the use of the specified MicroPDF417 variant. Only valid when using {@link Mode#MICRO
* micro} mode.
*
* @param variant the MicroPDF417 variant to use
*/
public void setVariant(final int variant) {
if (this.symbolMode != Mode.MICRO) {
throw new IllegalArgumentException("Can only set variant when using MICRO mode.");
}
if (variant < 1 || variant > 34) {
throw new IllegalArgumentException("Variant must be between 1 and 34.");
}
this.columns = MICRO_VARIANTS[variant - 1];
this.rows = MICRO_VARIANTS[variant - 1 + 34];
}
 
/**
* If this PDF417 symbol is part of a series of PDF417 symbols appended in a structured format
* (Macro PDF417), this method sets the position of this symbol in the series. Valid values are
* 1 through 99,999 inclusive.
*
* @param position the position of this PDF417 symbol in the structured append series
*/
public void setStructuredAppendPosition(final int position) {
if (position < 1 || position > 99_999) {
throw new IllegalArgumentException("Invalid PDF417 structured append position: " + position);
}
this.structuredAppendPosition = position;
}
 
/**
* Returns the position of this PDF417 symbol in a series of symbols using structured append
* (Macro PDF417). If this symbol is not part of such a series, this method will return
* <code>1</code>.
*
* @return the position of this PDF417 symbol in a series of symbols using structured append
*/
public int getStructuredAppendPosition() {
return this.structuredAppendPosition;
}
 
/**
* If this PDF417 symbol is part of a series of PDF417 symbols appended in a structured format
* (Macro PDF417), this method sets the total number of symbols in the series. Valid values are
* 1 through 99,999 inclusive. A value of 1 indicates that this symbol is not part of a
* structured append series.
*
* @param total the total number of PDF417 symbols in the structured append series
*/
public void setStructuredAppendTotal(final int total) {
if (total < 1 || total > 99_999) {
throw new IllegalArgumentException("Invalid PDF417 structured append total: " + total);
}
this.structuredAppendTotal = total;
}
 
/**
* Returns the size of the series of PDF417 symbols using structured append (Macro PDF417) that
* this symbol is part of. If this symbol is not part of a structured append series, this method
* will return <code>1</code>.
*
* @return size of the series that this symbol is part of
*/
public int getStructuredAppendTotal() {
return this.structuredAppendTotal;
}
 
/**
* If this PDF417 symbol is part of a series of PDF417 symbols appended in a structured format
* (Macro PDF417), this method sets the unique file ID for the series. Valid values are 0
* through 899 inclusive.
*
* @param fileId the unique file ID for the series that this symbol is part of
*/
public void setStructuredAppendFileId(final int fileId) {
if (fileId < 0 || fileId > 899) {
throw new IllegalArgumentException("Invalid PDF417 structured append file ID: " + fileId);
}
this.structuredAppendFileId = fileId;
}
 
/**
* Returns the unique file ID of the series of PDF417 symbols using structured append (Macro
* PDF417) that this symbol is part of. If this symbol is not part of a structured append
* series, this method will return <code>0</code>.
*
* @return the unique file ID for the series that this symbol is part of
*/
public int getStructuredAppendFileId() {
return this.structuredAppendFileId;
}
 
public void setMode(final Mode mode) {
this.symbolMode = mode;
}
 
public Mode getMode() {
return this.symbolMode;
}
 
@Override
protected void encode() {
 
eciProcess();
 
switch (this.symbolMode) {
case MICRO:
processMicroPdf417();
break;
case NORMAL:
case TRUNCATED:
default:
processPdf417();
break;
}
}
 
private void processPdf417() {
int j, loop, offset;
final int[] mccorrection = new int[520];
int total;
int c1, c2, c3;
final int[] dummy = new int[35];
int selectedECCLevel;
final StringBuilder codebarre = new StringBuilder();
final StringBuilder bin = new StringBuilder();
 
final List<Block> blocks = createBlocks(this.inputData);
 
/* now compress the data */
this.codeWordCount = 0;
 
if (this.readerInit) {
this.codeWords[this.codeWordCount] = 921; /* Reader Initialisation */
this.codeWordCount++;
}
 
if (this.eciMode != 3) {
/* Encoding ECI assignment number, from ISO/IEC 15438 Table 8 */
if (this.eciMode <= 899) {
this.codeWords[this.codeWordCount] = 927;
this.codeWordCount++;
this.codeWords[this.codeWordCount] = this.eciMode;
this.codeWordCount++;
}
 
if (this.eciMode >= 900 && this.eciMode <= 810899) {
this.codeWords[this.codeWordCount] = 926;
this.codeWordCount++;
this.codeWords[this.codeWordCount] = this.eciMode / 900 - 1;
this.codeWordCount++;
this.codeWords[this.codeWordCount] = this.eciMode % 900;
this.codeWordCount++;
}
 
if (this.eciMode >= 810900 && this.eciMode <= 811799) {
this.codeWords[this.codeWordCount] = 925;
this.codeWordCount++;
this.codeWords[this.codeWordCount] = this.eciMode - 810900;
this.codeWordCount++;
}
}
 
int blockCount = 0;
for (int i = 0; i < blocks.size(); i++) {
final Block block = blocks.get(i);
switch (block.mode) {
case TEX:
/* text mode */
final boolean firstBlock = i == 0;
processText(blockCount, block.length, firstBlock);
break;
case BYT:
/* octet stream mode */
final EncodingMode lastMode = i == 0 ? EncodingMode.TEX : blocks.get(i - 1).mode;
processBytes(blockCount, block.length, lastMode);
break;
case NUM:
/* numeric mode */
processNumbers(this.inputData, blockCount, block.length, false);
break;
default:
throw new OkapiException("Unknown block type: " + block.mode);
}
blockCount += block.length;
}
 
addMacroCodewords();
 
info("Codewords: ");
for (int i = 0; i < this.codeWordCount; i++) {
infoSpace(this.codeWords[i]);
}
infoLine();
 
/* Now take care of the number of CWs per row */
 
// if we have to default the ECC level, do so per the
// recommendations in the specification (Table E.1)
selectedECCLevel = this.preferredEccLevel;
if (selectedECCLevel < 0) {
if (this.codeWordCount <= 40) {
selectedECCLevel = 2;
} else if (this.codeWordCount <= 160) {
selectedECCLevel = 3;
} else if (this.codeWordCount <= 320) {
selectedECCLevel = 4;
} else if (this.codeWordCount <= 863) {
selectedECCLevel = 5;
} else {
selectedECCLevel = 6;
}
}
 
int k = 1 << selectedECCLevel + 1; // error correction codeword count
final int dataCodeWordCount = this.codeWordCount + k + 1; // not including padding
 
validateRows(3, 90);
validateColumns(1, 30);
 
if (this.columns != null) {
if (this.rows != null) {
// user specified both columns and rows; make sure the data fits
if (this.columns * this.rows < dataCodeWordCount) {
throw new OkapiException("Too few rows (" + this.rows + ") and columns (" + this.columns + ") to hold codewords (" + dataCodeWordCount + ")");
}
} else {
// user only specified column count; figure out row count
this.rows = (int) Math.ceil(dataCodeWordCount / (double) this.columns);
}
} else {
if (this.rows != null) {
// user only specified row count; figure out column count
this.columns = (int) Math.ceil(dataCodeWordCount / (double) this.rows);
} else {
// user didn't specify columns or rows; figure both out
this.columns = (int) (0.5 + Math.sqrt((dataCodeWordCount - 1) / 3.0));
this.rows = (int) Math.ceil(dataCodeWordCount / (double) this.columns);
}
}
 
validateRows(3, 90);
validateColumns(1, 30);
 
/* add the padding */
int paddingCount = this.columns * this.rows - this.codeWordCount - k - 1;
while (paddingCount > 0) {
this.codeWords[this.codeWordCount] = 900;
this.codeWordCount++;
paddingCount--;
}
 
/* add the length descriptor */
for (int i = this.codeWordCount; i > 0; i--) {
this.codeWords[i] = this.codeWords[i - 1];
}
this.codeWordCount++;
this.codeWords[0] = this.codeWordCount;
 
/* 796 - we now take care of the Reed Solomon codes */
switch (selectedECCLevel) {
case 1:
offset = 2;
break;
case 2:
offset = 6;
break;
case 3:
offset = 14;
break;
case 4:
offset = 30;
break;
case 5:
offset = 62;
break;
case 6:
offset = 126;
break;
case 7:
offset = 254;
break;
case 8:
offset = 510;
break;
default:
offset = 0;
break;
}
 
for (loop = 0; loop < 520; loop++) {
mccorrection[loop] = 0;
}
 
for (int i = 0; i < this.codeWordCount; i++) {
total = (this.codeWords[i] + mccorrection[k - 1]) % 929;
for (j = k - 1; j > 0; j--) {
mccorrection[j] = (mccorrection[j - 1] + 929 - total * COEFRS[offset + j] % 929) % 929;
}
mccorrection[0] = (929 - total * COEFRS[offset + j] % 929) % 929;
}
 
infoLine("Data Codewords: " + this.codeWordCount);
infoLine("ECC Codewords: " + k);
 
/* we add these codes to the string */
for (int i = k - 1; i >= 0; i--) {
this.codeWords[this.codeWordCount++] = mccorrection[i] != 0 ? 929 - mccorrection[i] : 0;
}
 
/* make sure total codeword count isn't too high */
if (this.codeWordCount > 929) {
throw new OkapiException("Too many codewords required (" + this.codeWordCount + ", but max is 929)");
}
 
/* 818 - The CW string is finished */
c1 = (this.rows - 1) / 3;
c2 = selectedECCLevel * 3 + (this.rows - 1) % 3;
c3 = this.columns - 1;
 
this.readable = "";
this.row_count = this.rows;
this.pattern = new String[this.rows];
this.row_height = new int[this.rows];
infoLine("Grid Size: " + this.columns + " X " + this.rows);
 
/* we now encode each row */
for (int i = 0; i < this.rows; i++) {
for (j = 0; j < this.columns; j++) {
dummy[j + 1] = this.codeWords[i * this.columns + j];
}
k = i / 3 * 30;
switch (i % 3) {
case 0:
offset = 0; // cluster 0
dummy[0] = k + c1; // left row indicator
dummy[this.columns + 1] = k + c3; // right row indicator
break;
case 1:
offset = 929; // cluster 3
dummy[0] = k + c2; // left row indicator
dummy[this.columns + 1] = k + c1; // right row indicator
break;
case 2:
offset = 1858; // cluster 6
dummy[0] = k + c3; // left row indicator
dummy[this.columns + 1] = k + c2; // right row indicator
break;
}
codebarre.setLength(0);
codebarre.append("+*");
for (j = 0; j <= this.columns + 1; j++) {
if (!(this.symbolMode == Mode.TRUNCATED && j > this.columns)) {
codebarre.append(CODAGEMC[offset + dummy[j]]);
codebarre.append('*');
}
}
if (this.symbolMode != Mode.TRUNCATED) {
codebarre.append('-');
}
bin.setLength(0);
for (j = 0; j < codebarre.length(); j++) {
bin.append(PDF_TTF[positionOf(codebarre.charAt(j), BR_SET)]);
}
this.pattern[i] = bin2pat(bin);
this.row_height[i] = this.default_height;
}
}
 
private void processMicroPdf417() { /* like PDF417 only much smaller! */
 
int k, j, longueur, offset;
int total;
int LeftRAPStart, CentreRAPStart, RightRAPStart, StartCluster;
int LeftRAP, CentreRAP, RightRAP, Cluster, flip, loop;
final int[] dummy = new int[5];
final int[] mccorrection = new int[50];
final StringBuilder codebarre = new StringBuilder();
final StringBuilder bin = new StringBuilder();
 
/* Encoding starts out the same as PDF417, so use the same code */
 
final List<Block> blocks = createBlocks(this.inputData);
 
/* 541 - now compress the data */
this.codeWordCount = 0;
if (this.readerInit) {
this.codeWords[this.codeWordCount] = 921; /* Reader Initialisation */
this.codeWordCount++;
}
 
if (this.eciMode != 3) {
/* Encoding ECI assignment number, from ISO/IEC 15438 Table 8 */
if (this.eciMode <= 899) {
this.codeWords[this.codeWordCount] = 927;
this.codeWordCount++;
this.codeWords[this.codeWordCount] = this.eciMode;
this.codeWordCount++;
}
 
if (this.eciMode >= 900 && this.eciMode <= 810899) {
this.codeWords[this.codeWordCount] = 926;
this.codeWordCount++;
this.codeWords[this.codeWordCount] = this.eciMode / 900 - 1;
this.codeWordCount++;
this.codeWords[this.codeWordCount] = this.eciMode % 900;
this.codeWordCount++;
}
 
if (this.eciMode >= 810900 && this.eciMode <= 811799) {
this.codeWords[this.codeWordCount] = 925;
this.codeWordCount++;
this.codeWords[this.codeWordCount] = this.eciMode - 810900;
this.codeWordCount++;
}
}
 
int blockCount = 0;
for (int i = 0; i < blocks.size(); i++) {
final Block block = blocks.get(i);
switch (block.mode) {
case TEX:
/* text mode */
processText(blockCount, block.length, false); // TODO: this shouldn't always be
// false?
break;
case BYT:
/* octet stream mode */
final EncodingMode lastMode = i == 0 ? EncodingMode.TEX : blocks.get(i - 1).mode;
processBytes(blockCount, block.length, lastMode);
break;
case NUM:
/* numeric mode */
processNumbers(this.inputData, blockCount, block.length, false);
break;
default:
throw new OkapiException("Unknown block type: " + block.mode);
}
blockCount += block.length;
}
 
addMacroCodewords();
 
info("Codewords: ");
for (int i = 0; i < this.codeWordCount; i++) {
infoSpace(this.codeWords[i]);
}
infoLine();
 
/* This is where it all changes! */
 
validateRows(4, 44);
validateColumns(1, 4);
 
if (this.columns != null) {
int max;
switch (this.columns) {
case 1:
max = 20;
break;
case 2:
max = 37;
break;
case 3:
max = 82;
break;
case 4:
max = 126;
break;
default:
throw new OkapiException("Invalid column count: " + this.columns);
}
if (this.codeWordCount > max) {
throw new OkapiException("Too few columns (" + this.columns + ") to hold data codewords (" + this.codeWordCount + ")");
}
}
 
/* Now figure out which variant of the symbol to use and load values accordingly */
 
int variant = getMicroPdf417Variant(this.codeWordCount, this.columns, this.rows);
 
/* Now we have the variant we can load the data */
 
variant--;
this.columns = MICRO_VARIANTS[variant]; /* columns */
this.rows = MICRO_VARIANTS[variant + 34]; /* rows */
k = MICRO_VARIANTS[variant + 68]; /* number of EC CWs */
longueur = this.columns * this.rows - k; /* number of non-EC CWs */
int padding = longueur - this.codeWordCount; /* amount of padding required */
offset = MICRO_VARIANTS[variant + 102]; /* coefficient offset */
 
infoLine("Data Codewords: " + longueur);
infoLine("ECC Codewords: " + k);
 
/* We add the padding */
while (padding > 0) {
this.codeWords[this.codeWordCount] = 900;
this.codeWordCount++;
padding--;
}
 
/* Reed-Solomon error correction */
longueur = this.codeWordCount;
for (loop = 0; loop < 50; loop++) {
mccorrection[loop] = 0;
}
 
for (int i = 0; i < longueur; i++) {
total = (this.codeWords[i] + mccorrection[k - 1]) % 929;
for (j = k - 1; j >= 0; j--) {
if (j == 0) {
mccorrection[j] = (929 - total * MICRO_COEFFS[offset + j] % 929) % 929;
} else {
mccorrection[j] = (mccorrection[j - 1] + 929 - total * MICRO_COEFFS[offset + j] % 929) % 929;
}
}
}
 
for (j = 0; j < k; j++) {
if (mccorrection[j] != 0) {
mccorrection[j] = 929 - mccorrection[j];
}
}
/* we add these codes to the string */
for (int i = k - 1; i >= 0; i--) {
this.codeWords[this.codeWordCount] = mccorrection[i];
this.codeWordCount++;
}
 
/* Now get the RAP (Row Address Pattern) start values */
LeftRAPStart = RAP_TABLE[variant];
CentreRAPStart = RAP_TABLE[variant + 34];
RightRAPStart = RAP_TABLE[variant + 68];
StartCluster = RAP_TABLE[variant + 102] / 3;
 
/* That's all values loaded, get on with the encoding */
 
LeftRAP = LeftRAPStart;
CentreRAP = CentreRAPStart;
RightRAP = RightRAPStart;
Cluster = StartCluster; /*
* Cluster can be 0, 1 or 2 for Cluster(0), Cluster(3) and
* Cluster(6)
*/
 
this.readable = "";
this.pattern = new String[this.rows];
this.row_count = this.rows;
this.row_height = new int[this.rows];
 
infoLine("Grid Size: " + this.columns + " X " + this.row_count);
 
for (int i = 0; i < this.rows; i++) {
codebarre.setLength(0);
offset = 929 * Cluster;
for (j = 0; j < 5; j++) {
dummy[j] = 0;
}
for (j = 0; j < this.columns; j++) {
dummy[j + 1] = this.codeWords[i * this.columns + j];
}
 
/* Copy the data into codebarre */
codebarre.append(RAPLR[LeftRAP]);
codebarre.append('1');
codebarre.append(CODAGEMC[offset + dummy[1]]);
codebarre.append('1');
if (this.columns == 3) {
codebarre.append(RAPC[CentreRAP]);
}
if (this.columns >= 2) {
codebarre.append('1');
codebarre.append(CODAGEMC[offset + dummy[2]]);
codebarre.append('1');
}
if (this.columns == 4) {
codebarre.append(RAPC[CentreRAP]);
}
if (this.columns >= 3) {
codebarre.append('1');
codebarre.append(CODAGEMC[offset + dummy[3]]);
codebarre.append('1');
}
if (this.columns == 4) {
codebarre.append('1');
codebarre.append(CODAGEMC[offset + dummy[4]]);
codebarre.append('1');
}
codebarre.append(RAPLR[RightRAP]);
codebarre.append('1'); /* stop */
 
/* Now codebarre is a mixture of letters and numbers */
 
flip = 1;
bin.setLength(0);
for (loop = 0; loop < codebarre.length(); loop++) {
if (codebarre.charAt(loop) >= '0' && codebarre.charAt(loop) <= '9') {
for (k = 0; k < Character.getNumericValue(codebarre.charAt(loop)); k++) {
if (flip == 0) {
bin.append('0');
} else {
bin.append('1');
}
}
if (flip == 0) {
flip = 1;
} else {
flip = 0;
}
} else {
bin.append(PDF_TTF[positionOf(codebarre.charAt(loop), BR_SET)]);
}
}
 
/* so now pattern[] holds the string of '1's and '0's. - copy this to the symbol */
this.pattern[i] = bin2pat(bin);
this.row_height[i] = this.default_height;
 
/* Set up RAPs and Cluster for next row */
LeftRAP++;
CentreRAP++;
RightRAP++;
Cluster++;
 
if (LeftRAP == 53) {
LeftRAP = 1;
}
if (CentreRAP == 53) {
CentreRAP = 1;
}
if (RightRAP == 53) {
RightRAP = 1;
}
if (Cluster == 3) {
Cluster = 0;
}
}
}
 
private void validateRows(final int min, final int max) {
if (this.rows != null) {
if (this.rows < min) {
throw new OkapiException("Too few rows (" + this.rows + ")");
} else if (this.rows > max) {
throw new OkapiException("Too many rows (" + this.rows + ")");
}
}
}
 
private void validateColumns(final int min, final int max) {
if (this.columns != null) {
if (this.columns < min) {
throw new OkapiException("Too few columns (" + this.columns + ")");
} else if (this.columns > max) {
throw new OkapiException("Too many columns (" + this.columns + ")");
}
}
}
 
private static EncodingMode chooseMode(final int codeascii) {
if (codeascii >= '0' && codeascii <= '9') {
return EncodingMode.NUM;
} else if (codeascii == '\t' || codeascii == '\n' || codeascii == '\r' || codeascii >= ' ' && codeascii <= '~') {
return EncodingMode.TEX;
} else {
return EncodingMode.BYT;
}
}
 
private static int getMicroPdf417Variant(final int codeWordCount, final Integer columns, final Integer rows) {
for (int i = 0; i < 34; i++) {
final int maxCodewordCount = MICRO_AUTOSIZE[i];
if (codeWordCount <= maxCodewordCount) {
final int variant = MICRO_AUTOSIZE[i + 34];
final int columnsForThisVariant = MICRO_VARIANTS[variant - 1];
final int rowsForThisVariant = MICRO_VARIANTS[variant - 1 + 34];
if ((columns == null || columns == columnsForThisVariant) && (rows == null || rows == rowsForThisVariant)) {
return variant;
}
}
}
throw new OkapiException("Unable to determine MicroPDF417 variant for " + codeWordCount + " codewords");
}
 
/** Determines the encoding block groups for the specified data. */
private static List<Block> createBlocks(final int[] data) {
 
final List<Block> blocks = new ArrayList<>();
Block current = null;
 
for (int i = 0; i < data.length; i++) {
final EncodingMode mode = chooseMode(data[i]);
if (current != null && current.mode == mode && (mode != EncodingMode.NUM || current.length < MAX_NUMERIC_COMPACTION_BLOCK_SIZE)) {
current.length++;
} else {
current = new Block(mode);
blocks.add(current);
}
}
 
smoothBlocks(blocks);
 
return blocks;
}
 
/** Combines adjacent blocks of different types in very specific scenarios. */
private static void smoothBlocks(final List<Block> blocks) {
 
for (int i = 0; i < blocks.size(); i++) {
final Block block = blocks.get(i);
final EncodingMode last = i > 0 ? blocks.get(i - 1).mode : EncodingMode.FALSE;
final EncodingMode next = i < blocks.size() - 1 ? blocks.get(i + 1).mode : EncodingMode.FALSE;
if (block.mode == EncodingMode.NUM) {
if (i == 0) { /* first block */
if (next == EncodingMode.TEX && block.length < 8) {
block.mode = EncodingMode.TEX;
} else if (next == EncodingMode.BYT && block.length == 1) {
block.mode = EncodingMode.BYT;
}
} else if (i == blocks.size() - 1) { /* last block */
if (last == EncodingMode.TEX && block.length < 7) {
block.mode = EncodingMode.TEX;
} else if (last == EncodingMode.BYT && block.length == 1) {
block.mode = EncodingMode.BYT;
}
} else { /* not first or last block */
if (last == EncodingMode.BYT && next == EncodingMode.BYT && block.length < 4) {
block.mode = EncodingMode.BYT;
} else if (last == EncodingMode.BYT && next == EncodingMode.TEX && block.length < 4) {
block.mode = EncodingMode.TEX;
} else if (last == EncodingMode.TEX && next == EncodingMode.BYT && block.length < 5) {
block.mode = EncodingMode.TEX;
} else if (last == EncodingMode.TEX && next == EncodingMode.TEX && block.length < 8) {
block.mode = EncodingMode.TEX;
} else if (last == EncodingMode.NUM && next == EncodingMode.TEX && block.length < 8) {
block.mode = EncodingMode.TEX;
}
}
}
}
 
mergeBlocks(blocks);
 
for (int i = 0; i < blocks.size(); i++) {
final Block block = blocks.get(i);
final EncodingMode last = i > 0 ? blocks.get(i - 1).mode : EncodingMode.FALSE;
final EncodingMode next = i < blocks.size() - 1 ? blocks.get(i + 1).mode : EncodingMode.FALSE;
if (block.mode == EncodingMode.TEX && i > 0) { /* not the first */
if (i == blocks.size() - 1) { /* the last one */
if (last == EncodingMode.BYT && block.length == 1) {
block.mode = EncodingMode.BYT;
}
} else { /* not the last one */
if (last == EncodingMode.BYT && next == EncodingMode.BYT && block.length < 5) {
block.mode = EncodingMode.BYT;
}
if ((last == EncodingMode.BYT && next != EncodingMode.BYT || last != EncodingMode.BYT && next == EncodingMode.BYT) && block.length < 3) {
block.mode = EncodingMode.BYT;
}
}
}
}
 
mergeBlocks(blocks);
}
 
/** Combines adjacent blocks of the same type. */
private static void mergeBlocks(final List<Block> blocks) {
for (int i = 1; i < blocks.size(); i++) {
final Block b1 = blocks.get(i - 1);
final Block b2 = blocks.get(i);
if (b1.mode == b2.mode && (b1.mode != EncodingMode.NUM || b1.length + b2.length <= MAX_NUMERIC_COMPACTION_BLOCK_SIZE)) {
b1.length += b2.length;
blocks.remove(i);
i--;
}
}
}
 
private void processText(final int start, final int length, final boolean skipLatch) {
int j, blockIndext, curtable;
int codeascii;
int wnet = 0;
final int[] listet0 = new int[length];
final int[] listet1 = new int[length];
final int[] chainet = new int[length * 4];
 
/* listet will contain the table numbers and the value of each characters */
for (blockIndext = 0; blockIndext < length; blockIndext++) {
codeascii = this.inputData[start + blockIndext];
switch (codeascii) {
case '\t':
listet0[blockIndext] = 12;
listet1[blockIndext] = 12;
break;
case '\n':
listet0[blockIndext] = 8;
listet1[blockIndext] = 15;
break;
case 13:
listet0[blockIndext] = 12;
listet1[blockIndext] = 11;
break;
default:
listet0[blockIndext] = ASCII_X[codeascii - 32];
listet1[blockIndext] = ASCII_Y[codeascii - 32];
break;
}
}
 
curtable = 1; /* default table */
for (j = 0; j < length; j++) {
if ((listet0[j] & curtable) != 0) { /* The character is in the current table */
chainet[wnet] = listet1[j];
wnet++;
} else { /* Obliged to change table */
boolean flag = false; /* True if we change table for only one character */
if (j == length - 1) {
flag = true;
} else {
if ((listet0[j] & listet0[j + 1]) == 0) {
flag = true;
}
}
 
if (flag) { /* we change only one character - look for temporary switch */
if ((listet0[j] & 1) != 0 && curtable == 2) { /* T_UPP */
chainet[wnet] = 27;
chainet[wnet + 1] = listet1[j];
wnet += 2;
}
if ((listet0[j] & 8) != 0) { /* T_PUN */
chainet[wnet] = 29;
chainet[wnet + 1] = listet1[j];
wnet += 2;
}
if (!((listet0[j] & 1) != 0 && curtable == 2 || (listet0[j] & 8) != 0)) {
/* No temporary switch available */
flag = false;
}
}
 
if (!flag) {
int newtable;
 
if (j == length - 1) {
newtable = listet0[j];
} else {
if ((listet0[j] & listet0[j + 1]) == 0) {
newtable = listet0[j];
} else {
newtable = listet0[j] & listet0[j + 1];
}
}
 
/* Maintain the first if several tables are possible */
switch (newtable) {
case 3:
case 5:
case 7:
case 9:
case 11:
case 13:
case 15:
newtable = 1;
break;
case 6:
case 10:
case 14:
newtable = 2;
break;
case 12:
newtable = 4;
break;
}
 
/* select the switch */
switch (curtable) {
case 1:
switch (newtable) {
case 2:
chainet[wnet] = 27;
wnet++;
break;
case 4:
chainet[wnet] = 28;
wnet++;
break;
case 8:
chainet[wnet] = 28;
wnet++;
chainet[wnet] = 25;
wnet++;
break;
}
break;
case 2:
switch (newtable) {
case 1:
chainet[wnet] = 28;
wnet++;
chainet[wnet] = 28;
wnet++;
break;
case 4:
chainet[wnet] = 28;
wnet++;
break;
case 8:
chainet[wnet] = 28;
wnet++;
chainet[wnet] = 25;
wnet++;
break;
}
break;
case 4:
switch (newtable) {
case 1:
chainet[wnet] = 28;
wnet++;
break;
case 2:
chainet[wnet] = 27;
wnet++;
break;
case 8:
chainet[wnet] = 25;
wnet++;
break;
}
break;
case 8:
switch (newtable) {
case 1:
chainet[wnet] = 29;
wnet++;
break;
case 2:
chainet[wnet] = 29;
wnet++;
chainet[wnet] = 27;
wnet++;
break;
case 4:
chainet[wnet] = 29;
wnet++;
chainet[wnet] = 28;
wnet++;
break;
}
break;
}
curtable = newtable;
/* at last we add the character */
chainet[wnet] = listet1[j];
wnet++;
}
}
}
 
if ((wnet & 1) != 0) {
chainet[wnet] = 29;
wnet++;
}
 
/* Now translate the string chainet into codewords */
 
if (!skipLatch) {
// text compaction mode is the default mode for PDF417,
// so no need for an explicit latch if this is the first block
this.codeWords[this.codeWordCount] = 900;
this.codeWordCount++;
}
 
for (j = 0; j < wnet; j += 2) {
final int cw_number = 30 * chainet[j] + chainet[j + 1];
this.codeWords[this.codeWordCount] = cw_number;
this.codeWordCount++;
}
}
 
private void processBytes(int start, final int length, final EncodingMode lastMode) {
int len = 0;
int chunkLen = 0;
BigInteger mantisa;
BigInteger total;
BigInteger word;
 
mantisa = new BigInteger("0");
total = new BigInteger("0");
 
if (length == 1 && lastMode == EncodingMode.TEX) {
this.codeWords[this.codeWordCount++] = 913;
this.codeWords[this.codeWordCount++] = this.inputData[start];
} else {
/* select the switch for multiple of 6 bytes */
if (length % 6 == 0) {
this.codeWords[this.codeWordCount++] = 924;
} else {
this.codeWords[this.codeWordCount++] = 901;
}
 
while (len < length) {
chunkLen = length - len;
if (6 <= chunkLen) /* Take groups of 6 */ {
chunkLen = 6;
len += chunkLen;
total = BigInteger.valueOf(0);
 
while (chunkLen-- != 0) {
mantisa = BigInteger.valueOf(this.inputData[start++]);
total = total.or(mantisa.shiftLeft(chunkLen * 8));
}
 
chunkLen = 5;
 
while (chunkLen-- != 0) {
 
word = total.mod(BigInteger.valueOf(900));
this.codeWords[this.codeWordCount + chunkLen] = word.intValue();
total = total.divide(BigInteger.valueOf(900));
}
this.codeWordCount += 5;
} else /* If it remain a group of less than 6 bytes */ {
len += chunkLen;
while (chunkLen-- != 0) {
this.codeWords[this.codeWordCount++] = this.inputData[start++];
}
}
}
}
}
 
private void processNumbers(final int[] data, final int start, final int length, final boolean skipLatch) {
 
BigInteger tVal, dVal;
final int[] d = new int[16];
int cw_count;
 
if (!skipLatch) {
// we don't need to latch to numeric mode in some cases, e.g.
// during numeric compaction of the Macro PDF417 segment index
this.codeWords[this.codeWordCount++] = 902;
}
 
final StringBuilder t = new StringBuilder(length + 1);
t.append('1');
for (int i = 0; i < length; i++) {
t.append((char) data[start + i]);
}
 
tVal = new BigInteger(t.toString());
 
cw_count = 0;
do {
dVal = tVal.mod(BigInteger.valueOf(900));
d[cw_count] = dVal.intValue();
tVal = tVal.divide(BigInteger.valueOf(900));
cw_count++;
} while (tVal.compareTo(BigInteger.ZERO) == 1);
 
for (int i = cw_count - 1; i >= 0; i--) {
this.codeWords[this.codeWordCount++] = d[i];
}
}
 
/** Adds the Macro PDF417 control block codewords (if any). */
private void addMacroCodewords() {
 
// if the structured append series size is 1, this isn't
// actually part of a structured append series
if (this.structuredAppendTotal == 1) {
return;
}
 
// add the Macro marker codeword
this.codeWords[this.codeWordCount++] = 928;
 
// add the segment index, padded with leading zeros to five digits
// use numeric compaction, but no latch
int segmentIndex = this.structuredAppendPosition - 1;
final int[] data = new int[5];
for (int x = data.length - 1; x >= 0; x--) {
data[x] = '0' + segmentIndex % 10;
segmentIndex /= 10;
}
processNumbers(data, 0, data.length, true);
 
// add the file ID (base 900, which is easy since we limit
// file ID values to the range 0 to 899)
this.codeWords[this.codeWordCount++] = this.structuredAppendFileId;
 
// NOTE: we could add the optional segment count field here, but
// it doesn't appear to be necessary... if we do eventually decide
// to add it, it will probably be [923, 001, count1, count2]
 
// add the terminator to the last symbol of the series
final boolean last = this.structuredAppendPosition == this.structuredAppendTotal;
if (last) {
this.codeWords[this.codeWordCount++] = 922;
}
}
 
private static class Block {
 
public EncodingMode mode;
public int length;
 
public Block(final EncodingMode mode) {
this.mode = mode;
this.length = 1;
}
 
@Override
public String toString() {
return this.mode + "x" + this.length;
}
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Symbol.java
New file
0,0 → 1,841
/*
* Copyright 2014-2018 Robin Stuart, Daniel Gredler
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import static uk.org.okapibarcode.backend.HumanReadableAlignment.CENTER;
import static uk.org.okapibarcode.backend.HumanReadableLocation.BOTTOM;
import static uk.org.okapibarcode.backend.HumanReadableLocation.NONE;
import static uk.org.okapibarcode.backend.HumanReadableLocation.TOP;
import static uk.org.okapibarcode.util.Arrays.containsAt;
import static uk.org.okapibarcode.util.Arrays.positionOf;
import static uk.org.okapibarcode.util.Doubles.roughlyEqual;
 
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
 
import uk.org.okapibarcode.output.Java2DRenderer;
import uk.org.okapibarcode.util.EciMode;
import uk.org.okapibarcode.util.Gs1;
 
/**
* Generic barcode symbology class.
*
* TODO: Setting attributes like module width, font size, etc should probably throw an exception if
* set *after* encoding has already been completed.
*
* TODO: GS1 data is encoded slightly differently depending on whether [AI]data content is used, or
* if FNC1 escape sequences are used. We may want to make sure that they encode to the same output.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public abstract class Symbol {
 
public static enum DataType {
ECI, GS1, HIBC
}
 
protected static final int FNC1 = -1;
protected static final int FNC2 = -2;
protected static final int FNC3 = -3;
protected static final int FNC4 = -4;
 
protected static final String FNC1_STRING = "\\<FNC1>";
protected static final String FNC2_STRING = "\\<FNC2>";
protected static final String FNC3_STRING = "\\<FNC3>";
protected static final String FNC4_STRING = "\\<FNC4>";
 
private static char[] HIBC_CHAR_TABLE = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', '%' };
 
// user-specified values and settings
 
protected DataType inputDataType = DataType.ECI;
protected boolean readerInit;
protected int default_height = 40;
protected int quietZoneHorizontal = 0;
protected int quietZoneVertical = 0;
protected int moduleWidth = 1;
protected Font font;
protected String fontName = "Helvetica";
protected int fontSize = 8;
protected HumanReadableLocation humanReadableLocation = BOTTOM;
protected HumanReadableAlignment humanReadableAlignment = CENTER;
protected boolean emptyContentAllowed = false;
 
// internal state calculated when setContent() is called
 
protected String content;
protected int eciMode = -1;
protected int[] inputData; // usually bytes (values 0-255), but may also contain FNC flags
protected String readable = "";
protected String[] pattern;
protected int row_count = 0;
protected int[] row_height;
protected int symbol_height = 0;
protected int symbol_width = 0;
protected StringBuilder encodeInfo = new StringBuilder();
protected List<Rectangle2D.Double> rectangles = new ArrayList<>(); // note positions do not
// account for quiet zones
// (handled in renderers)
protected List<TextBox> texts = new ArrayList<>(); // note positions do not account for quiet
// zones (handled in renderers)
protected List<Hexagon> hexagons = new ArrayList<>(); // note positions do not account for quiet
// zones (handled in renderers)
protected List<Ellipse2D.Double> target = new ArrayList<>(); // note positions do not account
// for quiet zones (handled in
// renderers)
 
/**
* <p>
* Sets the type of input data. This setting influences what pre-processing is done on data
* before encoding in the symbol. For example: for <code>GS1</code> mode the AI data will be
* used to calculate the position of 'FNC1' characters.
*
* <p>
* Valid values are:
*
* <ul>
* <li><code>ECI</code> Extended Channel Interpretations (default)
* <li><code>GS1</code> Application Identifier and data pairs in "[AI]DATA" format
* <li><code>HIBC</code> Health Industry Bar Code number (without check digit)
* </ul>
*
* @param dataType the type of input data
*/
public void setDataType(final DataType dataType) {
if (dataType == DataType.GS1 && !gs1Supported()) {
throw new IllegalArgumentException("This symbology type does not support GS1 data.");
}
this.inputDataType = dataType;
}
 
/**
* Returns the type of input data in this symbol.
*
* @return the type of input data in this symbol
*/
public DataType getDataType() {
return this.inputDataType;
}
 
/**
* Returns <code>true</code> if this type of symbology supports GS1 data.
*
* @return <code>true</code> if this type of symbology supports GS1 data
*/
protected boolean gs1Supported() {
return false;
}
 
/**
* If set to <code>true</code>, the symbol is prefixed with a "Reader Initialization" or "Reader
* Programming" instruction.
*
* @param readerInit whether or not to enable reader initialization
*/
public void setReaderInit(final boolean readerInit) {
this.readerInit = readerInit;
}
 
/**
* Returns whether or not reader initialization is enabled.
*
* @return whether or not reader initialization is enabled
*/
public boolean getReaderInit() {
return this.readerInit;
}
 
/**
* Sets the default bar height for this symbol (default value is <code>40</code>).
*
* @param barHeight the default bar height for this symbol
*/
public void setBarHeight(final int barHeight) {
this.default_height = barHeight;
}
 
/**
* Returns the default bar height for this symbol.
*
* @return the default bar height for this symbol
*/
public int getBarHeight() {
return this.default_height;
}
 
/**
* Sets the module width for this symbol (default value is <code>1</code>).
*
* @param moduleWidth the module width for this symbol
*/
public void setModuleWidth(final int moduleWidth) {
this.moduleWidth = moduleWidth;
}
 
/**
* Returns the module width for this symbol.
*
* @return the module width for this symbol
*/
public int getModuleWidth() {
return this.moduleWidth;
}
 
/**
* Sets the horizontal quiet zone (white space) added to the left and to the right of this
* symbol.
*
* @param quietZoneHorizontal the horizontal quiet zone (white space) added to the left and to
* the right of this symbol
*/
public void setQuietZoneHorizontal(final int quietZoneHorizontal) {
this.quietZoneHorizontal = quietZoneHorizontal;
}
 
/**
* Returns the horizontal quiet zone (white space) added to the left and to the right of this
* symbol.
*
* @return the horizontal quiet zone (white space) added to the left and to the right of this
* symbol
*/
public int getQuietZoneHorizontal() {
return this.quietZoneHorizontal;
}
 
/**
* Sets the vertical quiet zone (white space) added above and below this symbol.
*
* @param quietZoneVertical the vertical quiet zone (white space) added above and below this
* symbol
*/
public void setQuietZoneVertical(final int quietZoneVertical) {
this.quietZoneVertical = quietZoneVertical;
}
 
/**
* Returns the vertical quiet zone (white space) added above and below this symbol.
*
* @return the vertical quiet zone (white space) added above and below this symbol
*/
public int getQuietZoneVertical() {
return this.quietZoneVertical;
}
 
/**
* <p>
* Sets the font to use to render the human-readable text. This is an alternative to setting the
* {@link #setFontName(String) font name} and {@link #setFontSize(int) font size} separately.
* May allow some applications to avoid the use of
* {@link GraphicsEnvironment#registerFont(Font)} when using the {@link Java2DRenderer}.
*
* <p>
* Do not use this method in combination with {@link #setFontName(String)} or
* {@link #setFontSize(int)}.
*
* @param font the font to use to render the human-readable text
*/
public void setFont(final Font font) {
this.font = font;
this.fontName = font.getFontName();
this.fontSize = font.getSize();
}
 
/**
* Returns the font to use to render the human-readable text.
*
* @return the font to use to render the human-readable text
*/
public Font getFont() {
return this.font;
}
 
/**
* <p>
* Sets the name of the font to use to render the human-readable text (default value is
* <code>Helvetica</code>). The specified font name needs to be registered via
* {@link GraphicsEnvironment#registerFont(Font)} if you are using the {@link Java2DRenderer}.
* In order to set the font without registering the font with the graphics environment when
* using the {@link Java2DRenderer}, you may need to use {@link #setFont(Font)} instead.
*
* <p>
* Use this method in combination with {@link #setFontSize(int)}.
*
* <p>
* Do not use this method in combination with {@link #setFont(Font)}.
*
* @param fontName the name of the font to use to render the human-readable text
*/
public void setFontName(final String fontName) {
this.fontName = Objects.requireNonNull(fontName, "font name may not be null");
this.font = null;
}
 
/**
* Returns the name of the font to use to render the human-readable text.
*
* @return the name of the font to use to render the human-readable text
*/
public String getFontName() {
return this.fontName;
}
 
/**
* <p>
* Sets the size of the font to use to render the human-readable text (default value is
* <code>8</code>).
*
* <p>
* Use this method in combination with {@link #setFontName(String)}.
*
* <p>
* Do not use this method in combination with {@link #setFont(Font)}.
*
* @param fontSize the size of the font to use to render the human-readable text
*/
public void setFontSize(final int fontSize) {
this.fontSize = fontSize;
this.font = null;
}
 
/**
* Returns the size of the font to use to render the human-readable text.
*
* @return the size of the font to use to render the human-readable text
*/
public int getFontSize() {
return this.fontSize;
}
 
/**
* Gets the width of the encoded symbol, including the horizontal quiet zone.
*
* @return the width of the encoded symbol
*/
public int getWidth() {
return this.symbol_width + 2 * this.quietZoneHorizontal;
}
 
/**
* Returns the height of the symbol, including the human-readable text, if any, as well as the
* vertical quiet zone. This height is an approximation, since it is calculated without access
* to a font engine.
*
* @return the height of the symbol, including the human-readable text, if any, as well as the
* vertical quiet zone
*/
public int getHeight() {
return this.symbol_height + getHumanReadableHeight() + 2 * this.quietZoneVertical;
}
 
/**
* Returns the height of the human-readable text, including the space between the text and other
* symbols. This height is an approximation, since it is calculated without access to a font
* engine.
*
* @return the height of the human-readable text
*/
public int getHumanReadableHeight() {
if (this.texts.isEmpty()) {
return 0;
} else {
return getTheoreticalHumanReadableHeight();
}
}
 
/**
* Returns the height of the human-readable text, assuming this symbol had human-readable text.
*
* @return the height of the human-readable text, assuming this symbol had human-readable text
*/
protected int getTheoreticalHumanReadableHeight() {
return (int) Math.ceil(this.fontSize * 1.2); // 0.2 space between bars and text
}
 
/**
* Returns a human readable summary of the decisions made by the encoder when creating a symbol.
*
* @return a human readable summary of the decisions made by the encoder when creating a symbol
*/
public String getEncodeInfo() {
return this.encodeInfo.toString();
}
 
/**
* Returns the ECI mode used by this symbol. The ECI mode is chosen automatically during
* encoding if the symbol data type has been set to {@link DataType#ECI}. If this symbol does
* not use ECI, this method will return <code>-1</code>.
*
* @return the ECI mode used by this symbol
* @see #eciProcess()
*/
public int getEciMode() {
return this.eciMode;
}
 
/**
* Sets the location of the human-readable text (default value is
* {@link HumanReadableLocation#BOTTOM}).
*
* @param humanReadableLocation the location of the human-readable text
*/
public void setHumanReadableLocation(final HumanReadableLocation humanReadableLocation) {
this.humanReadableLocation = humanReadableLocation;
}
 
/**
* Returns the location of the human-readable text.
*
* @return the location of the human-readable text
*/
public HumanReadableLocation getHumanReadableLocation() {
return this.humanReadableLocation;
}
 
/**
* Sets the text alignment of the human-readable text (default value is
* {@link HumanReadableAlignment#CENTER}).
*
* @param humanReadableAlignment the text alignment of the human-readable text
*/
public void setHumanReadableAlignment(final HumanReadableAlignment humanReadableAlignment) {
this.humanReadableAlignment = humanReadableAlignment;
}
 
/**
* Returns the text alignment of the human-readable text.
*
* @return the text alignment of the human-readable text
*/
public HumanReadableAlignment getHumanReadableAlignment() {
return this.humanReadableAlignment;
}
 
/**
* Returns render information about the rectangles in this symbol.
*
* @return render information about the rectangles in this symbol
*/
public List<Rectangle2D.Double> getRectangles() {
return this.rectangles;
}
 
/**
* Returns render information about the text elements in this symbol.
*
* @return render information about the text elements in this symbol
*/
public List<TextBox> getTexts() {
return this.texts;
}
 
/**
* Returns render information about the hexagons in this symbol.
*
* @return render information about the hexagons in this symbol
*/
public List<Hexagon> getHexagons() {
return this.hexagons;
}
 
/**
* Returns render information about the target circles in this symbol.
*
* @return render information about the target circles in this symbol
*/
public List<Ellipse2D.Double> getTarget() {
return this.target;
}
 
protected static String bin2pat(final CharSequence bin) {
 
int len = 0;
boolean black = true;
final StringBuilder pattern = new StringBuilder(bin.length());
 
for (int i = 0; i < bin.length(); i++) {
if (black) {
if (bin.charAt(i) == '1') {
len++;
} else {
black = false;
pattern.append((char) (len + '0'));
len = 1;
}
} else {
if (bin.charAt(i) == '0') {
len++;
} else {
black = true;
pattern.append((char) (len + '0'));
len = 1;
}
}
}
 
pattern.append((char) (len + '0'));
return pattern.toString();
}
 
/**
* Sets whether or not empty content is allowed. Some symbologies may be able to generate empty
* symbols when no data is present, though this is not usually desired behavior. The default
* value is <code>false</code> (no empty content allowed).
*
* @param emptyContentAllowed whether or not empty content is allowed
*/
public void setEmptyContentAllowed(final boolean emptyContentAllowed) {
this.emptyContentAllowed = emptyContentAllowed;
}
 
/**
* Returns whether or not empty content is allowed.
*
* @return whether or not empty content is allowed
*/
public boolean getEmptyContentAllowed() {
return this.emptyContentAllowed;
}
 
/**
* Sets the data to be encoded and triggers encoding. Input data will be assumed to be of the
* type set by {@link #setDataType(DataType)}.
*
* @param data the data to encode
* @throws OkapiException if no data or data is invalid
*/
public void setContent(String data) {
 
if (data == null) {
data = "";
}
 
this.encodeInfo.setLength(0); // clear
 
switch (this.inputDataType) {
case GS1:
this.content = Gs1.verify(data, FNC1_STRING);
this.readable = data.replace('[', '(').replace(']', ')');
break;
case HIBC:
this.content = hibcProcess(data);
break;
default:
this.content = data;
break;
}
 
if (this.content.isEmpty() && !this.emptyContentAllowed) {
throw new OkapiException("No input data");
}
 
encode();
plotSymbol();
mergeVerticalBlocks();
}
 
/**
* Returns the content encoded by this symbol.
*
* @return the content encoded by this symbol
*/
public String getContent() {
return this.content;
}
 
/**
* Returns the human-readable text for this symbol.
*
* @return the human-readable text for this symbol
*/
public String getHumanReadableText() {
return this.readable;
}
 
/**
* Chooses the ECI mode most suitable for the content of this symbol.
*/
protected void eciProcess() {
 
final EciMode eci = EciMode.of(this.content, "ISO8859_1", 3).or(this.content, "ISO8859_2", 4).or(this.content, "ISO8859_3", 5).or(this.content, "ISO8859_4", 6).or(this.content, "ISO8859_5", 7)
.or(this.content, "ISO8859_6", 8).or(this.content, "ISO8859_7", 9).or(this.content, "ISO8859_8", 10).or(this.content, "ISO8859_9", 11).or(this.content, "ISO8859_10", 12)
.or(this.content, "ISO8859_11", 13).or(this.content, "ISO8859_13", 15).or(this.content, "ISO8859_14", 16).or(this.content, "ISO8859_15", 17).or(this.content, "ISO8859_16", 18)
.or(this.content, "Windows_1250", 21).or(this.content, "Windows_1251", 22).or(this.content, "Windows_1252", 23).or(this.content, "Windows_1256", 24).or(this.content, "SJIS", 20)
.or(this.content, "UTF8", 26);
 
if (EciMode.NONE.equals(eci)) {
throw new OkapiException("Unable to determine ECI mode.");
}
 
this.eciMode = eci.mode;
this.inputData = toBytes(this.content, eci.charset);
 
infoLine("ECI Mode: " + eci.mode);
infoLine("ECI Charset: " + eci.charset.name());
}
 
protected static int[] toBytes(final String s, final Charset charset, final int... suffix) {
 
if (!charset.newEncoder().canEncode(s)) {
return null;
}
 
final byte[] fnc1 = FNC1_STRING.getBytes(charset);
final byte[] fnc2 = FNC2_STRING.getBytes(charset);
final byte[] fnc3 = FNC3_STRING.getBytes(charset);
final byte[] fnc4 = FNC4_STRING.getBytes(charset);
 
final byte[] bytes = s.getBytes(charset);
int[] data = new int[bytes.length + suffix.length];
 
int i = 0, j = 0;
for (; i < bytes.length; i++, j++) {
if (containsAt(bytes, fnc1, i)) {
data[j] = FNC1;
i += fnc1.length - 1;
} else if (containsAt(bytes, fnc2, i)) {
data[j] = FNC2;
i += fnc1.length - 1;
} else if (containsAt(bytes, fnc3, i)) {
data[j] = FNC3;
i += fnc1.length - 1;
} else if (containsAt(bytes, fnc4, i)) {
data[j] = FNC4;
i += fnc1.length - 1;
} else {
data[j] = bytes[i] & 0xff;
}
}
 
int k = 0;
for (; k < suffix.length; k++) {
data[j + k] = suffix[k];
}
 
if (j + k < i) {
data = Arrays.copyOf(data, j + k);
}
 
return data;
}
 
protected abstract void encode();
 
protected void plotSymbol() {
int xBlock, yBlock;
double x, y, w, h;
boolean black;
 
this.rectangles.clear();
this.texts.clear();
 
int baseY;
if (this.humanReadableLocation == TOP) {
baseY = getTheoreticalHumanReadableHeight();
} else {
baseY = 0;
}
 
h = 0;
y = baseY;
 
for (yBlock = 0; yBlock < this.row_count; yBlock++) {
black = true;
x = 0;
for (xBlock = 0; xBlock < this.pattern[yBlock].length(); xBlock++) {
final char c = this.pattern[yBlock].charAt(xBlock);
w = getModuleWidth(c - '0') * this.moduleWidth;
if (black) {
if (this.row_height[yBlock] == -1) {
h = this.default_height;
} else {
h = this.row_height[yBlock];
}
if (w != 0 && h != 0) {
final Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h);
this.rectangles.add(rect);
}
if (x + w > this.symbol_width) {
this.symbol_width = (int) Math.ceil(x + w);
}
}
black = !black;
x += w;
}
if (y - baseY + h > this.symbol_height) {
this.symbol_height = (int) Math.ceil(y - baseY + h);
}
y += h;
}
 
if (this.humanReadableLocation != NONE && !this.readable.isEmpty()) {
double baseline;
if (this.humanReadableLocation == TOP) {
baseline = this.fontSize;
} else {
baseline = this.symbol_height + this.fontSize;
}
this.texts.add(new TextBox(0, baseline, this.symbol_width, this.readable, this.humanReadableAlignment));
}
}
 
/**
* Returns the module width to use for the specified original module width, taking into account
* any module width ratio customizations. Intended to be overridden by subclasses that support
* such module width ratio customization.
*
* @param originalWidth the original module width
* @return the module width to use for the specified original module width
*/
protected double getModuleWidth(final int originalWidth) {
return originalWidth;
}
 
/**
* Search for rectangles which have the same width and x position, and which join together
* vertically and merge them together to reduce the number of rectangles needed to describe a
* symbol. This can actually take a non-trivial amount of time for symbols with a large number
* of rectangles (like large PDF417 symbols) so we exploit the fact that the rectangles are
* ordered by rows (and within the rows that they are ordered by x position).
*/
protected void mergeVerticalBlocks() {
 
final int before = this.rectangles.size();
 
for (int i = this.rectangles.size() - 1; i >= 0; i--) {
final Rectangle2D.Double rect1 = this.rectangles.get(i);
for (int j = i - 1; j >= 0; j--) {
final Rectangle2D.Double rect2 = this.rectangles.get(j);
if (roughlyEqual(rect1.y, rect2.y + rect2.height)) {
// rect2 is in the segment of rectangles for the row directly above rect1
if (roughlyEqual(rect1.x, rect2.x) && roughlyEqual(rect1.width, rect2.width)) {
// we've found a match; merge the rectangles
rect2.height += rect1.height;
this.rectangles.remove(i);
break;
}
if (rect2.x < rect1.x) {
// we've moved past any rectangles that might be directly above rect1
break;
}
}
}
}
 
final int after = this.rectangles.size();
if (before != after) {
infoLine("Blocks Merged: " + before + " -> " + after);
}
}
 
/**
* Adds the HIBC prefix and check digit to the specified data, returning the resultant data
* string.
*
* @see <a href=
* "https://sourceforge.net/p/zint/code/ci/master/tree/backend/library.c">Corresponding
* Zint code</a>
*/
private String hibcProcess(String source) {
 
// HIBC 2.6 allows up to 110 characters, not including the "+" prefix or the check digit
if (source.length() > 110) {
throw new OkapiException("Data too long for HIBC LIC");
}
 
source = source.toUpperCase();
if (!source.matches("[A-Z0-9-\\. \\$/+\\%]+?")) {
throw new OkapiException("Invalid characters in input");
}
 
int counter = 41;
for (int i = 0; i < source.length(); i++) {
counter += positionOf(source.charAt(i), HIBC_CHAR_TABLE);
}
counter = counter % 43;
 
final char checkDigit = HIBC_CHAR_TABLE[counter];
 
infoLine("HIBC Check Digit Counter: " + counter);
infoLine("HIBC Check Digit: " + checkDigit);
 
return "+" + source + checkDigit;
}
 
/**
* Returns the intermediate coding of this bar code. Symbol types that use the test
* infrastructure should override this method.
*
* @return the intermediate coding of this bar code
*/
protected int[] getCodewords() {
throw new UnsupportedOperationException();
}
 
/**
* Returns this bar code's pattern, converted into a set of corresponding codewords. Useful for
* bar codes that encode their content as a pattern.
*
* @param size the number of digits in each codeword
* @return this bar code's pattern, converted into a set of corresponding codewords
*/
protected int[] getPatternAsCodewords(final int size) {
if (size >= 10) {
throw new IllegalArgumentException("Pattern groups of 10 or more digits are likely to be too large to parse as integers.");
}
if (this.pattern == null || this.pattern.length == 0) {
return new int[0];
} else {
final int count = (int) Math.ceil(this.pattern[0].length() / (double) size);
final int[] codewords = new int[this.pattern.length * count];
for (int i = 0; i < this.pattern.length; i++) {
final String row = this.pattern[i];
for (int j = 0; j < count; j++) {
final int substringStart = j * size;
final int substringEnd = Math.min((j + 1) * size, row.length());
codewords[i * count + j] = Integer.parseInt(row.substring(substringStart, substringEnd));
}
}
return codewords;
}
}
 
protected void info(final CharSequence s) {
this.encodeInfo.append(s);
}
 
protected void infoSpace(final int i) {
this.encodeInfo.append(i).append(' ');
}
 
protected void infoSpace(final char c) {
this.encodeInfo.append(c).append(' ');
}
 
protected void infoLine(final CharSequence s) {
this.encodeInfo.append(s).append('\n');
}
 
protected void infoLine() {
this.encodeInfo.append('\n');
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/DataBarLimited.java
New file
0,0 → 1,423
/*
* Copyright 2014-2018 Robin Stuart, Daniel Gredler
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import java.math.BigInteger;
 
/**
* <p>
* Implements GS1 DataBar Limited according to ISO/IEC 24724:2011.
*
* <p>
* Input data should be a 12-digit or 13-digit Global Trade Identification Number (GTIN) without
* check digit or Application Identifier [01].
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class DataBarLimited extends Symbol {
 
private static final int[] T_EVEN_LTD = { 28, 728, 6454, 203, 2408, 1, 16632 };
 
private static final int[] MODULES_ODD_LTD = { 17, 13, 9, 15, 11, 19, 7 };
 
private static final int[] MODULES_EVEN_LTD = { 9, 13, 17, 11, 15, 7, 19 };
 
private static final int[] WIDEST_ODD_LTD = { 6, 5, 3, 5, 4, 8, 1 };
 
private static final int[] WIDEST_EVEN_LTD = { 3, 4, 6, 4, 5, 1, 8 };
 
private static final int[] CHECKSUM_WEIGHT_LTD = { /* Table 7 */
1, 3, 9, 27, 81, 65, 17, 51, 64, 14, 42, 37, 22, 66, 20, 60, 2, 6, 18, 54, 73, 41, 34, 13, 39, 28, 84, 74 };
 
private static final int[] FINDER_PATTERN_LTD = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 3, 1, 1, 1,
1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1,
2, 1, 1, 3, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1,
1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 2, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1,
1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, 1, 1,
1, 1, 1, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1,
1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 2, 1, 1,
1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 2,
1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 1, 1,
1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2,
1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 2, 1, 1, 1,
1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 2, 1, 1,
1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1,
1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1,
2, 1, 2, 2, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1,
2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1,
3, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 2, 1,
1 /* ISO/IEC 24724-2011 57 */
};
 
private boolean linkageFlag;
 
/**
* Although this is a GS1 symbology, input data is expected to omit the [01] Application
* Identifier, as well as the check digit. Thus, the input data is not considered GS1-format
* data.
*/
@Override
protected boolean gs1Supported() {
return false;
}
 
protected void setLinkageFlag() {
this.linkageFlag = true;
}
 
protected void unsetLinkageFlag() {
this.linkageFlag = false;
}
 
@Override
protected void encode() {
BigInteger accum;
BigInteger left_reg;
BigInteger right_reg;
int left_group;
int right_group;
int i, j;
int left_character;
int right_character;
int left_odd;
int right_odd;
int left_even;
int right_even;
final int[] left_widths = new int[14];
final int[] right_widths = new int[14];
int checksum;
final int[] check_elements = new int[14];
final int[] total_widths = new int[46];
boolean bar_latch;
int check_digit = 0;
int count = 0;
String hrt;
int compositeOffset = 0;
 
if (this.content.length() > 13) {
throw new OkapiException("Input too long");
}
 
if (!this.content.matches("[0-9]+?")) {
throw new OkapiException("Invalid characters in input");
}
 
if (this.content.length() == 13 && this.content.charAt(0) != '0' && this.content.charAt(0) != '1') {
throw new OkapiException("Input out of range");
}
 
accum = new BigInteger(this.content);
 
if (this.linkageFlag) {
/* Add symbol linkage flag */
accum = accum.add(new BigInteger("2015133531096"));
}
 
/* Calculate left and right pair values */
left_reg = accum.divide(new BigInteger("2013571"));
right_reg = accum.mod(new BigInteger("2013571"));
 
left_group = 0;
if (left_reg.compareTo(new BigInteger("183063")) == 1) {
left_group = 1;
}
if (left_reg.compareTo(new BigInteger("820063")) == 1) {
left_group = 2;
}
if (left_reg.compareTo(new BigInteger("1000775")) == 1) {
left_group = 3;
}
if (left_reg.compareTo(new BigInteger("1491020")) == 1) {
left_group = 4;
}
if (left_reg.compareTo(new BigInteger("1979844")) == 1) {
left_group = 5;
}
if (left_reg.compareTo(new BigInteger("1996938")) == 1) {
left_group = 6;
}
 
right_group = 0;
if (right_reg.compareTo(new BigInteger("183063")) == 1) {
right_group = 1;
}
if (right_reg.compareTo(new BigInteger("820063")) == 1) {
right_group = 2;
}
if (right_reg.compareTo(new BigInteger("1000775")) == 1) {
right_group = 3;
}
if (right_reg.compareTo(new BigInteger("1491020")) == 1) {
right_group = 4;
}
if (right_reg.compareTo(new BigInteger("1979844")) == 1) {
right_group = 5;
}
if (right_reg.compareTo(new BigInteger("1996938")) == 1) {
right_group = 6;
}
 
infoLine("Data Characters: " + (left_group + 1) + " " + (right_group + 1));
 
switch (left_group) {
case 1:
left_reg = left_reg.subtract(new BigInteger("183064"));
break;
case 2:
left_reg = left_reg.subtract(new BigInteger("820064"));
break;
case 3:
left_reg = left_reg.subtract(new BigInteger("1000776"));
break;
case 4:
left_reg = left_reg.subtract(new BigInteger("1491021"));
break;
case 5:
left_reg = left_reg.subtract(new BigInteger("1979845"));
break;
case 6:
left_reg = left_reg.subtract(new BigInteger("1996939"));
break;
}
 
switch (right_group) {
case 1:
right_reg = right_reg.subtract(new BigInteger("183064"));
break;
case 2:
right_reg = right_reg.subtract(new BigInteger("820064"));
break;
case 3:
right_reg = right_reg.subtract(new BigInteger("1000776"));
break;
case 4:
right_reg = right_reg.subtract(new BigInteger("1491021"));
break;
case 5:
right_reg = right_reg.subtract(new BigInteger("1979845"));
break;
case 6:
right_reg = right_reg.subtract(new BigInteger("1996939"));
break;
}
 
left_character = left_reg.intValue();
right_character = right_reg.intValue();
 
left_odd = left_character / T_EVEN_LTD[left_group];
left_even = left_character % T_EVEN_LTD[left_group];
right_odd = right_character / T_EVEN_LTD[right_group];
right_even = right_character % T_EVEN_LTD[right_group];
 
int[] widths = getWidths(left_odd, MODULES_ODD_LTD[left_group], 7, WIDEST_ODD_LTD[left_group], 1);
left_widths[0] = widths[0];
left_widths[2] = widths[1];
left_widths[4] = widths[2];
left_widths[6] = widths[3];
left_widths[8] = widths[4];
left_widths[10] = widths[5];
left_widths[12] = widths[6];
 
widths = getWidths(left_even, MODULES_EVEN_LTD[left_group], 7, WIDEST_EVEN_LTD[left_group], 0);
left_widths[1] = widths[0];
left_widths[3] = widths[1];
left_widths[5] = widths[2];
left_widths[7] = widths[3];
left_widths[9] = widths[4];
left_widths[11] = widths[5];
left_widths[13] = widths[6];
 
widths = getWidths(right_odd, MODULES_ODD_LTD[right_group], 7, WIDEST_ODD_LTD[right_group], 1);
right_widths[0] = widths[0];
right_widths[2] = widths[1];
right_widths[4] = widths[2];
right_widths[6] = widths[3];
right_widths[8] = widths[4];
right_widths[10] = widths[5];
right_widths[12] = widths[6];
 
widths = getWidths(right_even, MODULES_EVEN_LTD[right_group], 7, WIDEST_EVEN_LTD[right_group], 0);
right_widths[1] = widths[0];
right_widths[3] = widths[1];
right_widths[5] = widths[2];
right_widths[7] = widths[3];
right_widths[9] = widths[4];
right_widths[11] = widths[5];
right_widths[13] = widths[6];
 
checksum = 0;
/* Calculate the checksum */
for (i = 0; i < 14; i++) {
checksum += CHECKSUM_WEIGHT_LTD[i] * left_widths[i];
checksum += CHECKSUM_WEIGHT_LTD[i + 14] * right_widths[i];
}
checksum %= 89;
 
infoLine("Checksum: " + checksum);
 
for (i = 0; i < 14; i++) {
check_elements[i] = FINDER_PATTERN_LTD[i + checksum * 14];
}
 
total_widths[0] = 1;
total_widths[1] = 1;
total_widths[44] = 1;
total_widths[45] = 1;
for (i = 0; i < 14; i++) {
total_widths[i + 2] = left_widths[i];
total_widths[i + 16] = check_elements[i];
total_widths[i + 30] = right_widths[i];
}
 
final StringBuilder bin = new StringBuilder();
final StringBuilder notbin = new StringBuilder();
 
bar_latch = false;
for (i = 0; i < 46; i++) {
for (j = 0; j < total_widths[i]; j++) {
if (bar_latch) {
bin.append('1');
notbin.append('0');
} else {
bin.append('0');
notbin.append('1');
}
}
if (bar_latch) {
bar_latch = false;
} else {
bar_latch = true;
}
}
 
/* Calculate check digit from Annex A and place human readable text */
 
this.readable = "(01)";
hrt = "";
for (i = this.content.length(); i < 13; i++) {
hrt += "0";
}
hrt += this.content;
 
for (i = 0; i < 13; i++) {
count += hrt.charAt(i) - '0';
if ((i & 1) == 0) {
count += 2 * (hrt.charAt(i) - '0');
}
}
 
check_digit = 10 - count % 10;
if (check_digit == 10) {
check_digit = 0;
}
 
hrt += (char) (check_digit + '0');
this.readable += hrt;
 
if (this.linkageFlag) {
compositeOffset = 1;
}
 
this.row_count = 1 + compositeOffset;
this.row_height = new int[1 + compositeOffset];
this.row_height[0 + compositeOffset] = -1;
this.pattern = new String[1 + compositeOffset];
this.pattern[0 + compositeOffset] = bin2pat(bin);
 
if (this.linkageFlag) {
// Add composite symbol separator
notbin.delete(70, notbin.length());
notbin.delete(0, 4);
this.row_height[0] = 1;
this.pattern[0] = "04" + bin2pat(notbin);
}
}
 
private static int getCombinations(final int n, final int r) {
 
int i, j;
int maxDenom, minDenom;
int val;
 
if (n - r > r) {
minDenom = r;
maxDenom = n - r;
} else {
minDenom = n - r;
maxDenom = r;
}
 
val = 1;
j = 1;
 
for (i = n; i > maxDenom; i--) {
val *= i;
if (j <= minDenom) {
val /= j;
j++;
}
}
 
for (; j <= minDenom; j++) {
val /= j;
}
 
return val;
}
 
static int[] getWidths(int val, int n, final int elements, final int maxWidth, final int noNarrow) {
 
int bar;
int elmWidth;
int mxwElement;
int subVal, lessVal;
int narrowMask = 0;
final int[] widths = new int[elements];
 
for (bar = 0; bar < elements - 1; bar++) {
for (elmWidth = 1, narrowMask |= 1 << bar;; elmWidth++, narrowMask &= ~(1 << bar)) {
/* get all combinations */
subVal = getCombinations(n - elmWidth - 1, elements - bar - 2);
/* less combinations with no single-module element */
if (noNarrow == 0 && narrowMask == 0 && n - elmWidth - (elements - bar - 1) >= elements - bar - 1) {
subVal -= getCombinations(n - elmWidth - (elements - bar), elements - bar - 2);
}
/* less combinations with elements > maxVal */
if (elements - bar - 1 > 1) {
lessVal = 0;
for (mxwElement = n - elmWidth - (elements - bar - 2); mxwElement > maxWidth; mxwElement--) {
lessVal += getCombinations(n - elmWidth - mxwElement - 1, elements - bar - 3);
}
subVal -= lessVal * (elements - 1 - bar);
} else if (n - elmWidth > maxWidth) {
subVal--;
}
val -= subVal;
if (val < 0) {
break;
}
}
val += subVal;
n -= elmWidth;
widths[bar] = elmWidth;
}
 
widths[bar] = n;
 
return widths;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Code2Of5.java
New file
0,0 → 1,469
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import static uk.org.okapibarcode.backend.HumanReadableLocation.NONE;
import static uk.org.okapibarcode.backend.HumanReadableLocation.TOP;
 
import java.awt.geom.Rectangle2D;
 
/**
* Implements the Code 2 of 5 family of barcode standards.
*
* @author <a href="mailto:jakel2006@me.com">Robert Elliott</a>
*/
public class Code2Of5 extends Symbol {
 
public enum ToFMode {
/**
* Standard Code 2 of 5 mode, also known as Code 2 of 5 Matrix. Encodes any length numeric
* input (digits 0-9). This is the default mode.
*/
MATRIX,
/**
* Industrial Code 2 of 5 which can encode any length numeric input (digits 0-9) and does
* not include a check digit.
*/
INDUSTRIAL,
/**
* International Air Transport Agency variation of Code 2 of 5. Encodes any length numeric
* input (digits 0-9) and does not include a check digit.
*/
IATA,
/**
* Code 2 of 5 Data Logic. Encodes any length numeric input (digits 0-9) and does not
* include a check digit.
*/
DATA_LOGIC,
/**
* Interleaved Code 2 of 5. Encodes pairs of numbers, and so can only encode an even number
* of digits (0-9). If an odd number of digits is entered a leading zero is added. No check
* digit is calculated.
*/
INTERLEAVED,
/**
* Interleaved Code 2 of 5 with check digit. Encodes pairs of numbers, and so can only
* encode an even number of digits (0-9). If adding the check digit results in an odd number
* of digits then a leading zero is added.
*/
INTERLEAVED_WITH_CHECK_DIGIT,
/**
* ITF-14, also known as UPC Shipping Container Symbol or Case Code. Requires a 13-digit
* numeric input (digits 0-9). One modulo-10 check digit is calculated.
*/
ITF14,
/**
* Deutsche Post Leitcode. Requires a 13-digit numerical input. Check digit is calculated.
*/
DP_LEITCODE,
/**
* Deutsche Post Identcode. Requires an 11-digit numerical input. Check digit is calculated.
*/
DP_IDENTCODE
}
 
private static final String[] C25_MATRIX_TABLE = { "113311", "311131", "131131", "331111", "113131", "313111", "133111", "111331", "311311", "131311" };
 
private static final String[] C25_INDUSTRIAL_TABLE = { "1111313111", "3111111131", "1131111131", "3131111111", "1111311131", "3111311111", "1131311111", "1111113131", "3111113111", "1131113111" };
 
private static final String[] C25_INTERLEAVED_TABLE = { "11331", "31113", "13113", "33111", "11313", "31311", "13311", "11133", "31131", "13131" };
 
/** The 2-of-5 mode. */
private ToFMode mode = ToFMode.MATRIX;
 
/** Ratio of wide bar width to narrow bar width. */
private double moduleWidthRatio = 3;
 
/**
* Sets the 2-of-5 mode. The default value is {@link ToFMode#MATRIX}.
*
* @param mode the 2-of-5 mode
*/
public void setMode(final ToFMode mode) {
this.mode = mode;
}
 
/**
* Returns the 2-of-5 mode.
*
* @return the 2-of-5 mode
*/
public ToFMode getMode() {
return this.mode;
}
 
/**
* Sets the ratio of wide bar width to narrow bar width. Valid values are usually between
* {@code 2} and {@code 3}. The default value is {@code 3}.
*
* @param moduleWidthRatio the ratio of wide bar width to narrow bar width
*/
public void setModuleWidthRatio(final double moduleWidthRatio) {
this.moduleWidthRatio = moduleWidthRatio;
}
 
/**
* Returns the ratio of wide bar width to narrow bar width.
*
* @return the ratio of wide bar width to narrow bar width
*/
public double getModuleWidthRatio() {
return this.moduleWidthRatio;
}
 
@Override
protected void encode() {
switch (this.mode) {
case MATRIX:
dataMatrix();
break;
case INDUSTRIAL:
industrial();
break;
case IATA:
iata();
break;
case INTERLEAVED:
interleaved(false);
break;
case INTERLEAVED_WITH_CHECK_DIGIT:
interleaved(true);
break;
case DATA_LOGIC:
dataLogic();
break;
case ITF14:
itf14();
break;
case DP_LEITCODE:
deutschePostLeitcode();
break;
case DP_IDENTCODE:
deutschePostIdentcode();
break;
}
}
 
private void dataMatrix() {
 
if (!this.content.matches("[0-9]*")) {
throw new OkapiException("Invalid characters in input");
}
 
String dest = "311111";
for (int i = 0; i < this.content.length(); i++) {
dest += C25_MATRIX_TABLE[Character.getNumericValue(this.content.charAt(i))];
}
dest += "31111";
 
this.readable = this.content;
this.pattern = new String[] { dest };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private void industrial() {
 
if (!this.content.matches("[0-9]*")) {
throw new OkapiException("Invalid characters in input");
}
 
String dest = "313111";
for (int i = 0; i < this.content.length(); i++) {
dest += C25_INDUSTRIAL_TABLE[Character.getNumericValue(this.content.charAt(i))];
}
dest += "31113";
 
this.readable = this.content;
this.pattern = new String[] { dest };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private void iata() {
 
if (!this.content.matches("[0-9]*")) {
throw new OkapiException("Invalid characters in input");
}
 
String dest = "1111";
for (int i = 0; i < this.content.length(); i++) {
dest += C25_INDUSTRIAL_TABLE[Character.getNumericValue(this.content.charAt(i))];
}
dest += "311";
 
this.readable = this.content;
this.pattern = new String[] { dest };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private void dataLogic() {
 
if (!this.content.matches("[0-9]*")) {
throw new OkapiException("Invalid characters in input");
}
 
String dest = "1111";
for (int i = 0; i < this.content.length(); i++) {
dest += C25_MATRIX_TABLE[Character.getNumericValue(this.content.charAt(i))];
}
dest += "311";
 
this.readable = this.content;
this.pattern = new String[] { dest };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private void interleaved(final boolean addCheckDigit) {
int i;
String dest;
 
this.readable = this.content;
 
if (!this.readable.matches("[0-9]*")) {
throw new OkapiException("Invalid characters in input");
}
 
if (addCheckDigit) {
final char checkDigit = checkDigit(this.readable, 1, 3);
this.readable += checkDigit;
infoLine("Check Digit: " + checkDigit);
}
 
if ((this.readable.length() & 1) != 0) {
this.readable = "0" + this.readable;
}
 
dest = "1111";
for (i = 0; i < this.readable.length(); i += 2) {
dest += interlace(i, i + 1);
}
dest += "311";
 
this.pattern = new String[] { dest };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private String interlace(final int x, final int y) {
final char a = this.readable.charAt(x);
final char b = this.readable.charAt(y);
 
final String one = C25_INTERLEAVED_TABLE[Character.getNumericValue(a)];
final String two = C25_INTERLEAVED_TABLE[Character.getNumericValue(b)];
 
final StringBuilder f = new StringBuilder(10);
for (int i = 0; i < 5; i++) {
f.append(one.charAt(i));
f.append(two.charAt(i));
}
 
return f.toString();
}
 
private void itf14() {
int i;
final int input_length = this.content.length();
String dest;
 
if (!this.content.matches("[0-9]*")) {
throw new OkapiException("Invalid characters in input");
}
 
if (input_length > 13) {
throw new OkapiException("Input data too long");
}
 
this.readable = "";
for (i = input_length; i < 13; i++) {
this.readable += "0";
}
this.readable += this.content;
 
final char checkDigit = checkDigit(this.readable, 1, 3);
this.readable += checkDigit;
infoLine("Check Digit: " + checkDigit);
 
dest = "1111";
for (i = 0; i < this.readable.length(); i += 2) {
dest += interlace(i, i + 1);
}
dest += "311";
 
this.pattern = new String[] { dest };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private void deutschePostLeitcode() {
int i;
final int input_length = this.content.length();
String dest;
 
if (!this.content.matches("[0-9]*")) {
throw new OkapiException("Invalid characters in input");
}
 
if (input_length > 13) {
throw new OkapiException("Input data too long");
}
 
this.readable = "";
for (i = input_length; i < 13; i++) {
this.readable += "0";
}
this.readable += this.content;
 
final char checkDigit = checkDigit(this.readable, 9, 4);
this.readable += checkDigit;
infoLine("Check digit: " + checkDigit);
 
dest = "1111";
for (i = 0; i < this.readable.length(); i += 2) {
dest += interlace(i, i + 1);
}
dest += "311";
 
this.pattern = new String[] { dest };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private void deutschePostIdentcode() {
int i;
final int input_length = this.content.length();
String dest;
 
if (!this.content.matches("[0-9]*")) {
throw new OkapiException("Invalid characters in input");
}
 
if (input_length > 11) {
throw new OkapiException("Input data too long");
}
 
this.readable = "";
for (i = input_length; i < 11; i++) {
this.readable += "0";
}
this.readable += this.content;
 
final char checkDigit = checkDigit(this.readable, 9, 4);
this.readable += checkDigit;
infoLine("Check Digit: " + checkDigit);
 
dest = "1111";
for (i = 0; i < this.readable.length(); i += 2) {
dest += interlace(i, i + 1);
}
dest += "311";
 
this.pattern = new String[] { dest };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private static char checkDigit(final String s, final int multiplier1, final int multiplier2) {
int count = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if ((i & 1) != 0) {
count += multiplier1 * (s.charAt(i) - '0');
} else {
count += multiplier2 * (s.charAt(i) - '0');
}
}
return (char) ((10 - count % 10) % 10 + '0');
}
 
@Override
protected void plotSymbol() {
 
int xBlock;
 
this.rectangles.clear();
this.texts.clear();
 
int baseY;
if (this.humanReadableLocation == TOP) {
baseY = getTheoreticalHumanReadableHeight();
} else {
baseY = 0;
}
 
double x = 0;
final int y = baseY;
int h = 0;
boolean black = true;
 
int offset = 0;
if (this.mode == ToFMode.ITF14) {
offset = 20;
}
 
for (xBlock = 0; xBlock < this.pattern[0].length(); xBlock++) {
final char c = this.pattern[0].charAt(xBlock);
final double w = getModuleWidth(c - '0') * this.moduleWidth;
if (black) {
if (this.row_height[0] == -1) {
h = this.default_height;
} else {
h = this.row_height[0];
}
if (w != 0 && h != 0) {
final Rectangle2D.Double rect = new Rectangle2D.Double(x + offset, y, w, h);
this.rectangles.add(rect);
}
this.symbol_width = (int) Math.ceil(x + w + 2 * offset);
}
black = !black;
x += w;
}
 
this.symbol_height = h;
 
if (this.mode == ToFMode.ITF14) {
// Add bounding box
final Rectangle2D.Double topBar = new Rectangle2D.Double(0, baseY, this.symbol_width, 4);
final Rectangle2D.Double bottomBar = new Rectangle2D.Double(0, baseY + this.symbol_height - 4, this.symbol_width, 4);
final Rectangle2D.Double leftBar = new Rectangle2D.Double(0, baseY, 4, this.symbol_height);
final Rectangle2D.Double rightBar = new Rectangle2D.Double(this.symbol_width - 4, baseY, 4, this.symbol_height);
this.rectangles.add(topBar);
this.rectangles.add(bottomBar);
this.rectangles.add(leftBar);
this.rectangles.add(rightBar);
}
 
if (this.humanReadableLocation != NONE && !this.readable.isEmpty()) {
double baseline;
if (this.humanReadableLocation == TOP) {
baseline = this.fontSize;
} else {
baseline = this.symbol_height + this.fontSize;
}
this.texts.add(new TextBox(0, baseline, this.symbol_width, this.readable, this.humanReadableAlignment));
}
}
 
/** {@inheritDoc} */
@Override
protected double getModuleWidth(final int originalWidth) {
if (originalWidth == 1) {
return 1;
} else {
return this.moduleWidthRatio;
}
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/KoreaPost.java
New file
0,0 → 1,64
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
/**
* <p>
* Implements Korea Post Barcode. Input should consist of of a six-digit number. A Modulo-10 check
* digit is calculated and added, and should not form part of the input data.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class KoreaPost extends Symbol {
 
private static final String[] KOREA_TABLE = { "1313150613", "0713131313", "0417131313", "1506131313", "0413171313", "17171313", "1315061313", "0413131713", "17131713", "13171713" };
 
@Override
protected void encode() {
 
if (!this.content.matches("[0-9]+")) {
throw new OkapiException("Invalid characters in input");
}
 
if (this.content.length() > 6) {
throw new OkapiException("Input data too long");
}
 
String padded = "";
for (int i = 0; i < 6 - this.content.length(); i++) {
padded += "0";
}
padded += this.content;
 
int total = 0;
String accumulator = "";
for (int i = 0; i < padded.length(); i++) {
final int j = Character.getNumericValue(padded.charAt(i));
accumulator += KOREA_TABLE[j];
total += j;
}
 
int checkd = 10 - total % 10;
if (checkd == 10) {
checkd = 0;
}
infoLine("Check Digit: " + checkd);
accumulator += KOREA_TABLE[checkd];
 
this.readable = padded + checkd;
this.pattern = new String[] { accumulator };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/GridMatrix.java
New file
0,0 → 1,1878
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import static uk.org.okapibarcode.util.Arrays.positionOf;
 
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
 
/**
* <p>
* Implements Grid Matrix bar code symbology according to AIMD014.
*
* <p>
* Grid Matrix is a matrix symbology which can encode characters in the ISO/IEC 8859-1 (Latin-1)
* character set as well as those in the GB-2312 character set. Input is assumed to be formatted as
* a UTF string.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class GridMatrix extends Symbol {
 
private static final char[] SHIFT_SET = {
/* From Table 7 - Encoding of control characters */
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, /*
* NULL
* ->
* SI
*/
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, /*
* DLE
* ->
* US
*/
'!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~' };
 
private static final int[] GM_RECOMMEND_CW = { 9, 30, 59, 114, 170, 237, 315, 405, 506, 618, 741, 875, 1021 };
 
private static final int[] GM_MAX_CW = { 11, 40, 79, 146, 218, 305, 405, 521, 650, 794, 953, 1125, 1313 };
 
private static final int[] GM_DATA_CODEWORDS = { 0, 15, 13, 11, 9, 45, 40, 35, 30, 25, 89, 79, 69, 59, 49, 146, 130, 114, 98, 81, 218, 194, 170, 146, 121, 305, 271, 237, 203, 169, 405, 360, 315,
270, 225, 521, 463, 405, 347, 289, 650, 578, 506, 434, 361, 794, 706, 618, 530, 441, 953, 847, 741, 635, 529, 1125, 1000, 875, 750, 625, 1313, 1167, 1021, 875, 729 };
 
private static final int[] GM_N1 = { 18, 50, 98, 81, 121, 113, 113, 116, 121, 126, 118, 125, 122 };
private static final int[] GM_B1 = { 1, 1, 1, 2, 2, 2, 2, 3, 2, 7, 5, 10, 6 };
private static final int[] GM_B2 = { 0, 0, 0, 0, 0, 1, 2, 2, 4, 0, 4, 0, 6 };
 
private static final int[] GM_EBEB = {
/* E1 B3 E2 B4 */
0, 0, 0, 0, // version 1
3, 1, 0, 0, 5, 1, 0, 0, 7, 1, 0, 0, 9, 1, 0, 0, 5, 1, 0, 0, // version 2
10, 1, 0, 0, 15, 1, 0, 0, 20, 1, 0, 0, 25, 1, 0, 0, 9, 1, 0, 0, // version 3
19, 1, 0, 0, 29, 1, 0, 0, 39, 1, 0, 0, 49, 1, 0, 0, 8, 2, 0, 0, // version 4
16, 2, 0, 0, 24, 2, 0, 0, 32, 2, 0, 0, 41, 1, 10, 1, 12, 2, 0, 0, // version 5
24, 2, 0, 0, 36, 2, 0, 0, 48, 2, 0, 0, 61, 1, 60, 1, 11, 3, 0, 0, // version 6
23, 1, 22, 2, 34, 2, 33, 1, 45, 3, 0, 0, 57, 1, 56, 2, 12, 1, 11, 3, // version 7
23, 2, 22, 2, 34, 3, 33, 1, 45, 4, 0, 0, 57, 1, 56, 3, 12, 2, 11, 3, // version 8
23, 5, 0, 0, 35, 3, 34, 2, 47, 1, 46, 4, 58, 4, 57, 1, 12, 6, 0, 0, // version 9
24, 6, 0, 0, 36, 6, 0, 0, 48, 6, 0, 0, 61, 1, 60, 5, 13, 4, 12, 3, // version 10
26, 1, 25, 6, 38, 5, 37, 2, 51, 2, 50, 5, 63, 7, 0, 0, 12, 6, 11, 3, // version 11
24, 4, 23, 5, 36, 2, 35, 7, 47, 9, 0, 0, 59, 7, 58, 2, 13, 5, 12, 5, // version 12
25, 10, 0, 0, 38, 5, 37, 5, 50, 10, 0, 0, 63, 5, 62, 5, 13, 1, 12, 11, // version 13
25, 3, 24, 9, 37, 5, 36, 7, 49, 7, 48, 5, 61, 9, 60, 3 };
 
private static final int[] GM_MACRO_MATRIX = { 728, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 727, 624, 529,
530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 651, 726, 623, 528, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450,
451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461, 462, 553, 652, 725, 622, 527, 440, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379,
380, 463, 554, 653, 724, 621, 526, 439, 360, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 381, 464, 555, 654, 723, 620, 525, 438, 359, 288,
225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 307, 382, 465, 556, 655, 722, 619, 524, 437, 358, 287, 224, 169, 170, 171, 172, 173, 174, 175, 176, 177,
178, 179, 180, 181, 182, 241, 308, 383, 466, 557, 656, 721, 618, 523, 436, 357, 286, 223, 168, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 183, 242, 309, 384, 467, 558,
657, 720, 617, 522, 435, 356, 285, 222, 167, 120, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 133, 184, 243, 310, 385, 468, 559, 658, 719, 616, 521, 434, 355, 284, 221, 166, 119, 80, 49, 50,
51, 52, 53, 54, 55, 56, 91, 134, 185, 244, 311, 386, 469, 560, 659, 718, 615, 520, 433, 354, 283, 220, 165, 118, 79, 48, 25, 26, 27, 28, 29, 30, 57, 92, 135, 186, 245, 312, 387, 470, 561,
660, 717, 614, 519, 432, 353, 282, 219, 164, 117, 78, 47, 24, 9, 10, 11, 12, 31, 58, 93, 136, 187, 246, 313, 388, 471, 562, 661, 716, 613, 518, 431, 352, 281, 218, 163, 116, 77, 46, 23, 8,
1, 2, 13, 32, 59, 94, 137, 188, 247, 314, 389, 472, 563, 662, 715, 612, 517, 430, 351, 280, 217, 162, 115, 76, 45, 22, 7, 0, 3, 14, 33, 60, 95, 138, 189, 248, 315, 390, 473, 564, 663, 714,
611, 516, 429, 350, 279, 216, 161, 114, 75, 44, 21, 6, 5, 4, 15, 34, 61, 96, 139, 190, 249, 316, 391, 474, 565, 664, 713, 610, 515, 428, 349, 278, 215, 160, 113, 74, 43, 20, 19, 18, 17,
16, 35, 62, 97, 140, 191, 250, 317, 392, 475, 566, 665, 712, 609, 514, 427, 348, 277, 214, 159, 112, 73, 42, 41, 40, 39, 38, 37, 36, 63, 98, 141, 192, 251, 318, 393, 476, 567, 666, 711,
608, 513, 426, 347, 276, 213, 158, 111, 72, 71, 70, 69, 68, 67, 66, 65, 64, 99, 142, 193, 252, 319, 394, 477, 568, 667, 710, 607, 512, 425, 346, 275, 212, 157, 110, 109, 108, 107, 106,
105, 104, 103, 102, 101, 100, 143, 194, 253, 320, 395, 478, 569, 668, 709, 606, 511, 424, 345, 274, 211, 156, 155, 154, 153, 152, 151, 150, 149, 148, 147, 146, 145, 144, 195, 254, 321,
396, 479, 570, 669, 708, 605, 510, 423, 344, 273, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196, 255, 322, 397, 480, 571, 670, 707, 604, 509, 422, 343, 272,
271, 270, 269, 268, 267, 266, 265, 264, 263, 262, 261, 260, 259, 258, 257, 256, 323, 398, 481, 572, 671, 706, 603, 508, 421, 342, 341, 340, 339, 338, 337, 336, 335, 334, 333, 332, 331,
330, 329, 328, 327, 326, 325, 324, 399, 482, 573, 672, 705, 602, 507, 420, 419, 418, 417, 416, 415, 414, 413, 412, 411, 410, 409, 408, 407, 406, 405, 404, 403, 402, 401, 400, 483, 574,
673, 704, 601, 506, 505, 504, 503, 502, 501, 500, 499, 498, 497, 496, 495, 494, 493, 492, 491, 490, 489, 488, 487, 486, 485, 484, 575, 674, 703, 600, 599, 598, 597, 596, 595, 594, 593,
592, 591, 590, 589, 588, 587, 586, 585, 584, 583, 582, 581, 580, 579, 578, 577, 576, 675, 702, 701, 700, 699, 698, 697, 696, 695, 694, 693, 692, 691, 690, 689, 688, 687, 686, 685, 684,
683, 682, 681, 680, 679, 678, 677, 676 };
 
private static final char[] MIXED_ALPHANUM_SET = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ' ' };
 
private enum Mode {
NULL, GM_NUMBER, GM_LOWER, GM_UPPER, GM_MIXED, GM_CONTROL, GM_BYTE, GM_CHINESE
}
 
private StringBuilder binary;
private final int[] word = new int[1460];
private boolean[] grid;
private Mode appxDnextSection = Mode.NULL;
private Mode appxDlastSection = Mode.NULL;
private int preferredVersion = 0;
private int preferredEccLevel = -1;
 
/**
* Set preferred size, or "version" of the symbol according to the following table. This value
* may be ignored if the data to be encoded does not fit into a symbol of the selected size.
*
* <table summary="Available Grid Matrix symbol sizes">
* <tbody>
* <tr>
* <th>Input</th>
* <th>Size</th>
* </tr>
* <tr>
* <td>1</td>
* <td>18 x 18</td>
* </tr>
* <tr>
* <td>2</td>
* <td>30 x 30</td>
* </tr>
* <tr>
* <td>3</td>
* <td>42 x 42</td>
* </tr>
* <tr>
* <td>4</td>
* <td>54 x 54</td>
* </tr>
* <tr>
* <td>5</td>
* <td>66 x 66</td>
* </tr>
* <tr>
* <td>6</td>
* <td>78 x 78</td>
* </tr>
* <tr>
* <td>7</td>
* <td>90 x 90</td>
* </tr>
* <tr>
* <td>8</td>
* <td>102 x 102</td>
* </tr>
* <tr>
* <td>9</td>
* <td>114 x 114</td>
* </tr>
* <tr>
* <td>10</td>
* <td>126 x 126</td>
* </tr>
* <tr>
* <td>11</td>
* <td>138 x 138</td>
* </tr>
* <tr>
* <td>12</td>
* <td>150 x 150</td>
* </tr>
* <tr>
* <td>13</td>
* <td>162 x 162</td>
* </tr>
* </tbody>
* </table>
*
* @param version symbol version
*/
public void setPreferredVersion(final int version) {
this.preferredVersion = version;
}
 
/**
* Set the preferred amount of the symbol which should be dedicated to error correction data.
* Values should be selected from the following table:
*
* <table summary="Available options for error correction capacity">
* <tbody>
* <tr>
* <th>Mode</th>
* <th>Error Correction Capacity</th>
* </tr>
* <tr>
* <td>1</td>
* <td>Approximately 10%</td>
* </tr>
* <tr>
* <td>2</td>
* <td>Approximately 20%</td>
* </tr>
* <tr>
* <td>3</td>
* <td>Approximately 30%</td>
* </tr>
* <tr>
* <td>4</td>
* <td>Approximately 40%</td>
* </tr>
* <tr>
* <td>5</td>
* <td>Approximately 50%</td>
* </tr>
* </tbody>
* </table>
*
* @param eccLevel error correction level
*/
public void setPreferredEccLevel(final int eccLevel) {
this.preferredEccLevel = eccLevel;
}
 
@Override
protected void encode() {
int size, modules, dark, error_number;
int auto_layers, min_layers, layers, auto_ecc_level, min_ecc_level, ecc_level;
int x, y, i;
int data_cw, input_latch = 0;
int data_max;
int length;
final StringBuilder bin = new StringBuilder();
 
for (i = 0; i < 1460; i++) {
this.word[i] = 0;
}
 
try {
final Charset gb2312 = Charset.forName("GB2312");
if (gb2312.newEncoder().canEncode(this.content)) {
/* GB2312 will work, use Chinese compaction */
final byte[] inputBytes = this.content.getBytes(gb2312);
this.inputData = new int[inputBytes.length];
length = 0;
for (i = 0; i < inputBytes.length; i++) {
if ((inputBytes[i] & 0xFF) >= 0xA1 && (inputBytes[i] & 0xFF) <= 0xF7) {
/* Double byte character */
this.inputData[length] = (inputBytes[i] & 0xFF) * 256 + (inputBytes[i + 1] & 0xFF);
i++;
length++;
} else {
/* Single byte character */
this.inputData[length] = inputBytes[i] & 0xFF;
length++;
}
}
infoLine("Using GB2312 character encoding");
this.eciMode = 29;
} else {
/* GB2312 encoding won't work, use other ECI mode */
eciProcess(); // Get ECI mode
length = this.inputData.length;
}
} catch (final UnsupportedCharsetException e) {
throw new OkapiException("Byte conversion encoding error");
}
 
error_number = encodeGridMatrixBinary(length, this.readerInit);
if (error_number != 0) {
throw new OkapiException("Input data too long");
}
 
/* Determine the size of the symbol */
data_cw = this.binary.length() / 7;
 
auto_layers = 1;
for (i = 0; i < 13; i++) {
if (GM_RECOMMEND_CW[i] < data_cw) {
auto_layers = i + 1;
}
}
 
min_layers = 13;
for (i = 12; i > 0; i--) {
if (GM_MAX_CW[i - 1] >= data_cw) {
min_layers = i;
}
}
layers = auto_layers;
auto_ecc_level = 3;
if (layers == 1) {
auto_ecc_level = 5;
}
if (layers == 2 || layers == 3) {
auto_ecc_level = 4;
}
min_ecc_level = 1;
if (layers == 1) {
min_ecc_level = 4;
}
if (layers == 2 || layers == 3) {
min_ecc_level = 2;
}
ecc_level = auto_ecc_level;
 
if (this.preferredVersion >= 1 && this.preferredVersion <= 13) {
input_latch = 1;
if (this.preferredVersion > min_layers) {
layers = this.preferredVersion;
} else {
layers = min_layers;
}
}
 
if (input_latch == 1) {
auto_ecc_level = 3;
if (layers == 1) {
auto_ecc_level = 5;
}
if (layers == 2 || layers == 3) {
auto_ecc_level = 4;
}
ecc_level = auto_ecc_level;
if (data_cw > GM_DATA_CODEWORDS[5 * (layers - 1) + ecc_level - 1]) {
layers++;
}
}
 
if (input_latch == 0) {
if (this.preferredEccLevel >= 1 && this.preferredEccLevel <= 5) {
if (this.preferredEccLevel > min_ecc_level) {
ecc_level = this.preferredEccLevel;
} else {
ecc_level = min_ecc_level;
}
}
if (data_cw > GM_DATA_CODEWORDS[5 * (layers - 1) + ecc_level - 1]) {
do {
layers++;
} while (data_cw > GM_DATA_CODEWORDS[5 * (layers - 1) + ecc_level - 1] && layers <= 13);
}
}
 
data_max = 1313;
switch (ecc_level) {
case 2:
data_max = 1167;
break;
case 3:
data_max = 1021;
break;
case 4:
data_max = 875;
break;
case 5:
data_max = 729;
break;
}
 
if (data_cw > data_max) {
throw new OkapiException("Input data too long");
}
 
addErrorCorrection(data_cw, layers, ecc_level);
size = 6 + layers * 12;
modules = 1 + layers * 2;
 
infoLine("Layers: " + layers);
infoLine("ECC Level: " + ecc_level);
infoLine("Data Codewords: " + data_cw);
infoLine("ECC Codewords: " + GM_DATA_CODEWORDS[(layers - 1) * 5 + ecc_level - 1]);
infoLine("Grid Size: " + modules + " X " + modules);
 
this.grid = new boolean[size * size];
 
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
this.grid[y * size + x] = false;
}
}
 
placeDataInGrid(modules, size);
addLayerId(size, layers, modules, ecc_level);
 
/* Add macromodule frames */
for (x = 0; x < modules; x++) {
dark = 1 - (x & 1);
for (y = 0; y < modules; y++) {
if (dark == 1) {
for (i = 0; i < 5; i++) {
this.grid[y * 6 * size + x * 6 + i] = true;
this.grid[(y * 6 + 5) * size + x * 6 + i] = true;
this.grid[(y * 6 + i) * size + x * 6] = true;
this.grid[(y * 6 + i) * size + x * 6 + 5] = true;
}
this.grid[(y * 6 + 5) * size + x * 6 + 5] = true;
dark = 0;
} else {
dark = 1;
}
}
}
 
/* Copy values to symbol */
this.symbol_width = size;
this.row_count = size;
this.row_height = new int[this.row_count];
this.pattern = new String[this.row_count];
 
for (x = 0; x < size; x++) {
bin.setLength(0);
for (y = 0; y < size; y++) {
if (this.grid[x * size + y]) {
bin.append('1');
} else {
bin.append('0');
}
}
this.row_height[x] = 1;
this.pattern[x] = bin2pat(bin);
}
}
 
private int encodeGridMatrixBinary(final int length, final boolean reader) {
/*
* Create a binary stream representation of the input data. 7 sets are defined - Chinese
* characters, Numerals, Lower case letters, Upper case letters, Mixed numerals and letters,
* Control characters and 8-bit binary data
*/
int sp, glyph = 0;
Mode current_mode, next_mode, last_mode;
int c1, c2;
boolean done;
int p = 0, ppos;
int punt = 0;
int number_pad_posn;
int byte_count_posn = 0, byte_count = 0;
int shift, i;
final int[] numbuf = new int[3];
final Mode[] modeMap = calculateModeMap(length);
 
this.binary = new StringBuilder();
 
sp = 0;
current_mode = Mode.NULL;
number_pad_posn = 0;
 
info("Encoding: ");
 
if (reader) {
this.binary.append("1010"); /* FNC3 - Reader Initialisation */
info("INIT ");
}
 
if (this.eciMode != 3 && this.eciMode != 29) {
this.binary.append("1100"); /* ECI */
 
if (this.eciMode >= 0 && this.eciMode <= 1023) {
this.binary.append('0');
for (i = 0x200; i > 0; i = i >> 1) {
if ((this.eciMode & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
}
 
if (this.eciMode >= 1024 && this.eciMode <= 32767) {
this.binary.append("10");
for (i = 0x4000; i > 0; i = i >> 1) {
if ((this.eciMode & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
}
 
if (this.eciMode >= 32768 && this.eciMode <= 811799) {
this.binary.append("11");
for (i = 0x80000; i > 0; i = i >> 1) {
if ((this.eciMode & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
}
 
info("ECI ");
infoSpace(this.eciMode);
}
 
do {
next_mode = modeMap[sp];
 
if (next_mode != current_mode) {
switch (current_mode) {
case NULL:
switch (next_mode) {
case GM_CHINESE:
this.binary.append("0001");
break;
case GM_NUMBER:
this.binary.append("0010");
break;
case GM_LOWER:
this.binary.append("0011");
break;
case GM_UPPER:
this.binary.append("0100");
break;
case GM_MIXED:
this.binary.append("0101");
break;
case GM_BYTE:
this.binary.append("0111");
break;
}
break;
case GM_CHINESE:
switch (next_mode) {
case GM_NUMBER:
this.binary.append("1111111100001");
break; // 8161
case GM_LOWER:
this.binary.append("1111111100010");
break; // 8162
case GM_UPPER:
this.binary.append("1111111100011");
break; // 8163
case GM_MIXED:
this.binary.append("1111111100100");
break; // 8164
case GM_BYTE:
this.binary.append("1111111100101");
break; // 8165
}
break;
case GM_NUMBER:
/* add numeric block padding value */
switch (p) {
case 1:
this.binary.insert(number_pad_posn, "10");
break; // 2 pad digits
case 2:
this.binary.insert(number_pad_posn, "01");
break; // 1 pad digit
case 3:
this.binary.insert(number_pad_posn, "00");
break; // 0 pad digits
}
 
switch (next_mode) {
case GM_CHINESE:
this.binary.append("1111111011");
break; // 1019
case GM_LOWER:
this.binary.append("1111111100");
break; // 1020
case GM_UPPER:
this.binary.append("1111111101");
break; // 1021
case GM_MIXED:
this.binary.append("1111111110");
break; // 1022
case GM_BYTE:
this.binary.append("1111111111");
break; // 1023
}
break;
case GM_LOWER:
case GM_UPPER:
switch (next_mode) {
case GM_CHINESE:
this.binary.append("11100");
break; // 28
case GM_NUMBER:
this.binary.append("11101");
break; // 29
case GM_LOWER:
case GM_UPPER:
this.binary.append("11110");
break; // 30
case GM_MIXED:
this.binary.append("1111100");
break; // 124
case GM_BYTE:
this.binary.append("1111110");
break; // 126
}
break;
case GM_MIXED:
switch (next_mode) {
case GM_CHINESE:
this.binary.append("1111110001");
break; // 1009
case GM_NUMBER:
this.binary.append("1111110010");
break; // 1010
case GM_LOWER:
this.binary.append("1111110011");
break; // 1011
case GM_UPPER:
this.binary.append("1111110100");
break; // 1012
case GM_BYTE:
this.binary.append("1111110111");
break; // 1015
}
break;
case GM_BYTE:
/* add byte block length indicator */
addByteCount(byte_count_posn, byte_count);
byte_count = 0;
switch (next_mode) {
case GM_CHINESE:
this.binary.append("0001");
break; // 1
case GM_NUMBER:
this.binary.append("0010");
break; // 2
case GM_LOWER:
this.binary.append("0011");
break; // 3
case GM_UPPER:
this.binary.append("0100");
break; // 4
case GM_MIXED:
this.binary.append("0101");
break; // 5
}
break;
}
 
switch (next_mode) {
case GM_CHINESE:
info("CHIN ");
break;
case GM_NUMBER:
info("NUMB ");
break;
case GM_LOWER:
info("LOWR ");
break;
case GM_UPPER:
info("UPPR ");
break;
case GM_MIXED:
info("MIXD ");
break;
case GM_BYTE:
info("BYTE ");
break;
}
 
}
last_mode = current_mode;
current_mode = next_mode;
 
switch (current_mode) {
case GM_CHINESE:
done = false;
if (this.inputData[sp] > 0xff) {
/* GB2312 character */
c1 = (this.inputData[sp] & 0xff00) >> 8;
c2 = this.inputData[sp] & 0xff;
 
if (c1 >= 0xa0 && c1 <= 0xa9) {
glyph = 0x60 * (c1 - 0xa1) + c2 - 0xa0;
}
if (c1 >= 0xb0 && c1 <= 0xf7) {
glyph = 0x60 * (c1 - 0xb0 + 9) + c2 - 0xa0;
}
done = true;
}
if (!done) {
if (sp != length - 1) {
if (this.inputData[sp] == 13 && this.inputData[sp + 1] == 10) {
/* End of Line */
glyph = 7776;
sp++;
}
done = true;
}
}
if (!done) {
if (sp != length - 1) {
if (this.inputData[sp] >= '0' && this.inputData[sp] <= '9' && this.inputData[sp + 1] >= '0' && this.inputData[sp + 1] <= '9') {
/* Two digits */
glyph = 8033 + 10 * (this.inputData[sp] - '0') + this.inputData[sp + 1] - '0';
sp++;
}
}
}
if (!done) {
/* Byte value */
glyph = 7777 + this.inputData[sp];
}
 
infoSpace(glyph);
 
for (i = 0x1000; i > 0; i = i >> 1) {
if ((glyph & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
sp++;
break;
 
case GM_NUMBER:
if (last_mode != current_mode) {
/* Reserve a space for numeric digit padding value (2 bits) */
number_pad_posn = this.binary.length();
}
p = 0;
ppos = -1;
 
/*
* Numeric compression can also include certain combinations of non-numeric
* character
*/
numbuf[0] = '0';
numbuf[1] = '0';
numbuf[2] = '0';
do {
if (this.inputData[sp] >= '0' && this.inputData[sp] <= '9') {
numbuf[p] = this.inputData[sp];
p++;
}
switch (this.inputData[sp]) {
case ' ':
case '+':
case '-':
case '.':
case ',':
punt = this.inputData[sp];
ppos = p;
break;
}
if (sp < length - 1) {
if (this.inputData[sp] == 13 && this.inputData[sp + 1] == 10) {
/* <end of line> */
punt = this.inputData[sp];
sp++;
ppos = p;
}
}
sp++;
} while (p < 3 && sp < length);
 
if (ppos != -1) {
switch (punt) {
case ' ':
glyph = 0;
break;
case '+':
glyph = 3;
break;
case '-':
glyph = 6;
break;
case '.':
glyph = 9;
break;
case ',':
glyph = 12;
break;
case 0x13:
glyph = 15;
break;
}
glyph += ppos;
glyph += 1000;
 
infoSpace(glyph);
 
for (i = 0x200; i > 0; i = i >> 1) {
if ((glyph & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
}
 
glyph = 100 * (numbuf[0] - '0') + 10 * (numbuf[1] - '0') + numbuf[2] - '0';
infoSpace(glyph);
 
for (i = 0x200; i > 0; i = i >> 1) {
if ((glyph & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
break;
 
case GM_BYTE:
if (last_mode != current_mode) {
/* Reserve space for byte block length indicator (9 bits) */
byte_count_posn = this.binary.length();
}
if (byte_count == 512) {
/*
* Maximum byte block size is 512 bytes. If longer is needed then start a new
* block
*/
addByteCount(byte_count_posn, byte_count);
this.binary.append("0111");
byte_count_posn = this.binary.length();
byte_count = 0;
}
 
glyph = this.inputData[sp];
infoSpace(glyph);
for (i = 0x80; i > 0; i = i >> 1) {
if ((glyph & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
sp++;
byte_count++;
break;
 
case GM_MIXED:
shift = 1;
if (this.inputData[sp] >= '0' && this.inputData[sp] <= '9') {
shift = 0;
}
if (this.inputData[sp] >= 'A' && this.inputData[sp] <= 'Z') {
shift = 0;
}
if (this.inputData[sp] >= 'a' && this.inputData[sp] <= 'z') {
shift = 0;
}
if (this.inputData[sp] == ' ') {
shift = 0;
}
 
if (shift == 0) {
/* Mixed Mode character */
glyph = positionOf((char) this.inputData[sp], MIXED_ALPHANUM_SET);
infoSpace(glyph);
 
for (i = 0x20; i > 0; i = i >> 1) {
if ((glyph & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
} else {
/* Shift Mode character */
this.binary.append("1111110110"); /* 1014 - shift indicator */
 
addShiftCharacter(this.inputData[sp]);
}
 
sp++;
break;
 
case GM_UPPER:
shift = 1;
if (this.inputData[sp] >= 'A' && this.inputData[sp] <= 'Z') {
shift = 0;
}
if (this.inputData[sp] == ' ') {
shift = 0;
}
 
if (shift == 0) {
/* Upper Case character */
glyph = positionOf((char) this.inputData[sp], MIXED_ALPHANUM_SET) - 10;
if (glyph == 52) {
// Space character
glyph = 26;
}
infoSpace(glyph);
 
for (i = 0x10; i > 0; i = i >> 1) {
if ((glyph & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
 
} else {
/* Shift Mode character */
this.binary.append("1111101"); /* 127 - shift indicator */
 
addShiftCharacter(this.inputData[sp]);
}
 
sp++;
break;
 
case GM_LOWER:
shift = 1;
if (this.inputData[sp] >= 'a' && this.inputData[sp] <= 'z') {
shift = 0;
}
if (this.inputData[sp] == ' ') {
shift = 0;
}
 
if (shift == 0) {
/* Lower Case character */
glyph = positionOf((char) this.inputData[sp], MIXED_ALPHANUM_SET) - 36;
infoSpace(glyph);
 
for (i = 0x10; i > 0; i = i >> 1) {
if ((glyph & i) != 0) {
this.binary.append('1');
} else {
this.binary.append('0');
}
}
 
} else {
/* Shift Mode character */
this.binary.append("1111101"); /* 127 - shift indicator */
 
addShiftCharacter(this.inputData[sp]);
}
 
sp++;
break;
}
if (this.binary.length() > 9191) {
return 1;
}
 
} while (sp < length);
 
infoLine();
 
if (current_mode == Mode.GM_NUMBER) {
/* add numeric block padding value */
switch (p) {
case 1:
this.binary.insert(number_pad_posn, "10");
break; // 2 pad digits
case 2:
this.binary.insert(number_pad_posn, "01");
break; // 1 pad digit
case 3:
this.binary.insert(number_pad_posn, "00");
break; // 0 pad digits
}
}
 
if (current_mode == Mode.GM_BYTE) {
/* Add byte block length indicator */
addByteCount(byte_count_posn, byte_count);
}
 
/* Add "end of data" character */
switch (current_mode) {
case GM_CHINESE:
this.binary.append("1111111100000");
break; // 8160
case GM_NUMBER:
this.binary.append("1111111010");
break; // 1018
case GM_LOWER:
case GM_UPPER:
this.binary.append("11011");
break; // 27
case GM_MIXED:
this.binary.append("1111110000");
break; // 1008
case GM_BYTE:
this.binary.append("0000");
break; // 0
}
 
/* Add padding bits if required */
p = 7 - this.binary.length() % 7;
if (p == 7) {
p = 0;
}
for (i = 0; i < p; i++) {
this.binary.append('0');
}
 
if (this.binary.length() > 9191) {
return 1;
}
 
return 0;
}
 
private Mode[] calculateModeMap(final int length) {
final Mode[] modeMap = new Mode[length];
int i;
int digitStart, digitLength;
boolean digits;
int spaceStart, spaceLength;
boolean spaces;
int[] segmentLength;
Mode[] segmentType;
int[] segmentStart;
int segmentCount;
 
// Step 1
// Characters in GB2312 are encoded as Chinese characters
for (i = 0; i < length; i++) {
modeMap[i] = Mode.NULL;
if (this.inputData[i] > 0xFF) {
modeMap[i] = Mode.GM_CHINESE;
}
}
 
// Consecutive <end of line> characters, if preceeded by or followed
// by chinese characters, are encoded as chinese characters.
if (length > 3) {
i = 1;
do {
if (this.inputData[i] == 13 && this.inputData[i + 1] == 10) {
// End of line (CR/LF)
 
if (modeMap[i - 1] == Mode.GM_CHINESE) {
modeMap[i] = Mode.GM_CHINESE;
modeMap[i + 1] = Mode.GM_CHINESE;
}
i += 2;
} else {
i++;
}
} while (i < length - 1);
 
i = length - 3;
do {
if (this.inputData[i] == 13 && this.inputData[i + 1] == 10) {
// End of line (CR/LF)
if (modeMap[i + 2] == Mode.GM_CHINESE) {
modeMap[i] = Mode.GM_CHINESE;
modeMap[i + 1] = Mode.GM_CHINESE;
}
i -= 2;
} else {
i--;
}
} while (i > 0);
}
 
// Digit pairs between chinese characters encode as chinese characters.
digits = false;
digitLength = 0;
digitStart = 0;
for (i = 1; i < length - 1; i++) {
if (this.inputData[i] >= 48 && this.inputData[i] <= 57) {
// '0' to '9'
if (digits == false) {
digits = true;
digitLength = 1;
digitStart = i;
} else {
digitLength++;
}
} else {
if (digits == true) {
if (digitLength % 2 == 0) {
if (modeMap[digitStart - 1] == Mode.GM_CHINESE && modeMap[i] == Mode.GM_CHINESE) {
for (int j = 0; j < digitLength; j++) {
modeMap[i - j - 1] = Mode.GM_CHINESE;
}
}
}
digits = false;
}
}
}
 
// Step 2: all characters 'a' to 'z' are lowercase.
for (i = 0; i < length; i++) {
if (this.inputData[i] >= 97 && this.inputData[i] <= 122) {
modeMap[i] = Mode.GM_LOWER;
}
}
 
// Step 3: all characters 'A' to 'Z' are uppercase.
for (i = 0; i < length; i++) {
if (this.inputData[i] >= 65 && this.inputData[i] <= 90) {
modeMap[i] = Mode.GM_UPPER;
}
}
 
// Step 4: find consecutive <space> characters preceeded or followed
// by uppercase or lowercase.
spaces = false;
spaceLength = 0;
spaceStart = 0;
for (i = 1; i < length - 1; i++) {
if (this.inputData[i] == 32) {
if (spaces == false) {
spaces = true;
spaceLength = 1;
spaceStart = i;
} else {
spaceLength++;
}
} else {
if (spaces == true) {
 
final Mode modeX = modeMap[spaceStart - 1];
final Mode modeY = modeMap[i];
 
if (modeX == Mode.GM_LOWER || modeX == Mode.GM_UPPER) {
for (int j = 0; j < spaceLength; j++) {
modeMap[i - j - 1] = modeX;
}
} else {
if (modeY == Mode.GM_LOWER || modeY == Mode.GM_UPPER) {
for (int j = 0; j < spaceLength; j++) {
modeMap[i - j - 1] = modeY;
}
}
}
spaces = false;
}
}
}
 
// Step 5: Unassigned characters '0' to '9' are assigned as numerals.
// Non-numeric characters in table 7 are also assigned as numerals.
for (i = 0; i < length; i++) {
if (modeMap[i] == Mode.NULL) {
if (this.inputData[i] >= 48 && this.inputData[i] <= 57) {
// '0' to '9'
modeMap[i] = Mode.GM_NUMBER;
} else {
switch (this.inputData[i]) {
case 32: // Space
case 43: // '+'
case 45: // '-'
case 46: // "."
case 44: // ","
modeMap[i] = Mode.GM_NUMBER;
break;
case 13: // CR
if (i < length - 1) {
if (this.inputData[i + 1] == 10) { // LF
// <end of line>
modeMap[i] = Mode.GM_NUMBER;
modeMap[i + 1] = Mode.GM_NUMBER;
}
}
}
}
}
}
 
// Step 6: The remining unassigned bytes are assigned as 8-bit binary
for (i = 0; i < length; i++) {
if (modeMap[i] == Mode.NULL) {
modeMap[i] = Mode.GM_BYTE;
}
}
 
// break into segments
segmentLength = new int[length];
segmentType = new Mode[length];
segmentStart = new int[length];
 
segmentCount = 0;
segmentLength[0] = 1;
segmentType[0] = modeMap[0];
segmentStart[0] = 0;
for (i = 1; i < length; i++) {
if (modeMap[i] == modeMap[i - 1]) {
segmentLength[segmentCount]++;
} else {
segmentCount++;
segmentLength[segmentCount] = 1;
segmentType[segmentCount] = modeMap[i];
segmentStart[segmentCount] = i;
}
}
 
// A segment can be a control segment if
// a) It is not the start segment of the data stream
// b) All characters are control characters
// c) The length of the segment is no more than 3
// d) The previous segment is not chinese
if (segmentCount > 1) {
for (i = 1; i < segmentCount; i++) { // (a)
if (segmentLength[i] <= 3 && segmentType[i - 1] != Mode.GM_CHINESE) { // (c) and (d)
boolean controlLatch = true;
for (int j = 0; j < segmentLength[i]; j++) {
boolean thischarLatch = false;
for (int k = 0; k < 63; k++) {
if (this.inputData[segmentStart[i] + j] == SHIFT_SET[k]) {
thischarLatch = true;
}
}
 
if (!thischarLatch) {
// This character is not a control character
controlLatch = false;
}
}
 
if (controlLatch) { // (b)
segmentType[i] = Mode.GM_CONTROL;
}
}
}
}
 
// Stages 7 to 9
if (segmentCount >= 3) {
for (i = 0; i < segmentCount - 1; i++) {
Mode pm, tm, nm, lm;
int tl, nl, ll, position;
boolean lastSegment = false;
 
if (i == 0) {
pm = Mode.NULL;
} else {
pm = segmentType[i - 1];
}
 
tm = segmentType[i];
tl = segmentLength[i];
 
nm = segmentType[i + 1];
nl = segmentLength[i + 1];
 
lm = segmentType[i + 2];
ll = segmentLength[i + 2];
 
position = segmentStart[i];
 
if (i + 2 == segmentCount) {
lastSegment = true;
}
 
segmentType[i] = getBestMode(pm, tm, nm, lm, tl, nl, ll, position, lastSegment);
 
if