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

前言

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

概述

整個(gè)打印流程大致分可以為三個(gè)步驟,①鏈接打印機(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打印命令集來做,以下會(huì)進(jìn)行相關(guān)的介紹。

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

ESC/POS打印命令集

簡(jiǎn)介

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

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

指令介紹

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

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

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

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

代碼:

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

注意:經(jīng)筆者測(cè)試發(fā)現(xiàn),使用初始化命令,之后的一條命令可能會(huì)失效,目前未找到原因,可能是打印機(jī)問題。另外,由于此命令會(huì)清除緩沖區(qū),頻繁調(diào)用可能會(huì)導(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點(diǎn)行紙

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

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

代碼:

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

注意:這里是走紙點(diǎn)行數(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時(shí),取最大值。

代碼:

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

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

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

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

說明:選擇約3.75mm 行間距。約34個(gè)點(diǎn)。

代碼:

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

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

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

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

代碼:

//設(shè)置行間距為n個(gè)點(diǎ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×橫向移動(dòng)單位或縱向移動(dòng)單位]英寸。0 ≤ n ≤255。最大右間距是31.91毫米(255/203 英寸)。任何超過這個(gè)值的設(shè)置都自動(dòng)轉(zhuǎn)換為最大右間距。

代碼:

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

注意:此命令對(duì)漢字無效

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

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

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

代碼:

//設(shè)置對(duì)齊方式
- (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ī)沒有半切功能時(shí),全切;
  • m=66, 0≤n≤255 ;當(dāng)m=66時(shí), 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)生錢箱控制脈沖,一般一個(gè)打印機(jī)連接一個(gè)錢箱,這里默認(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來使每一列對(duì)齊,可以直接這樣使用[self.printManager printWithContent:@"\t"];

圖片打印

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

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

原理

因?yàn)樾∑贝蛴C(jī)多為熱敏打印機(jī),或針式打印機(jī),且顏色只有黑白兩色。因此,要打印圖片,首先要獲取圖片的像素?cái)?shù)據(jù),然后將圖片進(jìn)行黑白二值化處理,之后拼接打印數(shù)據(jù),黑色為打印的點(diǎn),白色為不打印的點(diǎn)。如此逐行打印圖片數(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;
}

獲取像素?cái)?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];
    
    //橫向點(diǎn)數(shù)計(jì)算需要除以8
    NSInteger w8 = width / 8;
    //如果有余數(shù),點(diǎn)數(shù)+1
    NSInteger remain8 = width % 8;
    if (remain8 > 0) {
        w8 = w8 + 1;
    }
    /**
     根據(jù)公式計(jì)算出 打印指令需要的參數(shù)
     指令:十六進(jìn)制碼 1D 76 30 m xL xH yL yH d1...dk
     m為模式,如果是58毫秒打印機(jī),m=1即可
     xL 為寬度/256的余數(shù),由于橫向點(diǎn)數(shù)計(jì)算為像素?cái)?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 獲取圖片點(diǎn)陣圖數(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ù)像素點(diǎn)來逐行打印,因此數(shù)據(jù)量會(huì)遠(yuǎn)高于普通文字,這就造成了打印圖片的速度回比文字慢,尤其是藍(lán)牙打印機(jī)。解決方法可以從兩個(gè)方面入手,1、增加每次發(fā)送的數(shù)據(jù)量(主要針對(duì)藍(lán)牙打印機(jī));2、減少圖片的數(shù)據(jù)量。

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

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

- (void)printLongData:(NSData *)printContent{
    NSUInteger cellMin;
    NSUInteger cellLen;
    //數(shù)據(jù)長(zhǎng)度
    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是個(gè)宏定義,表示每次發(fā)送的數(shù)據(jù)長(zhǎng)度,經(jīng)筆者測(cè)試,當(dāng)MAX_CHARACTERISTIC_VALUE_SIZE = 20時(shí),打印文字是正常速度。但打印圖片的速度非常慢,應(yīng)該在硬件允許的范圍內(nèi),每次發(fā)盡量多的數(shù)據(jù)。不同品牌型號(hào)的打印機(jī),這個(gè)參數(shù)是不同的,筆者的藍(lán)牙打印機(jī)該值最多到140。超出后會(huì)出現(xiàn)無法打印問題。最后筆者將該值定為MAX_CHARACTERISTIC_VALUE_SIZE = 120,測(cè)試了公司幾臺(tái)打印機(jī)都沒有問題。

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

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

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

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

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

調(diào)用時(shí):

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

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

使用舉例

示例代碼

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

    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:@"價(jià)格"];
    
    [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:@"總計(jì):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打印命令集,拼接打印指令,排版打印格式。接下來的文章會(huì)介紹如何通過藍(lán)牙或Socket將我們編輯的打印數(shù)據(jù)發(fā)送給打印機(jī)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容