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/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 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 Label/.settings/org.eclipse.jdt.core.prefs
New file
0,0 → 1,7
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8
/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/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/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/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/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/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/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/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/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/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/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/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/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/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/uk/org/okapibarcode/util/Arrays.java
New file
0,0 → 1,112
/*
* Copyright 2018 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.util;
 
import uk.org.okapibarcode.backend.OkapiException;
 
/**
* Array utility class.
*
* @author Daniel Gredler
*/
public final class Arrays {
 
private Arrays() {
// utility class
}
 
/**
* Returns the position of the specified value in the specified array.
*
* @param value the value to search for
* @param array the array to search in
* @return the position of the specified value in the specified array
*/
public static int positionOf(final char value, final char[] array) {
for (int i = 0; i < array.length; i++) {
if (value == array[i]) {
return i;
}
}
throw new OkapiException("Unable to find character '" + value + "' in character array.");
}
 
/**
* Returns the position of the specified value in the specified array.
*
* @param value the value to search for
* @param array the array to search in
* @return the position of the specified value in the specified array
*/
public static int positionOf(final int value, final int[] array) {
for (int i = 0; i < array.length; i++) {
if (value == array[i]) {
return i;
}
}
throw new OkapiException("Unable to find integer '" + value + "' in integer array.");
}
 
/**
* Returns <code>true</code> if the specified array contains the specified value.
*
* @param array the array to check in
* @param value the value to check for
* @return true if the specified array contains the specified value
*/
public static boolean contains(final int[] array, final int value) {
for (int i = 0; i < array.length; i++) {
if (array[i] == value) {
return true;
}
}
return false;
}
 
/**
* Returns <code>true</code> if the specified array contains the specified sub-array at the
* specified index.
*
* @param array the array to search in
* @param searchFor the sub-array to search for
* @param index the index at which to search
* @return whether or not the specified array contains the specified sub-array at the specified
* index
*/
public static boolean containsAt(final byte[] array, final byte[] searchFor, final int index) {
for (int i = 0; i < searchFor.length; i++) {
if (index + i >= array.length || array[index + i] != searchFor[i]) {
return false;
}
}
return true;
}
 
/**
* Inserts the specified array into the specified original array at the specified index.
*
* @param original the original array into which we want to insert another array
* @param index the index at which we want to insert the array
* @param inserted the array that we want to insert
* @return the combined array
*/
public static int[] insertArray(final int[] original, final int index, final int[] inserted) {
final int[] modified = new int[original.length + inserted.length];
System.arraycopy(original, 0, modified, 0, index);
System.arraycopy(inserted, 0, modified, index, inserted.length);
System.arraycopy(original, index, modified, index + inserted.length, modified.length - index - inserted.length);
return modified;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/util/EciMode.java
New file
0,0 → 1,67
/*
* Copyright 2018 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.util;
 
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
 
public class EciMode {
 
public static final EciMode NONE = new EciMode(-1, null);
 
public final int mode;
public final Charset charset;
 
private EciMode(final int mode, final Charset charset) {
this.mode = mode;
this.charset = charset;
}
 
public static EciMode of(final String data, final String charsetName, final int mode) {
try {
final Charset charset = Charset.forName(charsetName);
if (charset.canEncode() && charset.newEncoder().canEncode(data)) {
return new EciMode(mode, charset);
} else {
return NONE;
}
} catch (final UnsupportedCharsetException e) {
return NONE;
}
}
 
public EciMode or(final String data, final String charsetName, final int mode) {
if (!equals(NONE)) {
return this;
} else {
return of(data, charsetName, mode);
}
}
 
@Override
public boolean equals(final Object other) {
return other instanceof EciMode && ((EciMode) other).mode == this.mode;
}
 
@Override
public int hashCode() {
return Integer.valueOf(this.mode).hashCode();
}
 
@Override
public String toString() {
return "EciMode[mode=" + this.mode + ", charset=" + this.charset + "]";
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/util/Strings.java
New file
0,0 → 1,226
/*
* Copyright 2018 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.util;
 
import static java.nio.charset.StandardCharsets.ISO_8859_1;
 
import java.nio.charset.StandardCharsets;
 
import uk.org.okapibarcode.backend.OkapiException;
 
/**
* String utility class.
*
* @author Daniel Gredler
*/
public final class Strings {
 
private Strings() {
// utility class
}
 
/**
* Replaces raw values with special placeholders, where applicable.
*
* @param s the string to add placeholders to
* @return the specified string, with placeholders added
* @see <a href="http://www.zint.org.uk/Manual.aspx?type=p&page=4">Zint placeholders</a>
* @see #unescape(String, boolean)
*/
public static String escape(final String s) {
final StringBuilder sb = new StringBuilder(s.length() + 10);
for (int i = 0; i < s.length(); i++) {
final char c = s.charAt(i);
switch (c) {
case '\u0000':
sb.append("\\0"); // null
break;
case '\u0004':
sb.append("\\E"); // end of transmission
break;
case '\u0007':
sb.append("\\a"); // bell
break;
case '\u0008':
sb.append("\\b"); // backspace
break;
case '\u0009':
sb.append("\\t"); // horizontal tab
break;
case '\n':
sb.append("\\n"); // line feed
break;
case '\u000b':
sb.append("\\v"); // vertical tab
break;
case '\u000c':
sb.append("\\f"); // form feed
break;
case '\r':
sb.append("\\r"); // carriage return
break;
case '\u001b':
sb.append("\\e"); // escape
break;
case '\u001d':
sb.append("\\G"); // group separator
break;
case '\u001e':
sb.append("\\R"); // record separator
break;
case '\\':
sb.append("\\\\"); // escape the escape character
break;
default:
if (c >= 32 && c <= 126) {
sb.append(c); // printable ASCII
} else {
final byte[] bytes = String.valueOf(c).getBytes(ISO_8859_1);
final String hex = String.format("%02X", bytes[0] & 0xFF);
sb.append("\\x").append(hex);
}
break;
}
}
return sb.toString();
}
 
/**
* Replaces any special placeholders with their raw values (not including FNC values).
*
* @param s the string to check for placeholders
* @param lenient whether or not to be lenient with unrecognized escape sequences
* @return the specified string, with placeholders replaced
* @see <a href="http://www.zint.org.uk/Manual.aspx?type=p&page=4">Zint placeholders</a>
* @see #escape(String)
*/
public static String unescape(final String s, final boolean lenient) {
final StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
final char c = s.charAt(i);
if (c != '\\') {
sb.append(c);
} else {
if (i + 1 >= s.length()) {
final String msg = "Error processing escape sequences: expected escape character, found end of string";
throw new OkapiException(msg);
} else {
final char c2 = s.charAt(i + 1);
switch (c2) {
case '0':
sb.append('\u0000'); // null
i++;
break;
case 'E':
sb.append('\u0004'); // end of transmission
i++;
break;
case 'a':
sb.append('\u0007'); // bell
i++;
break;
case 'b':
sb.append('\u0008'); // backspace
i++;
break;
case 't':
sb.append('\u0009'); // horizontal tab
i++;
break;
case 'n':
sb.append('\n'); // line feed
i++;
break;
case 'v':
sb.append('\u000b'); // vertical tab
i++;
break;
case 'f':
sb.append('\u000c'); // form feed
i++;
break;
case 'r':
sb.append('\r'); // carriage return
i++;
break;
case 'e':
sb.append('\u001b'); // escape
i++;
break;
case 'G':
sb.append('\u001d'); // group separator
i++;
break;
case 'R':
sb.append('\u001e'); // record separator
i++;
break;
case '\\':
sb.append('\\'); // escape the escape character
i++;
break;
case 'x':
if (i + 3 >= s.length()) {
final String msg = "Error processing escape sequences: expected hex sequence, found end of string";
throw new OkapiException(msg);
} else {
final char c3 = s.charAt(i + 2);
final char c4 = s.charAt(i + 3);
if (isHex(c3) && isHex(c4)) {
final byte b = (byte) Integer.parseInt("" + c3 + c4, 16);
sb.append(new String(new byte[] { b }, StandardCharsets.ISO_8859_1));
i += 3;
} else {
final String msg = "Error processing escape sequences: expected hex sequence, found '" + c3 + c4 + "'";
throw new OkapiException(msg);
}
}
break;
default:
if (lenient) {
sb.append(c);
} else {
throw new OkapiException("Error processing escape sequences: expected valid escape character, found '" + c2 + "'");
}
}
}
}
}
return sb.toString();
}
 
private static boolean isHex(final char c) {
return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';
}
 
/**
* Appends the specific integer to the specified string, in binary format, padded to the
* specified number of digits.
*
* @param s the string to append to
* @param value the value to append, in binary format
* @param digits the number of digits to pad to
*/
public static void binaryAppend(final StringBuilder s, final int value, final int digits) {
final int start = 0x01 << digits - 1;
for (int i = 0; i < digits; i++) {
if ((value & start >> i) == 0) {
s.append('0');
} else {
s.append('1');
}
}
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/util/Gs1.java
New file
0,0 → 1,596
/*
* Copyright 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.util;
 
import uk.org.okapibarcode.backend.OkapiException;
 
/**
* GS1 utility class.
*/
public final class Gs1 {
 
private Gs1() {
// utility class
}
 
/**
* Verifies that the specified data is in good GS1 format <tt>"[AI]data"</tt> pairs, and returns
* a reduced version of the input string containing FNC1 escape sequences instead of AI
* brackets. With a few small exceptions, this code matches the Zint GS1 validation code as
* closely as possible, in order to make it easier to keep in sync.
*
* @param s the data string to verify
* @param fnc1 the string to use to represent FNC1 in the output
* @return the input data, verified and with FNC1 strings added at the appropriate positions
* @see <a href="https://sourceforge.net/p/zint/code/ci/master/tree/backend/gs1.c">Corresponding
* Zint code</a>
* @see <a href="http://www.gs1.org/docs/gsmp/barcodes/GS1_General_Specifications.pdf">GS1
* specification</a>
*/
public static String verify(final String s, final String fnc1) {
 
// Enforce compliance with GS1 General Specification
// http://www.gs1.org/docs/gsmp/barcodes/GS1_General_Specifications.pdf
 
final char[] source = s.toCharArray();
final StringBuilder reduced = new StringBuilder(source.length);
final int[] ai_value = new int[100];
final int[] ai_location = new int[100];
final int[] data_location = new int[100];
final int[] data_length = new int[100];
int error_latch;
 
/* Detect extended ASCII characters */
for (int i = 0; i < source.length; i++) {
if (source[i] >= 128) {
throw new OkapiException("Extended ASCII characters are not supported by GS1");
}
if (source[i] < 32) {
throw new OkapiException("Control characters are not supported by GS1");
}
}
 
/* Make sure we start with an AI */
if (source[0] != '[') {
throw new OkapiException("Data does not start with an AI");
}
 
/* Check the position of the brackets */
int bracket_level = 0;
int max_bracket_level = 0;
int ai_length = 0;
int max_ai_length = 0;
int min_ai_length = 5;
int j = 0;
boolean ai_latch = false;
for (int i = 0; i < source.length; i++) {
ai_length += j;
if (j == 1 && source[i] != ']' && (source[i] < '0' || source[i] > '9')) {
ai_latch = true;
}
if (source[i] == '[') {
bracket_level++;
j = 1;
}
if (source[i] == ']') {
bracket_level--;
if (ai_length < min_ai_length) {
min_ai_length = ai_length;
}
j = 0;
ai_length = 0;
}
if (bracket_level > max_bracket_level) {
max_bracket_level = bracket_level;
}
if (ai_length > max_ai_length) {
max_ai_length = ai_length;
}
}
min_ai_length--;
 
if (bracket_level != 0) {
/* Not all brackets are closed */
throw new OkapiException("Malformed AI in input data (brackets don't match)");
}
 
if (max_bracket_level > 1) {
/* Nested brackets */
throw new OkapiException("Found nested brackets in input data");
}
 
if (max_ai_length > 4) {
/* AI is too long */
throw new OkapiException("Invalid AI in input data (AI too long)");
}
 
if (min_ai_length <= 1) {
/* AI is too short */
throw new OkapiException("Invalid AI in input data (AI too short)");
}
 
if (ai_latch) {
/* Non-numeric data in AI */
throw new OkapiException("Invalid AI in input data (non-numeric characters in AI)");
}
 
int ai_count = 0;
for (int i = 1; i < source.length; i++) {
if (source[i - 1] == '[') {
ai_location[ai_count] = i;
ai_value[ai_count] = 0;
for (j = 0; source[i + j] != ']'; j++) {
ai_value[ai_count] *= 10;
ai_value[ai_count] += Character.getNumericValue(source[i + j]);
}
ai_count++;
}
}
 
for (int i = 0; i < ai_count; i++) {
data_location[i] = ai_location[i] + 3;
if (ai_value[i] >= 100) {
data_location[i]++;
}
if (ai_value[i] >= 1000) {
data_location[i]++;
}
data_length[i] = source.length - data_location[i];
for (j = source.length - 1; j >= data_location[i]; j--) {
if (source[j] == '[') {
data_length[i] = j - data_location[i];
}
}
}
 
for (int i = 0; i < ai_count; i++) {
if (data_length[i] == 0) {
/* No data for given AI */
throw new OkapiException("Empty data field in input data");
}
}
 
// Check for valid AI values and data lengths according to GS1 General
// Specification Release 18, January 2018
for (int i = 0; i < ai_count; i++) {
 
error_latch = 2;
switch (ai_value[i]) {
// Length 2 Fixed
case 20: // VARIANT
if (data_length[i] != 2) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 3 Fixed
case 422: // ORIGIN
case 424: // COUNTRY PROCESS
case 426: // COUNTRY FULL PROCESS
if (data_length[i] != 3) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 4 Fixed
case 8111: // POINTS
if (data_length[i] != 4) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 6 Fixed
case 11: // PROD DATE
case 12: // DUE DATE
case 13: // PACK DATE
case 15: // BEST BY
case 16: // SELL BY
case 17: // USE BY
case 7006: // FIRST FREEZE DATE
case 8005: // PRICE PER UNIT
if (data_length[i] != 6) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 10 Fixed
case 7003: // EXPIRY TIME
if (data_length[i] != 10) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 13 Fixed
case 410: // SHIP TO LOC
case 411: // BILL TO
case 412: // PURCHASE FROM
case 413: // SHIP FOR LOC
case 414: // LOC NO
case 415: // PAY TO
case 416: // PROD/SERV LOC
case 7001: // NSN
if (data_length[i] != 13) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 14 Fixed
case 1: // GTIN
case 2: // CONTENT
case 8001: // DIMENSIONS
if (data_length[i] != 14) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 17 Fixed
case 402: // GSIN
if (data_length[i] != 17) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 18 Fixed
case 0: // SSCC
case 8006: // ITIP
case 8017: // GSRN PROVIDER
case 8018: // GSRN RECIPIENT
if (data_length[i] != 18) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 2 Max
case 7010: // PROD METHOD
if (data_length[i] > 2) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 3 Max
case 427: // ORIGIN SUBDIVISION
case 7008: // AQUATIC SPECIES
if (data_length[i] > 3) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 4 Max
case 7004: // ACTIVE POTENCY
if (data_length[i] > 4) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 6 Max
case 242: // MTO VARIANT
if (data_length[i] > 6) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 8 Max
case 30: // VAR COUNT
case 37: // COUNT
if (data_length[i] > 8) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 10 Max
case 7009: // FISHING GEAR TYPE
case 8019: // SRIN
if (data_length[i] > 10) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 12 Max
case 7005: // CATCH AREA
case 8011: // CPID SERIAL
if (data_length[i] > 12) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 20 Max
case 10: // BATCH/LOT
case 21: // SERIAL
case 22: // CPV
case 243: // PCN
case 254: // GLN EXTENSION COMPONENT
case 420: // SHIP TO POST
case 7020: // REFURB LOT
case 7021: // FUNC STAT
case 7022: // REV STAT
case 710: // NHRN PZN
case 711: // NHRN CIP
case 712: // NHRN CN
case 713: // NHRN DRN
case 714: // NHRN AIM
case 8002: // CMT NO
case 8012: // VERSION
if (data_length[i] > 20) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 25 Max
case 8020: // REF NO
if (data_length[i] > 25) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 30 Max
case 240: // ADDITIONAL ID
case 241: // CUST PART NO
case 250: // SECONDARY SERIAL
case 251: // REF TO SOURCE
case 400: // ORDER NUMBER
case 401: // GINC
case 403: // ROUTE
case 7002: // MEAT CUT
case 7023: // GIAI ASSEMBLY
case 8004: // GIAI
case 8010: // CPID
case 8013: // BUDI-DI
case 90: // INTERNAL
if (data_length[i] > 30) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 34 Max
case 8007: // IBAN
if (data_length[i] > 34) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
// Length 70 Max
case 8110: // Coupon code
case 8112: // Paperless coupon code
case 8200: // PRODUCT URL
if (data_length[i] > 70) {
error_latch = 1;
} else {
error_latch = 0;
}
break;
 
}
 
if (ai_value[i] == 253) { // GDTI
if (data_length[i] < 14 || data_length[i] > 30) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] == 255) { // GCN
if (data_length[i] < 14 || data_length[i] > 25) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 3100 && ai_value[i] <= 3169) {
if (data_length[i] != 6) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 3200 && ai_value[i] <= 3379) {
if (data_length[i] != 6) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 3400 && ai_value[i] <= 3579) {
if (data_length[i] != 6) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 3600 && ai_value[i] <= 3699) {
if (data_length[i] != 6) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 3900 && ai_value[i] <= 3909) { // AMOUNT
if (data_length[i] > 15) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 3910 && ai_value[i] <= 3919) { // AMOUNT
if (data_length[i] < 4 || data_length[i] > 18) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 3920 && ai_value[i] <= 3929) { // PRICE
if (data_length[i] > 15) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 3930 && ai_value[i] <= 3939) { // PRICE
if (data_length[i] < 4 || data_length[i] > 18) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 3940 && ai_value[i] <= 3949) { // PRCNT OFF
if (data_length[i] != 4) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] == 421) { // SHIP TO POST
if (data_length[i] < 3 || data_length[i] > 12) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] == 423 || ai_value[i] == 425) {
// COUNTRY INITIAL PROCESS || COUNTRY DISASSEMBLY
if (data_length[i] < 3 || data_length[i] > 15) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] == 7007) { // HARVEST DATE
if (data_length[i] < 6 || data_length[i] > 12) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 7030 && ai_value[i] <= 7039) { // PROCESSOR #
if (data_length[i] < 4 || data_length[i] > 30) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] == 8003) { // GRAI
if (data_length[i] < 15 || data_length[i] > 30) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] == 8008) { // PROD TIME
if (data_length[i] < 9 || data_length[i] > 12) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (ai_value[i] >= 91 && ai_value[i] <= 99) { // INTERNAL
if (data_length[i] > 90) {
error_latch = 1;
} else {
error_latch = 0;
}
}
 
if (error_latch == 1) {
throw new OkapiException("Invalid data length for AI");
}
 
if (error_latch == 2) {
throw new OkapiException("Invalid AI value");
}
}
 
