第一篇文章吧,少BB直接开始吧。
先直接上结果图:
先说需求吧,公司要求订单推送到商户端 APP,能够同步打印出小票。然后领导的要求能够适配市面上大多数蓝牙热敏打印机,然后就采购了一批打印机,什么得力,芯华,美达罗捷,佳博等等一共八牌子的,反正我都没听过,都是便宜货你懂的。
先说通常的思路吧,我在网上搜了一堆资料,多数的 Demo 和思路是,数据到客户端,然后在端完成数据的排版,然后通过蓝牙 socket 发送打印命令给打印机完成打印。这里就不再介绍关于客户端蓝牙链接和打印命令的东西了,有大神都整理的非常清楚。下面贴出来我觉的比较好的。
ios 端:https://www.jianshu.com/p/2d624044a27b
https://www.jianshu.com/p/90cc08d11b5a
android 端:https://www.jianshu.com/p/c0b6d1a4823b
这里我发现 ios 端大神写的 Demo 有些适配的问题:\x1d\x21 对应的 ascii 吗是GS! 不是 对所有的打印机都适配的。多数的做法是用 \x1b\x21。这里涉及的计算机一些基础的字节知识:诸如16进制,2进制,8进制,高位地位,ascii 码以及之间的各种转换。就不去科普了。我也是再这次工作中从头学过的。
再说我们的思路吧:
上面的思路肯定是没问题的了,但是面临一个问题就是,如果产品的需求变更,要求的小票的样式更改,或者要求打各种样式,那问题就来了,需要对客户端进行再次开发升级发包。我们的思路是通过服务端生成好的打印命令传输给客户端,然后android 和 ios 只要忠实的执行写入命令就行了 以后产品如果想改小票样式 或再变态的需求。我只要在服务端更改就可以了,两个客户端不用动弹,产品的天马行空,各位程序员都懂的。
开始我是打算自己封装一个 php 打印类写了,做了开始,在 github 发现有第三方的。
github地址:https://github.com/mike42/escpos-php 这个非常全,各种打印,想怎么打怎么打。
我自己封装的 php:
// 字体大小
const JsonLi_FontSizeSmall = "\x00";
const JsonLi_FontSizeMiddle = "\x11";
const JsonLi_FontSizeBig = "\x22";
// 对齐方式
const JsonLi_Left = "\x00";
const JsonLi_Center = "\x01";
const JsonLi_Right = "\x02";
const JsonLi_WIDTH_PIXEL = 384;
const JsonLi_IMAGE_SIZE = 320;
class PrintDeviceUtil
{
public $printWhiteString;
function __construct() {
$this->initPrintDevice();
}
/**
* 初始化打印机
*/
protected function initPrintDevice(){
// $this->printWhiteString = "\x1b\x40\x1b\x32\x1b\x4d\x00";
$this->printWhiteString = "\x1b\x40";
}
/**
* 设置字体大小
*/
protected function setTextFont($size) {
$this->printWhiteString .= "\x1d" . "\x21" . $size;
}
/**
* 设置对齐方式
*/
protected function setAlignment($alignment) {
$this->printWhiteString .= "\x1b" . "\x61" . $alignment;
}
/**
* 换一行
*/
protected function addNewLine() {
$this->addMuiltNewLine(1);
}
/**
* 换多行
*/
protected function addMuiltNewLine($linenum) {
for ($i = 0; $i < $linenum; $i++) {
$this->printWhiteString .= "\x0A";
}
}
/*
* 添加分割线
*/
protected function addDividingLine ()
{
$this->setAlignment(JsonLi_Center);
$this->setTextFont(JsonLi_FontSizeSmall);
$this->printWhiteString .= "- - - - - - - - - - - - - - - -";
$this->addNewLine();
}
/**
* 设置文本
*/
protected function setText($text) {
$this->printWhiteString .= iconv("UTF-8","GB2312//IGNORE",$text);
}
/*
* 获取偏移量
*/
protected function getOffset($str) {
return JsonLi_WIDTH_PIXEL - mb_strwidth($str);
}
/*
* 设置偏移量
*/
protected function setOffset($str) {
$offset = $this->getOffset($str);
$remainder = $offset % 256; // 低位
$consult = $offset >> 8; // 高位
$offsetBytes = "\x1B\x24" . chr($remainder) . chr($consult);
$this->printWhiteString .= $offsetBytes;
}
public function testPrint(){
$this->setAlignment(JsonLi_Center);
$this->setTextFont(JsonLi_FontSizeBig);
$this->setText("泛客云商");
$this->addNewLine();
$this->addDividingLine();
$this->setAlignment(JsonLi_Left);
$this->setTextFont(JsonLi_FontSizeSmall);
$this->setText("时间:");
$value = "2018-01-01";
$this->setOffset($value);
$this->setText($value);
$this->addNewLine();
$this->setAlignment(JsonLi_Left);
$this->setTextFont(JsonLi_FontSizeSmall);
$this->setText("订单号:");
$this->setAlignment(JsonLi_Right);
$this->setTextFont(JsonLi_FontSizeSmall);
$this->setText("XXXXXXXXXXX");
$this->addNewLine();
$this->setAlignment(JsonLi_Left);
$this->setTextFont(JsonLi_FontSizeSmall);
$this->setText("付款人:");
$this->setAlignment(JsonLi_Right);
$this->setTextFont(JsonLi_FontSizeSmall);
$this->setText("XXXXXXXXXXX");
$this->addNewLine();
$this->addDividingLine();
$this->addMuiltNewLine(3);
// $result = $this->printWhiteString . iconv("UTF-8","GB2312//IGNORE","泛客云商") ;
// $printWhiteString = $printWhiteString . "测试测试";
//return $this->printWhiteString;
return base64_encode($this->printWhiteString);
// return strlen("我爱上海");
// $hex = '';
// for ($i = 0; $i < strlen($this->printWhiteString); $i++) {
// $hex .= sprintf("%02x", ord($this->printWhiteString[$i]));
// }
//
// $hex .= PHP_EOL;
//
// return $hex;
// return $this->printWhiteString;
}
}
$p = new PrintDeviceUtil;
echo $p->testPrint();
使用第三方封装的:
<?php
require __DIR__ . '/vendor/autoload.php';
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
use Mike42\Escpos\Printer;
use Mike42\Escpos\EscposImage;
class BTPrinter extends Mike42\Escpos\Printer
{
const kPageWidth = 32;
protected $textWidth;
protected $bufferFile;
public function __construct()
{
//创建临时文件
$this->bufferFile = tempnam('/tmp', 'Printer');
$this->textWidth = static::kPageWidth;
//连接临时文件作为打印缓存
$connector = new FilePrintConnector($this->bufferFile);
parent::__construct($connector);
//程序结束后删除临时文件
register_shutdown_function(function () {
unlink($this->bufferFile);
});
}
/**
* @return int
*/
public function getTextWidth()
{
return $this->textWidth;
}
public function initialize()
{
parent::initialize();
$this->textWidth = static::kPageWidth;
}
public function setTextSize($widthMultiplier = 1, $heightMultiplier = 0)
{
if ($heightMultiplier === 0) {
$heightMultiplier = $widthMultiplier;
}
$this->textWidth = floor(static::kPageWidth / $widthMultiplier);
parent::setTextSize($widthMultiplier, $heightMultiplier);
}
public function text($str = "", $newline = false)
{
parent::text($str);
if ($newline) {
$this->newline();
}
}
public function textChinese($str = "", $newline = false)
{
parent::textChinese($str);
if ($newline) {
$this->newline();
}
}
public function textLabel($label, $text)
{
$space = $this->textWidth - mb_strwidth($label);
$this->textChinese($label);
if (mb_strwidth($text) > $space) {
$this->newline();
} else {
$text = mb_str_pad($text, $space, ' ', STR_PAD_LEFT);
}
$this->textChinese($text, true);
}
public function writeRaw($text)
{
$this->connector->write($text);
}
public function separator($sep = '-', $text = '')
{
$this->textChinese(mb_str_pad($text, $this->textWidth, $sep, STR_PAD_BOTH), true);
}
public function newline()
{
$this->text("\n");
}
public function flush()
{
$this->close();
return file_get_contents($this->bufferFile);
}
public function flushBase64()
{
return base64_encode($this->flush());
}
public function flushHex()
{
return bin2hex($this->flush());
}
}
if (!function_exists('mb_sprintf')) {
function mb_sprintf($format)
{
$argv = func_get_args() ;
array_shift($argv) ;
return mb_vsprintf($format, $argv) ;
}
}
if (!function_exists('mb_vsprintf')) {
/**
* Works with all encodings in format and arguments.
* Supported: Sign, padding, alignment, width and precision.
* Not supported: Argument swapping.
*/
function mb_vsprintf($format, $argv, $encoding=null)
{
if (is_null($encoding))
$encoding = mb_internal_encoding();
// Use UTF-8 in the format so we can use the u flag in preg_split
$format = mb_convert_encoding($format, 'UTF-8', $encoding);
$newformat = ""; // build a new format in UTF-8
$newargv = array(); // unhandled args in unchanged encoding
while ($format !== "") {
// Split the format in two parts: $pre and $post by the first %-directive
// We get also the matched groups
list ($pre, $sign, $filler, $align, $size, $precision, $type, $post) =
preg_split("!\%(\+?)('.|[0 ]|)(-?)([1-9][0-9]*|)(\.[1-9][0-9]*|)([%a-zA-Z])!u",
$format, 2, PREG_SPLIT_DELIM_CAPTURE) ;
$newformat .= mb_convert_encoding($pre, $encoding, 'UTF-8');
if ($type == '') {
// didn't match. do nothing. this is the last iteration.
}
elseif ($type == '%') {
// an escaped %
$newformat .= '%%';
}
elseif ($type == 's') {
$arg = array_shift($argv);
$arg = mb_convert_encoding($arg, 'UTF-8', $encoding);
$padding_pre = '';
$padding_post = '';
// truncate $arg
if ($precision !== '') {
$precision = intval(substr($precision,1));
if ($precision > 0 && mb_strwidth($arg, $encoding) > $precision)
$arg = mb_substr($precision,0,$precision,$encoding);
}
// define padding
if ($size > 0) {
$arglen = mb_strwidth($arg, $encoding);
if ($arglen < $size) {
if($filler==='')
$filler = ' ';
if ($align == '-')
$padding_post = str_repeat($filler, $size - $arglen);
else
$padding_pre = str_repeat($filler, $size - $arglen);
}
}
// escape % and pass it forward
$newformat .= $padding_pre . str_replace('%', '%%', $arg) . $padding_post;
}
else {
// another type, pass forward
$newformat .= "%$sign$filler$align$size$precision$type";
$newargv[] = array_shift($argv);
}
$format = strval($post);
}
// Convert new format back from UTF-8 to the original encoding
$newformat = mb_convert_encoding($newformat, $encoding, 'UTF-8');
return vsprintf($newformat, $newargv);
}
}
if (!function_exists('mb_str_pad')) {
function mb_str_pad($str, $pad_length, $pad_string, $pad_type = STR_PAD_RIGHT, $encoding = null)
{
if ($encoding === null) {
$encoding = mb_internal_encoding();
}
$w = mb_strwidth($str, $encoding);
$len = strlen($str);
$pad_length += $len - $w;
return str_pad($str, $pad_length, $pad_string, $pad_type);
}
}
/**
* 服务端以下代码放到 Ctroller 里面。
* 初始化BTPrinter得到$printer对象,排版填充数据后调用$printer->flushBase64()方法得到 base64数据,api返回给客户端即可。
* (注(服务端不用关心这句):$printer->flush 返回的是byte 数据,客户端经过Base64.decode即是该数据,通过bluesocket 发送打印机执行打印)
* json_li 2018-1-10 12:01
*/
/*
* 附录:下面的代码如下打印样式:
* &----------泛客云商订单----------.
&窗边小豆豆的奶茶铺子.
!&【外卖】.
!&--------------------------------.
&下单时间:.& 2018-01-10 12:00:06.
&订单编号:.& BU20171829UUKL.
&付款人:.& cfgxy.
&--------------------------------.
&商品 数量 单价.
&苹果 2 10.02.
&香蕉 3 9.03.
&商品名称比较长比较长比较长比较长长比较长长比较长长比较长长比较长长长长长长长长长.
& 1 10.00.
&--------------------------------.
&名称1:.& value1.
&--------------------------------.
&名称2:.& value2.
&--------------------------------.
&名称3:.& value3.
&--------------------------------.
!&地址:合肥市蜀山区怀宁路888号天睿大厦万家热线菜鸟级工程师json_li测试打印比较长的地址会怎样 so?fucking you!.
!&------------订单结束------------.
*/
$printer = new BTPrinter();
$printer->initialize();//打印机初始化
//Section: 表头
$printer->separator('-', '商品订单');
//Section: 店铺名
$line = '窗边小豆豆的奶茶铺子';
$printer->setJustification(Printer::JUSTIFY_CENTER);
$printer->textChinese($line, true);
//大字
$printer->setTextSize(2);
//Section: 外卖 / 自提
$line = '【外卖】';
$printer->textChinese($line, true);
//恢复默认
$printer->setTextSize();
$printer->setJustification();
$printer->separator('-');
//Section: 下单时间
$printer->textLabel('下单时间:', date('Y-m-d H:i:s'));
//Section: 订单编号
$printer->textLabel('订单编号:', "BU20171829UUKL");
//Section: 付款人
$printer->textLabel('付款人:', " cfgxy");
$printer->separator('-');
//Section: 表头
$printer->textChinese(mb_sprintf("%-16s%-6s%10s", "商品", "数量", "单价"), true);
//Section: 商品列表
$list = [
[
'name' => '苹果',
'amount' => 2,
'price' => number_format(10.023, 2)
],
[
'name' => '香蕉',
'amount' => 3,
'price' => number_format(9.026, 2)
],
[
'name' => '商品名称比较长比较长比较长比较长长比较长长比较长长比较长长比较长长长长长长长长长',
'amount' => 1,
'price' => number_format(10, 2)
]
];
foreach ($list as $item) {
if (mb_strwidth($item['name']) > 16) {
$printer->textChinese($item['name'], true);
$printer->textChinese(mb_sprintf("%-16s%-6d%10s", "", $item['amount'], $item['price']), true);
} else {
$printer->textChinese(mb_sprintf("%-16s%-6d%10s", $item['name'], $item['amount'], $item['price']), true);
}
}
$printer->separator('-');
//Section: 两列
$printer->textLabel('名称1:', "value1");
$printer->separator('-');
//Section: 两列
$printer->textLabel('名称2:', "value2");
$printer->separator('-');
//Section: 两列
$printer->textLabel('名称3:', "value3");
$printer->separator('-');
//Section: 地址
$printer->setTextSize(2);//大字
$line = '地址:合肥市菜鸟级工程师Jason测试打印比较长的地址会怎样长长长长长长长长长长长长长长长长长长长长长长长长 so?fucking you!';
$printer->setJustification(Printer::JUSTIFY_CENTER);
$printer->textChinese($line, true);
//恢复默认
$printer->setTextSize();
$printer->setJustification();
//Section: 表尾
$printer->separator('-', '订单结束');
$printer->feed(2);
// 切纸(多数大打印机不支持)
$printer->cut();
echo $printer->flushBase64();
ios 端我是抄上面那位大神的,稍作修改而已,他写的非常全。这里不赘述了。
还要板砖, 先写这里,方法和思路就这样。有问题的可以多沟通。
哦,最后分享一个今天发现一个很全的 linux 命令的网站: