OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Compare Revisions

Ignore whitespace Rev 180 → Rev 181

/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/StringProcessor.java
29,8 → 29,8
private JRadioButton bLower;
private JRadioButton bUpper;
 
public StringProcessor(SQLField field) {
this.field = field;
public StringProcessor(BatchField field) {
this.field = field.getField();
 
this.setLayout(new GridBagLayout());
bReplace = new JRadioButton("remplacer par");
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/BatchEditorPanel.java
24,10 → 24,13
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
 
import org.openconcerto.sql.Configuration;
import org.openconcerto.sql.PropsConfiguration;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowListRSH;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.request.SQLFieldTranslator;
import org.openconcerto.ui.DefaultGridBagConstraints;
import org.openconcerto.ui.JLabelBold;
36,24 → 39,34
 
public class BatchEditorPanel extends JPanel {
 
public BatchEditorPanel(final List<SQLRowValues> rows, FieldFilter filter) {
Configuration conf = PropsConfiguration.getInstance();
final SQLFieldTranslator translator = conf.getTranslator();
public BatchEditorPanel(final SQLElementDirectory dir, final List<SQLRowValues> rows, FieldFilter filter) {
SQLFieldTranslator translator = dir.getTranslator();
Set<SQLField> fields = rows.get(0).getTable().getFields();
List<SQLField> f = new ArrayList<SQLField>();
List<BatchField> f = new ArrayList<BatchField>();
for (SQLField sqlField : fields) {
if (ForbiddenFieldName.isAllowed(sqlField.getName()) && translator.getLabelFor(sqlField) != null) {
if (filter == null || !filter.isFiltered(sqlField)) {
f.add(sqlField);
f.add(new BatchField(dir, sqlField, null));
}
}
}
SQLTable tableTarif = rows.get(0).getTable().getTable("TARIF");
SQLTable tableArticleTarif = rows.get(0).getTable().getTable("ARTICLE_TARIF");
SQLSelect sel = new SQLSelect();
sel.addSelectStar(tableTarif);
List<SQLRow> rowTarif = SQLRowListRSH.execute(sel);
for (SQLRow sqlRow : rowTarif) {
f.add(new BatchField(dir, tableArticleTarif.getField("PV_HT"), sqlRow));
if (tableArticleTarif.contains("POURCENT_REMISE")) {
f.add(new BatchField(dir, tableArticleTarif.getField("POURCENT_REMISE"), sqlRow));
}
}
 
Collections.sort(f, new Comparator<SQLField>() {
Collections.sort(f, new Comparator<BatchField>() {
 
@Override
public int compare(SQLField o1, SQLField o2) {
return translator.getLabelFor(o1).compareToIgnoreCase(translator.getLabelFor(o2));
public int compare(BatchField o1, BatchField o2) {
return o1.getComboName().compareToIgnoreCase(o2.getComboName());
}
});
this.setLayout(new GridBagLayout());
60,11 → 73,11
GridBagConstraints c = new DefaultGridBagConstraints();
this.add(new JLabel("Champ"), c);
 
final JComboBox<SQLField> combo = new JComboBox<SQLField>(f.toArray(new SQLField[1]));
final JComboBox<BatchField> combo = new JComboBox<BatchField>(f.toArray(new BatchField[1]));
combo.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
String label = translator.getLabelFor(((SQLField) value));
String label = ((BatchField) value).getComboName();
return super.getListCellRendererComponent(list, label, index, isSelected, cellHasFocus);
}
});
86,7 → 99,7
c.gridy++;
c.anchor = GridBagConstraints.NORTHWEST;
final BatchDetailPanel comp = new BatchDetailPanel();
comp.setField((SQLField) combo.getSelectedItem());
comp.setField((BatchField) combo.getSelectedItem());
this.add(comp, c);
 
JPanel actions = new JPanel();
108,7 → 121,7
 
@Override
public void itemStateChanged(ItemEvent e) {
comp.setField((SQLField) combo.getSelectedItem());
comp.setField((BatchField) combo.getSelectedItem());
 
}
});
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/DateProcessor.java
17,8 → 17,8
private final JDate d = new JDate(true);
private final SQLField field;
 
public DateProcessor(SQLField field) {
this.field = field;
public DateProcessor(BatchField field) {
this.field = field.getField();
this.setLayout(new FlowLayout());
this.add(new JLabel("forcer la date au "));
this.add(d);
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/BooleanProcessor.java
13,13 → 13,13
import org.openconcerto.ui.VFlowLayout;
 
public class BooleanProcessor extends JPanel implements BatchProcessor {
private final SQLField field;
private final BatchField field;
 
private JRadioButton bTrue;
private JRadioButton bFalse;
private JRadioButton bInvert;
 
public BooleanProcessor(SQLField field) {
public BooleanProcessor(BatchField field) {
this.field = field;
this.setLayout(new VFlowLayout());
bTrue = new JRadioButton("forcer à vrai");
41,7 → 41,7
if (bTrue.isSelected()) {
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
rowValues.put(field.getName(), Boolean.TRUE);
rowValues.put(field.getField().getName(), Boolean.TRUE);
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
48,7 → 48,7
} else if (bFalse.isSelected()) {
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
rowValues.put(field.getName(), Boolean.FALSE);
rowValues.put(field.getField().getName(), Boolean.FALSE);
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
55,9 → 55,9
} else if (bInvert.isSelected()) {
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
final Boolean boolean1 = sqlRowAccessor.asRow().getBoolean(field.getName());
final Boolean boolean1 = sqlRowAccessor.asRow().getBoolean(field.getField().getName());
if (boolean1 != null) {
rowValues.put(field.getName(), boolean1.equals(Boolean.FALSE));
rowValues.put(field.getField().getName(), boolean1.equals(Boolean.FALSE));
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/BatchDetailPanel.java
22,46 → 22,47
this.setLayout(new VFlowLayout());
}
 
public void setField(SQLField field) {
public void setField(BatchField batchField) {
this.removeAll();
 
SQLField field = batchField.getField();
final SQLType type = field.getType();
final Class<?> javaType = type.getJavaType();
final String fName = field.getName();
if (fName.equals("PV_TTC")) {
final NumberProcessor p = new TTCProcessor(field);
final NumberProcessor p = new TTCProcessor(batchField);
this.add(p);
this.processor = p;
} else if (fName.equals("PV_HT")) {
final NumberProcessor p = new HTProcessor(field);
final NumberProcessor p = new HTProcessor(batchField);
this.add(p);
this.processor = p;
} else if (fName.equals("ID_TAXE")) {
final ReferenceProcessor p = new TVAProcessor(field);
final ReferenceProcessor p = new TVAProcessor(batchField);
this.add(p);
this.processor = p;
} else if (fName.equals("PA_HT")) {
final NumberProcessor p = new PurchaseProcessor(field);
final NumberProcessor p = new PurchaseProcessor(batchField);
this.add(p);
this.processor = p;
} else if (javaType.equals(Boolean.class)) {
final BooleanProcessor p = new BooleanProcessor(field);
final BooleanProcessor p = new BooleanProcessor(batchField);
this.add(p);
this.processor = p;
} else if (field.isKey()) {
final ReferenceProcessor p = new ReferenceProcessor(field);
final ReferenceProcessor p = new ReferenceProcessor(batchField);
this.add(p);
this.processor = p;
} else if (javaType.equals(String.class)) {
final StringProcessor p = new StringProcessor(field);
final StringProcessor p = new StringProcessor(batchField);
this.add(p);
this.processor = p;
} else if (javaType.equals(Date.class)) {
final DateProcessor p = new DateProcessor(field);
final DateProcessor p = new DateProcessor(batchField);
this.add(p);
this.processor = p;
} else if (javaType.equals(BigDecimal.class) || javaType.equals(Float.class) || javaType.equals(Double.class) || javaType.equals(Integer.class) || javaType.equals(Long.class)) {
final NumberProcessor p = new NumberProcessor(field);
final NumberProcessor p = new NumberProcessor(batchField);
this.add(p);
this.processor = p;
}
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/BatchField.java
New file
0,0 → 1,58
package org.openconcerto.modules.common.batchprocessing;
 
import java.util.List;
 
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowListRSH;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.request.SQLFieldTranslator;
 
public class BatchField {
 
private final SQLField field;
private final SQLRowAccessor foreignLinkRow;
private final SQLFieldTranslator translator;
private final SQLElement elementLink;
 
public BatchField(SQLElementDirectory dir, SQLField field, SQLRowAccessor foreignLinkRow) {
this.field = field;
this.foreignLinkRow = foreignLinkRow;
 
this.translator = dir.getTranslator();
if (foreignLinkRow == null) {
this.elementLink = null;
} else {
this.elementLink = dir.getElement(foreignLinkRow.getTable());
}
}
 
public SQLField getField() {
return field;
}
 
public SQLRowAccessor getForeignLinkRow() {
return foreignLinkRow;
}
 
public String getComboName() {
if (this.foreignLinkRow == null) {
return this.translator.getLabelFor(this.field);
} else {
return this.elementLink.getPluralName() + " " + this.foreignLinkRow.getString("NOM") + " " + this.translator.getLabelFor(this.field);
}
}
 
public List<SQLRow> getReferentRows(SQLRowAccessor rowOrigin) {
SQLSelect sel = new SQLSelect();
sel.addSelectStar(this.field.getTable());
final Where w = new Where(this.field.getTable().getField("ID_" + rowOrigin.getTable().getName()), "=", rowOrigin.getID());
sel.setWhere(w.and(new Where(this.field.getTable().getField("ID_" + foreignLinkRow.getTable().getName()), "=", foreignLinkRow.getID())));
return SQLRowListRSH.execute(sel);
}
 
}
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/Module.java
2,7 → 2,6
 
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.List;
 
9,18 → 8,12
import javax.swing.AbstractAction;
import javax.swing.JFrame;
 
import org.openconcerto.erp.config.Gestion;
import org.openconcerto.erp.modules.AbstractModule;
import org.openconcerto.erp.modules.ComponentsContext;
import org.openconcerto.erp.modules.ModuleFactory;
import org.openconcerto.erp.modules.ModuleManager;
import org.openconcerto.erp.modules.ModulePackager;
import org.openconcerto.erp.modules.RuntimeModuleFactory;
import org.openconcerto.sql.element.SQLElement;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRequestLog;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.ui.ConnexionPanel;
import org.openconcerto.sql.view.list.IListe;
import org.openconcerto.sql.view.list.IListeAction.IListeEvent;
import org.openconcerto.sql.view.list.RowAction;
33,7 → 26,7
}
 
@Override
protected void setupComponents(ComponentsContext ctxt) {
protected void setupComponents(final ComponentsContext ctxt) {
 
super.setupComponents(ctxt);
final SQLElement element = ctxt.getElement("ARTICLE");
60,7 → 53,7
 
};
 
f.setContentPane(new BatchEditorPanel(rows, filter));
f.setContentPane(new BatchEditorPanel(ctxt.getElement("ARTICLE").getDirectory(), rows, filter));
f.pack();
f.setMinimumSize(new Dimension(400, 300));
f.setLocationRelativeTo(IListe.get(e));
/trunk/Modules/Module Batch Processing/src/org/openconcerto/modules/common/batchprocessing/NumberProcessor.java
13,7 → 13,7
import javax.swing.JRadioButton;
import javax.swing.JTextField;
 
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.ui.DefaultGridBagConstraints;
20,7 → 20,7
 
public class NumberProcessor extends JPanel implements BatchProcessor {
 
private final SQLField field;
private final BatchField batchfield;
// Editors
final JTextField tReplace = new JTextField();
private JRadioButton bReplace;
30,8 → 30,8
final JTextField tRemove = new JTextField();
private JRadioButton bRemove;
 
public NumberProcessor(SQLField field) {
this.field = field;
public NumberProcessor(BatchField field) {
this.batchfield = field;
 
this.setLayout(new GridBagLayout());
bReplace = new JRadioButton("remplacer par");
110,10 → 110,22
if (bReplace.isSelected()) {
BigDecimal v = new BigDecimal(this.tReplace.getText().trim());
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
rowValues.put(field.getName(), decimalToFieldType(v));
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
 
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(batchfield.getField().getName(), decimalToFieldType(v));
processBeforeUpdate(sqlRowAccessor, rowValues);
rowValues.update();
}
}
} else if (bAdd.isSelected()) {
 
127,16 → 139,42
BigDecimal v = new BigDecimal(t);
 
for (SQLRowAccessor sqlRowAccessor : r) {
final SQLRowValues rowValues = sqlRowAccessor.createEmptyUpdateRow();
final BigDecimal value = sqlRowAccessor.asRow().getBigDecimal(field.getName());
if (value != null) {
if (isPercent) {
rowValues.put(field.getName(), decimalToFieldType(value.multiply(v.divide(new BigDecimal(100)).add(BigDecimal.ONE))));
} else {
rowValues.put(field.getName(), decimalToFieldType(value.add(v)));
 
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(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(sqlRowT, rowValues);
rowValues.update();
}
}
processBeforeUpdate(sqlRowAccessor, 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()) {
149,17 → 187,38
 
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());
if (value != null) {
if (isPercent) {
rowValues.put(field.getName(), decimalToFieldType(value.multiply(v.divide(new BigDecimal(-100)).add(BigDecimal.ONE))));
} else {
rowValues.put(field.getName(), decimalToFieldType(value.add(v)));
SQLRowValues rowValues = sqlRowT.createEmptyUpdateRow();
final BigDecimal value = sqlRowT.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(sqlRowT, rowValues);
rowValues.update();
}
}
}
processBeforeUpdate(sqlRowAccessor, 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();
}
}
}
}
166,7 → 225,7
}
 
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 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/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/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/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/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/backend/AztecRune.java
New file
0,0 → 1,160
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
/**
* <p>
* Implements the Aztec Runes bar code symbology according to ISO/IEC 24778:2008 Annex A.
*
* <p>
* Aztec Runes is a fixed-size matrix symbology which can encode whole integer values between 0 and
* 255.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class AztecRune extends Symbol {
 
private static final int[] BIT_PLACEMENT_MAP = { 1, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 29, 1, 0, 0, 0, 0, 0, 0, 0, 1, 9, 28, 1, 0, 1, 1, 1, 1, 1, 0, 1, 10, 27, 1, 0, 1,
0, 0, 0, 1, 0, 1, 11, 26, 1, 0, 1, 0, 1, 0, 1, 0, 1, 12, 25, 1, 0, 1, 0, 0, 0, 1, 0, 1, 13, 24, 1, 0, 1, 1, 1, 1, 1, 0, 1, 14, 23, 1, 0, 0, 0, 0, 0, 0, 0, 1, 15, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 0, 22, 21, 20, 19, 18, 17, 16, 0, 0 };
 
@Override
protected void encode() {
 
if (!this.content.matches("[0-9]+")) {
throw new OkapiException("Invalid input data");
}
 
int decimalValue = 0;
switch (this.content.length()) {
case 1:
decimalValue = this.content.charAt(0) - '0';
break;
case 2:
decimalValue = 10 * (this.content.charAt(0) - '0');
decimalValue += this.content.charAt(1) - '0';
break;
case 3:
decimalValue = 100 * (this.content.charAt(0) - '0');
decimalValue += 10 * (this.content.charAt(1) - '0');
decimalValue += this.content.charAt(2) - '0';
break;
default:
throw new OkapiException("Input too large");
}
 
if (decimalValue > 255) {
throw new OkapiException("Input too large");
}
 
final StringBuilder binaryDataStream = new StringBuilder(28);
for (int i = 0x80; i > 0; i = i >> 1) {
if ((decimalValue & i) != 0) {
binaryDataStream.append('1');
} else {
binaryDataStream.append('0');
}
}
 
final int[] dataCodeword = new int[3];
dataCodeword[0] = 0;
dataCodeword[1] = 0;
 
for (int i = 0; i < 2; i++) {
if (binaryDataStream.charAt(i * 4) == '1') {
dataCodeword[i] += 8;
}
if (binaryDataStream.charAt(i * 4 + 1) == '1') {
dataCodeword[i] += 4;
}
if (binaryDataStream.charAt(i * 4 + 2) == '1') {
dataCodeword[i] += 2;
}
if (binaryDataStream.charAt(i * 4 + 3) == '1') {
dataCodeword[i] += 1;
}
}
 
final int[] errorCorrectionCodeword = new int[6];
 
final ReedSolomon rs = new ReedSolomon();
rs.init_gf(0x13);
rs.init_code(5, 1);
rs.encode(2, dataCodeword);
 
for (int i = 0; i < 5; i++) {
errorCorrectionCodeword[i] = rs.getResult(i);
}
 
for (int i = 0; i < 5; i++) {
if ((errorCorrectionCodeword[4 - i] & 0x08) != 0) {
binaryDataStream.append('1');
} else {
binaryDataStream.append('0');
}
if ((errorCorrectionCodeword[4 - i] & 0x04) != 0) {
binaryDataStream.append('1');
} else {
binaryDataStream.append('0');
}
if ((errorCorrectionCodeword[4 - i] & 0x02) != 0) {
binaryDataStream.append('1');
} else {
binaryDataStream.append('0');
}
if ((errorCorrectionCodeword[4 - i] & 0x01) != 0) {
binaryDataStream.append('1');
} else {
binaryDataStream.append('0');
}
}
 
final StringBuilder reversedBinaryDataStream = new StringBuilder(28);
for (int i = 0; i < binaryDataStream.length(); i++) {
if ((i & 1) == 0) {
if (binaryDataStream.charAt(i) == '0') {
reversedBinaryDataStream.append('1');
} else {
reversedBinaryDataStream.append('0');
}
} else {
reversedBinaryDataStream.append(binaryDataStream.charAt(i));
}
}
 
infoLine("Binary: " + reversedBinaryDataStream);
 
this.readable = "";
this.pattern = new String[11];
this.row_count = 11;
this.row_height = new int[11];
 
for (int row = 0; row < 11; row++) {
final StringBuilder rowBinary = new StringBuilder(11);
for (int column = 0; column < 11; column++) {
if (BIT_PLACEMENT_MAP[row * 11 + column] == 1) {
rowBinary.append('1');
}
if (BIT_PLACEMENT_MAP[row * 11 + column] == 0) {
rowBinary.append('0');
}
if (BIT_PLACEMENT_MAP[row * 11 + column] >= 2) {
rowBinary.append(reversedBinaryDataStream.charAt(BIT_PLACEMENT_MAP[row * 11 + column] - 2));
}
}
this.pattern[row] = bin2pat(rowBinary);
this.row_height[row] = 1;
}
}
}
/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/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 >= '0' && letter <= '9' ? 1 : 0;
mode = findSubset(letter, numbers);
mode_type[0] = mode;
mode_length[0] += length(letter, mode);
for (i = 1; i < sourcelen; i++) {
letter = this.inputData[i];
last_mode = mode;
mode = findSubset(letter, numbers);
if (mode == last_mode) {
mode_length[index_point] += length(letter, mode);
} else {
index_point++;
mode_type[index_point] = mode;
mode_length[index_point] = length(letter, mode);
}
if (letter >= '0' && letter <= '9') {
numbers++;
} else {
numbers = 0;
}
}
index_point++;
index_point = reduceSubsetChanges(mode_type, mode_length, index_point);
 
/* Put set data into set[] */
read = 0;
for (i = 0; i < index_point; i++) {
for (j = 0; j < mode_length[i]; j++) {
set[read] = mode_type[i];
read++;
}
}
 
/* Resolve odd length LATCHC blocks */
int cs = 0, nums = 0, fncs = 0;
for (i = 0; i < read; i++) {
if (set[i] == Mode.LATCHC) {
cs++;
if (this.inputData[i] >= '0' && this.inputData[i] <= '9') {
nums++;
} else if (this.inputData[i] == FNC1) {
fncs++;
}
} else {
resolveOddCs(set, i, cs, nums, fncs);
cs = 0;
nums = 0;
fncs = 0;
}
}
resolveOddCs(set, i, cs, nums, fncs);
 
/* Adjust for strings which start with shift characters - make them latch instead */
if (set[0] == Mode.SHIFTA) {
i = 0;
do {
set[i] = Mode.LATCHA;
i++;
} while (set[i] == Mode.SHIFTA);
}
if (set[0] == Mode.SHIFTB) {
i = 0;
do {
set[i] = Mode.LATCHB;
i++;
} while (set[i] == Mode.SHIFTB);
}
 
/*
* Now we can calculate how long the barcode is going to be - and stop it from being too
* long
*/
last_set = Mode.NULL;
glyph_count = 0.0;
for (i = 0; i < sourcelen; i++) {
if (set[i] == Mode.SHIFTA || set[i] == Mode.SHIFTB) {
glyph_count += 1.0;
}
if (fset[i] == FMode.SHIFTF || fset[i] == FMode.SHIFTN) {
glyph_count += 1.0;
}
if (set[i] == Mode.LATCHA || set[i] == Mode.LATCHB || set[i] == Mode.LATCHC) {
if (set[i] != last_set) {
last_set = set[i];
glyph_count += 1.0;
}
}
if (i == 0) {
if (fset[i] == FMode.LATCHF) {
glyph_count += 2.0;
}
} else {
if (fset[i] == FMode.LATCHF && fset[i - 1] != FMode.LATCHF) {
glyph_count += 2.0;
}
if (fset[i] != FMode.LATCHF && fset[i - 1] == FMode.LATCHF) {
glyph_count += 2.0;
}
}
if (set[i] == Mode.LATCHC) {
if (this.inputData[i] == FNC1) {
glyph_count += 1.0;
} else {
glyph_count += 0.5;
}
} else {
glyph_count += 1.0;
}
}
if (glyph_count > 80.0) {
throw new OkapiException("Input data too long");
}
 
info("Encoding: ");
 
/* So now we know what start character to use - we can get on with it! */
if (this.readerInit) {
/* Reader Initialisation mode */
switch (set[0]) {
case LATCHA:
values[0] = 103;
current_set = Mode.LATCHA;
values[1] = 96;
bar_characters++;
info("STARTA FNC3 ");
break;
case LATCHB:
values[0] = 104;
current_set = Mode.LATCHB;
values[1] = 96;
bar_characters++;
info("STARTB FNC3 ");
break;
default: /* Start C */
values[0] = 104;
values[1] = 96;
values[2] = 99;
bar_characters += 2;
current_set = Mode.LATCHC;
info("STARTB FNC3 CODEC ");
break;
}
} else {
/* Normal mode */
switch (set[0]) {
case LATCHA:
values[0] = 103;
current_set = Mode.LATCHA;
info("STARTA ");
break;
case LATCHB:
values[0] = 104;
current_set = Mode.LATCHB;
info("STARTB ");
break;
default:
values[0] = 105;
current_set = Mode.LATCHC;
info("STARTC ");
break;
}
}
bar_characters++;
 
if (this.inputDataType == DataType.GS1) {
values[1] = 102;
bar_characters++;
info("FNC1 ");
}
 
if (fset[0] == FMode.LATCHF) {
switch (current_set) {
case LATCHA:
values[bar_characters] = 101;
values[bar_characters + 1] = 101;
info("FNC4 FNC4 ");
break;
case LATCHB:
values[bar_characters] = 100;
values[bar_characters + 1] = 100;
info("FNC4 FNC4 ");
break;
}
bar_characters += 2;
f_state = FMode.LATCHF;
}
 
/* Encode the data */
read = 0;
do {
 
if (read != 0 && set[read] != current_set) { /* Latch different code set */
switch (set[read]) {
case LATCHA:
values[bar_characters] = 101;
bar_characters++;
current_set = Mode.LATCHA;
info("CODEA ");
break;
case LATCHB:
values[bar_characters] = 100;
bar_characters++;
current_set = Mode.LATCHB;
info("CODEB ");
break;
case LATCHC:
values[bar_characters] = 99;
bar_characters++;
current_set = Mode.LATCHC;
info("CODEC ");
break;
}
}
 
if (read != 0) {
if (fset[read] == FMode.LATCHF && f_state == FMode.LATCHN) {
/* Latch beginning of extended mode */
switch (current_set) {
case LATCHA:
values[bar_characters] = 101;
values[bar_characters + 1] = 101;
info("FNC4 FNC4 ");
break;
case LATCHB:
values[bar_characters] = 100;
values[bar_characters + 1] = 100;
info("FNC4 FNC4 ");
break;
}
bar_characters += 2;
f_state = FMode.LATCHF;
}
if (fset[read] == FMode.LATCHN && f_state == FMode.LATCHF) {
/* Latch end of extended mode */
switch (current_set) {
case LATCHA:
values[bar_characters] = 101;
values[bar_characters + 1] = 101;
info("FNC4 FNC4 ");
break;
case LATCHB:
values[bar_characters] = 100;
values[bar_characters + 1] = 100;
info("FNC4 FNC4 ");
break;
}
bar_characters += 2;
f_state = FMode.LATCHN;
}
}
 
if (fset[read] == FMode.SHIFTF || fset[read] == FMode.SHIFTN) {
/* Shift to or from extended mode */
switch (current_set) {
case LATCHA:
values[bar_characters] = 101;
info("FNC4 ");
break;
case LATCHB:
values[bar_characters] = 100;
info("FNC4 ");
break;
}
bar_characters++;
}
 
if (set[read] == Mode.SHIFTA || set[read] == Mode.SHIFTB) {
/* Insert shift character */
values[bar_characters] = 98;
info("SHFT ");
bar_characters++;
}
 
/* Encode data characters */
c = this.inputData[read];
switch (set[read]) {
case SHIFTA:
case LATCHA:
if (c == FNC1) {
values[bar_characters] = 102;
info("FNC1 ");
} else if (c == FNC2) {
values[bar_characters] = 97;
info("FNC2 ");
} else if (c == FNC3) {
values[bar_characters] = 96;
info("FNC3 ");
} else if (c == FNC4) {
values[bar_characters] = 101;
info("FNC4 ");
} else if (c > 127) {
if (c < 160) {
values[bar_characters] = c - 128 + 64;
} else {
values[bar_characters] = c - 128 - 32;
}
infoSpace(values[bar_characters]);
} else {
if (c < 32) {
values[bar_characters] = c + 64;
} else {
values[bar_characters] = c - 32;
}
infoSpace(values[bar_characters]);
}
bar_characters++;
read++;
break;
case SHIFTB:
case LATCHB:
if (c == FNC1) {
values[bar_characters] = 102;
info("FNC1 ");
} else if (c == FNC2) {
values[bar_characters] = 97;
info("FNC2 ");
} else if (c == FNC3) {
values[bar_characters] = 96;
info("FNC3 ");
} else if (c == FNC4) {
values[bar_characters] = 100;
info("FNC4 ");
} else if (c > 127) {
values[bar_characters] = c - 32 - 128;
infoSpace(values[bar_characters]);
} else {
values[bar_characters] = c - 32;
infoSpace(values[bar_characters]);
}
bar_characters++;
read++;
break;
case LATCHC:
if (c == FNC1) {
values[bar_characters] = 102;
info("FNC1 ");
bar_characters++;
read++;
} else {
final int d = this.inputData[read + 1];
final int weight = 10 * (c - '0') + d - '0';
values[bar_characters] = weight;
infoSpace(values[bar_characters]);
bar_characters++;
read += 2;
}
break;
}
 
} while (read < sourcelen);
 
infoLine();
 
/*
* "...note that the linkage flag is an extra code set character between the last data
* character and the Symbol Check Character" (GS1 Specification)
*/
 
/* Linkage flags in GS1-128 are determined by ISO/IEC 24723 section 7.4 */
 
switch (this.compositeMode) {
case CCA:
case CCB:
/* CC-A or CC-B 2D component */
switch (set[sourcelen - 1]) {
case LATCHA:
linkage_flag = 100;
break;
case LATCHB:
linkage_flag = 99;
break;
case LATCHC:
linkage_flag = 101;
break;
}
infoLine("Linkage Flag: " + linkage_flag);
break;
case CCC:
/* CC-C 2D component */
switch (set[sourcelen - 1]) {
case LATCHA:
linkage_flag = 99;
break;
case LATCHB:
linkage_flag = 101;
break;
case LATCHC:
linkage_flag = 100;
break;
}
infoLine("Linkage Flag: " + linkage_flag);
break;
default:
break;
}
 
if (linkage_flag != 0) {
values[bar_characters] = linkage_flag;
bar_characters++;
}
 
infoLine("Data Codewords: " + bar_characters);
 
/* Check digit calculation */
for (i = 0; i < bar_characters; i++) {
total_sum += i == 0 ? values[i] : values[i] * i;
}
final int checkDigit = total_sum % 103;
infoLine("Check Digit: " + checkDigit);
 
/* Build pattern string */
final StringBuilder dest = new StringBuilder(6 * bar_characters + 6 + 7);
for (i = 0; i < bar_characters; i++) {
dest.append(CODE128_TABLE[values[i]]);
}
dest.append(CODE128_TABLE[checkDigit]);
dest.append(CODE128_TABLE[106]); // stop character
 
/* Readable text */
if (this.inputDataType != DataType.GS1) {
this.readable = removeFncEscapeSequences(this.content);
if (this.inputDataType == DataType.HIBC) {
this.readable = "*" + this.readable + "*";
}
}
 
if (this.compositeMode == Composite.OFF) {
this.pattern = new String[] { dest.toString() };
this.row_height = new int[] { -1 };
this.row_count = 1;
} else {
/* Add the separator pattern for composite symbols */
this.pattern = new String[] { "0" + dest, dest.toString() };
this.row_height = new int[] { 1, -1 };
this.row_count = 2;
}
}
 
private static String removeFncEscapeSequences(final String s) {
return s.replace(FNC1_STRING, "").replace(FNC2_STRING, "").replace(FNC3_STRING, "").replace(FNC4_STRING, "");
}
 
private void resolveOddCs(final Mode[] set, final int i, final int cs, final int nums, final int fncs) {
if ((nums & 1) != 0) {
int index;
Mode m;
if (i - cs == 0 || fncs > 0) {
// Rule 2: first block -> swap last digit to A or B
index = i - 1;
if (index + 1 < set.length && set[index + 1] != null && set[index + 1] != Mode.LATCHC) {
// next block is either A or B -- match it
m = set[index + 1];
} else {
// next block is C, or there is no next block -- just latch to B
m = Mode.LATCHB;
}
} else {
// Rule 3b: subsequent block -> swap first digit to A or B
// Note that we make an exception for C blocks which contain one (or more) FNC1
// characters,
// since swapping the first digit would place the FNC1 in an invalid position in the
// block
index = i - nums;
if (index - 1 >= 0 && set[index - 1] != null && set[index - 1] != Mode.LATCHC) {
// previous block is either A or B -- match it
m = set[index - 1];
} else {
// previous block is C, or there is no previous block -- just latch to B
m = Mode.LATCHB;
}
}
set[index] = m;
}
}
 
private Mode findSubset(final int letter, final int numbers) {
 
Mode mode;
 
if (letter == FNC1) {
if (numbers % 2 == 0) {
/* ISO 15417 Annex E Note 2 */
/*
* FNC1 may use subset C, so long as it doesn't break data into an odd number of
* digits
*/
mode = Mode.ABORC;
} else {
mode = Mode.AORB;
}
} else if (letter == FNC2 || letter == FNC3 || letter == FNC4) {
mode = Mode.AORB;
} else if (letter <= 31) {
mode = Mode.SHIFTA;
} else if (letter >= 48 && letter <= 57) {
mode = Mode.ABORC;
} else if (letter <= 95) {
mode = Mode.AORB;
} else if (letter <= 127) {
mode = Mode.SHIFTB;
} else if (letter <= 159) {
mode = Mode.SHIFTA;
} else if (letter <= 223) {
mode = Mode.AORB;
} else {
mode = Mode.SHIFTB;
}
 
if (this.suppressModeC && mode == Mode.ABORC) {
mode = Mode.AORB;
}
 
return mode;
}
 
private int length(final int letter, final Mode mode) {
if (letter == FNC1 && mode == Mode.ABORC) {
/* ISO 15417 Annex E Note 2 */
/* Logical length used for making subset switching decisions, not actual length */
return 2;
} else {
return 1;
}
}
 
/** Implements rules from ISO 15417 Annex E. Returns the updated index point. */
private int reduceSubsetChanges(final Mode[] mode_type, final int[] mode_length, final int index_point) {
 
int totalLength = 0;
int i, length;
Mode current, last, next;
 
for (i = 0; i < index_point; i++) {
current = mode_type[i];
length = mode_length[i];
if (i != 0) {
last = mode_type[i - 1];
} else {
last = Mode.NULL;
}
if (i != index_point - 1) {
next = mode_type[i + 1];
} else {
next = Mode.NULL;
}
 
/* ISO 15417 Annex E Note 2 */
/* Calculate difference between logical length and actual length in this block */
int extraLength = 0;
for (int j = 0; j < length - extraLength; j++) {
if (length(this.inputData[totalLength + j], current) == 2) {
extraLength++;
}
}
 
if (i == 0) { /* first block */
if (index_point == 1 && length == 2 && current == Mode.ABORC) { /* Rule 1a */
mode_type[i] = Mode.LATCHC;
current = Mode.LATCHC;
}
if (current == Mode.ABORC) {
if (length >= 4) { /* Rule 1b */
mode_type[i] = Mode.LATCHC;
current = Mode.LATCHC;
} else {
mode_type[i] = Mode.AORB;
current = Mode.AORB;
}
}
if (current == Mode.SHIFTA) { /* Rule 1c */
mode_type[i] = Mode.LATCHA;
current = Mode.LATCHA;
}
if (current == Mode.AORB && next == Mode.SHIFTA) { /* Rule 1c */
mode_type[i] = Mode.LATCHA;
current = Mode.LATCHA;
}
if (current == Mode.AORB) { /* Rule 1d */
mode_type[i] = Mode.LATCHB;
current = Mode.LATCHB;
}
} else {
if (current == Mode.ABORC && length >= 4) { /* Rule 3 */
mode_type[i] = Mode.LATCHC;
current = Mode.LATCHC;
}
if (current == Mode.ABORC) {
mode_type[i] = Mode.AORB;
current = Mode.AORB;
}
if (current == Mode.AORB && last == Mode.LATCHA) {
mode_type[i] = Mode.LATCHA;
current = Mode.LATCHA;
}
if (current == Mode.AORB && last == Mode.LATCHB) {
mode_type[i] = Mode.LATCHB;
current = Mode.LATCHB;
}
if (current == Mode.AORB && next == Mode.SHIFTA) {
mode_type[i] = Mode.LATCHA;
current = Mode.LATCHA;
}
if (current == Mode.AORB && next == Mode.SHIFTB) {
mode_type[i] = Mode.LATCHB;
current = Mode.LATCHB;
}
if (current == Mode.AORB) {
mode_type[i] = Mode.LATCHB;
current = Mode.LATCHB;
}
if (current == Mode.SHIFTA && length > 1) { /* Rule 4 */
mode_type[i] = Mode.LATCHA;
current = Mode.LATCHA;
}
if (current == Mode.SHIFTB && length > 1) { /* Rule 5 */
mode_type[i] = Mode.LATCHB;
current = Mode.LATCHB;
}
if (current == Mode.SHIFTA && last == Mode.LATCHA) {
mode_type[i] = Mode.LATCHA;
current = Mode.LATCHA;
}
if (current == Mode.SHIFTB && last == Mode.LATCHB) {
mode_type[i] = Mode.LATCHB;
current = Mode.LATCHB;
}
if (current == Mode.SHIFTA && next == Mode.AORB) {
mode_type[i] = Mode.LATCHA;
current = Mode.LATCHA;
}
if (current == Mode.SHIFTB && next == Mode.AORB) {
mode_type[i] = Mode.LATCHB;
current = Mode.LATCHB;
}
if (current == Mode.SHIFTA && last == Mode.LATCHC) {
mode_type[i] = Mode.LATCHA;
current = Mode.LATCHA;
}
if (current == Mode.SHIFTB && last == Mode.LATCHC) {
mode_type[i] = Mode.LATCHB;
current = Mode.LATCHB;
}
} /* Rule 2 is implemented elsewhere, Rule 6 is implied */
 
/* ISO 15417 Annex E Note 2 */
/*
* Convert logical length back to actual length for this block, now that we've decided
* on a subset
*/
mode_length[i] -= extraLength;
totalLength += mode_length[i];
}
 
return combineSubsetBlocks(mode_type, mode_length, index_point);
}
 
/**
* Modifies the specified mode and length arrays to combine adjacent modes of the same type,
* returning the updated index point.
*/
private int combineSubsetBlocks(final Mode[] mode_type, final int[] mode_length, int index_point) {
/* bring together same type blocks */
if (index_point > 1) {
for (int i = 1; i < index_point; i++) {
if (mode_type[i - 1] == mode_type[i]) {
/* bring together */
mode_length[i - 1] = mode_length[i - 1] + mode_length[i];
/* decrease the list */
for (int j = i + 1; j < index_point; j++) {
mode_length[j - 1] = mode_length[j];
mode_type[j - 1] = mode_type[j];
}
index_point--;
i--;
}
}
}
return index_point;
}
 
/** {@inheritDoc} */
@Override
protected int[] getCodewords() {
return getPatternAsCodewords(6);
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/UspsPackage.java
New file
0,0 → 1,129
/*
* Copyright 2015 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import java.awt.geom.Rectangle2D;
 
/**
* <p>
* Implements USPS Intelligent Mail Package Barcode (IMpb), a linear barcode based on GS1-128.
* Includes additional data checks.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
* @see <a href=
* "https://ribbs.usps.gov/intelligentmail_package/documents/tech_guides/BarcodePackageIMSpec.pdf">IMpb
* Specification</a>
*/
public class UspsPackage extends Symbol {
 
@Override
protected void encode() {
 
if (!this.content.matches("[0-9\\[\\]]+")) {
/* Input must be numeric only */
throw new OkapiException("Invalid IMpb data");
}
 
if (this.content.length() % 2 != 0) {
/* Input must be even length */
throw new OkapiException("Invalid IMpb data");
}
 
final Code128 code128 = new Code128();
code128.unsetCc();
code128.setDataType(DataType.GS1);
code128.setContent(this.content);
 
final boolean fourTwenty = this.content.length() > 4 && this.content.charAt(1) == '4' && this.content.charAt(2) == '2' && this.content.charAt(3) == '0';
 
String hrt = "";
int bracketCount = 0;
for (int i = 0; i < this.content.length(); i++) {
if (this.content.charAt(i) == '[') {
bracketCount++;
}
if (!(fourTwenty && bracketCount < 2)) {
if (this.content.charAt(i) >= '0' && this.content.charAt(i) <= '9') {
hrt += this.content.charAt(i);
}
}
}
 
String spacedHrt = "";
for (int i = 0; i < hrt.length(); i++) {
spacedHrt += hrt.charAt(i);
if (i % 4 == 3) {
spacedHrt += " ";
}
}
 
this.encodeInfo = code128.encodeInfo;
this.readable = spacedHrt;
this.pattern = new String[1];
this.pattern[0] = code128.pattern[0];
this.row_count = 1;
this.row_height = new int[1];
this.row_height[0] = -1;
}
 
@Override
protected void plotSymbol() {
int xBlock;
int x, y, w, h;
boolean black;
final int offset = 20;
final int yoffset = 15;
final String banner = "USPS TRACKING #";
 
this.rectangles.clear();
this.texts.clear();
y = yoffset;
h = 0;
black = true;
x = 0;
for (xBlock = 0; xBlock < this.pattern[0].length(); xBlock++) {
w = this.pattern[0].charAt(xBlock) - '0';
if (black) {
if (this.row_height[0] == -1) {
h = this.default_height;
} else {
h = this.row_height[0];
}
if (w != 0 && h != 0) {
final Rectangle2D.Double rect = new Rectangle2D.Double(x + offset, y, w, h);
this.rectangles.add(rect);
}
this.symbol_width = x + w + 2 * offset;
}
black = !black;
x += w;
}
this.symbol_height = h + 2 * yoffset;
 
// Add boundary bars
final Rectangle2D.Double topBar = new Rectangle2D.Double(0, 0, this.symbol_width, 2);
final Rectangle2D.Double bottomBar = new Rectangle2D.Double(0, this.symbol_height - 2, this.symbol_width, 2);
this.rectangles.add(topBar);
this.rectangles.add(bottomBar);
 
this.texts.add(new TextBox(0, this.symbol_height - 6.0, this.symbol_width, this.readable, this.humanReadableAlignment));
this.texts.add(new TextBox(0, 12.0, this.symbol_width, banner, this.humanReadableAlignment));
}
 
/** {@inheritDoc} */
@Override
protected int[] getCodewords() {
return getPatternAsCodewords(6);
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/DataBarExpanded.java
New file
0,0 → 1,1271
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import static uk.org.okapibarcode.backend.DataBarLimited.getWidths;
import static uk.org.okapibarcode.util.Strings.binaryAppend;
 
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
 
/**
* <p>
* Implements GS1 DataBar Expanded Omnidirectional and GS1 DataBar Expanded Stacked Omnidirectional
* according to ISO/IEC 24724:2011.
*
* <p>
* DataBar expanded encodes GS1 data in either a linear or stacked format.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class DataBarExpanded extends Symbol {
 
private static final int[] G_SUM_EXP = { 0, 348, 1388, 2948, 3988 };
 
private static final int[] T_EVEN_EXP = { 4, 20, 52, 104, 204 };
 
private static final int[] MODULES_ODD_EXP = { 12, 10, 8, 6, 4 };
 
private static final int[] MODULES_EVEN_EXP = { 5, 7, 9, 11, 13 };
 
private static final int[] WIDEST_ODD_EXP = { 7, 5, 4, 3, 1 };
 
private static final int[] WIDEST_EVEN_EXP = { 2, 4, 5, 6, 8 };
 
/** Table 14 */
private static final int[] CHECKSUM_WEIGHT_EXP = { 1, 3, 9, 27, 81, 32, 96, 77, 20, 60, 180, 118, 143, 7, 21, 63, 189, 145, 13, 39, 117, 140, 209, 205, 193, 157, 49, 147, 19, 57, 171, 91, 62, 186,
136, 197, 169, 85, 44, 132, 185, 133, 188, 142, 4, 12, 36, 108, 113, 128, 173, 97, 80, 29, 87, 50, 150, 28, 84, 41, 123, 158, 52, 156, 46, 138, 203, 187, 139, 206, 196, 166, 76, 17, 51,
153, 37, 111, 122, 155, 43, 129, 176, 106, 107, 110, 119, 146, 16, 48, 144, 10, 30, 90, 59, 177, 109, 116, 137, 200, 178, 112, 125, 164, 70, 210, 208, 202, 184, 130, 179, 115, 134, 191,
151, 31, 93, 68, 204, 190, 148, 22, 66, 198, 172, 94, 71, 2, 6, 18, 54, 162, 64, 192, 154, 40, 120, 149, 25, 75, 14, 42, 126, 167, 79, 26, 78, 23, 69, 207, 199, 175, 103, 98, 83, 38, 114,
131, 182, 124, 161, 61, 183, 127, 170, 88, 53, 159, 55, 165, 73, 8, 24, 72, 5, 15, 45, 135, 194, 160, 58, 174, 100, 89 };
 
/** Table 15 */
private static final int[] FINDER_PATTERN_EXP = { 1, 8, 4, 1, 1, 1, 1, 4, 8, 1, 3, 6, 4, 1, 1, 1, 1, 4, 6, 3, 3, 4, 6, 1, 1, 1, 1, 6, 4, 3, 3, 2, 8, 1, 1, 1, 1, 8, 2, 3, 2, 6, 5, 1, 1, 1, 1, 5, 6,
2, 2, 2, 9, 1, 1, 1, 1, 9, 2, 2 };
 
/** Table 16 */
private static final int[] FINDER_SEQUENCE = { 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 3, 8, 0, 0, 0, 0, 0, 0, 0, 1, 10, 3, 8, 5, 0, 0, 0, 0, 0, 0, 1, 10, 3, 8, 7,
12, 0, 0, 0, 0, 0, 1, 10, 3, 8, 9, 12, 11, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 10, 9, 0, 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 0, 1, 2, 3, 4, 5, 8, 7, 10, 9,
12, 11 };
 
private static final int[] WEIGHT_ROWS = { 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 10, 3, 4, 13, 14, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 3, 4, 13, 14, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18, 3, 4, 13, 14, 11, 12, 21, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 18,
3, 4, 13, 14, 15, 16, 21, 22, 19, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 17, 18, 15, 16,
0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 17, 18, 19, 20, 21, 22, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 11, 12, 17, 18, 15, 16, 21, 22, 19, 20 };
 
private enum EncodeMode {
NUMERIC, ALPHA, ISOIEC, INVALID_CHAR, ANY_ENC, ALPHA_OR_ISO
}
 
private boolean linkageFlag;
private int preferredColumns = 2;
private boolean stacked = true;
 
public DataBarExpanded() {
this.inputDataType = DataType.GS1;
}
 
@Override
public void setDataType(final DataType dataType) {
if (dataType != Symbol.DataType.GS1) {
throw new IllegalArgumentException("Only GS1 data type is supported for DataBar Expanded symbology.");
}
}
 
@Override
protected boolean gs1Supported() {
return true;
}
 
/**
* Sets the preferred width of a stacked symbol by selecting the number of "columns" or symbol
* segments in each row of data.
*
* @param columns the number of segments in each row
*/
public void setPreferredColumns(final int columns) {
if (columns < 1 || columns > 10) {
throw new IllegalArgumentException("Invalid column count: " + columns);
}
this.preferredColumns = columns;
}
 
/**
* Returns the preferred width of a stacked symbol by selecting the number of "columns" or
* symbol segments in each row of data.
*
* @return the number of segments in each row
*/
public int getPreferredColumns() {
return this.preferredColumns;
}
 
/**
* Sets whether or not this symbology is stacked.
*
* @param stacked <tt>true</tt> for GS1 DataBar Expanded Stacked Omnidirectional, <tt>false</tt>
* for GS1 DataBar Expanded Omnidirectional
*/
public void setStacked(final boolean stacked) {
this.stacked = stacked;
}
 
/**
* Returns whether or not this symbology is stacked.
*
* @return <tt>true</tt> for GS1 DataBar Expanded Stacked Omnidirectional, <tt>false</tt> for
* GS1 DataBar Expanded Omnidirectional
*/
public boolean isStacked() {
return this.stacked;
}
 
protected void setLinkageFlag(final boolean linkageFlag) {
this.linkageFlag = linkageFlag;
}
 
@Override
protected void encode() {
int i;
int j;
int k;
int data_chars;
final int[] vs = new int[21];
final int[] group = new int[21];
final int[] v_odd = new int[21];
final int[] v_even = new int[21];
final int[][] char_widths = new int[21][8];
int checksum;
int row;
int check_char;
int c_group;
int c_odd;
int c_even;
final int[] check_widths = new int[8];
int pattern_width;
final int[] elements = new int[235];
int codeblocks;
int stack_rows;
int blocksPerRow;
int current_block;
int current_row;
boolean special_case_row;
int elements_in_sub;
int reader;
int writer;
final int[] sub_elements = new int[235];
int l;
int symbol_row;
String separator_pattern;
boolean black;
boolean left_to_right;
int compositeOffset;
 
this.inputData = toBytes(this.content, StandardCharsets.US_ASCII);
 
final StringBuilder binaryString = new StringBuilder(this.inputData.length * 8);
 
if (this.linkageFlag) {
binaryString.append('1');
compositeOffset = 1;
} else {
binaryString.append('0');
compositeOffset = 0;
}
 
final int encodingMethod = calculateBinaryString(this.inputData, binaryString); // updates
// binaryString
infoLine("Encoding Method: " + encodingMethod);
logBinaryStringInfo(binaryString);
 
data_chars = binaryString.length() / 12;
 
info("Data Characters: ");
for (i = 0; i < data_chars; i++) {
vs[i] = 0;
for (j = 0; j < 12; j++) {
if (binaryString.charAt(i * 12 + j) == '1') {
vs[i] += 2048 >> j;
}
}
infoSpace(vs[i]);
}
infoLine();
 
for (i = 0; i < data_chars; i++) {
if (vs[i] <= 347) {
group[i] = 1;
}
if (vs[i] >= 348 && vs[i] <= 1387) {
group[i] = 2;
}
if (vs[i] >= 1388 && vs[i] <= 2947) {
group[i] = 3;
}
if (vs[i] >= 2948 && vs[i] <= 3987) {
group[i] = 4;
}
if (vs[i] >= 3988) {
group[i] = 5;
}
v_odd[i] = (vs[i] - G_SUM_EXP[group[i] - 1]) / T_EVEN_EXP[group[i] - 1];
v_even[i] = (vs[i] - G_SUM_EXP[group[i] - 1]) % T_EVEN_EXP[group[i] - 1];
 
int[] widths = getWidths(v_odd[i], MODULES_ODD_EXP[group[i] - 1], 4, WIDEST_ODD_EXP[group[i] - 1], 0);
char_widths[i][0] = widths[0];
char_widths[i][2] = widths[1];
char_widths[i][4] = widths[2];
char_widths[i][6] = widths[3];
 
widths = getWidths(v_even[i], MODULES_EVEN_EXP[group[i] - 1], 4, WIDEST_EVEN_EXP[group[i] - 1], 1);
char_widths[i][1] = widths[0];
char_widths[i][3] = widths[1];
char_widths[i][5] = widths[2];
char_widths[i][7] = widths[3];
}
 
/* 7.2.6 Check character */
/*
* The checksum value is equal to the mod 211 residue of the weighted sum of the widths of
* the elements in the data characters.
*/
checksum = 0;
for (i = 0; i < data_chars; i++) {
row = WEIGHT_ROWS[(data_chars - 2) / 2 * 21 + i];
for (j = 0; j < 8; j++) {
checksum += char_widths[i][j] * CHECKSUM_WEIGHT_EXP[row * 8 + j];
 
}
}
 
check_char = 211 * (data_chars + 1 - 4) + checksum % 211;
 
infoLine("Check Character: " + check_char);
 
c_group = 1;
if (check_char >= 348 && check_char <= 1387) {
c_group = 2;
}
if (check_char >= 1388 && check_char <= 2947) {
c_group = 3;
}
if (check_char >= 2948 && check_char <= 3987) {
c_group = 4;
}
if (check_char >= 3988) {
c_group = 5;
}
 
c_odd = (check_char - G_SUM_EXP[c_group - 1]) / T_EVEN_EXP[c_group - 1];
c_even = (check_char - G_SUM_EXP[c_group - 1]) % T_EVEN_EXP[c_group - 1];
 
int[] widths = getWidths(c_odd, MODULES_ODD_EXP[c_group - 1], 4, WIDEST_ODD_EXP[c_group - 1], 0);
check_widths[0] = widths[0];
check_widths[2] = widths[1];
check_widths[4] = widths[2];
check_widths[6] = widths[3];
 
widths = getWidths(c_even, MODULES_EVEN_EXP[c_group - 1], 4, WIDEST_EVEN_EXP[c_group - 1], 1);
check_widths[1] = widths[0];
check_widths[3] = widths[1];
check_widths[5] = widths[2];
check_widths[7] = widths[3];
 
/* Initialise element array */
pattern_width = ((data_chars + 1) / 2 + (data_chars + 1 & 1)) * 5 + (data_chars + 1) * 8 + 4;
for (i = 0; i < pattern_width; i++) {
elements[i] = 0;
}
 
elements[0] = 1;
elements[1] = 1;
elements[pattern_width - 2] = 1;
elements[pattern_width - 1] = 1;
 
/* Put finder patterns in element array */
for (i = 0; i < (data_chars + 1) / 2 + (data_chars + 1 & 1); i++) {
k = ((data_chars + 1 - 2) / 2 + (data_chars + 1 & 1) - 1) * 11 + i;
for (j = 0; j < 5; j++) {
elements[21 * i + j + 10] = FINDER_PATTERN_EXP[(FINDER_SEQUENCE[k] - 1) * 5 + j];
}
}
 
/* Put check character in element array */
for (i = 0; i < 8; i++) {
elements[i + 2] = check_widths[i];
}
 
/* Put forward reading data characters in element array */
for (i = 1; i < data_chars; i += 2) {
for (j = 0; j < 8; j++) {
elements[(i - 1) / 2 * 21 + 23 + j] = char_widths[i][j];
}
}
 
/* Put reversed data characters in element array */
for (i = 0; i < data_chars; i += 2) {
for (j = 0; j < 8; j++) {
elements[i / 2 * 21 + 15 + j] = char_widths[i][7 - j];
}
}
 
if (!this.stacked) {
/* Copy elements into symbol */
this.row_count = 1 + compositeOffset;
this.row_height = new int[1 + compositeOffset];
this.row_height[0 + compositeOffset] = -1;
this.pattern = new String[1 + compositeOffset];
 
writer = 0;
black = false;
final StringBuilder pat = new StringBuilder("0");
final StringBuilder separator_binary = new StringBuilder();
for (i = 0; i < pattern_width; i++) {
pat.append((char) (elements[i] + '0'));
for (j = 0; j < elements[i]; j++) {
if (black) {
separator_binary.append('0');
} else {
separator_binary.append('1');
}
}
black = !black;
writer += elements[i];
}
this.pattern[0 + compositeOffset] = pat.toString();
 
separator_binary.setCharAt(0, '0');
separator_binary.setCharAt(1, '0');
separator_binary.setCharAt(2, '0');
separator_binary.setCharAt(3, '0');
separator_binary.delete(writer - 4, separator_binary.length());
for (j = 0; j < writer / 49; j++) {
k = 49 * j + 18;
for (i = 0; i < 15; i++) {
if (separator_binary.charAt(i + k - 1) == '1' && separator_binary.charAt(i + k) == '1') {
separator_binary.setCharAt(i + k, '0');
}
}
}
if (this.linkageFlag) {
// Add composite code separator
this.pattern[0] = bin2pat(separator_binary);
this.row_height[0] = 1;
}
 
} else {
/* RSS Expanded Stacked */
codeblocks = (data_chars + 1) / 2 + (data_chars + 1) % 2;
 
blocksPerRow = this.preferredColumns;
 
if (this.linkageFlag && blocksPerRow == 1) {
/*
* "There shall be a minimum of four symbol characters in the first row of an RSS
* Expanded Stacked symbol when it is the linear component of an EAN.UCC Composite
* symbol."
*/
blocksPerRow = 2;
}
 
stack_rows = codeblocks / blocksPerRow;
if (codeblocks % blocksPerRow > 0) {
stack_rows++;
}
 
this.row_count = stack_rows * 4 - 3;
this.row_height = new int[this.row_count + compositeOffset];
this.pattern = new String[this.row_count + compositeOffset];
symbol_row = 0;
 
current_block = 0;
for (current_row = 1; current_row <= stack_rows; current_row++) {
for (i = 0; i < 235; i++) {
sub_elements[i] = 0;
}
special_case_row = false;
 
/* Row Start */
sub_elements[0] = 1;
sub_elements[1] = 1;
elements_in_sub = 2;
 
/* Row Data */
reader = 0;
do {
if ((blocksPerRow & 1) != 0 || (current_row & 1) != 0
|| current_row == stack_rows && codeblocks != current_row * blocksPerRow && (current_row * blocksPerRow - codeblocks & 1) != 0) {
/* left to right */
left_to_right = true;
i = 2 + current_block * 21;
for (j = 0; j < 21; j++) {
if (i + j < pattern_width) {
sub_elements[j + reader * 21 + 2] = elements[i + j];
elements_in_sub++;
}
}
} else {
/* right to left */
left_to_right = false;
if (current_row * blocksPerRow < codeblocks) {
/* a full row */
i = 2 + (current_row * blocksPerRow - reader - 1) * 21;
for (j = 0; j < 21; j++) {
if (i + j < pattern_width) {
sub_elements[20 - j + reader * 21 + 2] = elements[i + j];
elements_in_sub++;
}
}
} else {
/* a partial row */
k = current_row * blocksPerRow - codeblocks;
l = current_row * blocksPerRow - reader - 1;
i = 2 + (l - k) * 21;
for (j = 0; j < 21; j++) {
if (i + j < pattern_width) {
sub_elements[20 - j + reader * 21 + 2] = elements[i + j];
elements_in_sub++;
}
}
}
}
reader++;
current_block++;
} while (reader < blocksPerRow && current_block < codeblocks);
 
/* Row Stop */
sub_elements[elements_in_sub] = 1;
sub_elements[elements_in_sub + 1] = 1;
elements_in_sub += 2;
 
black = true;
StringBuilder pat = new StringBuilder();
this.row_height[symbol_row + compositeOffset] = -1;
 
if ((current_row & 1) != 0) {
pat.append('0');
black = false;
} else {
if (current_row == stack_rows && codeblocks != current_row * blocksPerRow && (current_row * blocksPerRow - codeblocks & 1) != 0) {
/* Special case bottom row */
special_case_row = true;
sub_elements[0] = 2;
pat.append('0');
black = false;
}
}
 
writer = 0;
 
final StringBuilder separator_binary = new StringBuilder();
for (i = 0; i < elements_in_sub; i++) {
pat.append((char) (sub_elements[i] + '0'));
for (j = 0; j < sub_elements[i]; j++) {
separator_binary.append(black ? '0' : '1');
}
black = !black;
writer += sub_elements[i];
}
this.pattern[symbol_row + compositeOffset] = pat.toString();
separator_binary.setCharAt(0, '0');
separator_binary.setCharAt(1, '0');
separator_binary.setCharAt(2, '0');
separator_binary.setCharAt(3, '0');
separator_binary.delete(writer - 4, separator_binary.length());
for (j = 0; j < reader; j++) {
k = 49 * j + (special_case_row ? 19 : 18);
if (left_to_right) {
for (i = 0; i < 15; i++) {
if (separator_binary.charAt(i + k - 1) == '1' && separator_binary.charAt(i + k) == '1') {
separator_binary.setCharAt(i + k, '0');
}
}
} else {
for (i = 14; i >= 0; i--) {
if (separator_binary.charAt(i + k + 1) == '1' && separator_binary.charAt(i + k) == '1') {
separator_binary.setCharAt(i + k, '0');
}
}
}
}
separator_pattern = bin2pat(separator_binary);
 
if (current_row == 1 && this.linkageFlag) {
// Add composite code separator
this.row_height[0] = 1;
this.pattern[0] = separator_pattern;
}
 
if (current_row != 1) {
/* middle separator pattern (above current row) */
pat = new StringBuilder("05");
for (j = 5; j < 49 * blocksPerRow; j += 2) {
pat.append("11");
}
this.pattern[symbol_row - 2 + compositeOffset] = pat.toString();
this.row_height[symbol_row - 2 + compositeOffset] = 1;
/* bottom separator pattern (above current row) */
this.row_height[symbol_row - 1 + compositeOffset] = 1;
this.pattern[symbol_row - 1 + compositeOffset] = separator_pattern;
}
 
if (current_row != stack_rows) {
this.row_height[symbol_row + 1 + compositeOffset] = 1;
this.pattern[symbol_row + 1 + compositeOffset] = separator_pattern;
}
 
symbol_row += 4;
}
this.readable = "";
this.row_count += compositeOffset;
}
}
 
/** Handles all data encodation from section 7.2.5 of ISO/IEC 24724. */
private static int calculateBinaryString(final int[] inputData, final StringBuilder binaryString) {
 
EncodeMode last_mode = EncodeMode.NUMERIC;
int i;
boolean latch;
int remainder, d1, d2, value;
String padstring;
int current_length;
 
/*
* Decide whether a compressed data field is required and if so what method to use: method 2
* = no compressed data field
*/
 
int encodingMethod;
if (inputData.length >= 16 && inputData[0] == '0' && inputData[1] == '1') {
/* (01) and other AIs */
encodingMethod = 1;
} else {
/* any AIs */
encodingMethod = 2;
}
 
if (inputData.length >= 20 && encodingMethod == 1 && inputData[2] == '9' && inputData[16] == '3') {
 
/* Possibly encoding method > 2 */
 
if (inputData.length >= 26 && inputData[17] == '1') {
/* Methods 3, 7, 9, 11 and 13 */
if (inputData[18] == '0') {
/* (01) and (310x), weight in kilos */
double weight = 0;
for (i = 0; i < 6; i++) {
weight *= 10;
weight += inputData[20 + i] - '0';
}
if (weight < 99_999) { /* Maximum weight = 99999 */
if (inputData[19] == '3' && inputData.length == 26) {
/* (01) and (3103) */
weight /= 1000.0;
if (weight <= 32.767) {
encodingMethod = 3;
}
}
if (inputData.length == 34) {
if (inputData[26] == '1' && inputData[27] == '1') {
/* (01), (310x) and (11) - metric weight and production date */
encodingMethod = 7;
}
if (inputData[26] == '1' && inputData[27] == '3') {
/* (01), (310x) and (13) - metric weight and packaging date */
encodingMethod = 9;
}
if (inputData[26] == '1' && inputData[27] == '5') {
/* (01), (310x) and (15) - metric weight and "best before" date */
encodingMethod = 11;
}
if (inputData[26] == '1' && inputData[27] == '7') {
/* (01), (310x) and (17) - metric weight and expiration date */
encodingMethod = 13;
}
}
}
}
}
 
if (inputData.length >= 26 && inputData[17] == '2') {
/* Methods 4, 8, 10, 12 and 14 */
if (inputData[18] == '0') {
/* (01) and (320x), weight in pounds */
double weight = 0;
for (i = 0; i < 6; i++) {
weight *= 10;
weight += inputData[20 + i] - '0';
}
if (weight < 99_999) { /* Maximum weight = 99999 */
if ((inputData[19] == '2' || inputData[19] == '3') && inputData.length == 26) {
/* (01) and (3202)/(3203) */
if (inputData[19] == '3') {
weight /= 1000.0;
if (weight <= 22.767) {
encodingMethod = 4;
}
} else {
weight /= 100.0;
if (weight <= 99.99) {
encodingMethod = 4;
}
}
}
if (inputData.length == 34) {
if (inputData[26] == '1' && inputData[27] == '1') {
/* (01), (320x) and (11) - English weight and production date */
encodingMethod = 8;
}
if (inputData[26] == '1' && inputData[27] == '3') {
/* (01), (320x) and (13) - English weight and packaging date */
encodingMethod = 10;
}
if (inputData[26] == '1' && inputData[27] == '5') {
/* (01), (320x) and (15) - English weight and "best before" date */
encodingMethod = 12;
}
if (inputData[26] == '1' && inputData[27] == '7') {
/* (01), (320x) and (17) - English weight and expiration date */
encodingMethod = 14;
}
}
}
}
}
 
if (inputData[17] == '9') {
/* Methods 5 and 6 */
if (inputData[18] == '2' && inputData[19] >= '0' && inputData[19] <= '3') {
/* (01) and (392x) */
encodingMethod = 5;
}
if (inputData[18] == '3' && inputData[19] >= '0' && inputData[19] <= '3') {
/* (01) and (393x) */
encodingMethod = 6;
}
}
}
 
/* Encoding method - Table 10 */
/* Variable length symbol bit field is just given a place holder (XX) for the time being */
int read_posn;
switch (encodingMethod) {
case 1:
binaryString.append("1XX");
read_posn = 16;
break;
case 2:
binaryString.append("00XX");
read_posn = 0;
break;
case 3:
binaryString.append("0100");
read_posn = inputData.length;
break;
case 4:
binaryString.append("0101");
read_posn = inputData.length;
break;
case 5:
binaryString.append("01100XX");
read_posn = 20;
break;
case 6:
binaryString.append("01101XX");
read_posn = 23;
break;
default: /* modes 7 (0111000) to 14 (0111111) */
binaryString.append("0" + Integer.toBinaryString(56 + encodingMethod - 7));
read_posn = inputData.length;
break;
}
 
/*
* Verify that the data to be placed in the compressed data field is all numeric data before
* carrying out compression
*/
for (i = 0; i < read_posn; i++) {
if (inputData[i] < '0' || inputData[i] > '9') {
/* Something is wrong */
throw new OkapiException("Invalid characters in input data");
}
}
 
/* Now encode the compressed data field */
 
if (encodingMethod == 1) {
/* Encoding method field "1" - general item identification data */
binaryAppend(binaryString, inputData[2] - '0', 4);
for (i = 1; i < 5; i++) {
final int group = parseInt(inputData, i * 3, 3);
binaryAppend(binaryString, group, 10);
}
}
 
if (encodingMethod == 3 || encodingMethod == 4) {
/* Encoding method field "0100" - variable weight item (0,001 kilogram increments) */
/*
* Encoding method field "0101" - variable weight item (0,01 or 0,001 pound increment)
*/
for (i = 1; i < 5; i++) {
final int group = parseInt(inputData, i * 3, 3);
binaryAppend(binaryString, group, 10);
}
int group = parseInt(inputData, 20, 6);
if (encodingMethod == 4 && inputData[19] == '3') {
group += 10_000;
}
binaryAppend(binaryString, group, 15);
}
 
if (encodingMethod == 5 || encodingMethod == 6) {
/* Encoding method field "01100" - variable measure item and price */
/*
* Encoding method "01101" - variable measure item and price with ISO 4217 currency code
*/
for (i = 1; i < 5; i++) {
final int group = parseInt(inputData, i * 3, 3);
binaryAppend(binaryString, group, 10);
}
binaryAppend(binaryString, inputData[19] - '0', 2);
if (encodingMethod == 6) {
final int currency = parseInt(inputData, 20, 3);
binaryAppend(binaryString, currency, 10);
}
}
 
if (encodingMethod >= 7 && encodingMethod <= 14) {
/*
* Encoding method fields "0111000" through "0111111" - variable weight item plus date
*/
for (i = 1; i < 5; i++) {
final int group = parseInt(inputData, i * 3, 3);
binaryAppend(binaryString, group, 10);
}
int weight = inputData[19] - '0';
for (i = 0; i < 5; i++) {
weight *= 10;
weight += inputData[21 + i] - '0';
}
binaryAppend(binaryString, weight, 20);
int date;
if (inputData.length == 34) {
/* Date information is included */
date = parseInt(inputData, 28, 2) * 384;
date += (parseInt(inputData, 30, 2) - 1) * 32;
date += parseInt(inputData, 32, 2);
} else {
date = 38_400;
}
binaryAppend(binaryString, date, 16);
}
 
/*
* The compressed data field has been processed if appropriate - the rest of the data (if
* any) goes into a general-purpose data compaction field
*/
 
final int[] generalField = Arrays.copyOfRange(inputData, read_posn, inputData.length);
 
if (generalField.length != 0) {
 
latch = false;
final EncodeMode[] generalFieldType = new EncodeMode[generalField.length];
 
for (i = 0; i < generalField.length; i++) {
/* Tables 11, 12, 13 - ISO/IEC 646 encodation */
final int c = generalField[i];
EncodeMode mode;
if (c == FNC1) {
// FNC1 can be encoded in any system
mode = EncodeMode.ANY_ENC;
} else if (c >= '0' && c <= '9') {
// numbers can be encoded in any system, but will usually narrow down to numeric
// encodation
mode = EncodeMode.ANY_ENC;
} else if (c >= 'A' && c <= 'Z' || c == '*' || c == ',' || c == '-' || c == '.' || c == '/') {
// alphanumeric encodation or ISO/IEC encodation
mode = EncodeMode.ALPHA_OR_ISO;
} else if (c >= 'a' && c <= 'z' || c == '!' || c == '"' || c == '%' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '+' || c == ':' || c == ';' || c == '<' || c == '='
|| c == '>' || c == '?' || c == '_' || c == ' ') {
// ISO/IEC encodation
mode = EncodeMode.ISOIEC;
} else {
// unable to encode this character
mode = EncodeMode.INVALID_CHAR;
latch = true;
}
generalFieldType[i] = mode;
}
 
if (latch) {
throw new OkapiException("Invalid characters in input data");
}
 
for (i = 0; i < generalField.length - 1; i++) {
if (generalFieldType[i] == EncodeMode.ISOIEC && generalField[i + 1] == FNC1) {
generalFieldType[i + 1] = EncodeMode.ISOIEC;
}
}
 
for (i = 0; i < generalField.length - 1; i++) {
if (generalFieldType[i] == EncodeMode.ALPHA_OR_ISO && generalField[i + 1] == FNC1) {
generalFieldType[i + 1] = EncodeMode.ALPHA_OR_ISO;
}
}
 
latch = applyGeneralFieldRules(generalFieldType); // modifies generalFieldType
 
/* Set initial mode if not NUMERIC */
if (generalFieldType[0] == EncodeMode.ALPHA) {
binaryString.append("0000"); /* Alphanumeric latch */
last_mode = EncodeMode.ALPHA;
}
if (generalFieldType[0] == EncodeMode.ISOIEC) {
binaryString.append("0000"); /* Alphanumeric latch */
binaryString.append("00100"); /* ISO/IEC 646 latch */
last_mode = EncodeMode.ISOIEC;
}
 
i = 0;
do {
switch (generalFieldType[i]) {
case NUMERIC:
if (last_mode != EncodeMode.NUMERIC) {
binaryString.append("000"); /* Numeric latch */
}
if (generalField[i] != FNC1) {
d1 = generalField[i] - '0';
} else {
d1 = 10;
}
if (generalField[i + 1] != FNC1) {
d2 = generalField[i + 1] - '0';
} else {
d2 = 10;
}
value = 11 * d1 + d2 + 8;
binaryAppend(binaryString, value, 7);
i += 2;
last_mode = EncodeMode.NUMERIC;
break;
 
case ALPHA:
if (i != 0) {
if (last_mode == EncodeMode.NUMERIC) {
binaryString.append("0000"); /* Alphanumeric latch */
}
if (last_mode == EncodeMode.ISOIEC) {
binaryString.append("00100"); /* Alphanumeric latch */
}
}
if (generalField[i] >= '0' && generalField[i] <= '9') {
value = generalField[i] - 43;
binaryAppend(binaryString, value, 5);
}
if (generalField[i] >= 'A' && generalField[i] <= 'Z') {
value = generalField[i] - 33;
binaryAppend(binaryString, value, 6);
}
last_mode = EncodeMode.ALPHA;
if (generalField[i] == FNC1) {
binaryString.append("01111");
// TODO: FNC1 should act as an implicit numeric latch, so the commented out
// line below should be correct, but ZXing cannot
// read barcodes which use FNC1 as an implicit numeric latch... so for now,
// and in order to achieve widest compatibility,
// we waste 3 bits and don't perform the implicit mode change (see
// https://sourceforge.net/p/zint/tickets/145/)
// last_mode = EncodeMode.NUMERIC;
} /* FNC1 / Numeric latch */
if (generalField[i] == '*') {
binaryString.append("111010"); /* asterisk */
}
if (generalField[i] == ',') {
binaryString.append("111011"); /* comma */
}
if (generalField[i] == '-') {
binaryString.append("111100"); /* minus or hyphen */
}
if (generalField[i] == '.') {
binaryString.append("111101"); /* period or full stop */
}
if (generalField[i] == '/') {
binaryString.append("111110"); /* slash or solidus */
}
i++;
break;
 
case ISOIEC:
if (i != 0) {
if (last_mode == EncodeMode.NUMERIC) {
binaryString.append("0000"); /* Alphanumeric latch */
binaryString.append("00100"); /* ISO/IEC 646 latch */
}
if (last_mode == EncodeMode.ALPHA) {
binaryString.append("00100"); /* ISO/IEC 646 latch */
}
}
if (generalField[i] >= '0' && generalField[i] <= '9') {
value = generalField[i] - 43;
binaryAppend(binaryString, value, 5);
}
if (generalField[i] >= 'A' && generalField[i] <= 'Z') {
value = generalField[i] - 1;
binaryAppend(binaryString, value, 7);
}
if (generalField[i] >= 'a' && generalField[i] <= 'z') {
value = generalField[i] - 7;
binaryAppend(binaryString, value, 7);
}
last_mode = EncodeMode.ISOIEC;
if (generalField[i] == FNC1) {
binaryString.append("01111");
// TODO: FNC1 should act as an implicit numeric latch, so the commented out
// line below should be correct, but ZXing cannot
// read barcodes which use FNC1 as an implicit numeric latch... so for now,
// and in order to achieve widest compatibility,
// we waste 3 bits and don't perform the implicit mode change (see
// https://sourceforge.net/p/zint/tickets/145/)
// last_mode = EncodeMode.NUMERIC;
} /* FNC1 / Numeric latch */
if (generalField[i] == '!') {
binaryString.append("11101000"); /* exclamation mark */
}
if (generalField[i] == 34) {
binaryString.append("11101001"); /* quotation mark */
}
if (generalField[i] == 37) {
binaryString.append("11101010"); /* percent sign */
}
if (generalField[i] == '&') {
binaryString.append("11101011"); /* ampersand */
}
if (generalField[i] == 39) {
binaryString.append("11101100"); /* apostrophe */
}
if (generalField[i] == '(') {
binaryString.append("11101101"); /* left parenthesis */
}
if (generalField[i] == ')') {
binaryString.append("11101110"); /* right parenthesis */
}
if (generalField[i] == '*') {
binaryString.append("11101111"); /* asterisk */
}
if (generalField[i] == '+') {
binaryString.append("11110000"); /* plus sign */
}
if (generalField[i] == ',') {
binaryString.append("11110001"); /* comma */
}
if (generalField[i] == '-') {
binaryString.append("11110010"); /* minus or hyphen */
}
if (generalField[i] == '.') {
binaryString.append("11110011"); /* period or full stop */
}
if (generalField[i] == '/') {
binaryString.append("11110100"); /* slash or solidus */
}
if (generalField[i] == ':') {
binaryString.append("11110101"); /* colon */
}
if (generalField[i] == ';') {
binaryString.append("11110110"); /* semicolon */
}
if (generalField[i] == '<') {
binaryString.append("11110111"); /* less-than sign */
}
if (generalField[i] == '=') {
binaryString.append("11111000"); /* equals sign */
}
if (generalField[i] == '>') {
binaryString.append("11111001"); /* greater-than sign */
}
if (generalField[i] == '?') {
binaryString.append("11111010"); /* question mark */
}
if (generalField[i] == '_') {
binaryString.append("11111011"); /* underline or low line */
}
if (generalField[i] == ' ') {
binaryString.append("11111100"); /* space */
}
i++;
break;
}
 
current_length = i;
if (latch) {
current_length++;
}
 
} while (current_length < generalField.length);
 
remainder = calculateRemainder(binaryString.length());
 
if (latch) {
/* There is still one more numeric digit to encode */
if (last_mode == EncodeMode.NUMERIC) {
if (remainder >= 4 && remainder <= 6) {
value = generalField[i] - '0';
value++;
binaryAppend(binaryString, value, 4);
} else {
d1 = generalField[i] - '0';
d2 = 10;
value = 11 * d1 + d2 + 8;
binaryAppend(binaryString, value, 7);
}
} else {
value = generalField[i] - 43;
binaryAppend(binaryString, value, 5);
}
}
}
 
if (binaryString.length() > 252) {
throw new OkapiException("Input too long");
}
 
remainder = calculateRemainder(binaryString.length());
 
/* Now add padding to binary string (7.2.5.5.4) */
i = remainder;
if (generalField.length != 0 && last_mode == EncodeMode.NUMERIC) {
padstring = "0000";
i -= 4;
} else {
padstring = "";
}
for (; i > 0; i -= 5) {
padstring += "00100";
}
 
binaryString.append(padstring.substring(0, remainder));
 
/* Patch variable length symbol bit field */
char patchEvenOdd, patchSize;
if ((binaryString.length() / 12 + 1 & 1) == 0) {
patchEvenOdd = '0';
} else {
patchEvenOdd = '1';
}
if (binaryString.length() <= 156) {
patchSize = '0';
} else {
patchSize = '1';
}
 
if (encodingMethod == 1) {
binaryString.setCharAt(2, patchEvenOdd);
binaryString.setCharAt(3, patchSize);
}
if (encodingMethod == 2) {
binaryString.setCharAt(3, patchEvenOdd);
binaryString.setCharAt(4, patchSize);
}
if (encodingMethod == 5 || encodingMethod == 6) {
binaryString.setCharAt(6, patchEvenOdd);
binaryString.setCharAt(7, patchSize);
}
 
return encodingMethod;
}
 
private static int calculateRemainder(final int binaryStringLength) {
int remainder = 12 - binaryStringLength % 12;
if (remainder == 12) {
remainder = 0;
}
if (binaryStringLength < 36) {
remainder = 36 - binaryStringLength;
}
return remainder;
}
 
/** Logs binary string as hexadecimal */
private void logBinaryStringInfo(final StringBuilder binaryString) {
 
infoLine("Binary Length: " + binaryString.length());
info("Binary String: ");
 
int nibble = 0;
for (int i = 0; i < binaryString.length(); i++) {
switch (i % 4) {
case 0:
if (binaryString.charAt(i) == '1') {
nibble += 8;
}
break;
case 1:
if (binaryString.charAt(i) == '1') {
nibble += 4;
}
break;
case 2:
if (binaryString.charAt(i) == '1') {
nibble += 2;
}
break;
case 3:
if (binaryString.charAt(i) == '1') {
nibble += 1;
}
info(Integer.toHexString(nibble));
nibble = 0;
break;
}
}
 
if (binaryString.length() % 4 != 0) {
info(Integer.toHexString(nibble));
}
 
infoLine();
}
 
/**
* Attempts to apply encoding rules from sections 7.2.5.5.1 to 7.2.5.5.3 of ISO/IEC 24724:2006
*/
private static boolean applyGeneralFieldRules(final EncodeMode[] generalFieldType) {
 
int block_count, i, j, k;
EncodeMode current, next, last;
final int[] blockLength = new int[200];
final EncodeMode[] blockType = new EncodeMode[200];
 
block_count = 0;
 
blockLength[block_count] = 1;
blockType[block_count] = generalFieldType[0];
 
for (i = 1; i < generalFieldType.length; i++) {
current = generalFieldType[i];
last = generalFieldType[i - 1];
 
if (current == last) {
blockLength[block_count] = blockLength[block_count] + 1;
} else {
block_count++;
blockLength[block_count] = 1;
blockType[block_count] = generalFieldType[i];
}
}
 
block_count++;
 
for (i = 0; i < block_count; i++) {
current = blockType[i];
next = blockType[i + 1];
 
if (current == EncodeMode.ISOIEC && i != block_count - 1) {
if (next == EncodeMode.ANY_ENC && blockLength[i + 1] >= 4) {
blockType[i + 1] = EncodeMode.NUMERIC;
}
if (next == EncodeMode.ANY_ENC && blockLength[i + 1] < 4) {
blockType[i + 1] = EncodeMode.ISOIEC;
}
if (next == EncodeMode.ALPHA_OR_ISO && blockLength[i + 1] >= 5) {
blockType[i + 1] = EncodeMode.ALPHA;
}
if (next == EncodeMode.ALPHA_OR_ISO && blockLength[i + 1] < 5) {
blockType[i + 1] = EncodeMode.ISOIEC;
}
}
 
if (current == EncodeMode.ALPHA_OR_ISO) {
blockType[i] = EncodeMode.ALPHA;
current = EncodeMode.ALPHA;
}
 
if (current == EncodeMode.ALPHA && i != block_count - 1) {
if (next == EncodeMode.ANY_ENC && blockLength[i + 1] >= 6) {
blockType[i + 1] = EncodeMode.NUMERIC;
}
if (next == EncodeMode.ANY_ENC && blockLength[i + 1] < 6) {
if (i == block_count - 2 && blockLength[i + 1] >= 4) {
blockType[i + 1] = EncodeMode.NUMERIC;
} else {
blockType[i + 1] = EncodeMode.ALPHA;
}
}
}
 
if (current == EncodeMode.ANY_ENC) {
blockType[i] = EncodeMode.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;
 
/* decrease 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] == EncodeMode.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++) {
generalFieldType[j] = blockType[i];
j++;
}
}
 
if (blockType[block_count - 1] == EncodeMode.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 static int parseInt(final int[] chars, final int index, final int length) {
int val = 0;
int pow = (int) Math.pow(10, length - 1);
for (int i = 0; i < length; i++) {
final int c = chars[index + i];
val += (c - '0') * pow;
pow /= 10;
}
return val;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/MicroQrCode.java
New file
0,0 → 1,1606
/*
* 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.io.UnsupportedEncodingException;
 
/**
* Implements Micro QR Code According to ISO/IEC 18004:2006 <br>
* A miniature version of the QR Code symbol for short messages. QR Code symbols can encode
* characters in the Latin-1 set and Kanji characters which are members of the Shift-JIS encoding
* scheme.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class MicroQrCode extends Symbol {
 
public enum EccMode {
L, M, Q, H
}
 
private enum qrMode {
NULL, KANJI, BINARY, ALPHANUM, NUMERIC
}
 
/* Table 5 - Encoding/Decoding table for Alphanumeric mode */
private static final char[] RHODIUM = { '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', ' ', '$', '%', '*', '+', '-', '.', '/', ':' };
 
private static final int[] QR_ANNEX_C1 = {
/* Micro QR Code format information */
0x4445, 0x4172, 0x4e2b, 0x4b1c, 0x55ae, 0x5099, 0x5fc0, 0x5af7, 0x6793, 0x62a4, 0x6dfd, 0x68ca, 0x7678, 0x734f, 0x7c16, 0x7921, 0x06de, 0x03e9, 0x0cb0, 0x0987, 0x1735, 0x1202, 0x1d5b,
0x186c, 0x2508, 0x203f, 0x2f66, 0x2a51, 0x34e3, 0x31d4, 0x3e8d, 0x3bba };
 
private static final int[] MICRO_QR_SIZES = { 11, 13, 15, 17 };
 
// user-specified values and settings
 
private int preferredVersion;
private EccMode preferredEccLevel = EccMode.L;
 
// internal state calculated when setContent() is called
 
private qrMode[] inputMode;
private StringBuilder binary;
private final int[] binaryCount = new int[4];
private int[] grid;
private int[] eval;
 
/**
* <p>
* Sets the preferred symbol size. This value may be ignored if the data string is too large to
* fit into the specified symbol. Input values correspond to symbol sizes as shown in the
* following table.
*
* <table summary="Range of Micro QR symbol sizes">
* <tbody>
* <tr>
* <th>Input</th>
* <th>Version</th>
* <th>Symbol Size</th>
* </tr>
* <tr>
* <td>1</td>
* <td>M1</td>
* <td>11 x 11</td>
* </tr>
* <tr>
* <td>2</td>
* <td>M2</td>
* <td>13 x 13</td>
* </tr>
* <tr>
* <td>3</td>
* <td>M3</td>
* <td>15 x 15</td>
* </tr>
* <tr>
* <td>4</td>
* <td>M4</td>
* <td>17 x 17</td>
* </tr>
* </tbody>
* </table>
*
* @param version symbol size
*/
public void setPreferredVersion(final int version) {
if (version < 0 || version > 4) { // TODO: min 1
throw new IllegalArgumentException("Invalid version: " + version);
}
this.preferredVersion = version;
}
 
/**
* Returns the preferred symbol size.
*
* @return the preferred symbol size
* @see #setPreferredVersion(int)
*/
public int getPreferredVersion() {
return this.preferredVersion;
}
 
/**
* <p>
* Set the amount of symbol space allocated to error correction. Levels are predefined according
* to the following table:
*
* <table summary="Micro QR Error correction levels">
* <tbody>
* <tr>
* <th>ECC Level</th>
* <th>Error Correction Capacity</th>
* <th>Recovery Capacity</th>
* </tr>
* <tr>
* <td>L (default)</td>
* <td>Approx 20% of symbol</td>
* <td>Approx 7%</td>
* </tr>
* <tr>
* <td>M</td>
* <td>Approx 37% of symbol</td>
* <td>Approx 15%</td>
* </tr>
* <tr>
* <td>Q</td>
* <td>Approx 55% of symbol</td>
* <td>Approx 25%</td>
* </tr>
* <tr>
* <td>H</td>
* <td>Approx 65% of symbol</td>
* <td>Approx 30%</td>
* </tr>
* </tbody>
* </table>
*
* @param eccMode error correction level
*/
public void setEccMode(final EccMode eccMode) {
this.preferredEccLevel = eccMode;
}
 
/**
* Returns the preferred ECC mode (error correction level).
*
* @return the preferred ECC mode
* @see #setEccMode(EccMode)
*/
public EccMode getEccMode() {
return this.preferredEccLevel;
}
 
@Override
protected void encode() {
int i, j, size;
final boolean[] version_valid = new boolean[4];
int n_count, a_count;
EccMode ecc_level;
int version, autoversion;
int bitmask;
int format, format_full;
final StringBuilder bin = new StringBuilder();
boolean byteModeUsed;
boolean alphanumModeUsed;
boolean kanjiModeUsed;
 
if (this.content.length() > 35) {
throw new OkapiException("Input data too long");
}
 
inputCharCheck();
 
for (i = 0; i < 4; i++) {
version_valid[i] = true;
}
 
this.inputMode = new qrMode[40];
selectEncodingMode();
 
n_count = 0;
a_count = 0;
for (i = 0; i < this.content.length(); i++) {
if (this.content.charAt(i) >= '0' && this.content.charAt(i) <= '9') {
n_count++;
}
if (isAlphanumeric(this.content.charAt(i))) {
a_count++;
}
}
 
if (a_count == this.content.length()) {
/* All data can be encoded in Alphanumeric mode */
for (i = 0; i < this.content.length(); i++) {
this.inputMode[i] = qrMode.ALPHANUM;
}
}
 
if (n_count == this.content.length()) {
/* All data can be encoded in Numeric mode */
for (i = 0; i < this.content.length(); i++) {
this.inputMode[i] = qrMode.NUMERIC;
}
}
 
byteModeUsed = false;
alphanumModeUsed = false;
kanjiModeUsed = false;
 
for (i = 0; i < this.content.length(); i++) {
if (this.inputMode[i] == qrMode.BINARY) {
byteModeUsed = true;
}
 
if (this.inputMode[i] == qrMode.ALPHANUM) {
alphanumModeUsed = true;
}
 
if (this.inputMode[i] == qrMode.KANJI) {
kanjiModeUsed = true;
}
}
 
getBinaryLength();
 
/* Eliminate possible versions depending on type of content */
if (byteModeUsed) {
version_valid[0] = false;
version_valid[1] = false;
}
 
if (alphanumModeUsed) {
version_valid[0] = false;
}
 
if (kanjiModeUsed) {
version_valid[0] = false;
version_valid[1] = false;
}
 
/* Eliminate possible versions depending on length of binary data */
if (this.binaryCount[0] > 20) {
version_valid[0] = false;
}
if (this.binaryCount[1] > 40) {
version_valid[1] = false;
}
if (this.binaryCount[2] > 84) {
version_valid[2] = false;
}
if (this.binaryCount[3] > 128) {
throw new OkapiException("Input data too long");
}
 
/* Eliminate possible versions depending on error correction level specified */
ecc_level = this.preferredEccLevel;
 
if (ecc_level == EccMode.H) {
throw new OkapiException("Error correction level H not available");
}
 
if (ecc_level == EccMode.Q) {
version_valid[0] = false;
version_valid[1] = false;
version_valid[2] = false;
if (this.binaryCount[3] > 80) {
throw new OkapiException("Input data too long");
}
}
 
if (ecc_level == EccMode.M) {
version_valid[0] = false;
if (this.binaryCount[1] > 32) {
version_valid[1] = false;
}
if (this.binaryCount[2] > 68) {
version_valid[2] = false;
}
if (this.binaryCount[3] > 112) {
throw new OkapiException("Input data too long");
}
}
 
autoversion = 3;
if (version_valid[2]) {
autoversion = 2;
}
if (version_valid[1]) {
autoversion = 1;
}
if (version_valid[0]) {
autoversion = 0;
}
 
version = autoversion;
/* Get version from user */
if (this.preferredVersion >= 1 && this.preferredVersion <= 4) {
if (this.preferredVersion - 1 >= autoversion) {
version = this.preferredVersion - 1;
}
}
 
/* If there is enough unused space then increase the error correction level */
if (version == 3) {
if (this.binaryCount[3] <= 112) {
ecc_level = EccMode.M;
}
if (this.binaryCount[3] <= 80) {
ecc_level = EccMode.Q;
}
}
 
if (version == 2 && this.binaryCount[2] <= 68) {
ecc_level = EccMode.M;
}
 
if (version == 1 && this.binaryCount[1] <= 32) {
ecc_level = EccMode.M;
}
 
this.binary = new StringBuilder();
generateBinary(version);
if (this.binary.length() > 128) {
throw new OkapiException("Input data too long");
}
 
switch (version) {
case 0:
generateM1Symbol();
infoLine("Version: M1");
break;
case 1:
generateM2Symbol(ecc_level);
infoLine("Version: M2");
infoLine("ECC Level: " + levelToLetter(ecc_level));
break;
case 2:
generateM3Symbol(ecc_level);
infoLine("Version: M3");
infoLine("ECC Level: " + levelToLetter(ecc_level));
break;
case 3:
generateM4Symbol(ecc_level);
infoLine("Version: M4");
infoLine("ECC Level: " + levelToLetter(ecc_level));
break;
}
 
size = MICRO_QR_SIZES[version];
 
this.grid = new int[size * size];
 
for (i = 0; i < size; i++) {
for (j = 0; j < size; j++) {
this.grid[i * size + j] = 0;
}
}
 
setupBitGrid(size);
populateBitGrid(size);
bitmask = applyBitmask(size);
 
infoLine("Mask Pattern: " + Integer.toBinaryString(bitmask));
 
/* Add format data */
format = 0;
switch (version) {
case 1:
switch (ecc_level) {
case L:
format = 1;
break;
case M:
format = 2;
break;
}
break;
case 2:
switch (ecc_level) {
case L:
format = 3;
break;
case M:
format = 4;
break;
}
break;
case 3:
switch (ecc_level) {
case L:
format = 5;
break;
case M:
format = 6;
break;
case Q:
format = 7;
break;
}
break;
}
 
format_full = QR_ANNEX_C1[(format << 2) + bitmask];
 
if ((format_full & 0x4000) != 0) {
this.grid[8 * size + 1] += 0x01;
}
if ((format_full & 0x2000) != 0) {
this.grid[8 * size + 2] += 0x01;
}
if ((format_full & 0x1000) != 0) {
this.grid[8 * size + 3] += 0x01;
}
if ((format_full & 0x800) != 0) {
this.grid[8 * size + 4] += 0x01;
}
if ((format_full & 0x400) != 0) {
this.grid[8 * size + 5] += 0x01;
}
if ((format_full & 0x200) != 0) {
this.grid[8 * size + 6] += 0x01;
}
if ((format_full & 0x100) != 0) {
this.grid[8 * size + 7] += 0x01;
}
if ((format_full & 0x80) != 0) {
this.grid[8 * size + 8] += 0x01;
}
if ((format_full & 0x40) != 0) {
this.grid[7 * size + 8] += 0x01;
}
if ((format_full & 0x20) != 0) {
this.grid[6 * size + 8] += 0x01;
}
if ((format_full & 0x10) != 0) {
this.grid[5 * size + 8] += 0x01;
}
if ((format_full & 0x08) != 0) {
this.grid[4 * size + 8] += 0x01;
}
if ((format_full & 0x04) != 0) {
this.grid[3 * size + 8] += 0x01;
}
if ((format_full & 0x02) != 0) {
this.grid[2 * size + 8] += 0x01;
}
if ((format_full & 0x01) != 0) {
this.grid[1 * size + 8] += 0x01;
}
 
this.readable = "";
this.pattern = new String[size];
this.row_count = size;
this.row_height = new int[size];
for (i = 0; i < size; i++) {
bin.setLength(0);
for (j = 0; j < size; j++) {
if ((this.grid[i * size + j] & 0x01) != 0) {
bin.append('1');
} else {
bin.append('0');
}
}
this.pattern[i] = bin2pat(bin);
this.row_height[i] = 1;
}
}
 
private void inputCharCheck() {
int qmarkBefore, qmarkAfter;
int i;
byte[] temp;
 
/* Check that input includes valid characters */
 
if (this.content.matches("[\u0000-\u00FF]+")) {
/* All characters in ISO 8859-1 */
return;
}
 
/* Otherwise check for Shift-JIS characters */
qmarkBefore = 0;
for (i = 0; i < this.content.length(); i++) {
if (this.content.charAt(i) == '?') {
qmarkBefore++;
}
}
 
try {
temp = this.content.getBytes("SJIS");
} catch (final UnsupportedEncodingException e) {
throw new OkapiException("Character encoding error");
}
 
qmarkAfter = 0;
for (i = 0; i < temp.length; i++) {
if (temp[i] == '?') {
qmarkAfter++;
}
}
 
/* If these values are the same, conversion was successful */
if (qmarkBefore != qmarkAfter) {
throw new OkapiException("Invalid characters in input data");
}
}
 
private char levelToLetter(final EccMode ecc_mode) {
switch (ecc_mode) {
case L:
return 'L';
case M:
return 'M';
case Q:
return 'Q';
case H:
return 'H';
default:
return ' ';
}
}
 
private void selectEncodingMode() {
int i, j;
int mlen;
final int length = this.content.length();
 
for (i = 0; i < length; i++) {
if (this.content.charAt(i) > 0xff) {
this.inputMode[i] = qrMode.KANJI;
} else {
this.inputMode[i] = qrMode.BINARY;
if (isAlphanumeric(this.content.charAt(i))) {
this.inputMode[i] = qrMode.ALPHANUM;
}
if (this.content.charAt(i) >= '0' && this.content.charAt(i) <= '9') {
this.inputMode[i] = qrMode.NUMERIC;
}
}
}
 
/* If less than 6 numeric digits together then don't use numeric mode */
for (i = 0; i < length; i++) {
if (this.inputMode[i] == qrMode.NUMERIC) {
if (i != 0 && this.inputMode[i - 1] != qrMode.NUMERIC || i == 0) {
mlen = 0;
while (mlen + i < length && this.inputMode[mlen + i] == qrMode.NUMERIC) {
mlen++;
}
if (mlen < 6) {
for (j = 0; j < mlen; j++) {
this.inputMode[i + j] = qrMode.ALPHANUM;
}
}
}
}
}
 
/* If less than 4 alphanumeric characters together then don't use alphanumeric mode */
for (i = 0; i < length; i++) {
if (this.inputMode[i] == qrMode.ALPHANUM) {
if (i != 0 && this.inputMode[i - 1] != qrMode.ALPHANUM || i == 0) {
mlen = 0;
while (mlen + i < length && this.inputMode[mlen + i] == qrMode.ALPHANUM) {
mlen++;
}
if (mlen < 6) {
for (j = 0; j < mlen; j++) {
this.inputMode[i + j] = qrMode.BINARY;
}
}
}
}
}
}
 
private boolean isAlphanumeric(final char cglyph) {
/* Returns true if input glyph is in the Alphanumeric set */
boolean retval = false;
 
if (cglyph >= '0' && cglyph <= '9') {
retval = true;
}
if (cglyph >= 'A' && cglyph <= 'Z') {
retval = true;
}
switch (cglyph) {
case ' ':
case '$':
case '%':
case '*':
case '+':
case '-':
case '.':
case '/':
case ':':
retval = true;
break;
}
 
return retval;
}
 
private String toBinary(final int data, int h) {
final StringBuilder binary = new StringBuilder();
for (; h != 0; h >>= 1) {
if ((data & h) != 0) {
binary.append('1');
} else {
binary.append('0');
}
}
return binary.toString();
}
 
private void getBinaryLength() {
int i;
qrMode currentMode = qrMode.NULL;
int blockLength;
 
/* Always include a terminator */
for (i = 0; i < 4; i++) {
this.binaryCount[i] = 0;
}
 
for (i = 0; i < this.content.length(); i++) {
if (currentMode != this.inputMode[i]) {
 
blockLength = 0;
do {
blockLength++;
} while (i + blockLength < this.content.length() && this.inputMode[i + blockLength] == this.inputMode[i]);
 
switch (this.inputMode[i]) {
case KANJI:
this.binaryCount[2] += 5 + blockLength * 13;
this.binaryCount[3] += 7 + blockLength * 13;
 
break;
case BINARY:
this.binaryCount[2] += 6 + blockLength * 8;
this.binaryCount[3] += 8 + blockLength * 8;
break;
case ALPHANUM:
int alphaLength;
 
if (blockLength % 2 == 1) {
/* Odd length block */
alphaLength = (blockLength - 1) / 2 * 11;
alphaLength += 6;
} else {
/* Even length block */
alphaLength = blockLength / 2 * 11;
}
 
this.binaryCount[1] += 4 + alphaLength;
this.binaryCount[2] += 6 + alphaLength;
this.binaryCount[3] += 8 + alphaLength;
break;
case NUMERIC:
int numLength;
 
switch (blockLength % 3) {
case 1:
/* one digit left over */
numLength = (blockLength - 1) / 3 * 10;
numLength += 4;
break;
case 2:
/* two digits left over */
numLength = (blockLength - 2) / 3 * 10;
numLength += 7;
break;
default:
/* blockLength is a multiple of 3 */
numLength = blockLength / 3 * 10;
break;
}
 
this.binaryCount[0] += 3 + numLength;
this.binaryCount[1] += 5 + numLength;
this.binaryCount[2] += 7 + numLength;
this.binaryCount[3] += 9 + numLength;
break;
}
currentMode = this.inputMode[i];
}
}
 
/* Add terminator */
if (this.binaryCount[1] < 37) {
this.binaryCount[1] += 5;
}
 
if (this.binaryCount[2] < 81) {
this.binaryCount[2] += 7;
}
 
if (this.binaryCount[3] < 125) {
this.binaryCount[3] += 9;
}
}
 
private void generateBinary(final int version) {
int position = 0;
int blockLength, i;
qrMode data_block;
int msb, lsb, prod, jis;
String oneChar;
byte[] jisBytes;
int count, first, second, third;
 
info("Encoding: ");
 
do {
data_block = this.inputMode[position];
blockLength = 0;
do {
blockLength++;
} while (blockLength + position < this.content.length() && this.inputMode[position + blockLength] == data_block);
 
switch (data_block) {
case KANJI:
/* Kanji mode */
/* Mode indicator */
switch (version) {
case 2:
this.binary.append("11");
break;
case 3:
this.binary.append("011");
break;
}
 
/* Character count indicator */
this.binary.append(toBinary(blockLength, 1 << version)); /* version = 2..3 */
 
info("KANJ (" + blockLength + ") ");
 
/* Character representation */
for (i = 0; i < blockLength; i++) {
oneChar = "";
oneChar += this.content.charAt(position + i);
 
/* Convert Unicode input to Shift-JIS */
try {
jisBytes = oneChar.getBytes("SJIS");
} catch (final UnsupportedEncodingException e) {
throw new OkapiException("Character encoding error");
}
 
jis = (jisBytes[0] & 0xFF) << 8;
if (jisBytes.length > 1) {
jis += jisBytes[1] & 0xFF;
}
 
if (jis > 0x9fff) {
jis -= 0xc140;
} else {
jis -= 0x8140;
}
msb = (jis & 0xff00) >> 8;
lsb = jis & 0xff;
prod = msb * 0xc0 + lsb;
 
this.binary.append(toBinary(prod, 0x1000));
 
infoSpace(prod);
}
 
break;
case BINARY:
/* Byte mode */
/* Mode indicator */
switch (version) {
case 2:
this.binary.append("10");
break;
case 3:
this.binary.append("010");
break;
}
 
/* Character count indicator */
this.binary.append(toBinary(blockLength, 2 << version)); /* version = 2..3 */
 
info("BYTE (" + blockLength + ") ");
 
/* Character representation */
for (i = 0; i < blockLength; i++) {
final int lbyte = this.content.charAt(position + i);
this.binary.append(toBinary(lbyte, 0x80));
infoSpace(lbyte);
}
 
break;
case ALPHANUM:
/* Alphanumeric mode */
/* Mode indicator */
switch (version) {
case 1:
this.binary.append("1");
break;
case 2:
this.binary.append("01");
break;
case 3:
this.binary.append("001");
break;
}
 
/* Character count indicator */
this.binary.append(toBinary(blockLength, 2 << version)); /* version = 1..3 */
 
info("ALPH (" + blockLength + ") ");
 
/* Character representation */
i = 0;
while (i < blockLength) {
first = positionOf(this.content.charAt(position + i), RHODIUM);
count = 1;
prod = first;
 
if (i + 1 < blockLength) {
if (this.inputMode[position + i + 1] == qrMode.ALPHANUM) {
second = positionOf(this.content.charAt(position + i + 1), RHODIUM);
count = 2;
prod = first * 45 + second;
}
}
 
this.binary.append(toBinary(prod, 1 << 5 * count)); /* count = 1..2 */
 
infoSpace(prod);
 
i += 2;
}
 
break;
case NUMERIC:
/* Numeric mode */
/* Mode indicator */
switch (version) {
case 1:
this.binary.append("0");
break;
case 2:
this.binary.append("00");
break;
case 3:
this.binary.append("000");
break;
}
 
/* Character count indicator */
this.binary.append(toBinary(blockLength, 4 << version)); /* version = 0..3 */
 
info("NUMB (" + blockLength + ") ");
 
/* Character representation */
i = 0;
while (i < blockLength) {
first = Character.getNumericValue(this.content.charAt(position + i));
count = 1;
prod = first;
 
if (i + 1 < blockLength) {
if (this.inputMode[position + i + 1] == qrMode.NUMERIC) {
second = Character.getNumericValue(this.content.charAt(position + i + 1));
count = 2;
prod = prod * 10 + second;
}
}
 
if (i + 2 < blockLength) {
if (this.inputMode[position + i + 2] == qrMode.NUMERIC) {
third = Character.getNumericValue(this.content.charAt(position + i + 2));
count = 3;
prod = prod * 10 + third;
}
}
 
this.binary.append(toBinary(prod, 1 << 3 * count)); /* count = 1..3 */
 
infoSpace(prod);
 
i += 3;
}
break;
}
 
position += blockLength;
} while (position < this.content.length() - 1);
 
/* Add terminator */
switch (version) {
case 0:
this.binary.append("000");
break;
case 1:
if (this.binary.length() < 37) {
this.binary.append("00000");
}
break;
case 2:
if (this.binary.length() < 81) {
this.binary.append("0000000");
}
break;
case 3:
if (this.binary.length() < 125) {
this.binary.append("000000000");
}
break;
}
 
infoLine();
}
 
private void generateM1Symbol() {
int i, latch;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
final int[] data_blocks = new int[4];
final int[] ecc_blocks = new int[3];
final ReedSolomon rs = new ReedSolomon();
 
bits_total = 20;
latch = 0;
 
/* Manage last (4-bit) block */
bits_left = bits_total - this.binary.length();
if (bits_left <= 4) {
for (i = 0; i < bits_left; i++) {
this.binary.append("0");
}
latch = 1;
}
 
if (latch == 0) {
/* Complete current byte */
remainder = 8 - this.binary.length() % 8;
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
this.binary.append("0");
}
 
/* Add padding */
bits_left = bits_total - this.binary.length();
if (bits_left > 4) {
remainder = (bits_left - 4) / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
this.binary.append("00010001");
} else {
this.binary.append("11101100");
}
}
}
this.binary.append("0000");
}
 
data_codewords = 3;
ecc_codewords = 2;
 
/* Copy data into codewords */
for (i = 0; i < data_codewords - 1; i++) {
data_blocks[i] = 0;
if (this.binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (this.binary.charAt(i * 8 + 1) == '1') {
data_blocks[i] += 0x40;
}
if (this.binary.charAt(i * 8 + 2) == '1') {
data_blocks[i] += 0x20;
}
if (this.binary.charAt(i * 8 + 3) == '1') {
data_blocks[i] += 0x10;
}
if (this.binary.charAt(i * 8 + 4) == '1') {
data_blocks[i] += 0x08;
}
if (this.binary.charAt(i * 8 + 5) == '1') {
data_blocks[i] += 0x04;
}
if (this.binary.charAt(i * 8 + 6) == '1') {
data_blocks[i] += 0x02;
}
if (this.binary.charAt(i * 8 + 7) == '1') {
data_blocks[i] += 0x01;
}
}
data_blocks[2] = 0;
if (this.binary.charAt(16) == '1') {
data_blocks[2] += 0x08;
}
if (this.binary.charAt(17) == '1') {
data_blocks[2] += 0x04;
}
if (this.binary.charAt(18) == '1') {
data_blocks[2] += 0x02;
}
if (this.binary.charAt(19) == '1') {
data_blocks[2] += 0x01;
}
 
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
infoSpace(data_blocks[i]);
}
infoLine();
 
/* Calculate Reed-Solomon error codewords */
rs.init_gf(0x11d);
rs.init_code(ecc_codewords, 0);
rs.encode(data_codewords, data_blocks);
for (i = 0; i < ecc_codewords; i++) {
ecc_blocks[i] = rs.getResult(i);
}
 
/* Add Reed-Solomon codewords to binary data */
for (i = 0; i < ecc_codewords; i++) {
this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
}
}
 
private void generateM2Symbol(final EccMode ecc_mode) {
int i;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
final int[] data_blocks = new int[6];
final int[] ecc_blocks = new int[7];
final ReedSolomon rs = new ReedSolomon();
 
bits_total = 40; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
bits_total = 32;
}
 
/* Complete current byte */
remainder = 8 - this.binary.length() % 8;
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
this.binary.append("0");
}
 
/* Add padding */
bits_left = bits_total - this.binary.length();
remainder = bits_left / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
this.binary.append("00010001");
} else {
this.binary.append("11101100");
}
}
 
data_codewords = 5;
ecc_codewords = 5; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
data_codewords = 4;
ecc_codewords = 6;
}
 
/* Copy data into codewords */
for (i = 0; i < data_codewords; i++) {
data_blocks[i] = 0;
if (this.binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (this.binary.charAt(i * 8 + 1) == '1') {
data_blocks[i] += 0x40;
}
if (this.binary.charAt(i * 8 + 2) == '1') {
data_blocks[i] += 0x20;
}
if (this.binary.charAt(i * 8 + 3) == '1') {
data_blocks[i] += 0x10;
}
if (this.binary.charAt(i * 8 + 4) == '1') {
data_blocks[i] += 0x08;
}
if (this.binary.charAt(i * 8 + 5) == '1') {
data_blocks[i] += 0x04;
}
if (this.binary.charAt(i * 8 + 6) == '1') {
data_blocks[i] += 0x02;
}
if (this.binary.charAt(i * 8 + 7) == '1') {
data_blocks[i] += 0x01;
}
}
 
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
infoSpace(data_blocks[i]);
}
infoLine();
 
/* Calculate Reed-Solomon error codewords */
rs.init_gf(0x11d);
rs.init_code(ecc_codewords, 0);
rs.encode(data_codewords, data_blocks);
for (i = 0; i < ecc_codewords; i++) {
ecc_blocks[i] = rs.getResult(i);
}
 
/* Add Reed-Solomon codewords to binary data */
for (i = 0; i < ecc_codewords; i++) {
this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
}
}
 
private void generateM3Symbol(final EccMode ecc_mode) {
int i, latch;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
final int[] data_blocks = new int[12];
final int[] ecc_blocks = new int[12];
final ReedSolomon rs = new ReedSolomon();
 
latch = 0;
 
bits_total = 84; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
bits_total = 68;
}
 
/* Manage last (4-bit) block */
bits_left = bits_total - this.binary.length();
if (bits_left <= 4) {
for (i = 0; i < bits_left; i++) {
this.binary.append("0");
}
latch = 1;
}
 
if (latch == 0) {
/* Complete current byte */
remainder = 8 - this.binary.length() % 8;
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
this.binary.append("0");
}
 
/* Add padding */
bits_left = bits_total - this.binary.length();
if (bits_left > 4) {
remainder = (bits_left - 4) / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
this.binary.append("00010001");
} else {
this.binary.append("11101100");
}
}
}
this.binary.append("0000");
}
 
data_codewords = 11;
ecc_codewords = 6; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
data_codewords = 9;
ecc_codewords = 8;
}
 
/* Copy data into codewords */
for (i = 0; i < data_codewords - 1; i++) {
data_blocks[i] = 0;
if (this.binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (this.binary.charAt(i * 8 + 1) == '1') {
data_blocks[i] += 0x40;
}
if (this.binary.charAt(i * 8 + 2) == '1') {
data_blocks[i] += 0x20;
}
if (this.binary.charAt(i * 8 + 3) == '1') {
data_blocks[i] += 0x10;
}
if (this.binary.charAt(i * 8 + 4) == '1') {
data_blocks[i] += 0x08;
}
if (this.binary.charAt(i * 8 + 5) == '1') {
data_blocks[i] += 0x04;
}
if (this.binary.charAt(i * 8 + 6) == '1') {
data_blocks[i] += 0x02;
}
if (this.binary.charAt(i * 8 + 7) == '1') {
data_blocks[i] += 0x01;
}
}
 
if (ecc_mode == EccMode.L) {
data_blocks[10] = 0;
if (this.binary.charAt(80) == '1') {
data_blocks[10] += 0x08;
}
if (this.binary.charAt(81) == '1') {
data_blocks[10] += 0x04;
}
if (this.binary.charAt(82) == '1') {
data_blocks[10] += 0x02;
}
if (this.binary.charAt(83) == '1') {
data_blocks[10] += 0x01;
}
}
 
if (ecc_mode == EccMode.M) {
data_blocks[8] = 0;
if (this.binary.charAt(64) == '1') {
data_blocks[8] += 0x08;
}
if (this.binary.charAt(65) == '1') {
data_blocks[8] += 0x04;
}
if (this.binary.charAt(66) == '1') {
data_blocks[8] += 0x02;
}
if (this.binary.charAt(67) == '1') {
data_blocks[8] += 0x01;
}
}
 
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
infoSpace(data_blocks[i]);
}
infoLine();
 
/* Calculate Reed-Solomon error codewords */
rs.init_gf(0x11d);
rs.init_code(ecc_codewords, 0);
rs.encode(data_codewords, data_blocks);
for (i = 0; i < ecc_codewords; i++) {
ecc_blocks[i] = rs.getResult(i);
}
 
/* Add Reed-Solomon codewords to binary data */
for (i = 0; i < ecc_codewords; i++) {
this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
}
}
 
private void generateM4Symbol(final EccMode ecc_mode) {
int i;
int bits_total, bits_left, remainder;
int data_codewords, ecc_codewords;
final int[] data_blocks = new int[17];
final int[] ecc_blocks = new int[15];
final ReedSolomon rs = new ReedSolomon();
 
bits_total = 128; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
bits_total = 112;
}
if (ecc_mode == EccMode.Q) {
bits_total = 80;
}
 
/* Complete current byte */
remainder = 8 - this.binary.length() % 8;
if (remainder == 8) {
remainder = 0;
}
for (i = 0; i < remainder; i++) {
this.binary.append("0");
}
 
/* Add padding */
bits_left = bits_total - this.binary.length();
remainder = bits_left / 8;
for (i = 0; i < remainder; i++) {
if ((i & 1) != 0) {
this.binary.append("00010001");
} else {
this.binary.append("11101100");
}
}
 
data_codewords = 16;
ecc_codewords = 8; // ecc_mode == EccMode.L
if (ecc_mode == EccMode.M) {
data_codewords = 14;
ecc_codewords = 10;
}
if (ecc_mode == EccMode.Q) {
data_codewords = 10;
ecc_codewords = 14;
}
 
/* Copy data into codewords */
for (i = 0; i < data_codewords; i++) {
data_blocks[i] = 0;
if (this.binary.charAt(i * 8) == '1') {
data_blocks[i] += 0x80;
}
if (this.binary.charAt(i * 8 + 1) == '1') {
data_blocks[i] += 0x40;
}
if (this.binary.charAt(i * 8 + 2) == '1') {
data_blocks[i] += 0x20;
}
if (this.binary.charAt(i * 8 + 3) == '1') {
data_blocks[i] += 0x10;
}
if (this.binary.charAt(i * 8 + 4) == '1') {
data_blocks[i] += 0x08;
}
if (this.binary.charAt(i * 8 + 5) == '1') {
data_blocks[i] += 0x04;
}
if (this.binary.charAt(i * 8 + 6) == '1') {
data_blocks[i] += 0x02;
}
if (this.binary.charAt(i * 8 + 7) == '1') {
data_blocks[i] += 0x01;
}
}
 
info("Codewords: ");
for (i = 0; i < data_codewords; i++) {
infoSpace(data_blocks[i]);
}
infoLine();
 
/* Calculate Reed-Solomon error codewords */
rs.init_gf(0x11d);
rs.init_code(ecc_codewords, 0);
rs.encode(data_codewords, data_blocks);
for (i = 0; i < ecc_codewords; i++) {
ecc_blocks[i] = rs.getResult(i);
}
 
/* Add Reed-Solomon codewords to binary data */
for (i = 0; i < ecc_codewords; i++) {
this.binary.append(toBinary(ecc_blocks[ecc_codewords - i - 1], 0x80));
}
}
 
private void setupBitGrid(final int size) {
int i, toggle = 1;
 
/* Add timing patterns */
for (i = 0; i < size; i++) {
if (toggle == 1) {
this.grid[i] = 0x21;
this.grid[i * size] = 0x21;
toggle = 0;
} else {
this.grid[i] = 0x20;
this.grid[i * size] = 0x20;
toggle = 1;
}
}
 
/* Add finder patterns */
placeFinderPattern(size, 0, 0);
 
/* Add separators */
for (i = 0; i < 7; i++) {
this.grid[7 * size + i] = 0x10;
this.grid[i * size + 7] = 0x10;
}
this.grid[7 * size + 7] = 0x10;
 
/* Reserve space for format information */
for (i = 0; i < 8; i++) {
this.grid[8 * size + i] += 0x20;
this.grid[i * size + 8] += 0x20;
}
this.grid[8 * size + 8] += 0x20;
}
 
private void placeFinderPattern(final int size, final int x, final int y) {
int xp, yp;
 
final int[] finder = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 };
 
for (xp = 0; xp < 7; xp++) {
for (yp = 0; yp < 7; yp++) {
if (finder[xp + 7 * yp] == 1) {
this.grid[(yp + y) * size + xp + x] = 0x11;
} else {
this.grid[(yp + y) * size + xp + x] = 0x10;
}
}
}
}
 
