public class Globals {
// TODO IMPORTANT CONFIG CHANGES
// ** IMPORTANT CONFIGURATION
private static String serverRealUrl = "http://www.myserver.com/api"; //Real Server configuration
private static String serverLocalUrl = "http://localhost:80/api"; //local configuration
/**
* Substitute you own sender ID here. This is the project number you got
* from the API Console, as described in "Getting Started."
*/
public static String SENDER_ID = "000000";
public static String base64EncodedPublicKey = "xxxxx";
//DEBUG MODO
public static boolean debugModeBoolean = true; //USE in DEBUG MODE
//public static boolean debugModeBoolean = false; //USE in REAL MODE
//PRINT FROM WEB ENABLES
//public static boolean printFronWebEnabled = true; //USE to activate print fron web
public static boolean printFronWebEnabled = false; //USE to deactivate print fron web
//GCM Register
//public static boolean gcmRegisterEnabled = true; //USE to register device in GCM
public static boolean gcmRegisterEnabled = false; // not use gcm services
//IN APP BILLING
//public static boolean inAppBillingModeON = true; //USE in pay app modo on
public static boolean inAppBillingModeON = false; //USE in pay app modo off
private static String internalClassName = "Globals"; // * Tag used on log messages.
public static int deviceType = 0; // * Device type
//0 Pending config (Default as start)
//1 NET Print
//2 RS232 ->Pending
//3 USB
//4 BT
//Google subscriptions
public static boolean mSubscribedToYearlyUsed = false;
// Bluetooth variable Config
public static BluetoothAdapter mBluetoothAdapter;
public static BluetoothSocket mmSocket;
public static BluetoothDevice mmDevice;
public static String logoProcesed = "";
public static OutputStream mmOutputStream;
public static InputStream mmInputStream;
// NET Printer Config
public static String deviceIP = "";
public static int devicePort = 0;
public static int usbDeviceID = 0;
public static int usbVendorID = 0;
public static int usbProductID = 0;
public static String blueToothDeviceAdress = "";
public static int blueToothSpinnerSelected = -1;
public static String tmpDeviceIP = "";
public static int tmpDevicePort = 0;
public static int tmpUsbDeviceID = 0;
public static int tmpUsbVendorID = 0;
public static int tmpUsbProductID = 0;
public static String link_code = "";
public static String tmpBlueToothDeviceAdress = "";
public static String getServerUrl() {
String actualServer = "";
if (debugModeBoolean) {
actualServer = serverLocalUrl;
} else {
actualServer = serverRealUrl;
}
return actualServer;
}
public static String server_registerPrinter = "registerPrinter";
public static String server_getData = "getData";
public static String regid = "";
public static String picturePath = "";
public static void savePreferences(SharedPreferences sharedPrefs) {
Log.i(internalClassName, "savePreferences");
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString("usbDeviceID", String.valueOf(usbDeviceID));
editor.putString("usbVendorID", String.valueOf(usbVendorID));
editor.putString("usbProductID", String.valueOf(usbProductID));
editor.putString("blueToothDeviceAdress", String.valueOf(blueToothDeviceAdress));
editor.putString("deviceType", String.valueOf(deviceType));
//Ethernet
editor.putString("deviceIP", String.valueOf(deviceIP));
editor.putString("devicePort", String.valueOf(devicePort));
editor.putString("link_code", String.valueOf(link_code));
editor.putString("picturePath", String.valueOf(picturePath));
editor.putString("logoProcesed", logoProcesed);
Log.i(internalClassName, " deviceType=" + String.valueOf(deviceType));
Log.i(internalClassName, " deviceIP=" + String.valueOf(deviceIP));
Log.i(internalClassName, " devicePort=" + String.valueOf(devicePort));
Log.i(internalClassName, " usbDeviceID=" + String.valueOf(usbDeviceID));
Log.i(internalClassName, " blueToothDeviceAdress=" + String.valueOf(blueToothDeviceAdress));
editor.commit();
}
public static void savePreferenceskitchen(SharedPreferences sharedPrefs) {
Log.i(internalClassName, "savePreferences");
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putString("usbDeviceID", String.valueOf(usbDeviceID));
editor.putString("usbVendorID", String.valueOf(usbVendorID));
editor.putString("usbProductID", String.valueOf(usbProductID));
editor.putString("blueToothDeviceAdress", String.valueOf(blueToothDeviceAdress));
editor.putString("deviceType", String.valueOf(deviceType));
//Ethernet
editor.putString("deviceIP", String.valueOf(deviceIP));
editor.putString("devicePort", String.valueOf(devicePort));
editor.putString("link_code", String.valueOf(link_code));
editor.putString("picturePath", String.valueOf(picturePath));
editor.putString("logoProcesed", logoProcesed);
Log.i(internalClassName, " deviceType=" + String.valueOf(deviceType));
Log.i(internalClassName, " deviceIP=" + String.valueOf(deviceIP));
Log.i(internalClassName, " devicePort=" + String.valueOf(devicePort));
Log.i(internalClassName, " usbDeviceID=" + String.valueOf(usbDeviceID));
Log.i(internalClassName, " blueToothDeviceAdress=" + String.valueOf(blueToothDeviceAdress));
editor.commit();
}
public static void loadPreferences(SharedPreferences sharedPrefs) {
Log.i(internalClassName, "loadPreferences");
usbDeviceID = mathIntegerFromString(sharedPrefs.getString("usbDeviceID", "0"), 0);
usbVendorID = mathIntegerFromString(sharedPrefs.getString("usbVendorID", "0"), 0);
usbProductID = mathIntegerFromString(sharedPrefs.getString("usbProductID", "0"), 0);
link_code = sharedPrefs.getString("link_code", "");
picturePath = sharedPrefs.getString("picturePath", "");
blueToothDeviceAdress = sharedPrefs.getString("blueToothDeviceAdress", "x:x:x:x");
deviceType = mathIntegerFromString(sharedPrefs.getString("deviceType", "0"), 0);
//Ethernet
deviceIP = sharedPrefs.getString("deviceIP", "192.168.0.98");
devicePort = mathIntegerFromString(sharedPrefs.getString("devicePort", "9100"), 9100);
logoProcesed = sharedPrefs.getString("logoProcesed", "");
Log.i(internalClassName, " deviceType=" + String.valueOf(deviceType));
Log.i(internalClassName, " deviceIP=" + String.valueOf(deviceIP));
Log.i(internalClassName, " devicePort=" + String.valueOf(devicePort));
Log.i(internalClassName, " usbDeviceID=" + String.valueOf(usbDeviceID));
Log.i(internalClassName, " blueToothDeviceAdress=" + String.valueOf(blueToothDeviceAdress));
}
public static void loadPreferenceskitchen(SharedPreferences sharedPrefs) {
Log.i(internalClassName, "loadPreferences");
usbDeviceID = mathIntegerFromString(sharedPrefs.getString("usbDeviceID", "0"), 0);
usbVendorID = mathIntegerFromString(sharedPrefs.getString("usbVendorID", "0"), 0);
usbProductID = mathIntegerFromString(sharedPrefs.getString("usbProductID", "0"), 0);
link_code = sharedPrefs.getString("link_code", "");
picturePath = sharedPrefs.getString("picturePath", "");
blueToothDeviceAdress = sharedPrefs.getString("blueToothDeviceAdress", "x:x:x:x");
deviceType = mathIntegerFromString(sharedPrefs.getString("deviceType", "0"), 0);
//Ethernet
deviceIP = sharedPrefs.getString("deviceIP", "192.168.0.98");
devicePort = mathIntegerFromString(sharedPrefs.getString("devicePort", "9100"), 9100);
logoProcesed = sharedPrefs.getString("logoProcesed", "");
Log.i(internalClassName, " deviceType=" + String.valueOf(deviceType));
Log.i(internalClassName, " deviceIP=" + String.valueOf(deviceIP));
Log.i(internalClassName, " devicePort=" + String.valueOf(devicePort));
Log.i(internalClassName, " usbDeviceID=" + String.valueOf(usbDeviceID));
Log.i(internalClassName, " blueToothDeviceAdress=" + String.valueOf(blueToothDeviceAdress));
}
public static Integer mathIntegerFromString(String inti, Integer defi) {
//used for preferences
Integer returi = defi;
if (!inti.equals("")) {
try {
returi = Integer.valueOf(inti);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
return returi;
}
public static String prepareDataToPrint(String data) {
//ESC POS Comamnds convertions
data = data.replace("$bold$", "·27·E·1·");
data = data.replace("$unbold$", "·27·E·0·");
data = data.replace("$intro$", "·13··10·");
data = data.replace("$cut$", "·27·m");
data = data.replace("$cutt$", "·27·i");
data = data.replace("$al_left$", "·27·a·0·");
data = data.replace("$al_center$", "·27·a·1·");
data = data.replace("$al_right$", "·27·a·2·");
data = data.replace("$small$", "·27·!·1·");
data = data.replace("$smallh$", "·27·!·17·");
data = data.replace("$smallw$", "·27·!·33·");
data = data.replace("$smallhw$", "·27·!·49·");
data = data.replace("$smallu$", "·27·!·129·");
data = data.replace("$smalluh$", "·27·!·145·");
data = data.replace("$smalluw$", "·27·!·161·");
data = data.replace("$smalluhw$", "·27·!·177·");
data = data.replace("$big$", "·27·!·0·");
data = data.replace("$bigh$", "·27·!·16·");
data = data.replace("$bigw$", "·27·!·32·");
data = data.replace("$bighw$", "·27·!·48·");
data = data.replace("$bigu$", "·27·!·128·");
data = data.replace("$biguh$", "·27·!·144·");
data = data.replace("$biguw$", "·27·!·160·");
data = data.replace("$biguhw$", "·27·!·176·");
data = data.replace("$drawer$", "·27··112··48··200··100·");
data = data.replace("$drawer2$", "·27··112··49··200··100·");
data = data.replace("$enter$", "·13··10·");
data = data.replace("$letrap$", "·27·!·1·");
data = data.replace("$letraph$", "·27·!·17·");
data = data.replace("$letrapw$", "·27·!·33·");
data = data.replace("$letraphw$", "·27·!·49·");
data = data.replace("$letrapu$", "·27·!·129·");
data = data.replace("$letrapuh$", "·27·!·145·");
data = data.replace("$letrapuw$", "·27·!·161·");
data = data.replace("$letrapuhw$", "·27·!·177·");
data = data.replace("$letrag$", "·27·!·0·");
data = data.replace("$letragh$", "·27·!·16·");
data = data.replace("$letragw$", "·27·!·32·");
data = data.replace("$letraghw$", "·27·!·48·");
data = data.replace("$letragu$", "·27·!·128·");
data = data.replace("$letraguh$", "·27·!·144·");
data = data.replace("$letraguw$", "·27·!·160·");
data = data.replace("$letraguhw$", "·27·!·176·");
data = data.replace("$letracorte$", "·27·m");
data = data.replace("$LETRACORTE$", "·27·m");
data = data.replace("$ENTER$", "·13··10·");
data = data.replace("$LETRAP$", "·27·!·1·");
data = data.replace("$LETRAPH$", "·27·!·17·");
data = data.replace("$LETRAPW$", "·27·!·33·");
data = data.replace("$LETRAPHW$", "·27·!·49·");
data = data.replace("$LETRAPU$", "·27·!·129·");
data = data.replace("$LETRAPUH$", "·27·!·145·");
data = data.replace("$LETRAPUW$", "·27·!·161·");
data = data.replace("$LETRAPUHW$", "·27·!·177·");
data = data.replace("$LETRAG$", "·27·!·0·");
data = data.replace("$LETRAGH$", "·27·!·16·");
data = data.replace("$LETRAGW$", "·27·!·32·");
data = data.replace("$LETRAGHW$", "·27·!·48·");
data = data.replace("$LETRAGU$", "·27·!·128·");
data = data.replace("$LETRAGUH$", "·27·!·144·");
data = data.replace("$LETRAGUW$", "·27·!·160·");
data = data.replace("$LETRAGUHW$", "·27·!·176·");
data = data.replace("á", "·160·");
data = data.replace("à", "·133·");
data = data.replace("â", "·131·");
data = data.replace("ä", "·132·");
data = data.replace("å", "·134·");
data = data.replace("Á", "·193·");
data = data.replace("À", "·192·");
data = data.replace("Â", "·194·");
data = data.replace("Ä", "·142·");
data = data.replace("Å", "·143·");
data = data.replace("é", "·130·");
data = data.replace("è", "·138·");
data = data.replace("ê", "·136·");
data = data.replace("ë", "·137·");
//data = data.replace("","··");
data = data.replace("É", "·144·");
data = data.replace("È", "··");
data = data.replace("Ê", "··");
data = data.replace("Ë", "··");
data = data.replace("ñ", "·164·");
data = data.replace("Ñ", "·165·");
//data = data.replace("","··");
data = data.replace("í", "··");
data = data.replace("ì", "·141·");
data = data.replace("î", "·140·");
data = data.replace("ï", "·139·");
//data = data.replace("","··");
data = data.replace("ó", "·149·");
data = data.replace("ò", "··");
data = data.replace("ô", "·147·");
data = data.replace("ö", "·148·");
//data = data.replace("","··");
data = data.replace("Ó", "··");
data = data.replace("Ò", "··");
data = data.replace("Ô", "··");
data = data.replace("Ö", "·153·");
//data = data.replace("","··");
data = data.replace("ú", "··");
data = data.replace("ù", "·151·");
data = data.replace("û", "··");
data = data.replace("ü", "·129·");
//data = data.replace("","··");
data = data.replace("ú", "··");
data = data.replace("ù", "·151·");
data = data.replace("û", "··");
data = data.replace("ü", "·129·");
//data = data.replace("..", "");
for (int l = 0; l <= 255; l++) {
data = data.replace("·" + String.valueOf(l) + "·", String.valueOf(((char) l)));
data = data.replace("$codepage" + String.valueOf(l) + "$", String.valueOf(((char) 27)) + "t" + String.valueOf(((char) l)));
}
Log.d("here----", ""+data);
return data;
}
//Convert string in Byte asccii to print
// public static byte[] stringToBytesASCII(String str) {
// byte[] b = new byte[str.length()];
// for (int i = 0; i < b.length; i++) {
// b[i] = (byte) str.charAt(i);
// }
// Log.d("bytes=-===", ""+b.toString());
// return b;
// }
public static byte[] stringToBytesASCII(String input) {
// int length = input.length();
// byte[] retVal = new byte[length];
//
// for(int i=0; i<length; i++)
// {
// char c = input.charAt(i);
//
// if (c < 127)
// {
// retVal[i] = (byte)c;
// }
// else
// {
// retVal[i] = (byte)(c - 256);
// }
// }
//
// return retVal;
return stringToBytesASCIIOK(input);
// return createImageFromText(100, 500,18 ,input, "Center");
}
// generate demo text
public static String getDemoText(Resources resources, Context context) {
String dataToPrint = "";
StringBuilder textData = new StringBuilder();
textData.append("$codepage32$");
textData.append("$al_center$$al_center$");
textData.append("$big$"+M.getBrandName(context)+"$intro$");
textData.append(M.getRestAddress(context)+"$intro$");
textData.append("Phone: "+ M.getRestPhoneNumber(context)+"$intro$");
textData.append("GST# "+ M.getGST(context)+"$intro$");
textData.append("--------------------------------------$intro$");
textData.append("Order By : "+ M.getWaitername(context)+"\t $intro$");
textData.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+"\n");
textData.append("ORDER # RES11-121"+"\n");
textData.append("--------------------------------------\n");
textData.append("TOKEN # "+21+"\t"+ "dine in" +"\n");
textData.append("--------------------------------------\n");
textData.append("Order comment \t");
textData.append("need spicy" + "\n");
textData.append("--------------------------------------\n");
String qty = "1";
textData.append(padLine(qty + " Cheese Mayo Fries", 120 + "", 40, " ") + "\n");
textData.append(padLine(qty + " Chocolate Milkshake", 130 + "", 40, " ") + "\n");
textData.append(padLine(qty + " Veg Sandwich", 100 + "", 40, " ") + "\n");
textData.append(padLine(qty + " بيتزا ", 120 + "", 40, " ") + "\n");
textData.append(padLine(qty + " بيتزا ", 130 + "", 40, " ") + "\n");
textData.append(padLine(qty + " بيتزا ", 100 + "", 40, " ") + "\n");
textData.append("--------------------------------------\n");
textData.append("--------------------------------------\n");
textData.append("SUBTOTAL "+350+"\n");
textData.append("CGST @"+6+"% "+21+"\n");
textData.append("SGST @"+6+"% "+21+"\n");
textData.append("TOTAL 392"+"\n");
textData.append("CASH 400"+"\n");
textData.append("CHANGE "+8+"\n");
textData.append("--------------------------------------\n");
textData.append("Thank you "+"Visit Again\n");
// dataToPrint = dataToPrint + "$codepage0$$intro$";
// dataToPrint = dataToPrint + Globals.getImageDataPosPrinterLogo(resources);
//
// dataToPrint = dataToPrint + "$bighw$PosPrinterDriver.com$intro$$intro$";
// dataToPrint = dataToPrint + "$big$ ESC/POS Types:$intro$ ";
// dataToPrint = dataToPrint + "$small$ ·36·small·36· ->Small type letter$intro$";
// dataToPrint = dataToPrint + "$big$ ·36·big·36· ->Big type letter$intro$";
//
//
// dataToPrint = dataToPrint + "$big$ ESC/POS functions:$intro$ ";
// dataToPrint = dataToPrint + "$big$ ·36·cut·36·: Cutt paper$intro$ ";
// dataToPrint = dataToPrint + "$big$ ·36·cutt·36·: Cutt total paper$intro$ ";
// dataToPrint = dataToPrint + "$big$ ·36·drawer·36·: Open drawer 1 $intro$ ";
// dataToPrint = dataToPrint + "$big$ ·36·drawer2·36·: Open drawer 2 $intro$ ";
//
// dataToPrint = dataToPrint + "Specials characters capabilities $intro$";
//test to find page codes
//for (int l = 50; l <= 255; l++) {
// datos=datos+ "valor "+ String.valueOf(l) + "= ·" + String.valueOf(l) + "·";
//}
/* String defTextToPrint="ABCDEFGHIJKLMNOPQRSTUVWXYZ $intro$aáàâäå AÁÀÂÄÅ $intro$eéèêë " +
"EÉÈÊË$intro$iíìîï IÍÌÎÏ $intro$oóòôö OÓÒÔÖ $intro$uúùûü UÚÙÛÜ $intro$ñ Ñ € $intro$";
dataToPrint = dataToPrint + "$codepage0$$intro$";
dataToPrint = dataToPrint + "Select code page 0 PC437 [U.S.A., Standard Europe]$intro$";
dataToPrint = dataToPrint + defTextToPrint;
dataToPrint = dataToPrint + "$codepage1$$intro$";
dataToPrint = dataToPrint + "Select code page 1 [Katakana]$intro$";
dataToPrint = dataToPrint + defTextToPrint;
dataToPrint = dataToPrint + "$codepage2$$intro$";
dataToPrint = dataToPrint + "Select code page 2 PC850 [Multilingual]$intro$";
dataToPrint = dataToPrint + defTextToPrint;
dataToPrint = dataToPrint + "$codepage3$$intro$";
dataToPrint = dataToPrint + "Select code page 3 PC860 [Portuguese]$intro$";
dataToPrint = dataToPrint + defTextToPrint;
dataToPrint = dataToPrint + "$codepage4$$intro$";
dataToPrint = dataToPrint + "Select code page 4 PC863 [Canadian-French]$intro$";
dataToPrint = dataToPrint + defTextToPrint;
dataToPrint = dataToPrint + "$codepage5$$intro$";
dataToPrint = dataToPrint + "Select code page 5 PC865 [Nordic]$intro$";
dataToPrint = dataToPrint + defTextToPrint;
dataToPrint = dataToPrint + "$codepage17$$intro$";
dataToPrint = dataToPrint + "Select code page 17 PC866 [Cyrillic]$intro$";
dataToPrint = dataToPrint + defTextToPrint;
//0 PC437 [U.S.A., Standard Europe]
//1 Katakana
// 2 PC850 [Multilingual]
// 3 PC860 [Portuguese]
// 4 PC863 [Canadian-French]
// 5 PC865 [Nordic]
// 17 PC866 [Cyrillic #2]
dataToPrint = dataToPrint + "$codepage31$$intro$";
dataToPrint = dataToPrint + "Select code page 31 HEBREW $intro$";
dataToPrint = dataToPrint + defTextToPrint;
//
if (Globals.picturePath != "") {
dataToPrint = dataToPrint + Globals.getImageData();
}
Log.e(internalClassName, "dataToPrint" + dataToPrint);*/
// dataToPrint = dataToPrint +"$intro$$intro$$intro$$intro$$cutt$$intro$";
textData.append("$intro$$intro$$intro$$intro$$cutt$$intro$");
dataToPrint = textData.toString();
return dataToPrint;
}
protected static String padLine(@Nullable String partOne, @Nullable String partTwo, int columnsPerLine, String str){
if(partOne == null) {partOne = "";}
if(partTwo == null) {partTwo = "";}
if(str == null) {str = " ";}
String concat;
if((partOne.length() + partTwo.length()) > columnsPerLine) {
concat = partOne + str + partTwo;
} else {
int padding = columnsPerLine - (partOne.length() + partTwo.length());
concat = partOne + repeat(str, padding) + partTwo;
}
return concat;
}
protected static String repeat(String str, int i){
return new String(new char[i]).replace("\0", str);
}
public static final byte GS = 0x1D;// Group separator
//get actual logo data
public static byte[] getImageData(Bitmap bitmap) {
// if (Globals.picturePath!=null && Globals.picturePath != "") {
// if (Globals.logoProcesed.length() > 0) {
// //response with calculated logo data text
// return Globals.logoProcesed;
// } else {
// byte[] bytes1 = new byte[4];
// bytes1[0] = GS;
// bytes1[1] = 0x76;
// bytes1[2] = 0x30;
// bytes1[3] = 0x00;
//
// byte[] bytes2 = BytesUtil.getBytesFromBitMap(bitmap);
return getBytesFromBitMap(bitmap, 32);
// }
// } else {
// return "";
// }
}
public static byte[] stringToBytesASCIIOK(String str) {
String TAG = "posprinterdriver_AsyncNetPrint";
byte[] b = new byte[0];
try {
b = str.getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// byte[] b = new byte[str.length()];
// if (str.length() > 0) {
// Log.i(TAG, " Caracter de 2 partes=" + str + " ->parte1=" + str.charAt(0) + "->" + str.charAt(1));
//
// }
// for (int i = 0; i < b.length; i++) {
//
// b[i] = (byte) str.charAt(i);
//
//
// }
for (int i = 0; i < b.length; i++) {
b[i] = (byte) str.charAt(i);
Log.i("", " CAdena encontrada=" + i + " ->parte1=" + str.charAt(i) + " ->parte1=" + (int) str.charAt(i));
}
return b;
}
I have Android app, which is working very well with English printing from the thermal printer. The code is perfect. But the problem is when I pass Arabic characters or any other Unicode it does not print properly.
The problem is with a code page. I tried different codepage which supports Arabic as well as Unicode while converting strings to byte. It shows the proper string in the log also of Arabic, but when It prints it does not work.
Note: My printer supports Arabic and codepage is 32. I need a universal printer solution. I am not using Epson SDK. I am using ESC/POS commands.
Import the project to your android studio. Note: Pair your android device to the thermal printer first then you can click connect in the application and proceed to print. Feel free the modify the cloned project to suit your need.
Still not working? Your thermal printer probably needs a good cleaning. We recommend using Thermal Printer Cleaning Cards with every paper roll change to remove residue and other contaminants from thermal print heads, paper guides and paper paths, helping to prevent paper jams and misprints.
Make the Connection Connect one end of the USB cable to the printer and the other end to the USB OTG. Then connect the other end of the USB OTG to your Android phone. A plugin should pop-up on your Android phone. Tap “OK” to activate it for printing.
most easy would be to use the Epson SDK; because it does not care if it's an Epson printer - only the vendor ID in the device-filter for USB devices differs in between the vendors; but Star Micronics and other 100% compatibles work just fine. if you want to write your own driver, which literally means re-inventing the wheel; have a look at ESC/POS appendix C, page 73:
for Arabic you'd need to select character table 13
with the ESC t
command, which is IBM code-page CP864. the sequence would be: \x1B \x74 \xD
. the output also would have to be encoded as CP864
, converted from UTF-16
, so that it matches the code-page, which the printer then expects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With