ng-thermal-print
ng-thermal-print copied to clipboard
This library without printing images is useless
I wrote this:
import { Inject, Injectable, NgModule } from '@angular/core';
export const WIDTH = 'WIDTH';
export const HEIGHT = 'HEIGHT';
export const INVERT_COLORS = 'INVERT_COLORS';
@Injectable({
providedIn: 'root',
})
export class ImagePrintingService {
private imageData: ImageData;
private invertColors: boolean;
private ESC_INIT = [0x1b, 0x40];
private ESC_BIT_IMAGE = [0x1b, 0x2a]
private DOTS_DENSITY = 24
private LUMINANCE = {
RED: 0.299,
GREEN: 0.587,
BLUE: 0.114
}
private LINE_FEED = 0x0a;
// https://www.sparkag.com.br/wp-content/uploads/2016/06/ESC_POS-AK912-English-Command-Specifications-V1.4.pdf
private LINE_FEED_AMOUNT = [0x1b, 0x33, 0x18]; // the third parameter is a numeric value where 1 = 0.125mm
private LINE_FEED_DEFAULT = [0x1b, 0x32];
// the best print quality is achieved when width and height are both multiples of 8
constructor(image: HTMLImageElement, @Inject(INVERT_COLORS) invertColors: boolean = false, @Inject(WIDTH) width?: number, @Inject(HEIGHT) height?: number) {
const canvas = document.createElement('canvas');
canvas.width = width ?? image.width;
canvas.height = height ?? image.height;
const context = canvas.getContext('2d');
if (!context) {
throw new Error('Could not get 2D context for canvas');
}
context.drawImage(image, 0, 0, canvas.width, canvas.height);
this.imageData = context.getImageData(0, 0, canvas.width, canvas.height);
this.invertColors = invertColors;
}
private calculateLuminance(pixel: number[]): number {
let lum = this.LUMINANCE.RED * pixel[0] + this.LUMINANCE.GREEN * pixel[1] + this.LUMINANCE.BLUE * pixel[2];
return this.invertColors ? 255 - lum : lum;
}
private calculateSlice(x: number, y: number): number {
const threshold = 127;
let slice = 0;
for (let bit = 0; bit < 8; bit++) {
if ((y + bit) >= this.imageData.height)
continue;
const index = (this.imageData.width * (y + bit) + x) * 4;
const pixel = [
this.imageData.data[index],
this.imageData.data[index + 1],
this.imageData.data[index + 2]
];
const luminance = this.calculateLuminance(pixel);
slice |= (luminance < threshold ? 1 : 0) << 7 - bit;
}
return slice;
}
private collectStripe(x: number, y: number): number[] {
let slices = [];
let z = y + this.DOTS_DENSITY;
let i = 0
while (y < z && i < 3){
slices.push(this.calculateSlice(x, y));
y += 8
}
return slices;
}
private manipulateImage(): number[] {
let data = [];
const imageWidth = this.imageData.width;
for (let y = 0; y < this.imageData.height; y += this.DOTS_DENSITY){
data.push(...this.ESC_BIT_IMAGE, 33, (0x00ff & imageWidth), (0xff00 & imageWidth) >> 8);
for (let x = 0; x < imageWidth; x++) {
data.push(...this.collectStripe(x, y));
}
data.push(this.LINE_FEED);
}
return data;
}
public getUint8Array(): Uint8Array {
let transformedImage = [];
// transformedImage.push(...this.ESC_INIT);
transformedImage.push(...this.LINE_FEED_AMOUNT);
transformedImage.push(...this.manipulateImage());
transformedImage.push(...this.LINE_FEED_DEFAULT);
return new Uint8Array(transformedImage);
}
}
Use it like this:
let img = new Image();
img.src = "assets/logo.svg";
img.onload = () => {
let imagePrintingService = new ImagePrintingService(img, true, 240, 56);
this.escEncoder.raw(imagePrintingService.getUint8Array())
}
@PiegoIlTempo , thanks alot for ur Help, can i see (this.escEncoder)
Np man, I had the same problem trying to print images, actually let me know if it gets printed correctly since I tested it on few printers.
This is an example of a printing service in Angular 11 with usb and bluetooth:
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import EscPosEncoder from 'esc-pos-encoder';
import { UsbDriver, BluetoothDriver, PrintService } from 'ngx-thermal-print';
import { BehaviorSubject, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { ImagePrintingService } from 'src/app/route-modules/web-app-root/services/image-printing-service';
import { formatToEuro } from 'src/app/shared/pipes/euro.pipe';
import { ConnectThermalPrinterDialogComponent } from '../components/connect-thermal-printer-dialog/connect-thermal-printer-dialog.component';
enum DeviceType {
USB,
BLUETOOTH
}
@Injectable({
providedIn: 'root',
})
export class ThermalPrinterService {
private readonly AUTO_PRINT_RECEIPT = "auto_print_receipt"
private readonly PRINTER_DEVICE = "printer_device"
private readonly productsTableFormat = [
{ width: 18, marginRight: 1, align: 'left' },
{ width: 4, align: 'right' },
{ width: 9, align: 'right' }
]
private readonly escEncoder = new EscPosEncoder({
imageMode: 'legacy',
codepageMapping: 'legacy'
});
private usbPrintDriver = new UsbDriver();
private bluetoothPrintDriver = new BluetoothDriver();
readonly isConnected = new BehaviorSubject<boolean>(false)
private codepage = "auto"
constructor(private matDialog: MatDialog) {
const device = this.getSavedPrinterDevice()
if(device != null) {
if(device.type == DeviceType.USB) {
this.usbPrintDriver = new UsbDriver(device.vendorId, device.productId)
this.usbPrintDriver.connect()
}
}
const isConnectedObservable = combineLatest([this.usbPrintDriver.isConnected, this.bluetoothPrintDriver.isConnected]).pipe(
map(([usb, bluetooth]) => {
return usb || bluetooth
})
)
isConnectedObservable.subscribe(this.isConnected)
}
private savePrinterDevice(type: DeviceType, vendorId: number, productId: number) {
localStorage.setItem(this.PRINTER_DEVICE, JSON.stringify({type: type, vendorId: vendorId, productId: productId}));
}
private getSavedPrinterDevice(): any|null {
const deviceData = localStorage.getItem(this.PRINTER_DEVICE)
if(deviceData != null) {
return JSON.parse(deviceData)
}
else return null;
}
private deleteSavedPrinterDevice() {
localStorage.removeItem(this.PRINTER_DEVICE)
}
disconnect() {
this.usbPrintDriver = new UsbDriver()
this.bluetoothPrintDriver = new BluetoothDriver()
this.deleteSavedPrinterDevice()
window.location.reload()
}
connectUsbPrinter() {
this.usbPrintDriver.requestUsb().subscribe(
(result) => {
// HACK to this fucking brand that doesn't support any codepage except chinese
if (result.manufacturerName == "GEZHI") {
this.codepage = "cp54936"
} else {
this.codepage = "auto"
}
this.usbPrintDriver.connect()
this.savePrinterDevice(DeviceType.USB, result.vendorId, result.productId)
},
(error) => {
console.log(error);
}
);
}
connectBluetoothPrinter() {
this.bluetoothPrintDriver.requestBluetooth().subscribe(
(result) => {
// HACK to this fucking brand that doesn't support any codepage except chinese
if (result.name == "MTP-II") {
this.codepage = "cp54936"
} else {
this.codepage = "auto"
}
this.bluetoothPrintDriver.connect()
},
(error) => {
console.log(error);
}
)
}
get hasPrinterSupport(): boolean {
return this.usbPrintDriver.isSupported || this.bluetoothPrintDriver.isSupported
}
get isBluetoothSupported(): boolean {
return this.bluetoothPrintDriver.isSupported
}
get isUsbSupported(): boolean {
return this.usbPrintDriver.isSupported
}
set autoPrintReceipt(value: boolean) {
localStorage.setItem(this.AUTO_PRINT_RECEIPT, value ? "1" : "0")
}
get autoPrintReceipt(): boolean {
const value = localStorage.getItem(this.AUTO_PRINT_RECEIPT)
if (value == null) {
return true
} else {
return value == "1"
}
}
connectPrinterDialog() {
ConnectThermalPrinterDialogComponent.openDialog(this.matDialog, this)
}
private endOfPrint() {
this.escEncoder.newline()
this.escEncoder.newline()
this.escEncoder.newline()
this.escEncoder.newline()
this.escEncoder.cut()
this.writeBuffer(this.escEncoder.encode())
}
printCheckout(documentId: string, checkhoutDetails: CheckoutDetails) {
if (this.isConnected.value) {
this.internalPrintScontrino(documentId, scontrinoDetails)
} else {
ConnectThermalPrinterDialogComponent.openDialog(this.matDialog, this).subscribe(result => {
if (result) {
this.internalPrintScontrino(documentId, scontrinoDetails)
}
})
}
}
private async internalPrintCheckout(documentId: string, checkhoutDetails: CheckoutDetails) {
this.escEncoder.codepage(this.codepage as any)
this.escEncoder.align("left")
this.escEncoder.newline()
this.escEncoder.bold(true)
this.escEncoder.table(
this.productsTableFormat,
[
["", "VAT", "EUR"],
]
)
this.escEncoder.bold(false)
this.escEncoder.table(
this.productsTableFormat, [[/* your table */]]
)
this.escEncoder.line(documentId)
this.escEncoder.newline()
this.escEncoder.line("Thank you, come again!")
this.escEncoder.newline()
let logo = new Image();
logo.src = "assets/logo.svg";
logo.onload = () => {
let imagePrintingService = new ImagePrintingService(logo, true, 240, 56);
this.escEncoder.raw(imagePrintingService.getUint8Array())
if(checkoutDetails.referral_link != null) {
this.escEncoder.bold(true)
this.escEncoder.line("Download the app!")
this.escEncoder.qrcode(checkoutDetails.referral_link, 1, 8)
this.escEncoder.bold(false)
}
this.escEncoder.align("left")
this.endOfPrint()
}
}
private writeBuffer(buffer: Uint8Array) {
if (this.usbPrintDriver.isConnected.value) {
this.usbPrintDriver.write(buffer)
} else if (this.bluetoothPrintDriver.isConnected.value) {
this.bluetoothPrintDriver.write(buffer)
}
}
}
NOTE: The EscPosEncoder has an image() printing method, but it never worked at all for me, so that's why I print images using raw(), it might work for you out of the box maybe.