/* Resolve AI data - put resulting string in 'reduced' */
int last_ai = 0;
boolean fixedLengthAI = true;
for (int i = 0; i < source.length; i++) {
if (source[i] != '[' && source[i] != ']') {
reduced.append(source[i]);
}
if (source[i] == '[') {
/* Start of an AI string */
if (!fixedLengthAI) {
reduced.append(fnc1);
}
last_ai = 10 * Character.getNumericValue(source[i + 1]) + Character.getNumericValue(source[i + 2]);
/*
* The following values from
* "GS-1 General Specification version 8.0 issue 2, May 2008" figure 5.4.8.2.1 - 1
* "Element Strings with Pre-Defined Length Using Application Identifiers"
*/
fixedLengthAI = last_ai >= 0 && last_ai <= 4 || last_ai >= 11 && last_ai <= 20 || last_ai == 23
|| /* legacy support - see 5.3.8.2.2 */
last_ai >= 31 && last_ai <= 36 || last_ai == 41;
}
}
 
return reduced.toString();
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/util/Doubles.java
New file
0,0 → 1,39
/*
* Copyright 2015 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.util;
 
/**
* Double utility class.
*
* @author Daniel Gredler
*/
public final class Doubles {
 
private Doubles() {
// utility class
}
 
/**
* It's usually not a good idea to check floating point numbers for exact equality. This method
* allows us to check for approximate equality.
*
* @param d1 the first double
* @param d2 the second double
* @return whether or not the two doubles are approximately equal (to within 0.0001)
*/
public static boolean roughlyEqual(final double d1, final double d2) {
return Math.abs(d1 - d2) < 0.0001;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/output/ExtendedOutputStreamWriter.java
New file
0,0 → 1,80
/*
* Copyright 2015 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.output;
 
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
 
/**
* {@link OutputStreamWriter} extension which provides some convenience methods for writing numbers.
*/
class ExtendedOutputStreamWriter extends OutputStreamWriter {
 
/** Format to use when writing doubles to the stream. */
private final String doubleFormat;
 
/**
* Creates a new extended output stream writer, using the UTF-8 charset.
*
* @param out the stream to write to
* @param doubleFormat the format to use when writing doubles to the stream
*/
public ExtendedOutputStreamWriter(final OutputStream out, final String doubleFormat) {
super(out, StandardCharsets.UTF_8);
this.doubleFormat = doubleFormat;
}
 
/** {@inheritDoc} */
@Override
public ExtendedOutputStreamWriter append(final CharSequence cs) throws IOException {
super.append(cs);
return this;
}
 
/** {@inheritDoc} */
@Override
public ExtendedOutputStreamWriter append(final CharSequence cs, final int start, final int end) throws IOException {
super.append(cs, start, end);
return this;
}
 
/**
* Writes the specified double to the stream, formatted according to the format specified in the
* constructor.
*
* @param d the double to write to the stream
* @return this writer
* @throws IOException if an I/O error occurs
*/
public ExtendedOutputStreamWriter append(final double d) throws IOException {
super.append(String.format(Locale.ROOT, this.doubleFormat, d));
return this;
}
 
/**
* Writes the specified integer to the stream.
*
* @param i the integer to write to the stream
* @return this writer
* @throws IOException if an I/O error occurs
*/
public ExtendedOutputStreamWriter appendInt(final int i) throws IOException {
super.append(String.valueOf(i));
return this;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/output/SymbolRenderer.java
New file
0,0 → 1,34
/*
* Copyright 2015 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.output;
 
import java.io.IOException;
 
import uk.org.okapibarcode.backend.Symbol;
 
/**
* Renders symbols to some output format.
*/
public interface SymbolRenderer {
 
/**
* Renders the specified symbology.
*
* @param symbol the symbology to render
* @throws IOException if there is an I/O error
*/
void render(Symbol symbol) throws IOException;
 
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/output/Java2DRenderer.java
New file
0,0 → 1,169
/*
* Copyright 2014-2015 Robin Stuart, Robert Elliott, 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.output;
 
import static uk.org.okapibarcode.backend.HumanReadableAlignment.CENTER;
import static uk.org.okapibarcode.backend.HumanReadableAlignment.JUSTIFY;
 
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.Collections;
import java.util.List;
 
import uk.org.okapibarcode.backend.Hexagon;
import uk.org.okapibarcode.backend.HumanReadableAlignment;
import uk.org.okapibarcode.backend.Symbol;
import uk.org.okapibarcode.backend.TextBox;
 
/**
* Renders symbologies using the Java 2D API.
*/
public class Java2DRenderer implements SymbolRenderer {
 
/** The graphics to render to. */
private final Graphics2D g2d;
 
/** The magnification factor to apply. */
private final double magnification;
 
/** The paper (background) color. */
private final Color paper;
 
/** The ink (foreground) color. */
private final Color ink;
 
/**
* Creates a new Java 2D renderer. If the specified paper color is <tt>null</tt>, the symbol is
* drawn without clearing the existing <tt>g2d</tt> background.
*
* @param g2d the graphics to render to
* @param magnification the magnification factor to apply
* @param paper the paper (background) color (may be <tt>null</tt>)
* @param ink the ink (foreground) color
*/
public Java2DRenderer(final Graphics2D g2d, final double magnification, final Color paper, final Color ink) {
this.g2d = g2d;
this.magnification = magnification;
this.paper = paper;
this.ink = ink;
}
 
/** {@inheritDoc} */
@Override
public void render(final Symbol symbol) {
 
final int marginX = (int) (symbol.getQuietZoneHorizontal() * this.magnification);
final int marginY = (int) (symbol.getQuietZoneVertical() * this.magnification);
 
Font f = symbol.getFont();
if (f != null) {
f = f.deriveFont((float) (f.getSize2D() * this.magnification));
} else {
f = new Font(symbol.getFontName(), Font.PLAIN, (int) (symbol.getFontSize() * this.magnification));
f = f.deriveFont(Collections.singletonMap(TextAttribute.TRACKING, 0));
}
 
final Font oldFont = this.g2d.getFont();
final Color oldColor = this.g2d.getColor();
 
if (this.paper != null) {
final int w = (int) (symbol.getWidth() * this.magnification);
final int h = (int) (symbol.getHeight() * this.magnification);
this.g2d.setColor(this.paper);
this.g2d.fillRect(0, 0, w, h);
}
 
this.g2d.setColor(this.ink);
 
for (final Rectangle2D.Double rect : symbol.getRectangles()) {
final double x = rect.x * this.magnification + marginX;
final double y = rect.y * this.magnification + marginY;
final double w = rect.width * this.magnification;
final double h = rect.height * this.magnification;
this.g2d.fillRect((int) x, (int) y, (int) w, (int) h);
}
 
for (final TextBox text : symbol.getTexts()) {
final HumanReadableAlignment alignment = text.alignment == JUSTIFY && text.text.length() == 1 ? CENTER : text.alignment;
final Font font = alignment != JUSTIFY ? f : addTracking(f, text.width * this.magnification, text.text, this.g2d);
this.g2d.setFont(font);
final FontMetrics fm = this.g2d.getFontMetrics();
final Rectangle2D bounds = fm.getStringBounds(text.text, this.g2d);
final float y = (float) (text.y * this.magnification) + marginY;
float x;
switch (alignment) {
case LEFT:
case JUSTIFY:
x = (float) (this.magnification * text.x + marginX);
break;
case RIGHT:
x = (float) (this.magnification * text.x + this.magnification * text.width - bounds.getWidth() + marginX);
break;
case CENTER:
x = (float) (this.magnification * text.x + this.magnification * text.width / 2 - bounds.getWidth() / 2 + marginX);
break;
default:
throw new IllegalStateException("Unknown alignment: " + alignment);
}
this.g2d.drawString(text.text, x, y);
}
 
for (final Hexagon hexagon : symbol.getHexagons()) {
final Polygon polygon = new Polygon();
for (int j = 0; j < 6; j++) {
polygon.addPoint((int) (hexagon.pointX[j] * this.magnification + marginX), (int) (hexagon.pointY[j] * this.magnification + marginY));
}
this.g2d.fill(polygon);
}
 
final List<Ellipse2D.Double> target = symbol.getTarget();
for (int i = 0; i + 1 < target.size(); i += 2) {
final Ellipse2D.Double outer = adjust(target.get(i), this.magnification, marginX, marginY);
final Ellipse2D.Double inner = adjust(target.get(i + 1), this.magnification, marginX, marginY);
final Area area = new Area(outer);
area.subtract(new Area(inner));
this.g2d.fill(area);
}
 
this.g2d.setFont(oldFont);
this.g2d.setColor(oldColor);
}
 
private static Ellipse2D.Double adjust(final Ellipse2D.Double ellipse, final double magnification, final int marginX, final int marginY) {
final double x = ellipse.x * magnification + marginX;
final double y = ellipse.y * magnification + marginY;
final double w = ellipse.width * magnification + marginX;
final double h = ellipse.height * magnification + marginY;
return new Ellipse2D.Double(x, y, w, h);
}
 
private static Font addTracking(final Font baseFont, final double maxTextWidth, final String text, final Graphics2D g2d) {
final FontRenderContext frc = g2d.getFontRenderContext();
final double originalWidth = baseFont.getStringBounds(text, frc).getWidth();
final double extraSpace = maxTextWidth - originalWidth;
final double extraSpacePerGap = extraSpace / (text.length() - 1);
final double scaleX = baseFont.isTransformed() ? baseFont.getTransform().getScaleX() : 1;
final double tracking = extraSpacePerGap / (baseFont.getSize2D() * scaleX);
return baseFont.deriveFont(Collections.singletonMap(TextAttribute.TRACKING, tracking));
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/output/PostScriptRenderer.java
New file
0,0 → 1,211
/*
* Copyright 2015 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.output;
 
import static uk.org.okapibarcode.backend.HumanReadableAlignment.CENTER;
import static uk.org.okapibarcode.backend.HumanReadableAlignment.JUSTIFY;
import static uk.org.okapibarcode.util.Doubles.roughlyEqual;
 
import java.awt.Color;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.OutputStream;
 
import uk.org.okapibarcode.backend.Hexagon;
import uk.org.okapibarcode.backend.HumanReadableAlignment;
import uk.org.okapibarcode.backend.Symbol;
import uk.org.okapibarcode.backend.TextBox;
 
/**
* Renders symbologies to EPS (Encapsulated PostScript).
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
* @author Daniel Gredler
*/
public class PostScriptRenderer implements SymbolRenderer {
 
/** The output stream to render to. */
private final OutputStream out;
 
/** The magnification factor to apply. */
private final double magnification;
 
/** The paper (background) color. */
private final Color paper;
 
/** The ink (foreground) color. */
private final Color ink;
 
/**
* Creates a new PostScript renderer.
*
* @param out the output stream to render to
* @param magnification the magnification factor to apply
* @param paper the paper (background) color
* @param ink the ink (foreground) color
*/
public PostScriptRenderer(final OutputStream out, final double magnification, final Color paper, final Color ink) {
this.out = out;
this.magnification = magnification;
this.paper = paper;
this.ink = ink;
}
 
/** {@inheritDoc} */
@Override
public void render(final Symbol symbol) throws IOException {
 
// All y dimensions are reversed because EPS origin (0,0) is at the bottom left, not top
// left
 
final String content = symbol.getContent();
final int width = (int) (symbol.getWidth() * this.magnification);
final int height = (int) (symbol.getHeight() * this.magnification);
final int marginX = (int) (symbol.getQuietZoneHorizontal() * this.magnification);
final int marginY = (int) (symbol.getQuietZoneVertical() * this.magnification);
 
String title;
if (content == null || content.isEmpty()) {
title = "OkapiBarcode Generated Symbol";
} else {
title = content;
}
 
try (ExtendedOutputStreamWriter writer = new ExtendedOutputStreamWriter(this.out, "%.2f")) {
 
// Header
writer.append("%!PS-Adobe-3.0 EPSF-3.0\n");
writer.append("%%Creator: OkapiBarcode\n");
writer.append("%%Title: ").append(title).append('\n');
writer.append("%%Pages: 0\n");
writer.append("%%BoundingBox: 0 0 ").appendInt(width).append(" ").appendInt(height).append("\n");
writer.append("%%EndComments\n");
 
// Definitions
writer.append("/TL { setlinewidth moveto lineto stroke } bind def\n");
writer.append("/TC { moveto 0 360 arc 360 0 arcn fill } bind def\n");
writer.append("/TH { 0 setlinewidth moveto lineto lineto lineto lineto lineto closepath fill } bind def\n");
writer.append("/TB { 2 copy } bind def\n");
writer.append("/TR { newpath 4 1 roll exch moveto 1 index 0 rlineto 0 exch rlineto neg 0 rlineto closepath fill } bind def\n");
writer.append("/TE { pop pop } bind def\n");
 
// Background
writer.append("newpath\n");
writer.append(this.ink.getRed() / 255.0).append(" ").append(this.ink.getGreen() / 255.0).append(" ").append(this.ink.getBlue() / 255.0).append(" setrgbcolor\n");
writer.append(this.paper.getRed() / 255.0).append(" ").append(this.paper.getGreen() / 255.0).append(" ").append(this.paper.getBlue() / 255.0).append(" setrgbcolor\n");
writer.append(height).append(" 0.00 TB 0.00 ").append(width).append(" TR\n");
 
// Rectangles
for (int i = 0; i < symbol.getRectangles().size(); i++) {
final Rectangle2D.Double rect = symbol.getRectangles().get(i);
if (i == 0) {
writer.append("TE\n");
writer.append(this.ink.getRed() / 255.0).append(" ").append(this.ink.getGreen() / 255.0).append(" ").append(this.ink.getBlue() / 255.0).append(" setrgbcolor\n");
writer.append(rect.height * this.magnification).append(" ").append(height - (rect.y + rect.height) * this.magnification - marginY).append(" TB ")
.append(rect.x * this.magnification + marginX).append(" ").append(rect.width * this.magnification).append(" TR\n");
} else {
final Rectangle2D.Double prev = symbol.getRectangles().get(i - 1);
if (!roughlyEqual(rect.height, prev.height) || !roughlyEqual(rect.y, prev.y)) {
writer.append("TE\n");
writer.append(this.ink.getRed() / 255.0).append(" ").append(this.ink.getGreen() / 255.0).append(" ").append(this.ink.getBlue() / 255.0).append(" setrgbcolor\n");
writer.append(rect.height * this.magnification).append(" ").append(height - (rect.y + rect.height) * this.magnification - marginY).append(" ");
}
writer.append("TB ").append(rect.x * this.magnification + marginX).append(" ").append(rect.width * this.magnification).append(" TR\n");
}
}
 
// Text
for (int i = 0; i < symbol.getTexts().size(); i++) {
final TextBox text = symbol.getTexts().get(i);
final HumanReadableAlignment alignment = text.alignment == JUSTIFY && text.text.length() == 1 ? CENTER : text.alignment;
if (i == 0) {
writer.append("TE\n");
;
writer.append(this.ink.getRed() / 255.0).append(" ").append(this.ink.getGreen() / 255.0).append(" ").append(this.ink.getBlue() / 255.0).append(" setrgbcolor\n");
}
writer.append("matrix currentmatrix\n");
writer.append("/").append(symbol.getFontName()).append(" findfont\n");
writer.append(symbol.getFontSize() * this.magnification).append(" scalefont setfont\n");
final double y = height - text.y * this.magnification - marginY;
switch (alignment) {
case LEFT:
final double leftX = this.magnification * text.x + marginX;
writer.append(" 0 0 moveto ").append(leftX).append(" ").append(y).append(" translate 0.00 rotate 0 0 moveto\n");
writer.append(" (").append(text.text).append(") show\n");
break;
case JUSTIFY:
final double textX = this.magnification * text.x + marginX;
final double textW = this.magnification * text.width;
writer.append(" 0 0 moveto ").append(textX).append(" ").append(y).append(" translate 0.00 rotate 0 0 moveto\n");
writer.append(" (").append(text.text).append(") dup stringwidth pop ").append(textW).append(" sub neg 1 index length 1 sub div 0").append(" 3 -1 roll ashow\n");
break;
case RIGHT:
final double rightX = this.magnification * text.x + this.magnification * text.width + marginX;
writer.append(" 0 0 moveto ").append(rightX).append(" ").append(y).append(" translate 0.00 rotate 0 0 moveto\n");
writer.append(" (").append(text.text).append(") stringwidth\n");
writer.append("pop\n");
writer.append("-1 mul 0 rmoveto\n");
writer.append(" (").append(text.text).append(") show\n");
break;
case CENTER:
final double centerX = this.magnification * text.x + this.magnification * text.width / 2 + marginX;
writer.append(" 0 0 moveto ").append(centerX).append(" ").append(y).append(" translate 0.00 rotate 0 0 moveto\n");
writer.append(" (").append(text.text).append(") stringwidth\n");
writer.append("pop\n");
writer.append("-2 div 0 rmoveto\n");
writer.append(" (").append(text.text).append(") show\n");
break;
default:
throw new IllegalStateException("Unknown alignment: " + alignment);
}
writer.append("setmatrix\n");
}
 
// Circles
// Because MaxiCode size is fixed, this ignores magnification
for (int i = 0; i < symbol.getTarget().size(); i += 2) {
final Ellipse2D.Double ellipse1 = symbol.getTarget().get(i);
final Ellipse2D.Double ellipse2 = symbol.getTarget().get(i + 1);
if (i == 0) {
writer.append("TE\n");
writer.append(this.ink.getRed() / 255.0).append(" ").append(this.ink.getGreen() / 255.0).append(" ").append(this.ink.getBlue() / 255.0).append(" setrgbcolor\n");
writer.append(this.ink.getRed() / 255.0).append(" ").append(this.ink.getGreen() / 255.0).append(" ").append(this.ink.getBlue() / 255.0).append(" setrgbcolor\n");
}
final double x1 = ellipse1.x + ellipse1.width / 2;
final double x2 = ellipse2.x + ellipse2.width / 2;
final double y1 = height - ellipse1.y - ellipse1.width / 2;
final double y2 = height - ellipse2.y - ellipse2.width / 2;
final double r1 = ellipse1.width / 2;
final double r2 = ellipse2.width / 2;
writer.append(x1 + marginX).append(" ").append(y1 - marginY).append(" ").append(r1).append(" ").append(x2 + marginX).append(" ").append(y2 - marginY).append(" ").append(r2).append(" ")
.append(x2 + r2 + marginX).append(" ").append(y2 - marginY).append(" TC\n");
}
 
// Hexagons
// Because MaxiCode size is fixed, this ignores magnification
for (int i = 0; i < symbol.getHexagons().size(); i++) {
final Hexagon hexagon = symbol.getHexagons().get(i);
for (int j = 0; j < 6; j++) {
writer.append(hexagon.pointX[j] + marginX).append(" ").append(height - hexagon.pointY[j] - marginY).append(" ");
}
writer.append(" TH\n");
}
 
// Footer
writer.append("\nshowpage\n");
}
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/output/SvgRenderer.java
New file
0,0 → 1,225
/*
* Copyright 2014-2015 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.output;
 
import static uk.org.okapibarcode.backend.HumanReadableAlignment.CENTER;
import static uk.org.okapibarcode.backend.HumanReadableAlignment.JUSTIFY;
 
import java.awt.Color;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
 
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
 
import org.w3c.dom.Document;
import org.w3c.dom.Text;
 
import uk.org.okapibarcode.backend.Hexagon;
import uk.org.okapibarcode.backend.HumanReadableAlignment;
import uk.org.okapibarcode.backend.Symbol;
import uk.org.okapibarcode.backend.TextBox;
 
/**
* Renders symbologies to SVG (Scalable Vector Graphics).
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
* @author Daniel Gredler
*/
public class SvgRenderer implements SymbolRenderer {
 
/** The output stream to render to. */
private final OutputStream out;
 
/** The magnification factor to apply. */
private final double magnification;
 
/** The paper (background) color. */
private final Color paper;
 
/** The ink (foreground) color. */
private final Color ink;
 
/** Whether or not to include the XML prolog in the output. */
private final boolean xmlProlog;
 
/**
* Creates a new SVG renderer.
*
* @param out the output stream to render to
* @param magnification the magnification factor to apply
* @param paper the paper (background) color
* @param ink the ink (foreground) color
* @param xmlProlog whether or not to include the XML prolog in the output (usually {@code true}
* for standalone SVG documents, {@code false} for SVG content embedded directly in HTML
* documents)
*/
public SvgRenderer(final OutputStream out, final double magnification, final Color paper, final Color ink, final boolean xmlProlog) {
this.out = out;
this.magnification = magnification;
this.paper = paper;
this.ink = ink;
this.xmlProlog = xmlProlog;
}
 
/** {@inheritDoc} */
@Override
public void render(final Symbol symbol) throws IOException {
 
final String content = symbol.getContent();
final int width = (int) (symbol.getWidth() * this.magnification);
final int height = (int) (symbol.getHeight() * this.magnification);
final int marginX = (int) (symbol.getQuietZoneHorizontal() * this.magnification);
final int marginY = (int) (symbol.getQuietZoneVertical() * this.magnification);
 
String title;
if (content == null || content.isEmpty()) {
title = "OkapiBarcode Generated Symbol";
} else {
title = content;
}
 
final String fgColour = String.format("%02X", this.ink.getRed()) + String.format("%02X", this.ink.getGreen()) + String.format("%02X", this.ink.getBlue());
 
final String bgColour = String.format("%02X", this.paper.getRed()) + String.format("%02X", this.paper.getGreen()) + String.format("%02X", this.paper.getBlue());
 
try (ExtendedOutputStreamWriter writer = new ExtendedOutputStreamWriter(this.out, "%.2f")) {
 
// XML Prolog
if (this.xmlProlog) {
writer.append("<?xml version=\"1.0\" standalone=\"no\"?>\n");
writer.append("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n");
writer.append(" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
}
 
// Header
writer.append("<svg width=\"").appendInt(width).append("\" height=\"").appendInt(height).append("\" version=\"1.1").append("\" xmlns=\"http://www.w3.org/2000/svg\">\n");
writer.append(" <desc>").append(clean(title)).append("</desc>\n");
writer.append(" <g id=\"barcode\" fill=\"#").append(fgColour).append("\">\n");
writer.append(" <rect x=\"0\" y=\"0\" width=\"").appendInt(width).append("\" height=\"").appendInt(height).append("\" fill=\"#").append(bgColour).append("\" />\n");
 
// Rectangles
for (int i = 0; i < symbol.getRectangles().size(); i++) {
final Rectangle2D.Double rect = symbol.getRectangles().get(i);
writer.append(" <rect x=\"").append(rect.x * this.magnification + marginX).append("\" y=\"").append(rect.y * this.magnification + marginY).append("\" width=\"")
.append(rect.width * this.magnification).append("\" height=\"").append(rect.height * this.magnification).append("\" />\n");
}
 
// Text
for (int i = 0; i < symbol.getTexts().size(); i++) {
final TextBox text = symbol.getTexts().get(i);
final HumanReadableAlignment alignment = text.alignment == JUSTIFY && text.text.length() == 1 ? CENTER : text.alignment;
double x;
String anchor;
switch (alignment) {
case LEFT:
case JUSTIFY:
x = this.magnification * text.x + marginX;
anchor = "start";
break;
case RIGHT:
x = this.magnification * text.x + this.magnification * text.width + marginX;
anchor = "end";
break;
case CENTER:
x = this.magnification * text.x + this.magnification * text.width / 2 + marginX;
anchor = "middle";
break;
default:
throw new IllegalStateException("Unknown alignment: " + alignment);
}
writer.append(" <text x=\"").append(x).append("\" y=\"").append(text.y * this.magnification + marginY).append("\" text-anchor=\"").append(anchor).append("\"\n");
if (alignment == JUSTIFY) {
writer.append(" textLength=\"").append(text.width * this.magnification).append("\" lengthAdjust=\"spacing\"\n");
}
writer.append(" font-family=\"").append(clean(symbol.getFontName())).append("\" font-size=\"").append(symbol.getFontSize() * this.magnification).append("\" fill=\"#")
.append(fgColour).append("\">\n");
writer.append(" ").append(clean(text.text)).append("\n");
writer.append(" </text>\n");
}
 
// Circles
for (int i = 0; i < symbol.getTarget().size(); i++) {
final Ellipse2D.Double ellipse = symbol.getTarget().get(i);
String color;
if ((i & 1) == 0) {
color = fgColour;
} else {
color = bgColour;
}
writer.append(" <circle cx=\"").append((ellipse.x + ellipse.width / 2) * this.magnification + marginX).append("\" cy=\"")
.append((ellipse.y + ellipse.width / 2) * this.magnification + marginY).append("\" r=\"").append(ellipse.width / 2 * this.magnification).append("\" fill=\"#").append(color)
.append("\" />\n");
}
 
// Hexagons
for (int i = 0; i < symbol.getHexagons().size(); i++) {
final Hexagon hexagon = symbol.getHexagons().get(i);
writer.append(" <path d=\"");
for (int j = 0; j < 6; j++) {
if (j == 0) {
writer.append("M ");
} else {
writer.append("L ");
}
writer.append(hexagon.pointX[j] * this.magnification + marginX).append(" ").append(hexagon.pointY[j] * this.magnification + marginY).append(" ");
}
writer.append("Z\" />\n");
}
 
// Footer
writer.append(" </g>\n");
writer.append("</svg>\n");
}
}
 
/**
* Cleans / sanitizes the specified string for inclusion in XML. A bit convoluted, but we're
* trying to do it without adding an external dependency just for this...
*
* @param s the string to be cleaned / sanitized
* @return the cleaned / sanitized string
*/
protected String clean(String s) {
 
// remove control characters
s = s.replaceAll("[\u0000-\u001f]", "");
 
// escape XML characters
try {
final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
final Text text = document.createTextNode(s);
final Transformer transformer = TransformerFactory.newInstance().newTransformer();
final DOMSource source = new DOMSource(text);
final StringWriter writer = new StringWriter();
final StreamResult result = new StreamResult(writer);
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.transform(source, result);
return writer.toString();
} catch (ParserConfigurationException | TransformerException | TransformerFactoryConfigurationError e) {
return s;
}
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Composite.java
New file
0,0 → 1,2769
/*
* 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.awt.geom.Rectangle2D;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
 
import uk.org.okapibarcode.backend.DataBar14.Mode;
 
/**
* <p>
* Implements GS1 Composite symbology according to ISO/IEC 24723:2010.
*
* <p>
* Composite symbols comprise a 2D element which encodes GS1 data and a "linear" element which can
* be UPC, EAN, Code 128 or GS1 DataBar symbol.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class Composite extends Symbol {
 
/** The linear component choices available. */
public static enum LinearEncoding {
UPCA, UPCE, EAN, CODE_128, DATABAR_14, DATABAR_14_STACK, DATABAR_14_STACK_OMNI, DATABAR_LIMITED, DATABAR_EXPANDED, DATABAR_EXPANDED_STACK
}
 
/** The 2D component choices available. */
public static enum CompositeMode {
/**
* Indicates that the composite symbol uses a MicroPDF417 variant as the 2D component. Of
* the 2D component choices, this one holds the least amount of data.
*/
CC_A,
/**
* Indicates that the composite symbol uses a MicroPDF417 symbol as the 2D component,
* starting with a codeword of 920.
*/
CC_B,
/**
* Indicates that the composite symbol uses a PDF417 symbol as the 2D component, starting
* with a codeword of 920. Of the 2D component choices, this one holds the most amount of
* data. May only be used if the linear component is {@link LinearEncoding#CODE_128 Code
* 128}.
*/
CC_C
}
 
private static enum GeneralFieldMode {
NUMERIC, ALPHA, ISOIEC, INVALID_CHAR, ANY_ENC, ALPHA_OR_ISO
}
 
/* CC-A component coefficients from ISO/IEC 24728:2006 Annex F */
private static final int[] CCA_COEFFS = {
/* k = 4 */
522, 568, 723, 809,
/* k = 5 */
427, 919, 460, 155, 566,
/* k = 6 */
861, 285, 19, 803, 17, 766,
/* k = 7 */
76, 925, 537, 597, 784, 691, 437,
/* k = 8 */
237, 308, 436, 284, 646, 653, 428, 379 };
 
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 };
 
/* rows, error codewords, k-offset of valid CC-A sizes from ISO/IEC 24723:2006 Table 9 */
private static final int[] CCA_VARIANTS = { 5, 6, 7, 8, 9, 10, 12, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 4, 4, 5, 5, 6, 6, 7, 4, 5, 6, 7, 7, 4, 5, 6, 7, 8, 0, 0, 4, 4, 9, 9, 15, 0, 4, 9, 15, 15, 0, 4, 9,
15, 22 };
 
/*
* following is Left RAP, Centre RAP, Right RAP and Start Cluster from ISO/IEC 24723:2006 tables
* 10 and 11
*/
private static final int[] A_RAP_TABLE = { 39, 1, 32, 8, 14, 43, 20, 11, 1, 5, 15, 21, 40, 43, 46, 34, 29, 0, 0, 0, 0, 0, 0, 0, 43, 33, 37, 47, 1, 20, 23, 26, 14, 9, 19, 33, 12, 40, 46, 23, 52,
23, 13, 17, 27, 33, 52, 3, 6, 46, 41, 6, 0, 3, 3, 3, 0, 3, 3, 0, 3, 6, 6, 0, 0, 0, 0, 3 };
 
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" };
 
/* 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" };
 
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, 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, 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, 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 };
 
/* rows, columns, error codewords, k-offset */
/* MicroPDF417 coefficients from ISO/IEC 24728:2006 Annex F */
private static final int[] MICROCOEFFS = {
/* 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 };
 
/*
* 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, 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, 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, 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 };
 
private String binary_string;
private int ecc;
private LinearEncoding symbology = LinearEncoding.CODE_128;
private String general_field;
private GeneralFieldMode[] general_field_type;
private int cc_width;
private final int[][] pwr928 = new int[69][7];
private final int[] codeWords = new int[180];
private int codeWordCount;
private final int[] bitStr = new int[13];
private int[] inputData;
private CompositeMode cc_mode;
private String linearContent;
private CompositeMode userPreferredMode = CompositeMode.CC_A;
private int target_bitsize;
private int remainder;
private int linearWidth; // Width of Code 128 linear
 
public Composite() {
this.inputDataType = Symbol.DataType.GS1;
}
 
@Override
public void setDataType(final DataType dataType) {
if (dataType != Symbol.DataType.GS1) {
throw new IllegalArgumentException("Only GS1 data type is supported for GS1 Composite symbology.");
}
}
 
@Override
protected boolean gs1Supported() {
return true;
}
 
/**
* Set the type of linear component included in the composite symbol, this will determine how
* the lower part of the symbol is encoded.
*
* @param linearSymbology The symbology of the linear component
*/
public void setSymbology(final LinearEncoding linearSymbology) {
this.symbology = linearSymbology;
}
 
/**
* Returns the type of linear component included in the composite symbol.
*
* @return the type of linear component included in the composite symbol
*/
public LinearEncoding getSymbology() {
return this.symbology;
}
 
/**
* Set the data to be encoded in the linear component of the composite symbol.
*
* @param linearContent the linear data in GS1 format
*/
public void setLinearContent(final String linearContent) {
this.linearContent = linearContent;
}
 
/**
* Returns the data encoded in the linear component of the composite symbol.
*
* @return the data encoded in the linear component of the composite symbol
*/
public String getLinearContent() {
return this.linearContent;
}
 
/**
* Set the preferred encoding method for the 2D component of the composite symbol. This value
* may be ignored if the amount of data supplied is too big for the selected encoding. Mode CC-C
* can only be used with a Code 128 linear component.
*
* @param userMode Preferred mode
*/
public void setPreferredMode(final CompositeMode userMode) {
this.userPreferredMode = userMode;
}
 
/**
* Returns the preferred encoding method for the 2D component of the composite symbol.
*
* @return the preferred encoding method for the 2D component of the composite symbol
*/
public CompositeMode getPreferredMode() {
return this.userPreferredMode;
}
 
@Override
protected void encode() {
 
List<Rectangle2D.Double> linear_rect;
List<TextBox> linear_txt;
final List<Rectangle2D.Double> combine_rect = new ArrayList<>();
final List<TextBox> combine_txt = new ArrayList<>();
String linear_encodeInfo;
int linear_height;
int top_shift = 0; // 2D component x-coordinate shift
int bottom_shift = 0; // linear component x-coordinate shift
this.linearWidth = 0;
 
if (this.linearContent.isEmpty()) {
throw new OkapiException("No linear data set");
}
 
// Manage composite component encoding first
encodeComposite();
 
// Then encode linear component
switch (this.symbology) {
case UPCA:
final Upc upca = new Upc();
upca.setMode(Upc.Mode.UPCA);
upca.setLinkageFlag(true);
upca.setContent(this.linearContent);
linear_rect = upca.rectangles;
linear_txt = upca.texts;
linear_height = upca.symbol_height;
linear_encodeInfo = upca.getEncodeInfo();
bottom_shift = 6;
top_shift = 3;
break;
case UPCE:
final Upc upce = new Upc();
upce.setMode(Upc.Mode.UPCE);
upce.setLinkageFlag(true);
upce.setContent(this.linearContent);
linear_rect = upce.rectangles;
linear_txt = upce.texts;
linear_height = upce.symbol_height;
linear_encodeInfo = upce.getEncodeInfo();
bottom_shift = 6;
top_shift = 3;
break;
case EAN:
final Ean ean = new Ean();
if (eanCalculateVersion() == 8) {
ean.setMode(Ean.Mode.EAN8);
bottom_shift = 14;
} else {
ean.setMode(Ean.Mode.EAN13);
bottom_shift = 6;
top_shift = 3;
}
ean.setLinkageFlag(true);
ean.setContent(this.linearContent);
linear_rect = ean.rectangles;
linear_txt = ean.texts;
linear_height = ean.symbol_height;
linear_encodeInfo = ean.getEncodeInfo();
break;
case CODE_128:
final Code128 code128 = new Code128();
switch (this.cc_mode) {
case CC_A:
code128.setCca();
break;
case CC_B:
code128.setCcb();
break;
case CC_C:
code128.setCcc();
bottom_shift = 7;
break;
}
code128.setDataType(Symbol.DataType.GS1);
code128.setContent(this.linearContent);
this.linearWidth = code128.symbol_width;
linear_rect = code128.rectangles;
linear_txt = code128.texts;
linear_height = code128.symbol_height;
linear_encodeInfo = code128.getEncodeInfo();
break;
case DATABAR_14:
final DataBar14 dataBar14 = new DataBar14();
dataBar14.setLinkageFlag(true);
dataBar14.setMode(Mode.LINEAR);
dataBar14.setContent(this.linearContent);
linear_rect = dataBar14.rectangles;
linear_txt = dataBar14.texts;
linear_height = dataBar14.symbol_height;
linear_encodeInfo = dataBar14.getEncodeInfo();
bottom_shift = 4;
break;
case DATABAR_14_STACK_OMNI:
final DataBar14 dataBar14SO = new DataBar14();
dataBar14SO.setLinkageFlag(true);
dataBar14SO.setMode(Mode.OMNI);
dataBar14SO.setContent(this.linearContent);
linear_rect = dataBar14SO.rectangles;
linear_txt = dataBar14SO.texts;
linear_height = dataBar14SO.symbol_height;
linear_encodeInfo = dataBar14SO.getEncodeInfo();
top_shift = 1;
break;
case DATABAR_14_STACK:
final DataBar14 dataBar14S = new DataBar14();
dataBar14S.setLinkageFlag(true);
dataBar14S.setMode(Mode.STACKED);
dataBar14S.setContent(this.linearContent);
linear_rect = dataBar14S.rectangles;
linear_txt = dataBar14S.texts;
linear_height = dataBar14S.symbol_height;
linear_encodeInfo = dataBar14S.getEncodeInfo();
top_shift = 1;
break;
case DATABAR_LIMITED:
final DataBarLimited dataBarLimited = new DataBarLimited();
dataBarLimited.setLinkageFlag();
dataBarLimited.setContent(this.linearContent);
linear_rect = dataBarLimited.rectangles;
linear_txt = dataBarLimited.texts;
linear_height = dataBarLimited.symbol_height;
linear_encodeInfo = dataBarLimited.getEncodeInfo();
top_shift = 1;
bottom_shift = 10;
break;
case DATABAR_EXPANDED:
final DataBarExpanded dataBarExpanded = new DataBarExpanded();
dataBarExpanded.setLinkageFlag(true);
dataBarExpanded.setStacked(false);
dataBarExpanded.setContent(this.linearContent);
linear_rect = dataBarExpanded.rectangles;
linear_txt = dataBarExpanded.texts;
linear_height = dataBarExpanded.symbol_height;
linear_encodeInfo = dataBarExpanded.getEncodeInfo();
top_shift = 2;
break;
case DATABAR_EXPANDED_STACK:
final DataBarExpanded dataBarExpandedS = new DataBarExpanded();
dataBarExpandedS.setLinkageFlag(true);
dataBarExpandedS.setStacked(true);
dataBarExpandedS.setContent(this.linearContent);
linear_rect = dataBarExpandedS.rectangles;
linear_txt = dataBarExpandedS.texts;
linear_height = dataBarExpandedS.symbol_height;
linear_encodeInfo = dataBarExpandedS.getEncodeInfo();
top_shift = 2;
break;
default:
throw new OkapiException("Linear symbol not recognised");
}
 
if (this.cc_mode == CompositeMode.CC_C && this.symbology == LinearEncoding.CODE_128) {
/*
* Width of composite component depends on width of linear component, so recalculate.
*/
this.row_count = 0;
this.rectangles.clear();
this.symbol_height = 0;
this.symbol_width = 0;
this.encodeInfo.setLength(0);
encodeComposite();
}
 
if (this.cc_mode != CompositeMode.CC_C && this.symbology == LinearEncoding.CODE_128) {
if (this.linearWidth > this.symbol_width) {
top_shift = (this.linearWidth - this.symbol_width) / 2;
}
}
 
for (final Rectangle2D.Double orig : this.rectangles) {
combine_rect.add(new Rectangle2D.Double(orig.x + top_shift, orig.y, orig.width, orig.height));
}
 
for (final Rectangle2D.Double orig : linear_rect) {
combine_rect.add(new Rectangle2D.Double(orig.x + bottom_shift, orig.y + this.symbol_height, orig.width, orig.height));
}
 
int max_x = 0;
for (final Rectangle2D.Double rect : combine_rect) {
if (rect.x + rect.width > max_x) {
max_x = (int) Math.ceil(rect.x + rect.width);
}
}
 
for (final TextBox orig : linear_txt) {
combine_txt.add(new TextBox(orig.x + bottom_shift, orig.y + this.symbol_height, orig.width, orig.text, this.humanReadableAlignment));
}
 
this.rectangles = combine_rect;
this.texts = combine_txt;
this.symbol_height += linear_height;
this.symbol_width = max_x;
info(linear_encodeInfo);
}
 
private void encodeComposite() {
 
if (this.content.length() > 2990) {
throw new OkapiException("2D component input data too long");
}
 
this.cc_mode = this.userPreferredMode;
 
if (this.cc_mode == CompositeMode.CC_C && this.symbology != LinearEncoding.CODE_128) {
/* CC-C can only be used with a GS1-128 linear part */
throw new OkapiException("Invalid mode (CC-C only valid with GS1-128 linear component)");
}
 
switch (this.symbology) {
/* Determine width of 2D component according to ISO/IEC 24723 Table 1 */
case EAN:
if (eanCalculateVersion() == 8) {
this.cc_width = 3;
} else {
this.cc_width = 4;
}
break;
case UPCE:
case DATABAR_14_STACK_OMNI:
case DATABAR_14_STACK:
this.cc_width = 2;
break;
case DATABAR_LIMITED:
this.cc_width = 3;
break;
case CODE_128:
case DATABAR_14:
case DATABAR_EXPANDED:
case UPCA:
case DATABAR_EXPANDED_STACK:
this.cc_width = 4;
break;
}
 
infoLine("Composite Width: " + this.cc_width);
 
if (this.cc_mode == CompositeMode.CC_A && !cc_binary_string()) {
this.cc_mode = CompositeMode.CC_B;
}
 
if (this.cc_mode == CompositeMode.CC_B) { /*
* If the data didn't fit into CC-A it is
* recalculated for CC-B
*/
if (!cc_binary_string()) {
if (this.symbology != LinearEncoding.CODE_128) {
throw new OkapiException("Input too long");
} else {
this.cc_mode = CompositeMode.CC_C;
}
}
}
 
if (this.cc_mode == CompositeMode.CC_C) {
/*
* If the data didn't fit in CC-B (and linear part is GS1-128) it is recalculated for
* CC-C
*/
if (!cc_binary_string()) {
throw new OkapiException("Input too long");
}
}
 
switch (this.cc_mode) { /* Note that ecc_level is only relevant to CC-C */
case CC_A:
cc_a();
infoLine("Composite Type: CC-A");
break;
case CC_B:
cc_b();
infoLine("Composite Type: CC-B");
break;
case CC_C:
cc_c();
infoLine("Composite Type: CC-C");
break;
}
 
super.plotSymbol();
}
 
@Override
protected void plotSymbol() {
// empty
}
 
private int eanCalculateVersion() {
/* Determine if EAN-8 or EAN-13 is being used */
 
int length = 0;
int i;
boolean latch;
 
latch = true;
for (i = 0; i < this.linearContent.length(); i++) {
if (this.linearContent.charAt(i) >= '0' && this.linearContent.charAt(i) <= '9') {
if (latch) {
length++;
}
} else {
latch = false;
}
}
 
if (length <= 7) {
// EAN-8
return 8;
} else {
// EAN-13
return 13;
}
}
 
private boolean calculateSymbolSize() {
int i;
final int binary_length = this.binary_string.length();
if (this.cc_mode == CompositeMode.CC_A) {
/* CC-A 2D component - calculate remaining space */
switch (this.cc_width) {
case 2:
if (binary_length > 167) {
return false;
}
if (binary_length <= 167) {
this.target_bitsize = 167;
}
if (binary_length <= 138) {
this.target_bitsize = 138;
}
if (binary_length <= 118) {
this.target_bitsize = 118;
}
if (binary_length <= 108) {
this.target_bitsize = 108;
}
if (binary_length <= 88) {
this.target_bitsize = 88;
}
if (binary_length <= 78) {
this.target_bitsize = 78;
}
if (binary_length <= 59) {
this.target_bitsize = 59;
}
break;
case 3:
if (binary_length > 167) {
return false;
}
if (binary_length <= 167) {
this.target_bitsize = 167;
}
if (binary_length <= 138) {
this.target_bitsize = 138;
}
if (binary_length <= 118) {
this.target_bitsize = 118;
}
if (binary_length <= 98) {
this.target_bitsize = 98;
}
if (binary_length <= 78) {
this.target_bitsize = 78;
}
break;
case 4:
if (binary_length > 197) {
return false;
}
if (binary_length <= 197) {
this.target_bitsize = 197;
}
if (binary_length <= 167) {
this.target_bitsize = 167;
}
if (binary_length <= 138) {
this.target_bitsize = 138;
}
if (binary_length <= 108) {
this.target_bitsize = 108;
}
if (binary_length <= 78) {
this.target_bitsize = 78;
}
break;
}
}
 
if (this.cc_mode == CompositeMode.CC_B) {
/* CC-B 2D component - calculated from ISO/IEC 24728 Table 1 */
switch (this.cc_width) {
case 2:
if (binary_length > 336) {
return false;
}
if (binary_length <= 336) {
this.target_bitsize = 336;
}
if (binary_length <= 296) {
this.target_bitsize = 296;
}
if (binary_length <= 256) {
this.target_bitsize = 256;
}
if (binary_length <= 208) {
this.target_bitsize = 208;
}
if (binary_length <= 160) {
this.target_bitsize = 160;
}
if (binary_length <= 104) {
this.target_bitsize = 104;
}
if (binary_length <= 56) {
this.target_bitsize = 56;
}
break;
case 3:
if (binary_length > 768) {
return false;
}
if (binary_length <= 768) {
this.target_bitsize = 768;
}
if (binary_length <= 648) {
this.target_bitsize = 648;
}
if (binary_length <= 536) {
this.target_bitsize = 536;
}
if (binary_length <= 416) {
this.target_bitsize = 416;
}
if (binary_length <= 304) {
this.target_bitsize = 304;
}
if (binary_length <= 208) {
this.target_bitsize = 208;
}
if (binary_length <= 152) {
this.target_bitsize = 152;
}
if (binary_length <= 112) {
this.target_bitsize = 112;
}
if (binary_length <= 72) {
this.target_bitsize = 72;
}
if (binary_length <= 32) {
this.target_bitsize = 32;
}
break;
case 4:
if (binary_length > 1184) {
return false;
}
if (binary_length <= 1184) {
this.target_bitsize = 1184;
}
if (binary_length <= 1016) {
this.target_bitsize = 1016;
}
if (binary_length <= 840) {
this.target_bitsize = 840;
}
if (binary_length <= 672) {
this.target_bitsize = 672;
}
if (binary_length <= 496) {
this.target_bitsize = 496;
}
if (binary_length <= 352) {
this.target_bitsize = 352;
}
if (binary_length <= 264) {
this.target_bitsize = 264;
}
if (binary_length <= 208) {
this.target_bitsize = 208;
}
if (binary_length <= 152) {
this.target_bitsize = 152;
}
if (binary_length <= 96) {
this.target_bitsize = 96;
}
if (binary_length <= 56) {
this.target_bitsize = 56;
}
break;
}
}
 
if (this.cc_mode == CompositeMode.CC_C) {
/* CC-C 2D Component is a bit more complex! */
int byte_length, codewords_used, ecc_level, ecc_codewords, rows;
int codewords_total, target_codewords, target_bytesize;
 
byte_length = binary_length / 8;
if (binary_length % 8 != 0) {
byte_length++;
}
 
codewords_used = byte_length / 6 * 5;
codewords_used += byte_length % 6;
 
ecc_level = 7;
if (codewords_used <= 1280) {
ecc_level = 6;
}
if (codewords_used <= 640) {
ecc_level = 5;
}
if (codewords_used <= 320) {
ecc_level = 4;
}
if (codewords_used <= 160) {
ecc_level = 3;
}
if (codewords_used <= 40) {
ecc_level = 2;
}
this.ecc = ecc_level;
ecc_codewords = 1;
for (i = 1; i <= ecc_level + 1; i++) {
ecc_codewords *= 2;
}
 
codewords_used += ecc_codewords;
codewords_used += 3;
 
if (this.linearWidth == 0) {
/* Linear component not yet calculated */
this.cc_width = (int) (0.5 + Math.sqrt(codewords_used / 3.0));
} else {
this.cc_width = (this.linearWidth - 53) / 17;
}
 
if (codewords_used / this.cc_width > 90) {
/* stop the symbol from becoming too high */
this.cc_width = this.cc_width + 1;
}
 
rows = codewords_used / this.cc_width;
if (codewords_used % this.cc_width != 0) {
rows++;
}
 
while (this.cc_width > 3 * rows) {
/* stop the symbol from becoming too wide (section 10) */
this.cc_width--;
 
rows = codewords_used / this.cc_width;
if (codewords_used % this.cc_width != 0) {
rows++;
}
}
 
codewords_total = this.cc_width * rows;
 
target_codewords = codewords_total - ecc_codewords;
target_codewords -= 3;
 
target_bytesize = 6 * (target_codewords / 5);
target_bytesize += target_codewords % 5;
 
this.target_bitsize = 8 * target_bytesize;
}
 
this.remainder = this.target_bitsize - binary_length;
return true;
}
 
private boolean cc_binary_string() {
/* Handles all data encodation from section 5 of ISO/IEC 24723 */
int encoding_method, read_posn, d1, d2, value, alpha_pad;
int i, j, ai_crop, fnc1_latch;
int group_val;
int ai90_mode;
boolean latch;
int alpha, alphanum, numeric, test1, test2, test3, next_ai_posn;
int numeric_value, table3_letter;
String numeric_part;
String ninety;
int latchOffset;
 
encoding_method = 1;
read_posn = 0;
ai_crop = 0;
fnc1_latch = 0;
alpha_pad = 0;
ai90_mode = 0;
this.ecc = 0;
value = 0;
this.target_bitsize = 0;
 
if (this.content.charAt(0) == '1' && (this.content.charAt(1) == '0' || this.content.charAt(1) == '1' || this.content.charAt(1) == '7') && this.content.length() >= 8) {
/* Source starts (10), (11) or (17) */
encoding_method = 2;
}
 
if (this.content.charAt(0) == '9' && this.content.charAt(1) == '0') {
/* Source starts (90) */
encoding_method = 3;
}
 
info("Composite Encodation: ");
switch (encoding_method) {
case 1:
infoLine("0");
break;
case 2:
infoLine("10");
break;
case 3:
infoLine("11");
break;
}
 
this.binary_string = "";
 
if (encoding_method == 1) {
this.binary_string += "0";
}
 
if (encoding_method == 2) {
/* Encoding Method field "10" - date and lot number */
 
this.binary_string += "10";
 
if (this.content.charAt(1) == '0') {
/* No date data */
this.binary_string += "11";
read_posn = 2;
} else {
/* Production Date (11) or Expiration Date (17) */
group_val = (10 * (this.content.charAt(2) - '0') + this.content.charAt(3) - '0') * 384;
group_val += (10 * (this.content.charAt(4) - '0') + this.content.charAt(5) - '0' - 1) * 32;
group_val += 10 * (this.content.charAt(6) - '0') + this.content.charAt(7) - '0';
 
for (j = 0; j < 16; j++) {
if ((group_val & 0x8000 >> j) == 0) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
 
if (this.content.charAt(1) == '1') {
/* Production Date AI 11 */
this.binary_string += "0";
} else {
/* Expiration Date AI 17 */
this.binary_string += "1";
}
read_posn = 8;
}
 
if (read_posn + 2 < this.content.length()) {
if (this.content.charAt(read_posn) == '1' && this.content.charAt(read_posn + 1) == '0') {
/* Followed by AI 10 - strip this from general field */
read_posn += 2;
} else {
/* An FNC1 character needs to be inserted in the general field */
fnc1_latch = 1;
}
} else {
fnc1_latch = 1;
}
}
 
if (encoding_method == 3) {
/* Encodation Method field of "11" - AI 90 */
/*
* "This encodation method may be used if an element string with an AI 90 occurs at the
* start of the data message, and if the data field following the two-digit AI 90 starts
* with an alphanumeric string which complies with a specific format." (para 5.2.2)
*/
 
j = this.content.length();
for (i = this.content.length(); i > 2; i--) {
if (this.content.charAt(i - 1) == '[') {
j = i;
}
}
 
ninety = this.content.substring(2, j - 1);
 
/* Find out if the AI 90 data is alphabetic or numeric or both */
 
alpha = 0;
alphanum = 0;
numeric = 0;
 
for (i = 0; i < ninety.length(); i++) {
 
if (ninety.charAt(i) >= 'A' && ninety.charAt(i) <= 'Z') {
/* Character is alphabetic */
alpha += 1;
}
 
if (ninety.charAt(i) >= '0' && ninety.charAt(i) <= '9') {
/* Character is numeric */
numeric += 1;
}
 
switch (ninety.charAt(i)) {
case '*':
case ',':
case '-':
case '.':
case '/':
alphanum += 1;
break;
}
 
if (!(ninety.charAt(i) >= '0' && ninety.charAt(i) <= '9' || ninety.charAt(i) >= 'A' && ninety.charAt(i) <= 'Z')) {
if (ninety.charAt(i) != '*' && ninety.charAt(i) != ',' && ninety.charAt(i) != '-' && ninety.charAt(i) != '.' && ninety.charAt(i) != '/') {
/* An Invalid AI 90 character */
throw new OkapiException("Invalid AI 90 data");
}
}
}
 
/* must start with 0, 1, 2 or 3 digits followed by an uppercase character */
test1 = -1;
for (i = 3; i >= 0; i--) {
if (ninety.charAt(i) >= 'A' && ninety.charAt(i) <= 'Z') {
test1 = i;
}
}
 
test2 = 0;
for (i = 0; i < test1; i++) {
if (!(ninety.charAt(i) >= '0' && ninety.charAt(i) <= '9')) {
test2 = 1;
}
}
 
/* leading zeros are not permitted */
test3 = 0;
if (test1 >= 1 && ninety.charAt(0) == '0') {
test3 = 1;
}
 
if (test1 != -1 && test2 != 1 && test3 == 0) {
/* Encodation method "11" can be used */
this.binary_string += "11";
 
numeric -= test1;
alpha--;
 
/* Decide on numeric, alpha or alphanumeric mode */
/* Alpha mode is a special mode for AI 90 */
 
if (alphanum > 0) {
/* Alphanumeric mode */
this.binary_string += "0";
ai90_mode = 1;
} else {
if (alpha > numeric) {
/* Alphabetic mode */
this.binary_string += "11";
ai90_mode = 2;
} else {
/* Numeric mode */
this.binary_string += "10";
ai90_mode = 3;
}
}
 
next_ai_posn = 2 + ninety.length();
 
if (this.content.charAt(next_ai_posn) == '[') {
/* There are more AIs afterwords */
if (this.content.charAt(next_ai_posn + 1) == '2' && this.content.charAt(next_ai_posn + 2) == '1') {
/* AI 21 follows */
ai_crop = 1;
}
 
if (this.content.charAt(next_ai_posn + 1) == '8' && this.content.charAt(next_ai_posn + 2) == '0' && this.content.charAt(next_ai_posn + 3) == '0'
&& this.content.charAt(next_ai_posn + 4) == '4') {
/* AI 8004 follows */
ai_crop = 2;
}
}
 
switch (ai_crop) {
case 0:
this.binary_string += "0";
break;
case 1:
this.binary_string += "10";
break;
case 2:
this.binary_string += "11";
break;
}
 
if (test1 == 0) {
numeric_part = "0";
} else {
numeric_part = ninety.substring(0, test1);
}
 
numeric_value = 0;
for (i = 0; i < numeric_part.length(); i++) {
numeric_value *= 10;
numeric_value += numeric_part.charAt(i) - '0';
}
 
table3_letter = -1;
if (numeric_value < 31) {
switch (ninety.charAt(test1)) {
case 'B':
table3_letter = 0;
break;
case 'D':
table3_letter = 1;
break;
case 'H':
table3_letter = 2;
break;
case 'I':
table3_letter = 3;
break;
case 'J':
table3_letter = 4;
break;
case 'K':
table3_letter = 5;
break;
case 'L':
table3_letter = 6;
break;
case 'N':
table3_letter = 7;
break;
case 'P':
table3_letter = 8;
break;
case 'Q':
table3_letter = 9;
break;
case 'R':
table3_letter = 10;
break;
case 'S':
table3_letter = 11;
break;
case 'T':
table3_letter = 12;
break;
case 'V':
table3_letter = 13;
break;
case 'W':
table3_letter = 14;
break;
case 'Z':
table3_letter = 15;
break;
}
}
 
if (table3_letter != -1) {
/* Encoding can be done according to 5.2.2 c) 2) */
/* five bit binary string representing value before letter */
for (j = 0; j < 5; j++) {
if ((numeric_value & 0x10 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
 
/* followed by four bit representation of letter from Table 3 */
for (j = 0; j < 4; j++) {
if ((table3_letter & 0x08 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
} else {
/* Encoding is done according to 5.2.2 c) 3) */
this.binary_string += "11111";
/* ten bit representation of number */
for (j = 0; j < 10; j++) {
if ((numeric_value & 0x200 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
 
/* five bit representation of ASCII character */
for (j = 0; j < 5; j++) {
if ((ninety.charAt(test1) - 65 & 0x10 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
read_posn = test1 + 3;
} else {
/* Use general field encodation instead */
this.binary_string += "0";
read_posn = 0;
}
 
/* Now encode the rest of the AI 90 data field */
if (ai90_mode == 2) {
/* Alpha encodation (section 5.2.3) */
do {
if (this.content.charAt(read_posn) >= '0' && this.content.charAt(read_posn) <= '9') {
for (j = 0; j < 5; j++) {
if ((this.content.charAt(read_posn) + 4 & 0x10 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
if (this.content.charAt(read_posn) >= 'A' && this.content.charAt(read_posn) <= 'Z') {
for (j = 0; j < 6; j++) {
if ((this.content.charAt(read_posn) - 65 & 0x20 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
if (this.content.charAt(read_posn) == '[') {
this.binary_string += "11111";
}
 
read_posn++;
} while (this.content.charAt(read_posn - 1) != '[' && read_posn < this.content.length());
alpha_pad = 1; /* This is overwritten if a general field is encoded */
}
 
if (ai90_mode == 1) {
/* Alphanumeric mode */
do {
if (this.content.charAt(read_posn) >= '0' && this.content.charAt(read_posn) <= '9') {
for (j = 0; j < 5; j++) {
if ((this.content.charAt(read_posn) - 43 & 0x10 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
if (this.content.charAt(read_posn) >= 'A' && this.content.charAt(read_posn) <= 'Z') {
for (j = 0; j < 6; j++) {
if ((this.content.charAt(read_posn) - 33 & 0x20 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
switch (this.content.charAt(read_posn)) {
case '[':
this.binary_string += "01111";
break;
case '*':
this.binary_string += "111010";
break;
case ',':
this.binary_string += "111011";
break;
case '-':
this.binary_string += "111100";
break;
case '.':
this.binary_string += "111101";
break;
case '/':
this.binary_string += "111110";
break;
}
 
read_posn++;
} while (this.content.charAt(read_posn - 1) != '[' && this.content.charAt(read_posn - 1) != '\0');
}
 
read_posn += 2 * ai_crop;
}
 
/*
* The compressed data field has been processed if appropriate - the rest of the data (if
* any) goes into a general-purpose data compaction field
*/
 
j = 0;
this.general_field = "";
if (fnc1_latch == 1) {
/*
* Encodation method "10" has been used but it is not followed by AI 10, so a FNC1
* character needs to be added
*/
this.general_field += "[";
}
 
this.general_field += this.content.substring(read_posn);
 
latch = false;
if (this.general_field.length() != 0) {
alpha_pad = 0;
 
this.general_field_type = new GeneralFieldMode[this.general_field.length()];
 
for (i = 0; i < this.general_field.length(); i++) {
/* Table 13 - ISO/IEC 646 encodation */
if (this.general_field.charAt(i) < ' ' || this.general_field.charAt(i) > 'z') {
this.general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
latch = true;
} else {
this.general_field_type[i] = GeneralFieldMode.ISOIEC;
}
 
if (this.general_field.charAt(i) == '#') {
this.general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
latch = true;
}
if (this.general_field.charAt(i) == '$') {
this.general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
latch = true;
}
if (this.general_field.charAt(i) == '@') {
this.general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
latch = true;
}
if (this.general_field.charAt(i) == 92) {
this.general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
latch = true;
}
if (this.general_field.charAt(i) == '^') {
this.general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
latch = true;
}
if (this.general_field.charAt(i) == 96) {
this.general_field_type[i] = GeneralFieldMode.INVALID_CHAR;
latch = true;
}
 
/* Table 12 - Alphanumeric encodation */
if (this.general_field.charAt(i) >= 'A' && this.general_field.charAt(i) <= 'Z') {
this.general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
}
if (this.general_field.charAt(i) == '*') {
this.general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
}
if (this.general_field.charAt(i) == ',') {
this.general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
}
if (this.general_field.charAt(i) == '-') {
this.general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
}
if (this.general_field.charAt(i) == '.') {
this.general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
}
if (this.general_field.charAt(i) == '/') {
this.general_field_type[i] = GeneralFieldMode.ALPHA_OR_ISO;
}
 
/* Numeric encodation */
if (this.general_field.charAt(i) >= '0' && this.general_field.charAt(i) <= '9') {
this.general_field_type[i] = GeneralFieldMode.ANY_ENC;
}
if (this.general_field.charAt(i) == '[') {
/* FNC1 can be encoded in any system */
this.general_field_type[i] = GeneralFieldMode.ANY_ENC;
}
 
}
 
if (latch) {
/* Invalid characters in input data */
throw new OkapiException("Invalid characters in input data");
}
 
for (i = 0; i < this.general_field.length() - 1; i++) {
if (this.general_field_type[i] == GeneralFieldMode.ISOIEC && this.general_field.charAt(i + 1) == '[') {
this.general_field_type[i + 1] = GeneralFieldMode.ISOIEC;
}
}
 
for (i = 0; i < this.general_field.length() - 1; i++) {
if (this.general_field_type[i] == GeneralFieldMode.ALPHA_OR_ISO && this.general_field.charAt(i + 1) == '[') {
this.general_field_type[i + 1] = GeneralFieldMode.ALPHA_OR_ISO;
}
}
 
latch = applyGeneralFieldRules();
 
i = 0;
do {
switch (this.general_field_type[i]) {
case NUMERIC:
if (i != 0) {
if (this.general_field_type[i - 1] != GeneralFieldMode.NUMERIC && this.general_field.charAt(i - 1) != '[') {
this.binary_string += "000"; /* Numeric latch */
}
}
 
if (this.general_field.charAt(i) != '[') {
d1 = this.general_field.charAt(i) - '0';
} else {
d1 = 10;
}
 
if (i < this.general_field.length() - 1) {
if (this.general_field.charAt(i + 1) != '[') {
d2 = this.general_field.charAt(i + 1) - '0';
} else {
d2 = 10;
}
} else {
d2 = 10;
}
 
if (d1 != 10 || d2 != 10) {
/* If (d1==10)&&(d2==10) then input is either FNC1,FNC1 or FNC1,EOL */
value = 11 * d1 + d2 + 8;
 
for (j = 0; j < 7; j++) {
if ((value & 0x40 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
 
i += 2;
}
break;
 
case ALPHA:
if (i != 0) {
if (this.general_field_type[i - 1] == GeneralFieldMode.NUMERIC || this.general_field.charAt(i - 1) == '[') {
this.binary_string += "0000"; /* Alphanumeric latch */
}
if (this.general_field_type[i - 1] == GeneralFieldMode.ISOIEC) {
this.binary_string += "00100"; /* ISO/IEC 646 latch */
}
}
 
if (this.general_field.charAt(i) >= '0' && this.general_field.charAt(i) <= '9') {
 
value = this.general_field.charAt(i) - 43;
 
for (j = 0; j < 5; j++) {
if ((value & 0x10 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
if (this.general_field.charAt(i) >= 'A' && this.general_field.charAt(i) <= 'Z') {
 
value = this.general_field.charAt(i) - 33;
 
for (j = 0; j < 6; j++) {
if ((value & 0x20 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
if (this.general_field.charAt(i) == '[') {
this.binary_string += "01111"; /* FNC1/Numeric latch */
}
if (this.general_field.charAt(i) == '*') {
this.binary_string += "111010"; /* asterisk */
}
if (this.general_field.charAt(i) == ',') {
this.binary_string += "111011"; /* comma */
}
if (this.general_field.charAt(i) == '-') {
this.binary_string += "111100"; /* minus or hyphen */
}
if (this.general_field.charAt(i) == '.') {
this.binary_string += "111101"; /* period or full stop */
}
if (this.general_field.charAt(i) == '/') {
this.binary_string += "111110"; /* slash or solidus */
}
 
i++;
break;
 
case ISOIEC:
if (i != 0) {
if (this.general_field_type[i - 1] == GeneralFieldMode.NUMERIC || this.general_field.charAt(i - 1) == '[') {
this.binary_string += "0000"; /* Alphanumeric latch */
this.binary_string += "00100"; /* ISO/IEC 646 latch */
}
if (this.general_field_type[i - 1] == GeneralFieldMode.ALPHA) {
this.binary_string += "00100"; /* ISO/IEC 646 latch */
}
}
 
if (this.general_field.charAt(i) >= '0' && this.general_field.charAt(i) <= '9') {
 
value = this.general_field.charAt(i) - 43;
 
for (j = 0; j < 5; j++) {
if ((value & 0x10 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
if (this.general_field.charAt(i) >= 'A' && this.general_field.charAt(i) <= 'Z') {
 
value = this.general_field.charAt(i) - 1;
 
for (j = 0; j < 7; j++) {
if ((value & 0x40 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
if (this.general_field.charAt(i) >= 'a' && this.general_field.charAt(i) <= 'z') {
 
value = this.general_field.charAt(i) - 7;
 
for (j = 0; j < 7; j++) {
if ((value & 0x40 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
}
 
if (this.general_field.charAt(i) == '[') {
this.binary_string += "01111"; /* FNC1/Numeric latch */
}
if (this.general_field.charAt(i) == '!') {
this.binary_string += "11101000"; /* exclamation mark */
}
if (this.general_field.charAt(i) == 34) {
this.binary_string += "11101001"; /* quotation mark */
}
if (this.general_field.charAt(i) == 37) {
this.binary_string += "11101010"; /* percent sign */
}
if (this.general_field.charAt(i) == '&') {
this.binary_string += "11101011"; /* ampersand */
}
if (this.general_field.charAt(i) == 39) {
this.binary_string += "11101100"; /* apostrophe */
}
if (this.general_field.charAt(i) == '(') {
this.binary_string += "11101101"; /* left parenthesis */
}
if (this.general_field.charAt(i) == ')') {
this.binary_string += "11101110"; /* right parenthesis */
}
if (this.general_field.charAt(i) == '*') {
this.binary_string += "11101111"; /* asterisk */
}
if (this.general_field.charAt(i) == '+') {
this.binary_string += "11110000"; /* plus sign */
}
if (this.general_field.charAt(i) == ',') {
this.binary_string += "11110001"; /* comma */
}
if (this.general_field.charAt(i) == '-') {
this.binary_string += "11110010"; /* minus or hyphen */
}
if (this.general_field.charAt(i) == '.') {
this.binary_string += "11110011"; /* period or full stop */
}
if (this.general_field.charAt(i) == '/') {
this.binary_string += "11110100"; /* slash or solidus */
}
if (this.general_field.charAt(i) == ':') {
this.binary_string += "11110101"; /* colon */
}
if (this.general_field.charAt(i) == ';') {
this.binary_string += "11110110"; /* semicolon */
}
if (this.general_field.charAt(i) == '<') {
this.binary_string += "11110111"; /* less-than sign */
}
if (this.general_field.charAt(i) == '=') {
this.binary_string += "11111000"; /* equals sign */
}
if (this.general_field.charAt(i) == '>') {
this.binary_string += "11111001"; /* greater-than sign */
}
if (this.general_field.charAt(i) == '?') {
this.binary_string += "11111010"; /* question mark */
}
if (this.general_field.charAt(i) == '_') {
this.binary_string += "11111011"; /* underline or low line */
}
if (this.general_field.charAt(i) == ' ') {
this.binary_string += "11111100"; /* space */
}
 
i++;
break;
}
 
latchOffset = 0;
if (latch) {
latchOffset = 1;
}
} while (i + latchOffset < this.general_field.length());
}
 
if (!calculateSymbolSize()) {
return false;
}
 
if (latch) {
i = this.general_field.length() - 1;
/* There is still one more numeric digit to encode */
 
if (this.general_field.charAt(i) == '[') {
this.binary_string += "000001111";
} else {
if (this.remainder >= 4 && this.remainder <= 6) {
d1 = this.general_field.charAt(i) - '0';
d1++;
 
for (j = 0; j < 4; j++) {
if ((value & 0x08 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
} else {
d1 = this.general_field.charAt(i) - '0';
d2 = 10;
 
value = 11 * d1 + d2 + 8;
 
for (j = 0; j < 7; j++) {
if ((value & 0x40 >> j) == 0x00) {
this.binary_string += "0";
} else {
this.binary_string += "1";
}
}
/* This may push the symbol up to the next size */
}
}
}
 
if (this.binary_string.length() > 11805) { /* (2361 * 5) */
throw new OkapiException("Input too long");
}
 
/* size of the symbol may have changed when adding data in the above sequence */
if (!calculateSymbolSize()) {
return false;
}
 
infoLine("Composite Binary Length: " + this.binary_string.length());
displayBinaryString();
 
if (this.binary_string.length() < this.target_bitsize) {
/* Now add padding to binary string */
if (alpha_pad == 1) {
this.binary_string += "11111";
alpha_pad = 0;
/* Extra FNC1 character required after Alpha encodation (section 5.2.3) */
}
 
if (this.general_field.length() != 0 && this.general_field_type[this.general_field.length() - 1] == GeneralFieldMode.NUMERIC) {
this.binary_string += "0000";
}
 
while (this.binary_string.length() < this.target_bitsize) {
this.binary_string += "00100";
}
 
this.binary_string = this.binary_string.substring(0, this.target_bitsize);
}
 
return true;
}
 
private void displayBinaryString() {
int i, nibble;
/* Display binary string as hexadecimal */
 
info("Composite Binary String: ");
nibble = 0;
for (i = 0; i < this.binary_string.length(); i++) {
switch (i % 4) {
case 0:
if (this.binary_string.charAt(i) == '1') {
nibble += 8;
}
break;
case 1:
if (this.binary_string.charAt(i) == '1') {
nibble += 4;
}
break;
case 2:
if (this.binary_string.charAt(i) == '1') {
nibble += 2;
}
break;
case 3:
if (this.binary_string.charAt(i) == '1') {
nibble += 1;
}
info(Integer.toHexString(nibble));
nibble = 0;
break;
}
}
 
if (this.binary_string.length() % 4 != 0) {
info(Integer.toHexString(nibble));
}
infoLine();
}
 
private boolean applyGeneralFieldRules() {
/*
* Attempts to apply encoding rules from secions 7.2.5.5.1 to 7.2.5.5.3 of ISO/IEC
* 24724:2006
*/
 
int block_count, i, j, k;
GeneralFieldMode current, next, last;
final int[] blockLength = new int[200];
final GeneralFieldMode[] blockType = new GeneralFieldMode[200];
 
block_count = 0;
 
blockLength[block_count] = 1;
blockType[block_count] = this.general_field_type[0];
 
for (i = 1; i < this.general_field.length(); i++) {
current = this.general_field_type[i];
last = this.general_field_type[i - 1];
 
if (current == last) {
blockLength[block_count] = blockLength[block_count] + 1;
} else {
block_count++;
blockLength[block_count] = 1;
blockType[block_count] = this.general_field_type[i];
}
}
 
block_count++;
 
for (i = 0; i < block_count; i++) {
current = blockType[i];
next = blockType[i + 1];
 
if (current == GeneralFieldMode.ISOIEC && i != block_count - 1) {
if (next == GeneralFieldMode.ANY_ENC && blockLength[i + 1] >= 4) {
blockType[i + 1] = GeneralFieldMode.NUMERIC;
}
if (next == GeneralFieldMode.ANY_ENC && blockLength[i + 1] < 4) {
blockType[i + 1] = GeneralFieldMode.ISOIEC;
}
if (next == GeneralFieldMode.ALPHA_OR_ISO && blockLength[i + 1] >= 5) {
blockType[i + 1] = GeneralFieldMode.ALPHA;
}
if (next == GeneralFieldMode.ALPHA_OR_ISO && blockLength[i + 1] < 5) {
blockType[i + 1] = GeneralFieldMode.ISOIEC;
}
}
 
if (current == GeneralFieldMode.ALPHA_OR_ISO) {
blockType[i] = GeneralFieldMode.ALPHA;
}
 
if (current == GeneralFieldMode.ALPHA && i != block_count - 1) {
if (next == GeneralFieldMode.ANY_ENC && blockLength[i + 1] >= 6) {
blockType[i + 1] = GeneralFieldMode.NUMERIC;
}
if (next == GeneralFieldMode.ANY_ENC && blockLength[i + 1] < 6) {
if (i == block_count - 2 && blockLength[i + 1] >= 4) {
blockType[i + 1] = GeneralFieldMode.NUMERIC;
} else {
blockType[i + 1] = GeneralFieldMode.ALPHA;
}
}
}
 
if (current == GeneralFieldMode.ANY_ENC) {
blockType[i] = GeneralFieldMode.NUMERIC;
}
}
 
if (block_count > 1) {
i = 1;
while (i < block_count) {
if (blockType[i - 1] == blockType[i]) {
/* bring together */
blockLength[i - 1] = blockLength[i - 1] + blockLength[i];
j = i + 1;
 
/* decreace the list */
while (j < block_count) {
blockLength[j - 1] = blockLength[j];
blockType[j - 1] = blockType[j];
j++;
}
block_count--;
i--;
}
i++;
}
}
 
for (i = 0; i < block_count - 1; i++) {
if (blockType[i] == GeneralFieldMode.NUMERIC && (blockLength[i] & 1) != 0) {
/* Odd size numeric block */
blockLength[i] = blockLength[i] - 1;
blockLength[i + 1] = blockLength[i + 1] + 1;
}
}
 
j = 0;
for (i = 0; i < block_count; i++) {
for (k = 0; k < blockLength[i]; k++) {
this.general_field_type[j] = blockType[i];
j++;
}
}
 
if (blockType[block_count - 1] == GeneralFieldMode.NUMERIC && (blockLength[block_count - 1] & 1) != 0) {
/*
* If the last block is numeric and an odd size, further processing needs to be done
* outside this procedure
*/
return true;
} else {
return false;
}
}
 
private void cc_a() {
/* CC-A 2D component */
int i, strpos, segment, cwCnt, variant, rows;
int k, offset, j, total;
final int[] rsCodeWords = new int[8];
int LeftRAPStart, RightRAPStart, CentreRAPStart, StartCluster;
int LeftRAP, RightRAP, CentreRAP, Cluster;
final int[] dummy = new int[5];
int flip, loop;
String codebarre;
final StringBuilder bin = new StringBuilder();
String local_source; /* A copy of source but with padding zeroes to make 208 bits */
 
variant = 0;
 
for (i = 0; i < 13; i++) {
this.bitStr[i] = 0;
}
for (i = 0; i < 28; i++) {
this.codeWords[i] = 0;
}
 
local_source = this.binary_string;
for (i = this.binary_string.length(); i < 208; i++) {
local_source += "0";
}
 
for (segment = 0; segment < 13; segment++) {
strpos = segment * 16;
this.bitStr[segment] = 0;
for (i = 0; i < 16; i++) {
if (local_source.charAt(strpos + i) == '1') {
this.bitStr[segment] += 0x8000 >> i;
}
}
}
 
init928();
/* encode codeWords from bitStr */
cwCnt = encode928(this.binary_string.length());
 
switch (this.cc_width) {
case 2:
switch (cwCnt) {
case 6:
variant = 0;
break;
case 8:
variant = 1;
break;
case 9:
variant = 2;
break;
case 11:
variant = 3;
break;
case 12:
variant = 4;
break;
case 14:
variant = 5;
break;
case 17:
variant = 6;
break;
}
break;
case 3:
switch (cwCnt) {
case 8:
variant = 7;
break;
case 10:
variant = 8;
break;
case 12:
variant = 9;
break;
case 14:
variant = 10;
break;
case 17:
variant = 11;
break;
}
break;
case 4:
switch (cwCnt) {
case 8:
variant = 12;
break;
case 11:
variant = 13;
break;
case 14:
variant = 14;
break;
case 17:
variant = 15;
break;
case 20:
variant = 16;
break;
}
break;
}
 
rows = CCA_VARIANTS[variant];
k = CCA_VARIANTS[17 + variant];
offset = CCA_VARIANTS[34 + variant];
 
/* Reed-Solomon error correction */
 
for (i = 0; i < 8; i++) {
rsCodeWords[i] = 0;
}
total = 0;
info("Composite Codewords: ");
for (i = 0; i < cwCnt; i++) {
total = (this.codeWords[i] + rsCodeWords[k - 1]) % 929;
for (j = k - 1; j >= 0; j--) {
if (j == 0) {
rsCodeWords[j] = (929 - total * CCA_COEFFS[offset + j] % 929) % 929;
} else {
rsCodeWords[j] = (rsCodeWords[j - 1] + 929 - total * CCA_COEFFS[offset + j] % 929) % 929;
}
}
infoSpace(this.codeWords[i]);
}
infoLine();
 
for (j = 0; j < k; j++) {
if (rsCodeWords[j] != 0) {
rsCodeWords[j] = 929 - rsCodeWords[j];
}
}
 
for (i = k - 1; i >= 0; i--) {
this.codeWords[cwCnt] = rsCodeWords[i];
cwCnt++;
}
 
/* Place data into table */
LeftRAPStart = A_RAP_TABLE[variant];
CentreRAPStart = A_RAP_TABLE[variant + 17];
RightRAPStart = A_RAP_TABLE[variant + 34];
StartCluster = A_RAP_TABLE[variant + 51] / 3;
 
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.row_count = rows;
this.pattern = new String[this.row_count];
this.row_height = new int[this.row_count];
 
for (i = 0; i < rows; i++) {
codebarre = "";
offset = 929 * Cluster;
for (j = 0; j < 5; j++) {
dummy[j] = 0;
}
for (j = 0; j < this.cc_width; j++) {
dummy[j + 1] = this.codeWords[i * this.cc_width + j];
}
/* Copy the data into codebarre */
codebarre += RAPLR[LeftRAP];
codebarre += "1";
codebarre += CODAGEMC[offset + dummy[1]];
codebarre += "1";
if (this.cc_width == 3) {
codebarre += RAPC[CentreRAP];
}
if (this.cc_width >= 2) {
codebarre += "1";
codebarre += CODAGEMC[offset + dummy[2]];
codebarre += "1";
}
if (this.cc_width == 4) {
codebarre += RAPC[CentreRAP];
}
if (this.cc_width >= 3) {
codebarre += "1";
codebarre += CODAGEMC[offset + dummy[3]];
codebarre += "1";
}
if (this.cc_width == 4) {
codebarre += "1";
codebarre += CODAGEMC[offset + dummy[4]];
codebarre += "1";
}
codebarre += RAPLR[RightRAP];
codebarre += "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 < codebarre.charAt(loop) - '0'; 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)]);
}
}
 
this.row_height[i] = 2;
this.pattern[i] = bin2pat(bin);
 
/* 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;
}
}
}
 
/* initialize pwr928 encoding table */
private void init928() {
int i, j, v;
final int[] cw = new int[7];
cw[6] = 1;
for (i = 5; i >= 0; i--) {
cw[i] = 0;
}
 
for (i = 0; i < 7; i++) {
this.pwr928[0][i] = cw[i];
}
for (j = 1; j < 69; j++) {
for (v = 0, i = 6; i >= 1; i--) {
v = 2 * cw[i] + v / 928;
this.pwr928[j][i] = cw[i] = v % 928;
}
this.pwr928[j][0] = cw[0] = 2 * cw[0] + v / 928;
}
}
 
/* converts bit string to base 928 values, codeWords[0] is highest order */
private int encode928(final int bitLng) {
int i, j, b, bitCnt, cwNdx, cwCnt, cwLng;
for (cwNdx = cwLng = b = 0; b < bitLng; b += 69, cwNdx += 7) {
bitCnt = min(bitLng - b, 69);
cwLng += cwCnt = bitCnt / 10 + 1;
for (i = 0; i < cwCnt; i++) {
this.codeWords[cwNdx + i] = 0; /* init 0 */
}
for (i = 0; i < bitCnt; i++) {
if (getBit(b + bitCnt - i - 1)) {
for (j = 0; j < cwCnt; j++) {
this.codeWords[cwNdx + j] += this.pwr928[i][j + 7 - cwCnt];
}
}
}
for (i = cwCnt - 1; i > 0; i--) {
/* add "carries" */
this.codeWords[cwNdx + i - 1] += this.codeWords[cwNdx + i] / 928;
this.codeWords[cwNdx + i] %= 928;
}
}
return cwLng;
}
 
private int min(final int first, final int second) {
if (first <= second) {
return first;
} else {
return second;
}
}
 
/* gets bit in bitString at bitPos */
private boolean getBit(final int arg) {
if ((this.bitStr[arg >> 4] & 0x8000 >> (arg & 15)) != 0) {
return true;
} else {
return false;
}
}
 
private void cc_b() {
/* CC-B 2D component */
int length, i, binloc;
int k, j, longueur, offset;
final int[] mccorrection = new int[50];
int total;
final int[] dummy = new int[5];
String codebarre;
final StringBuilder bin = new StringBuilder();
int variant, LeftRAPStart, CentreRAPStart, RightRAPStart, StartCluster;
int LeftRAP, CentreRAP, RightRAP, Cluster, flip, loop;
int option_2, rows;
this.inputData = new int[this.binary_string.length() / 8 + 3];
 
length = this.binary_string.length() / 8;
 
for (i = 0; i < length; i++) {
binloc = i * 8;
 
this.inputData[i] = 0;
for (j = 0; j < 8; j++) {
if (this.binary_string.charAt(binloc + j) == '1') {
this.inputData[i] += 0x80 >> j;
}
}
}
 
this.codeWordCount = 0;
 
/*
* "the CC-B component shall have codeword 920 in the first symbol character position"
* (section 9a)
*/
this.codeWords[this.codeWordCount] = 920;
this.codeWordCount++;
 
byteprocess(0, length);
 
/* Now figure out which variant of the symbol to use and load values accordingly */
 
variant = 0;
 
if (this.cc_width == 2) {
variant = 13;
if (this.codeWordCount <= 33) {
variant = 12;
}
if (this.codeWordCount <= 29) {
variant = 11;
}
if (this.codeWordCount <= 24) {
variant = 10;
}
if (this.codeWordCount <= 19) {
variant = 9;
}
if (this.codeWordCount <= 13) {
variant = 8;
}
if (this.codeWordCount <= 8) {
variant = 7;
}
}
 
if (this.cc_width == 3) {
variant = 23;
if (this.codeWordCount <= 70) {
variant = 22;
}
if (this.codeWordCount <= 58) {
variant = 21;
}
if (this.codeWordCount <= 46) {
variant = 20;
}
if (this.codeWordCount <= 34) {
variant = 19;
}
if (this.codeWordCount <= 24) {
variant = 18;
}
if (this.codeWordCount <= 18) {
variant = 17;
}
if (this.codeWordCount <= 14) {
variant = 16;
}
if (this.codeWordCount <= 10) {
variant = 15;
}
if (this.codeWordCount <= 6) {
variant = 14;
}
}
 
if (this.cc_width == 4) {
variant = 34;
if (this.codeWordCount <= 108) {
variant = 33;
}
if (this.codeWordCount <= 90) {
variant = 32;
}
if (this.codeWordCount <= 72) {
variant = 31;
}
if (this.codeWordCount <= 54) {
variant = 30;
}
if (this.codeWordCount <= 39) {
variant = 29;
}
if (this.codeWordCount <= 30) {
variant = 28;
}
if (this.codeWordCount <= 24) {
variant = 27;
}
if (this.codeWordCount <= 18) {
variant = 26;
}
if (this.codeWordCount <= 12) {
variant = 25;
}
if (this.codeWordCount <= 8) {
variant = 24;
}
}
 
/*
* Now we have the variant we can load the data - from here on the same as MicroPDF417 code
*/
variant--;
option_2 = MICRO_VARIANTS[variant]; /* columns */
rows = MICRO_VARIANTS[variant + 34]; /* rows */
k = MICRO_VARIANTS[variant + 68]; /* number of EC CWs */
longueur = option_2 * rows - k; /* number of non-EC CWs */
i = longueur - this.codeWordCount; /* amount of padding required */
offset = MICRO_VARIANTS[variant + 102]; /* coefficient offset */
 
/* We add the padding */
while (i > 0) {
this.codeWords[this.codeWordCount] = 900;
this.codeWordCount++;
i--;
}
 
/* Reed-Solomon error correction */
longueur = this.codeWordCount;
for (loop = 0; loop < 50; loop++) {
mccorrection[loop] = 0;
}
total = 0;
info("Composite Codewords: ");
for (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 * MICROCOEFFS[offset + j] % 929) % 929;
} else {
mccorrection[j] = (mccorrection[j - 1] + 929 - total * MICROCOEFFS[offset + j] % 929) % 929;
}
}
infoSpace(this.codeWords[i]);
}
infoLine();
 
for (j = 0; j < k; j++) {
if (mccorrection[j] != 0) {
mccorrection[j] = 929 - mccorrection[j];
}
}
/* we add these codes to the string */
for (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.row_count = rows;
this.pattern = new String[this.row_count];
this.row_height = new int[this.row_count];
 
for (i = 0; i < rows; i++) {
codebarre = "";
offset = 929 * Cluster;
for (j = 0; j < 5; j++) {
dummy[j] = 0;
}
for (j = 0; j < option_2; j++) {
dummy[j + 1] = this.codeWords[i * option_2 + j];
}
/* Copy the data into codebarre */
codebarre += RAPLR[LeftRAP];
codebarre += "1";
codebarre += CODAGEMC[offset + dummy[1]];
codebarre += "1";
if (this.cc_width == 3) {
codebarre += RAPC[CentreRAP];
}
if (this.cc_width >= 2) {
codebarre += "1";
codebarre += CODAGEMC[offset + dummy[2]];
codebarre += "1";
}
if (this.cc_width == 4) {
codebarre += RAPC[CentreRAP];
}
if (this.cc_width >= 3) {
codebarre += "1";
codebarre += CODAGEMC[offset + dummy[3]];
codebarre += "1";
}
if (this.cc_width == 4) {
codebarre += "1";
codebarre += CODAGEMC[offset + dummy[4]];
codebarre += "1";
}
codebarre += RAPLR[RightRAP];
codebarre += "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 < codebarre.charAt(loop) - '0'; 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)]);
}
}
 
this.pattern[i] = bin2pat(bin);
this.row_height[i] = 2;
 
/* 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 cc_c() {
/* CC-C 2D component - byte compressed PDF417 */
int length, i, binloc, k;
int offset, longueur, loop, total, j;
final int[] mccorrection = new int[520];
int c1, c2, c3;
final int[] dummy = new int[35];
String codebarre;
final StringBuilder bin = new StringBuilder();
this.inputData = new int[this.binary_string.length() / 8 + 4];
 
length = this.binary_string.length() / 8;
 
for (i = 0; i < length; i++) {
binloc = i * 8;
this.inputData[i] = 0;
for (j = 0; j < 8; j++) {
if (this.binary_string.charAt(binloc + j) == '1') {
this.inputData[i] += 0x80 >> j;
}
}
}
 
this.codeWordCount = 0;
this.codeWords[this.codeWordCount] = 0; /* space for length descriptor */
this.codeWordCount++;
this.codeWords[this.codeWordCount] = 920; /* CC-C identifier */
this.codeWordCount++;
 
byteprocess(0, length);
 
this.codeWords[0] = this.codeWordCount;
 
k = 1;
for (i = 1; i <= this.ecc + 1; i++) {
k *= 2;
}
 
/* 796 - we now take care of the Reed Solomon codes */
switch (this.ecc) {
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;
}
 
longueur = this.codeWordCount;
for (loop = 0; loop < 520; loop++) {
mccorrection[loop] = 0;
}
total = 0;
info("Composite Codewords: ");
for (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 * COEFRS[offset + j] % 929) % 929;
} else {
mccorrection[j] = (mccorrection[j - 1] + 929 - total * COEFRS[offset + j] % 929) % 929;
}
}
infoSpace(this.codeWords[i]);
}
infoLine();
 
for (j = 0; j < k; j++) {
if (mccorrection[j] != 0) {
mccorrection[j] = 929 - mccorrection[j];
}
}
/* we add these codes to the string */
for (i = k - 1; i >= 0; i--) {
this.codeWords[this.codeWordCount] = mccorrection[i];
this.codeWordCount++;
}
 
/* 818 - The CW string is finished */
c1 = (this.codeWordCount / this.cc_width - 1) / 3;
c2 = this.ecc * 3 + (this.codeWordCount / this.cc_width - 1) % 3;
c3 = this.cc_width - 1;
 
this.readable = "";
this.row_count = this.codeWordCount / this.cc_width;
this.pattern = new String[this.row_count];
this.row_height = new int[this.row_count];
 
/* we now encode each row */
for (i = 0; i <= this.codeWordCount / this.cc_width - 1; i++) {
for (j = 0; j < this.cc_width; j++) {
dummy[j + 1] = this.codeWords[i * this.cc_width + j];
}
k = i / 3 * 30;
switch (i % 3) {
/*
* follows this pattern from US Patent 5,243,655: Row 0: L0 (row #, # of rows) R0 (row
* #, # of columns) Row 1: L1 (row #, security level) R1 (row #, # of rows) Row 2: L2
* (row #, # of columns) R2 (row #, security level) Row 3: L3 (row #, # of rows) R3 (row
* #, # of columns) etc.
*/
case 0:
dummy[0] = k + c1;
dummy[this.cc_width + 1] = k + c3;
break;
case 1:
dummy[0] = k + c2;
dummy[this.cc_width + 1] = k + c1;
break;
case 2:
dummy[0] = k + c3;
dummy[this.cc_width + 1] = k + c2;
break;
}
codebarre = "+*"; /* Start with a start char and a separator */
 
for (j = 0; j <= this.cc_width + 1; j++) {
switch (i % 3) {
case 1:
offset = 929;
/* cluster(3) */ break;
case 2:
offset = 1858;
/* cluster(6) */ break;
default:
offset = 0;
/* cluster(0) */ break;
}
codebarre += CODAGEMC[offset + dummy[j]];
codebarre += "*";
}
codebarre += "-";
 
bin.setLength(0);
for (loop = 0; loop < codebarre.length(); loop++) {
bin.append(PDF_TTF[positionOf(codebarre.charAt(loop), BR_SET)]);
}
this.pattern[i] = bin2pat(bin);
this.row_height[i] = 3;
}
}
 
private void byteprocess(int start, final int length) {
int len = 0;
int chunkLen = 0;
BigInteger mantisa;
BigInteger total;
BigInteger word;
 
/* select the switch for multiple of 6 bytes */
if (this.binary_string.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++];
}
}
}
}
}
/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/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/Code128.java
New file
0,0 → 1,865
/*
* 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 java.nio.charset.StandardCharsets.ISO_8859_1;
 
/**
* <p>
* Implements Code 128 bar code symbology according to ISO/IEC 15417:2007.
*
* <p>
* Code 128 supports encoding of 8-bit ISO 8859-1 (Latin-1) characters.
*
* <p>
* Setting GS1 mode allows encoding in GS1-128 (also known as UCC/EAN-128).
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
* @author Daniel Gredler
*/
public class Code128 extends Symbol {
 
private enum Mode {
NULL, SHIFTA, LATCHA, SHIFTB, LATCHB, SHIFTC, LATCHC, AORB, ABORC
}
 
private enum FMode {
SHIFTN, LATCHN, SHIFTF, LATCHF
}
 
private enum Composite {
OFF, CCA, CCB, CCC
}
 
protected static final String[] CODE128_TABLE = { "212222", "222122", "222221", "121223", "121322", "131222", "122213", "122312", "132212", "221213", "221312", "231212", "112232", "122132",
"122231", "113222", "123122", "123221", "223211", "221132", "221231", "213212", "223112", "312131", "311222", "321122", "321221", "312212", "322112", "322211", "212123", "212321",
"232121", "111323", "131123", "131321", "112313", "132113", "132311", "211313", "231113", "231311", "112133", "112331", "132131", "113123", "113321", "133121", "313121", "211331",
"231131", "213113", "213311", "213131", "311123", "311321", "331121", "312113", "312311", "332111", "314111", "221411", "431111", "111224", "111422", "121124", "121421", "141122",
"141221", "112214", "112412", "122114", "122411", "142112", "142211", "241211", "221114", "413111", "241112", "134111", "111242", "121142", "121241", "114212", "124112", "124211",
"411212", "421112", "421211", "212141", "214121", "412121", "111143", "111341", "131141", "114113", "114311", "411113", "411311", "113141", "114131", "311141", "411131", "211412",
"211214", "211232", "2331112" };
 
private boolean suppressModeC = false;
private Composite compositeMode = Composite.OFF;
 
/**
* Optionally prevents this symbol from using subset mode C for numeric data compression.
*
* @param suppressModeC whether or not to prevent this symbol from using subset mode C
*/
public void setSuppressModeC(final boolean suppressModeC) {
this.suppressModeC = suppressModeC;
}
 
/**
* Returns whether or not this symbol is prevented from using subset mode C for numeric data
* compression.
*
* @return whether or not this symbol is prevented from using subset mode C for numeric data
* compression
*/
public boolean getSuppressModeC() {
return this.suppressModeC;
}
 
protected void setCca() {
this.compositeMode = Composite.CCA;
}
 
protected void setCcb() {
this.compositeMode = Composite.CCB;
}
 
protected void setCcc() {
this.compositeMode = Composite.CCC;
}
 
public void unsetCc() {
this.compositeMode = Composite.OFF;
}
 
@Override
protected boolean gs1Supported() {
return true;
}
 
@Override
protected void encode() {
int i, j, k;
final int input_point = 0;
Mode mode, last_mode;
Mode last_set, current_set;
double glyph_count;
int bar_characters = 0, total_sum = 0;
FMode f_state = FMode.LATCHN;
final Mode[] mode_type = new Mode[200];
final int[] mode_length = new int[200];
final int[] values = new int[200];
int c;
int linkage_flag = 0;
int index_point = 0;
int read = 0;
 
this.inputData = toBytes(this.content, ISO_8859_1);
if (this.inputData == null) {
throw new OkapiException("Invalid characters in input data");
}
 
final int sourcelen = this.inputData.length;
 
final FMode[] fset = new FMode[200];
final Mode[] set = new Mode[200]; /* set[] = Calculated mode for each character */
 
if (sourcelen > 170) {
throw new OkapiException("Input data too long");
}
 
/* Detect extended ASCII characters */
for (i = 0; i < sourcelen; i++) {
final int ch = this.inputData[i];
if (ch >= 128 && ch != FNC1 && ch != FNC2 && ch != FNC3 && ch != FNC4) {
fset[i] = FMode.SHIFTF;
} else {
fset[i] = FMode.LATCHN;
}
}
 
/* Decide when to latch to extended mode - Annex E note 3 */
j = 0;
for (i = 0; i < sourcelen; i++) {
if (fset[i] == FMode.SHIFTF) {
j++;
} else {
j = 0;
}
if (j >= 5) {
for (k = i; k > i - 5; k--) {
fset[k] = FMode.LATCHF;
}
}
if (j >= 3 && i == sourcelen - 1) {
for (k = i; k > i - 3; k--) {
fset[k] = FMode.LATCHF;
}
}
}
 
/*
* Decide if it is worth reverting to 646 encodation for a few characters as described in
* 4.3.4.2 (d)
*/
for (i = 1; i < sourcelen; i++) {
if (fset[i - 1] == FMode.LATCHF && fset[i] == FMode.LATCHN) {
/* Detected a change from 8859-1 to 646 - count how long for */
for (j = 0; fset[i + j] == FMode.LATCHN && i + j < sourcelen; j++) {
;
}
if (j < 5 || j < 3 && i + j == sourcelen - 1) {
/* Uses the same figures recommended by Annex E note 3 */
/* Change to shifting back rather than latching back */
for (k = 0; k < j; k++) {
fset[i + k] = FMode.SHIFTN;
}
}
}
}
 
/* Decide on mode using same system as PDF417 and rules of ISO 15417 Annex E */
int letter = this.inputData[input_point];
int numbers = letter >= '