Related
Take the following non-null safe Dart code:
static String appBarShiftTitleString(int fromEpochSeconds) {
String monthWord;
String dayWord;
DateTime dt = DateTime.fromMillisecondsSinceEpoch(fromEpochSeconds * 1000);
switch (dt.month) {
case 1:
monthWord = "Jan";
break;
case 2:
monthWord = "Feb";
break;
case 3:
monthWord = "Mar";
break;
case 4:
monthWord = "Apr";
break;
case 5:
monthWord = "May";
break;
case 6:
monthWord = "Jun";
break;
case 7:
monthWord = "Jul";
break;
case 8:
monthWord = "Aug";
break;
case 9:
monthWord = "Sep";
break;
case 10:
monthWord = "Oct";
break;
case 11:
monthWord = "Nov";
break;
case 12:
monthWord = "Dec";
break;
}
switch (dt.weekday) {
case 1:
dayWord = "Mon";
break;
case 2:
dayWord = "Tue";
break;
case 3:
dayWord = "Wed";
break;
case 4:
dayWord = "Thu";
break;
case 5:
dayWord = "Fri";
break;
case 6:
dayWord = "Sat";
break;
case 7:
dayWord = "Sun";
break;
}
return dayWord + ' ' + monthWord + ' ' + dt.day.toString();
}
Android Studio is saying, "The non-nullable local variable 'dayWord' must be assigned before it can be used."
I understand the error and have discovered that I can simply modify the first two lines of the method like this:
String monthWord = "error!";
String dayWord = "error!";
This way, I satisfy the language rules, and it will be plainly obvious if we reach what ought to be an impossible situation of the variable not having been assigned.
This seems hacky though... so in these types of scenarios, what is the elegant and proper way to convert this code to null safety, and if there are multiple ways, then what are the pros and cons?
In general, you have a few options:
1. Initialize the variable to some non-null sentinel value and assert later:
String monthWord = '';
// ...
switch (dt.month) {
// ...
}
assert(monthWord.isNotEmpty);
This will cause debug builds to throw AssertionError at runtime if you neglect to handle a case for it in the switch.
2. Make the variable nullable and use the null assertion operator:
String? monthWord;
// ...
switch (dt.month) {
// ...
}
monthWord!;
// Since `monthWord` is a local variable, it will now be promoted to a
// non-nullable `String` type.
This will throw a TypeError in all build types if you neglect to set the variable to a non-null value.
3. Make the variable late
Declaring variables as late states that you promise that the variables will be initialized before they are ever read. The compiler will generate runtime checks that verify that the variable is initialized when you try to access it. This will throw a LateInitializationError in all build types if you neglect to set the variable.
4. Add a default case that throws
If all of your cases set a local variable, adding a default case that throws allows the compiler to deduce that that variable must always be set if code after the switch statement is reached:
String monthWord; // No explicit initialization required!
// ...
switch (dt.month) {
case 1:
monthWord = "Jan";
break;
// ... etc. ...
default:
throw AssertionError('Unhandled case: ${dt.month}');
}
// The compiler now can deduce that `monthWord` is guaranteed to be
// initialized.
(Note that you should not add a default case for this purpose if you're using a switch statement on an enum type. For enums, the compiler and analyzer can determine if your cases are exhaustive and will generate analysis warnings if you accidentally omit any cases.)
As for which approach to use, it's mostly a matter of preference. They're all mostly equivalent in that they'll result in runtime errors. I personally would choose #1 (assert) or #4 (default case) to avoid unnecessary checks in release builds.
In your particular example, I also would just use DateTime.month and DateTime.day as indices into Lists of the month and day names respectively:
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
assert(months.length == 12);
assert(days.length == 7);
var monthWord = months[dt.month - 1];
var dayWord = days [dt.day - 1];
I want to convert a time string to Arabic. For example I want to convert 10:00 PM to مساءاً ١٠:٠٠. I am using format function with locale.
import ar from 'date-fns/locale/ar-SA'
...
str = format(date, 'h:mm a', {locale: ar})
But it gives a string like this 10:00 م. Basically digits are not getting translated.
You can try with this logic
const mappedDigit = (char: string): string => {
switch (char) {
case '0':
return '٠';
case '1':
return '١';
case '2':
return '٢';
case '3':
return '٣';
case '4':
return '٤';
case '5':
return '٥';
case '6':
return '٦';
case '7':
return '٧';
case '8':
return '٨';
case '9':
return '٩';
default:
return char;
}
};
const getArabicFormats = (englishString: string): string => {
let newArabicString = '';
for (let i = 0; i < englishString.length; i += 1) {
newArabicString += mappedDigit(englishString[i]);
}
newArabicString = newArabicString.replace('AM', 'ص');
newArabicString = newArabicString.replace('PM', 'م');
return newArabicString;
};
In a Google spreadsheet doc, I need to set the cell colour based in duplicated value in column with different colours for each duplicated values. The colour should be set to 'random' so that all of them will be different.
After the condition is applied the colour of the cells should look this: https://docs.google.com/spreadsheets/d/1YuUjg_PqD53AoTrxgvnEHYwZ_disqvNKDMmp5dRYq4I/edit?usp=sharing
This question is similar to this How to highlight cell if value duplicate in same column for google spreadsheet? but not the same since I need to give different colours for each duplicated value
I guess I might need a script to get this done? I don't think this could be done with 'conditional formatting' rule feature.
UPDATE:
The conditionals posted by Player0 works great. But the problem is that in the example I just posted a few cells. In the real spreadsheet I have hundreds of values which change from time to time. Then with this approach I will need to create a lot of condicional rules. I need to do this dynamically with a single formula. It should be a single formula/script to be applied to Column A which generate a random color for each value coincidence...
yellow:
=(ARRAYFORMULA(VLOOKUP(A1; {UNIQUE(A$1:A)\
ROW(INDIRECT("A1:A"&COUNTUNIQUE(A$1:A)+1))}; 2; 0))=1)*
(COUNTIF(A:A; A1)>1)
blue:
=(ARRAYFORMULA(VLOOKUP(A1; {UNIQUE(A$1:A)\
ROW(INDIRECT("A1:A"&COUNTUNIQUE(A$1:A)+1))}; 2; 0))=2)*
(COUNTIF(A:A; A1)>1)
pink:
=(ARRAYFORMULA(VLOOKUP(A1; {UNIQUE(A$1:A)\
ROW(INDIRECT("A1:A"&COUNTUNIQUE(A$1:A)+1))}; 2; 0))=3)*
(COUNTIF(A:A; A1)>1)
green:
=(ARRAYFORMULA(VLOOKUP(A1; {UNIQUE(A$1:A)\
ROW(INDIRECT("A1:A"&COUNTUNIQUE(A$1:A)+1))}; 2; 0))=4)*
(COUNTIF(A:A; A1)>1)
red:
=(ARRAYFORMULA(VLOOKUP(A1; {UNIQUE(A$1:A)\
ROW(INDIRECT("A1:A"&COUNTUNIQUE(A$1:A)+1))}; 2; 0))=5)*
(COUNTIF(A:A; A1)>1)
orange:
=(ARRAYFORMULA(VLOOKUP(A1; {UNIQUE(A$1:A)\
ROW(INDIRECT("A1:A"&COUNTUNIQUE(A$1:A)+1))}; 2; 0))=6)*
(COUNTIF(A:A; A1)>1)
Here is a script that works:
function colorDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lr = ss.getLastRow();
var column = 1
ss.getRange(2, column, lr).setBackground(null);
var color = ["#EA9999","#F9CB9C","#FFE599","#B6D7A8","#A2C4C9","#9FC5E8","#B4A7D6","#D5A6BD","#CCCCCC","#B45F06","#666666","#FF0000","#FF9900","#FFFF00","#00FF00","#00FFFF"];
var c = 0;
var checkcolor = false;
for (var i = 2; i < lr+1;i++){
if (checkcolor == true) {
c++;
checkcolor = false;
}
var a = ss.getRange(i, column).getValue();
if (a == "") {continue;}
var cellcolor = ss.getRange(i, column).getBackground();
if (cellcolor != "#ffffff") {continue;}
for (var j = i+1;j< lr+1;j++){
var b = ss.getRange(j, column).getValue();
if (a != b) {continue;}
var cellcolor = ss.getRange(j, column).getBackground();
if (cellcolor != "#ffffff") {continue;}
ss.getRange(i, column).setBackground(color[c]);
ss.getRange(j, column).setBackground(color[c]);
checkcolor = true;
}
}
}
function colorDuplicates2() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lr = getLastRowSpecial(ss.getRange("A:A").getValues());
var lc = ss.getLastColumn();
ss.getRange(2, 1, lr).setBackground(null);
ss.insertColumnAfter(lc);
ss.getRange(1, lc+1).setFormula("=FILTER(UNIQUE(A2:A);UNIQUE(A2:A)<>\"\")"); // Change the ";" for a "," if you are in the us
var numValues = getLastRowSpecial(ss.getRange(1, lc+1, lr).getValues());
var values = new Array(36);
for (var a = 1;a<numValues+1;a++){
values[a] = String(ss.getRange(a, lc+1).getValue());
}
var color = ["#980000", "#ff0000", "#ff9900", "#ffff00", "#00ff00", "#00ffff", "#4a86e8", "#0000ff", "#9900ff", "#ff00ff", "#e6b8af", "#f4cccc", "#fce5cd", "#ea9999", "#f9cb9c", "#ffe599", "#b6d7a8", "#a2c4c9", "#a4c2f4", "#9fc5e8", "#b4a7d6", "#d5a6bd", "#cc4125", "#e06666", "#f6b26b", "#ffd966", "#93c47d", "#76a5af", "#6d9eeb", "#6fa8dc", "#8e7cc3", "#c27ba0", "#a61c00", "#cc0000", "#e69138", "#f1c232", "#6aa84f", "#45818e", "#3c78d8", "#3d85c6", "#674ea7", "#a64d79", "#85200c", "#990000"];
for (var i = 2;i<lr+1;i++){
switch (String(ss.getRange(i, 1).getValue())) {
case values[1]:
ss.getRange(i, 1).setBackground(color[1]);
break;
case values[2]:
ss.getRange(i, 1).setBackground(color[2]);
break;
case values[3]:
ss.getRange(i, 1).setBackground(color[3]);
break;
case values[4]:
ss.getRange(i, 1).setBackground(color[4]);
break;
case values[5]:
ss.getRange(i, 1).setBackground(color[5]);
break;
case values[6]:
ss.getRange(i, 1).setBackground(color[6]);
break;
case values[7]:
ss.getRange(i, 1).setBackground(color[7]);
break;
case values[8]:
ss.getRange(i, 1).setBackground(color[8]);
break;
case values[9]:
ss.getRange(i, 1).setBackground(color[9]);
break;
case values[10]:
ss.getRange(i, 1).setBackground(color[10]);
break;
case values[11]:
ss.getRange(i, 1).setBackground(color[11]);
break;
case values[12]:
ss.getRange(i, 1).setBackground(color[12]);
break;
case values[13]:
ss.getRange(i, 1).setBackground(color[13]);
break;
case values[14]:
ss.getRange(i, 1).setBackground(color[14]);
break;
case values[15]:
ss.getRange(i, 1).setBackground(color[15]);
break;
case values[16]:
ss.getRange(i, 1).setBackground(color[16]);
break;
case values[17]:
ss.getRange(i, 1).setBackground(color[17]);
break;
case values[18]:
ss.getRange(i, 1).setBackground(color[18]);
break;
case values[19]:
ss.getRange(i, 1).setBackground(color[19]);
break;
case values[20]:
ss.getRange(i, 1).setBackground(color[20]);
break;
case values[21]:
ss.getRange(i, 1).setBackground(color[21]);
break;
case values[22]:
ss.getRange(i, 1).setBackground(color[22]);
break;
case values[23]:
ss.getRange(i, 1).setBackground(color[23]);
break;
case values[24]:
ss.getRange(i, 1).setBackground(color[24]);
break;
case values[25]:
ss.getRange(i, 1).setBackground(color[25]);
break;
case values[26]:
ss.getRange(i, 1).setBackground(color[26]);
break;
case values[27]:
ss.getRange(i, 1).setBackground(color[27]);
break;
case values[28]:
ss.getRange(i, 1).setBackground(color[28]);
break;
case values[29]:
ss.getRange(i, 1).setBackground(color[29]);
break;
case values[30]:
ss.getRange(i, 1).setBackground(color[30]);
break;
case values[31]:
ss.getRange(i, 1).setBackground(color[31]);
break;
case values[32]:
ss.getRange(i, 1).setBackground(color[32]);
break;
case values[33]:
ss.getRange(i, 1).setBackground(color[33]);
break;
case values[34]:
ss.getRange(i, 1).setBackground(color[34]);
break;
case values[35]:
ss.getRange(i, 1).setBackground(color[35]);
break;
case values[36]:
ss.getRange(i, 1).setBackground(color[36]);
break;
}
}
ss.deleteColumn(lc+1);
}
function getLastRowSpecial(range){
var rowNum = 0;
var blank = false;
for(var row = 0; row < range.length; row++){
if(range[row][0] === "" && !blank){
rowNum = row;
blank = true;
}else if(range[row][0] !== ""){
blank = false;
};
};
return rowNum;
}
It has only 16 colors, and it will leave blank the duplicates that finds after that, but you can add as many more colors as you need.
It works for finding duplicates in column A, but you can also change that if you need it to be another column.
Try it and feel free to ask me anything. Good luck!
*I edited my previous answer beacuse it didnt take in to account values that appeard more than twice.
*I edited again to avoid empty cells.
You can use a script function to take advantage of a hashing algorithm like md5 and obtain an (almost) unique byte string for any cell value.
Selecting the first 3 bytes and adding a leading "#", you can convert such a hash into a hex color.
After that, looping over the range values to determine the color codes and setting the background is all that is required.
/**
* Return an md5 hash as hex string.
* #example md5("lorem ipsum") => "80a751fde577028640c419000e33eba6"
*/
function md5(txt) {
return Utilities.computeDigest(
Utilities.DigestAlgorithm.MD5,
txt
).map(
c => (255 & c).toString(16).padStart(2, '0')
).join(
""
);
}
/**
* Return a hex color from any string.
* #example txt2color("lorem ipsum") => "#80a751"
*/
function txt2color(txt) {
return "#" + md5(txt).slice(0, 6);
}
/**
* Set the background color of the cells in the active
* range so that the cells with same value get the
* same background color
*/
function highlight_same_values() {
const range = SpreadsheetApp.getActiveRange();
const bgcolors = range.getValues().map(
row => row.map(
cell => cell ? txt2color(cell) : null
)
);
range.setBackgrounds(bgcolors);
}
How can you convert a DXGI_FORMAT to a bpp (bit per pixel) value without enumeration? (see formats)
e.g.:
DXGI_FORMAT_R10G10B10A2_UNORM -> 32
DXGI_FORMAT_B5G5R5A1_UNORM -> 16
DirectXTex implements this utility function, and I have it internally in DirectX Tool Kit for DDSTextureLoader.
//-------------------------------------------------------------------------------------
// Returns bits-per-pixel for a given DXGI format, or 0 on failure
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
size_t DirectX::BitsPerPixel(DXGI_FORMAT fmt)
{
switch (static_cast<int>(fmt))
{
case DXGI_FORMAT_R32G32B32A32_TYPELESS:
case DXGI_FORMAT_R32G32B32A32_FLOAT:
case DXGI_FORMAT_R32G32B32A32_UINT:
case DXGI_FORMAT_R32G32B32A32_SINT:
return 128;
case DXGI_FORMAT_R32G32B32_TYPELESS:
case DXGI_FORMAT_R32G32B32_FLOAT:
case DXGI_FORMAT_R32G32B32_UINT:
case DXGI_FORMAT_R32G32B32_SINT:
return 96;
case DXGI_FORMAT_R16G16B16A16_TYPELESS:
case DXGI_FORMAT_R16G16B16A16_FLOAT:
case DXGI_FORMAT_R16G16B16A16_UNORM:
case DXGI_FORMAT_R16G16B16A16_UINT:
case DXGI_FORMAT_R16G16B16A16_SNORM:
case DXGI_FORMAT_R16G16B16A16_SINT:
case DXGI_FORMAT_R32G32_TYPELESS:
case DXGI_FORMAT_R32G32_FLOAT:
case DXGI_FORMAT_R32G32_UINT:
case DXGI_FORMAT_R32G32_SINT:
case DXGI_FORMAT_R32G8X24_TYPELESS:
case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
case DXGI_FORMAT_Y416:
case DXGI_FORMAT_Y210:
case DXGI_FORMAT_Y216:
return 64;
case DXGI_FORMAT_R10G10B10A2_TYPELESS:
case DXGI_FORMAT_R10G10B10A2_UNORM:
case DXGI_FORMAT_R10G10B10A2_UINT:
case DXGI_FORMAT_R11G11B10_FLOAT:
case DXGI_FORMAT_R8G8B8A8_TYPELESS:
case DXGI_FORMAT_R8G8B8A8_UNORM:
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
case DXGI_FORMAT_R8G8B8A8_UINT:
case DXGI_FORMAT_R8G8B8A8_SNORM:
case DXGI_FORMAT_R8G8B8A8_SINT:
case DXGI_FORMAT_R16G16_TYPELESS:
case DXGI_FORMAT_R16G16_FLOAT:
case DXGI_FORMAT_R16G16_UNORM:
case DXGI_FORMAT_R16G16_UINT:
case DXGI_FORMAT_R16G16_SNORM:
case DXGI_FORMAT_R16G16_SINT:
case DXGI_FORMAT_R32_TYPELESS:
case DXGI_FORMAT_D32_FLOAT:
case DXGI_FORMAT_R32_FLOAT:
case DXGI_FORMAT_R32_UINT:
case DXGI_FORMAT_R32_SINT:
case DXGI_FORMAT_R24G8_TYPELESS:
case DXGI_FORMAT_D24_UNORM_S8_UINT:
case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
case DXGI_FORMAT_R9G9B9E5_SHAREDEXP:
case DXGI_FORMAT_R8G8_B8G8_UNORM:
case DXGI_FORMAT_G8R8_G8B8_UNORM:
case DXGI_FORMAT_B8G8R8A8_UNORM:
case DXGI_FORMAT_B8G8R8X8_UNORM:
case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
case DXGI_FORMAT_B8G8R8A8_TYPELESS:
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
case DXGI_FORMAT_B8G8R8X8_TYPELESS:
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
case DXGI_FORMAT_AYUV:
case DXGI_FORMAT_Y410:
case DXGI_FORMAT_YUY2:
case XBOX_DXGI_FORMAT_R10G10B10_7E3_A2_FLOAT:
case XBOX_DXGI_FORMAT_R10G10B10_6E4_A2_FLOAT:
case XBOX_DXGI_FORMAT_R10G10B10_SNORM_A2_UNORM:
return 32;
case DXGI_FORMAT_P010:
case DXGI_FORMAT_P016:
case XBOX_DXGI_FORMAT_D16_UNORM_S8_UINT:
case XBOX_DXGI_FORMAT_R16_UNORM_X8_TYPELESS:
case XBOX_DXGI_FORMAT_X16_TYPELESS_G8_UINT:
case WIN10_DXGI_FORMAT_V408:
return 24;
case DXGI_FORMAT_R8G8_TYPELESS:
case DXGI_FORMAT_R8G8_UNORM:
case DXGI_FORMAT_R8G8_UINT:
case DXGI_FORMAT_R8G8_SNORM:
case DXGI_FORMAT_R8G8_SINT:
case DXGI_FORMAT_R16_TYPELESS:
case DXGI_FORMAT_R16_FLOAT:
case DXGI_FORMAT_D16_UNORM:
case DXGI_FORMAT_R16_UNORM:
case DXGI_FORMAT_R16_UINT:
case DXGI_FORMAT_R16_SNORM:
case DXGI_FORMAT_R16_SINT:
case DXGI_FORMAT_B5G6R5_UNORM:
case DXGI_FORMAT_B5G5R5A1_UNORM:
case DXGI_FORMAT_A8P8:
case DXGI_FORMAT_B4G4R4A4_UNORM:
case WIN10_DXGI_FORMAT_P208:
case WIN10_DXGI_FORMAT_V208:
return 16;
case DXGI_FORMAT_NV12:
case DXGI_FORMAT_420_OPAQUE:
case DXGI_FORMAT_NV11:
return 12;
case DXGI_FORMAT_R8_TYPELESS:
case DXGI_FORMAT_R8_UNORM:
case DXGI_FORMAT_R8_UINT:
case DXGI_FORMAT_R8_SNORM:
case DXGI_FORMAT_R8_SINT:
case DXGI_FORMAT_A8_UNORM:
case DXGI_FORMAT_AI44:
case DXGI_FORMAT_IA44:
case DXGI_FORMAT_P8:
case XBOX_DXGI_FORMAT_R4G4_UNORM:
return 8;
case DXGI_FORMAT_R1_UNORM:
return 1;
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
return 4;
case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT_BC6H_TYPELESS:
case DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT_BC6H_SF16:
case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
return 8;
default:
return 0;
}
}
Of course, the real question is why do you need it?
You can simply create a function with switch statement:
int getBPP(DXGI_FORMAT format)
{
switch(format)
{
case DXGI_FORMAT_R16G16B16A16_UNORM: return 16*4;
case DXGI_FORMAT_R32G32B32A32_FLOAT: return 32*4;
//...
}
}
Alternatively, you could rely on the fact that the formats generally are grouped by their BPP. This is not strictly true - for example, there are multiple groups of 32bpp formats, but this could be handled with additional conditionals. For example:
int getBPP(DXGI_FORMAT format)
{
//...
else if (format >= DXGI_FORMAT_R32_TYPELESS &&
format <= DXGI_FORMAT_X24_TYPELESS_G8_UINT)
{
return 32;
}
else if (format >= DXGI_FORMAT_R9G9B9E5_SHAREDEXP &&
format <= DXGI_FORMAT_G8R8_G8B8_UNORM)
{
return 32;
}
//...
}
This of course relies on the assumption that the DXGI_FORMAT enum won't change and break this convention (although, that's pretty unlikely).
There is no utilities to perform the conversion for you, most 3d engine just implement their own with a basic large switch case. Also, the value you list are wrong, unless you consider the per component bits and not the whole pixel.
int DxgiFormat2Bpp( DXGI_FORMAT fmt ) {
switch(fmt) {
// many cases
case DXGI_FORMAT_R16G16B16A16_UNORM : return 64;
case DXGI_FORMAT_R32G32B32A32_FLOAT : return 128;
default: return -1;
}
}
I have the task of re-designing a system to print shipping labels, using a networked Zebra GK420T. I have been able to send ZPL print jobs to it perfectly fine, but I cannot seem to get it to print unicode characters, such as cyrillic letters. I have downloaded the lucida sans unicode font to the printer using the Seagull Scientific drivers and I am using the following ZPL code to test:
^XA
^LH100,150
^CI28
^FT0,0^A#N,50,50,R:LUCIDASR.FNT^CI28^FDTesting 1 2 3^FS
^FT0,50^A#N,50,50,R:LUCIDASR.FNT^CI28^FDДо свидания^FS
^FT0,100^B3^FDAAA001^FS
^XZ
It will print the 'Testing 1 2 3' and the barcode, but it leaves a blank space instead of the cyrillic characters.
I also tried using the Zebra swiss unicode font and now it prints the russian characters as question marks:
^XA
^LH100,150
^CWT,E:TT0003M_.FNT
^CFT,30,30
^CI28
^FT0,0^FDTesting 1 2 3^FS
^FT0,50^FDДо свидания^FS
^FT0,100^B3^FDAAA001^FS
^XZ
Am I doing something wrong like not escaping characters or something or is it a problem with the printer?
I just discovered that you need to escape characters above ASCII by first putting ^FH before any ^FD fields that could contain a utf character and you need to prefix the utf-8 hex code with an underscore
_D0_94 will print as Д. My final ZPL code is as follows:
^XA
^LH100,150
^CWT,E:TT0003M_.FNT
^CFT,30,30
^CI28
^FT0,0^FH^FDTesting 1 2 3^FS
^FT0,50^FH^FD_D0_94_D0_BE _D1_81_D0_B2_D0_B8_D0_B4_D0_B0_D0_BD_D0_B8_D1_8F^FS
^FT0,100^B3^FDAAA001^FS
^XZ
I'm just going to have to make a way to generate the escape sequences, which should be much easier!
I had the same problem, you should add an ^FH(Field Hexadecimal Indicator) before any ^FD(Field Data) command that contains special characters, in my case I need spanish chars so I had to use ^CI28(Change International Font/Encoding)
UTF 8 HEX codes list
sample: to print Alvaro Jesús Pérez Peñaranda
we need to convert those special characters to UTF 8 Hex code and add an _ before each code, this is the result: Alvaro Jes_c3_bas P_c3_a9rez Pe_c3_b1aranda
^XA
^CI28
^FO60,75
^ASN,36,20^FH^FDAlvaro Jes_c3_bas P_c3_a9rez Pe_c3_b1aranda^FS
^XZ
I'm using Zebra ZM400 printer and use TT0003M_ font.
this font does not print kazakh cyrillic.
if you want to print cryillic + kazakh cyrillic + latin alphabet, use ARI000.FNT (arial font)
I'm using the following method convert char to hex code
I hope this helps
stringConverTextToHex(stringtext)
{
stringnewText="";
char[]charArray=text.ToCharArray();
foreach(charcincharArray)
{
switch(c)
{
case'й':
newText+="_D0_B9";
break;
case'Й':
newText+="_D0_99";
break;
case'ц':
newText+="_D1_86";
break;
case'Ц':
newText+="_D0_A6";
break;
case'у':
newText+="_D1_83";
break;
case'У':
newText+="_D0_A3";
break;
case'к':
newText+="_D0_BA";
break;
case'К':
newText+="_D0_9A";
break;
case'е':
newText+="_D0_B5";
break;
case'Е':
newText+="_D0_95";
break;
case'н':
newText+="_D0_BD";
break;
case'Н':
newText+="_D0_9D";
break;
case'г':
newText+="_D0_B3";
break;
case'Г':
newText+="_D0_93";
break;
case'ш':
newText+="_D1_88";
break;
case'Ш':
newText+="_D0_A8";
break;
case'щ':
newText+="_D1_89";
break;
case'Щ':
newText+="_D0_A9";
break;
case'з':
newText+="_D0_B7";
break;
case'З':
newText+="_D0_97";
break;
case'х':
newText+="_D1_85";
break;
case'Х':
newText+="_D0_A5";
break;
case'ъ':
newText+="_D1_8A";
break;
case'Ъ':
newText+="_D0_AA";
break;
case'ф':
newText+="_D1_84";
break;
case'Ф':
newText+="_D0_A4";
break;
case'ы':
newText+="_D1_8B";
break;
case'Ы':
newText+="_D0_AB";
break;
case'в':
newText+="_D0_B2";
break;
case'В':
newText+="_D0_92";
break;
case'а':
newText+="_D0_B0";
break;
case'А':
newText+="_D0_90";
break;
case'п':
newText+="_D0_BF";
break;
case'П':
newText+="_D0_9F";
break;
case'р':
newText+="_D1_80";
break;
case'Р':
newText+="_D0_A0";
break;
case'о':
newText+="_D0_BE";
break;
case'О':
newText+="_D0_9E";
break;
case'л':
newText+="_D0_BB";
break;
case'Л':
newText+="_D0_9B";
break;
case'д':
newText+="_D0_B4";
break;
case'Д':
newText+="_D0_94";
break;
case'ж':
newText+="_D0_B6";
break;
case'Ж':
newText+="_D0_96";
break;
case'э':
newText+="_D1_8D";
break;
case'Э':
newText+="_D0_AD";
break;
case'я':
newText+="_D1_8F";
break;
case'Я':
newText+="_D0_AF";
break;
case'ч':
newText+="_D1_87";
break;
case'Ч':
newText+="_D0_A7";
break;
case'с':
newText+="_D1_81";
break;
case'С':
newText+="_D0_A1";
break;
case'м':
newText+="_D0_BC";
break;
case'М':
newText+="_D0_9C";
break;
case'и':
newText+="_D0_B8";
break;
case'И':
newText+="_D0_98";
break;
case'т':
newText+="_D1_82";
break;
case'Т':
newText+="_D0_A2";
break;
case'ь':
newText+="_D1_8C";
break;
case'Ь':
newText+="_D0_AC";
break;
case'б':
newText+="_D0_B1";
break;
case'Б':
newText+="_D0_91";
break;
case'ю':
newText+="_D1_8E";
break;
case'Ю':
newText+="_D0_AE";
break;
case'ӑ':
newText+="_D3_91";
break;
case'Ӑ':
newText+="_D3_90";
break;
case'ӓ':
newText+="_D3_93";
break;
case'Ӓ':
newText+="_D3_92";
break;
case'ә':
newText+="_D3_99";
break;
case'Ә':
newText+="_D3_98";
break;
case'ӛ':
newText+="_D3_9B";
break;
case'Ӛ':
newText+="_D3_9A";
break;
case'ӕ':
newText+="_D3_95";
break;
case'Ӕ':
newText+="_D3_94";
break;
case'ґ':
newText+="_D2_91";
break;
case'Ґ':
newText+="_D2_90";
break;
case'ѓ':
newText+="_D1_93";
break;
case'Ѓ':
newText+="_D0_83";
break;
case'ғ':
newText+="_D2_93";
break;
case'Ғ':
newText+="_D2_92";
break;
case'ӷ':
newText+="_D3_B7";
break;
case'Ӷ':
newText+="_D3_B6";
break;
case'ҕ':
newText+="_D2_95";
break;
case'Ҕ':
newText+="_D2_94";
break;
case'ђ':
newText+="_D1_92";
break;
case'Ђ':
newText+="_D0_82";
break;
case'ѐ':
newText+="_D1_90";
break;
case'Ѐ':
newText+="_D0_80";
break;
case'ӗ':
newText+="_D3_97";
break;
case'Ӗ':
newText+="_D3_96";
break;
case'ҽ':
newText+="_D2_BD";
break;
case'Ҽ':
newText+="_D2_BC";
break;
case'ҿ':
newText+="_D2_BF";
break;
case'Ҿ':
newText+="_D2_BE";
break;
case'є':
newText+="_D1_94";
break;
case'Є':
newText+="_D0_84";
break;
case'ӂ':
newText+="_D3_82";
break;
case'Ӂ':
newText+="_D3_81";
break;
case'җ':
newText+="_D2_97";
break;
case'Җ':
newText+="_D2_96";
break;
case'ӝ':
newText+="_D3_9D";
break;
case'Ӝ':
newText+="_D3_9C";
break;
case'ҙ':
newText+="_D2_99";
break;
case'Ҙ':
newText+="_D2_98";
break;
case'ӟ':
newText+="_D3_9F";
break;
case'Ӟ':
newText+="_D3_9E";
break;
case'ӡ':
newText+="_D3_A1";
break;
case'Ӡ':
newText+="_D3_A0";
break;
case'ѕ':
newText+="_D1_95";
break;
case'Ѕ':
newText+="_D0_85";
break;
case'ѝ':
newText+="_D1_9D";
break;
case'Ѝ':
newText+="_D0_8D";
break;
case'ӥ':
newText+="_D3_A5";
break;
case'Ӥ':
newText+="_D3_A4";
break;
case'ӣ':
newText+="_D3_A3";
break;
case'Ӣ':
newText+="_D3_A2";
break;
case'і':
newText+="_D1_96";
break;
case'І':
newText+="_D0_86";
break;
case'ї':
newText+="_D1_97";
break;
case'Ї':
newText+="_D0_87";
break;
case'Ӏ':
newText+="_D3_80";
break;
case'ҋ':
newText+="_D2_8B";
break;
case'Ҋ':
newText+="_D2_8A";
break;
case'ј':
newText+="_D1_98";
break;
case'Ј':
newText+="_D0_88";
break;
case'қ':
newText+="_D2_9B";
break;
case'Қ':
newText+="_D2_9A";
break;
case'ҟ':
newText+="_D2_9F";
break;
case'Ҟ':
newText+="_D2_9E";
break;
case'ҡ':
newText+="_D2_A1";
break;
case'Ҡ':
newText+="_D2_A0";
break;
case'ӄ':
newText+="_D3_84";
break;
case'Ӄ':
newText+="_D3_83";
break;
case'ҝ':
newText+="_D2_9D";
break;
case'Ҝ':
newText+="_D2_9C";
break;
case'ӆ':
newText+="_D3_86";
break;
case'Ӆ':
newText+="_D3_85";
break;
case'љ':
newText+="_D1_99";
break;
case'Љ':
newText+="_D0_89";
break;
case'ӎ':
newText+="_D3_8E";
break;
case'Ӎ':
newText+="_D3_8D";
break;
case'ӊ':
newText+="_D3_8A";
break;
case'Ӊ':
newText+="_D3_89";
break;
case'ң':
newText+="_D2_A3";
break;
case'Ң':
newText+="_D2_A2";
break;
case'ӈ':
newText+="_D3_88";
break;
case'Ӈ':
newText+="_D3_87";
break;
case'ҥ':
newText+="_D2_A5";
break;
case'Ҥ':
newText+="_D2_A4";
break;
case'њ':
newText+="_D1_9A";
break;
case'Њ':
newText+="_D0_8A";
break;
case'ӧ':
newText+="_D3_A7";
break;
case'Ӧ':
newText+="_D3_A6";
break;
case'ө':
newText+="_D3_A9";
break;
case'Ө':
newText+="_D3_A8";
break;
case'ӫ':
newText+="_D3_AB";
break;
case'Ӫ':
newText+="_D3_AA";
break;
case'ҩ':
newText+="_D2_A9";
break;
case'Ҩ':
newText+="_D2_A8";
break;
case'ҧ':
newText+="_D2_A7";
break;
case'Ҧ':
newText+="_D2_A6";
break;
case'ҏ':
newText+="_D2_8F";
break;
case'Ҏ':
newText+="_D2_8E";
break;
case'ҫ':
newText+="_D2_AB";
break;
case'Ҫ':
newText+="_D2_AA";
break;
case'ҭ':
newText+="_D2_AD";
break;
case'Ҭ':
newText+="_D2_AC";
break;
case'ћ':
newText+="_D1_9B";
break;
case'Ћ':
newText+="_D0_8B";
break;
case'ќ':
newText+="_D1_9C";
break;
case'Ќ':
newText+="_D0_8C";
break;
case'ў':
newText+="_D1_9E";
break;
case'Ў':
newText+="_D0_8E";
break;
case'ӳ':
newText+="_D3_B3";
break;
case'Ӳ':
newText+="_D3_B2";
break;
case'ӱ':
newText+="_D3_B1";
break;
case'Ӱ':
newText+="_D3_B0";
break;
case'ӯ':
newText+="_D3_AF";
break;
case'Ӯ':
newText+="_D3_AE";
break;
case'ү':
newText+="_D2_AF";
break;
case'Ү':
newText+="_D2_AE";
break;
case'ұ':
newText+="_D2_B1";
break;
case'Ұ':
newText+="_D2_B0";
break;
case'ҳ':
newText+="_D2_B3";
break;
case'Ҳ':
newText+="_D2_B2";
break;
case'һ':
newText+="_D2_BB";
break;
case'Һ':
newText+="_D2_BA";
break;
case'ҵ':
newText+="_D2_B5";
break;
case'Ҵ':
newText+="_D2_B4";
break;
case'ӵ':
newText+="_D3_B5";
break;
case'Ӵ':
newText+="_D3_B4";
break;
case'ҷ':
newText+="_D2_B7";
break;
case'Ҷ':
newText+="_D2_B6";
break;
case'ӌ':
newText+="_D3_8C";
break;
case'Ӌ':
newText+="_D3_8B";
break;
case'ҹ':
newText+="_D2_B9";
break;
case'Ҹ':
newText+="_D2_B8";
break;
case'џ':
newText+="_D1_9F";
break;
case'Џ':
newText+="_D0_8F";
break;
case'ӹ':
newText+="_D3_B9";
break;
case'Ӹ':
newText+="_D3_B8";
break;
case'ҍ':
newText+="_D2_8D";
break;
case'Ҍ':
newText+="_D2_8C";
break;
case'ӭ':
newText+="_D3_AD";
break;
case'Ӭ':
newText+="_D3_AC";
break;
case'A':
newText+="_41";
break;
case'a':
newText+="_61";
break;
case'B':
newText+="_42";
break;
case'b':
newText+="_62";
break;
case'C':
newText+="_43";
break;
case'c':
newText+="_63";
break;
case'D':
newText+="_44";
break;
case'd':
newText+="_64";
break;
case'E':
newText+="_45";
break;
case'e':
newText+="_65";
break;
case'F':
newText+="_46";
break;
case'f':
newText+="_66";
break;
case'G':
newText+="_47";
break;
case'g':
newText+="_67";
break;
case'H':
newText+="_48";
break;
case'h':
newText+="_68";
break;
case'I':
newText+="_49";
break;
case'i':
newText+="_69";
break;
case'J':
newText+="_4A";
break;
case'j':
newText+="_6A";
break;
case'K':
newText+="_4B";
break;
case'k':
newText+="_6B";
break;
case'L':
newText+="_4C";
break;
case'l':
newText+="_6C";
break;
case'M':
newText+="_4D";
break;
case'm':
newText+="_6D";
break;
case'N':
newText+="_4E";
break;
case'n':
newText+="_6E";
break;
case'O':
newText+="_4F";
break;
case'o':
newText+="_6F";
break;
case'P':
newText+="_50";
break;
case'p':
newText+="_70";
break;
case'R':
newText+="_52";
break;
case'r':
newText+="_72";
break;
case'S':
newText+="_53";
break;
case's':
newText+="_73";
break;
case'T':
newText+="_54";
break;
case't':
newText+="_74";
break;
case'U':
newText+="_55";
break;
case'u':
newText+="_75";
break;
case'V':
newText+="_56";
break;
case'v':
newText+="_76";
break;
case'Y':
newText+="_59";
break;
case'y':
newText+="_79";
break;
case'Z':
newText+="_5A";
break;
case'z':
newText+="_7A";
break;
case'':
newText+="";
break;
default:
newText+=c;
break;
}
}
returnnewText;
}
this is the sample code
^SP
^XA
^PON^FS
^FPH^FO102,63,0
^A#N,60,60,E:ARIOOO_.FNT
^FH^FD_42_75_72_61_6B _D0_A8_D3_99
^FS
^XZ
Russian and many other characters can be printed using the free Zebra swiss unicode font. It is already included in most printers as TT0003M_ and supports Roman, Cyrillic, Eastern European, Turkish, Arabic, Hebrew.
For printing languages like Japanese or Chinese, which have thousands of characters, you need a printer with at least 23 MB of free memory and a TrueType font file you can upload (they call it download).
This file can be bought from Zebra (and they say you need 64 MB), but I was also very successful with a very old TTF file found on my Windows 7 system in the Fonts folder: ARIALUNI.TTF 1.01 (23.275.812 Bytes), Arial Unicode MS. It was installed by a MS Office installation and is maybe not licensed for this use.
Most likely you can also use other TTF files, but I tried only this one.
While ZPL-printing on this Zebra printer worked without any original driver (just generic text only), for the font installation the driver was needed. If anyone knows how to send the TTF file to the printer without driver, please comment.
I installed the Zebra Setup Utilities, which include a Fonts Downloader. Click new, then add the font (must be installed in the system), and ignore the message that 226 characters are included. Also ignore that if you configure a test string with Unicode characters, it will not display correctly. You are beeing asked if you want to download now and it takes a long time.
You can check the installation by listing the directory contents (Administration web page or printout). There the font appears as ARI000.TTF in my case.
To print, you need to send the ZPL text as UTF-8. You can copy this example to notepad and select UTF-8 in the save dialog:
^XA
^LH100,150
^CWT,E:ARI000.FNT
^CFT,30,30
^CI28
^FT0,0^FH^FDyour unicode characters here^FS
^XZ
Then, for testing, you can use a simple copy command to send it to the printer:
In case of USB you need to share this printer in the network first.
Then net use lpt1: \\localhost\sharename
and copy file.txt lpt1
We tested with many common Japanese and Chinese symbols and it works very well with high quality, on a ZT230 printer with 32 MB flash.
Your "До свидания" were probably in cp1251. Encode it in actual UTF-8 and try again. Blank spaces are a good indicator that you have an encoding problem.
Verified with v56.17.112 firmware and ^A#N,,,E:TT0003M_.FNT
if you want print Russian Cyrillic letters using :TT0003M_.FNT, you should save commands to file with UTF-8 encoding!
^XA
^LH100,150
^CWT,E:TT0003M_.FNT
^CFT,30,30
^CI28
^FT0,0^FH^FDTesting 1 2 3^FS
^FT0,30^FH^FDДо свидания^FS
^FT0,100^B3^FDAAA001^FS
^XZ
Then, using command line you can send it to printer port.
An example: copy C:\Users\xxx\Desktop\test_ru.txt com1
I hope that will help...
You can replace character that greater then one byte to UTF-8 hex string with underscore like "ћ => _D1_9B". Sample code below;
var zpl_code = "^XA" +
"^LH100,150" +
"^CWT,E:TT0003M_.FNT" +
"^CFT,30,30" +
"^CI28" +
"^FT0,0^FDTesting 1 2 3^FS" +
"^FT0,50^FDДо свидания^FS" +
"^FT0,100^B3^FDAAA001^FS" +
"^XZ";
var unicodeCharacterList = zpl_code.Distinct()
.Select(c => c.ToString())
.Select(c => new { key = c, UTF8Bytes = Encoding.UTF8.GetBytes(c) })
.Where(c => c.UTF8Bytes.Length > 1);
foreach (var character in unicodeCharacterList)
{
var characterHexCode = string.Join("", character.UTF8Bytes.Select(c => "_" + BitConverter.ToString(new byte[] { c }).ToLower()).ToArray());
zpl_code = zpl_code.Replace(character.key, characterHexCode);
}
This code set zpl_code variable to below output
^XA
^LH100,150
^CWT,E:TT0003M_.FNT
^CFT,30,30
^CI28
^FT0,0^FDTesting 1 2 3^FS
^FT0,50^FD_d0_94_d0_be _d1_81_d0_b2_d0_b8_d0_b4_d0_b0_d0_bd_d0_b8_d1_8f^FS
^FT0,100^B3^FDAAA001^FS
^XZ
In latest firmware versions (since v x.16.x) you can use ^CI33 for codepage Windows-1251 encoded text (and other codepages) without ^FH. See manual
As others have noted, make sure to use ^CI28 (Change International Font/Encoding) and ^FH (Field Hexadecimal Indicator) and escape any non-ascii utf8 characters with an underscore and their hex value.
However another answer included code to format the utf8 string using a gigantic switch-case block. Here is the method I use for encoding to utf8, it should be able to format any valid utf8 byte array.
To get the byte array from a string use Encoding.UTF8.GetBytes(content).
// From the wikipedia page on utf8 encoding - https://en.wikipedia.org/wiki/UTF-8
private const int _Last1ByteCodePointByte1 = 0x7F;
private const int _First2ByteCodePointByte1 = 0xC0;
private const int _Last2ByteCodePointByte1 = 0xDF;
private const int _Last3ByteCodePointByte1 = 0xEF;
private const int _Last4ByteCodePointByte1 = 0xF7;
private const int _FirstMultiByteCodePointByte2 = 0x80;
private const int _LastMultiByteCodePointByte2 = 0xBF;
private const char _ZplMultiByteEscapeCharacter = '_';
/// <summary>
/// Encodes a sequence of utf8 bytes for printing with the ZPL language, this means escaping multi-byte characters with an underscore ('_') followed by the hex code
/// for each byte in the multi-byte characters.
/// </summary>
/// <param name="utf8Bytes">The bytes that make up the entire string, including bytes that need to be encoded and bytes that can be printed as-is.</param>
/// <returns>A string for printing with the ZPL language. Ie all multi-byte characters escaped with an underscore ('_') followed by the hex code for each byte.</returns>
/// <throws><see cref="ArgumentException"/> when <paramref name="utf8Bytes"/> isn't a valid utf8 encoding of a string.</throws>
/// <remarks>
/// Plan is to figure out how many bytes this character (code point) takes up, and if it's a 1 byte character, just use the character, but otherwise since it's a multi-byte
/// character then use an underscore ('_') followed by the hex encoded byte and each other byte in this code point will also be encoded. If we start the loop but have bytes
/// remaining in the current code point we know to hex encode this byte and continue.
/// </remarks>
private static string EncodeUtf8BytesForZPLIIPrinting(byte[] utf8Bytes)
{
var contentWithMultiByteCharsEscaped = new List<char>();
var multiByteCodePoint = new List<char>();
var remainingBytesInCurrentCodePoint = 0;
string errorMessage = null;
foreach (byte utf8Byte in utf8Bytes)
{
if (remainingBytesInCurrentCodePoint > 0)
{
if (utf8Byte < _FirstMultiByteCodePointByte2 || utf8Byte > _LastMultiByteCodePointByte2)
{
errorMessage = $"The byte {utf8Byte.ToString("X2")} is not a valid as the second or later byte of a multi-byte utf8 character (codepoint).";
break;
}
multiByteCodePoint.Add(_ZplMultiByteEscapeCharacter);
AddHexValuesToListFromByte(multiByteCodePoint, utf8Byte);
remainingBytesInCurrentCodePoint--;
continue; // continue since we've dealt with this byte and don't want to flow on.
}
if (multiByteCodePoint.Any())
{
foreach (char c in multiByteCodePoint) contentWithMultiByteCharsEscaped.Add(c);
multiByteCodePoint.Clear();
// flow on to loop to see what to do with the current byte.
}
if (utf8Byte <= _Last1ByteCodePointByte1)
{
// 1 byte - no escaping
contentWithMultiByteCharsEscaped.Add((char)utf8Byte);
}
else if (utf8Byte >= _First2ByteCodePointByte1 && utf8Byte <= _Last2ByteCodePointByte1)
{
// 2 bytes
multiByteCodePoint.Add(_ZplMultiByteEscapeCharacter);
AddHexValuesToListFromByte(multiByteCodePoint, utf8Byte);
remainingBytesInCurrentCodePoint = 1;
}
else if (utf8Byte <= _Last3ByteCodePointByte1)
{
// 3 bytes
multiByteCodePoint.Add(_ZplMultiByteEscapeCharacter);
AddHexValuesToListFromByte(multiByteCodePoint, utf8Byte);
remainingBytesInCurrentCodePoint = 2;
}
else if (utf8Byte <= _Last4ByteCodePointByte1)
{
// 4 bytes
multiByteCodePoint.Add(_ZplMultiByteEscapeCharacter);
AddHexValuesToListFromByte(multiByteCodePoint, utf8Byte);
remainingBytesInCurrentCodePoint = 3;
}
else
{
errorMessage = $"The byte {utf8Byte.ToString("X2")} is not a valid as the first byte of a utf8 character.";
break;
}
}
// if the last char was multiByte add it now.
if (multiByteCodePoint.Any())
{
foreach (var c in multiByteCodePoint) contentWithMultiByteCharsEscaped.Add(c);
multiByteCodePoint.Clear();
}
if (remainingBytesInCurrentCodePoint != 0 && errorMessage == null)
{
errorMessage = $"The last character didn't have enough bytes to finish the codepoint. It was a multi-byte character that needed {remainingBytesInCurrentCodePoint}" +
$" more byte{(remainingBytesInCurrentCodePoint == 1 ? null : "s")}.";
}
if (errorMessage != null)
{
throw new ArgumentException($"The byte array was not a valid byte array for a utf8 string: {errorMessage}", nameof(utf8Bytes));
}
return new string(contentWithMultiByteCharsEscaped.ToArray());
void AddHexValuesToListFromByte(List<char> list, byte #byte)
{
// A byte is <= 255 so will always fit in a 2-digit hex number, hence the 2 in "X2". The X means hex.
foreach (char c in #byte.ToString("X2"))
{
list.Add(c);
}
}
}
I took Dysnomin's answer and converted something over to Javascript. His answer was in C#
To run it, just take your string object and call variable.zplHexEncode() where the variable is your string. This defaults to the _ for the escape character. You will still have to prefix all you ^FD fields with the ^FH command. Personally, I use something like the doT module to create my ZPL and fill in the fields with values with escaped characters. YMMV.
const _Last1ByteCode = 0x7E;
const _First2ByteCode = 0xA0;
const _Last2ByteCode = 0xBF;
const _Last3ByteCode = 0xFF;
const _3ByteOffset = 0x40;
const _ZplEscapeCharacter = '_';
const _2BytePre = 'c2';
const _3BytePre = 'c3';
String.prototype.zplHexEncode = function(){
var hex, i, escHex;
var result = "";
for (i=0; i< this.length; i++) {
var charCode = this.charCodeAt(i);
if (charCode <= _Last1ByteCode)
result += String.fromCharCode(charCode);
else if (charCode >= _First2ByteCode && charCode <=_Last2ByteCode) {
hex = charCode.toString(16);
escHex = ("0"+hex).slice(-2);
result += _ZplEscapeCharacter+_2BytePre+_ZplEscapeCharacter+escHex;
}
else if (charCode > _Last2ByteCode && charCode <=_Last3ByteCode) {
charCode = charCode - _3ByteOffset;
hex = charCode.toString(16);
escHex = ("0"+hex).slice(-2);
result += _ZplEscapeCharacter+_3BytePre+_ZplEscapeCharacter+escHex;
}
else
result += '';
}
return result
}
var str = "This is a test with a unicode character¿";
console.log(str.zplHexEncode());
When we use CP1251 as system encoding it causes empty symbols in labels, if we write Cyrillic in ZPL code. CP1251 users could first force convert "До свидания" to UTF-8 and get:
До свидания
Replace До свидания in ZPL code with these strange symbols and get "До свидания" on the label. It works with tt0003m_.fnt with ^CI28, but IMHO it is better to use hex-codes instead.
For Cyrillic it is enough to change ^CI28 to ^CI33
^XA
^LH100,150
^CWT,E:TT0003M_.FNT
^CFT,30,30
^CI33
^FT0,0^FDTesting 1 2 3^FS
^FT0,50^FDДо свидания^FS
^FT0,100^B3^FDAAA001^FS
^XZ