├── receipt-with-logo.bin ├── doc ├── receipt-with-logo-img.png ├── receipt-with-logo-html.png ├── receipt-with-logo-small.png ├── esc2html.md ├── esc2text.md └── escimages.md ├── src ├── Parser │ ├── Command │ │ ├── LineBreak.php │ │ ├── TextContainer.php │ │ ├── CancelCmd.php │ │ ├── Code2DDataCmd.php │ │ ├── HorizontalTabCmd.php │ │ ├── UnknownDataCmd.php │ │ ├── FormFeedCmd.php │ │ ├── PulseCmd.php │ │ ├── CarriageReturnCmd.php │ │ ├── EnableSmoothingCmd.php │ │ ├── SelectCodeTableCmd.php │ │ ├── SetBarcodeHeightCmd.php │ │ ├── SetBarcodeWidthCmd.php │ │ ├── TransmitPrinterID.php │ │ ├── EnableDoubleStrikeCmd.php │ │ ├── EnablePanelButtonsCmd.php │ │ ├── SelectHriPrintPosCmd.php │ │ ├── SelectLineSpacingCmd.php │ │ ├── UnknownCommandOneArg.php │ │ ├── CancelKanjiCharacterMode.php │ │ ├── SelectAlternateColorCmd.php │ │ ├── SelectKanjiCharacterCode.php │ │ ├── SelectPaperEndSensorsCmd.php │ │ ├── SetAbsolutePrintPosCmd.php │ │ ├── RequestResponseTransmissionCmd.php │ │ ├── SelectPeripheralDeviceCmd.php │ │ ├── SelectPrintStopSensorsCmd.php │ │ ├── SelectDefaultLineSpacingCmd.php │ │ ├── SelectInternationalCharacterSetCmd.php │ │ ├── SetRelativeVerticalPrintPositionCmd.php │ │ ├── UnknownDataSubCmd.php │ │ ├── InlineFormattingCmd.php │ │ ├── ImageContainer.php │ │ ├── LineFeedCmd.php │ │ ├── PrintAndFeedCmd.php │ │ ├── PrintBufferredDataGraphicsSubCmd.php │ │ ├── PrintAndFeedLinesCmd.php │ │ ├── PrintAndReverseFeedLinesCmd.php │ │ ├── StoreColumnFmtDataToPrintBufferGraphicsSubCmd.php │ │ ├── EscposCommand.php │ │ ├── GraphicsLargeDataCmd.php │ │ ├── InitializeCmd.php │ │ ├── EnableEmphasisCmd.php │ │ ├── BarcodeAData.php │ │ ├── CommandOneArg.php │ │ ├── DataSubCmd.php │ │ ├── BarcodeBData.php │ │ ├── CommandTwoArgs.php │ │ ├── TextCmd.php │ │ ├── EnableBlackWhiteInvertCmd.php │ │ ├── EnableUpsideDownPrintModeCmd.php │ │ ├── CommandThreeArgs.php │ │ ├── FeedAndCutCmd.php │ │ ├── SelectPrintModeCmd.php │ │ ├── SelectFontCmd.php │ │ ├── Command.php │ │ ├── SelectCharacterSizeCmd.php │ │ ├── EnableUnderlineCmd.php │ │ ├── PrintBarcodeCmd.php │ │ ├── SelectJustificationCmd.php │ │ ├── DataCmd.php │ │ ├── LargeDataCmd.php │ │ ├── PrintRasterBitImageCmd.php │ │ ├── GraphicsDataCmd.php │ │ ├── SelectBitImageModeCmd.php │ │ ├── StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php │ │ └── Printout.php │ ├── Context │ │ ├── ParserContext.php │ │ ├── ParserContextImpl.php │ │ └── InlineFormatting.php │ └── Parser.php └── resources │ └── esc2html.css ├── composer.json ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── esc2text.php ├── README.md ├── CONTRIBUTING.md ├── escimages.php ├── composer.lock └── esc2html.php /receipt-with-logo.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/HEAD/receipt-with-logo.bin -------------------------------------------------------------------------------- /doc/receipt-with-logo-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/HEAD/doc/receipt-with-logo-img.png -------------------------------------------------------------------------------- /doc/receipt-with-logo-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/HEAD/doc/receipt-with-logo-html.png -------------------------------------------------------------------------------- /doc/receipt-with-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/HEAD/doc/receipt-with-logo-small.png -------------------------------------------------------------------------------- /src/Parser/Command/LineBreak.php: -------------------------------------------------------------------------------- 1 | stack = $stack; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Parser/Command/GraphicsLargeDataCmd.php: -------------------------------------------------------------------------------- 1 | reset(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Parser/Command/EnableEmphasisCmd.php: -------------------------------------------------------------------------------- 1 | setBold($this -> getArg() == 1); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Parser/Command/BarcodeAData.php: -------------------------------------------------------------------------------- 1 | done) { 14 | return false; 15 | } 16 | if ($char == NUL) { 17 | $this -> done = true; 18 | } else { 19 | $this -> data .= $char; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Parser/Command/CommandOneArg.php: -------------------------------------------------------------------------------- 1 | arg === null) { 13 | $this -> arg = ord($char); 14 | return true; 15 | } else { 16 | return false; 17 | } 18 | } 19 | 20 | protected function getArg() 21 | { 22 | return $this -> arg; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Parser/Command/DataSubCmd.php: -------------------------------------------------------------------------------- 1 | dataSize = $dataSize; 14 | } 15 | 16 | public function addChar($char) 17 | { 18 | if (strlen($this -> data) < $this -> dataSize) { 19 | $this -> data .= $char; 20 | return true; 21 | } 22 | return false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Parser/Command/BarcodeBData.php: -------------------------------------------------------------------------------- 1 | len === null) { 14 | $this -> len = ord($char); 15 | return true; 16 | } 17 | if (strlen($this -> data) < $this -> len) { 18 | $this -> data .= $char; 19 | return true; 20 | } 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Parser/Command/CommandTwoArgs.php: -------------------------------------------------------------------------------- 1 | arg1 === null) { 14 | $this -> arg1 = ord($char); 15 | return true; 16 | } elseif ($this -> arg2 === null) { 17 | $this -> arg2 = ord($char); 18 | return true; 19 | } 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Parser/Command/TextCmd.php: -------------------------------------------------------------------------------- 1 | str .= iconv('CP437', 'UTF-8', $char); 18 | return true; 19 | } 20 | 21 | public function getText() 22 | { 23 | return $this -> str; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Parser/Command/EnableBlackWhiteInvertCmd.php: -------------------------------------------------------------------------------- 1 | getArg(); 13 | if ($arg === 0 || $arg === 48) { 14 | $formatting -> setInvert(false); 15 | } else if ($arg === 1 || $arg === 49) { 16 | $formatting -> setInvert(true); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dist: trusty 3 | sudo: false 4 | 5 | language: php 6 | 7 | php: 8 | - 5.6 9 | - 7.0 10 | 11 | addons: 12 | apt: 13 | sources: 14 | - sourceline: 'ppa:mike42/mdcheckr' 15 | packages: 16 | - mdcheckr 17 | 18 | install: 19 | - composer install 20 | 21 | before_script: 22 | - printf "\n" | pecl install imagick 23 | 24 | script: 25 | # Check all PHP files with phpcs 26 | - php vendor/bin/phpcs --standard=psr2 --ignore=vendor -n . 27 | # Check all *.md files in git with mdcheckr 28 | - git ls-files | grep '\.md$' | tr '\n' '\0' | xargs -0 mdcheckr 29 | # Run a few examples 30 | - php esc2text.php receipt-with-logo.bin 31 | - php esc2html.php receipt-with-logo.bin 32 | - php escimages.php -f receipt-with-logo.bin 33 | 34 | -------------------------------------------------------------------------------- /src/Parser/Command/EnableUpsideDownPrintModeCmd.php: -------------------------------------------------------------------------------- 1 | getArg(); 13 | if ($arg === 0 || $arg === 48) { 14 | $formatting -> setUpsideDown(false); 15 | } else if ($arg === 1 || $arg === 49) { 16 | $formatting -> setUpsideDown(true); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Parser/Command/CommandThreeArgs.php: -------------------------------------------------------------------------------- 1 | arg1 === null) { 15 | $this -> arg1 = ord($char); 16 | return true; 17 | } elseif ($this -> arg2 === null) { 18 | $this -> arg2 = ord($char); 19 | return true; 20 | } elseif ($this -> arg3 === null) { 21 | $this -> arg3 = ord($char); 22 | return true; 23 | } 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Parser/Command/FeedAndCutCmd.php: -------------------------------------------------------------------------------- 1 | arg1 === null) { 14 | $this -> arg1 = ord($char); 15 | return true; 16 | } elseif (in_array($this -> arg1, array(0, 48, 1, 49)) || $this -> arg2 !== null) { 17 | // One arg only, or arg already set 18 | return false; 19 | } else { 20 | // Read feed length also 21 | $this -> arg2 = ord($char); 22 | return true; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Parser/Command/SelectPrintModeCmd.php: -------------------------------------------------------------------------------- 1 | getArg(); 13 | $formatting -> setFont($arg & 1); 14 | $formatting -> setBold($arg & 8); 15 | $formatting -> setHeightMultiple($arg & 16 ? 2 : 1); 16 | $formatting -> setWidthMultiple($arg & 32 ? 2 : 1); 17 | $formatting -> setUnderline($arg & 128); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Parser/Context/ParserContextImpl.php: -------------------------------------------------------------------------------- 1 | profile; 15 | } 16 | 17 | public function setProfile(CapabilityProfile $profile) 18 | { 19 | $this -> profile = $profile; 20 | } 21 | 22 | public static function byProfileName($profileName) 23 | { 24 | $ctx = new ParserContextImpl(); 25 | $profile = CapabilityProfile::load($profileName); 26 | $ctx -> setProfile($profile); 27 | return $ctx; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Parser/Command/SelectFontCmd.php: -------------------------------------------------------------------------------- 1 | getArg(); 13 | if ($arg === 0 || $arg === 48) { 14 | $formatting -> setFont(0); 15 | } else if ($arg === 1 || $arg === 49) { 16 | $formatting -> setFont(1); 17 | } else if ($arg === 2 || $arg === 50) { 18 | $formatting -> setFont(2); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Parser/Command/Command.php: -------------------------------------------------------------------------------- 1 | context = $context; 13 | } 14 | 15 | public function addChar($char) 16 | { 17 | return false; 18 | } 19 | 20 | public function isAvailableAs($interface) 21 | { 22 | $className = get_called_class(); 23 | if ($className == "ReceiptPrintHq\\EscposTools\\Parser\\Command\\$interface") { 24 | return true; 25 | } 26 | $impl = class_implements($this); 27 | return isset($impl["ReceiptPrintHq\\EscposTools\\Parser\\Command\\$interface"]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Parser/Command/SelectCharacterSizeCmd.php: -------------------------------------------------------------------------------- 1 | getArg(); 13 | // TODO Add height multiples from this command 14 | $formatting -> setWidthMultiple(2); 15 | $width = intdiv($arg, 16) + 1; 16 | $height = ($arg % 16) + 1; 17 | $formatting -> setWidthMultiple($width); 18 | $formatting -> setHeightMultiple($height); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Parser/Command/EnableUnderlineCmd.php: -------------------------------------------------------------------------------- 1 | getArg(); 13 | if ($arg === 0 || $arg === 48) { 14 | $formatting -> setUnderline(0); 15 | } else if ($arg === 1 || $arg === 49) { 16 | $formatting -> setUnderline(1); 17 | } else if ($arg === 2 || $arg === 50) { 18 | $formatting -> setUnderline(2); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Parser/Command/PrintBarcodeCmd.php: -------------------------------------------------------------------------------- 1 | m === null) { 14 | $this -> m = ord($char); 15 | if ((0 <= $this -> m) && ($this -> m <= 6)) { 16 | $this -> subCommand = new BarcodeAData($this -> context); 17 | } elseif ((65 <= $this -> m) && ($this -> m <= 78)) { 18 | $this -> subCommand = new BarcodeBData($this -> context); 19 | } 20 | return true; 21 | } 22 | if ($this -> subCommand === null) { 23 | return false; 24 | } 25 | return $this -> subCommand -> addChar($char); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Parser/Command/SelectJustificationCmd.php: -------------------------------------------------------------------------------- 1 | getArg(); 13 | if ($arg === 0 || $arg === 48) { 14 | $formatting -> setJustification(InlineFormatting::JUSTIFY_LEFT); 15 | } else if ($arg === 1 || $arg === 49) { 16 | $formatting -> setJustification(InlineFormatting::JUSTIFY_CENTER); 17 | } else if ($arg === 2 || $arg === 50) { 18 | $formatting -> setJustification(InlineFormatting::JUSTIFY_RIGHT); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Parser/Parser.php: -------------------------------------------------------------------------------- 1 | printout = new Printout($context); 20 | } 21 | 22 | public function getCommands() 23 | { 24 | return $this -> printout -> commands; 25 | } 26 | 27 | public function addFile($fp) 28 | { 29 | while (!feof($fp) && is_resource($fp)) { 30 | $block = fread($fp, 8192); 31 | for ($i = 0; $i < strlen($block); $i++) { 32 | $this -> printout -> addChar($block[$i]); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-17 Michael Billington `< michael.billington@gmail.com >` and 2 | others. 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the “Software”), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | -------------------------------------------------------------------------------- /esc2text.php: -------------------------------------------------------------------------------- 1 | addFile($fp); 21 | 22 | // Extract text 23 | $commands = $parser -> getCommands(); 24 | foreach ($commands as $cmd) { 25 | if ($debug) { 26 | // Debug output if requested. List commands and the interface for retrieving the data. 27 | $className = shortName(get_class($cmd)); 28 | $impl = class_implements($cmd); 29 | foreach ($impl as $key => $val) { 30 | $impl[$key] = shortName($val); 31 | } 32 | $implStr = count($impl) == 0 ? "" : "(" . implode(", ", $impl) . ")"; 33 | fwrite(STDERR, "[DEBUG] $className {$implStr}\n"); 34 | } 35 | if ($cmd -> isAvailableAs('TextContainer')) { 36 | echo $cmd -> getText(); 37 | } 38 | if ($cmd -> isAvailableAs('LineBreak')) { 39 | echo "\n"; 40 | } 41 | } 42 | 43 | // Just for debugging 44 | function shortName($longName) 45 | { 46 | $nameParts = explode("\\", $longName); 47 | return array_pop($nameParts); 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ESC/POS command-line tools [![Build Status](https://travis-ci.org/receipt-print-hq/escpos-tools.svg?branch=master)](https://travis-ci.org/receipt-print-hq/escpos-tools) 2 | -------------- 3 | 4 | This repository provides command-line utilities for extracting information from 5 | binary ESC/POS data. ESC/POS is a page description language that is commonly 6 | used for receipt printing. 7 | 8 | Currently we have a prototype ESC/POS parser, which can extract the commands 9 | contained in printable ESC/POS binary data, and render them to various formats. 10 | 11 | ## Quick start 12 | 13 | This project is requires: 14 | 15 | - PHP 5.6 or better 16 | - The `mbstring` and `imagick` extensions 17 | - [composer](https://getcomposer.org/) 18 | 19 | To install from source: 20 | 21 | ```bash 22 | git clone https://github.com/receipt-print-hq/escpos-tools 23 | cd escpos-tools 24 | composer install 25 | ``` 26 | 27 | ## Included utilities 28 | 29 | ### esc2text 30 | 31 | `esc2text` extracts text and line breaks from binary ESC/POS files. 32 | 33 | - [esc2text documentation](doc/esc2text.md) 34 | 35 | ### esc2html 36 | 37 | `esc2html` converts binary ESC/POS files to HTML. 38 | 39 | - [esc2html documentation](doc/esc2html.md) 40 | 41 | ### escimages 42 | 43 | `escimages` extracts graphics from binary ESC/POS files in PBM and PNG format. 44 | 45 | - [escimages documentation](doc/escimages.md) 46 | 47 | ## Contribute 48 | 49 | - [CONTRIBUTING.md](CONTRIBUTING.md) 50 | 51 | ## Licensing 52 | 53 | - [LICENSE.md](LICENSE.md) 54 | -------------------------------------------------------------------------------- /src/Parser/Command/DataCmd.php: -------------------------------------------------------------------------------- 1 | p1 === null) { 19 | $this -> p1 = ord($char); 20 | return true; 21 | } elseif ($this -> p2 === null) { 22 | $this -> p2 = ord($char); 23 | $this -> dataSize = $this -> p1 + $this -> p2 * 256; 24 | return true; 25 | } elseif ($this -> arg1 === null) { 26 | $this -> arg1 = ord($char); 27 | return true; 28 | } elseif ($this -> arg2 === null) { 29 | $this -> arg2 = ord($char); 30 | $this -> subCommand = $this -> getSubCommand($this -> arg1, $this -> arg2, $this -> dataSize - 2); 31 | return true; 32 | } 33 | return $this -> subCommand -> addChar($char); 34 | } 35 | 36 | public function getSubCommand($arg1, $arg2, $len) 37 | { 38 | return new UnknownDataSubCmd($len); 39 | } 40 | 41 | public function subCommand() 42 | { 43 | // TODO rename and take getSubCommand() name. 44 | return $this -> subCommand; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Parser/Command/LargeDataCmd.php: -------------------------------------------------------------------------------- 1 | p1 === null) { 21 | $this -> p1 = ord($char); 22 | return true; 23 | } elseif ($this -> p2 === null) { 24 | $this -> p2 = ord($char); 25 | return true; 26 | } elseif ($this -> p3 === null) { 27 | $this -> p3 = ord($char); 28 | return true; 29 | } elseif ($this -> p4 === null) { 30 | $this -> p4 = ord($char); 31 | $this -> dataSize = $this -> p1 + $this -> p2 * 256 + $this -> p3 * 65536 + $this -> p4 * 16777216; 32 | return true; 33 | } elseif ($this -> arg1 === null) { 34 | $this -> arg1 = ord($char); 35 | return true; 36 | } elseif ($this -> arg2 === null) { 37 | $this -> arg2 = ord($char); 38 | $this -> subCommand = $this -> getSubCommand($this -> arg1, $this -> arg2, $this -> dataSize - 2); 39 | return true; 40 | } 41 | return $this -> subCommand -> addChar($char); 42 | } 43 | 44 | public function getSubCommand($arg1, $arg2, $len) 45 | { 46 | return new UnknownDataSubCmd($len); 47 | } 48 | 49 | public function subCommand() 50 | { 51 | // TODO rename and take getSubCommand() name. 52 | return $this -> subCommand; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Parser/Context/InlineFormatting.php: -------------------------------------------------------------------------------- 1 | reset(); 28 | } 29 | 30 | public function setBold($bold) 31 | { 32 | $this -> bold = $bold; 33 | } 34 | 35 | public function setInvert($invert) 36 | { 37 | $this -> invert = $invert; 38 | } 39 | 40 | public function setWidthMultiple($width) 41 | { 42 | $this -> widthMultiple = $width; 43 | } 44 | 45 | public function setHeightMultiple($height) 46 | { 47 | $this -> heightMultiple = $height; 48 | } 49 | 50 | public function setFont($font) 51 | { 52 | $this -> font = $font; 53 | } 54 | 55 | public function setJustification($justification) 56 | { 57 | $this -> justification = $justification; 58 | } 59 | 60 | public function setUnderline($underline) 61 | { 62 | $this -> underline = $underline; 63 | } 64 | 65 | public function setUpsideDown($upsideDown) 66 | { 67 | $this -> upsideDown = $upsideDown; 68 | } 69 | 70 | public static function getDefault() 71 | { 72 | return new InlineFormatting(); 73 | } 74 | 75 | public function reset() 76 | { 77 | $this -> bold = false; 78 | $this -> widthMultiple = 1; 79 | $this -> heightMultiple = 1; 80 | $this -> justification = InlineFormatting::JUSTIFY_LEFT; 81 | $this -> underline = 0; 82 | $this -> invert = false; 83 | $this -> font = 0; 84 | $this -> upsideDown = false; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /doc/esc2html.md: -------------------------------------------------------------------------------- 1 | # esc2html 2 | 3 | `esc2html` converts an ESC/POS binary file into a HTML document. It is currently capable of rendering some types of 4 | formatting and images, plus any ASCII text in the input file. 5 | 6 | ``` 7 | $ php esc2html.php receipt-with-logo.bin > output.html 8 | ``` 9 | 10 | ## Installation 11 | 12 | This utility is included with escpos-tools. See the 13 | [escpos-tools documentation](https://github.com/receipt-print-hq/escpos-tools) for installation instructions. 14 | 15 | ## Usage 16 | 17 | ``` 18 | php esc2html FILE 19 | ``` 20 | 21 | ## Example 22 | 23 | ``` 24 | $ php esc2html.php receipt-with-logo.bin > receipt-with-logo.html 25 | ``` 26 | 27 | The HTML representation of the receipt is saved to a new file, and appears as: 28 | 29 | ![receipt-with-logo-img.png](https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/master/doc/receipt-with-logo-html.png) 30 | 31 | The same binary data, when sent to a printer, renders as below: 32 | 33 | ``` 34 | $ cat receipt-with-logo.bin > /dev/usb/lp0 35 | ``` 36 | 37 | ![receipt-with-logo-small.png](https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/master/doc/receipt-with-logo-small.png) 38 | 39 | The input file used as an example here was generated by [escpos-php](https://github.com/mike42/escpos-php), and is available [here](https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/master/receipt-with-logo.bin). 40 | 41 | ## Further conversions 42 | 43 | This utility will create a formatted HTML file. This can be converted accurately to PDF 44 | via `wkhtmltopdf`, where formatted plaintext can be subsequently extracted via 45 | `pdftotext -f -nopagebrk`, or images can be extracted via `pdfimages`. If some text 46 | is embedded in the finished document as an image, then `pdfsandwich` can apply optical 47 | character recognition to this. 48 | 49 | Alternatively, the HTML may be converted accurately to a raster image via `wkhtmltoimage`. 50 | 51 | The correct layout of the HTML document partially depends on the use of CSS3, so the 52 | use of document converters such as `unoconv` and `pandoc` tends to lose some information. 53 | 54 | These can can be used to produce RTF, DOC, and some other formats if the lossy conversion 55 | is acceptable. 56 | 57 | ## See also 58 | 59 | - [esc2text](esc2text.md) 60 | - [escimages](escimages.md) 61 | -------------------------------------------------------------------------------- /src/Parser/Command/PrintRasterBitImageCmd.php: -------------------------------------------------------------------------------- 1 | dataLen !== null) { 22 | if (strlen($this -> data) < $this -> dataLen) { 23 | $this -> data .= $char; 24 | return true; 25 | } 26 | return false; 27 | } 28 | if ($this -> m === null) { 29 | $this -> m = ord($char); 30 | return true; 31 | } 32 | if ($this -> xL === null) { 33 | $this -> xL = ord($char); 34 | return true; 35 | } 36 | if ($this -> xH === null) { 37 | $this -> xH = ord($char); 38 | return true; 39 | } 40 | if ($this -> yL === null) { 41 | $this -> yL = ord($char); 42 | return true; 43 | } 44 | if ($this -> yH === null) { 45 | $this -> yH = ord($char); 46 | $this -> width = $this -> xL + $this -> xH * 256; 47 | $this -> height = $this -> yL + $this -> yH * 256; 48 | $this -> dataLen = $this -> width * $this -> height; 49 | return true; 50 | } 51 | return false; 52 | } 53 | public function getHeight() 54 | { 55 | return $this -> height; 56 | } 57 | 58 | public function asPbm() 59 | { 60 | return "P4\n" . $this -> getWidth() . " " . $this -> getHeight() . "\n" . $this -> data; 61 | } 62 | 63 | public function getWidth() 64 | { 65 | return $this -> width * 8; 66 | } 67 | 68 | public function asPng() 69 | { 70 | // Just a format conversion PBM -> PNG 71 | $pbmBlob = $this -> asPbm(); 72 | $im = new Imagick(); 73 | $im -> readImageBlob($pbmBlob, 'pbm'); 74 | $im->setResourceLimit(6, 1); // Prevent libgomp1 segfaults, grumble grumble. 75 | $im -> setFormat('png'); 76 | return $im -> getImageBlob(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /doc/esc2text.md: -------------------------------------------------------------------------------- 1 | # esc2text 2 | 3 | `esc2text` extracts text from binary ESC/POS files. 4 | 5 | The plaintext output is UTF-8 encoded, and written to standard output. 6 | 7 | It operates by reading all of the commands in the input file, and discarding 8 | those which do not contain text or line breaks. Add `-v` as the last argument 9 | to see all the other commands for debugging purposes. 10 | 11 | ## Installation 12 | 13 | This utility is included with escpos-tools. See the 14 | [escpos-tools documentation](https://github.com/receipt-print-hq/escpos-tools) 15 | for installation instructions. 16 | 17 | ## Usage 18 | 19 | ``` 20 | php esc2text.php FILE [ -v ] 21 | ``` 22 | 23 | ## Example 24 | 25 | ``` 26 | $ php esc2text.php receipt-with-logo.bin 27 | ExampleMart Ltd. 28 | Shop No. 42. 29 | 30 | SALES INVOICE 31 | $ 32 | Example item #1 4.00 33 | Another thing 3.50 34 | Something else 1.00 35 | A final item 4.45 36 | Subtotal 12.95 37 | 38 | A local tax 1.30 39 | Total $ 14.25 40 | 41 | Thank you for shopping at ExampleMart 42 | For trading hours, please visit example.com 43 | 44 | Monday 6th of April 2015 02:56:25 PM 45 | ``` 46 | 47 | The same binary data, when sent to a printer, renders as below: 48 | 49 | ``` 50 | $ cat receipt-with-logo.bin > /dev/usb/lp0 51 | ``` 52 | 53 | ![receipt-with-logo-small.png](https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/master/doc/receipt-with-logo-small.png) 54 | 55 | The input file used as an example here was generated by [escpos-php](https://github.com/mike42/escpos-php), and is available [here](https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/master/receipt-with-logo.bin). 56 | 57 | ## Further conversions 58 | 59 | This utility will create unformatted plaintext files. Other formats can be accessed 60 | by performing a second conversion via the `unoconv` and `pandoc` utilites. 61 | 62 | These can produce DOC, HTML, PDF or RTF versions of the same output, among other formats. 63 | 64 | In some cases, drivers convert text to a series of image before printing, so there is no 65 | text the ESC/POS file. In this case, `esc2text` will correctly return a blank text file. 66 | Images can be extracted with `escimage` and put through a suitable OCR tool. 67 | 68 | ## See also 69 | 70 | - [esc2html](esc2html.md) 71 | - [escimages](escimages.md) 72 | -------------------------------------------------------------------------------- /src/Parser/Command/GraphicsDataCmd.php: -------------------------------------------------------------------------------- 1 | /dev/usb/lp0 53 | ``` 54 | 55 | ![receipt-with-logo-small.png](https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/master/doc/receipt-with-logo-small.png) 56 | 57 | The input file used as an example here was generated by [escpos-php](https://github.com/mike42/escpos-php), and is available [here](https://raw.githubusercontent.com/receipt-print-hq/escpos-tools/master/receipt-with-logo.bin). 58 | 59 | ## Further conversions 60 | 61 | This utility will create raster images only. They can be printed back to a printer 62 | via `escpos-php`, or rendered to other formats with the ImageMagick `convert` utility. 63 | 64 | In some cases, drivers convert text to a series of images before printing. To recover 65 | text from this type of receipt, the images from `escimage` can be stacked and 66 | sent through a suitable OCR tool. 67 | 68 | For example, using `tesseract`: 69 | 70 | ``` 71 | rm *.pbm 72 | php escimages.php -f input.bin --pbm 73 | convert -append *.pbm pbm:- | tesseract - - 74 | ``` 75 | 76 | ## See also 77 | 78 | - [esc2html](esc2html.md) 79 | - [esc2text](esc2text.md) 80 | -------------------------------------------------------------------------------- /src/Parser/Command/SelectBitImageModeCmd.php: -------------------------------------------------------------------------------- 1 | m === null) { 23 | $this->m = ord($char); 24 | return true; 25 | } else if ($this->p1 === null) { 26 | $this->p1 = ord($char); 27 | return true; 28 | } elseif ($this->p2 === null) { 29 | $this->p2 = ord($char); 30 | $this->width = $this->p1 + $this->p2 * 256; 31 | if ($this->m == 32 || $this->m == 33) { 32 | $this->dataSize = $this->width * 3; 33 | $this->height = 24; 34 | } else { 35 | $this->dataSize = $this->width; 36 | $this->height = 8; 37 | } 38 | return true; 39 | } else if (strlen($this->data) < $this->dataSize) { 40 | $this->data .= $char; 41 | return true; 42 | } 43 | return false; 44 | } 45 | 46 | public function getHeight() 47 | { 48 | return $this -> height; 49 | } 50 | 51 | public function getWidth() 52 | { 53 | return $this -> width; 54 | } 55 | 56 | protected function asReflectedPbm() 57 | { 58 | // Gemerate a PBM image from the source data. If we add a PBM header to the column 59 | // format ESC/POS data with the width and height swapped, then we get a valid PBM, with 60 | // the image reflected diagonally compared with the original. 61 | return "P4\n" . $this -> getHeight() . " " . $this -> getWidth() . "\n" . $this -> data; 62 | } 63 | 64 | public function asPbm() 65 | { 66 | // Reflect image diagonally from internally generated PBM 67 | $pbmBlob = $this -> asReflectedPbm(); 68 | $im = new Imagick(); 69 | $im -> readImageBlob($pbmBlob, 'pbm'); 70 | $im -> rotateImage('#fff', 90.0); 71 | $im -> flopImage(); 72 | return $im -> getImageBlob(); 73 | } 74 | 75 | public function asPng() 76 | { 77 | // Just a format conversion PBM -> PNG 78 | $pbmBlob = $this -> asPbm(); 79 | $im = new Imagick(); 80 | $im -> readImageBlob($pbmBlob, 'pbm'); 81 | $im->setResourceLimit(6, 1); // Prevent libgomp1 segfaults, grumble grumble. 82 | $im -> setFormat('png'); 83 | return $im -> getImageBlob(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Parser/Command/StoreRasterFmtDataToPrintBufferGraphicsSubCmd.php: -------------------------------------------------------------------------------- 1 | dataSize = $dataSize - 8; 27 | } 28 | 29 | public function addChar($char) 30 | { 31 | if ($this -> tone == null) { 32 | $this -> tone = ord($char); 33 | return true; 34 | } else if ($this -> color === null) { 35 | $this -> color = ord($char); 36 | return true; 37 | } else if ($this -> widthMultiple === null) { 38 | $this -> widthMultiple = ord($char); 39 | return true; 40 | } else if ($this -> heightMultiple === null) { 41 | $this -> heightMultiple = ord($char); 42 | return true; 43 | } else if ($this -> x1 === null) { 44 | $this -> x1 = ord($char); 45 | return true; 46 | } else if ($this -> x2 === null) { 47 | $this -> x2 = ord($char); 48 | return true; 49 | } else if ($this -> y1 === null) { 50 | $this -> y1 = ord($char); 51 | return true; 52 | } else if ($this -> y2 === null) { 53 | $this -> y2 = ord($char); 54 | return true; 55 | } else if (strlen($this -> data) < $this -> dataSize) { 56 | $this -> data .= $char; 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | public function getWidth() 63 | { 64 | return $this -> x1 + $this -> x2 * 256; 65 | } 66 | 67 | public function getHeight() 68 | { 69 | return $this -> y1 + $this -> y2 * 256; 70 | } 71 | 72 | public function asPbm() 73 | { 74 | return "P4\n" . $this -> getWidth() . " " . $this -> getHeight() . "\n" . $this -> data; 75 | } 76 | 77 | public function asPng() 78 | { 79 | $pbmBlob = $this -> asPbm(); 80 | $im = new Imagick(); 81 | $im -> readImageBlob($pbmBlob, 'pbm'); 82 | $im->setResourceLimit(6, 1); // Prevent libgomp1 segfaults, grumble grumble. 83 | $im -> setFormat('png'); 84 | return $im -> getImageBlob(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | This project is open to many different types of contribution. You can help with improving the documentation and examples, sharing your insights on the issue tracker, adding fixes to the code, or providing test cases. 4 | 5 | ## Issue tracker 6 | 7 | Open issues of all sorts are tracked on the [issue tracker](https://github.com/receipt-print-hq/escpos-tools/issues). Please check [the docs](https://github.com/receipt-print-hq/escpos-tools/blob/master/README.md) before you post, and practice good [bug tracker etiquette](https://bugzilla.mozilla.org/page.cgi?id=etiquette.html) to keep it running smoothly. 8 | 9 | Issues are [loosely categorised](https://github.com/receipt-print-hq/escpos-tools/labels), and will stay open while there is still something that can be resolved. 10 | 11 | Anybody may add to the discussion on the bug tracker. Just be sure to add new questions as separate issues, and to avoid commenting on closed issues. 12 | 13 | ## Submitting changes 14 | 15 | Code changes may be submitted as a "[pull request](https://help.github.com/en/articles/about-pull-requests)" at [receipt-print-hq/escpos-tools](https://github.com/receipt-print-hq/escpos-tools). The description should include some information about how the change improves the library. 16 | 17 | The project is MIT-licensed (see [LICENSE.md](https://github.com/receipt-print-hq/escpos-tools/blob/master/LICENSE.md) for details). You are not required to assign copyright in order to submit changes, but you do need to agree for your code to be distributed under this license in order for it to be accepted. 18 | 19 | ### Documentation changes 20 | 21 | The official documentaton is also located in the main repository, under the [doc/](https://github.com/receipt-print-hq/escpos-tools/tree/master/doc) folder. 22 | 23 | You are welcome to post any suggested improvements as pull requests. 24 | 25 | ### Release process 26 | 27 | This project is still quite new, and does not have a formalised release process. 28 | 29 | Changes should be submitted via pull request directly to the shared "master" branch. 30 | 31 | ## Code style 32 | 33 | This project uses the [PSR-2 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) for all PHP source code. 34 | 35 | ## Testing and CI 36 | 37 | The tests are executed on [Travis CI](https://travis-ci.org/receipt-print-hq/escpos-tools) over PHP 5.6 and 7.0. Earlier versions of PHP are not supported. 38 | 39 | For development, you will require the `imagick` and `Xdebug` PHP exensions, the `composer` dependency manager, and acces to a `sass` compiler. 40 | 41 | Fetch a copy of this code and load dependencies with composer: 42 | 43 | git clone https://github.com/receipt-print-hq/escpos-tools 44 | cd escpos-tools/ 45 | composer install 46 | 47 | Code style can be checked via [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer): 48 | 49 | php vendor/bin/phpcs --standard=psr2 src/ -n 50 | 51 | The CI scripts currently just render a few receipts to check for obvious errors. You can find the commands to run locally in `travis.yml`. 52 | -------------------------------------------------------------------------------- /escimages.php: -------------------------------------------------------------------------------- 1 | getWidth() . 'x' . $img -> getHeight(); 14 | echo "[ Image $imgNo: $desc ]\n"; 15 | $outpFilename = $outputDir . '/' . "$receiptName-" . sprintf('%02d', $imgNo); 16 | if ($outputPbm) { 17 | file_put_contents($outpFilename . ".pbm", $img -> asPbm()); 18 | } 19 | if ($outputPng) { 20 | file_put_contents($outpFilename . ".png", $img -> asPng()); 21 | } 22 | } 23 | 24 | // Read CLI options in 25 | $shortopts = "f:o:h"; 26 | $longopts = array( 27 | "file:", 28 | "output-dir:", 29 | "png", 30 | "pbm", 31 | "help" 32 | ); 33 | $options = getopt($shortopts, $longopts); 34 | $usage = "Usage: " . $argv[0] . " OPTIONS --file 'filename'\n"; 35 | // Input file 36 | $inputFile = null; 37 | $inputFile = array_key_exists("f", $options) ? $options["f"] : $inputFile; 38 | $inputFile = array_key_exists("file", $options) ? $options["file"] : $inputFile; 39 | // Output dir 40 | $outputDir = "."; 41 | $outputDir = array_key_exists("o", $options) ? $options["o"] : $outputDir; 42 | $outputDir = array_key_exists("output-dir", $options) ? $options["output-dir"] : $outputDir; 43 | // Help 44 | $showHelp = array_key_exists("h", $options) || array_key_exists("help", $options); 45 | // Output formats 46 | $outputPng = array_key_exists("png", $options); 47 | $outputPbm = array_key_exists("pbm", $options); 48 | if (!$outputPng && !$outputPbm) { 49 | // Default 50 | $outputPng = true; 51 | } 52 | 53 | if (empty($options) || ( $inputFile === null && !$showHelp)) { 54 | // Incorrect usage shows error and quits nonzero 55 | $error = "$usage\nTry '" . $argv[0] . " --help' for more information.\n"; 56 | file_put_contents("php://stderr", $error); 57 | exit(1); 58 | } 59 | if (array_key_exists("h", $options) || array_key_exists("help", $options)) { 60 | // Request for help 61 | $message = "$usage 62 | Required options: 63 | 64 | -f, --file FILE The input file to read. 65 | 66 | Output options: 67 | 68 | -o, --output-dir DIRECTORY The directory to write output files to. 69 | 70 | Output format: 71 | 72 | Select one or more formats for output. If none is specified, then PNG is used. 73 | 74 | --png Write output files in PNG format. 75 | --pbm Write output files in PBM format. 76 | 77 | Other options: 78 | -h, --help Show this help\n"; 79 | echo $message; 80 | exit(0); 81 | } 82 | 83 | // Quick validation 84 | if (!file_exists($outputDir) || !is_dir($outputDir)) { 85 | $error = "Output location does not exist, or is not a directory.\n"; 86 | file_put_contents("php://stderr", $error); 87 | exit(1); 88 | } 89 | $outputDir = rtrim($outputDir, '/'); 90 | if (!file_exists($inputFile) || !is_readable($inputFile)) { 91 | $error = "Input file does not exist, or is not readable.\n"; 92 | file_put_contents("php://stderr", $error); 93 | exit(1); 94 | } 95 | $receiptName = $path_parts = pathinfo($inputFile)['filename']; 96 | 97 | // Load in a file 98 | $fp = @fopen($inputFile, 'rb'); 99 | if (!$fp) { 100 | $error = "Failed to open the input file\n"; 101 | file_put_contents("php://stderr", $error); 102 | exit(1); 103 | } 104 | 105 | $parser = new Parser(); 106 | $parser -> addFile($fp); 107 | 108 | // Extract images 109 | $bufferedImg = null; 110 | $imgNo = 0; 111 | $commands = $parser -> getCommands(); 112 | foreach ($commands as $cmd) { 113 | if ($cmd -> isAvailableAs('GraphicsDataCmd') || $cmd -> isAvailableAs('GraphicsLargeDataCmd')) { 114 | $sub = $cmd -> subCommand(); 115 | if ($sub -> isAvailableAs('StoreRasterFmtDataToPrintBufferGraphicsSubCmd')) { 116 | // Assign image when stored 117 | $bufferedImg = $sub; 118 | } else if ($sub -> isAvailableAs('PrintBufferredDataGraphicsSubCmd')) { 119 | // Print assigned image 120 | $imgNo = $imgNo + 1; 121 | outpImg($outputDir, $imgNo, $bufferedImg, $outputPbm, $outputPng, $receiptName); 122 | $bufferedImg = null; 123 | } 124 | } else if ($cmd -> isAvailableAs('ImageContainer')) { 125 | $imgNo = $imgNo + 1; 126 | outpImg($outputDir, $imgNo, $cmd, $outputPbm, $outputPng, $receiptName); 127 | } 128 | } 129 | 130 | // Just for debugging 131 | function shortName($longName) 132 | { 133 | $nameParts = explode("\\", $longName); 134 | return array_pop($nameParts); 135 | } 136 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "b5630260eb622bbc042c2d420c2abe1e", 8 | "content-hash": "ea62982abf2ff4d1f3c1f9246556fbb2", 9 | "packages": [ 10 | { 11 | "name": "mike42/escpos-php", 12 | "version": "v1.5.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/mike42/escpos-php.git", 16 | "reference": "11d8ed8950b775c6f120722de10ee2ef6fb90f18" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/mike42/escpos-php/zipball/11d8ed8950b775c6f120722de10ee2ef6fb90f18", 21 | "reference": "11d8ed8950b775c6f120722de10ee2ef6fb90f18", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "ext-mbstring": "*", 26 | "php": ">=5.3.9" 27 | }, 28 | "require-dev": { 29 | "guzzlehttp/guzzle": "~3.0|~4.0|~5.0|~6.0", 30 | "phpunit/phpunit": "4.8.*", 31 | "squizlabs/php_codesniffer": "2.*" 32 | }, 33 | "suggest": { 34 | "ext-gd": "Used for image printing if present.", 35 | "ext-imagick": "Will be used for image printing if present. Required for PDF printing or use of custom fonts.", 36 | "guzzlehttp/guzzle": "Allows the use of the ApiConnector to send print jobs over HTTP." 37 | }, 38 | "type": "library", 39 | "autoload": { 40 | "psr-4": { 41 | "Mike42\\": "src/Mike42" 42 | } 43 | }, 44 | "notification-url": "https://packagist.org/downloads/", 45 | "license": [ 46 | "MIT" 47 | ], 48 | "authors": [ 49 | { 50 | "name": "Michael Billington", 51 | "email": "michael.billington@gmail.com" 52 | } 53 | ], 54 | "description": "PHP receipt printer library for use with ESC/POS-compatible thermal and impact printers", 55 | "homepage": "https://github.com/mike42/escpos-php", 56 | "keywords": [ 57 | "ESC-POS", 58 | "driver", 59 | "escpos", 60 | "print", 61 | "receipt" 62 | ], 63 | "time": "2017-03-12 12:17:11" 64 | }, 65 | { 66 | "name": "squizlabs/php_codesniffer", 67 | "version": "2.8.1", 68 | "source": { 69 | "type": "git", 70 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", 71 | "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d" 72 | }, 73 | "dist": { 74 | "type": "zip", 75 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", 76 | "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", 77 | "shasum": "" 78 | }, 79 | "require": { 80 | "ext-simplexml": "*", 81 | "ext-tokenizer": "*", 82 | "ext-xmlwriter": "*", 83 | "php": ">=5.1.2" 84 | }, 85 | "require-dev": { 86 | "phpunit/phpunit": "~4.0" 87 | }, 88 | "bin": [ 89 | "scripts/phpcs", 90 | "scripts/phpcbf" 91 | ], 92 | "type": "library", 93 | "extra": { 94 | "branch-alias": { 95 | "dev-master": "2.x-dev" 96 | } 97 | }, 98 | "autoload": { 99 | "classmap": [ 100 | "CodeSniffer.php", 101 | "CodeSniffer/CLI.php", 102 | "CodeSniffer/Exception.php", 103 | "CodeSniffer/File.php", 104 | "CodeSniffer/Fixer.php", 105 | "CodeSniffer/Report.php", 106 | "CodeSniffer/Reporting.php", 107 | "CodeSniffer/Sniff.php", 108 | "CodeSniffer/Tokens.php", 109 | "CodeSniffer/Reports/", 110 | "CodeSniffer/Tokenizers/", 111 | "CodeSniffer/DocGenerators/", 112 | "CodeSniffer/Standards/AbstractPatternSniff.php", 113 | "CodeSniffer/Standards/AbstractScopeSniff.php", 114 | "CodeSniffer/Standards/AbstractVariableSniff.php", 115 | "CodeSniffer/Standards/IncorrectPatternException.php", 116 | "CodeSniffer/Standards/Generic/Sniffs/", 117 | "CodeSniffer/Standards/MySource/Sniffs/", 118 | "CodeSniffer/Standards/PEAR/Sniffs/", 119 | "CodeSniffer/Standards/PSR1/Sniffs/", 120 | "CodeSniffer/Standards/PSR2/Sniffs/", 121 | "CodeSniffer/Standards/Squiz/Sniffs/", 122 | "CodeSniffer/Standards/Zend/Sniffs/" 123 | ] 124 | }, 125 | "notification-url": "https://packagist.org/downloads/", 126 | "license": [ 127 | "BSD-3-Clause" 128 | ], 129 | "authors": [ 130 | { 131 | "name": "Greg Sherwood", 132 | "role": "lead" 133 | } 134 | ], 135 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 136 | "homepage": "http://www.squizlabs.com/php-codesniffer", 137 | "keywords": [ 138 | "phpcs", 139 | "standards" 140 | ], 141 | "time": "2017-03-01 22:17:45" 142 | } 143 | ], 144 | "packages-dev": [], 145 | "aliases": [], 146 | "minimum-stability": "stable", 147 | "stability-flags": [], 148 | "prefer-stable": false, 149 | "prefer-lowest": false, 150 | "platform": [], 151 | "platform-dev": [] 152 | } 153 | -------------------------------------------------------------------------------- /src/Parser/Command/Printout.php: -------------------------------------------------------------------------------- 1 | 'HorizontalTabCmd', 24 | LF => 'LineFeedCmd', 25 | FF => 'FormFeedCmd', 26 | CR => 'CarriageReturnCmd', 27 | ESC => array( 28 | '@' => 'InitializeCmd', 29 | '*' => 'SelectBitImageModeCmd', 30 | '!' => 'SelectPrintModeCmd', 31 | '{' => 'EnableUpsideDownPrintModeCmd', 32 | '=' => 'SelectPeripheralDeviceCmd', 33 | 'c' => array( 34 | '0' => 'UnknownCommandOneArg', 35 | '1' => 'UnknownCommandOneArg', 36 | '3' => 'SelectPaperEndSensorsCmd', 37 | '4' => 'SelectPrintStopSensorsCmd', 38 | '5' => 'EnablePanelButtonsCmd' 39 | ), 40 | '2' => 'SelectDefaultLineSpacingCmd', 41 | '3' => 'SelectLineSpacingCmd', 42 | 'r' => 'SelectAlternateColorCmd', 43 | 'R' => 'SelectInternationalCharacterSetCmd', 44 | 't' => 'SelectCodeTableCmd', 45 | 'J' => 'PrintAndFeedCmd', 46 | '-' => 'EnableUnderlineCmd', 47 | 'd' => 'PrintAndFeedLinesCmd', 48 | 'G' => 'EnableDoubleStrikeCmd', 49 | 'M' => 'SelectFontCmd', 50 | 'a' => 'SelectJustificationCmd', 51 | 'e' => 'PrintAndReverseFeedLinesCmd', 52 | '$' => 'SetAbsolutePrintPosCmd', 53 | 'E' => 'EnableEmphasisCmd', 54 | 'p' => 'PulseCmd' 55 | ), 56 | GS => array( 57 | '\\' => 'SetRelativeVerticalPrintPositionCmd', // low and high values for vertrical print position (page mode) 58 | '!' => 'SelectCharacterSizeCmd', 59 | 'V' => 'FeedAndCutCmd', 60 | 'b' => 'EnableSmoothingCmd', 61 | 'B' => 'EnableBlackWhiteInvertCmd', 62 | '(' => array( 63 | 'C' => array( 64 | 65 | ), 66 | 'E' => array( 67 | 68 | ), 69 | 'H' => 'RequestResponseTransmissionCmd', 70 | 'K' => array( 71 | 72 | ), 73 | 'L' => 'GraphicsDataCmd', 74 | 'k' => 'Code2DDataCmd', 75 | // Don't know what this command is, but could be a data command 76 | 'J' => 'UnknownDataCmd', 77 | ), 78 | 'I' => 'TransmitPrinterID', 79 | 'h' => 'SetBarcodeHeightCmd', 80 | 'w' => 'SetBarcodeWidthCmd', 81 | 'H' => 'SelectHriPrintPosCmd', 82 | 'k' => 'PrintBarcodeCmd', 83 | 'v' => array( 84 | '0' => 'PrintRasterBitImageCmd' 85 | ), 86 | '8' => array( 87 | 'L' => 'GraphicsLargeDataCmd' 88 | ), 89 | // Set horizontal and vertical motion units. args are x and y 90 | 'P' => 'CommandTwoArgs' 91 | ), 92 | FS => array( 93 | '.' => 'CancelKanjiCharacterMode', 94 | 'C' => 'SelectKanjiCharacterCode' 95 | ), 96 | DLE => array( 97 | 98 | ), 99 | CAN => 'CancelCmd' 100 | ); 101 | 102 | public $commands = array(); 103 | private $search; 104 | private $searchStack; 105 | 106 | public function __construct(ParserContext $context) 107 | { 108 | parent::__construct($context); 109 | $this -> commands = array(); 110 | $this -> reset(); 111 | } 112 | 113 | public function reset() 114 | { 115 | $this -> search = null; 116 | $this -> searchStack = array(); 117 | } 118 | 119 | public function addChar($char) 120 | { 121 | if (count($this -> searchStack) > 0) { 122 | // Matching parts of a command now. 123 | return $this -> navigateCommand($char); 124 | } 125 | 126 | if (count($this -> commands) != 0) { 127 | // Command is in progress 128 | $top = $this -> commands[count($this -> commands) - 1]; 129 | $ret = $top -> addChar($char); 130 | if ($ret) { 131 | // Character has been accepted by the command 132 | return true; 133 | } 134 | } 135 | // Has been rejected or we don't have a command yet. See if we can start a string 136 | if (count($this -> commands) == 0 || !is_a($this -> commands[count($this -> commands) - 1], 'TextCmd')) { 137 | $top = new TextCmd($this -> context, array()); 138 | if ($top -> addChar($char)) { 139 | // Character was accepted to start a string. 140 | $this -> commands[] = $top; 141 | return true; 142 | } 143 | } 144 | // Character starts a command sequence 145 | $this -> search = self::$tree; 146 | return $this -> navigateCommand($char); 147 | } 148 | 149 | public function navigateCommand($char) 150 | { 151 | $this -> searchStack[] = $char; 152 | if (!isset($this -> search[$char])) { 153 | // Failed to match a command 154 | $this -> logUnknownCommand($this -> searchStack); 155 | $this -> reset(); 156 | } elseif (is_array($this -> search[$char])) { 157 | // Command continues after this 158 | $this -> search = $this -> search[$char]; 159 | } else { 160 | // Matched a command right here 161 | $class = "ReceiptPrintHq\\EscposTools\\Parser\\Command\\" . $this -> search[$char]; 162 | $this -> commands[] = new $class($this -> context, $this -> searchStack); 163 | $this -> reset(); 164 | } 165 | } 166 | 167 | public function logUnknownCommand(array $searchStack) 168 | { 169 | $nonPrintableMap = array( 170 | NUL => "NUL", 171 | HT => "HT", 172 | LF => "LF", 173 | FF => "FF", 174 | CR => "CR", 175 | ESC => "ESC", 176 | GS => "GS", 177 | FS => "FS", 178 | DLE => "DLE", 179 | CAN => "CAN" 180 | ); 181 | $cmdStack = array(); 182 | foreach ($searchStack as $s) { 183 | if (isset($nonPrintableMap[$s])) { 184 | $cmdStack[] = $nonPrintableMap[$s]; 185 | } else { 186 | $cmdStack[] = $s; 187 | } 188 | } 189 | fwrite(STDERR, "WARNING: Unknown command " . implode($cmdStack, ' ') . "\n"); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /esc2html.php: -------------------------------------------------------------------------------- 1 | addFile($fp); 21 | 22 | // Extract text 23 | $commands = $parser -> getCommands(); 24 | $formatting = InlineFormatting::getDefault(); 25 | $outp = array(); 26 | $lineHtml = ""; 27 | $bufferedImg = null; 28 | $imgNo = 0; 29 | $skipLineBreak = false; 30 | foreach ($commands as $cmd) { 31 | if ($cmd -> isAvailableAs('InitializeCmd')) { 32 | $formatting = InlineFormatting::getDefault(); 33 | } 34 | if ($cmd -> isAvailableAs('InlineFormattingCmd')) { 35 | $cmd -> applyToInlineFormatting($formatting); 36 | } 37 | if ($cmd -> isAvailableAs('TextContainer')) { 38 | // Add text to line 39 | // TODO could decode text properly from legacy code page to UTF-8 here. 40 | $spanContentText = $cmd -> getText(); 41 | $lineHtml .= span($formatting, $spanContentText); 42 | } 43 | if ($cmd -> isAvailableAs('LineBreak') && $skipLineBreak) { 44 | $skipLineBreak = false; 45 | } else if ($cmd -> isAvailableAs('LineBreak')) { 46 | // Write fresh block element out to HTML 47 | if ($lineHtml === "") { 48 | $lineHtml = span($formatting); 49 | } 50 | // Block-level formatting such as text justification 51 | $classes = getBlockClasses($formatting); 52 | $classesStr = implode($classes, " "); 53 | $outp[] = wrapInline("
", "
", $lineHtml); 54 | $lineHtml = ""; 55 | } 56 | if ($cmd -> isAvailableAs('GraphicsDataCmd') || $cmd -> isAvailableAs('GraphicsLargeDataCmd')) { 57 | $sub = $cmd -> subCommand(); 58 | if ($sub -> isAvailableAs('StoreRasterFmtDataToPrintBufferGraphicsSubCmd')) { 59 | $bufferedImg = $sub; 60 | } else if ($sub -> isAvailableAs('PrintBufferredDataGraphicsSubCmd') && $bufferedImg !== null) { 61 | // Append and flush buffer 62 | $classes = getBlockClasses($formatting); 63 | $classesStr = implode($classes, " "); 64 | $outp[] = wrapInline("
", "
", imgAsDataUrl($bufferedImg)); 65 | $lineHtml = ""; 66 | } 67 | } else if ($cmd -> isAvailableAs('ImageContainer')) { 68 | // Append and flush buffer 69 | $classes = getBlockClasses($formatting); 70 | $classesStr = implode($classes, " "); 71 | $outp[] = wrapInline("
", "
", imgAsDataUrl($cmd)); 72 | $lineHtml = ""; 73 | // Should load into print buffer and print next line break, but we print immediately, so need to skip the next line break. 74 | $skipLineBreak = true; 75 | } 76 | } 77 | 78 | // Stuff we need in the HTML header 79 | const CSS_FILE = __DIR__ . "/src/resources/esc2html.css"; 80 | $metaInfo = array_merge( 81 | array( 82 | "", 83 | "" 88 | ) 89 | ); 90 | 91 | // Final document assembly 92 | $receipt = wrapBlock("
", "
", $outp); 93 | $head = wrapBlock("", "", $metaInfo); 94 | $body = wrapBlock("", "", $receipt); 95 | $html = wrapBlock("", "", array_merge($head, $body), false); 96 | echo "\n" . implode("\n", $html) . "\n"; 97 | 98 | function imgAsDataUrl($bufferedImg) 99 | { 100 | $imgAlt = "Image " . $bufferedImg -> getWidth() . 'x' . $bufferedImg -> getHeight(); 101 | $imgSrc = "data:image/png;base64," . base64_encode($bufferedImg -> asPng()); 102 | $imgWidth = $bufferedImg -> getWidth() / 2; // scaling, images are quite high res and dwarf the text 103 | $bufferedImg = null; 104 | return "\"$imgAlt\""; 105 | } 106 | 107 | function wrapInline($tag, $closeTag, $content) 108 | { 109 | return $tag . $content . $closeTag; 110 | } 111 | 112 | function wrapBlock($tag, $closeTag, array $content, $indent = true) 113 | { 114 | $ret = array(); 115 | $ret[] = $tag; 116 | foreach ($content as $line) { 117 | $ret[] = ($indent ? ' ' : '') . $line; 118 | } 119 | $ret[] = $closeTag; 120 | return $ret; 121 | } 122 | 123 | function span(InlineFormatting $formatting, $spanContentText = false) 124 | { 125 | // Gut some features- 126 | if ($formatting -> widthMultiple > 8) { 127 | // Widths > 2 are not implemented. Cap the width at 2 to avoid formatting issues. 128 | $formatting -> widthMultiple = 8; 129 | } 130 | if ($formatting -> heightMultiple > 8) { 131 | // Widths > 8 are not implemented either 132 | $formatting -> heightMultiple = 8; 133 | } 134 | 135 | // Determine formatting classes to use 136 | $classes = array(); 137 | 138 | if ($formatting -> bold) { 139 | $classes[] = "esc-emphasis"; 140 | } 141 | if ($formatting -> underline > 0) { 142 | $classes[] = $formatting -> underline > 1 ? "esc-underline-double" : "esc-underline"; 143 | } 144 | if ($formatting -> invert) { 145 | $classes[] = "esc-invert"; 146 | } 147 | if ($formatting -> upsideDown) { 148 | $classes[] = "esc-upside-down"; 149 | } 150 | if ($formatting -> font == 1) { 151 | $classes[] = "esc-font-b"; 152 | } 153 | if ($formatting -> widthMultiple > 1 || $formatting -> heightMultiple > 1) { 154 | $classes[] = "esc-text-scaled"; 155 | // Add a single class representing height and width scaling 156 | $widthClass = $formatting -> widthMultiple > 1 ? "-width-" . $formatting -> widthMultiple : ""; 157 | $heightClass = $formatting -> heightMultiple > 1 ? "-height-" . $formatting -> heightMultiple : ""; 158 | $classes[] = "esc" . $widthClass . $heightClass; 159 | } 160 | 161 | // Provide span content as HTML 162 | if ($spanContentText === false) { 163 | $spanContentHtml = " "; 164 | } else { 165 | $spanContentHtml = htmlentities($spanContentText); 166 | } 167 | 168 | // Output span with any non-default classes 169 | if (count($classes) == 0) { 170 | return $spanContentHtml; 171 | } 172 | return "" . $spanContentHtml . ""; 173 | } 174 | 175 | function getBlockClasses($formatting) 176 | { 177 | $classes = ["esc-line"]; 178 | if ($formatting -> justification === InlineFormatting::JUSTIFY_CENTER) { 179 | $classes[] = "esc-justify-center"; 180 | } else if ($formatting -> justification === InlineFormatting::JUSTIFY_RIGHT) { 181 | $classes[] = "esc-justify-right"; 182 | } 183 | return $classes; 184 | } 185 | -------------------------------------------------------------------------------- /src/resources/esc2html.css: -------------------------------------------------------------------------------- 1 | .esc-receipt { 2 | border: 1px solid #888; 3 | font-family: monospace; 4 | margin: 1em; 5 | padding: 1em; 6 | min-width: 28em; 7 | display: inline-block; 8 | } 9 | 10 | .esc-line { 11 | white-space: pre; 12 | } 13 | 14 | .esc-emphasis { 15 | font-weight: bold; 16 | } 17 | 18 | .esc-justify-center .esc-text-scaled { 19 | transform-origin: 50% 0; 20 | } 21 | 22 | .esc-justify-right .esc-text-scaled { 23 | transform-origin: 100% 0; 24 | } 25 | 26 | .esc-justify-center { 27 | text-align: center; 28 | } 29 | 30 | .esc-justify-right { 31 | text-align: right; 32 | } 33 | 34 | .esc-text-scaled { 35 | display: inline-block; 36 | transform-origin: 0 0; 37 | } 38 | 39 | .esc-justify-center .esc-bitimage { 40 | margin-left: auto; 41 | margin-right: auto; 42 | } 43 | 44 | .esc-justify-right .esc-bitimage { 45 | margin-left: auto; 46 | } 47 | 48 | .esc-underline { 49 | border-bottom: 2px solid #000; 50 | } 51 | 52 | .esc-underline-double { 53 | border-bottom: 2px solid #000; 54 | } 55 | 56 | .esc-invert { 57 | background: #000; 58 | color: #fff; 59 | } 60 | 61 | .esc-upside-down { 62 | transform: rotate(180deg); 63 | } 64 | 65 | .esc-font-b { 66 | font-size: 75% 67 | } 68 | 69 | span { 70 | display: inline-block; 71 | } 72 | 73 | /* 74 | TODO 75 | - Generate height/width mixes up to 8x8 76 | - Figure out how do display multiple height/widths inline correctly 77 | */ 78 | .esc-width-2 { 79 | transform: scale(2, 1); 80 | } 81 | 82 | .esc-width-3 { 83 | transform: scale(3, 1); 84 | } 85 | 86 | .esc-width-4 { 87 | transform: scale(4, 1); 88 | } 89 | 90 | .esc-width-5 { 91 | transform: scale(5, 1); 92 | } 93 | 94 | .esc-width-6 { 95 | transform: scale(6, 1); 96 | } 97 | 98 | .esc-width-7 { 99 | transform: scale(7, 1); 100 | } 101 | 102 | .esc-width-8 { 103 | transform: scale(8, 1); 104 | } 105 | 106 | .esc-height-2 { 107 | transform: scale(1, 2); 108 | margin-bottom: 1em; 109 | } 110 | 111 | .esc-height-3 { 112 | transform: scale(1, 3); 113 | margin-bottom: 2em; 114 | } 115 | 116 | .esc-height-4 { 117 | transform: scale(1, 4); 118 | margin-bottom: 3em; 119 | } 120 | 121 | .esc-height-5 { 122 | transform: scale(1, 5); 123 | margin-bottom: 4em; 124 | } 125 | 126 | .esc-height-6 { 127 | transform: scale(1, 6); 128 | margin-bottom: 5em; 129 | } 130 | 131 | .esc-height-7 { 132 | transform: scale(1, 7); 133 | margin-bottom: 6em; 134 | } 135 | 136 | .esc-height-8 { 137 | transform: scale(1, 8); 138 | margin-bottom: 7em; 139 | } 140 | 141 | .esc-width-2-height-2 { 142 | transform: scale(2, 2); 143 | margin-bottom: 1em; 144 | } 145 | 146 | .esc-width-2-height-3 { 147 | transform: scale(2, 3); 148 | margin-bottom: 2em; 149 | } 150 | 151 | .esc-width-2-height-4 { 152 | transform: scale(2, 4); 153 | margin-bottom: 3em; 154 | } 155 | 156 | .esc-width-2-height-5 { 157 | transform: scale(2, 5); 158 | margin-bottom: 4em; 159 | } 160 | 161 | .esc-width-2-height-6 { 162 | transform: scale(2, 6); 163 | margin-bottom: 5em; 164 | } 165 | 166 | .esc-width-2-height-7 { 167 | transform: scale(2, 7); 168 | margin-bottom: 6em; 169 | } 170 | 171 | .esc-width-2-height-8 { 172 | transform: scale(2, 8); 173 | margin-bottom: 7em; 174 | } 175 | 176 | .esc-width-3-height-2 { 177 | transform: scale(3, 2); 178 | margin-bottom: 1em; 179 | } 180 | 181 | .esc-width-3-height-3 { 182 | transform: scale(3, 3); 183 | margin-bottom: 2em; 184 | } 185 | 186 | .esc-width-3-height-4 { 187 | transform: scale(3, 4); 188 | margin-bottom: 3em; 189 | } 190 | 191 | .esc-width-3-height-5 { 192 | transform: scale(3, 5); 193 | margin-bottom: 4em; 194 | } 195 | 196 | .esc-width-3-height-6 { 197 | transform: scale(3, 6); 198 | margin-bottom: 5em; 199 | } 200 | 201 | .esc-width-3-height-7 { 202 | transform: scale(3, 7); 203 | margin-bottom: 6em; 204 | } 205 | 206 | .esc-width-3-height-8 { 207 | transform: scale(3, 8); 208 | margin-bottom: 7em; 209 | } 210 | 211 | .esc-width-4-height-2 { 212 | transform: scale(4, 2); 213 | margin-bottom: 1em; 214 | } 215 | 216 | .esc-width-4-height-3 { 217 | transform: scale(4, 3); 218 | margin-bottom: 2em; 219 | } 220 | 221 | .esc-width-4-height-4 { 222 | transform: scale(4, 4); 223 | margin-bottom: 3em; 224 | } 225 | 226 | .esc-width-4-height-5 { 227 | transform: scale(4, 5); 228 | margin-bottom: 4em; 229 | } 230 | 231 | .esc-width-4-height-6 { 232 | transform: scale(4, 6); 233 | margin-bottom: 5em; 234 | } 235 | 236 | .esc-width-4-height-7 { 237 | transform: scale(4, 7); 238 | margin-bottom: 6em; 239 | } 240 | 241 | .esc-width-4-height-8 { 242 | transform: scale(4, 8); 243 | margin-bottom: 7em; 244 | } 245 | 246 | .esc-width-5-height-2 { 247 | transform: scale(5, 2); 248 | margin-bottom: 1em; 249 | } 250 | 251 | .esc-width-5-height-3 { 252 | transform: scale(5, 3); 253 | margin-bottom: 2em; 254 | } 255 | 256 | .esc-width-5-height-4 { 257 | transform: scale(5, 4); 258 | margin-bottom: 3em; 259 | } 260 | 261 | .esc-width-5-height-5 { 262 | transform: scale(5, 5); 263 | margin-bottom: 4em; 264 | } 265 | 266 | .esc-width-5-height-6 { 267 | transform: scale(5, 6); 268 | margin-bottom: 5em; 269 | } 270 | 271 | .esc-width-5-height-7 { 272 | transform: scale(5, 7); 273 | margin-bottom: 6em; 274 | } 275 | 276 | .esc-width-5-height-8 { 277 | transform: scale(5, 8); 278 | margin-bottom: 7em; 279 | } 280 | 281 | .esc-width-6-height-2 { 282 | transform: scale(6, 2); 283 | margin-bottom: 1em; 284 | } 285 | 286 | .esc-width-6-height-3 { 287 | transform: scale(6, 3); 288 | margin-bottom: 2em; 289 | } 290 | 291 | .esc-width-6-height-4 { 292 | transform: scale(6, 4); 293 | margin-bottom: 3em; 294 | } 295 | 296 | .esc-width-6-height-5 { 297 | transform: scale(6, 5); 298 | margin-bottom: 4em; 299 | } 300 | 301 | .esc-width-6-height-6 { 302 | transform: scale(6, 6); 303 | margin-bottom: 5em; 304 | } 305 | 306 | .esc-width-6-height-7 { 307 | transform: scale(6, 7); 308 | margin-bottom: 6em; 309 | } 310 | 311 | .esc-width-6-height-8 { 312 | transform: scale(6, 8); 313 | margin-bottom: 7em; 314 | } 315 | 316 | .esc-width-7-height-2 { 317 | transform: scale(7, 2); 318 | margin-bottom: 1em; 319 | } 320 | 321 | .esc-width-7-height-3 { 322 | transform: scale(7, 3); 323 | margin-bottom: 2em; 324 | } 325 | 326 | .esc-width-7-height-4 { 327 | transform: scale(7, 4); 328 | margin-bottom: 3em; 329 | } 330 | 331 | .esc-width-7-height-5 { 332 | transform: scale(7, 5); 333 | margin-bottom: 4em; 334 | } 335 | 336 | .esc-width-7-height-6 { 337 | transform: scale(7, 6); 338 | margin-bottom: 5em; 339 | } 340 | 341 | .esc-width-7-height-7 { 342 | transform: scale(7, 7); 343 | margin-bottom: 6em; 344 | } 345 | 346 | .esc-width-7-height-8 { 347 | transform: scale(7, 8); 348 | margin-bottom: 7em; 349 | } 350 | 351 | .esc-width-8-height-2 { 352 | transform: scale(8, 2); 353 | margin-bottom: 1em; 354 | } 355 | 356 | .esc-width-8-height-3 { 357 | transform: scale(8, 3); 358 | margin-bottom: 2em; 359 | } 360 | 361 | .esc-width-8-height-4 { 362 | transform: scale(8, 4); 363 | margin-bottom: 3em; 364 | } 365 | 366 | .esc-width-8-height-5 { 367 | transform: scale(8, 5); 368 | margin-bottom: 4em; 369 | } 370 | 371 | .esc-width-8-height-6 { 372 | transform: scale(8, 6); 373 | margin-bottom: 5em; 374 | } 375 | 376 | .esc-width-8-height-7 { 377 | transform: scale(8, 7); 378 | margin-bottom: 6em; 379 | } 380 | 381 | .esc-width-8-height-8 { 382 | transform: scale(8, 8); 383 | margin-bottom: 7em; 384 | } 385 | 386 | .esc-bitimage { 387 | display: block; 388 | } 389 | --------------------------------------------------------------------------------