Printing warehouse labels in Dynamics 365 Finance & Operations can be complex, especially when you need to display product matrices directly on labels. Historically, generating matrix layouts required direct manipulation of ZPL (Zebra Programming Language) code, which created maintenance challenges and limited flexibility. This comprehensive guide introduces a robust solution that enables document routing layouts to print professional matrix labels without interfering with ZPL code behavior.
The Challenge: Matrix Labels in D365FO
Modern warehouse operations often require labels that display multiple dimensions of a product—sizes, colors, and quantities—in a matrix format. For example, a fashion retailer needs to print labels showing available sizes across different colors with corresponding inventory quantities.
Traditional Approach Problems:
- Manual ZPL code modifications for every label layout
- Risk of breaking existing label functionality
- Difficulty maintaining version control for label customizations
- Limited ability for users to create custom layouts through document routing
- Complex, error-prone manual positioning calculations
The ZPL Matrix Label feature solves these challenges by introducing a declarative tag-based approach that generates matrix layouts automatically.
Solution Overview: The Custom Tag System
Rather than modifying ZPL code directly, this solution introduces a custom tag system that you embed in your document routing layout. The system then automatically generates the matrix with intelligent positioning, sizing, and formatting.
The Magic Tag: ^CN
The core of this solution is a custom tag you place in your ZPL label definition:
^CN<RP>,<CP>,<FontHeight>,<FontWidth>,$ItemId$,$Total$,<Rotation>^CN
Parameter Breakdown:
| Parameter | Description | Example |
|---|---|---|
<RP> | Row Position - Vertical starting position in dots | 50 |
<CP> | Column Position - Horizontal starting position in dots | 100 |
<FontHeight> | Font Breadth - Height multiplier for font size | 20 |
<FontWidth> | Font Width - Width multiplier for font size | 10 |
$ItemId$ | The item identifier (replaced by system) | AUTO |
$Total$ | Toggle for total rows/columns ($Total$ or leave empty) | $Total$ |
<Rotation> | Matrix orientation: TB (top-to-bottom) or LR (left-to-right) | TB |
How It Works
When the system processes a label containing the
^CN...^CN tag:- Detection - The system locates the custom
^CN...^CNtag in the ZPL code - Parsing - Parameters are extracted and validated
- Generation - The
FoxWHSZPLMatrixGeneratorclass generates the matrix - Replacement - The original tag is replaced with generated ZPL code
- Output - The final label prints with the matrix intact
The beauty of this approach is that the replacement happens at the document routing level—your core ZPL generation logic remains untouched.
Technical Architecture
Core Class: FoxWHSZPLMatrixGenerator
This powerful class handles all matrix generation logic:
x++/// <summary> /// Class to generate the ZPL matrix /// </summary> class FoxWHSZPLMatrixGenerator { WHSZPL inputZPL; int horizontalPosition; int verticalPosition; int fixedPosition; int changingPosition; int zplColorStartPos; int zplStyleStartPos; int zplSizeStartPos; Map sizeMap; Map sizeTotal; str outputZPL; real fontBreadth; int fontWidth; }
Key Methods
generate() - Standard Matrix Generation
Processes items with multiple color, style, and size dimensions:
x++private WHSZPL generate() { // Parse custom tag // Determine rotation orientation (top-to-bottom or left-to-right) // Build matrix header (item id, color, style columns) // Add data rows with quantities // Add total row and total column if requested // Return modified ZPL with matrix embedded }
generateNOOS() - Specialized NOOS Processing
For "Not Out Of Stock" items, this specialized method:
- Queries inventory dimensions (size, color, variant)
- Applies different font sizes for headers vs. values
- Optimizes spacing for readability
- Reduces redundant data queries
Matrix Orientation: Two Display Modes
Top-to-Bottom (TB) - Column-Wise Layout
Perfect for labels with more colors than sizes:
Item ID | RED | BLUE | GREEN
---------|------|------|-------
Size S | 10 | 15 | 8
Size M | 12 | 18 | 10
Size L | 9 | 12 | 7
TOTAL | 31 | 45 | 25
- Row position (RP) fixes the vertical position
- Column position (CP) increments as you move across sizes
- Optimized for landscape labels with more color variants
Left-to-Right (LR) - Row-Wise Layout
Ideal for labels with more sizes than colors:
Item ID | Size S | Size M | Size L | TOTAL
---------|--------|--------|--------|-------
RED | 10 | 12 | 9 | 31
BLUE | 15 | 18 | 12 | 45
GREEN | 8 | 10 | 7 | 25
- Column position (CP) fixes the horizontal position
- Row position (RP) increments moving down colors
- Optimized for portrait labels with more size variants
Implementation Example
Step 1: Define Your Document Routing Layout
In your document routing configuration, create a label layout with the custom tag embedded:
zpl^XA ^MMT ^PW832 ^LL1200 ^LS0 ^FT50,50^A0N,30,25^FDLabel Title^FS ^FT50,100^A0N,20,20^FDItem: [ItemId]^FS ^FT50,150^A0N,20,20^FDBrand: [BrandName]^FS REM *** Matrix Generation Tag *** ^CN100,200,20,10,$ItemId$,$Total$,TB^CN ^FT50,900^A0N,15,15^FDDate: [CurrentDate]^FS ^XZ
Step 2: Configure the Routing Format
In Dynamics 365 Finance & Operations:
- Navigate to Warehouse Management > Document Routing
- Create or edit a format for your label type
- Set the output format to ZPL
- Paste your label template with the
^CN...^CNtag - Test with sample items
Step 3: Call the Generator
In your custom code or document routing handler:
x++WHSZPL zplOutput = FoxWHSZPLMatrixGenerator::gererateZPLMatrix(inputZPL);
For NOOS items:
x++WHSZPL zplOutput = FoxWHSZPLMatrixGenerator::gererateZPLMatrixNOOS(inputZPL);
Advanced Features
Intelligent Spacing Calculations
The generator automatically calculates positioning based on font dimensions:
x++// For each size discovered, position is calculated int sizePosition = zplSizeStartPos + (sizeCount * SizeQuantitySpace); // Font-aware spacing ensures text doesn't overlap int fontAdjustedSpace = (fontBreadth * 24) / 40;
Totaling System
When
$Total$ is specified, the system:- Total Row: Sums quantities for each size across all colors
- Total Column: Sums quantities for each color across all sizes
- Grand Total: Provides overall inventory count
- Automatic positioning to avoid overlaps
Zero-Fill Logic
For consistency, the system fills missing size/color combinations with "0":
x++private str addZeroSizes() { // Iterate through all known sizes // For each color row, check if size combination exists // If missing, place "0" at correct position // Maintains matrix structure for quick scanning }
NOOS Specialized Features
The
generateNOOS() method includes:x++real normalSize = 1.3; // Standard font multiplier for values real largeSize = 3; // Larger font for headers int headerWidth = 100; // Fixed spacing for headers
Labels include:
- Size label with large font
- Color label with large font
- Variant ID and Retail Variant in normal font
- Automatic size and spacing adjustments based on
rotationTag
Complete Implementation Code
This section provides the full, production-ready source code for implementing the ZPL matrix label feature. You can download these X++ classes directly into your Dynamics 365 Finance & Operations environment.
How to Implement
- Create the Constants Class - First add
FoxWHSZPLMatrixConstantsto your project - Create the Generator Class - Then add
FoxWHSZPLMatrixGeneratorwhich depends on the constants - Integrate - Call the static methods from your document routing handlers
- Test - Print sample labels to verify positioning and output
FoxWHSZPLMatrixConstants Class
This class provides all constants used throughout the matrix generation:
x++/// <summary> /// Class to contain constants used for ZPL matrix generation /// </summary> public static class FoxWHSZPLMatrixConstants { public static const str CustomItemTag = '^CN'; public static const int ItemIdSpace = 130; public static const int ColourCodeSpace = 100; public static const int StyleCodeSpace = 100; public static const int SizeQuantitySpace = 50; public static const int SpaceBtnLine = 28; public static const str StraightTextTag = '^A0'; public static const str VerticalTextTag = '^A0B'; public static const str LeftJustified = '^FO'; public static const str RightJustified = '^FT'; public static const str FontStart = '^CI4^FD'; public static const str FontEnd = '^FS'; public static const str EmptyStyle = '-'; public static const str TotalTag = '$Total$'; public static const str ZeroSize = '0'; public static const str LabelParm = identifierStr('_label'); public static const int NOOSHeaderWidth = 100; public static const str RotatedTextTag = '^A0R'; public static const str MatrixDirectionLeftToRight = 'LR'; public static const str MatrixDirectionTopToBotttom = 'TB'; public static const int NOOSMatrixIterationConstant = 10; public static const real NOOSMatrixNormalSizeConstant = 1.3; public static const real NOOSMatrixLargeSizeConstant = 3; }
FoxWHSZPLMatrixGenerator Class - Core Methods
Class Declaration and Initialization
x++/// <summary> /// Class to generate the ZPL matrix /// </summary> class FoxWHSZPLMatrixGenerator { WHSZPL inputZPL; int horizontalPosition; int verticalPosition; int fixedPosition; int changingPosition; int zplColorStartPos; int zplStyleStartPos; int zplSizeStartPos; int lineSpaceIterator; int sizeHoriPosIterator; Map sizeMap; Map sizeTotal; str outputZPL; container colorTotal; real fontBreadth; int fontWidth; container allSizeContainer; str rotationTag; /// <summary> /// New method overridden to input ZPL text /// </summary> /// <param name = "_zpl">Input ZPL text from label</param> protected void new(WHSZPL _zpl) { inputZPL = _zpl; } }
Main Generation Method
x++private WHSZPL generate() { WHSZPL ret; str lineMatrix; str colRowTotalTag; int noOfItemTagChar = strlen(FoxWHSZPLMatrixConstants::CustomItemTag); int startTagPosition = strScan(inputZPL, FoxWHSZPLMatrixConstants::CustomItemTag, 0, strLen(inputZPL)); if (!startTagPosition) { return inputZPL; // if no Custom tag do not apply matrix } int endTagPosition = strScan( inputZPL, FoxWHSZPLMatrixConstants::CustomItemTag, noOfItemTagChar + startTagPosition, strLen(inputZPL)); str inputString = subStr( inputZPL, startTagPosition + noOfItemTagChar, endTagPosition - startTagPosition - noOfItemTagChar); container inputCon = str2con(inputString, ",", false); horizontalPosition = conPeek(inputCon, 1); verticalPosition = conPeek(inputCon, 2); fontBreadth = conPeek(inputCon, 3); fontWidth = conPeek(inputCon, 4); ItemId itemId = conPeek(inputCon, 5); colRowTotalTag = conPeek(inputCon, 6); rotationTag = conpeek(inputCon, 7); // Check if the matrix needs to be printed from left to right or from top to bottom if (rotationTag == FoxWHSZPLMatrixConstants::MatrixDirectionTopToBotttom) { // assign the starting positions of the columns which will be fixed zplColorStartPos = verticalPosition + FoxWHSZPLMatrixConstants::ItemIdSpace; zplStyleStartPos = zplColorStartPos + FoxWHSZPLMatrixConstants::ColourCodeSpace; zplSizeStartPos = zplStyleStartPos + FoxWHSZPLMatrixConstants::StyleCodeSpace; // In this case the vertical position is fixed where as the horizontal position of the fields gets changed lineSpaceIterator = horizontalPosition; fixedPosition = verticalPosition; changingPosition = horizontalPosition; } else { // assign the starting positions of the columns which will be fixed zplColorStartPos = horizontalPosition + FoxWHSZPLMatrixConstants::ItemIdSpace; zplStyleStartPos = zplColorStartPos + FoxWHSZPLMatrixConstants::ColourCodeSpace; zplSizeStartPos = zplStyleStartPos + FoxWHSZPLMatrixConstants::StyleCodeSpace; // In this case the horizontal position is fixed where as the vertical position of the fields gets changed lineSpaceIterator = verticalPosition; fixedPosition = horizontalPosition; changingPosition = verticalPosition; } outputZPL = this.addHeaderToZpl(); sizeHoriPosIterator = zplSizeStartPos; sizeMap = new Map(Types::String ,Types::Integer); sizeTotal = new Map(Types::String ,Types::Real); lineMatrix = this.addLinesToZPL(itemId); outputZPL += lineMatrix; if (colRowTotalTag == FoxWHSZPLMatrixConstants::TotalTag) { outputZPL += this.addTotalRow(); outputZPL += this.addTotalColumn(); } ret = strDel(inputZPL, startTagPosition, endTagPosition + noOfItemTagChar - startTagPosition); ret = strIns(ret, outputZPL, startTagPosition); return ret; }
Data Formatting Methods
x++/// <summary> /// Format and add data to ZPL matrix /// </summary> private str addDataToZpl(real _rp, real _cp, str _data) { str ret; // if the matrix direction is from top to bottom the column position will become the row position and vice versa if(rotationTag == FoxWHSZPLMatrixConstants::MatrixDirectionTopToBotttom) { ret = strFmt("%1,%2,%3%4%5,%6%7%8%9\n", FoxWHSZPLMatrixConstants::RotatedTextTag, fontBreadth, fontWidth, FoxWHSZPLMatrixConstants::LeftJustified, Num2Str(_cp,0,2,1,0), Num2Str(_rp,0,2,1,0), FoxWHSZPLMatrixConstants::FontStart, _data, FoxWHSZPLMatrixConstants::FontEnd); } else { ret = strFmt("%1,%2,%3%4%5,%6%7%8%9\n", FoxWHSZPLMatrixConstants::StraightTextTag, fontBreadth, fontWidth, FoxWHSZPLMatrixConstants::LeftJustified, Num2Str(_rp,0,2,1,0), Num2Str(_cp,0,2,1,0), FoxWHSZPLMatrixConstants::FontStart, _data, FoxWHSZPLMatrixConstants::FontEnd); } return ret; } /// <summary> /// Format and add Vertical text to ZPL matrix /// </summary> private str addVerticalDataToZpl(real _rp, real _cp, str _data) { str ret; if(rotationTag == FoxWHSZPLMatrixConstants::MatrixDirectionTopToBotttom) { ret = strFmt("%1,%2,%3%4%5,%6%7%8%9\n", FoxWHSZPLMatrixConstants::StraightTextTag, fontBreadth, fontWidth, FoxWHSZPLMatrixConstants::RightJustified, Num2Str(_cp,0,2,1,0), Num2Str(_rp,0,2,1,0), FoxWHSZPLMatrixConstants::FontStart, _data, FoxWHSZPLMatrixConstants::FontEnd); } else { ret = strFmt("%1,%2,%3%4%5,%6%7%8%9\n", FoxWHSZPLMatrixConstants::VerticalTextTag, fontBreadth, fontWidth, FoxWHSZPLMatrixConstants::RightJustified, Num2Str(_rp,0,2,1,0), Num2Str(_cp,0,2,1,0), FoxWHSZPLMatrixConstants::FontStart, _data, FoxWHSZPLMatrixConstants::FontEnd); } return ret; }
Matrix Lines Generation
x++/// <summary> /// Add matrix lines /// </summary> private str addLinesToZPL(ItemId _itemId) { str ret; BOM bom; InventDim inventDim; boolean isFirst = true; int iterator = 1; Query query; QueryRun queryRun; QueryBuildDataSource qbdsInventDim, qbdsBOM, qbdsBOMVersion; ItemId lastItemId; EcoResColorName lastColorId; EcoResSizeName lastSizeId; EcoResStyleName lastStyleId; Qty colorTotalQty; container sizeContainer; // Query to fetch BOM with dimensions grouped by color, style, and size query = new Query(); qbdsBOM = query.addDataSource(tableNum(BOM)); qbdsBOM.addSelectionField(fieldNum(BOM, BOMQty), SelectionField::Sum); qbdsBOM.addGroupByField(fieldNum(BOM, ItemId)); qbdsInventDim = qbdsBOM.addDataSource(tablenum(InventDim)); qbdsInventDim.addGroupByField(fieldNum(InventDim, InventColorId)); qbdsInventDim.addGroupByField(fieldNum(InventDim, InventStyleId)); qbdsInventDim.addGroupByField(fieldNum(InventDim, InventSizeId)); qbdsInventDim.joinMode(JoinMode::InnerJoin); qbdsInventDim.relations(true); qbdsBOMVersion = qbdsInventDim.addDataSource(tablenum(BOMVersion)); qbdsBOMVersion.addLink(fieldNum(BOM, BOMId), Fieldnum(BOMVersion, BOMId), qbdsBOM.name()); qbdsBOMVersion.joinMode(JoinMode::ExistsJoin); qbdsBOMVersion.addRange(fieldnum(BOMVersion, ItemId)).value(_itemId); qbdsBOMVersion.addRange(fieldnum(BOMVersion, Active)).value(SysQuery::value(NoYes::Yes)); queryRun = new QueryRun(query); // For each line add to the zpl text while(queryRun.next()) { bom = queryRun.get(tableNum(Bom)); inventDim = queryRun.get(tableNum(InventDim)); if (isFirst || lastItemId != bom.ItemId || lastColorId != inventDim.InventColorId || lastStyleId != inventDim.InventStyleId) { lineSpaceIterator = this.updateLineSpaceIterator(lineSpaceIterator); ret += this.addDataToZpl(fixedPosition, lineSpaceIterator, bom.ItemId); ret += this.addDataToZpl(zplColorStartPos, lineSpaceIterator, inventDim.InventColorId); if (inventDim.InventStyleId) { ret += this.addDataToZpl(zplStyleStartPos, lineSpaceIterator, inventDim.InventStyleId); } else { ret += this.addDataToZpl(zplStyleStartPos, lineSpaceIterator, FoxWHSZPLMatrixConstants::EmptyStyle); } if (!isFirst) { colorTotal += colorTotalQty; colorTotalQty = 0; allSizeContainer = conIns(allSizeContainer, iterator, sizeContainer); sizeContainer = conNull(); iterator++; } } ret += this.addDataToZpl(this.findOrCreateSizeGetPos(inventDim.InventSizeId), lineSpaceIterator, strFmt("%1", real2int(bom.BOMQty))); this.findOrCreateSizeTotal(inventDim.InventSizeId, bom.BOMQty); sizeContainer += inventDim.InventSizeId; colorTotalQty += bom.BOMQty; lastItemId = bom.ItemId; lastColorId = inventDim.InventColorId; lastStyleId = inventDim.InventStyleId; lastSizeId = inventDim.InventSizeId; isFirst = false; } if (ret) { colorTotal += colorTotalQty; allSizeContainer = conIns(allSizeContainer, iterator, sizeContainer); } ret += this.addZeroSizes(); return ret; }
Total Row and Column Methods
x++/// <summary> /// Add total row to ZPL matrix /// </summary> private str addTotalRow() { MapIterator sizeMapIterator = new MapIterator(sizeTotal); EcoResSizeName sizeName; int sizePosition; Qty sizeTotalQty; str ret; lineSpaceIterator = this.updateLineSpaceIterator(lineSpaceIterator); ret = this.addDataToZpl(fixedPosition, lineSpaceIterator, "@SYS9242"); while (sizeMapIterator.more()) { sizeName = sizeMapIterator.key(); sizePosition = sizeMap.lookup(sizeName); sizeTotalQty = sizeMapIterator.value(); ret += this.addDataToZpl(sizePosition, lineSpaceIterator, strFmt("%1", real2int(sizeTotalQty))); sizeMapIterator.next(); } return ret; } /// <summary> /// Add total column to ZPL matrix /// </summary> private str addTotalColumn() { int colorSizeIterator; Qty colorTotalQty, totalAllQty; int colorTotalPos = changingPosition; str ret; ret = this.addDataToZpl(sizeHoriPosIterator, changingPosition, "@SYS9242"); colorTotalPos = this.updateLineSpaceIterator(colorTotalPos); for (colorSizeIterator = 1; colorSizeIterator <= conLen(colorTotal); colorSizeIterator++) { colorTotalQty = conPeek(colorTotal, colorSizeIterator); totalAllQty += colorTotalQty; ret += this.addDataToZpl(sizeHoriPosIterator, colorTotalPos, strFmt("%1", real2int(colorTotalQty))); colorTotalPos = this.updateLineSpaceIterator(colorTotalPos); } ret += this.addDataToZpl(sizeHoriPosIterator, colorTotalPos, strFmt("%1", real2int(totalAllQty))); return ret; }
Static Entry Points
x++/// <summary> /// Static method to call to generate ZPL matrix for input ZPL label text /// </summary> public static WHSZPL gererateZPLMatrix(WHSZPL _zpl) { WHSZPL ret; FoxWHSZPLMatrixGenerator matrixGenerator = new FoxWHSZPLMatrixGenerator(_zpl); ret = matrixGenerator.generate(); return ret; } /// <summary> /// Static method to call to generate ZPL matrix for input ZPL label text for NOOS items /// </summary> public static WHSZPL gererateZPLMatrixNOOS(WHSZPL _zpl) { WHSZPL ret; FoxWHSZPLMatrixGenerator matrixGenerator = new FoxWHSZPLMatrixGenerator(_zpl); ret = matrixGenerator.generateNOOS(); return ret; } /// <summary> /// Update the line space iterator /// </summary> public int updateLineSpaceIterator(int _lineSpaceIterator) { int lineSpaceIteratorLoc; lineSpaceIteratorLoc = _lineSpaceIterator; if(rotationTag == FoxWHSZPLMatrixConstants::MatrixDirectionTopToBotttom) { // If the direction is top to bottom the line space iterator will decrease in value lineSpaceIteratorLoc -= FoxWHSZPLMatrixConstants::SpaceBtnLine; } else { // If the direction is left to right the line space iterator will increase in value lineSpaceIteratorLoc += FoxWHSZPLMatrixConstants::SpaceBtnLine; } return lineSpaceIteratorLoc; }
Size Management Methods
x++/// <summary> /// Populate size map with size positions /// </summary> private int findOrCreateSizeGetPos(EcoResItemSizeName _sizeId) { int ret; if (sizeMap.exists(_sizeId)) { ret = sizeMap.lookup(_sizeId); } else { sizeMap.insert(_sizeId, sizeHoriPosIterator); ret = sizeHoriPosIterator; outputZPL += this.addVerticalDataToZpl( sizeHoriPosIterator + ((fontWidth * 24) / 40), changingPosition + ((fontBreadth * 23) / 40), _sizeId); sizeHoriPosIterator += FoxWHSZPLMatrixConstants::SizeQuantitySpace; } return ret; } /// <summary> /// Store size total for each size position /// </summary> private void findOrCreateSizeTotal(EcoResItemSizeName _sizeId, Qty _qty) { Qty sizeQty; if (sizeTotal.exists(_sizeId)) { sizeQty = sizeTotal.lookup(_sizeId); sizeTotal.insert(_sizeId, sizeQty + _qty); } else { sizeTotal.insert(_sizeId, _qty); } } /// <summary> /// Add zero to empty size /// </summary> private str addZeroSizes() { MapIterator sizeMapIterator = new MapIterator(sizeTotal); EcoResSizeName sizeName; int sizeVPosition = changingPosition + FoxWHSZPLMatrixConstants::SpaceBtnLine; int sizeHPosition; int iterator; container sizeContainer; str ret; for(iterator = 1; iterator <= conLen(allSizeContainer); iterator++) { sizeContainer = conPeek(allSizeContainer, iterator); sizeMapIterator.begin(); while (sizeMapIterator.more()) { sizeName = sizeMapIterator.key(); sizeHPosition = sizeMap.lookup(sizeName); if (!conFind(sizeContainer, sizeName)) { ret += this.addDataToZpl(sizeHPosition, sizeVPosition, strFmt("%1", FoxWHSZPLMatrixConstants::ZeroSize)); } sizeMapIterator.next(); } sizeVPosition += FoxWHSZPLMatrixConstants::SpaceBtnLine; } return ret; }
NOOS Specialized Methods
x++/// <summary> /// Generate method entry point for class for item of type NOOS /// </summary> private WHSZPL generateNOOS() { real normalSize = FoxWHSZPLMatrixConstants::NOOSMatrixNormalSizeConstant; real largeSize = FoxWHSZPLMatrixConstants::NOOSMatrixLargeSizeConstant; WHSZPL ret; int noOfItemTagChar = strlen(FoxWHSZPLMatrixConstants::CustomItemTag); int startTagPosition = strScan(inputZPL, FoxWHSZPLMatrixConstants::CustomItemTag, 0, strLen(inputZPL)); if (!startTagPosition) { return inputZPL; // if no Custom tag do not apply matrix } int endTagPosition = strScan(inputZPL, FoxWHSZPLMatrixConstants::CustomItemTag, noOfItemTagChar + startTagPosition, strLen(inputZPL)); str inputString = subStr(inputZPL, startTagPosition + noOfItemTagChar, endTagPosition - startTagPosition - noOfItemTagChar); container inputCon = str2con(inputString, ",", false); horizontalPosition = conPeek(inputCon, 1); verticalPosition = conPeek(inputCon, 2); fontBreadth = conPeek(inputCon, 3); fontWidth = conPeek(inputCon, 4); rotationTag = conpeek(inputCon, 8); ItemBarCode itemBarCode = conPeek(inputCon, 6); EcoResProductName styleName = conPeek(inputCon, 7); InventItemBarcode inventItemBarcode = InventItemBarcode::findBarcode(itemBarCode, NoYes::No, NoYes::No); InventDim inventDim = InventDim::find(inventItemBarcode.inventDimId); real posIterator; if (rotationTag == FoxWHSZPLMatrixConstants::MatrixDirectionTopToBotttom) { fixedPosition = verticalPosition; changingPosition = horizontalPosition; posIterator = horizontalPosition; } else { fixedPosition = horizontalPosition; changingPosition = verticalPosition; posIterator = verticalPosition; } outputZPL = this.addNOOSDataToZPL(fontBreadth, fontWidth, fixedPosition, posIterator, "@SYS73727:"); outputZPL += this.addNOOSDataToZPL(fontBreadth * largeSize, fontWidth * largeSize, fixedPosition + FoxWHSZPLMatrixConstants::NOOSHeaderWidth, posIterator, inventDim.InventSizeId); if (rotationTag == FoxWHSZPLMatrixConstants::MatrixDirectionTopToBotttom) { posIterator -= this.getChangedSpaceLargeFontBreadth(fontBreadth); } else { posIterator += this.getChangedSpaceLargeFontBreadth(fontBreadth); } outputZPL += this.addNOOSDataToZPL(fontBreadth, fontWidth, fixedPosition, posIterator, "@SYS73726:"); outputZPL += this.addNOOSDataToZPL((fontBreadth * normalSize), (fontWidth * normalSize), fixedPosition + FoxWHSZPLMatrixConstants::NOOSHeaderWidth, posIterator, inventDim.InventColorId); if (rotationTag == FoxWHSZPLMatrixConstants::MatrixDirectionTopToBotttom) { posIterator -= this.getChangedSpaceNormalFontBreadth(fontBreadth); } else { posIterator += this.getChangedSpaceNormalFontBreadth(fontBreadth); } outputZPL += this.addNOOSDataToZPL(fontBreadth, fontWidth, fixedPosition, posIterator, "@RET3715:"); outputZPL += this.addNOOSDataToZPL((fontBreadth * normalSize), (fontWidth * normalSize), fixedPosition + FoxWHSZPLMatrixConstants::NOOSHeaderWidth, posIterator, inventItemBarcode.RetailVariantId); if (rotationTag == FoxWHSZPLMatrixConstants::MatrixDirectionTopToBotttom) { posIterator -= this.getChangedSpaceNormalFontBreadth(fontBreadth); } else { posIterator += this.getChangedSpaceNormalFontBreadth(fontBreadth); } outputZPL += this.addNOOSDataToZPL(fontBreadth, fontWidth, fixedPosition, posIterator, "@SYS4001473:"); outputZPL += this.addNOOSDataToZPL((fontBreadth * normalSize), (fontWidth * normalSize), fixedPosition + FoxWHSZPLMatrixConstants::NOOSHeaderWidth, posIterator, styleName); ret = strDel(inputZPL, startTagPosition, endTagPosition + noOfItemTagChar - startTagPosition); ret = strIns(ret, outputZPL, startTagPosition); return ret; } /// <summary> /// Add Parameters to ZPL text for NOOS /// </summary> private str addNOOSDataToZPL(real _fontBreadth, real _fontWidth, real _cp, real _rp, str _data) { if (rotationTag == FoxWHSZPLMatrixConstants::MatrixDirectionTopToBotttom) { return strFmt("%1,%2,%3%4%5,%6%7%8%9\n", FoxWHSZPLMatrixConstants::RotatedTextTag, _fontBreadth, _fontWidth, FoxWHSZPLMatrixConstants::LeftJustified, Num2Str(_rp,0,2,1,0), Num2Str(_cp,0,2,1,0), FoxWHSZPLMatrixConstants::FontStart, _data, FoxWHSZPLMatrixConstants::FontEnd); } else { return strFmt("%1,%2,%3%4%5,%6%7%8%9\n", FoxWHSZPLMatrixConstants::StraightTextTag, _fontBreadth, _fontWidth, FoxWHSZPLMatrixConstants::LeftJustified, Num2Str(_cp,0,2,1,0), Num2Str(_rp,0,2,1,0), FoxWHSZPLMatrixConstants::FontStart, _data, FoxWHSZPLMatrixConstants::FontEnd); } } /// <summary> /// Get space based on font breadth considering the normal font size /// </summary> public real getChangedSpaceNormalFontBreadth(real _fontBreadth) { return ((_fontBreadth * FoxWHSZPLMatrixConstants::NOOSMatrixNormalSizeConstant) + FoxWHSZPLMatrixConstants::NOOSMatrixIterationConstant); } /// <summary> /// Get space based on font breadth considering the large font size /// </summary> public real getChangedSpaceLargeFontBreadth(real _fontBreadth) { return ((_fontBreadth * FoxWHSZPLMatrixConstants::NOOSMatrixLargeSizeConstant) + FoxWHSZPLMatrixConstants::NOOSMatrixIterationConstant); }
Using the Generator in Your Code
To integrate the matrix generator into your document routing or label printing logic:
x++// Example: In your document routing handler or label printing class public void printMatrixLabel(WHSZPL _zplTemplate, ItemId _itemId) { WHSZPL generatedZPL; // Generate matrix-enhanced ZPL generatedZPL = FoxWHSZPLMatrixGenerator::gererateZPLMatrix(_zplTemplate); // Send to printer this.sendToPrinter(generatedZPL); } public void printNOOSLabel(WHSZPL _zplTemplate, ItemId _itemId) { WHSZPL generatedZPL; // Generate NOOS-specific matrix generatedZPL = FoxWHSZPLMatrixGenerator::gererateZPLMatrixNOOS(_zplTemplate); // Send to printer this.sendToPrinter(generatedZPL); }
Advantages of This Approach
✅ Non-Invasive - No modification to core ZPL generation logic
✅ User-Friendly - Document routing layout designers can create matrices without coding
✅ Flexible - Supports two orientation modes for different label layouts
✅ Automatic Positioning - No manual dot calculations needed
✅ Scalable - Handles any number of sizes, colors, and styles
✅ Maintainable - All matrix logic centralized in one class
✅ Extensible - Easy to add new matrix types or NOOS variants
✅ User-Friendly - Document routing layout designers can create matrices without coding
✅ Flexible - Supports two orientation modes for different label layouts
✅ Automatic Positioning - No manual dot calculations needed
✅ Scalable - Handles any number of sizes, colors, and styles
✅ Maintainable - All matrix logic centralized in one class
✅ Extensible - Easy to add new matrix types or NOOS variants
Best Practices
1. Test Positioning First
Before deploying to production:
- Print test labels with sample items
- Verify matrix positioning relative to other label elements
- Adjust
<RP>and<CP>values based on actual output
2. Font Size Optimization
- Smaller
<FontHeight>and<FontWidth>for dense matrices - Larger sizes for labels with few dimensions
- Test readability with actual printers
3. Choose Orientation Wisely
Use TB (Top-to-Bottom) when: Use LR (Left-to-Right) when:
- More colors than sizes - More sizes than colors
- Landscape label format - Portrait label format
- Wide label area - Tall label area
4. Handle Total Rows Carefully
- Use
$Total$for high-value inventory tracking - Omit totals for complex multi-variant items to save space
- Verify row/column sum accuracy before production
5. Performance Considerations
- The generator queries BOM and InventDim tables—ensure indexes are optimized
- For high-volume printing, consider query performance tuning
- NOOS processing uses additional lookups; monitor performance
Troubleshooting
Matrix Not Appearing
Problem: Custom tag not replaced with matrix
Solution:
Solution:
- Verify tag format exactly:
^CN...^CN(case-sensitive) - Confirm all parameters are present (7 parameters required)
- Check that the routing format calls the generator method
Positioning Issues
Problem: Matrix overlaps with other label elements
Solution:
Solution:
- Recalculate starting positions based on label dimensions
- Adjust
<RP>and<CP>values - Account for font size impact on total space needed
Missing Inventory Data
Problem: Sizes or colors not appearing in matrix
Solution:
Solution:
- Verify item BOM includes all dimensions
- Check InventDim records exist for all combinations
- Confirm BOMVersion records are active for the item
Conclusion
The ZPL Matrix Label feature transforms warehouse label printing in Dynamics 365 Finance & Operations. By introducing a simple, declarative tag system, it eliminates the complexity of manual ZPL manipulation while providing powerful, flexible matrix generation capabilities.
Whether you're managing fashion inventory with multiple colors and sizes, pharmaceuticals with batch variants, or any product with dimensional complexity, this solution delivers professional, scannable labels through standard document routing layouts. The intelligent positioning, automatic spacing calculations, and specialized NOOS support make it a comprehensive solution for modern warehouse operations.
Key Takeaway: Use the
^CN...^CN custom tag in your document routing layouts to unlock professional matrix label printing without touching core ZPL code.Additional Resources
- Dynamics 365 Document Routing: Microsoft Learn - Document Routing
- ZPL Programming Guide: Zebra Technologies - ZPL Programming Language
- Warehouse Management in D365FO: Microsoft Learn - Warehouse Management Overview