├── image-ports ├── tulips.png ├── drivers │ ├── python-escpos.py │ └── escpos-php.php ├── benchmark │ ├── plot.gnuplot │ ├── README.md │ ├── test_all.sh │ └── test_image.sh ├── rasterFormat.php ├── rasterFormat.py ├── columnFormat.php └── columnFormat.py ├── unifont-bitmap-font ├── test.bin ├── README.md ├── test.txt └── unifont-example.php ├── js-binary-websocket-demo ├── receipt-with-logo.bin ├── README.md └── page.html ├── parser └── README.md ├── capability-db └── README.md ├── chrome-escpos-receipt └── README.md ├── .gitmodules ├── README.md └── LICENSE.md /image-ports/tulips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/escpos-snippets/HEAD/image-ports/tulips.png -------------------------------------------------------------------------------- /unifont-bitmap-font/test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/escpos-snippets/HEAD/unifont-bitmap-font/test.bin -------------------------------------------------------------------------------- /unifont-bitmap-font/README.md: -------------------------------------------------------------------------------- 1 | # Unifont output for ESC/POS printers 2 | 3 | Testing new text rendering techniques for receipt printers. 4 | 5 | -------------------------------------------------------------------------------- /js-binary-websocket-demo/receipt-with-logo.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mike42/escpos-snippets/HEAD/js-binary-websocket-demo/receipt-with-logo.bin -------------------------------------------------------------------------------- /parser/README.md: -------------------------------------------------------------------------------- 1 | This snippet has now been moved to its own repository: [receipt-print-hq/escpos-tools](https://github.com/receipt-print-hq/escpos-tools) 2 | -------------------------------------------------------------------------------- /capability-db/README.md: -------------------------------------------------------------------------------- 1 | This snippet has now been moved to its own repository: [receipt-print-hq/escpos-printer-db](https://github.com/receipt-print-hq/escpos-printer-db) 2 | 3 | -------------------------------------------------------------------------------- /chrome-escpos-receipt/README.md: -------------------------------------------------------------------------------- 1 | This snippet has now been moved to its own repository: [receipt-print-hq/chrome-raw-print](https://github.com/receipt-print-hq/chrome-raw-print) 2 | -------------------------------------------------------------------------------- /js-binary-websocket-demo/README.md: -------------------------------------------------------------------------------- 1 | # js-binary-websocket-demo 2 | 3 | Minimal example of the use of Javascript to retrieve a binary ESC/POS file, and pass it to a websocket. 4 | 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "drivers/escpos-php"] 2 | path = drivers/escpos-php 3 | url = https://github.com/mike42/escpos-php 4 | [submodule "drivers/python-escpos"] 5 | path = drivers/python-escpos 6 | url = https://github.com/mike42/python-escpos 7 | -------------------------------------------------------------------------------- /image-ports/drivers/python-escpos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | sys.path.append('../../drivers/python-escpos') 4 | from escpos import printer 5 | device = printer.File("/dev/stdout") 6 | 7 | if len(sys.argv) > 1: 8 | filename = sys.argv[1] 9 | else: 10 | filename = u"../tulips.png" 11 | device.image(filename) 12 | -------------------------------------------------------------------------------- /image-ports/benchmark/plot.gnuplot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/gnuplot -persist 2 | set term pdf size 15cm,20cm 3 | set output 'plot.pdf' 4 | 5 | set key inside left top vertical Right noreverse enhanced autotitle box 6 | set title "Time to generate output by language, format" 7 | set xlabel "PNG image bytes" 8 | set ylabel "Seconds" 9 | 10 | set datafile separator "," 11 | set log x 12 | set log y 13 | plot for [i=5:4+n] 'out.csv' u 2:i title columnheader(i) ps 0.5 14 | 15 | -------------------------------------------------------------------------------- /image-ports/benchmark/README.md: -------------------------------------------------------------------------------- 1 | Escpos image benchmarks 2 | ----------------------- 3 | 4 | Compare performance of image implementations over a test set. 5 | 6 | Eg, [this collection of sample images](http://people.sc.fsu.edu/~jburkardt/data/png/png.html) can be tested like so: 7 | 8 | wget --recursive --level=1 --accept png --no-clobber --no-host-directories --no-directories http://people.sc.fsu.edu/~jburkardt/data/png/ 9 | ./test_all.sh 10 | 11 | The output is then saved to 'out.csv' and 'plot.pdf'. 12 | 13 | -------------------------------------------------------------------------------- /image-ports/benchmark/test_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Header 3 | echo "file,bytes,width,height,PHP columnFormat,PHP rasterFormat,Python columnFormat,Python rasterFormat,escpos-php,python-escpos" > out.csv 4 | 5 | # nCPUs minus 1 so that we aren't here all week 6 | CONCURRENCY=$((`getconf _NPROCESSORS_ONLN`-1)) 7 | 8 | # Execute w/ progress bar 9 | find . -name '*.png' -print0 | parallel --no-notice --bar -0 -P 7 --no-run-if-empty --max-args=1 ./test_image.sh 10 | 11 | # Plot everything 12 | cols=6 13 | gnuplot -e "n=$cols" plot.gnuplot 14 | -------------------------------------------------------------------------------- /image-ports/drivers/escpos-php.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | bitImage($im); 16 | } catch(Exception $e) { 17 | /* Images not supported on your PHP, or image file not found */ 18 | $printer -> text($e -> getMessage() . "\n"); 19 | } 20 | 21 | $printer -> close(); 22 | ?> 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | escpos-snippets 2 | --------------- 3 | 4 | This repo is for prototyping improvements for receipt print drivers: [escpos-php](https://github.com/mike42/escpos-php), [python-escpos](https://github.com/python-escpos/python-escpos). Pull requests are welcome for speed improvements, new file formats, or ports of these snippets to new languages. 5 | 6 | If you aren't developing a driver, imagesetter or CUPS filter, then please check out the linked projects instead. 7 | 8 | ## Licensing 9 | The code in this repository is licensed under the MIT Licence, and may be incorportated into LGPL or GPL works. See LICENSE.md for the full text. 10 | 11 | The tulip test image is sourced from this set of [Public-Domain Test Images](https://homepages.cae.wisc.edu/~ece533/images/). 12 | 13 | -------------------------------------------------------------------------------- /image-ports/benchmark/test_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function runtime { 4 | # Return runtime of a process in seconds 5 | >&2 echo -- $@ 6 | a=`mktemp` 7 | >&2 /usr/bin/time --quiet --format='%e' --output=$a $@ > /dev/null 8 | ret=$? 9 | num=`cat $a | tr -d '\n'` 10 | echo -n $num 11 | } 12 | 13 | size=`stat --printf="%s" "$1"` 14 | dimensions=`identify -format "%w,%h" "$1"` 15 | t1=`runtime php ../columnFormat.php "$1"` 16 | t2=`runtime php ../rasterFormat.php "$1"` 17 | t3=`runtime python3 ../columnFormat.py "$1"` 18 | t4=`runtime python3 ../rasterFormat.py "$1"` 19 | t5=`runtime php ../drivers/escpos-php.php "$1"` 20 | t6=`runtime python3 ../drivers/python-escpos.py "$1"` 21 | 22 | echo $1,$size,$dimensions,$t1,$t2,$t3,$t4,$t5,$t6 >> out.csv 23 | 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-16 Michael Billington `< michael.billington@gmail.com >` 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the “Software”), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /image-ports/rasterFormat.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | setFormat('pbm'); 24 | $blob = $im -> getImageBlob(); 25 | $i = strpos($blob, "\n", 3); // Find where header ends 26 | return substr($blob, $i + 1); 27 | } 28 | 29 | /** 30 | * Generate number as ESC/POS image header 31 | * 32 | * @param int $input 33 | * number 34 | * @param int $length 35 | * number of bytes output 36 | * @return string 37 | */ 38 | function intLowHigh($input, $length) { 39 | $outp = ""; 40 | for($i = 0; $i < $length; $i ++) { 41 | $outp .= chr ( $input % 256 ); 42 | $input = ( int ) ($input / 256); 43 | } 44 | return $outp; 45 | } 46 | 47 | // Configure 48 | $highDensityHorizontal = true; 49 | $highDensityVertical = true; 50 | $filename = isset($argv[1]) ? $argv[1] : 'tulips.png'; 51 | 52 | // Load image 53 | $im = new Imagick(); 54 | $im->setResourceLimit ( 6, 1 ); // Prevent libgomp1 segfaults, grumble grumble. 55 | $im -> readimage($filename); 56 | 57 | // Print 58 | $GS = "\x1d"; 59 | $widthBytes = (int)(($im -> getimagewidth() + 7) / 8); 60 | $heightPixels = $im -> getimageheight(); 61 | $densityByte = ($highDensityHorizontal ? 0 : 1) + ($highDensityVertical ? 0 : 2); 62 | $header = $GS . "v0" . chr($densityByte) . intLowHigh($widthBytes, 2) . intLowHigh($heightPixels, 2); 63 | echo $header . toRasterFormat($im); 64 | -------------------------------------------------------------------------------- /image-ports/rasterFormat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This is a minimal ESC/POS printing script which uses the 'raster format' 4 | of image output. 5 | 6 | The snippet is designed to efficiently delegate image processing to 7 | PIL, rather than spend CPU cycles looping over pixels. 8 | 9 | Do not attempt to use this snippet in production, get a copy of python-escpos instead! 10 | """ 11 | 12 | from PIL import Image, ImageOps 13 | import six 14 | import sys 15 | import os 16 | 17 | def _to_raster_format(im): 18 | """ Convert image to raster-format binary 19 | 20 | : param im: Input image 21 | """ 22 | # Convert down to greyscale 23 | im = im.convert("L") 24 | # Invert: Only works on 'L' images 25 | im = ImageOps.invert(im) 26 | # Pure black and white 27 | im = im.convert("1") 28 | return im.tobytes() 29 | 30 | def _int_low_high(inp_number, out_bytes): 31 | """ Generate multiple bytes for a number: In lower and higher parts, or more parts as needed. 32 | 33 | :param inp_number: Input number 34 | :param out_bytes: The number of bytes to output (1 - 4). 35 | """ 36 | max_input = (256 << (out_bytes * 8) - 1); 37 | if not 1 <= out_bytes <= 4: 38 | raise ValueError("Can only output 1-4 byes") 39 | if not 0 <= inp_number <= max_input: 40 | raise ValueError("Number too large. Can only output up to {0} in {1} byes".format(max_input, out_bytes)) 41 | outp = b''; 42 | for _ in range(0, out_bytes): 43 | outp += six.int2byte(inp_number % 256) 44 | inp_number = inp_number // 256 45 | return outp 46 | 47 | if __name__ == "__main__": 48 | # Configure 49 | high_density_horizontal = True 50 | high_density_vertical = True 51 | if len(sys.argv) > 1: 52 | filename = sys.argv[1] 53 | else: 54 | filename = u"tulips.png" 55 | 56 | # Load Image 57 | im = Image.open(filename) 58 | 59 | # Print 60 | GS = b"\x1d"; 61 | width, height_pixels = im.size 62 | width_bytes = (int)((width + 7) / 8); 63 | density_byte = (0 if high_density_vertical else 1) + (0 if high_density_horizontal else 2); 64 | header = GS + b"v0" + six.int2byte(density_byte) + _int_low_high(width_bytes, 2) + _int_low_high(height_pixels, 2); 65 | 66 | with os.fdopen(sys.stdout.fileno(), 'wb') as fp: 67 | fp.write(header + _to_raster_format(im)); 68 | -------------------------------------------------------------------------------- /unifont-bitmap-font/test.txt: -------------------------------------------------------------------------------- 1 | Afrikaans 2 | Alemannisch 3 | አማርኛ 4 | Ænglisc 5 | العربية 6 | Aragonés 7 | ܐܪܡܝܐ 8 | Armãneashti 9 | Arpetan 10 | Asturianu 11 | Avañe'ẽ 12 | Azərbaycanca 13 | تۆرکجه 14 | বাংলা 15 | Bân-lâm-gú 16 | Башҡортса 17 | Беларуская 18 | Беларуская (тарашкевіца) 19 | भोजपुरी 20 | Български 21 | Boarisch 22 | Bosanski 23 | Brezhoneg 24 | Буряад 25 | Català 26 | Čeština 27 | Corsu 28 | Cymraeg 29 | Dansk 30 | Deitsch 31 | Deutsch 32 | Diné bizaad 33 | Eesti 34 | Ελληνικά 35 | Español 36 | Esperanto 37 | Euskara 38 | فارسی 39 | Fiji Hindi 40 | Føroyskt 41 | Français 42 | Frysk 43 | Gaeilge 44 | Gaelg 45 | Galego 46 | 贛語 47 | گیلکی 48 | ગુજરાતી 49 | 한국어 50 | Hausa 51 | Հայերեն 52 | हिन्दी 53 | Hrvatski 54 | Ido 55 | Bahasa Indonesia 56 | Interlingua 57 | Interlingue 58 | Ирон 59 | Íslenska 60 | Italiano 61 | עברית 62 | Basa Jawa 63 | ಕನ್ನಡ 64 | ქართული 65 | Kaszëbsczi 66 | Қазақша 67 | Kiswahili 68 | Kongo 69 | Kreyòl ayisyen 70 | Кыргызча 71 | Ladino 72 | Лезги 73 | ລາວ 74 | Latina 75 | Latviešu 76 | Lëtzebuergesch 77 | Lietuvių 78 | Lingála 79 | Livvinkarjala 80 | Lumbaart 81 | Magyar 82 | Македонски 83 | Malagasy 84 | മലയാളം 85 | Malti 86 | Māori 87 | मराठी 88 | مصرى 89 | Bahasa Melayu 90 | Mìng-dĕ̤ng-ngṳ̄ 91 | Монгол 92 | မြန်မာဘာသာ 93 | Nāhuatl 94 | Dorerin Naoero 95 | Nederlands 96 | Nedersaksies 97 | Nēhiyawēwin / ᓀᐦᐃᔭᐍᐏᐣ 98 | नेपाल भाषा 99 | 日本語 100 | Нохчийн 101 | Norsk 102 | Norsk nynorsk 103 | Nouormand 104 | Occitan 105 | Oʻzbekcha/ўзбекча 106 | ਪੰਜਾਬੀ 107 | پنجابی 108 | Patois 109 | Piemontèis 110 | Polski 111 | Ποντιακά 112 | Português 113 | Qaraqalpaqsha 114 | Română 115 | Romani 116 | Runa Simi 117 | Русиньскый 118 | Русский 119 | Саха тыла 120 | Scots 121 | Seeltersk 122 | Shqip 123 | සිංහල 124 | Simple English 125 | سنڌي 126 | Slovenčina 127 | Slovenščina 128 | Словѣньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ 129 | Ślůnski 130 | Soomaaliga 131 | کوردی 132 | Српски / srpski 133 | Srpskohrvatski / српскохрватски 134 | Basa Sunda 135 | Suomi 136 | Svenska 137 | Tagalog 138 | தமிழ் 139 | తెలుగు 140 | ไทย 141 | Тоҷикӣ 142 | Tsetsêhestâhese 143 | Türkçe 144 | Тыва дыл 145 | ᨅᨔ ᨕᨘᨁᨗ 146 | Українська 147 | ئۇيغۇرچە / Uyghurche 148 | Vahcuengh 149 | Vèneto 150 | Vepsän kel’ 151 | Tiếng Việt 152 | Võro 153 | Walon 154 | 文言 155 | Winaray 156 | ייִדיש 157 | 粵語 158 | Zazaki 159 | Žemaitėška 160 | 中文 161 | Kabɩyɛ 162 | 163 | -------------------------------------------------------------------------------- /js-binary-websocket-demo/page.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |This snippet forwards raw data to a local websocket.
10 | 11 | 15 | 16 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /image-ports/columnFormat.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getimagewidth (); 24 | if ($imgWidth == $lineHeight) { 25 | // Return glob of this panel 26 | $blob = $im->getimageblob (); 27 | $i = strpos ( $blob, "\n", 3 ); 28 | return array ( 29 | substr ( $blob, $i + 1 ) 30 | ); 31 | } else { 32 | // Calculations 33 | $slicesLeft = ceil ( $imgWidth / $lineHeight / 2 ); 34 | $widthLeft = $slicesLeft * $lineHeight; 35 | $widthRight = $imgWidth - $widthLeft; 36 | // Slice up 37 | $left = $im->clone (); 38 | $left->extentimage ( $widthLeft, $left->getimageheight (), 0, 0 ); 39 | $right = $im->clone (); 40 | $right->extentimage ( $widthRight < $lineHeight ? $lineHeight : $widthRight, $right->getimageheight (), $widthLeft, 0 ); 41 | return array_merge ( toColumnFormat ( $left, $lineHeight ), toColumnFormat ( $right, $lineHeight ) ); 42 | } 43 | } 44 | 45 | /** 46 | * Generate number as ESC/POS image header 47 | * 48 | * @param int $input 49 | * number 50 | * @param int $length 51 | * number of bytes output 52 | * @return string 53 | */ 54 | function intLowHigh($input, $length) { 55 | $outp = ""; 56 | for($i = 0; $i < $length; $i ++) { 57 | $outp .= chr ( $input % 256 ); 58 | $input = ( int ) ($input / 256); 59 | } 60 | return $outp; 61 | } 62 | 63 | // Configure 64 | $highDensityHorizontal = true; 65 | $highDensityVertical = true; 66 | $filename = isset($argv[1]) ? $argv[1] : 'tulips.png'; 67 | 68 | // Load image 69 | $im = new Imagick (); 70 | $im->setResourceLimit ( 6, 1 ); // Prevent libgomp1 segfaults, grumble grumble. 71 | $im->readimage ( $filename ); 72 | $im-> setImageBackgroundColor('white'); 73 | $im-> setImageAlphaChannel(Imagick::ALPHACHANNEL_REMOVE); 74 | $im-> mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN); 75 | 76 | // Initial rotate. mirror, and extract blobs for each 8 or 24-pixel row 77 | $im->setformat ( 'pbm' ); 78 | $im->getimageblob (); // Forces 1-bit rendering now, so that subsequent operations are faster 79 | $im->rotateImage ( '#fff', 90.0 ); 80 | $im->flopImage (); 81 | $lineHeight = $highDensityVertical ? 3 : 1; 82 | $blobs = toColumnFormat ( $im, $lineHeight * 8 ); 83 | 84 | // Generate ESC/POS header and print image 85 | $ESC = "\x1b"; 86 | $widthPixels = $im->getimageheight (); 87 | $densityCode = ($highDensityHorizontal ? 1 : 0) + ($highDensityVertical ? 32 : 0); 88 | $header = $ESC . "*" . chr ( $densityCode ) . intLowHigh ( $widthPixels, 2 ); 89 | echo $ESC . "3" . chr ( 16 ); // Adjust line-feed size 90 | foreach ( $blobs as $blob ) { 91 | echo $header . $blob . "\n"; 92 | } 93 | echo $ESC . "2"; // Reset line-feed size 94 | -------------------------------------------------------------------------------- /image-ports/columnFormat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This is a minimal ESC/POS printing script which uses the 'column format' 4 | of image output. 5 | 6 | The snippet is designed to efficiently delegate image processing to 7 | PIL, rather than spend CPU cycles looping over pixels. 8 | 9 | Do not attempt to use this snippet in production, get a copy of python-escpos instead! 10 | """ 11 | 12 | from PIL import Image, ImageOps 13 | import six 14 | import sys 15 | import os 16 | 17 | def _to_column_format(im, line_height): 18 | """ 19 | Extract slices of an image as equal-sized blobs of column-format data. 20 | 21 | :param im: Image to extract from 22 | :param line_height: Printed line height in dots 23 | """ 24 | width_pixels, height_pixels = im.size 25 | top = 0 26 | left = 0 27 | blobs = [] 28 | while left < width_pixels: 29 | remaining_pixels = width_pixels - left 30 | box = (left, top, left + line_height, top + height_pixels) 31 | slice = im.transform((line_height, height_pixels), Image.EXTENT, box) 32 | bytes = slice.tobytes() 33 | blobs.append(bytes) 34 | left += line_height 35 | return blobs 36 | 37 | def _int_low_high(inp_number, out_bytes): 38 | """ Generate multiple bytes for a number: In lower and higher parts, or more parts as needed. 39 | 40 | :param inp_number: Input number 41 | :param out_bytes: The number of bytes to output (1 - 4). 42 | """ 43 | max_input = (256 << (out_bytes * 8) - 1); 44 | if not 1 <= out_bytes <= 4: 45 | raise ValueError("Can only output 1-4 byes") 46 | if not 0 <= inp_number <= max_input: 47 | raise ValueError("Number too large. Can only output up to {0} in {1} byes".format(max_input, out_bytes)) 48 | outp = b''; 49 | for _ in range(0, out_bytes): 50 | outp += six.int2byte(inp_number % 256) 51 | inp_number = inp_number // 256 52 | return outp 53 | 54 | if __name__ == "__main__": 55 | # Configure 56 | high_density_horizontal = True 57 | high_density_vertical = True 58 | if len(sys.argv) > 1: 59 | filename = sys.argv[1] 60 | else: 61 | filename = u"tulips.png" 62 | 63 | # Load Image 64 | im = Image.open(filename) 65 | 66 | # Initial rotate. mirror, and extract blobs for each 8 or 24-pixel row 67 | # Convert to black & white via greyscale (so that bits can be inverted) 68 | im = im.convert("L") # Invert: Only works on 'L' images 69 | im = ImageOps.invert(im) # Bits are sent with 0 = white, 1 = black in ESC/POS 70 | im = im.convert("1") # Pure black and white 71 | im = im.transpose(Image.ROTATE_270).transpose(Image.FLIP_LEFT_RIGHT) 72 | line_height = 3 if high_density_vertical else 1 73 | blobs = _to_column_format (im, line_height * 8); 74 | 75 | # Generate ESC/POS header and print image 76 | ESC = b"\x1b"; 77 | # Height and width refer to output size here, image is rotated in memory so coordinates are swapped 78 | height_pixels, width_pixels = im.size 79 | density_byte = (1 if high_density_horizontal else 0) + (32 if high_density_vertical else 0); 80 | header = ESC + b"*" + six.int2byte(density_byte) + _int_low_high( width_pixels, 2 ); 81 | 82 | with os.fdopen(sys.stdout.fileno(), 'wb') as fp: 83 | fp.write(ESC + b"3" + six.int2byte(16)); # Adjust line-feed size 84 | for blob in blobs: 85 | fp.write(header + blob + b"\n") 86 | fp.write(ESC + b"2"); # Reset line-feed size 87 | -------------------------------------------------------------------------------- /unifont-bitmap-font/unifont-example.php: -------------------------------------------------------------------------------- 1 | fp = fopen($filename, "wb+"); 55 | if ($this -> fp === false) { 56 | throw new Exception("Cannot initialise FilePrintConnector."); 57 | } 58 | } 59 | 60 | public function __destruct() 61 | { 62 | if ($this -> fp !== false) { 63 | trigger_error("Print connector was not finalized. Did you forget to close the printer?", E_USER_NOTICE); 64 | } 65 | } 66 | 67 | /** 68 | * Close file pointer 69 | */ 70 | public function finalize() 71 | { 72 | fclose($this -> fp); 73 | $this -> fp = false; 74 | } 75 | 76 | /* (non-PHPdoc) 77 | * @see PrintConnector::read() 78 | */ 79 | public function read($len) 80 | { 81 | return fread($this -> fp, $len); 82 | } 83 | 84 | /** 85 | * Write data to the file 86 | * 87 | * @param string $data 88 | */ 89 | public function write($data) 90 | { 91 | fwrite($this -> fp, $data); 92 | } 93 | } 94 | 95 | interface PrintBuffer 96 | { 97 | /** 98 | * Cause the buffer to send any partial input and wait on a newline. 99 | * If the printer is already on a new line, this does nothing. 100 | */ 101 | public function flush(); 102 | 103 | /** 104 | * Used by Escpos to check if a printer is set. 105 | */ 106 | public function getPrinter(); 107 | 108 | /** 109 | * Used by Escpos to hook up one-to-one link between buffers and printers. 110 | * 111 | * @param Escpos $printer New printer 112 | */ 113 | public function setPrinter(Printer $printer = null); 114 | 115 | /** 116 | * Accept UTF-8 text for printing. 117 | * 118 | * @param string $text Text to print 119 | */ 120 | public function writeText($text); 121 | 122 | /** 123 | * Accept 8-bit text in the current encoding and add it to the buffer. 124 | * 125 | * @param string $text Text to print, already the target encoding. 126 | */ 127 | public function writeTextRaw($text); 128 | } 129 | 130 | 131 | class Printer { 132 | /** 133 | * ASCII escape control character 134 | */ 135 | const ESC = "\x1b"; 136 | 137 | /** 138 | * Make a full cut, when used with Printer::cut 139 | */ 140 | const CUT_FULL = 65; 141 | 142 | /** 143 | * ASCII group separator control character 144 | */ 145 | const GS = "\x1d"; 146 | 147 | /** 148 | * Use Font A, when used with Printer::setFont 149 | */ 150 | const FONT_A = 0; 151 | 152 | /** 153 | * Use Font B, when used with Printer::setFont 154 | */ 155 | const FONT_B = 1; 156 | 157 | /** 158 | * Use Font C, when used with Printer::setFont 159 | */ 160 | const FONT_C = 2; 161 | 162 | 163 | /** 164 | * Use Font A, when used with Printer::selectPrintMode 165 | */ 166 | const MODE_FONT_A = 0; 167 | 168 | /** 169 | * Use Font B, when used with Printer::selectPrintMode 170 | */ 171 | const MODE_FONT_B = 1; 172 | 173 | /** 174 | * Use text emphasis, when used with Printer::selectPrintMode 175 | */ 176 | const MODE_EMPHASIZED = 8; 177 | 178 | /** 179 | * Use double height text, when used with Printer::selectPrintMode 180 | */ 181 | const MODE_DOUBLE_HEIGHT = 16; 182 | 183 | /** 184 | * Use double width text, when used with Printer::selectPrintMode 185 | */ 186 | const MODE_DOUBLE_WIDTH = 32; 187 | 188 | /** 189 | * Underline text, when used with Printer::selectPrintMode 190 | */ 191 | const MODE_UNDERLINE = 128; 192 | 193 | /** 194 | * @var PrintBuffer $buffer 195 | * The printer's output buffer. 196 | */ 197 | protected $buffer; 198 | 199 | /** 200 | * @var int $characterTable 201 | * Current character code table 202 | */ 203 | protected $characterTable; 204 | 205 | 206 | public function __construct(PrintConnector $connector) { 207 | /* Set connector */ 208 | $this -> connector = $connector; 209 | /* Set buffer */ 210 | $buffer = new UnicodePrintBuffer(); 211 | $this -> buffer = null; 212 | $this -> setPrintBuffer($buffer); 213 | $this -> initialize(); 214 | } 215 | 216 | /** 217 | * Close the underlying buffer. With some connectors, the 218 | * job will not actually be sent to the printer until this is called. 219 | */ 220 | public function close() 221 | { 222 | $this -> connector -> finalize(); 223 | } 224 | 225 | /** 226 | * Cut the paper. 227 | * 228 | * @param int $mode Cut mode, either Printer::CUT_FULL or Printer::CUT_PARTIAL. If not specified, `Printer::CUT_FULL` will be used. 229 | * @param int $lines Number of lines to feed 230 | */ 231 | public function cut($mode = Printer::CUT_FULL, $lines = 3) 232 | { 233 | // TODO validation on cut() inputs 234 | $this -> connector -> write(self::GS . "V" . chr($mode) . chr($lines)); 235 | } 236 | 237 | /** 238 | * @return number 239 | */ 240 | public function getCharacterTable() 241 | { 242 | return $this -> characterTable; 243 | } 244 | 245 | /** 246 | * @return PrintBuffer 247 | */ 248 | public function getPrintBuffer() 249 | { 250 | return $this -> buffer; 251 | } 252 | 253 | /** 254 | * @return PrintConnector 255 | */ 256 | public function getPrintConnector() 257 | { 258 | return $this -> connector; 259 | } 260 | 261 | /** 262 | * Add text to the buffer. 263 | * 264 | * Text should either be followed by a line-break, or feed() should be called 265 | * after this to clear the print buffer. 266 | * 267 | * @param string $str Text to print 268 | */ 269 | public function text($str = "") 270 | { 271 | $this -> buffer -> writeText((string)$str); 272 | } 273 | 274 | /** 275 | * Attach a different print buffer to the printer. Buffers are responsible for handling text output to the printer. 276 | * 277 | * @param PrintBuffer $buffer The buffer to use. 278 | * @throws InvalidArgumentException Where the buffer is already attached to a different printer. 279 | */ 280 | public function setPrintBuffer(PrintBuffer $buffer) 281 | { 282 | if ($buffer === $this -> buffer) { 283 | return; 284 | } 285 | if ($buffer -> getPrinter() != null) { 286 | throw new InvalidArgumentException("This buffer is already attached to a printer."); 287 | } 288 | if ($this -> buffer !== null) { 289 | $this -> buffer -> setPrinter(null); 290 | } 291 | $this -> buffer = $buffer; 292 | $this -> buffer -> setPrinter($this); 293 | } 294 | 295 | /** 296 | * Initialize printer. This resets formatting back to the defaults. 297 | */ 298 | public function initialize() 299 | { 300 | $this -> connector -> write(self::ESC . "@"); 301 | $this -> characterTable = 0; 302 | } 303 | 304 | public function selectUserDefinedCharacterSet($on = true) 305 | { 306 | $this -> connector -> write(self::ESC . "%". ($on ? chr(1) : chr(0))); 307 | } 308 | 309 | } 310 | 311 | class ColumnFormatGlyph { 312 | public $width; 313 | public $data; 314 | 315 | function segment(int $maxWidth) { 316 | if($this -> width <= $maxWidth) { 317 | return [$this]; 318 | } 319 | $dataChunks = str_split($this -> data, $maxWidth * 3); 320 | $ret = []; 321 | foreach($dataChunks as $chunk) { 322 | $g = new ColumnFormatGlyph(); 323 | $g -> data = $chunk; 324 | $g -> width = strlen($chunk) / 3; 325 | $ret[] = $g; 326 | } 327 | return $ret; 328 | } 329 | } 330 | 331 | interface ColumnFormatGlyphFactory { 332 | function getGlyph($codePoint); 333 | } 334 | 335 | class UnifontCodeSource implements ColumnFormatGlyphFactory { 336 | protected $unifontFile; 337 | 338 | public static function colFormat16(array $in) { 339 | // Map 16 x 16 bit unifont (32 bytes) to 16 x 24 ESC/POS column format image (48 bytes). 340 | return UnifontCodeSource::colFormat8($in, 2, 1) . UnifontCodeSource::colFormat8($in, 2, 2); 341 | } 342 | 343 | public static function colFormat8(array $in, $chars = 1, $idx = 1) { 344 | // Map 8 x 16 bit unifont (32 bytes) to 8 x 24 ESC/POS column format image (24 bytes). 345 | return implode([ 346 | chr( 347 | (($in[0 * $chars + $idx] & 0x80)) | 348 | (($in[1 * $chars + $idx] & 0x80) >> 1) | 349 | (($in[2 * $chars + $idx] & 0x80) >> 2) | 350 | (($in[3 * $chars + $idx] & 0x80) >> 3) | 351 | (($in[4 * $chars + $idx] & 0x80) >> 4) | 352 | (($in[5 * $chars + $idx] & 0x80) >> 5) | 353 | (($in[6 * $chars + $idx] & 0x80) >> 6) | 354 | (($in[7 * $chars + $idx] & 0x80) >> 7) 355 | ), 356 | chr( 357 | (($in[8 * $chars + $idx] & 0x80)) | 358 | (($in[9 * $chars + $idx] & 0x80) >> 1) | 359 | (($in[10 * $chars + $idx] & 0x80) >> 2) | 360 | (($in[11 * $chars + $idx] & 0x80) >> 3) | 361 | (($in[12 * $chars + $idx] & 0x80) >> 4) | 362 | (($in[13 * $chars + $idx] & 0x80) >> 5) | 363 | (($in[14 * $chars + $idx] & 0x80) >> 6) | 364 | (($in[15 * $chars + $idx] & 0x80) >> 7) 365 | ), 366 | chr(0), 367 | chr( 368 | (($in[0 * $chars + $idx] & 0x40) << 1) | 369 | (($in[1 * $chars + $idx] & 0x40)) | 370 | (($in[2 * $chars + $idx] & 0x40) >> 1) | 371 | (($in[3 * $chars + $idx] & 0x40) >> 2) | 372 | (($in[4 * $chars + $idx] & 0x40) >> 3) | 373 | (($in[5 * $chars + $idx] & 0x40) >> 4) | 374 | (($in[6 * $chars + $idx] & 0x40) >> 5) | 375 | (($in[7 * $chars + $idx] & 0x40) >> 6) 376 | ), 377 | chr( 378 | (($in[8 * $chars + $idx] & 0x40) << 1) | 379 | (($in[9 * $chars + $idx] & 0x40) >> 0) | 380 | (($in[10 * $chars + $idx] & 0x40) >> 1) | 381 | (($in[11 * $chars + $idx] & 0x40) >> 2) | 382 | (($in[12 * $chars + $idx] & 0x40) >> 3) | 383 | (($in[13 * $chars + $idx] & 0x40) >> 4) | 384 | (($in[14 * $chars + $idx] & 0x40) >> 5) | 385 | (($in[15 * $chars + $idx] & 0x40) >> 6) 386 | ), 387 | chr(0), 388 | chr( 389 | (($in[0 * $chars + $idx] & 0x20) << 2) | 390 | (($in[1 * $chars + $idx] & 0x20) << 1) | 391 | (($in[2 * $chars + $idx] & 0x20)) | 392 | (($in[3 * $chars + $idx] & 0x20) >> 1) | 393 | (($in[4 * $chars + $idx] & 0x20) >> 2) | 394 | (($in[5 * $chars + $idx] & 0x20) >> 3) | 395 | (($in[6 * $chars + $idx] & 0x20) >> 4) | 396 | (($in[7 * $chars + $idx] & 0x20) >> 5) 397 | ), 398 | chr( 399 | (($in[8 * $chars + $idx] & 0x20) << 2) | 400 | (($in[9 * $chars + $idx] & 0x20) << 1) | 401 | (($in[10 * $chars + $idx] & 0x20)) | 402 | (($in[11 * $chars + $idx] & 0x20) >> 1) | 403 | (($in[12 * $chars + $idx] & 0x20) >> 2) | 404 | (($in[13 * $chars + $idx] & 0x20) >> 3) | 405 | (($in[14 * $chars + $idx] & 0x20) >> 4) | 406 | (($in[15 * $chars + $idx] & 0x20) >> 5) 407 | ), 408 | chr(0), 409 | chr( 410 | (($in[0 * $chars + $idx] & 0x10) << 3) | 411 | (($in[1 * $chars + $idx] & 0x10) << 2) | 412 | (($in[2 * $chars + $idx] & 0x10) << 1) | 413 | (($in[3 * $chars + $idx] & 0x10)) | 414 | (($in[4 * $chars + $idx] & 0x10) >> 1) | 415 | (($in[5 * $chars + $idx] & 0x10) >> 2) | 416 | (($in[6 * $chars + $idx] & 0x10) >> 3) | 417 | (($in[7 * $chars + $idx] & 0x10) >> 4) 418 | ), 419 | chr( 420 | (($in[8 * $chars + $idx] & 0x10) << 3) | 421 | (($in[9 * $chars + $idx] & 0x10) << 2) | 422 | (($in[10 * $chars + $idx] & 0x10) << 1) | 423 | (($in[11 * $chars + $idx] & 0x10)) | 424 | (($in[12 * $chars + $idx] & 0x10) >> 1) | 425 | (($in[13 * $chars + $idx] & 0x10) >> 2) | 426 | (($in[14 * $chars + $idx] & 0x10) >> 3) | 427 | (($in[15 * $chars + $idx] & 0x10) >> 4) 428 | ), 429 | chr(0), 430 | chr( 431 | (($in[0 * $chars + $idx] & 0x08) << 4) | 432 | (($in[1 * $chars + $idx] & 0x08) << 3) | 433 | (($in[2 * $chars + $idx] & 0x08) << 2) | 434 | (($in[3 * $chars + $idx] & 0x08) << 1) | 435 | (($in[4 * $chars + $idx] & 0x08)) | 436 | (($in[5 * $chars + $idx] & 0x08) >> 1) | 437 | (($in[6 * $chars + $idx] & 0x08) >> 2) | 438 | (($in[7 * $chars + $idx] & 0x08) >> 3) 439 | ), 440 | chr( 441 | (($in[8 * $chars + $idx] & 0x08) << 4) | 442 | (($in[9 * $chars + $idx] & 0x08) << 3) | 443 | (($in[10 * $chars + $idx] & 0x08) << 2) | 444 | (($in[11 * $chars + $idx] & 0x08) << 1) | 445 | (($in[12 * $chars + $idx] & 0x08)) | 446 | (($in[13 * $chars + $idx] & 0x08) >> 1) | 447 | (($in[14 * $chars + $idx] & 0x08) >> 2) | 448 | (($in[15 * $chars + $idx] & 0x08) >> 3) 449 | ), 450 | chr(0), 451 | chr( 452 | (($in[0 * $chars + $idx] & 0x04) << 5) | 453 | (($in[1 * $chars + $idx] & 0x04) << 4) | 454 | (($in[2 * $chars + $idx] & 0x04) << 3) | 455 | (($in[3 * $chars + $idx] & 0x04) << 2) | 456 | (($in[4 * $chars + $idx] & 0x04) << 1) | 457 | (($in[5 * $chars + $idx] & 0x04)) | 458 | (($in[6 * $chars + $idx] & 0x04) >> 1) | 459 | (($in[7 * $chars + $idx] & 0x04) >> 2) 460 | ), 461 | chr( 462 | (($in[8 * $chars + $idx] & 0x04) << 5) | 463 | (($in[9 * $chars + $idx] & 0x04) << 4) | 464 | (($in[10 * $chars + $idx] & 0x04) << 3) | 465 | (($in[11 * $chars + $idx] & 0x04) << 2) | 466 | (($in[12 * $chars + $idx] & 0x04) << 1) | 467 | (($in[13 * $chars + $idx] & 0x04)) | 468 | (($in[14 * $chars + $idx] & 0x04) >> 1) | 469 | (($in[15 * $chars + $idx] & 0x04) >> 2) 470 | ), 471 | chr(0), 472 | chr( 473 | (($in[0 * $chars + $idx] & 0x02) << 6) | 474 | (($in[1 * $chars + $idx] & 0x02) << 5) | 475 | (($in[2 * $chars + $idx] & 0x02) << 4) | 476 | (($in[3 * $chars + $idx] & 0x02) << 3) | 477 | (($in[4 * $chars + $idx] & 0x02) << 2) | 478 | (($in[5 * $chars + $idx] & 0x02) << 1) | 479 | (($in[6 * $chars + $idx] & 0x02)) | 480 | (($in[7 * $chars + $idx] & 0x02) >> 1) 481 | ), 482 | chr( 483 | (($in[8 * $chars + $idx] & 0x02) << 6) | 484 | (($in[9 * $chars + $idx] & 0x02) << 5) | 485 | (($in[10 * $chars + $idx] & 0x02) << 4) | 486 | (($in[11 * $chars + $idx] & 0x02) << 3) | 487 | (($in[12 * $chars + $idx] & 0x02) << 2) | 488 | (($in[13 * $chars + $idx] & 0x02) << 1) | 489 | (($in[14 * $chars + $idx] & 0x02)) | 490 | (($in[15 * $chars + $idx] & 0x02) >> 1) 491 | ), 492 | chr(0), 493 | chr( 494 | (($in[0 * $chars + $idx] & 0x01) << 7) | 495 | (($in[1 * $chars + $idx] & 0x01) << 6) | 496 | (($in[2 * $chars + $idx] & 0x01) << 5) | 497 | (($in[3 * $chars + $idx] & 0x01) << 4) | 498 | (($in[4 * $chars + $idx] & 0x01) << 3) | 499 | (($in[5 * $chars + $idx] & 0x01) << 2) | 500 | (($in[6 * $chars + $idx] & 0x01) << 1) | 501 | (($in[7 * $chars + $idx] & 0x01)) 502 | ), 503 | chr( 504 | (($in[8 * $chars + $idx] & 0x01) << 7) | 505 | (($in[9 * $chars + $idx] & 0x01) << 6) | 506 | (($in[10 * $chars + $idx] & 0x01) << 5) | 507 | (($in[11 * $chars + $idx] & 0x01) << 4) | 508 | (($in[12 * $chars + $idx] & 0x01) << 3) | 509 | (($in[13 * $chars + $idx] & 0x01) << 2) | 510 | (($in[14 * $chars + $idx] & 0x01) >> 1) | 511 | (($in[15 * $chars + $idx] & 0x01)) 512 | ), 513 | chr(0) 514 | ]); 515 | } 516 | 517 | public function __construct() { 518 | $unifont = "/usr/share/unifont/unifont.hex"; 519 | $this -> unifontFile = explode("\n", file_get_contents($unifont)); 520 | } 521 | 522 | public function getGlyph($codePoint) { 523 | // Binary search for correct line. 524 | $min = 0; 525 | $max = count($this -> unifontFile) - 1; 526 | $foundId = 0; 527 | $m = 255; // Bias toward low side. 528 | while($min <= $max) { 529 | $thisCodePoint = hexdec(substr($this -> unifontFile[$m], 0, 4)); 530 | if($codePoint === $thisCodePoint) { 531 | $foundId = $m; 532 | break; 533 | } else if($codePoint < $thisCodePoint) { 534 | $max = $m - 1; 535 | } else { 536 | $min = $m + 1; 537 | } 538 | $m = floor(($min + $max) / 2); 539 | } 540 | $unifontLine = $this -> unifontFile[$foundId]; 541 | 542 | // Convert to column format 543 | $binStr = unpack("C*", pack("H*", substr($unifontLine, 5))); 544 | $bytes = count($binStr); 545 | if($bytes == 32) { 546 | $width = 16; 547 | $colFormat = UnifontCodeSource::colFormat16($binStr); 548 | } else if($bytes == 16) { 549 | $width = 8; 550 | $colFormat = UnifontCodeSource::colFormat8($binStr); 551 | } 552 | // Write to obj 553 | $glyph = new ColumnFormatGlyph(); 554 | $glyph -> width = $width; 555 | $glyph -> data = $colFormat; 556 | return $glyph; 557 | } 558 | 559 | } 560 | 561 | class FontMap { 562 | protected $printer; 563 | 564 | const MIN = 0x20; 565 | const MAX = 0x7E; 566 | const FONT_A_WIDTH = 12; 567 | const FONT_B_WIDTH = 9; 568 | 569 | // Map memory locations to code points 570 | protected $memory; 571 | 572 | // Map unicode code points to bytes 573 | protected $chars; 574 | 575 | // next available slot 576 | protected $next = 0; 577 | 578 | public function __construct(ColumnFormatGlyphFactory $glyphFactory, Printer $printer) { 579 | $this -> printer = $printer; 580 | $this -> glyphFactory = $glyphFactory; 581 | $this -> reset(); 582 | } 583 | 584 | public function cacheChars(array $codePoints) { 585 | // TODO flush existing cache to fill with these chars. 586 | } 587 | 588 | public function writeChar(int $codePoint) { 589 | if(!$this -> addChar($codePoint, true)) { 590 | throw new InvalidArgumentException("Code point $codePoint not available"); 591 | } 592 | $data = implode($this -> chars[$codePoint]); 593 | $this -> printer -> getPrintConnector() -> write($data); 594 | } 595 | 596 | public function reset() { 597 | $this -> chars = []; 598 | $this -> memory = array_fill(0, (FontMap::MAX - FontMap::MIN) + 1, -1); 599 | } 600 | 601 | public function occupied($id) { 602 | return $this -> memory[$id] !== -1; 603 | } 604 | 605 | public function evict($id) { 606 | if(!$this -> occupied($id)) { 607 | return true; 608 | } 609 | unset($this -> chars[$this -> memory[$id]]); 610 | $this -> memory[$id] = -1; 611 | return true; 612 | } 613 | 614 | public function addChar(int $codePoint, $evict = true) { 615 | if(isset($this -> chars[$codePoint])) { 616 | // Char already available 617 | return true; 618 | } 619 | // Get glyph 620 | $glyph = $this -> glyphFactory -> getGlyph($codePoint); 621 | $glyphParts = $glyph -> segment(self::FONT_B_WIDTH); 622 | //print_r($glyphParts); 623 | // 624 | // Clear count($glyphParts) of space from $start 625 | $start = $this -> next; 626 | $chars = []; 627 | $submit = []; 628 | for($i = 0; $i < count($glyphParts); $i++) { 629 | $id = ($this -> next + $i) % count($this -> memory); 630 | if($this -> occupied($id)) { 631 | if($evict){ 632 | $this -> evict($id); 633 | } else { 634 | return false; 635 | } 636 | } 637 | $thisChar = $id + self::MIN; 638 | $chars[] = chr($thisChar); 639 | $submit[$thisChar] = $glyphParts[$i]; 640 | } 641 | 642 | // Success in locating memory space, move along counters 643 | $this -> next = ($this -> next + count($glyphParts)) % count($this -> memory); 644 | $this -> submitCharsToPrinterFont($submit); 645 | $this -> memory[$start] = $codePoint; 646 | $this -> chars[$codePoint] = $chars; 647 | 648 | return true; 649 | } 650 | 651 | public function submitCharsToPrinterFont(array $chars) { 652 | ksort($chars); 653 | // TODO We can sort into batches of contiguous characters here. 654 | foreach($chars as $char => $glyph) { 655 | $verticalBytes = 3; 656 | $data = Printer::ESC . "&" . chr($verticalBytes) . chr($char) . chr($char) . chr($glyph -> width) . $glyph -> data; 657 | $this -> printer -> getPrintConnector() -> write($data); 658 | } 659 | } 660 | } 661 | 662 | class UnicodePrintBuffer implements PrintBuffer { 663 | protected $printer; 664 | protected $fontMap; 665 | protected $started = false; 666 | public function __construct() { 667 | $this -> unifont = new UnifontCodeSource(); 668 | } 669 | 670 | public function writeChar($codePoint) { 671 | if($codePoint == 10) { 672 | $this -> write("\n"); 673 | } else if($codePoint == 13) { 674 | // Ignore CR char 675 | } else { 676 | // Straight column-format prints 677 | $this -> fontMap -> writeChar($codePoint); 678 | } 679 | } 680 | 681 | public function writeText($text) 682 | { 683 | if(!$this -> started) { 684 | $mode = Printer::MODE_FONT_B | Printer::MODE_DOUBLE_HEIGHT | Printer::MODE_DOUBLE_WIDTH; 685 | $this -> printer -> getPrintConnector() -> write(Printer::ESC . "!" . chr($mode)); 686 | $this -> printer -> selectUserDefinedCharacterSet(true); 687 | } 688 | $chrArray = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY); 689 | $codePoints = array_map("IntlChar::ord", $chrArray); 690 | foreach($codePoints as $char) { 691 | $this -> writeChar($char); 692 | } 693 | } 694 | 695 | public function flush() 696 | { 697 | 698 | } 699 | 700 | public function setPrinter(Printer $printer = null) 701 | { 702 | $this -> printer = $printer; 703 | $this -> fontMap = new FontMap($this -> unifont, $this -> printer); 704 | } 705 | 706 | public function writeTextRaw($text) 707 | {} 708 | 709 | public function getPrinter() 710 | { 711 | return $this -> printer; 712 | } 713 | 714 | /** 715 | * Write data to the underlying connector. 716 | * 717 | * @param string $data 718 | */ 719 | private function write($data) 720 | { 721 | $this -> printer -> getPrintConnector() -> write($data); 722 | } 723 | } 724 | 725 | $text = file_get_contents("php://stdin"); 726 | $connector = new FilePrintConnector("php://stdout"); 727 | $printer = new Printer($connector); 728 | $printer -> text($text); 729 | $printer -> cut(); 730 | $printer -> close(); 731 | 732 | ?> --------------------------------------------------------------------------------