iOS開發(fā)之藍(lán)牙/Socket鏈接小票打印機(jī)(一)

前言

之前公司有個面向商戶的項目,需要連接商戶打印機(jī)打印小票的功能。于是對這方面進(jìn)行了學(xué)習(xí)研究,最后“順利”的完成了項目需求。這里主要是對項目中用到的知識點進(jìn)行一些總結(jié)。這篇文章主要包含的相關(guān)知識有:SocketCoreBluetooth網(wǎng)口小票打印機(jī)藍(lán)牙小票打印機(jī)ESC/POS打印命令集圖片打印等。

概述

整個打印流程大致分可以為三個步驟,①鏈接打印機(jī);②編輯排版打印內(nèi)容;③發(fā)送數(shù)據(jù)給打印機(jī);

①和③根據(jù)不同的打印機(jī)類型,我們要采取不同的鏈接方式。網(wǎng)口打印機(jī)通過Socket進(jìn)行鏈接(需在同一局域網(wǎng)下),藍(lán)牙打印機(jī)自然是通過藍(lán)牙進(jìn)行鏈接。
②編輯排版打印內(nèi)容,需要通過ESC/POS打印命令集來做,以下會進(jìn)行相關(guān)的介紹。

其實,步驟②編輯排版打印內(nèi)容,放到后臺做是更加合理的,這樣Android和iOS兩端就避免了都要寫編輯排版的代碼,而且也能避免排版上的差異。我們公司也是這樣做的,所以步驟②就可以改為從后臺獲取要打印的數(shù)據(jù)。

ESC/POS打印命令集

簡介

WPSON StandardCode for Printer 是EPSON公司自己制定的針式打印機(jī)的標(biāo)準(zhǔn)化指令集,現(xiàn)在已成為針式打印機(jī)控制語言事實上的工業(yè)標(biāo)準(zhǔn)。ESC/POS打印命令集是ESC打印控制命令的簡化版本,現(xiàn)在大多數(shù)票據(jù)打印都采用ESC/POS指令集。其顯著特征是:其中很大一部分指令都是以ESC控制符開始的一串代碼。

打印機(jī)的型號種類有很多,不同的廠家也對其產(chǎn)品做了相應(yīng)的定制。但是,ESC/POS指令集基本都會支持。關(guān)于指令的詳細(xì)內(nèi)容,網(wǎng)上有很多文檔,另外每個品牌的官網(wǎng),也會有對應(yīng)的打印機(jī)指令文檔提供下載。我們可以下載下來研究。這里簡單介紹幾種常用的指令:

指令介紹

說明:一般打印機(jī)接受指令都支持三種格式:ASCII、十進(jìn)制、十六進(jìn)制。

1、初始化打印機(jī)

ASCII 十進(jìn)制 十六進(jìn)制
ESC @ 27 64 1B 40

說明:清除打印緩沖區(qū),刪除用戶自定義字符,打印模式被設(shè)為上電時的默認(rèn)值模式。

代碼:

//重置打印機(jī)
- (void)resetPrinter {
   Byte reset[] = {0x1B,0x40};
   [self.printData appendBytes:reset length:1];
}

注意:經(jīng)筆者測試發(fā)現(xiàn),使用初始化命令,之后的一條命令可能會失效,目前未找到原因,可能是打印機(jī)問題。另外,由于此命令會清除緩沖區(qū),頻繁調(diào)用可能會導(dǎo)致數(shù)據(jù)丟失,因此盡量少用此命令。

2、打印并換行

ASCII 十進(jìn)制 十六進(jìn)制
LF 10 0A

說明:將打印緩沖區(qū)中的數(shù)據(jù)打印出來,并且按照當(dāng)前行間距,把打印紙向前推進(jìn)一行。

代碼:

//打印機(jī)并換行
- (void)printAndNewline {
   Byte next[] = {0x0A};
   [self.printData appendBytes:next length:1];
}

3、打印并走n點行紙

ASCII 十進(jìn)制 十六進(jìn)制
LESC J n 27 74 n 1B 4A n