private void populateBitGrid(final int size) {
boolean goingUp = true;
int row = 0; /* right hand side */
 
int i, n, x, y;
 
n = this.binary.length();
y = size - 1;
i = 0;
do {
x = size - 2 - row * 2;
 
if ((this.grid[y * size + x + 1] & 0xf0) == 0) {
if (this.binary.charAt(i) == '1') {
this.grid[y * size + x + 1] = 0x01;
} else {
this.grid[y * size + x + 1] = 0x00;
}
i++;
}
 
if (i < n) {
if ((this.grid[y * size + x] & 0xf0) == 0) {
if (this.binary.charAt(i) == '1') {
this.grid[y * size + x] = 0x01;
} else {
this.grid[y * size + x] = 0x00;
}
i++;
}
}
 
if (goingUp) {
y--;
} else {
y++;
}
if (y == 0) {
/* reached the top */
row++;
y = 1;
goingUp = false;
}
if (y == size) {
/* reached the bottom */
row++;
y = size - 1;
goingUp = true;
}
} while (i < n);
}
 
private int applyBitmask(final int size) {
int x, y;
int p;
int local_pattern;
final int[] value = new int[8];
int best_val, best_pattern;
int bit;
 
final int[] mask = new int[size * size];
this.eval = new int[size * size];
 
/* Perform data masking */
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
mask[y * size + x] = 0x00;
 
if ((this.grid[y * size + x] & 0xf0) == 0) {
if ((y & 1) == 0) {
mask[y * size + x] += 0x01;
}
 
if ((y / 2 + x / 3 & 1) == 0) {
mask[y * size + x] += 0x02;
}
 
if (((y * x & 1) + y * x % 3 & 1) == 0) {
mask[y * size + x] += 0x04;
}
 
if (((y + x & 1) + y * x % 3 & 1) == 0) {
mask[y * size + x] += 0x08;
}
}
}
}
 
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
if ((this.grid[y * size + x] & 0x01) != 0) {
p = 0xff;
} else {
p = 0x00;
}
 
