Sometimes barcodes or QR codes in printed reports are not recognized correctly by barcode readers. This can be the case with generating GS-1 barcodes or QR codes that require specific Application identifiers present at certain locations within the string that is used to render the barcode.
In other cases they can’t be inserted in specific objects in the template at all. This might happen when you want to insert a Barcode tagging element in a Text box to, for example, to support last page footer scenario.
In such cases, the barcode can be generated from X++ through an external .NET library and added to the data source as a calculated field. The field can then be inserted into the document template like any other image.
The following examples use the external ZXing.Net library to generate GS1-128, GS1 QR Code, GS1 DataMatrix, and Code 39 symbols.
GS1-128, GS1 QR Code, and GS1 DataMatrix
The input is supplied in the familiar GS1 format, for example: (01)04912345123459(15)970331(30)128(10)ABC123.
The encodeGS1Data method parses the bracketed GS1 input, performs basic structural checks, removes the brackets, and inserts the required separator characters after variable-length element strings. These separators are especially important after variable-length application identifiers, such as batch number (10) or quantity (30), so that barcode scanners can correctly distinguish one value from the next.
The generateXmlDataSource method creates the same GS1 data as a Code 128, QR code, and Data Matrix barcode. Each barcode is generated as a bitmap, converted to a PNG byte container, and added to the reporting data source as a calculated field. These fields can then be placed in the document template as images.
Note: This is a sample implementation intended to demonstrate the approach. The external library must be referenced and deployed according to the requirements of the target Dynamics 365 Finance and Operations environment. The generated symbols should be tested with the scanners, printers, document scaling, and output formats used in the target process.
using System.IO;
using System.Text.RegularExpressions;
using ZXing;
using ZXing.QrCode;
using ZXing.Datamatrix;
/// <summary>
/// Sample DSP class that generates GS1 barcodes (Code 128, QR, Datamatrix) as images with external library
/// </summary>
public class DocGS1BarcodesDSP extends DocDataSourceProviderSrsReporting
{
/// <summary>
/// Description of the DSP class.
/// </summary>
/// <returns>Class description.</returns>
public ClassDescription description()
{
return 'GS1 barcodes DSP';
}
private container convertBitmapToContainer(System.Drawing.Bitmap image)
{
using(MemoryStream ms = new MemoryStream())
{
image.Save(ms, System.Drawing.Imaging.ImageFormat::Png);
return DocGlobalHelper::convertBytesToContainer(ms.ToArray());
}
}
public str encodeGS1Data(str _data, str _FNC1, boolean startSymbol = true)
{
// Variable-length GS1 Application Identifiers.
// Source: GS1 General Specifications, Section 3,
// and the GS1 Application Identifier Browser:
// https://ref.gs1.org/ai/
container variableLenghtAIPatterns = ['10', '21', '22', '23*', '24*', '25*', '30', '37', '39*', '400', '401',
'403', '420', '421', '423', '7002', '7004', '703*', '8002', '8003',
'8004', '8007', '8008', '8020', '8110', '8200', '9*'];
;
str ret = '';
System.String contents = _data;
System.String[] segments = contents.Split('(');
if ((segments.Length == 0) || (segments.GetValue(0) != ''))
{
return ''; // Invalid format of GS1 content
}
for (int i = 1; i < segments.Length; i++)
{
System.String segment = segments.GetValue(i);
System.String[] aiData = segment.Split(')');
if (aiData.Length != 2)
return '';
System.String ai = aiData.GetValue(0);
System.String data = aiData.GetValue(1);
if (ret == '' && startSymbol)
ret += _FNC1;
ret += ai + data;
// for variable length AI add FNC1 at the end
int variableLenghtAIPatternsLen = conLen(variableLenghtAIPatterns);
for (int j=1; j<=variableLenghtAIPatternsLen; j++)
{
str pattern = conPeek(variableLenghtAIPatterns, j);
if (Regex::IsMatch(ai, '^' + Regex::Escape(pattern).Replace('\\?', '.').Replace('\\*', '.*') + '$'))
{
ret += _FNC1;
break;
}
}
}
// remove FNC1 at the end
if (strEndsWith(ret, _FNC1))
ret = subStr(ret, 1, strLen(ret) - 1);
return ret;
}
protected void generateXmlDataSource(DocXmlRecordBuilder _recordBuilder)
{
//str gs1Data = "(01)12345678901234(17)210101"; // works
str gs1Data = "(01)04912345123459(15)970331(30)128(10)ABC123"; // works
str gs1DataEncoded;// = strRem(gs1Data, '() '); // without it encodes also brackets and is not recognised as GS1 QR barcode
// GS1 Code-128
BarcodeWriter writer = new BarcodeWriter();
writer.Format = BarcodeFormat::CODE_128;
ZXing.Common.EncodingOptions options = new ZXing.Common.EncodingOptions();
options.GS1Format = true;
writer.Options = options;
/*gs1DataEncoded = strReplace(gs1Data,'(', num2Char(241));
gs1DataEncoded = strRem(gs1DataEncoded, '() ');*/
gs1DataEncoded = this.encodeGS1Data(gs1Data, num2Char(241));
System.Drawing.Bitmap code128CodeBitmap = writer.Write(gs1DataEncoded);
_recordBuilder.addCalculatedField('GS1-128_Barcode', this.convertBitmapToContainer(code128CodeBitmap));
// GS1 QR
/*BarcodeWriter*/ writer = new BarcodeWriter();
writer.Format = BarcodeFormat::QR_CODE;
QrCodeEncodingOptions qrOptions = new QrCodeEncodingOptions();
qrOptions.DisableECI = true;
qrOptions.CharacterSet = 'UTF-8';
qrOptions.Width = 300;
qrOptions.Height = 300;
qrOptions.GS1Format = true; // Without it encoding mode (in the header) is not GS1. That cannot be changed by payload manipulation.
writer.Options = qrOptions;
/*str gs1DataQR = "(01)04912345123459(15)970331(30)128" + num2Char(29) + "(10)ABC123" + num2Char(29);
gs1DataEncoded = strRem(gs1DataQR, '() ');*/
gs1DataEncoded = this.encodeGS1Data(gs1Data, num2Char(29), false);
System.Drawing.Bitmap qrCodeBitmap = writer.Write(gs1DataEncoded);
_recordBuilder.addCalculatedField('GS1-QR_Barcode', this.convertBitmapToContainer(qrCodeBitmap));
// GS1 Datamatrix
/*BarcodeWriter*/ writer = new BarcodeWriter();
writer.Format = BarcodeFormat::DATA_MATRIX;
DatamatrixEncodingOptions dmOptions = new DatamatrixEncodingOptions();
//dmOptions.CharacterSet = 'UTF-8'; // does not work with it
dmOptions.Width = 300;
dmOptions.Height = 300;
dmOptions.GS1Format = true; // Without it encoding mode (in the header) is not GS1. That cannot be changed by payload manipulation.
writer.Options = dmOptions;
/*gs1DataEncoded = strReplace(gs1Data,'(', num2Char(29)) + num2Char(29);
gs1DataEncoded = strRem(gs1DataEncoded, '() ');*/
gs1DataEncoded = this.encodeGS1Data(gs1Data, num2Char(29));
System.Drawing.Bitmap dmCodeBitmap = writer.Write(gs1DataEncoded);
//dmCodeBitmap.Save(@'C:\Temp\dmCodeBitmap.png'); // debug only
_recordBuilder.addCalculatedField('GS1-Datamatrix_Barcode', this.convertBitmapToContainer(dmCodeBitmap));
}
}
Sample output
Code 39 Barcode
The class takes a sample value, CIV-239483, creates a barcode image from it, converts that image to PNG format, and then adds it to the report data source as a calculated field. That means the barcode can later be placed on the report template just like any other image field.
The example generates three variations of the same Code 39 barcode to show how different output options affect the result. The first version creates a “pure” barcode with no extra padding, so it is tightly cropped around the bars. The second version is also a pure barcode, but with padding added around it. The third version turns off PureBarcode, which allows the human-readable text value to be shown with the barcode, while still keeping padding turned off. In simple terms, the code demonstrates how to produce different visual styles of the same barcode value depending on whether you want whitespace around it and whether you want the barcode text displayed underneath.
Note: This is a sample implementation intended to demonstrate the approach. The external library must be referenced and deployed according to the requirements of the target Dynamics 365 Finance and Operations environment. The generated symbols should be tested with the scanners, printers, document scaling, and output formats used in the target process.
using System.IO;
using ZXing;
using ZXing.Common;
/// <summary>
/// Sample DSP class that generates Code 39 barcode as image with external library
/// </summary>
public class DocCode39BarcodesDSP extends DocDataSourceProviderSrsReporting
{
/// <summary>
/// Description of the DSP class.
/// </summary>
/// <returns>Class description.</returns>
public ClassDescription description()
{
return 'Code 39 Barcode DSP';
}
private container convertBitmapToContainer(System.Drawing.Bitmap image)
{
using(MemoryStream ms = new MemoryStream())
{
image.Save(ms, System.Drawing.Imaging.ImageFormat::Png);
return DocGlobalHelper::convertBytesToContainer(ms.ToArray());
}
}
protected void generateXmlDataSource(DocXmlRecordBuilder _recordBuilder)
{
str sampleInvoiceId = "CIV-239483";
// Code-39 Pure barcode No padding
BarcodeWriter writer = new BarcodeWriter();
writer.Format = BarcodeFormat::CODE_39;
ZXing.Common.EncodingOptions options = new ZXing.Common.EncodingOptions();
options.Width = 300;
options.Height = 100;
options.GS1Format = false;
options.PureBarcode = true;
options.noPadding = true;
writer.Options = options;
System.Drawing.Bitmap code39CodeBitmap = writer.Write(sampleInvoiceId);
_recordBuilder.addCalculatedField('Code39_Barcode', this.convertBitmapToContainer(code39CodeBitmap));
// Code-39 Pure barcode With padding
ZXing.Common.EncodingOptions options2 = new ZXing.Common.EncodingOptions();
options2.Width = 300;
options2.Height = 100;
options2.GS1Format = false;
options2.PureBarcode = true;
options2.noPadding = false;
writer.Options = options2;
System.Drawing.Bitmap code39CodeBitmap2 = writer.Write(sampleInvoiceId);
_recordBuilder.addCalculatedField('Code39_Barcode_wPadding', this.convertBitmapToContainer(code39CodeBitmap2));
// Code-39 With content string No padding
ZXing.Common.EncodingOptions options3 = new ZXing.Common.EncodingOptions();
options3.Width = 300;
options3.Height = 100;
options3.GS1Format = false;
options3.PureBarcode = false;
options3.noPadding = true;
writer.Options = options3;
System.Drawing.Bitmap code39CodeBitmap3 = writer.Write(sampleInvoiceId);
_recordBuilder.addCalculatedField('Code39_Barcode_wContent_wPadding', this.convertBitmapToContainer(code39CodeBitmap3));
}
}
Sample output
Conclusion
This approach is useful when the required barcode encoding or template placement cannot be achieved with a standard barcode tagging element. By generating the barcode in a custom DSP class and exposing it as a calculated image field, it can be used in the Docentric template like any other image.
The examples should be treated as a starting point. Validate the input against the applicable barcode specification and test the final document with the printers and scanners used in the target process.