說明:打印緩沖區(qū)數(shù)據(jù)并走紙[ n × 縱向或橫向移動單位] 英寸。0 ≤n ≤ 255。最大走紙距離是956 mm(不同品牌打印機(jī)數(shù)值不同)。如果超出這個距離,取最大距離。

代碼:

//打印緩沖區(qū)數(shù)據(jù),并往前走紙n點行
- (void)printAndGoNPointLine:(int)n {
   Byte line[] = {0x1B, 0x4A, n};
   [self.printData appendBytes:line length:3];
}

注意:這里是走紙點行數(shù),要與字符行數(shù)區(qū)分

4、打印并走n行紙

ASCII 十進(jìn)制 十六進(jìn)制
ESC d n 27 100 n 1B 64 n

說明:打印緩沖區(qū)里的數(shù)據(jù)并向前走紙n行(字符行)。0 ≤n ≤ 255。該命令不影響由ESC 2 或ESC 3設(shè)置的行間距。 最大走紙距離為1016 mm,當(dāng)所設(shè)的值大于1016 mm時,取最大值。

代碼:

//打印緩沖區(qū)數(shù)據(jù),并往前走紙n行
- (void)printAndGoNLine:(int)n {
   Byte line[] = {0x1B, 0x64, n};
   [self.printData appendBytes:line length:3];
}

注意:這里是走紙字符行數(shù),要與點行數(shù)區(qū)分。只有設(shè)置了行距后,此命令才有效。使用此命令前,要先使用換行指令,否則設(shè)置無效

5、設(shè)置默認(rèn)行距(1/6英寸)

ASCII 十進(jìn)制 十六進(jìn)制
ESC 2 27 50 1B 32

說明:選擇約3.75mm 行間距。約34個點。

代碼:

//設(shè)置默認(rèn)行間距
- (void)printDefaultLineSpace {
   Byte defaultLineSpace[] = {0x1B,0x32};
   [self.printData appendBytes:defaultLineSpace length:2];
}

6、設(shè)置行間距為n 點行

ASCII 十進(jìn)制 十六進(jìn)制
ESC 3 n 27 51 n 1B 33 n

說明:設(shè)置行間距為[ n × 縱向或橫向移動單位] 英寸。

代碼:

//設(shè)置行間距為n個點
- (void)printLineSpace:(int)n {
   Byte lineSpace[] = {0x1B,0x33,n};
   [self.printData appendBytes:lineSpace length:3];
}

注意:使用此命令前,要先使用換行指令,否則設(shè)置無效

7、設(shè)置字符右間距

ASCII 十進(jìn)制 十六進(jìn)制
ESC SP n 27 32 n 1B 20 n

說明:設(shè)置字符的右間距為[n×橫向移動單位或縱向移動單位]英寸。0 ≤ n ≤255。最大右間距是31.91毫米(255/203 英寸)。任何超過這個值的設(shè)置都自動轉(zhuǎn)換為最大右間距。

代碼:

//字符右間距
- (void)printCharRightSpace:(int)n {
   Byte line[] = {0x1B, 0x20, n};
   [self.printData appendBytes:line length:3];
}

注意:此命令對漢字無效

8、設(shè)置輸出對齊方式

ASCII 十進(jìn)制 十六進(jìn)制
ESC a n 27 97 n 1B 61 n

說明:n = 0或48 為左對齊;n = 1或49為中間對齊;n = 2或50位右對齊。

代碼:

//設(shè)置對齊方式
- (void)setAlignment:(MNAlignmentType)alignmentType {
   Byte align[] = {0x1B,0x61,alignmentType};
   [self.printData appendBytes:align length:3];
}

9、設(shè)置字體大小

ASCII 十進(jìn)制 十六進(jìn)制
GS ! n 29 33 n 1D 21 n

說明:用0 到2 位選擇字符高度,4 到7 位選擇字符寬度。

代碼:

//字符放大倍數(shù)
typedef enum: UInt8 {
   MNPrintFont_1 = 0x00,
   MNPrintFont_2 = 0x11,
   MNPrintFont_3 = 0x22,
   MNPrintFont_4 = 0x33,
   MNPrintFont_5 = 0x44,
   MNPrintFont_6 = 0x55,
   MNPrintFont_7 = 0x66,
   MNPrintFont_8 = 0x77,
} MNPrintFont;