this.eval[y * size + x] = mask[y * size + x] ^ p;
}
}
 
/* Evaluate result */
for (local_pattern = 0; local_pattern < 4; local_pattern++) {
value[local_pattern] = evaluateBitmask(size, local_pattern);
}
 
best_pattern = 0;
best_val = value[0];
for (local_pattern = 1; local_pattern < 4; local_pattern++) {
if (value[local_pattern] > best_val) {
best_pattern = local_pattern;
best_val = value[local_pattern];
}
}
 
/* Apply mask */
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
bit = 0;
switch (best_pattern) {
case 0:
if ((mask[y * size + x] & 0x01) != 0) {
bit = 1;
}
break;
case 1:
if ((mask[y * size + x] & 0x02) != 0) {
bit = 1;
}
break;
case 2:
if ((mask[y * size + x] & 0x04) != 0) {
bit = 1;
}
break;
case 3:
if ((mask[y * size + x] & 0x08) != 0) {
bit = 1;
}
break;
}
if (bit == 1) {
if ((this.grid[y * size + x] & 0x01) != 0) {
this.grid[y * size + x] = 0x00;
} else {
this.grid[y * size + x] = 0x01;
}
}
}
}
 
return best_pattern;
}
 
private int evaluateBitmask(final int size, final int pattern) {
int sum1, sum2, i, filter = 0, retval;
 
switch (pattern) {
case 0:
filter = 0x01;
break;
case 1:
filter = 0x02;
break;
case 2:
filter = 0x04;
break;
case 3:
filter = 0x08;
break;
}
 
sum1 = 0;
sum2 = 0;
for (i = 1; i < size; i++) {
if ((this.eval[i * size + size - 1] & filter) != 0) {
sum1++;
}
if ((this.eval[(size - 1) * size + i] & filter) != 0) {
sum2++;
}
}
 
if (sum1 <= sum2) {
retval = sum1 * 16 + sum2;
} else {
retval = sum2 * 16 + sum1;
}
 
return retval;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Pharmacode2Track.java
New file
0,0 → 1,117
/*
* 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 java.awt.geom.Rectangle2D;
 
/**
* Implements the Two-Track Pharmacode bar code symbology. <br>
* Pharmacode Two-Track is an alternative system to Pharmacode One-Track used for the identification
* of pharmaceuticals. The symbology is able to encode whole numbers between 4 and 64570080.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class Pharmacode2Track extends Symbol {
 
@Override
protected void encode() {
int i, tester = 0;
String inter, dest;
 
if (this.content.length() > 8) {
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 < 4 || tester > 64570080) {
throw new OkapiException("Data out of range");
}
 
inter = "";
do {
switch (tester % 3) {
case 0:
inter += "F";
tester = (tester - 3) / 3;
break;
case 1:
inter += "D";
tester = (tester - 1) / 3;
break;
case 2:
inter += "A";
tester = (tester - 2) / 3;
break;
}
} while (tester != 0);
 
dest = "";
for (i = inter.length() - 1; i >= 0; i--) {
dest += inter.charAt(i);
}
 
infoLine("Encoding: " + dest);
 
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;
}
 
@Override
protected void plotSymbol() {
int xBlock;
int x, y, w, h;
 
this.rectangles.clear();
x = 0;
w = 1;
y = 0;
h = 0;
for (xBlock = 0; xBlock < this.pattern[0].length(); xBlock++) {
switch (this.pattern[0].charAt(xBlock)) {
case 'A':
y = 0;
h = this.default_height / 2;
break;
case 'D':
y = this.default_height / 2;
h = this.default_height / 2;
break;
case 'F':
y = 0;
h = this.default_height;
break;
}
 
final Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h);
this.rectangles.add(rect);
 
x += 2;
}
this.symbol_width = this.pattern[0].length() * 2;
this.symbol_height = this.default_height;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Code11.java
New file
0,0 → 1,215
/*
* Copyright 2014 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;
 
/**
* <p>
* Implements Code 11 bar code symbology.
*
* <p>
* Code 11 can encode any length string consisting of the digits 0-9 and the dash character (-). One
* or two modulo-11 check digits are calculated.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
* @author Daniel Gredler
*/
public class Code11 extends Symbol {
 
private static final String[] CODE_11_TABLE = { "111121", "211121", "121121", "221111", "112121", "212111", "122111", "111221", "211211", "211111", "112111" };
 
private static final char[] CHARACTER_SET = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' };
 
/** Ratio of wide bar width to narrow bar width. */
private double moduleWidthRatio = 2;
 
/** The number of check digits to calculate ({@code 1} or {@code 2}). */
private int checkDigitCount = 2;
 
/** Optional start delimiter to be shown in the human-readable text. */
private Character startDelimiter;
 
/** Optional stop delimiter to be shown in the human-readable text. */
private Character stopDelimiter;
 
/**
* Sets the ratio of wide bar width to narrow bar width. Valid values are usually between
* {@code 2} and {@code 3}. The default value is {@code 2}.
*
* @param moduleWidthRatio the ratio of wide bar width to narrow bar width
*/
public void setModuleWidthRatio(final double moduleWidthRatio) {
this.moduleWidthRatio = moduleWidthRatio;
}
 
/**
* Returns the ratio of wide bar width to narrow bar width.
*
* @return the ratio of wide bar width to narrow bar width
*/
public double getModuleWidthRatio() {
return this.moduleWidthRatio;
}
 
/**
* Sets the number of check digits to calculate ({@code 1} or {@code 2}). The default value is
* {@code 2}.
*
* @param checkDigitCount the number of check digits to calculate
*/
public void setCheckDigitCount(final int checkDigitCount) {
if (checkDigitCount < 1 || checkDigitCount > 2) {
throw new IllegalArgumentException("Check digit count must be 1 or 2.");
}
this.checkDigitCount = checkDigitCount;
}
 
/**
* Returns the number of check digits to calculate (1 or 2).
*
* @return the number of check digits to calculate
*/
public int getCheckDigitCount() {
return this.checkDigitCount;
}
 
/**
* Sets an optional start delimiter to be shown in the human-readable text (defaults to
* <code>null</code>).
*
* @param startDelimiter an optional start delimiter to be shown in the human-readable text
*/
public void setStartDelimiter(final Character startDelimiter) {
this.startDelimiter = startDelimiter;
}
 
/**
* Returns the optional start delimiter to be shown in the human-readable text.
*
* @return the optional start delimiter to be shown in the human-readable text
*/
public Character getStartDelimiter() {
return this.startDelimiter;
}
 
/**
* Sets an optional stop delimiter to be shown in the human-readable text (defaults to
* <code>null</code>).
*
* @param stopDelimiter an optional stop delimiter to be shown in the human-readable text
*/
public void setStopDelimiter(final Character stopDelimiter) {
this.stopDelimiter = stopDelimiter;
}
 
/**
* Returns the optional stop delimiter to be shown in the human-readable text.
*
* @return the optional stop delimiter to be shown in the human-readable text
*/
public Character getStopDelimiter() {
return this.stopDelimiter;
}
 
/** {@inheritDoc} */
@Override
protected void encode() {
 
if (!this.content.matches("[0-9-]+")) {
throw new OkapiException("Invalid characters in input");
}
 
String horizontalSpacing = "112211";
String humanReadable = this.content;
final int length = this.content.length();
final int[] weight = new int[length + 1];
 
for (int i = 0; i < length; i++) {
final char c = this.content.charAt(i);
weight[i] = positionOf(c, CHARACTER_SET);
horizontalSpacing += CODE_11_TABLE[weight[i]];
}
 
final int checkDigitC = getCheckDigitC(weight, length);
horizontalSpacing += CODE_11_TABLE[checkDigitC];
humanReadable += CHARACTER_SET[checkDigitC];
infoLine("Check Digit C: " + checkDigitC);
 
if (this.checkDigitCount == 2) {
weight[length] = checkDigitC;
final int checkDigitK = getCheckDigitK(weight, length + 1);
horizontalSpacing += CODE_11_TABLE[checkDigitK];
humanReadable += CHARACTER_SET[checkDigitK];
infoLine("Check Digit K: " + checkDigitK);
}
 
horizontalSpacing += "112211";
 
this.readable = humanReadable;
if (this.startDelimiter != null) {
this.readable = this.startDelimiter + this.readable;
}
if (this.stopDelimiter != null) {
this.readable = this.readable + this.stopDelimiter;
}
 
this.pattern = new String[] { horizontalSpacing };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private static int getCheckDigitC(final int[] weight, final int length) {
int countC = 0;
int weightC = 1;
for (int i = length - 1; i >= 0; i--) {
countC += weightC * weight[i];
weightC++;
if (weightC > 10) {
weightC = 1;
}
}
return countC % 11;
}
 
private static int getCheckDigitK(final int[] weight, final int length) {
int countK = 0;
int weightK = 1;
for (int i = length - 1; i >= 0; i--) {
countK += weightK * weight[i];
weightK++;
if (weightK > 9) {
weightK = 1;
}
}
return countK % 11;
}
 
/** {@inheritDoc} */
@Override
protected double getModuleWidth(final int originalWidth) {
if (originalWidth == 1) {
return 1;
} else {
return this.moduleWidthRatio;
}
}
 
/** {@inheritDoc} */
@Override
protected int[] getCodewords() {
return getPatternAsCodewords(6);
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/QrCode.java
New file
0,0 → 1,1692
/*
* Copyright 2014 Robin Stuart
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package uk.org.okapibarcode.backend;
 
import static uk.org.okapibarcode.util.Arrays.positionOf;
 
import java.nio.CharBuffer;
import java.nio.charset.Charset;
 
/**
* <p>
* Implements QR Code bar code symbology According to ISO/IEC 18004:2015.
*
* <p>
* The maximum capacity of a (version 40) QR Code symbol is 7089 numeric digits, 4296 alphanumeric
* characters or 2953 bytes of data. QR Code symbols can also be used to encode GS1 data. QR Code
* symbols can encode characters in the Latin-1 set and Kanji characters which are members of the
* Shift-JIS encoding scheme.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class QrCode extends Symbol {
 
public enum EccLevel {
L, M, Q, H
}
 
private enum QrMode {
NULL, KANJI, BINARY, ALPHANUM, NUMERIC
}
 
/* Table 5 - Encoding/Decoding table for Alphanumeric mode */
private static final char[] RHODIUM = { '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', ' ', '$', '%', '*', '+', '-', '.', '/', ':' };
 
private static final int[] QR_DATA_CODEWORDS_L = { 19, 34, 55, 80, 108, 136, 156, 194, 232, 274, 324, 370, 428, 461, 523, 589, 647, 721, 795, 861, 932, 1006, 1094, 1174, 1276, 1370, 1468, 1531,
1631, 1735, 1843, 1955, 2071, 2191, 2306, 2434, 2566, 2702, 2812, 2956 };
 
private static final int[] QR_DATA_CODEWORDS_M = { 16, 28, 44, 64, 86, 108, 124, 154, 182, 216, 254, 290, 334, 365, 415, 453, 507, 563, 627, 669, 714, 782, 860, 914, 1000, 1062, 1128, 1193, 1267,
1373, 1455, 1541, 1631, 1725, 1812, 1914, 1992, 2102, 2216, 2334 };
 
private static final int[] QR_DATA_CODEWORDS_Q = { 13, 22, 34, 48, 62, 76, 88, 110, 132, 154, 180, 206, 244, 261, 295, 325, 367, 397, 445, 485, 512, 568, 614, 664, 718, 754, 808, 871, 911, 985,
1033, 1115, 1171, 1231, 1286, 1354, 1426, 1502, 1582, 1666 };
 
private static final int[] QR_DATA_CODEWORDS_H = { 9, 16, 26, 36, 46, 60, 66, 86, 100, 122, 140, 158, 180, 197, 223, 253, 283, 313, 341, 385, 406, 442, 464, 514, 538, 596, 628, 661, 701, 745, 793,
845, 901, 961, 986, 1054, 1096, 1142, 1222, 1276 };
 
private static final int[] QR_BLOCKS_L = { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25 };
 
private static final int[] QR_BLOCKS_M = { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49 };
 
private static final int[] QR_BLOCKS_Q = { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68 };
 
private static final int[] QR_BLOCKS_H = { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81 };
 
private static final int[] QR_TOTAL_CODEWORDS = { 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085, 1156, 1258, 1364, 1474, 1588, 1706, 1828, 1921,
2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706 };
 
private static final int[] QR_SIZES = { 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 129, 133, 137, 141, 145, 149, 153, 157,
161, 165, 169, 173, 177 };
 
private static final int[] QR_ALIGN_LOOPSIZE = { 0, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7 };
 
private static final int[] QR_TABLE_E1 = { 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0,
0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0,
0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54,
80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56,
82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132,
158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 };
 
private static final int[] QR_ANNEX_C = {
/* Format information bit sequences */
0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c,
0x083b, 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed };
 
private static final int[] QR_ANNEX_D = {
/* Version information bit sequences */
0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e,
0x1cc1a, 0x1d33f, 0x1ed75, 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, 0x27541, 0x28c69 };
 
private int preferredVersion;
private EccLevel preferredEccLevel = EccLevel.L;
 
/**
* Sets the preferred symbol size / version. This value may be ignored if the data string is too
* large to fit into the specified symbol. Input values correspond to symbol sizes as shown in
* the following table:
*
* <table summary="Available QR Code sizes">
* <tbody>
* <tr>
* <th>Input</th>
* <th>Symbol Size</th>
* <th>Input</th>
* <th>Symbol Size</th>
* </tr>
* <tr>
* <td>1</td>
* <td>21 x 21</td>
* <td>21</td>
* <td>101 x 101</td>
* </tr>
* <tr>
* <td>2</td>
* <td>25 x 25</td>
* <td>22</td>
* <td>105 x 105</td>
* </tr>
* <tr>
* <td>3</td>
* <td>29 x 29</td>
* <td>23</td>
* <td>109 x 109</td>
* </tr>
* <tr>
* <td>4</td>
* <td>33 x 33</td>
* <td>24</td>
* <td>113 x 113</td>
* </tr>
* <tr>
* <td>5</td>
* <td>37 x 37</td>
* <td>25</td>
* <td>117 x 117</td>
* </tr>
* <tr>
* <td>6</td>
* <td>41 x 41</td>
* <td>26</td>
* <td>121 x 121</td>
* </tr>
* <tr>
* <td>7</td>
* <td>45 x 45</td>
* <td>27</td>
* <td>125 x 125</td>
* </tr>
* <tr>
* <td>8</td>
* <td>49 x 49</td>
* <td>28</td>
* <td>129 x 129</td>
* </tr>
* <tr>
* <td>9</td>
* <td>53 x 53</td>
* <td>29</td>
* <td>133 x 133</td>
* </tr>
* <tr>
* <td>10</td>
* <td>57 x 57</td>
* <td>30</td>
* <td>137 x 137</td>
* </tr>
* <tr>
* <td>11</td>
* <td>61 x 61</td>
* <td>31</td>
* <td>141 x 141</td>
* </tr>
* <tr>
* <td>12</td>
* <td>65 x 65</td>
* <td>32</td>
* <td>145 x 145</td>
* </tr>
* <tr>
* <td>13</td>
* <td>69 x 69</td>
* <td>33</td>
* <td>149 x 149</td>
* </tr>
* <tr>
* <td>14</td>
* <td>73 x 73</td>
* <td>34</td>
* <td>153 x 153</td>
* </tr>
* <tr>
* <td>15</td>
* <td>77 x 77</td>
* <td>35</td>
* <td>157 x 157</td>
* </tr>
* <tr>
* <td>16</td>
* <td>81 x 81</td>
* <td>36</td>
* <td>161 x 161</td>
* </tr>
* <tr>
* <td>17</td>
* <td>85 x 85</td>
* <td>37</td>
* <td>165 x 165</td>
* </tr>
* <tr>
* <td>18</td>
* <td>89 x 89</td>
* <td>38</td>
* <td>169 x 169</td>
* </tr>
* <tr>
* <td>19</td>
* <td>93 x 93</td>
* <td>39</td>
* <td>173 x 173</td>
* </tr>
* <tr>
* <td>20</td>
* <td>97 x 97</td>
* <td>40</td>
* <td>177 x 177</td>
* </tr>
* </tbody>
* </table>
*
* @param version the preferred symbol version
*/
public void setPreferredVersion(final int version) {
this.preferredVersion = version;
}
 
/**
* Returns the preferred symbol version.
*
* @return the preferred symbol version
*/
public int getPreferredVersion() {
return this.preferredVersion;
}
 
/**
* Sets the preferred amount of symbol space allocated to error correction. This value may be
* ignored if there is room for a higher error correction level. Levels are predefined according
* to the following table:
*
* <table summary="QR Code error correction levels">
* <tbody>
* <tr>
* <th>ECC Level</th>
* <th>Error Correction Capacity</th>
* <th>Recovery Capacity</th>
* </tr>
* <tr>
* <td>L (default)</td>
* <td>Approx 20% of symbol</td>
* <td>Approx 7%</td>
* </tr>
* <tr>
* <td>M</td>
* <td>Approx 37% of symbol</td>
* <td>Approx 15%</td>
* </tr>
* <tr>
* <td>Q</td>
* <td>Approx 55% of symbol</td>
* <td>Approx 25%</td>
* </tr>
* <tr>
* <td>H</td>
* <td>Approx 65% of symbol</td>
* <td>Approx 30%</td>
* </tr>
* </tbody>
* </table>
*
* @param preferredEccLevel the preferred error correction level
*/
public void setPreferredEccLevel(final EccLevel preferredEccLevel) {
this.preferredEccLevel = preferredEccLevel;
}
 
/**
* Returns the preferred amount of symbol space allocated to error correction.
*
* @return the preferred amount of symbol space allocated to error correction
*/
public EccLevel getPreferredEccLevel() {
return this.preferredEccLevel;
}
 
@Override
protected boolean gs1Supported() {
return true;
}
 
@Override
protected void encode() {
 
int i, j;
int est_binlen;
EccLevel ecc_level;
int max_cw;
int targetCwCount, version, blocks;
int size;
int bitmask;
final boolean gs1 = this.inputDataType == DataType.GS1;
 
eciProcess(); // Get ECI mode
 
if (this.eciMode == 20) {
/* Shift-JIS encoding, use Kanji mode */
final Charset c = Charset.forName("Shift_JIS");
this.inputData = new int[this.content.length()];
for (i = 0; i < this.inputData.length; i++) {
final CharBuffer buffer = CharBuffer.wrap(this.content, i, i + 1);
final byte[] bytes = c.encode(buffer).array();
final int value = bytes.length == 2 ? (bytes[0] & 0xff) << 8 | bytes[1] & 0xff : bytes[0];
this.inputData[i] = value;
}
} else {
/* inputData already initialized in eciProcess() */
}
 
QrMode[] inputMode = new QrMode[this.inputData.length];
defineMode(inputMode, this.inputData);
est_binlen = getBinaryLength(40, inputMode, this.inputData, gs1, this.eciMode);
 
ecc_level = this.preferredEccLevel;
switch (this.preferredEccLevel) {
case L:
default:
max_cw = 2956;
break;
case M:
max_cw = 2334;
break;
case Q:
max_cw = 1666;
break;
case H:
max_cw = 1276;
break;
}
 
if (est_binlen > 8 * max_cw) {
throw new OkapiException("Input too long for selected error correction level");
}
 
// ZINT NOTE: this block is different from the corresponding block of code in Zint;
// it is simplified, but the simplification required that the applyOptimisation method
// be changed to be free of side effects (by putting the optimized mode array into a
// new array instead of modifying the existing array)
 
version = 40;
for (i = 39; i >= 0; i--) {
int[] dataCodewords;
switch (ecc_level) {
case L:
default:
dataCodewords = QR_DATA_CODEWORDS_L;
break;
case M:
dataCodewords = QR_DATA_CODEWORDS_M;
break;
case Q:
dataCodewords = QR_DATA_CODEWORDS_Q;
break;
case H:
dataCodewords = QR_DATA_CODEWORDS_H;
break;
}
final int proposedVersion = i + 1;
final int proposedBinLen = getBinaryLength(proposedVersion, inputMode, this.inputData, gs1, this.eciMode);
if (8 * dataCodewords[i] >= proposedBinLen) {
version = proposedVersion;
est_binlen = proposedBinLen;
}
}
 
inputMode = applyOptimisation(version, inputMode);
 
// ZINT NOTE: end of block of code that is different
 
// TODO: delete this
//
// autosize = 40;
// for (i = 39; i >= 0; i--) {
// switch (ecc_level) {
// case L:
// if ((8 * QR_DATA_CODEWORDS_L[i]) >= est_binlen) {
// autosize = i + 1;
// }
// break;
// case M:
// if ((8 * QR_DATA_CODEWORDS_M[i]) >= est_binlen) {
// autosize = i + 1;
// }
// break;
// case Q:
// if ((8 * QR_DATA_CODEWORDS_Q[i]) >= est_binlen) {
// autosize = i + 1;
// }
// break;
// case H:
// if ((8 * QR_DATA_CODEWORDS_H[i]) >= est_binlen) {
// autosize = i + 1;
// }
// break;
// }
// }
//
// // Now see if the optimized binary will fit in a smaller symbol.
// canShrink = true;
//
// do {
// if (autosize == 1) {
// est_binlen = getBinaryLength(autosize, inputMode, inputData, gs1, eciMode); // TODO:
// added
// canShrink = false;
// } else {
// est_binlen = getBinaryLength(autosize - 1, inputMode, inputData, gs1, eciMode);
//
// switch (ecc_level) {
// case L:
// if ((8 * QR_DATA_CODEWORDS_L[autosize - 2]) < est_binlen) {
// canShrink = false;
// }
// break;
// case M:
// if ((8 * QR_DATA_CODEWORDS_M[autosize - 2]) < est_binlen) {
// canShrink = false;
// }
// break;
// case Q:
// if ((8 * QR_DATA_CODEWORDS_Q[autosize - 2]) < est_binlen) {
// canShrink = false;
// }
// break;
// case H:
// if ((8 * QR_DATA_CODEWORDS_H[autosize - 2]) < est_binlen) {
// canShrink = false;
// }
// break;
// }
//
// if (canShrink) {
// // Optimization worked - data will fit in a smaller symbol
// autosize--;
// } else {
// // Data did not fit in the smaller symbol, revert to original size
// est_binlen = getBinaryLength(autosize, inputMode, inputData, gs1, eciMode);
// }
// }
// } while (canShrink);
//
// version = autosize;
 
if (this.preferredVersion >= 1 && this.preferredVersion <= 40) {
/*
* If the user has selected a larger symbol than the smallest available, then use the
* size the user has selected, and re-optimize for this symbol size.
*/
if (this.preferredVersion > version) {
version = this.preferredVersion;
est_binlen = getBinaryLength(this.preferredVersion, inputMode, this.inputData, gs1, this.eciMode);
inputMode = applyOptimisation(version, inputMode);
}
if (this.preferredVersion < version) {
throw new OkapiException("Input too long for selected symbol size");
}
}
 
/* Ensure maximum error correction capacity */
if (est_binlen <= QR_DATA_CODEWORDS_M[version - 1] * 8) {
ecc_level = EccLevel.M;
}
if (est_binlen <= QR_DATA_CODEWORDS_Q[version - 1] * 8) {
ecc_level = EccLevel.Q;
}
if (est_binlen <= QR_DATA_CODEWORDS_H[version - 1] * 8) {
ecc_level = EccLevel.H;
}
 
targetCwCount = QR_DATA_CODEWORDS_L[version - 1];
blocks = QR_BLOCKS_L[version - 1];
switch (ecc_level) {
case M:
targetCwCount = QR_DATA_CODEWORDS_M[version - 1];
blocks = QR_BLOCKS_M[version - 1];
break;
case Q:
targetCwCount = QR_DATA_CODEWORDS_Q[version - 1];
blocks = QR_BLOCKS_Q[version - 1];
break;
case H:
targetCwCount = QR_DATA_CODEWORDS_H[version - 1];
blocks = QR_BLOCKS_H[version - 1];
break;
}
 
final int[] datastream = new int[targetCwCount + 1];
final int[] fullstream = new int[QR_TOTAL_CODEWORDS[version - 1] + 1];
 
qrBinary(datastream, version, targetCwCount, inputMode, this.inputData, gs1, this.eciMode, est_binlen);
addEcc(fullstream, datastream, version, targetCwCount, blocks);
 
size = QR_SIZES[version - 1];
 
final int[] grid = new int[size * size];
 
infoLine("Version: " + version);
infoLine("ECC Level: " + ecc_level.name());
 
setupGrid(grid, size, version);
populateGrid(grid, size, fullstream, QR_TOTAL_CODEWORDS[version - 1]);
 
if (version >= 7) {
addVersionInfo(grid, size, version);
}
 
bitmask = applyBitmask(grid, size, ecc_level);
infoLine("Mask Pattern: " + Integer.toBinaryString(bitmask));
addFormatInfo(grid, size, ecc_level, bitmask);
 
this.readable = "";
this.pattern = new String[size];
this.row_count = size;
this.row_height = new int[size];
for (i = 0; i < size; i++) {
final StringBuilder bin = new StringBuilder(size);
for (j = 0; j < size; j++) {
if ((grid[i * size + j] & 0x01) != 0) {
bin.append('1');
} else {
bin.append('0');
}
}
this.pattern[i] = bin2pat(bin);
this.row_height[i] = 1;
}
}
 
/** Place Kanji / Binary / Alphanumeric / Numeric values in inputMode. */
private static void defineMode(final QrMode[] inputMode, final int[] inputData) {
 
for (int i = 0; i < inputData.length; i++) {
if (inputData[i] > 0xff) {
inputMode[i] = QrMode.KANJI;
} else {
inputMode[i] = QrMode.BINARY;
if (isAlpha(inputData[i])) {
inputMode[i] = QrMode.ALPHANUM;
}
if (inputData[i] == FNC1) {
inputMode[i] = QrMode.ALPHANUM;
}
if (isNumeric(inputData[i])) {
inputMode[i] = QrMode.NUMERIC;
}
}
}
 
// TODO: uncomment
// /* If less than 6 numeric digits together then don't use numeric mode */
// for (int i = 0; i < inputMode.length; i++) {
// if (inputMode[i] == QrMode.NUMERIC) {
// if (((i != 0) && (inputMode[i - 1] != QrMode.NUMERIC)) || (i == 0)) {
// mlen = 0;
// while (((mlen + i) < inputMode.length) && (inputMode[mlen + i] == QrMode.NUMERIC)) {
// mlen++;
// };
// if (mlen < 6) {
// for (int j = 0; j < mlen; j++) {
// inputMode[i + j] = QrMode.ALPHANUM;
// }
// }
// }
// }
// }
//
// /* If less than 4 alphanumeric characters together then don't use alphanumeric mode */
// for (int i = 0; i < inputMode.length; i++) {
// if (inputMode[i] == QrMode.ALPHANUM) {
// if (((i != 0) && (inputMode[i - 1] != QrMode.ALPHANUM)) || (i == 0)) {
// mlen = 0;
// while (((mlen + i) < inputMode.length) && (inputMode[mlen + i] == QrMode.ALPHANUM)) {
// mlen++;
// };
// if (mlen < 4) {
// for (int j = 0; j < mlen; j++) {
// inputMode[i + j] = QrMode.BINARY;
// }
// }
// }
// }
// }
}
 
/** Calculate the actual bit length of the proposed binary string. */
private static int getBinaryLength(final int version, final QrMode[] inputModeUnoptimized, final int[] inputData, final boolean gs1, final int eciMode) {
 
int i, j;
QrMode currentMode;
final int inputLength = inputModeUnoptimized.length;
int count = 0;
int alphaLength;
int percent = 0;
 
// ZINT NOTE: in Zint, this call modifies the input mode array directly; here, we leave
// the original array alone so that subsequent binary length checks don't irrevocably
// optimize the mode array for the wrong QR Code version
final QrMode[] inputMode = applyOptimisation(version, inputModeUnoptimized);
 
currentMode = QrMode.NULL;
 
if (gs1) {
count += 4;
}
 
if (eciMode != 3) {
count += 12;
}
 
for (i = 0; i < inputLength; i++) {
if (inputMode[i] != currentMode) {
count += 4;
switch (inputMode[i]) {
case KANJI:
count += tribus(version, 8, 10, 12);
count += blockLength(i, inputMode) * 13;
break;
case BINARY:
count += tribus(version, 8, 16, 16);
for (j = i; j < i + blockLength(i, inputMode); j++) {
if (inputData[j] > 0xff) {
count += 16;
} else {
count += 8;
}
}
break;
case ALPHANUM:
count += tribus(version, 9, 11, 13);
alphaLength = blockLength(i, inputMode);
// In alphanumeric mode % becomes %%
if (gs1) {
for (j = i; j < i + alphaLength; j++) { // TODO: need to do this only if
// in GS1 mode? or is the other
// code wrong?
// https://sourceforge.net/p/zint/tickets/104/#227b
if (inputData[j] == '%') {
percent++;
}
}
}
alphaLength += percent;
switch (alphaLength % 2) {
case 0:
count += alphaLength / 2 * 11;
break;
case 1:
count += (alphaLength - 1) / 2 * 11;
count += 6;
break;
}
break;
case NUMERIC:
count += tribus(version, 10, 12, 14);
switch (blockLength(i, inputMode) % 3) {
case 0:
count += blockLength(i, inputMode) / 3 * 10;
break;
case 1:
count += (blockLength(i, inputMode) - 1) / 3 * 10;
count += 4;
break;
case 2:
count += (blockLength(i, inputMode) - 2) / 3 * 10;
count += 7;
break;
}
break;
}
currentMode = inputMode[i];
}
}
 
return count;
}
 
/**
* Implements a custom optimization algorithm, because implementation of the algorithm shown in
* Annex J.2 created LONGER binary sequences.
*/
private static QrMode[] applyOptimisation(final int version, final QrMode[] inputMode) {
 
final int inputLength = inputMode.length;
int blockCount = 0;
int i, j;
QrMode currentMode = QrMode.NULL;
 
for (i = 0; i < inputLength; i++) {
if (inputMode[i] != currentMode) {
currentMode = inputMode[i];
blockCount++;
}
}
 
final int[] blockLength = new int[blockCount];
final QrMode[] blockMode = new QrMode[blockCount];
 
j = -1;
currentMode = QrMode.NULL;
for (i = 0; i < inputLength; i++) {
if (inputMode[i] != currentMode) {
j++;
blockLength[j] = 1;
blockMode[j] = inputMode[i];
currentMode = inputMode[i];
} else {
blockLength[j]++;
}
}
 
if (blockCount > 1) {
// Search forward
for (i = 0; i <= blockCount - 2; i++) {
if (blockMode[i] == QrMode.BINARY) {
switch (blockMode[i + 1]) {
case KANJI:
if (blockLength[i + 1] < tribus(version, 4, 5, 6)) {
blockMode[i + 1] = QrMode.BINARY;
}
break;
case ALPHANUM:
if (blockLength[i + 1] < tribus(version, 7, 8, 9)) {
blockMode[i + 1] = QrMode.BINARY;
}
break;
case NUMERIC:
if (blockLength[i + 1] < tribus(version, 3, 4, 5)) {
blockMode[i + 1] = QrMode.BINARY;
}
break;
}
}
 
if (blockMode[i] == QrMode.ALPHANUM && blockMode[i + 1] == QrMode.NUMERIC) {
if (blockLength[i + 1] < tribus(version, 6, 8, 10)) {
blockMode[i + 1] = QrMode.ALPHANUM;
}
}
}
 
// Search backward
for (i = blockCount - 1; i > 0; i--) {
if (blockMode[i] == QrMode.BINARY) {
switch (blockMode[i - 1]) {
case KANJI:
if (blockLength[i - 1] < tribus(version, 4, 5, 6)) {
blockMode[i - 1] = QrMode.BINARY;
}
break;
case ALPHANUM:
if (blockLength[i - 1] < tribus(version, 7, 8, 9)) {
blockMode[i - 1] = QrMode.BINARY;
}
break;
case NUMERIC:
if (blockLength[i - 1] < tribus(version, 3, 4, 5)) {
blockMode[i - 1] = QrMode.BINARY;
}
break;
}
}
 
if (blockMode[i] == QrMode.ALPHANUM && blockMode[i - 1] == QrMode.NUMERIC) {
if (blockLength[i - 1] < tribus(version, 6, 8, 10)) {
blockMode[i - 1] = QrMode.ALPHANUM;
}
}
}
}
 
// ZINT NOTE: this method is different from the original Zint code in that it creates a
// new array to hold the optimized values and returns it, rather than modifying the
// original array; this allows this method to be called as many times as we want without
// worrying about side effects
 
final QrMode[] optimized = new QrMode[inputMode.length];
 
j = 0;
for (int block = 0; block < blockCount; block++) {
currentMode = blockMode[block];
for (i = 0; i < blockLength[block]; i++) {
optimized[j] = currentMode;
j++;
}
}
 
return optimized;
}
 
/** Find the length of the block starting from 'start'. */
private static int blockLength(final int start, final QrMode[] inputMode) {
 
final QrMode mode = inputMode[start];
int count = 0;
final int i = start;
 
do {
count++;
} while (i + count < inputMode.length && inputMode[i + count] == mode);
 
return count;
}
 
/** Choose from three numbers based on version. */
private static int tribus(final int version, final int a, final int b, final int c) {
if (version < 10) {
return a;
} else if (version >= 10 && version <= 26) {
return b;
} else {
return c;
}
}
 
/** Returns true if input is in the Alphanumeric set (see Table J.1) */
private static boolean isAlpha(final int c) {
return c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c == ' ' || c == '$' || c == '%' || c == '*' || c == '+' || c == '-' || c == '.' || c == '/' || c == ':';
}
 
/** Returns true if input is in the Numeric set (see Table J.1) */
private static boolean isNumeric(final int c) {
return c >= '0' && c <= '9';
}
 
/** Converts input data to a binary stream and adds padding. */
private void qrBinary(final int[] datastream, final int version, final int target_binlen, final QrMode[] inputMode, final int[] inputData, final boolean gs1, final int eciMode,
final int est_binlen) {
 
// TODO: make encodeInfo a StringBuilder, make this method static?
 
int position = 0;
int short_data_block_length, i;
int padbits;
int current_binlen, current_bytes;
int toggle;
QrMode data_block;
 
final StringBuilder binary = new StringBuilder(est_binlen + 12);
 
if (gs1) {
binary.append("0101"); /* FNC1 */
}
 
if (eciMode != 3) {
binary.append("0111"); /* ECI (Table 4) */
if (eciMode <= 127) {
binaryAppend(eciMode, 8, binary); /* 000000 to 000127 */
} else if (eciMode <= 16383) {
binaryAppend(0x8000 + eciMode, 16, binary); /* 000000 to 016383 */
} else {
binaryAppend(0xC00000 + eciMode, 24, binary); /* 000000 to 999999 */
}
}
 
info("Encoding: ");
 
do {
data_block = inputMode[position];
short_data_block_length = 0;
do {
short_data_block_length++;
} while (short_data_block_length + position < inputMode.length && inputMode[position + short_data_block_length] == data_block);
 
switch (data_block) {
 
case KANJI:
/* Kanji mode */
/* Mode indicator */
binary.append("1000");
 
/* Character count indicator */
binaryAppend(short_data_block_length, tribus(version, 8, 10, 12), binary);
 
info("KNJI ");
 
/* Character representation */
for (i = 0; i < short_data_block_length; i++) {
int jis = inputData[position + i];
if (jis >= 0x8140 && jis <= 0x9ffc) {
jis -= 0x8140;
} else if (jis >= 0xe040 && jis <= 0xebbf) {
jis -= 0xc140;
}
final int prod = (jis >> 8) * 0xc0 + (jis & 0xff);
binaryAppend(prod, 13, binary);
infoSpace(prod);
}
 
break;
 
case BINARY:
/* Byte mode */
/* Mode indicator */
binary.append("0100");
 
/* Character count indicator */
binaryAppend(short_data_block_length, tribus(version, 8, 16, 16), binary);
 
info("BYTE ");
 
/* Character representation */
for (i = 0; i < short_data_block_length; i++) {
int b = inputData[position + i];
if (b == FNC1) {
b = 0x1d; /* FNC1 */
}
binaryAppend(b, 8, binary);
infoSpace(b);
}
 
break;
 
case ALPHANUM:
/* Alphanumeric mode */
/* Mode indicator */
binary.append("0010");
 
/* If in GS1 mode, expand FNC1 -> '%' and expand '%' -> '%%' in a new array */
int percentCount = 0;
if (gs1) {
for (i = 0; i < short_data_block_length; i++) {
if (inputData[position + i] == '%') {
percentCount++;
}
}
}
final int[] inputExpanded = new int[short_data_block_length + percentCount];
percentCount = 0;
for (i = 0; i < short_data_block_length; i++) {
final int c = inputData[position + i];
if (c == FNC1) {
inputExpanded[i + percentCount] = '%'; /* FNC1 */
} else {
inputExpanded[i + percentCount] = c;
if (gs1 && c == '%') {
percentCount++;
inputExpanded[i + percentCount] = c;
}
}
}
 
/* Character count indicator */
binaryAppend(inputExpanded.length, tribus(version, 9, 11, 13), binary);
 
info("ALPH ");
 
/* Character representation */
for (i = 0; i + 1 < inputExpanded.length; i += 2) {
final int first = positionOf((char) inputExpanded[i], RHODIUM);
final int second = positionOf((char) inputExpanded[i + 1], RHODIUM);
final int prod = first * 45 + second;
final int count = 2;
binaryAppend(prod, 1 + 5 * count, binary);
infoSpace(prod);
}
if (inputExpanded.length % 2 != 0) {
final int first = positionOf((char) inputExpanded[inputExpanded.length - 1], RHODIUM);
final int prod = first;
final int count = 1;
binaryAppend(prod, 1 + 5 * count, binary);
infoSpace(prod);
}
 
break;
 
case NUMERIC:
/* Numeric mode */
/* Mode indicator */
binary.append("0001");
 
/* Character count indicator */
binaryAppend(short_data_block_length, tribus(version, 10, 12, 14), binary);
 
info("NUMB ");
 
/* Character representation */
i = 0;
while (i < short_data_block_length) {
 
final int first = Character.getNumericValue(inputData[position + i]);
int count = 1;
int prod = first;
 
if (i + 1 < short_data_block_length) {
final int second = Character.getNumericValue(inputData[position + i + 1]);
count = 2;
prod = prod * 10 + second;
 
if (i + 2 < short_data_block_length) {
final int third = Character.getNumericValue(inputData[position + i + 2]);
count = 3;
prod = prod * 10 + third;
}
}
 
binaryAppend(prod, 1 + 3 * count, binary);
 
infoSpace(prod);
 
i += count;
}
 
break;
}
 
position += short_data_block_length;
 
} while (position < inputMode.length);
 
infoLine();
 
/* Terminator */
binary.append("0000");
 
current_binlen = binary.length();
padbits = 8 - current_binlen % 8;
if (padbits == 8) {
padbits = 0;
}
current_bytes = (current_binlen + padbits) / 8;
 
/* Padding bits */
for (i = 0; i < padbits; i++) {
binary.append('0');
}
 
/* Put data into 8-bit codewords */
for (i = 0; i < current_bytes; i++) {
datastream[i] = 0x00;
for (int p = 0; p < 8; p++) {
if (binary.charAt(i * 8 + p) == '1') {
datastream[i] += 0x80 >> p;
}
}
}
 
/* Add pad codewords */
toggle = 0;
for (i = current_bytes; i < target_binlen; i++) {
if (toggle == 0) {
datastream[i] = 0xec;
toggle = 1;
} else {
datastream[i] = 0x11;
toggle = 0;
}
}
 
info("Codewords: ");
for (i = 0; i < target_binlen; i++) {
infoSpace(datastream[i]);
}
infoLine();
}
 
private static void binaryAppend(final int value, final int length, final StringBuilder binary) {
final int start = 0x01 << length - 1;
for (int i = 0; i < length; i++) {
if ((value & start >> i) != 0) {
binary.append('1');
} else {
binary.append('0');
}
}
}
 
/**
* Splits data into blocks, adds error correction and then interleaves the blocks and error
* correction data.
*/
private static void addEcc(final int[] fullstream, final int[] datastream, final int version, final int data_cw, final int blocks) {
 
final int ecc_cw = QR_TOTAL_CODEWORDS[version - 1] - data_cw;
final int short_data_block_length = data_cw / blocks;
final int qty_long_blocks = data_cw % blocks;
final int qty_short_blocks = blocks - qty_long_blocks;
final int ecc_block_length = ecc_cw / blocks;
int i, j, length_this_block, posn;
 
final int[] data_block = new int[short_data_block_length + 2];
final int[] ecc_block = new int[ecc_block_length + 2];
final int[] interleaved_data = new int[data_cw + 2];
final int[] interleaved_ecc = new int[ecc_cw + 2];
 
posn = 0;
 
for (i = 0; i < blocks; i++) {
if (i < qty_short_blocks) {
length_this_block = short_data_block_length;
} else {
length_this_block = short_data_block_length + 1;
}
 
for (j = 0; j < ecc_block_length; j++) {
ecc_block[j] = 0;
}
 
for (j = 0; j < length_this_block; j++) {
data_block[j] = datastream[posn + j];
}
 
final ReedSolomon rs = new ReedSolomon();
rs.init_gf(0x11d);
rs.init_code(ecc_block_length, 0);
rs.encode(length_this_block, data_block);
 
for (j = 0; j < ecc_block_length; j++) {
ecc_block[j] = rs.getResult(j);
}
 
for (j = 0; j < short_data_block_length; j++) {
interleaved_data[j * blocks + i] = data_block[j];
}
 
if (i >= qty_short_blocks) {
interleaved_data[short_data_block_length * blocks + i - qty_short_blocks] = data_block[short_data_block_length];
}
 
for (j = 0; j < ecc_block_length; j++) {
interleaved_ecc[j * blocks + i] = ecc_block[ecc_block_length - j - 1];
}
 
posn += length_this_block;
}
 
for (j = 0; j < data_cw; j++) {
fullstream[j] = interleaved_data[j];
}
for (j = 0; j < ecc_cw; j++) {
fullstream[j + data_cw] = interleaved_ecc[j];
}
}
 
private static void setupGrid(final int[] grid, final int size, final int version) {
 
int i;
boolean toggle = true;
 
/* Add timing patterns */
for (i = 0; i < size; i++) {
if (toggle) {
grid[6 * size + i] = 0x21;
grid[i * size + 6] = 0x21;
toggle = false;
} else {
grid[6 * size + i] = 0x20;
grid[i * size + 6] = 0x20;
toggle = true;
}
}
 
/* Add finder patterns */
placeFinder(grid, size, 0, 0);
placeFinder(grid, size, 0, size - 7);
placeFinder(grid, size, size - 7, 0);
 
/* Add separators */
for (i = 0; i < 7; i++) {
grid[7 * size + i] = 0x10;
grid[i * size + 7] = 0x10;
grid[7 * size + size - 1 - i] = 0x10;
grid[i * size + size - 8] = 0x10;
grid[(size - 8) * size + i] = 0x10;
grid[(size - 1 - i) * size + 7] = 0x10;
}
grid[7 * size + 7] = 0x10;
grid[7 * size + size - 8] = 0x10;
grid[(size - 8) * size + 7] = 0x10;
 
/* Add alignment patterns */
if (version != 1) {
/* Version 1 does not have alignment patterns */
final int loopsize = QR_ALIGN_LOOPSIZE[version - 1];
for (int x = 0; x < loopsize; x++) {
for (int y = 0; y < loopsize; y++) {
final int xcoord = QR_TABLE_E1[(version - 2) * 7 + x];
final int ycoord = QR_TABLE_E1[(version - 2) * 7 + y];
if ((grid[ycoord * size + xcoord] & 0x10) == 0) {
placeAlign(grid, size, xcoord, ycoord);
}
}
}
}
 
/* Reserve space for format information */
for (i = 0; i < 8; i++) {
grid[8 * size + i] += 0x20;
grid[i * size + 8] += 0x20;
grid[8 * size + size - 1 - i] = 0x20;
grid[(size - 1 - i) * size + 8] = 0x20;
}
grid[8 * size + 8] += 0x20;
grid[(size - 1 - 7) * size + 8] = 0x21; /* Dark Module from Figure 25 */
 
/* Reserve space for version information */
if (version >= 7) {
for (i = 0; i < 6; i++) {
grid[(size - 9) * size + i] = 0x20;
grid[(size - 10) * size + i] = 0x20;
grid[(size - 11) * size + i] = 0x20;
grid[i * size + size - 9] = 0x20;
grid[i * size + size - 10] = 0x20;
grid[i * size + size - 11] = 0x20;
}
}
}
 
private static void placeFinder(final int[] grid, final int size, final int x, final int y) {
 
final int[] finder = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 };
 
for (int xp = 0; xp < 7; xp++) {
for (int yp = 0; yp < 7; yp++) {
if (finder[xp + 7 * yp] == 1) {
grid[(yp + y) * size + xp + x] = 0x11;
} else {
grid[(yp + y) * size + xp + x] = 0x10;
}
}
}
}
 
private static void placeAlign(final int[] grid, final int size, int x, int y) {
 
final int[] alignment = { 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1 };
 
x -= 2;
y -= 2; /* Input values represent centre of pattern */
 
for (int xp = 0; xp < 5; xp++) {
for (int yp = 0; yp < 5; yp++) {
if (alignment[xp + 5 * yp] == 1) {
grid[(yp + y) * size + xp + x] = 0x11;
} else {
grid[(yp + y) * size + xp + x] = 0x10;
}
}
}
}
 
private static void populateGrid(final int[] grid, final int size, final int[] fullstream, final int cw) {
 
boolean goingUp = true;
int row = 0; /* right hand side */
 
int i, n, y;
 
n = cw * 8;
y = size - 1;
i = 0;
do {
int x = size - 2 - row * 2;
if (x < 6) {
x--; /* skip over vertical timing pattern */
}
 
if ((grid[y * size + x + 1] & 0xf0) == 0) {
if (cwbit(fullstream, i)) {
grid[y * size + x + 1] = 0x01;
} else {
grid[y * size + x + 1] = 0x00;
}
i++;
}
 
if (i < n) {
if ((grid[y * size + x] & 0xf0) == 0) {
if (cwbit(fullstream, i)) {
grid[y * size + x] = 0x01;
} else {
grid[y * size + x] = 0x00;
}
i++;
}
}
 
if (goingUp) {
y--;
} else {
y++;
}
if (y == -1) {
/* reached the top */
row++;
y = 0;
goingUp = false;
}
if (y == size) {
/* reached the bottom */
row++;
y = size - 1;
goingUp = true;
}
} while (i < n);
}
 
private static boolean cwbit(final int[] fullstream, final int i) {
return (fullstream[i / 8] & 0x80 >> i % 8) != 0;
}
 
private static int applyBitmask(final int[] grid, final int size, final EccLevel ecc_level) {
 
int x, y;
char p;
int pattern;
int best_val, best_pattern;
final int[] penalty = new int[8];
final byte[] mask = new byte[size * size];
final byte[] eval = new byte[size * size];
 
/* Perform data masking */
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
mask[y * size + x] = 0x00;
// all eight bit mask variants are encoded in the 8 bits of the bytes that make up
// the mask array
if ((grid[y * size + x] & 0xf0) == 0) { // exclude areas not to be masked
if ((y + x & 1) == 0) {
mask[y * size + x] += (byte) 0x01;
}
if ((y & 1) == 0) {
mask[y * size + x] += (byte) 0x02;
}
if (x % 3 == 0) {
mask[y * size + x] += (byte) 0x04;
}
if ((y + x) % 3 == 0) {
mask[y * size + x] += (byte) 0x08;
}
if ((y / 2 + x / 3 & 1) == 0) {
mask[y * size + x] += (byte) 0x10;
}
if ((y * x & 1) + y * x % 3 == 0) {
mask[y * size + x] += (byte) 0x20;
}
if (((y * x & 1) + y * x % 3 & 1) == 0) {
mask[y * size + x] += (byte) 0x40;
}
if (((y + x & 1) + y * x % 3 & 1) == 0) {
mask[y * size + x] += (byte) 0x80;
}
}
}
}
 
/* Apply data masks to grid, result in eval */
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
if ((grid[y * size + x] & 0x01) != 0) {
p = 0xff;
} else {
p = 0x00;
}
eval[y * size + x] = (byte) (mask[y * size + x] ^ p);
}
}
 
/* Evaluate result */
for (pattern = 0; pattern < 8; pattern++) {
addFormatInfoEval(eval, size, ecc_level, pattern);
penalty[pattern] = evaluate(eval, size, pattern);
}
 
best_pattern = 0;
best_val = penalty[0];
for (pattern = 1; pattern < 8; pattern++) {
if (penalty[pattern] < best_val) {
best_pattern = pattern;
best_val = penalty[pattern];
}
}
 
/* Apply mask */
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
if ((mask[y * size + x] & 0x01 << best_pattern) != 0) {
if ((grid[y * size + x] & 0x01) != 0) {
grid[y * size + x] = 0x00;
} else {
grid[y * size + x] = 0x01;
}
}
}
}
 
return best_pattern;
}
 
/** Adds format information to eval. */
private static void addFormatInfoEval(final byte[] eval, final int size, final EccLevel ecc_level, final int pattern) {
 
int format = pattern;
int seq;
int i;
 
switch (ecc_level) {
case L:
format += 0x08;
break;
case Q:
format += 0x18;
break;
case H:
format += 0x10;
break;
}
 
seq = QR_ANNEX_C[format];
 
for (i = 0; i < 6; i++) {
eval[i * size + 8] = (byte) ((seq >> i & 0x01) != 0 ? 0x01 >> pattern : 0x00);
}
 
for (i = 0; i < 8; i++) {
eval[8 * size + size - i - 1] = (byte) ((seq >> i & 0x01) != 0 ? 0x01 >> pattern : 0x00);
}
 
for (i = 0; i < 6; i++) {
eval[8 * size + 5 - i] = (byte) ((seq >> i + 9 & 0x01) != 0 ? 0x01 >> pattern : 0x00);
}
 
for (i = 0; i < 7; i++) {
eval[(size - 7 + i) * size + 8] = (byte) ((seq >> i + 8 & 0x01) != 0 ? 0x01 >> pattern : 0x00);
}
 
eval[7 * size + 8] = (byte) ((seq >> 6 & 0x01) != 0 ? 0x01 >> pattern : 0x00);
eval[8 * size + 8] = (byte) ((seq >> 7 & 0x01) != 0 ? 0x01 >> pattern : 0x00);
eval[8 * size + 7] = (byte) ((seq >> 8 & 0x01) != 0 ? 0x01 >> pattern : 0x00);
}
 