//設(shè)置字體大小
-(void)printCharSize:(MNPrintFont)printFont {
   Byte font[] = {0x1D,0x21,printFont};
   [self.printData appendBytes:font length:3];
};

10、選擇切紙模式和切紙

ASCII 十進(jìn)制 十六進(jìn)制
GS V m 29 86 m 1D 56 m
ASCII 十進(jìn)制 十六進(jìn)制
GS V m n 29 86 m n 1D 56 m n

說明:

  • m=0,1,49 ;0 表示全切, 1表示半切,當(dāng)打印機(jī)沒有半切功能時,全切;
  • m=66, 0≤n≤255 ;當(dāng)m=66時, n表示走紙到(約18mm)+[n*0.125mm] 位置切紙

代碼:

//切紙模式
typedef enum :UInt8 {
   MNCutPaperModelFull = 0x00,
   MNCutPaperModelHalf = 0x01,
   MNCutPaperModelFeedPaperHalf = 0x66
}MNCutPaperModel;

- (void)printCutPaper:(MNCutPaperModel)model Num:(int)n {
   if (model == MNCutPaperModelFull) {
       Byte cut[] = {0x1D, 0x56, model, n};
       [self.printData appendBytes:cut length:4];
   } else {
       Byte cut[] = {0x1D, 0x56, model};
       [self.printData appendBytes:cut length:3];
   }
}

注意這條指令需要打印機(jī)支持切紙

10、產(chǎn)生錢箱脈沖(開錢箱)

ASCII 十進(jìn)制 十六進(jìn)制
ESC p m t1 t2 27 112 m t1 t2 1B 70 m t1 t2

說明:

  • m = 0, 1, 48, 49 ; 0 ≤ t1 ≤ 255, 0 ≤ t2 ≤ 255 ;
  • 輸出由t1和t2設(shè)定的錢箱開啟脈沖到由m指定的引腳:
M 十進(jìn)制
0, 48 錢箱插座的引腳 2
1, 49 錢箱插座的引腳 5

代碼:

//產(chǎn)生錢箱控制脈沖,一般一個打印機(jī)連接一個錢箱,這里默認(rèn)寫死了
-(void)printOpenCashDrawer {
    Byte open[] = {0x1B, 0x70, 0x00, 0x80, 0xFF};
    [self.printData appendBytes:open length:5];
}

注意這條指令需要打印機(jī)連接錢箱

打印內(nèi)容

說明:這里只要將打印內(nèi)容通過kCFStringEncodingGB_18030_2000編碼,然后發(fā)送給打印機(jī)

代碼:

- (void)printWithContent:(NSString *)content {
    NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
    NSData *data = [content dataUsingEncoding:enc];
    [self.printData appendData:data];
}

以上只是部分指令,可根據(jù)需求,參考指令集文檔再做相應(yīng)的添加。這里要提一下的是,小票打印多用于訂單詳情類信息,為了是排版更美觀,這里用的比較多的是制表符/t來使每一列對齊,可以直接這樣使用[self.printManager printWithContent:@"\t"];

圖片打印

關(guān)于圖片打印,這里介紹兩種打印指令:

位圖模式.png
光柵位圖.png

原理

因為小票打印機(jī)多為熱敏打印機(jī),或針式打印機(jī),且顏色只有黑白兩色。因此,要打印圖片,首先要獲取圖片的像素數(shù)據(jù),然后將圖片進(jìn)行黑白二值化處理,之后拼接打印數(shù)據(jù),黑色為打印的點,白色為不打印的點。如此逐行打印圖片數(shù)據(jù)。

調(diào)整分辨率