private static int evaluate(final byte[] eval, final int size, final int pattern) {
 
int x, y, block, weight;
int result = 0;
int state;
int p;
int dark_mods;
int percentage, k;
int a, b, afterCount, beforeCount;
final byte[] local = new byte[size * size];
 
// all eight bit mask variants have been encoded in the 8 bits of the bytes
// that make up the grid array; select them for evaluation according to the
// desired pattern
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
if ((eval[y * size + x] & 0x01 << pattern) != 0) {
local[y * size + x] = '1';
} else {
local[y * size + x] = '0';
}
}
}
 
/* Test 1: Adjacent modules in row/column in same colour */
/* Vertical */
for (x = 0; x < size; x++) {
state = local[x];
block = 0;
for (y = 0; y < size; y++) {
if (local[y * size + x] == state) {
block++;
} else {
if (block > 5) {
result += 3 + block - 5;
}
block = 0;
state = local[y * size + x];
}
}
if (block > 5) {
result += 3 + block - 5;
}
}
 
/* Horizontal */
for (y = 0; y < size; y++) {
state = local[y * size];
block = 0;
for (x = 0; x < size; x++) {
if (local[y * size + x] == state) {
block++;
} else {
if (block > 5) {
result += 3 + block - 5;
}
block = 0;
state = local[y * size + x];
}
}
if (block > 5) {
result += 3 + block - 5;
}
}
 
/* Test 2: Block of modules in same color */
for (x = 0; x < size - 1; x++) {
for (y = 0; y < size - 1; y++) {
if (local[y * size + x] == local[(y + 1) * size + x] && local[y * size + x] == local[y * size + x + 1] && local[y * size + x] == local[(y + 1) * size + x + 1]) {
result += 3;
}
}
}
 
/* Test 3: 1:1:3:1:1 ratio pattern in row/column */
/* Vertical */
for (x = 0; x < size; x++) {
for (y = 0; y < size - 7; y++) {
p = 0;
for (weight = 0; weight < 7; weight++) {
if (local[(y + weight) * size + x] == '1') {
p += 0x40 >> weight;
}
}
if (p == 0x5d) {
/* Pattern found, check before and after */
beforeCount = 0;
for (b = y - 4; b < y; b++) {
if (b < 0) {
beforeCount++;
} else {
if (local[b * size + x] == '0') {
beforeCount++;
} else {
beforeCount = 0;
}
}
}
 
afterCount = 0;
for (a = y + 7; a <= y + 10; a++) {
if (a >= size) {
afterCount++;
} else {
if (local[a * size + x] == '0') {
afterCount++;
} else {
afterCount = 0;
}
}
}
 
if (beforeCount == 4 || afterCount == 4) {
// Pattern is preceded or followed by light area 4 modules wide
result += 40;
}
}
}
}
 