-(UIImage*)scaleImageWithImage:(UIImage*)image width:(NSInteger)width height:(NSInteger)height
{
    CGSize size;
    size.width = width;
    size.height = height;
    UIGraphicsBeginImageContext(size);
    [image drawInRect:CGRectMake(0, 0, width, height)];
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

獲取像素數(shù)據(jù)

-(CGContextRef)CreateARGBBitmapContextWithCGImageRef:(CGImageRef)inImage
{
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;
    
    // Get image width, height. We'll use the entire image.
    size_t pixelsWide = CGImageGetWidth(inImage);
    size_t pixelsHigh = CGImageGetHeight(inImage);
    
    // Declare the number of bytes per row. Each pixel in the bitmap in this
    // example is represented by 4 bytes; 8 bits each of red, green, blue, and
    // alpha.
    bitmapBytesPerRow   = (int)(pixelsWide * 4);
    bitmapByteCount     = (int)(bitmapBytesPerRow * pixelsHigh);
    
    // Use the generic RGB color space.
    colorSpace =CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL)
    {
        return NULL;
    }
    
    // Allocate memory for image data. This is the destination in memory
    // where any drawing to the bitmap context will be rendered.
    bitmapData = malloc( bitmapByteCount );
    if (bitmapData == NULL)
    {
        CGColorSpaceRelease( colorSpace );
        return NULL;
    }
    
    // Create the bitmap context. We want pre-multiplied ARGB, 8-bits
    // per component. Regardless of what the source image format is
    // (CMYK, Grayscale, and so on) it will be converted over to the format
    // specified here by CGBitmapContextCreate.
    context = CGBitmapContextCreate (bitmapData,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,      // bits per component
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedFirst);
    if (context == NULL)
    {
        free (bitmapData);
    }
    
    // Make sure and release colorspace before returning
    CGColorSpaceRelease( colorSpace );
    
    return context;
}

位圖模式指令

  • 根據(jù)像素信息將圖片進(jìn)行黑白化處理,并逐行拼接打印信息
typedef enum {
    ALPHA = 0,
    BLUE = 1,
    GREEN = 2,
    RED = 3
} PIXELS;

- (NSData *) imageToThermalData:(UIImage*)image {
    CGImageRef imageRef = image.CGImage;
    // Create a bitmap context to draw the uiimage into
    CGContextRef context = [self CreateARGBBitmapContextWithCGImageRef:imageRef];
    if(!context) {
        return NULL;
    }
    
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    
    CGRect rect = CGRectMake(0, 0, width, height);
    
    // Draw image into the context to get the raw image data
    CGContextDrawImage(context, rect, imageRef);
    
    // Get a pointer to the data
    uint32_t *bitmapData = (uint32_t *)CGBitmapContextGetData(context);
    
    if(bitmapData) {
        
        uint8_t *m_imageData = (uint8_t *) malloc(width * height/8 + 8*height/8);
        memset(m_imageData, 0, width * height/8 + 8*height/8);
        int result_index = 0;
        
        for(int y = 0; (y + 24) < height;) {
            m_imageData[result_index++] = 27;
            m_imageData[result_index++] = 51;
            m_imageData[result_index++] = 0;
            
            m_imageData[result_index++] = 27;
            m_imageData[result_index++] = 42;
            m_imageData[result_index++] = 33;
            
            m_imageData[result_index++] = width%256;
            m_imageData[result_index++] = width/256;
            for(int x = 0; x < width; x++) {
                int value = 0;
                for (int temp_y = 0 ; temp_y < 8; ++temp_y)
                {
                    uint8_t *rgbaPixel = (uint8_t *) &bitmapData[(y+temp_y) * width + x];
                    uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE];
                    
                    if (gray < 127)
                    {
                        value += 1<<(7-temp_y)&255;
                    }
                }
                m_imageData[result_index++] = value;
                
                value = 0;
                for (int temp_y = 8 ; temp_y < 16; ++temp_y)
                {
                    uint8_t *rgbaPixel = (uint8_t *) &bitmapData[(y+temp_y) * width + x];
                    uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE];
                    
                    if (gray < 127)
                    {
                        value += 1<<(7-temp_y%8)&255;
                    }
                    
                }
                m_imageData[result_index++] = value;
                
                value = 0;
                for (int temp_y = 16 ; temp_y < 24; ++temp_y)
                {
                    uint8_t *rgbaPixel = (uint8_t *) &bitmapData[(y+temp_y) * width + x];
                    uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE];
                    
                    if (gray < 127)
                    {
                        value += 1<<(7-temp_y%8)&255;
                    }
                    
                }
                m_imageData[result_index++] = value;
            }
            m_imageData[result_index++] = 13;
            m_imageData[result_index++] = 10;
            y += 24;
        }
        NSMutableData *data = [[NSMutableData alloc] initWithCapacity:0];
        [data appendBytes:m_imageData length:result_index];
        free(bitmapData);
        return data;
        
    } else {
        NSLog(@"Error getting bitmap pixel data\n");
    }
    
    CGContextRelease(context);
    
    return nil ;
}