/* Horizontal */
for (y = 0; y < size; y++) {
for (x = 0; x < size - 7; x++) {
p = 0;
for (weight = 0; weight < 7; weight++) {
if (local[y * size + x + weight] == '1') {
p += 0x40 >> weight;
}
}
if (p == 0x5d) {
/* Pattern found, check before and after */
beforeCount = 0;
for (b = x - 4; b < x; b++) {
if (b < 0) {
beforeCount++;
} else {
if (local[y * size + b] == '0') {
beforeCount++;
} else {
beforeCount = 0;
}
}
}
 
afterCount = 0;
for (a = x + 7; a <= x + 10; a++) {
if (a >= size) {
afterCount++;
} else {
if (local[y * size + a] == '0') {
afterCount++;
} else {
afterCount = 0;
}
}
}
 
if (beforeCount == 4 || afterCount == 4) {
// Pattern is preceded or followed by light area 4 modules wide
result += 40;
}
}
}
}
 
/* Test 4: Proportion of dark modules in entire symbol */
dark_mods = 0;
for (x = 0; x < size; x++) {
for (y = 0; y < size; y++) {
if (local[y * size + x] == '1') {
dark_mods++;
}
}
}
percentage = 100 * (dark_mods / (size * size));
if (percentage <= 50) {
k = (100 - percentage - 50) / 5;
} else {
k = (percentage - 50) / 5;
}
 
result += 10 * k;
 
return result;
}
 
/* Adds format information to grid. */
private static void addFormatInfo(final int[] grid, final int size, final EccLevel ecc_level, final int pattern) {
 
int format = pattern;
int seq;
int i;
 
switch (ecc_level) {
case L:
format += 0x08;
break;
case Q:
format += 0x18;
break;
case H:
format += 0x10;
break;
}
 
seq = QR_ANNEX_C[format];
 
for (i = 0; i < 6; i++) {
grid[i * size + 8] += seq >> i & 0x01;
}
 
for (i = 0; i < 8; i++) {
grid[8 * size + size - i - 1] += seq >> i & 0x01;
}
 
for (i = 0; i < 6; i++) {
grid[8 * size + 5 - i] += seq >> i + 9 & 0x01;
}
 
for (i = 0; i < 7; i++) {
grid[(size - 7 + i) * size + 8] += seq >> i + 8 & 0x01;
}
 
grid[7 * size + 8] += seq >> 6 & 0x01;
grid[8 * size + 8] += seq >> 7 & 0x01;
grid[8 * size + 7] += seq >> 8 & 0x01;
}
 
/** Adds version information. */
private static void addVersionInfo(final int[] grid, final int size, final int version) {
// TODO: Zint masks with 0x41 instead of 0x01; which is correct?
// https://sourceforge.net/p/zint/tickets/110/
final int version_data = QR_ANNEX_D[version - 7];
for (int i = 0; i < 6; i++) {
grid[(size - 11) * size + i] += version_data >> i * 3 & 0x01;
grid[(size - 10) * size + i] += version_data >> i * 3 + 1 & 0x01;
grid[(size - 9) * size + i] += version_data >> i * 3 + 2 & 0x01;
grid[i * size + size - 11] += version_data >> i * 3 & 0x01;
grid[i * size + size - 10] += version_data >> i * 3 + 1 & 0x01;
grid[i * size + size - 9] += version_data >> i * 3 + 2 & 0x01;
}
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/RoyalMail4State.java
New file
0,0 → 1,122
/*
* 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.util.Locale;
 
/**
* <p>
* Encodes data according to the Royal Mail 4-State Country Code.
*
* <p>
* Data input can consist of numbers 0-9 and letters A-Z and usually includes delivery postcode
* followed by house number. A check digit is calculated and added.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class RoyalMail4State extends Symbol {
 
private static final String[] ROYAL_TABLE = { "TTFF", "TDAF", "TDFA", "DTAF", "DTFA", "DDAA", "TADF", "TFTF", "TFDA", "DATF", "DADA", "DFTA", "TAFD", "TFAD", "TFFT", "DAAD", "DAFT", "DFAT",
"ATDF", "ADTF", "ADDA", "FTTF", "FTDA", "FDTA", "ATFD", "ADAD", "ADFT", "FTAD", "FTFT", "FDAT", "AADD", "AFTD", "AFDT", "FATD", "FADT", "FFTT" };
 
private static final char[] KR_SET = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z' };
 
@Override
protected void encode() {
String dest;
int i, top = 0, bottom = 0;
int row, column;
int index;
 
this.content = this.content.toUpperCase(Locale.ENGLISH);
if (!this.content.matches("[0-9A-Z]+")) {
throw new OkapiException("Invalid characters in data");
}
dest = "A";
 
for (i = 0; i < this.content.length(); i++) {
index = positionOf(this.content.charAt(i), KR_SET);
dest += ROYAL_TABLE[index];
top += (index + 1) % 6;
bottom += (index / 6 + 1) % 6;
}
 
/* calculate check digit */
row = top % 6 - 1;
column = bottom % 6 - 1;
if (row == -1) {
row = 5;
}
if (column == -1) {
column = 5;
}
final int check = 6 * row + column;
dest += ROYAL_TABLE[check];
infoLine("Check Digit: " + check);
 
/* Stop character */
dest += "F";
 
infoLine("Encoding: " + dest);
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;
}
 