光柵位圖指令

#pragma mark ********************另一種打印圖片的方式****************************
typedef struct ARGBPixel {
    
    u_int8_t             red;
    u_int8_t             green;
    u_int8_t             blue;
    u_int8_t             alpha;
    
} ARGBPixel ;

#pragma mark 獲取打印圖片數(shù)據(jù)
-(NSData *)getDataForPrintWith:(UIImage *)image{
    
    CGImageRef cgImage = [image CGImage];
    
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    
    
    NSData* bitmapData = [self getBitmapImageDataWith:cgImage];
    
    const char * bytes = bitmapData.bytes;
    
    NSMutableData * data = [[NSMutableData alloc] init];
    
    //橫向點數(shù)計算需要除以8
    NSInteger w8 = width / 8;
    //如果有余數(shù),點數(shù)+1
    NSInteger remain8 = width % 8;
    if (remain8 > 0) {
        w8 = w8 + 1;
    }
    /**
     根據(jù)公式計算出 打印指令需要的參數(shù)
     指令:十六進(jìn)制碼 1D 76 30 m xL xH yL yH d1...dk
     m為模式,如果是58毫秒打印機(jī),m=1即可
     xL 為寬度/256的余數(shù),由于橫向點數(shù)計算為像素數(shù)/8,因此需要 xL = width/(8*256)
     xH 為寬度/256的整數(shù)
     yL 為高度/256的余數(shù)
     yH 為高度/256的整數(shù)
     **/
    NSInteger xL = w8 % 256;
    NSInteger xH = width / (88 * 256);
    NSInteger yL = height % 256;
    NSInteger yH = height / 256;
    
    Byte cmd[] = {0x1d,0x76,0x30,0,xL,xH,yL,yH};
    
    
    [data appendBytes:cmd length:8];
    
    for (int h = 0; h < height; h++) {
        for (int w = 0; w < w8; w++) {
            u_int8_t n = 0;
            for (int i=0; i<8; i++) {
                int x = i + w * 8;
                u_int8_t ch;
                if (x < width) {
                    int pindex = h * (int)width + x;
                    ch = bytes[pindex];
                }
                else{
                    ch = 0x00;
                }
                n = n << 1;
                n = n | ch;
            }
            [data appendBytes:&n length:1];
        }
    }
    return data;
}
#pragma mark 獲取圖片點陣圖數(shù)據(jù)
-(NSData *)getBitmapImageDataWith:(CGImageRef)cgImage{
    
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    
    NSInteger psize = sizeof(ARGBPixel);
    
    ARGBPixel * pixels = malloc(width * height * psize);
    
    NSMutableData* data = [[NSMutableData alloc] init];
    
    [self ManipulateImagePixelDataWithCGImageRef:cgImage imageData:pixels];
    
    for (int h = 0; h < height; h++) {
        for (int w = 0; w < width; w++) {
            
            int pIndex = (w + (h * (u_int32_t)width));
            ARGBPixel pixel = pixels[pIndex];
            
            if ((0.3*pixel.red + 0.59*pixel.green + 0.11*pixel.blue) <= 127) {
                //打印黑
                u_int8_t ch = 0x01;
                [data appendBytes:&ch length:1];
            }
            else{
                //打印白
                u_int8_t ch = 0x00;
                [data appendBytes:&ch length:1];
            }
        }
    }
    
    return data;
}