@Override
protected void plotSymbol() {
int xBlock;
int x, y, w, h;
 
this.rectangles.clear();
x = 0;
w = 1;
y = 0;
h = 0;
for (xBlock = 0; xBlock < this.pattern[0].length(); xBlock++) {
switch (this.pattern[0].charAt(xBlock)) {
case 'A':
y = 0;
h = 5;
break;
case 'D':
y = 3;
h = 5;
break;
case 'F':
y = 0;
h = 8;
break;
case 'T':
y = 3;
h = 2;
break;
}
 
final Rectangle2D.Double rect = new Rectangle2D.Double(x, y, w, h);
this.rectangles.add(rect);
 
x += 2;
}
this.symbol_width = this.pattern[0].length() * 3;
this.symbol_height = 8;
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Code93.java
New file
0,0 → 1,189
/*
* 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.backend;
 
import static uk.org.okapibarcode.util.Arrays.positionOf;
 
/**
* <p>
* Implements <a href="http://en.wikipedia.org/wiki/Code_93">Code 93</a>.
*
* <p>
* Supports encoding of 7-bit ASCII text. Two check digits are added.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
* @author Daniel Gredler
*/
public class Code93 extends Symbol {
 
/**
* Code 93 control characters, indexed by ASCII codes (NOTE: a = Ctrl $, b = Ctrl %, c = Ctrl /,
* d = Ctrl + for sequences of two characters).
*/
private static final String[] CODE_93_CTRL = { "bU", "aA", "aB", "aC", "aD", "aE", "aF", "aG", "aH", "aI", "aJ", "aK", "aL", "aM", "aN", "aO", "aP", "aQ", "aR", "aS", "aT", "aU", "aV", "aW", "aX",
"aY", "aZ", "bA", "bB", "bC", "bD", "bE", " ", "cA", "cB", "cC", "$", "%", "cF", "cG", "cH", "cI", "cJ", "+", "cL", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "cZ",
"bF", "bG", "bH", "bI", "bJ", "bV", "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", "bK", "bL", "bM",
"bN", "bO", "bW", "dA", "dB", "dC", "dD", "dE", "dF", "dG", "dH", "dI", "dJ", "dK", "dL", "dM", "dN", "dO", "dP", "dQ", "dR", "dS", "dT", "dU", "dV", "dW", "dX", "dY", "dZ", "bP", "bQ",
"bR", "bS", "bT" };
 
/**
* Mapping of control characters to pattern table index (NOTE: a = Ctrl $, b = Ctrl %, c = Ctrl
* /, d = Ctrl + for sequences of two characters).
*/
private static final char[] CODE_93_LOOKUP = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', '%', 'a', 'b', 'c', 'd' };
 
/** Code 93 pattern table. */
private static final String[] CODE_93_TABLE = { "131112", "111213", "111312", "111411", "121113", "121212", "121311", "111114", "131211", "141111", "211113", "211212", "211311", "221112",
"221211", "231111", "112113", "112212", "112311", "122112", "132111", "111123", "111222", "111321", "121122", "131121", "212112", "212211", "211122", "211221", "221121", "222111",
"112122", "112221", "122121", "123111", "121131", "311112", "311211", "321111", "112131", "113121", "211131", "121221", "312111", "311121", "122211" };
 
/** Whether or not to show check digits in the human-readable text. */
private boolean showCheckDigits = true;
 
/** Optional start/stop delimiter to be shown in the human-readable text. */
private Character startStopDelimiter;
 
/**
* Sets whether or not to show check digits in the human-readable text (defaults to
* <code>true</code>).
*
* @param showCheckDigits whether or not to show check digits in the human-readable text
*/
public void setShowCheckDigits(final boolean showCheckDigits) {
this.showCheckDigits = showCheckDigits;
}
 
/**
* Returns whether or not this symbol shows check digits in the human-readable text.
*
* @return whether or not this symbol shows check digits in the human-readable text
*/
public boolean getShowCheckDigits() {
return this.showCheckDigits;
}
 
/**
* Sets an optional start/stop delimiter to be shown in the human-readable text (defaults to
* <code>null</code>).
*
* @param startStopDelimiter an optional start/stop delimiter to be shown in the human-readable
* text
*/
public void setStartStopDelimiter(final Character startStopDelimiter) {
this.startStopDelimiter = startStopDelimiter;
}
 
/**
* Returns the optional start/stop delimiter to be shown in the human-readable text.
*
* @return the optional start/stop delimiter to be shown in the human-readable text
*/
public Character getStartStopDelimiter() {
return this.startStopDelimiter;
}
 
/** {@inheritDoc} */
@Override
protected void encode() {
 
final char[] controlChars = toControlChars(this.content);
int l = controlChars.length;
 
if (!this.content.matches("[\u0000-\u007F]+")) {
throw new OkapiException("Invalid characters in input data");
}
 
final int[] values = new int[controlChars.length + 2];
for (int i = 0; i < l; i++) {
values[i] = positionOf(controlChars[i], CODE_93_LOOKUP);
}
 
final int c = calculateCheckDigitC(values, l);
values[l] = c;
l++;
 
final int k = calculateCheckDigitK(values, l);
values[l] = k;
l++;
 
this.readable = this.content;
if (this.showCheckDigits) {
this.readable = this.readable + CODE_93_LOOKUP[c] + CODE_93_LOOKUP[k];
}
if (this.startStopDelimiter != null) {
this.readable = this.startStopDelimiter + this.readable + this.startStopDelimiter;
}
 
infoLine("Check Digit C: " + c);
infoLine("Check Digit K: " + k);
this.pattern = new String[] { toPattern(values) };
this.row_count = 1;
this.row_height = new int[] { -1 };
}
 
private static char[] toControlChars(final String s) {
final StringBuilder buffer = new StringBuilder();
final char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; i++) {
final int asciiCode = chars[i];
buffer.append(CODE_93_CTRL[asciiCode]);
}
return buffer.toString().toCharArray();
}
 
private static int calculateCheckDigitC(final int[] values, final int length) {
int c = 0;
int weight = 1;
for (int i = length - 1; i >= 0; i--) {
c += values[i] * weight;
weight++;
if (weight == 21) {
weight = 1;
}
}
c = c % 47;
return c;
}
 
private static int calculateCheckDigitK(final int[] values, final int length) {
int k = 0;
int weight = 1;
for (int i = length - 1; i >= 0; i--) {
k += values[i] * weight;
weight++;
if (weight == 16) {
weight = 1;
}
}
k = k % 47;
return k;
}
 
private static String toPattern(final int[] values) {
final StringBuilder buffer = new StringBuilder("111141");
for (int i = 0; i < values.length; i++) {
buffer.append(CODE_93_TABLE[values[i]]);
}
buffer.append("1111411");
return buffer.toString();
}
 
/** {@inheritDoc} */
@Override
protected int[] getCodewords() {
return getPatternAsCodewords(6);
}
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/HumanReadableAlignment.java
New file
0,0 → 1,34
/*
* 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.backend;
 
/**
* The text alignment of a bar code's human-readable text.
*/
public enum HumanReadableAlignment {
 
/** Left-align the human-readable text. */
LEFT,
 
/** Right-align the human-readable text. */
RIGHT,
 
/** Center the human-readable text. */
CENTER,
 
/** Justify the human-readable text by adjusting the spaces between the characters. */
JUSTIFY
 
}
/trunk/Modules/Module Label/src/uk/org/okapibarcode/backend/Code49.java
New file
0,0 → 1,850
/*
* 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.nio.charset.StandardCharsets;
 
/**
* <p>
* Implements Code 49 according to ANSI/AIM-BC6-2000.
*
* <p>
* Supports full 7-bit ASCII input up to a maximum of 49 characters or 81 numeric digits. GS1 data
* encoding is also supported.
*
* @author <a href="mailto:rstuart114@gmail.com">Robin Stuart</a>
*/
public class Code49 extends Symbol {
 
private static final String[] C49_TABLE7 = {
/* Table 7: Code 49 ASCII Chart */
"! ", "!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", "!1", "!2", "!3", "!4",
"!5", " ", "!6", "!7", "!8", "$", "%", "!9", "!0", "!-", "!.", "!$", "+", "!/", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!+", "&1", "&2", "&3", "&4", "&5", "&6",
"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", "&7", "&8", "&9", "&0", "&-", "&.", "&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", "&$", "&/", "&+", "&%", "& " };
 
/* Table 5: Check Character Weighting Values */
private static final int[] C49_X_WEIGHT = { 1, 9, 31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13, 5, 41, 33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10 };
 
private static final int[] C49_Y_WEIGHT = { 9, 31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13, 5, 41, 33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10, 24 };
 
private static final int[] C49_Z_WEIGHT = { 31, 26, 2, 12, 17, 23, 37, 18, 22, 6, 27, 44, 15, 43, 39, 11, 13, 5, 41, 33, 36, 8, 4, 32, 3, 19, 40, 25, 29, 10, 24, 30 };
 
private static final String[] C49_TABLE4 = {
/* Table 4: Row Parity Pattern for Code 49 Symbols */
"OEEO", "EOEO", "OOEE", "EEOO", "OEOE", "EOOE", "OOOO", "EEEE" };
 
private static final String[] C49_APPXE_EVEN = {
/* Appendix E - Code 49 Encodation Patterns (Even Symbol Character Parity) */
/* Column 1 */
"11521132", "25112131", "14212132", "25121221", "14221222", "12412132", "23321221", "12421222", "21521221", "15112222", "15121312", "13312222", "24221311", "13321312", "11512222",
"22421311", "11521312", "25112311", "14212312", "23312311", "12412312", "21512311", "16121131", "14321131", "12521131", "15212131", "15221221", "13412131", "13421221", "11612131",
"16112221", "16121311", "14312221", "14321311", "12512221", "12521311", "15212311", "13412311", "11612311", "11131135", "31131133", "51131131", "21122134", "41122132", "21131224",
"41131222", "11113135", "31113133", "51113131", "11122225", "31122223", "51122221", "11131315", "31131313", "51131311", "21113224", "41113222", "21122314",
/* Column 2 */
"41122312", "11113315", "31113313", "51113311", "12131134", "32131132", "21231133", "41231131", "22122133", "42122131", "11222134", "22131223", "42131221", "11231224", "31231222",
"12113134", "32113132", "12122224", "32122222", "12131314", "32131312", "21231313", "41231311", "22113223", "42113221", "11213224", "22122313", "42122311", "11222314", "31222312",
"12113314", "32113312", "21213313", "41213311", "13131133", "33131131", "22231132", "11331133", "31331131", "23122132", "12222133", "23131222", "12231223", "32231221", "21331222",
"13113133", "33113131", "13122223", "33122221", "11313133", "13131313", "33131311", "11322223", "22231312", "11331313", "31331311", "23113222", "12213223",
/* Column 3 */
"23122312", "12222313", "32222311", "21322312", "13113313", "33113311", "22213312", "11313313", "31313311", "14131132", "23231131", "12331132", "21431131", "24122131", "13222132",
"24131221", "13231222", "11422132", "22331221", "11431222", "14113132", "14122222", "12313132", "14131312", "12322222", "23231311", "12331312", "21431311", "24113221", "13213222",
"24122311", "13222312", "11413222", "22322311", "11422312", "14113312", "23213311", "12313312", "21413311", "15131131", "13331131", "14222131", "14231221", "12422131", "12431221",
"15113131", "15122221", "13313131", "15131311", "13322221", "11513131", "13331311", "11522221", "14213221", "14222311", "12413221", "12422311", "15113311",
/* Column 4 */
"13313311", "11513311", "11141134", "31141132", "21132133", "41132131", "21141223", "41141221", "11123134", "31123132", "11132224", "31132222", "11141314", "31141312", "21114133",
"41114131", "21123223", "41123221", "21132313", "41132311", "11114224", "31114222", "11123314", "31123312", "21114313", "41114311", "12141133", "32141131", "21241132", "22132132",
"11232133", "22141222", "11241223", "31241221", "12123133", "32123131", "12132223", "32132221", "12141313", "32141311", "21241312", "22114132", "11214133", "22123222", "11223223",
"22132312", "11232313", "31232311", "12114223", "32114221", "12123313", "32123311", "21223312", "22114312", "11214313", "31214311", "13141132", "22241131",
/* Column 5 */
"11341132", "23132131", "12232132", "23141221", "12241222", "21341221", "13123132", "13132222", "11323132", "13141312", "11332222", "22241311", "11341312", "23114131", "12214132",
"23123221", "12223222", "23132311", "12232312", "21332311", "13114222", "13123312", "11314222", "22223311", "11323312", "23114311", "12214312", "21314311", "14141131", "12341131",
"13232131", "13241221", "11432131", "14123131", "14132221", "12323131", "14141311", "12332221", "12341311", "13214131", "13223221", "11414131", "13232311", "11423221", "11432311",
"14114221", "14123311", "12314221", "12323311", "13214311", "11414311", "11151133", "31151131", "21142132", "21151222", "11133133", "31133131", "11142223",
/* Column 6 */
"31142221", "11151313", "31151311", "21124132", "21133222", "21142312", "11115133", "31115131", "11124223", "31124221", "11133313", "31133311", "21115222", "21124312", "12151132",
"21251131", "22142131", "11242132", "22151221", "11251222", "12133132", "12142222", "12151312", "21251311", "22124131", "11224132", "22133221", "11233222", "22142311", "11242312",
"12115132", "12124222", "12133312", "21233311", "22115221", "11215222", "22124311", "11224312", "13151131", "12242131", "12251221", "13133131", "13142221", "11333131", "13151311",
"11342221", "12224131", "12233221", "12242311", "13115131", "13124221", "11315131", "13133311", "11324221", "11333311", "12215221", "12224311", "11161132",
/* Column 7 */
"21152131", "21161221", "11143132", "11152222", "11161312", "21134131", "21143221", "21152311", "11125132", "11134222", "11143312", "21116131", "21125221", "21134311", "12161131",
"11252131", "12143131", "12152221", "12161311", "11234131", "11243221", "11252311", "12125131", "12134221", "12143311", "11216131", "11225221", "11234311", "11111236", "31111234",
"51111232", "21111325", "41111323", "61111321", "11111416", "31111414", "51111412", "31211143", "51211141", "12111235", "32111233", "52111231", "21211234", "41211232", "22111324",
"42111322", "11211325", "31211323", "51211321", "12111415", "32111413", "52111411", "21211414", "41211412", "12211144", "32211142", "21311143", "41311141",
/* Column 8 */
"13111234", "33111232", "22211233", "42211231", "11311234", "31311232", "23111323", "43111321", "12211324", "32211322", "21311323", "41311321", "13111414", "33111412", "22211413",
"42211411", "11311414", "31311412", "13211143", "33211141", "22311142", "11411143", "31411141", "14111233", "34111231", "23211232", "12311233", "32311231", "21411232", "24111322",
"13211323", "33211321", "22311322", "11411323", "31411321", "14111413", "34111411", "23211412", "12311413", "32311411", "21411412", "14211142", "23311141", "12411142", "21511141",
"15111232", "24211231", "13311232", "22411231", "11511232", "25111321", "14211322", "23311321", "12411322", "21511321", "15111412", "24211411", "13311412",
/* Column 9 */
"22411411", "11511412", "15211141", "13411141", "11611141", "16111231", "14311231", "12511231", "15211321", "13411321", "11611321", "16111411", "14311411", "12511411", "21121144",
"41121142", "11112145", "31112143", "51112141", "11121235", "31121233", "51121231", "21112234", "41112232", "21121324", "41121322", "11112325", "31112323", "51112321", "11121415",
"31121413", "51121411", "21112414", "41112412", "22121143", "42121141", "11221144", "31221142", "12112144", "32112142", "12121234", "32121232", "21221233", "41221231", "22112233",
"42112231", "11212234", "22121323", "42121321", "11221324", "31221322", "12112324", "32112322", "12121414", "32121412", "21221413", "41221411", "22112413",
/* Column 10 */
"42112411", "11212414", "31212412", "23121142", "12221143", "32221141", "21321142", "13112143", "33112141", "13121233", "33121231", "11312143", "22221232", "11321233", "31321231",
"23112232", "12212233", "23121322", "12221323", "32221321", "21321322", "13112323", "33112321", "13121413", "33121411", "11312323", "22221412", "11321413", "31321411", "23112412",
"12212413", "32212411", "21312412", "24121141", "13221142", "22321141", "11421142", "14112142", "14121232", "12312142", "23221231", "12321232", "21421231", "24112231", "13212232",
"24121321", "13221322", "11412232", "22321321", "11421322", "14112322", "14121412", "12312322", "23221411", "12321412", "21421411", "24112411", "13212412",
/* Column 11 */
"22312411", "11412412", "14221141", "12421141", "15112141", "15121231", "13312141", "13321231", "11512141", "11521231", "14212231", "14221321", "12412231", "12421321", "15112321",
"15121411", "13312321", "13321411", "11512321", "11521411", "14212411", "12412411", "21131143", "41131141", "11122144", "31122142", "11131234", "31131232", "21113143", "41113141",
"21122233", "41122231", "21131323", "41131321", "11113234", "31113232", "11122324", "31122322", "11131414", "31131412", "21113323", "41113321", "21122413", "41122411", "11113414",
"31113412", "22131142", "11231143", "31231141", "12122143", "32122141", "12131233", "32131231", "21231232", "22113142", "11213143", "22122232", "11222233",
/* Column 12 */
"22131322", "11231323", "31231321", "12113233", "32113231", "12122323", "32122321", "12131413", "32131411", "21231412", "22113322", "11213323", "22122412", "11222413", "31222411",
"12113413", "32113411", "21213412", "23131141", "12231142", "21331141", "13122142", "13131232", "11322142", "22231231", "11331232", "23113141", "12213142", "23122231", "12222232",
"23131321", "12231322", "21331321", "13113232", "13122322", "11313232", "13131412", "11322322", "22231411", "11331412", "23113321", "12213322", "23122411", "12222412", "21322411",
"13113412", "22213411", "11313412", "13231141", "11431141", "14122141", "14131231", "12322141", "12331231", "13213141", "13222231", "11413141", "13231321",
/* Column 13 */
"11422231", "11431321", "14113231", "14122321", "12313231", "14131411", "12322321", "12331411", "13213321", "13222411", "11413321", "11422411", "14113411", "12313411", "21141142",
"11132143", "31132141", "11141233", "31141231", "21123142", "21132232", "21141322", "11114143", "31114141", "11123233", "31123231", "11132323", "31132321", "11141413", "31141411",
"21114232", "21123322", "21132412", "11114323", "31114321", "11123413", "31123411", "22141141", "11241142", "12132142", "12141232", "21241231", "22123141", "11223142", "22132231",
"11232232", "22141321", "11241322", "12114142", "12123232", "12132322", "12141412", "21241411", "22114231", "11214232", "22123321", "11223322", "22132411",
/* Column 14 */
"11232412", "12114322", "12123412", "21223411", "12241141", "13132141", "13141231", "11332141", "11341231", "12223141", "12232231", "12241321", "13114141", "13123231", "11314141",
"13132321", "11323231", "13141411", "11332321", "11341411", "12214231", "12223321", "12232411", "13114321", "13123411", "11314321", "11323411", "21151141", "11142142", "11151232",
"21133141", "21142231", "21151321", "11124142", "11133232", "11142322", "11151412", "21115141", "21124231", "21133321", "21142411", "11115232", "11124322", "11133412", "11251141",
"12142141", "12151231", "11233141", "11242231", "11251321", "12124141", "12133231", "12142321", "12151411", "11215141", "11224231", "11233321", "11242411",
/* Column 15 */
"12115231", "12124321", "12133411", "11152141", "11161231", "11134141", "11143231", "11152321", "11161411", "11116141", "11125231", "11134321", "11143411", "21111244", "41111242",
"11111335", "31111333", "51111331", "21111424", "41111422", "11111515", "31111513", "51111511", "21211153", "41211151", "22111243", "42111241", "11211244", "31211242", "12111334",
"32111332", "21211333", "41211331", "22111423", "42111421", "11211424", "31211422", "12111514", "32111512", "21211513", "41211511", "22211152", "11311153", "31311151", "23111242",
"12211243", "32211241", "21311242", "13111333", "33111331", "22211332", "11311333", "31311331", "23111422", "12211423", "32211421", "21311422", "13111513",
/* Column 16 */
"33111511", "22211512", "11311513", "31311511", "23211151", "12311152", "21411151", "24111241", "13211242", "22311241", "11411242", "14111332", "23211331", "12311332", "21411331",
"24111421", "13211422", "22311421", "11411422", "14111512", "23211511", "12311512", "21411511", "13311151", "11511151", "14211241", "12411241", "15111331", "13311331", "11511331",
"14211421", "12411421", "15111511", "13311511", "11511511", "31121152", "21112153", "41112151", "21121243", "41121241", "11112244", "31112242", "11121334", "31121332", "21112333",
"41112331", "21121423", "41121421", "11112424", "31112422", "11121514", "31121512", "21112513", "41112511", "12121153", "32121151", "21221152", "22112152",
/* Column 17 */
"11212153", "22121242", "11221243", "31221241", "12112243", "32112241", "12121333", "32121331", "21221332", "22112332", "11212333", "22121422", "11221423", "31221421", "12112423",
"32112421", "12121513", "32121511", "21221512", "22112512", "11212513", "31212511", "13121152", "22221151", "11321152", "23112151", "12212152", "23121241", "12221242", "21321241",
"13112242", "13121332", "11312242", "22221331", "11321332", "23112331", "12212332", "23121421", "12221422", "21321421", "13112422", "13121512", "11312422", "22221511", "11321512",
"23112511", "12212512", "21312511", "14121151", "12321151", "13212151", "13221241", "11412151", "11421241", "14112241", "14121331", "12312241", "12321331",
/* Column 18 */
"13212331", "13221421", "11412331", "11421421", "14112421", "14121511", "12312421", "12321511", "13212511", "11412511", "11131153", "31131151", "21122152", "21131242", "11113153",
"31113151", "11122243", "31122241", "11131333", "31131331", "21113242", "21122332", "21131422", "11113333", "31113331", "11122423", "31122421", "11131513", "31131511", "21113422",
"21122512", "12131152", "21231151", "22122151", "11222152", "22131241", "11231242", "12113152", "12122242", "12131332", "21231331", "22113241", "11213242", "22122331", "11222332",
"22131421", "11231422", "12113332", "12122422", "12131512", "21231511", "22113421", "11213422", "22122511", "11222512", "13131151", "11331151", "12222151",
/* Column 19 */
"12231241", "13113151", "13122241", "11313151", "13131331", "11322241", "11331331", "12213241", "12222331", "12231421", "13113331", "13122421", "11313331", "13131511", "11322421",
"11331511", "12213421", "12222511", "11141152", "21132151", "21141241", "11123152", "11132242", "11141332", "21114151", "21123241", "21132331", "21141421", "11114242", "11123332",
"11132422", "11141512", "21114331", "21123421", "21132511", "12141151", "11232151", "11241241", "12123151", "12132241", "12141331", "11214151", "11223241", "11232331", "11241421",
"12114241", "12123331", "12132421", "12141511", "11214331", "11223421", "11232511", "11151151", "11133151", "11142241", "11151331", "11115151", "11124241",
/* Column 20 */
"11133331", "11142421", "11151511", "11111254", "31111252", "21111343", "41111341", "11111434", "31111432", "21111523", "41111521", "11111614", "31111612", "31211161", "12111253",
"32111251", "21211252", "22111342", "11211343", "31211341", "12111433", "32111431", "21211432", "22111522", "11211523", "31211521", "12111613", "32111611", "21211612", "12211162",
"21311161", "13111252", "22211251", "11311252", "23111341", "12211342", "21311341", "13111432", "22211431", "11311432", "23111521", "12211522", "21311521", "13111612", "22211611",
"11311612", "13211161", "11411161", "14111251", "12311251", "13211341", "11411341", "14111431", "12311431", "13211521", "11411521", "14111611", "12311611",
/* Column 21 */
"21121162", "11112163", "31112161", "11121253", "31121251", "21112252", "21121342", "11112343", "31112341", "11121433", "31121431", "21112432", "21121522&