// 獲取像素信息
-(void)ManipulateImagePixelDataWithCGImageRef:(CGImageRef)inImage imageData:(void*)oimageData
{
    // Create the bitmap context
    CGContextRef cgctx = [self CreateARGBBitmapContextWithCGImageRef:inImage];
    if (cgctx == NULL)
    {
        // error creating context
        return;
    }
    
    // Get image width, height. We'll use the entire image.
    size_t w = CGImageGetWidth(inImage);
    size_t h = CGImageGetHeight(inImage);
    CGRect rect = {{0,0},{w,h}};
    
    // Draw the image to the bitmap context. Once we draw, the memory
    // allocated for the context for rendering will then contain the
    // raw image data in the specified color space.
    CGContextDrawImage(cgctx, rect, inImage);
    
    // Now we can get a pointer to the image data associated with the bitmap
    // context.
    void *data = CGBitmapContextGetData(cgctx);
    if (data != NULL)
    {
        CGContextRelease(cgctx);
        memcpy(oimageData, data, w * h * sizeof(u_int8_t) * 4);
        free(data);
        return;
    }
    
    // When finished, release the context
    CGContextRelease(cgctx);
    // Free image data memory for the context
    if (data)
    {
        free(data);
    }
    
    return;
}

拼接圖片數(shù)據(jù),準(zhǔn)備發(fā)給打印機(jī)

//打印圖片
- (void)printWithImage:(UIImage *)image width:(float)width height:(float)height {
    UIImage * printImage = [self scaleImageWithImage:image width:width height:height];
    NSData *data = [self imageToThermalData:printImage];
    [self.printData appendData:data];
}

提高圖片打印速度

由于打印圖片是根據(jù)像素點來逐行打印,因此數(shù)據(jù)量會遠(yuǎn)高于普通文字,這就造成了打印圖片的速度回比文字慢,尤其是藍(lán)牙打印機(jī)。解決方法可以從兩個方面入手,1、增加每次發(fā)送的數(shù)據(jù)量(主要針對藍(lán)牙打印機(jī));2、減少圖片的數(shù)據(jù)量。

增加每次發(fā)送的數(shù)據(jù)量(主要針對藍(lán)牙打印機(jī));

關(guān)于這一點,在下一篇講到藍(lán)牙時也會說到,由于藍(lán)牙硬件限制,每次給打印機(jī)發(fā)送的數(shù)據(jù)量是有限制的,因此要將打印數(shù)據(jù)拆分,循環(huán)發(fā)送,代碼如下:

- (void)printLongData:(NSData *)printContent{
    NSUInteger cellMin;
    NSUInteger cellLen;
    //數(shù)據(jù)長度
    NSUInteger strLength = [printContent length];
    if (strLength < 1) {
        return;
    }
    //MAX_CHARACTERISTIC_VALUE_SIZE = 120
    NSUInteger cellCount = (strLength % MAX_CHARACTERISTIC_VALUE_SIZE) ? (strLength/MAX_CHARACTERISTIC_VALUE_SIZE + 1):(strLength/MAX_CHARACTERISTIC_VALUE_SIZE);
    for (int i = 0; i < cellCount; i++) {
        cellMin = i*MAX_CHARACTERISTIC_VALUE_SIZE;
        if (cellMin + MAX_CHARACTERISTIC_VALUE_SIZE > strLength) {
            cellLen = strLength-cellMin;
        }
        else {
            cellLen = MAX_CHARACTERISTIC_VALUE_SIZE;
        }
        NSRange rang = NSMakeRange(cellMin, cellLen);
        //        截取打印數(shù)據(jù)
        NSData *subData = [printContent subdataWithRange:rang];
        //循環(huán)寫入數(shù)據(jù)
        [self.peripheral writeValue:subData forCharacteristic:self.characteristicInfo type:CBCharacteristicWriteWithResponse];
    }
}

這里的MAX_CHARACTERISTIC_VALUE_SIZE是個宏定義,表示每次發(fā)送的數(shù)據(jù)長度,經(jīng)筆者測試,當(dāng)MAX_CHARACTERISTIC_VALUE_SIZE = 20時,打印文字是正常速度。但打印圖片的速度非常慢,應(yīng)該在硬件允許的范圍內(nèi),每次發(fā)盡量多的數(shù)據(jù)。不同品牌型號的打印機(jī),這個參數(shù)是不同的,筆者的藍(lán)牙打印機(jī)該值最多到140。超出后會出現(xiàn)無法打印問題。最后筆者將該值定為MAX_CHARACTERISTIC_VALUE_SIZE = 120,測試了公司幾臺打印機(jī)都沒有問題。

另外iOS9以后增加了方法maximumWriteValueLengthForType:可以獲取寫入特診的最大寫入數(shù)據(jù)量,但經(jīng)筆者測試,對于部分打印機(jī)(比如我們公司的)是不準(zhǔn)確的,因此,不要太依賴此方法,最好還是自己取一個合適的值。

減少圖片的數(shù)據(jù)量

要減少圖片的數(shù)據(jù)量,我們可以降低分辨率。通過研究指令集筆者發(fā)現(xiàn),光柵位圖的倍寬,橫向分辨率降低了一倍。倍高,縱向分辨率降低了一倍。因此,筆者嘗試選擇倍寬、倍高模式,即m=3;此時發(fā)現(xiàn)打印出的圖片尺寸比圖片要大一倍。這樣我們只要將圖片的寬、高分別除以2。

比如我們要打印寬、高為250的圖片。m = 3 時,打印命令改為:

Byte cmd[] = {0x1d,0x76,0x30,3,xL,xH,yL,yH};

調(diào)用時:

[self.printManager printWithImage:[UIImage imageNamed:@"1513654780"] width:250/2 height:250/2];

經(jīng)筆者測試,倍寬、倍高模式打印機(jī)圖片的速度,和打印文字速度相差無幾。但圖片的清晰度會有所下降。究竟使用哪種,可自行權(quán)衡。

使用舉例

示例代碼

這里只是簡單的講解舉例,代碼并沒有很好的封裝,我們可以根據(jù)自己的需求,封裝一個適合自己的模板類。

    self.printManager.printData.length = 0;
//    [self.printManager resetPrinter];
    
//    [self.printManager printLineSpace:50];
    [self.printManager printCharSize:MNPrintFont_2];
    [self.printManager setAlignment:MNAlignmentTypeCenter];
    [self.printManager printCharRightSpace:1];
    [self.printManager printWithContent:@"這是標(biāo)題"];
    
    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:1];
    [self.printManager printAndGoNPointLine:60];
    
    [self.printManager setAlignment:MNAlignmentTypeLeft];
    [self.printManager printCharSize:MNPrintFont_1];
    [self.printManager printWithContent:@"商品名稱"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"數(shù)量"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"價格"];
    
    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:1];
    [self.printManager printAndGoNPointLine:34];
    
    [self.printManager printWithContent:@"商品1"];
    [self.printManager printWithContent:@"\t\t"];
    [self.printManager printWithContent:@"2"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"1999"];
    
    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:1];
    [self.printManager printAndGoNPointLine:25];
    
    [self.printManager printWithContent:@"商品2"];
    [self.printManager printWithContent:@"\t\t"];
    [self.printManager printWithContent:@"200"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"19"];
    
    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:1];
    [self.printManager printAndGoNPointLine:25];
    
    [self.printManager printWithContent:@"商品3"];
    [self.printManager printWithContent:@"\t\t"];
    [self.printManager printWithContent:@"200"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"19"];
    
    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:2];
    [self.printManager printAndGoNPointLine:25];
    
    [self.printManager setAlignment:MNAlignmentTypeCenter];
    [self.printManager printWithContent:@"-----------------------------"];
    
    [self.printManager printAndNewline];
    //    [self.printManager printAndGoNLine:2];
    [self.printManager printAndGoNPointLine:25];
    
    [self.printManager setAlignment:MNAlignmentTypeRight];
    [self.printManager printWithContent:@"總計:11598元"];
    
    [self.printManager printAndNewline];
    [self.printManager printAndGoNPointLine:100];
    
    [self.printManager setAlignment:MNAlignmentTypeCenter];
    [self.printManager printWithImage:[UIImage imageNamed:@"1513654780"] width:200 height:200];
    
    [self.printManager printAndNewline];
    [self.printManager printAndGoNPointLine:150];
    

效果圖

小票效果圖.jpeg

總結(jié)

篇幅所限,這一篇先介紹通過ESC/POS打印命令集,拼接打印指令,排版打印格式。接下來的文章會介紹如何通過藍(lán)牙或Socket將我們編輯的打印數(shù)據(jù)發(fā)送給打印機(jī)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內(nèi)容