├── .github └── workflows │ └── tests.yml ├── LICENSE.md ├── README.md ├── composer.json ├── phpunit.xml.dist └── src ├── Command ├── Barcode.php ├── Bitmap.php ├── Clear.php ├── Concerns │ ├── Alignable.php │ ├── FontAware.php │ ├── ImageFallback.php │ ├── Invertible.php │ ├── Magnifiable.php │ ├── PositionAware.php │ └── Rotatable.php ├── ExternalImage.php ├── InternalImage.php ├── Raw.php ├── TextBlock.php └── TextLine.php ├── Compiler.php ├── CompilerFactory.php ├── Connector ├── ArrayConnector.php ├── FileConnector.php └── NetworkConnector.php ├── Contract ├── Command.php ├── Condition.php ├── Connector.php ├── Job.php ├── Label.php ├── Language.php └── Media.php ├── Emulation ├── Canvas.php ├── EmulateText.php ├── PbmCodec.php └── RllCodec.php ├── Enum ├── Anchor.php ├── Angle.php ├── Charset.php ├── Direction.php ├── TsplBpp.php └── Unit.php ├── Exception ├── CouldNotConnectToPrinter.php └── LabelPrinterException.php ├── Label ├── Batch.php ├── BooleanCondition.php ├── Element.php ├── Label.php ├── LanguageCondition.php └── Media.php ├── Language ├── Fingerprint.php ├── Fingerprint │ ├── Concerns │ │ ├── Align.php │ │ ├── Font.php │ │ ├── Invert.php │ │ ├── Magnify.php │ │ ├── Position.php │ │ └── Rotate.php │ ├── FingerprintBarcode.php │ ├── FingerprintBitmap.php │ ├── FingerprintClear.php │ ├── FingerprintExternalImage.php │ ├── FingerprintInternalImage.php │ ├── FingerprintRaw.php │ ├── FingerprintTextBlock.php │ └── FingerprintTextLine.php ├── Tspl.php └── Tspl │ ├── TsplBarcode.php │ ├── TsplBitmap.php │ ├── TsplClear.php │ ├── TsplInternalImage.php │ ├── TsplRaw.php │ ├── TsplTextBlock.php │ └── TsplTextLine.php └── Printer.php /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | tests: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | fail-fast: true 14 | matrix: 15 | php: [7.1, 7.2, 7.3, 7.4] 16 | stability: [prefer-lowest, prefer-stable] 17 | 18 | name: PHP ${{ matrix.php }} / ${{ matrix.stability }} 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | 24 | - name: Cache dependencies 25 | uses: actions/cache@v1 26 | with: 27 | path: ~/.composer/cache/files 28 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 29 | 30 | - name: Setup PHP 31 | uses: shivammathur/setup-php@v2 32 | with: 33 | php-version: ${{ matrix.php }} 34 | extensions: intl, json, libxml, soap 35 | coverage: xdebug 36 | 37 | - name: Validate composer.json and composer.lock 38 | run: composer validate 39 | 40 | - name: Install dependencies 41 | run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-suggest 42 | 43 | - name: Execute tests 44 | run: vendor/bin/phpunit --verbose --coverage-text --coverage-clover=coverage.clover 45 | 46 | - name: Prepare code coverage 47 | if: matrix.php == 7.4 && matrix.stability == 'prefer-stable' 48 | uses: wei/wget@v1.1.1 49 | with: 50 | args: -q https://scrutinizer-ci.com/ocular.phar 51 | 52 | - name: Code coverage 53 | if: matrix.php == 7.4 && matrix.stability == 'prefer-stable' 54 | run: php ocular.phar code-coverage:upload --format=php-clover coverage.clover 55 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © appwilio 4 | Copyright © JhaoDa 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

PhpAidc LabelPrinter

2 | 3 |

4 | Latest Version on Packagist 5 | Testing 6 | Quality Score 7 | Code Coverage 8 | Total Downloads 9 | License MIT 10 |

11 | 12 | PhpAidc LabelPrinter is a library that help you create and print labels on printers that support 13 | Direct Protocol, Fingerprint, TSPL/TSPL2 languages (Honeywell, Intermec, TSC) via TCP/IP. 14 | 15 | --- 16 | 17 | - [Requirements](#requirements) 18 | - [Installation](#installation) 19 | - [Basic usage](#basic-usage) 20 | 21 | ## Requirements 22 | - PHP 7.1+ 23 | - ext-mbstring 24 | 25 | ## Installation 26 | 27 | LabelPrinter is installed via [Composer](https://getcomposer.org/): 28 | ```bash 29 | composer require php-aidc/label-printer 30 | ``` 31 | 32 | You can of course also manually edit your composer.json file 33 | ```json 34 | { 35 | "require": { 36 | "php-aidc/label-printer": "v0.4" 37 | } 38 | } 39 | ``` 40 | 41 | ## Basic usage 42 | 43 | > Some TSPL2-like printers, such as Atol BP41/Rongta RP410, do not support all TSPL2 features. 44 | 45 | #### Read data from printer 46 | 47 | ```php 48 | use PhpAidc\LabelPrinter\Printer; 49 | use PhpAidc\LabelPrinter\Connector\NetworkConnector; 50 | 51 | $printer = new Printer(new NetworkConnector('192.168.x.x')); 52 | 53 | \var_dump($printer->ask('? VERSION$(0)')); 54 | 55 | // "Direct Protocol 10.15.017559 \r\n" 56 | ``` 57 | 58 | #### Create and print label 59 | ```php 60 | use PhpAidc\LabelPrinter\Enum\Unit; 61 | use PhpAidc\LabelPrinter\Enum\Anchor; 62 | use PhpAidc\LabelPrinter\Enum\Charset; 63 | use PhpAidc\LabelPrinter\Printer; 64 | use PhpAidc\LabelPrinter\Label\Label; 65 | use PhpAidc\LabelPrinter\Label\Element; 66 | use PhpAidc\LabelPrinter\CompilerFactory; 67 | use PhpAidc\LabelPrinter\Connector\NetworkConnector; 68 | 69 | $label = Label::create(Unit::MM(), 43, 25) 70 | ->charset(Charset::UTF8()) 71 | ->add(Element::textBlock(168, 95, 'Hello!', 'Univers', 8)->box(338, 100, 0)->anchor(Anchor::CENTER())) 72 | ->add(Element::barcode(10, 10, '123456', 'CODE93')->height(60)) 73 | ; 74 | 75 | (new Printer(new NetworkConnector('192.168.x.x'), CompilerFactory::tspl()))->print($label); 76 | ``` 77 | 78 | #### Add elements only for a specific language 79 | ```php 80 | use PhpAidc\LabelPrinter\Label\Label; 81 | use PhpAidc\LabelPrinter\Label\Element; 82 | use PhpAidc\LabelPrinter\Language\Tspl; 83 | use PhpAidc\LabelPrinter\Language\Fingerprint; 84 | 85 | $label = Label::create() 86 | ->for(Fingerprint::class, static function (Label $label) { 87 | $label->add(Element::textLine(168, 95, 'Hello!', 'Univers', 8)); 88 | }) 89 | ->for(Tspl::class, static function (Label $label) { 90 | $label->add(Element::textLine(10, 10, 'Hello!', 'ROMAN.TTF', 8)); 91 | }) 92 | ; 93 | ``` 94 | 95 | #### Add elements if some value is truthy 96 | ```php 97 | use PhpAidc\LabelPrinter\Label\Label; 98 | use PhpAidc\LabelPrinter\Label\Element; 99 | 100 | $text = ''; 101 | 102 | $label = Label::create() 103 | ->when($text, static function (Label $label, $text) { 104 | // will not be added until the $text is empty 105 | $label->add(Element::textLine(168, 95, $text, 'Univers', 8)); 106 | }) 107 | ; 108 | ``` 109 | 110 | #### Print images 111 | ```php 112 | use PhpAidc\LabelPrinter\Label\Label; 113 | use PhpAidc\LabelPrinter\Label\Element; 114 | use PhpAidc\LabelPrinter\Language\Tspl; 115 | use PhpAidc\LabelPrinter\Language\Fingerprint; 116 | 117 | $image = new \Imagick('gift.svg'); 118 | 119 | $label = Label::create() 120 | ->for(Fingerprint::class, static function (Label $label) { 121 | // from printer's memory — png, bmp, pcx 122 | $label->add(Element::intImage(10, 10, 'GLOBE.1')); 123 | // from filesystem 124 | $label->add(Element::extImage(10, 10, \realpath('alien.png'))); 125 | }) 126 | ->for(Tspl::class, static function (Label $label) { 127 | // from printer's memory — bmp, pcx 128 | $label->add(Element::intImage(10, 10, 'ALIEN.BMP')); 129 | }) 130 | // from filesystem via Imagick — any supported types 131 | ->add(Element::bitmap(50, 10, $image)) 132 | ; 133 | ``` 134 | 135 | #### Print text with emulation 136 | ```php 137 | use PhpAidc\LabelPrinter\Label\Label; 138 | use PhpAidc\LabelPrinter\Label\Element; 139 | 140 | $label = Label::create() 141 | ->add(Element::textLine(10, 10, 'Hello!', '/path/to/font/roboto.ttf', 20)->emulate()) 142 | ->add(Element::textBlock(100, 10, 'Hello again!', '/path/to/font/roboto.ttf', 20)->box(300, 20)->emulate()) 143 | ; 144 | ``` 145 | Text will be drawn with Imagick and printed as bitmap. 146 | 147 | #### Specify the number of copies 148 | ```php 149 | use PhpAidc\LabelPrinter\Label\Label; 150 | use PhpAidc\LabelPrinter\Label\Element; 151 | 152 | $label = Label::create() 153 | ->add(Element::textLine(168, 95, 'Hello!', 'Univers', 8)) 154 | ->copies(3) 155 | ; 156 | ``` 157 | 158 | #### Batch printing 159 | ```php 160 | use PhpAidc\LabelPrinter\Printer; 161 | use PhpAidc\LabelPrinter\Label\Batch; 162 | use PhpAidc\LabelPrinter\Label\Label; 163 | use PhpAidc\LabelPrinter\Label\Element; 164 | use PhpAidc\LabelPrinter\CompilerFactory; 165 | use PhpAidc\LabelPrinter\Connector\NetworkConnector; 166 | 167 | $batch = (new Batch()) 168 | ->add(Label::create()->add(Element::textLine(168, 95, 'Hello!', 'Univers', 8))) 169 | ->add(Label::create()->add(Element::textLine(168, 95, 'Bye!', 'Univers', 8))) 170 | ; 171 | 172 | (new Printer(new NetworkConnector('192.168.x.x'), CompilerFactory::fingerprint()))->print($label); 173 | ``` 174 | 175 | ## License 176 | 177 | The PhpAidc LabelPrinter is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT). 178 | 179 | > Some ideas taken from [mike42/escpos-php](https://github.com/mike42/escpos-php). 180 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-aidc/label-printer", 3 | "description": "Easily create and print labels on various label printers", 4 | "keywords": [ 5 | "DirectProtocol", "Fingerprint", "TSPL", "TSPL2", 6 | "Honeywell", "Intermec", "TSC", 7 | "aidc", "pos", "wms", "barcode", "label", "print", "printer" 8 | ], 9 | "homepage": "https://github.com/php-aidc/label-printer", 10 | "type": "library", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "JhaoDa", 15 | "email": "jhaoda@gmail.com" 16 | } 17 | ], 18 | "support": { 19 | "issues": "https://github.com/php-aidc/label-printer/issues" 20 | }, 21 | "require": { 22 | "php": ">=7.1", 23 | "ext-mbstring": "*", 24 | "myclabs/php-enum": "^1.7" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "~7.5" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "PhpAidc\\LabelPrinter\\": "src/", 32 | "PhpAidc\\LabelPrinter\\Tests\\Unit\\": "tests/Unit/" 33 | } 34 | }, 35 | "suggest": { 36 | "ext-imagick": "Required to print unsupported image formats, PDF and text with custom fonts." 37 | }, 38 | "config": { 39 | "sort-packages": true 40 | }, 41 | "prefer-stable": true 42 | } 43 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests/Unit 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Command/Barcode.php: -------------------------------------------------------------------------------- 1 | 1, 'wide' => 1]; 42 | 43 | /** @var bool */ 44 | private $readable = false; 45 | 46 | public function __construct(int $x, int $y, string $data, string $type) 47 | { 48 | $this->x = $x; 49 | $this->y = $y; 50 | $this->data = $data; 51 | $this->type = $type; 52 | } 53 | 54 | public function height(int $value) 55 | { 56 | $this->height = $value; 57 | 58 | return $this; 59 | } 60 | 61 | public function magnify(int $value) 62 | { 63 | $this->magnification = $value; 64 | 65 | return $this; 66 | } 67 | 68 | public function ratio(int $narrow, int $wide) 69 | { 70 | $this->ratio = \compact('narrow', 'wide'); 71 | 72 | return $this; 73 | } 74 | 75 | public function readable(bool $readable = true) 76 | { 77 | $this->readable = $readable; 78 | 79 | return $this; 80 | } 81 | public function getType(): string 82 | { 83 | return $this->type; 84 | } 85 | 86 | public function getData(): string 87 | { 88 | return $this->data; 89 | } 90 | 91 | public function getHeight(): ?int 92 | { 93 | return $this->height; 94 | } 95 | 96 | public function getMagnification(): ?int 97 | { 98 | return $this->magnification; 99 | } 100 | 101 | public function getRatio(): array 102 | { 103 | return $this->ratio; 104 | } 105 | 106 | public function isReadable(): bool 107 | { 108 | return $this->readable; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Command/Bitmap.php: -------------------------------------------------------------------------------- 1 | x = $x; 38 | $this->y = $y; 39 | $this->canvas = clone $canvas; 40 | $this->width = $this->canvas->getImageWidth(); 41 | $this->height = $this->canvas->getImageHeight(); 42 | 43 | $this->canvas->setImageType(\Imagick::IMGTYPE_TRUECOLOR); 44 | $this->canvas->thresholdImage(.5 * \Imagick::getQuantumRange()['quantumRangeLong']); 45 | } 46 | 47 | public function overlay($value) 48 | { 49 | $this->overlay = $value; 50 | 51 | return $this; 52 | } 53 | 54 | public function getOverlay() 55 | { 56 | return $this->overlay; 57 | } 58 | 59 | public function getCanvas() 60 | { 61 | return $this->canvas; 62 | } 63 | 64 | public function getWidth(): int 65 | { 66 | return $this->width; 67 | } 68 | 69 | public function getHeight(): int 70 | { 71 | return $this->height; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Command/Clear.php: -------------------------------------------------------------------------------- 1 | field = $field; 34 | } 35 | 36 | public function getField(): ?string 37 | { 38 | return $this->field; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Command/Concerns/Alignable.php: -------------------------------------------------------------------------------- 1 | anchor = $anchor; 27 | 28 | return $this; 29 | } 30 | 31 | public function getAnchor(): ?Anchor 32 | { 33 | return $this->anchor; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Command/Concerns/FontAware.php: -------------------------------------------------------------------------------- 1 | fontName; 28 | } 29 | 30 | public function getFontSize() 31 | { 32 | return $this->fontSize; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Command/Concerns/ImageFallback.php: -------------------------------------------------------------------------------- 1 | emulate = true; 25 | 26 | return $this; 27 | } 28 | 29 | public function shouldEmulate(): bool 30 | { 31 | return $this->emulate; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Command/Concerns/Invertible.php: -------------------------------------------------------------------------------- 1 | invert = $value; 25 | 26 | return $this; 27 | } 28 | 29 | public function isInverted(): bool 30 | { 31 | return (bool) $this->invert; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Command/Concerns/Magnifiable.php: -------------------------------------------------------------------------------- 1 | magnificationWidth = $width; 26 | $this->magnificationHeight = $height; 27 | 28 | return $this; 29 | } 30 | 31 | public function getMagnificationWidth(): int 32 | { 33 | return $this->magnificationWidth; 34 | } 35 | 36 | public function getMagnificationHeight(): int 37 | { 38 | return $this->magnificationHeight; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Command/Concerns/PositionAware.php: -------------------------------------------------------------------------------- 1 | x; 28 | } 29 | 30 | public function getY(): int 31 | { 32 | return $this->y; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Command/Concerns/Rotatable.php: -------------------------------------------------------------------------------- 1 | angle = $angle; 27 | 28 | return $this; 29 | } 30 | 31 | public function getRotation(): Angle 32 | { 33 | return $this->angle ?? Angle::_0(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Command/ExternalImage.php: -------------------------------------------------------------------------------- 1 | x = $x; 38 | $this->y = $y; 39 | $this->filename = $filename; 40 | } 41 | 42 | public function getData(): string 43 | { 44 | return $this->read($this->filename); 45 | } 46 | 47 | private function read($data): string 48 | { 49 | if (\is_string($data)) { 50 | $data = new \SplFileInfo($data); 51 | } 52 | 53 | if ($data instanceof \SplFileInfo) { 54 | if (!$data->isReadable()) { 55 | throw new \RuntimeException(); 56 | } 57 | 58 | return \file_get_contents($data->getRealPath()); 59 | } 60 | 61 | throw new \InvalidArgumentException(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Command/InternalImage.php: -------------------------------------------------------------------------------- 1 | x = $x; 46 | $this->y = $y; 47 | $this->name = $name; 48 | } 49 | 50 | /** 51 | * (TSPL) Bits per pixel. 52 | * 53 | * @param TsplBpp $bpp 54 | * 55 | * @return $this 56 | */ 57 | public function bpp(TsplBpp $bpp) 58 | { 59 | $this->bpp = $bpp; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * (TSPL) Contrast of grayscale graphic. 66 | * 67 | * @param int $value 68 | * 69 | * @return $this 70 | */ 71 | public function contrast(int $value) 72 | { 73 | $this->contrast = $value; 74 | 75 | return $this; 76 | } 77 | 78 | public function getBpp(): ?TsplBpp 79 | { 80 | return $this->bpp; 81 | } 82 | 83 | public function getContrast(): ?int 84 | { 85 | return $this->contrast; 86 | } 87 | 88 | public function getName(): string 89 | { 90 | return $this->name; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Command/Raw.php: -------------------------------------------------------------------------------- 1 | data = [$data]; 34 | } else { 35 | $this->data = $data; 36 | } 37 | } 38 | 39 | public function getData(): iterable 40 | { 41 | return $this->data; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Command/TextBlock.php: -------------------------------------------------------------------------------- 1 | null, 'height' => null, 'border' => 0]; 43 | 44 | public function __construct(int $x, int $y, string $text, string $font, float $size = null) 45 | { 46 | $this->x = $x; 47 | $this->y = $y; 48 | $this->text = $text; 49 | $this->fontName = $font; 50 | $this->fontSize = $size; 51 | } 52 | 53 | public function box(int $width, int $height, int $border = 0) 54 | { 55 | $this->box = \compact('width', 'height', 'border'); 56 | 57 | return $this; 58 | } 59 | 60 | public function spacing(int $dots) 61 | { 62 | $this->spacing = $dots; 63 | 64 | return $this; 65 | } 66 | 67 | public function getBoxBorder(): int 68 | { 69 | return $this->box['border']; 70 | } 71 | 72 | public function getBoxWidth(): ?int 73 | { 74 | return $this->box['width']; 75 | } 76 | 77 | public function getBoxHeight(): ?int 78 | { 79 | return $this->box['height']; 80 | } 81 | 82 | public function getText(): string 83 | { 84 | return $this->text; 85 | } 86 | 87 | public function getSpacing(): ?int 88 | { 89 | return $this->spacing; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Command/TextLine.php: -------------------------------------------------------------------------------- 1 | x = $x; 45 | $this->y = $y; 46 | $this->text = $text; 47 | $this->fontName = $font; 48 | $this->fontSize = $size; 49 | } 50 | 51 | public function maxWidth(int $value) 52 | { 53 | $this->maxWidth = $value; 54 | 55 | return $this; 56 | } 57 | 58 | public function getText(): string 59 | { 60 | return $this->text; 61 | } 62 | 63 | public function getMaxWidth(): ?int 64 | { 65 | return $this->maxWidth; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Compiler.php: -------------------------------------------------------------------------------- 1 | language = $language; 38 | } 39 | 40 | public function compile(Job $job): string 41 | { 42 | $instructions = []; 43 | 44 | if ($job instanceof Label) { 45 | $instructions[] = $this->compileLabel($job); 46 | } 47 | 48 | if ($job instanceof Batch) { 49 | $instructions[] = $this->compileBatch($job); 50 | } 51 | 52 | $payload = \array_reduce($instructions, static function ($carry, $item) { 53 | return $item instanceof \Generator 54 | ? \array_merge($carry, \iterator_to_array($item, false)) 55 | : \array_merge($carry, $item); 56 | }, []); 57 | 58 | return \implode($payload); 59 | } 60 | 61 | private function compileBatch(Batch $batch): \Generator 62 | { 63 | foreach ($batch as $label) { 64 | yield from $this->compileLabel($label); 65 | } 66 | } 67 | 68 | private function compileLabel(Label $label): \Generator 69 | { 70 | yield from $this->language->compileDeclaration($label); 71 | 72 | yield from $this->compileStatements($label); 73 | 74 | yield from $this->language->compilePrint($label->getCopies()); 75 | } 76 | 77 | private function compileStatements(Label $label): \Generator 78 | { 79 | foreach ($label as $statement) { 80 | yield from $this->compileStatement($label, $statement); 81 | } 82 | } 83 | 84 | private function compileStatement(Label $label, $statement): \Generator 85 | { 86 | if ($statement instanceof LanguageCondition && $statement->isMatch(\get_class($this->language))) { 87 | yield from $this->compileStatements($statement->apply((clone $label)->erase())); 88 | 89 | return; 90 | } 91 | 92 | if ($statement instanceof BooleanCondition && $statement->isTruthy()) { 93 | yield from $this->compileStatements($statement->apply((clone $label)->erase())); 94 | 95 | return; 96 | } 97 | 98 | if ($statement instanceof Command && $this->language->isSupport($statement)) { 99 | yield from $this->language->compileCommand($statement); 100 | 101 | return; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/CompilerFactory.php: -------------------------------------------------------------------------------- 1 | buffer)) { 27 | return; 28 | } 29 | 30 | $this->buffer = []; 31 | } 32 | 33 | public function close(): void 34 | { 35 | $this->buffer = null; 36 | } 37 | 38 | public function write($data): int 39 | { 40 | $this->open(); 41 | 42 | $this->buffer[] = $data; 43 | 44 | return \strlen($data); 45 | } 46 | 47 | public function read(int $length = 0) 48 | { 49 | throw new \BadMethodCallException('Not applicable.'); 50 | } 51 | 52 | public function get(): array 53 | { 54 | return $this->buffer; 55 | } 56 | 57 | public function __destruct() 58 | { 59 | $this->close(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Connector/FileConnector.php: -------------------------------------------------------------------------------- 1 | file = $file; 31 | } 32 | 33 | public function open(): void 34 | { 35 | if (\is_resource($this->handle)) { 36 | return; 37 | } 38 | 39 | $this->handle = \fopen($this->file, 'w+b'); 40 | 41 | if ($this->handle === false) { 42 | throw CouldNotConnectToPrinter::becauseCannotOpenFile($this->file); 43 | } 44 | } 45 | 46 | public function close(): void 47 | { 48 | if (\is_resource($this->handle)) { 49 | \fclose($this->handle); 50 | 51 | $this->handle = null; 52 | } 53 | } 54 | 55 | public function write($data): int 56 | { 57 | $this->open(); 58 | 59 | return \fwrite($this->handle, $data, \strlen($data)); 60 | } 61 | 62 | public function read(int $length = 0) 63 | { 64 | $this->open(); 65 | 66 | return \fread($this->handle, $length); 67 | } 68 | 69 | public function __destruct() 70 | { 71 | $this->close(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Connector/NetworkConnector.php: -------------------------------------------------------------------------------- 1 | host = $host; 40 | $this->port = $port; 41 | $this->timeout = $timeout; 42 | } 43 | 44 | public function open(): void 45 | { 46 | if (\is_resource($this->handle)) { 47 | return; 48 | } 49 | 50 | $this->setErrorHandler(); 51 | 52 | try { 53 | $this->handle = \stream_socket_client("tcp://{$this->host}:{$this->port}", $severity, $message, $this->timeout); 54 | 55 | $this->flushErrors(); 56 | 57 | \stream_set_timeout($this->handle, $this->timeout, 0); 58 | } catch (\ErrorException $e) { 59 | throw CouldNotConnectToPrinter::becauseNetworkError($e->getMessage()); 60 | } 61 | } 62 | 63 | public function write($data): int 64 | { 65 | $this->open(); 66 | 67 | $this->setErrorHandler(); 68 | 69 | $result = \fwrite($this->handle, $data, \strlen($data)); 70 | 71 | $this->flushErrors(); 72 | 73 | return $result; 74 | } 75 | 76 | public function read(int $length = 0) 77 | { 78 | $this->open(); 79 | 80 | $result = ''; 81 | 82 | while (true) { 83 | $this->setErrorHandler(); 84 | 85 | $buffer = \fread($this->handle, 8192); 86 | 87 | $this->flushErrors(); 88 | 89 | $result .= $buffer; 90 | 91 | if (\mb_strlen($buffer) < 8192) { 92 | break; 93 | } 94 | } 95 | 96 | return $result; 97 | } 98 | 99 | public function close(): void 100 | { 101 | if (\is_resource($this->handle)) { 102 | $this->setErrorHandler(); 103 | 104 | \fclose($this->handle); 105 | 106 | $this->flushErrors(); 107 | 108 | $this->handle = null; 109 | } 110 | } 111 | 112 | public function __destruct() 113 | { 114 | $this->close(); 115 | } 116 | 117 | private function setErrorHandler(): void 118 | { 119 | $this->lastError = null; 120 | 121 | \set_error_handler(function ($severity, $message, $file, $line) { 122 | $this->lastError = \compact('severity', 'message', 'file', 'line'); 123 | }); 124 | } 125 | 126 | private function flushErrors(): void 127 | { 128 | \restore_error_handler(); 129 | 130 | if ($this->lastError) { 131 | throw new \ErrorException( 132 | $this->lastError['message'], 133 | 0, 134 | $this->lastError['severity'], 135 | $this->lastError['file'], 136 | $this->lastError['line'] 137 | ); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Contract/Command.php: -------------------------------------------------------------------------------- 1 | width = $width; 34 | $this->height = $height; 35 | 36 | $this->image = new \Imagick(); 37 | } 38 | 39 | public function write( 40 | string $text, 41 | string $font, 42 | float $size, 43 | Angle $rotation, 44 | ?Anchor $anchor, 45 | bool $inverted = false, 46 | ?int $spacing = null 47 | ): void { 48 | $canvas = new \ImagickDraw(); 49 | 50 | $canvas->setFont($font); 51 | $canvas->setFontSize($size); 52 | $canvas->setTextAntialias(true); 53 | 54 | $canvas->setGravity($anchor ? $anchor->getValue() : \Imagick::GRAVITY_NORTHWEST); 55 | 56 | if ($spacing !== null) { 57 | $canvas->setTextInterLineSpacing($spacing); 58 | } 59 | 60 | if ($inverted) { 61 | $foreground = new \ImagickPixel('#FFF'); 62 | $background = new \ImagickPixel('#00000000'); 63 | 64 | $canvas->setTextUnderColor(new \ImagickPixel('#000000')); 65 | } else { 66 | $foreground = new \ImagickPixel('#000'); 67 | $background = new \ImagickPixel('#FFF'); 68 | } 69 | 70 | $canvas->setFillColor($foreground); 71 | 72 | $metrics = $this->image->queryFontMetrics($canvas, $text, true); 73 | 74 | if ($metrics['textWidth'] > $this->width) { 75 | [$realWidth, $lines] = $this->splitText($canvas, $text); 76 | 77 | $realWidth = $this->width ?? $realWidth; 78 | $realHeight = $this->height ?? $metrics['textHeight'] * \count($lines); 79 | 80 | $text = \implode("\n", $lines); 81 | } else { 82 | $metrics = $this->image->queryFontMetrics($canvas, $text, false); 83 | 84 | $realWidth = \max($this->width, $metrics['textWidth']); 85 | $realHeight = \max($this->height, $metrics['textHeight']); 86 | } 87 | 88 | $canvas->annotation(0, 0, $text); 89 | 90 | $this->image->newImage((int) $realWidth, (int) $realHeight, $background); 91 | 92 | $this->image->drawImage($canvas); 93 | 94 | if ($rotation->getDegrees()) { 95 | $this->image->rotateImage($background, $rotation->getDegrees()); 96 | } 97 | } 98 | 99 | public function toImage(): \Imagick 100 | { 101 | $this->image->setImageFormat('png'); 102 | 103 | $this->image->setImageType(\Imagick::IMGTYPE_GRAYSCALE); 104 | 105 | return $this->image; 106 | } 107 | 108 | private function splitText(\ImagickDraw $canvas, string $text): array 109 | { 110 | $words = \preg_split('~\s~u', \trim($text), -1, \PREG_SPLIT_NO_EMPTY); 111 | 112 | if (empty($words)) { 113 | return [ 114 | $this->image->queryFontMetrics($canvas, $text)['textWidth'], 115 | [$text], 116 | ]; 117 | } 118 | 119 | $i = 1; 120 | $lines = []; 121 | $lineWidth = []; 122 | 123 | while(\count($words) > 0) { 124 | $metrics = $this->image->queryFontMetrics($canvas, \implode(' ', \array_slice($words, 0, $i))); 125 | 126 | if ($metrics['textWidth'] > $this->width || \count($words) < $i) { 127 | $lineWidth[] = $metrics['textWidth']; 128 | 129 | $lines[] = \implode(' ', \array_slice($words, 0, \max($i - 1, 1))); 130 | 131 | $words = \array_slice($words, \max($i - 1, 1)); 132 | 133 | $i = 0; 134 | } 135 | 136 | $i++; 137 | } 138 | 139 | return [\max($lineWidth), $lines]; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Emulation/EmulateText.php: -------------------------------------------------------------------------------- 1 | write( 35 | $command->getText(), 36 | $command->getFontName(), 37 | (float) $command->getFontSize(), 38 | $command->getRotation(), 39 | $command->getAnchor(), 40 | $command->isInverted(), 41 | $spacing 42 | ); 43 | 44 | return $canvas->toImage(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Emulation/PbmCodec.php: -------------------------------------------------------------------------------- 1 | image = $image; 36 | 37 | $this->bytesPerRow = \intdiv($image->getImageWidth() + 7, 8); 38 | 39 | $this->buffer = \array_fill(0, $this->bytesPerRow * $image->getImageHeight(), 0); 40 | } 41 | 42 | public function encode(): string 43 | { 44 | foreach ((new \ImagickPixelIterator($this->image)) as $y => $row) { 45 | $bit = $byte = 0; 46 | 47 | /** @var \ImagickPixel[] $row */ 48 | foreach ($row as $x => $pixel) { 49 | $color = $pixel->getColor(); 50 | 51 | $value = (int) (($color['r'] + $color['g'] + $color['b']) / 3) >> 7; 52 | 53 | $bit = $x % 8; 54 | 55 | $byte = ($y * $this->bytesPerRow) + \intdiv($x, 8); 56 | 57 | if ($value === 0) { 58 | $this->buffer[$byte] &= ~(1 << (7 - $bit)); 59 | } else { 60 | $this->buffer[$byte] |= (1 << (7 - $bit)); 61 | } 62 | } 63 | 64 | if ($bit !== 7) { 65 | $this->buffer[$byte] ^= (1 << (7 - $bit)) - 1; 66 | } 67 | } 68 | 69 | return \pack('C*', ...$this->buffer); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Emulation/RllCodec.php: -------------------------------------------------------------------------------- 1 | image = $image; 33 | } 34 | 35 | public function encode(): string 36 | { 37 | return \pack('C*', ...\array_merge(...$this->compressLines($this->scan()))); 38 | } 39 | 40 | private function scan(): \Generator 41 | { 42 | foreach ((new \ImagickPixelIterator($this->image)) as $y => $line) { 43 | $buffer = \array_reduce($line, static function ($carry, $pixel) { 44 | $color = $pixel->getColor(); 45 | 46 | $carry[] = 1 - ((int) (($color['r'] + $color['g'] + $color['b']) / 3) >> 7); 47 | 48 | return $carry; 49 | }, []); 50 | 51 | yield $this->compressRuns($buffer); 52 | } 53 | } 54 | 55 | private function compressRuns(array $line): array 56 | { 57 | $prev = null; 58 | $buffer = []; 59 | $runLength = 0; 60 | $size = \count($line); 61 | 62 | foreach ($line as $i => $byte) { 63 | if ($prev === null || $prev === $byte) { 64 | $runLength++; 65 | } else { 66 | $this->encodeRun($buffer, $runLength); 67 | 68 | $runLength = 1; 69 | } 70 | 71 | if ($i + 1 === $size) { 72 | $this->encodeRun($buffer, $runLength); 73 | } 74 | 75 | $prev = $byte; 76 | } 77 | 78 | if ($line[0] === 1) { 79 | \array_unshift($buffer, 0); 80 | } 81 | 82 | if (\end($line) === 1) { 83 | $buffer[] = 0; 84 | } 85 | 86 | return $buffer; 87 | } 88 | 89 | private function encodeRun(array &$buffer, int $length) 90 | { 91 | if ($length < self::MAX_RUN_LENGTH) { 92 | $buffer[] = \max($length, 1); 93 | 94 | return; 95 | } 96 | 97 | \array_map(static function () use (&$buffer) { 98 | $buffer[] = self::MAX_RUN_LENGTH; 99 | $buffer[] = 0; 100 | }, \range(1, \intdiv($length, self::MAX_RUN_LENGTH))); 101 | 102 | $buffer[] = $length % self::MAX_RUN_LENGTH; 103 | } 104 | 105 | private function compressLines(\Generator $lines): array 106 | { 107 | $count = 1; 108 | $line = []; 109 | $buffer = []; 110 | $prev = $lines->current(); 111 | 112 | $lines->next(); 113 | 114 | while ($lines->valid()) { 115 | $line = $lines->current(); 116 | 117 | if ($prev === $line) { 118 | $count++; 119 | 120 | $prev = $line; 121 | 122 | $lines->next(); 123 | 124 | continue; 125 | } 126 | 127 | $this->encodeLineRepetitions($buffer, $prev, $count); 128 | 129 | $count = 1; 130 | 131 | $prev = $line; 132 | 133 | $lines->next(); 134 | } 135 | 136 | $this->encodeLineRepetitions($buffer, $line, $count); 137 | 138 | return $buffer; 139 | } 140 | 141 | private function encodeLineRepetitions(array &$buffer, array $line, int $count): void 142 | { 143 | if ($count <= 1) { 144 | $buffer[] = $line; 145 | } elseif ($count > 1 && $count <= self::MAX_LINE_REPETITIONS) { 146 | $buffer[] = $this->buildLineRepetition($line, $count); 147 | } else { 148 | \array_map(function () use (&$buffer, $line) { 149 | $buffer[] = $this->buildLineRepetition($line, self::MAX_LINE_REPETITIONS); 150 | }, \range(1, \intdiv($count, self::MAX_LINE_REPETITIONS))); 151 | 152 | $buffer[] = $this->buildLineRepetition($line, $count % self::MAX_LINE_REPETITIONS); 153 | } 154 | } 155 | 156 | private function buildLineRepetition(array $line, int $count): array 157 | { 158 | return \array_merge([-$count], $line, [-$count]); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Enum/Anchor.php: -------------------------------------------------------------------------------- 1 | getValue() * 90; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Enum/Charset.php: -------------------------------------------------------------------------------- 1 | labels[] = $label; 27 | 28 | return $this; 29 | } 30 | 31 | public function getIterator() 32 | { 33 | return new \ArrayIterator($this->labels); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Label/BooleanCondition.php: -------------------------------------------------------------------------------- 1 | value = $value; 31 | $this->callback = $callback; 32 | } 33 | 34 | public function isTruthy(): bool 35 | { 36 | return (bool) $this->value; 37 | } 38 | 39 | public function apply(LabelContract $label): LabelContract 40 | { 41 | \call_user_func($this->callback, $label, $this->value); 42 | 43 | return $label; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Label/Element.php: -------------------------------------------------------------------------------- 1 | media = new Media($unit, $width, $height); 54 | } 55 | 56 | public function add(Command $command) 57 | { 58 | $this->statements[] = $command; 59 | 60 | return $this; 61 | } 62 | 63 | public function charset(Charset $value) 64 | { 65 | $this->charset = $value; 66 | 67 | return $this; 68 | } 69 | 70 | public function copies(int $copies) 71 | { 72 | $this->copies = $copies; 73 | 74 | return $this; 75 | } 76 | 77 | public function direction(Direction $value) 78 | { 79 | $this->direction = $value; 80 | 81 | return $this; 82 | } 83 | 84 | public function getCharset(): ?Charset 85 | { 86 | return $this->charset; 87 | } 88 | 89 | public function getCopies(): int 90 | { 91 | return $this->copies; 92 | } 93 | 94 | public function getDirection(): ?Direction 95 | { 96 | return $this->direction; 97 | } 98 | 99 | public function getMedia(): MediaContract 100 | { 101 | return $this->media; 102 | } 103 | 104 | public function getWidth(): ?float 105 | { 106 | return $this->media['width'] ?? null; 107 | } 108 | 109 | public function getHeight(): ?float 110 | { 111 | return $this->media['height'] ?? null; 112 | } 113 | 114 | public function erase() 115 | { 116 | $this->statements = []; 117 | 118 | return $this; 119 | } 120 | 121 | /** 122 | * Apply the callback if the languages match. 123 | * 124 | * @param string $language 125 | * @param callable $callback 126 | * 127 | * @return $this 128 | */ 129 | public function for(string $language, callable $callback) 130 | { 131 | $this->statements[] = new LanguageCondition($language, $callback); 132 | 133 | return $this; 134 | } 135 | 136 | /** 137 | * Apply the callback if the value is truthy. 138 | * 139 | * @param mixed $value 140 | * @param callable $callback 141 | * 142 | * @return $this 143 | */ 144 | public function when($value, callable $callback) 145 | { 146 | $this->statements[] = new BooleanCondition($value, $callback); 147 | 148 | return $this; 149 | } 150 | 151 | public function getIterator() 152 | { 153 | return new \ArrayIterator($this->statements); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Label/LanguageCondition.php: -------------------------------------------------------------------------------- 1 | language = $language; 31 | $this->callback = $callback; 32 | } 33 | 34 | public function isMatch(string $language): bool 35 | { 36 | return $this->language === $language; 37 | } 38 | 39 | public function apply(LabelContract $label): LabelContract 40 | { 41 | \call_user_func($this->callback, $label); 42 | 43 | return $label; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Label/Media.php: -------------------------------------------------------------------------------- 1 | unit = $unit; 34 | $this->width = $width; 35 | $this->height = $height; 36 | } 37 | 38 | public function getUnit(): ?Unit 39 | { 40 | return $this->unit; 41 | } 42 | 43 | public function getWidth(): ?float 44 | { 45 | return $this->width; 46 | } 47 | 48 | public function getHeight(): ?float 49 | { 50 | return $this->height; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Language/Fingerprint.php: -------------------------------------------------------------------------------- 1 | Handlers\FingerprintRaw::class, 44 | Clear::class => Handlers\FingerprintClear::class, 45 | Bitmap::class => Handlers\FingerprintBitmap::class, 46 | Barcode::class => Handlers\FingerprintBarcode::class, 47 | TextLine::class => Handlers\FingerprintTextLine::class, 48 | TextBlock::class => Handlers\FingerprintTextBlock::class, 49 | ExternalImage::class => Handlers\FingerprintExternalImage::class, 50 | InternalImage::class => Handlers\FingerprintInternalImage::class, 51 | ]; 52 | 53 | /** @var float */ 54 | private $density; 55 | 56 | public function __construct(float $density) 57 | { 58 | $this->density = $density; 59 | } 60 | 61 | public function compileDeclaration(Label $label): iterable 62 | { 63 | yield from $this->translateMedia($label->getMedia()); 64 | yield from $this->translateCharset($label->getCharset()); 65 | } 66 | 67 | public function isSupport(Command $command): bool 68 | { 69 | return isset(self::HANDLERS[\get_class($command)]); 70 | } 71 | 72 | public function compileCommand(Command $command): iterable 73 | { 74 | if ($this->isSupport($command)) { 75 | $class = self::HANDLERS[\get_class($command)]; 76 | 77 | $handler = new $class(); 78 | 79 | foreach ((new $handler())->translate($command) as $instruction) { 80 | yield $instruction.self::EOC; 81 | } 82 | } else { 83 | // throw exception 84 | yield null; 85 | } 86 | } 87 | 88 | public function compilePrint(int $copies): iterable 89 | { 90 | if ($copies <= 0) { 91 | throw new \InvalidArgumentException('Number of copies must be greather than 0.'); 92 | } 93 | 94 | yield "PF {$copies}".self::EOC; 95 | } 96 | 97 | private function translateCharset(?Charset $charset): iterable 98 | { 99 | if ($charset) { 100 | yield \sprintf('NASC "%s"', $charset->getValue()).self::EOC; 101 | } 102 | } 103 | 104 | private function translateMedia(Media $media): iterable 105 | { 106 | if ($media->getWidth()) { 107 | yield \sprintf('SETUP "MEDIA,MEDIA SIZE,WIDTH,%d'.self::EOC, $this->valueToDots($media->getWidth(), $media->getUnit())); 108 | } 109 | 110 | if ($media->getHeight()) { 111 | yield \sprintf('SETUP "MEDIA,MEDIA SIZE,HEIGHT,%d'.self::EOC, $this->valueToDots($media->getHeight(), $media->getUnit())); 112 | } 113 | } 114 | 115 | private function valueToDots(float $value, Unit $unit): int 116 | { 117 | if ($unit->equals(Unit::DOT())) { 118 | return (int) $value; 119 | } 120 | 121 | if ($unit->equals(Unit::IN())) { 122 | return (int) \round($this->density * $value); 123 | } 124 | 125 | return (int) \round(($this->density / 2.54 / 10) * $value); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/Concerns/Align.php: -------------------------------------------------------------------------------- 1 | getAnchor() && !$command->getAnchor()->equals(Anchor::SOUTHWEST())) { 28 | $anchor = $command->getAnchor()->getValue(); 29 | 30 | $newAnchor = $anchor + 6; 31 | 32 | if ($newAnchor > 9) { 33 | $newAnchor = $anchor > 6 ? $anchor - 6 : $anchor; 34 | } 35 | 36 | yield "AN {$newAnchor}"; 37 | } 38 | } 39 | 40 | public function resetAlign(Command $command) 41 | { 42 | /** @var Command|Alignable $command */ 43 | if ($command->getAnchor() && !$command->getAnchor()->equals(Anchor::SOUTHWEST())) { 44 | yield 'AN '.Fingerprint::DEFAULT_ANCHOR; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/Concerns/Font.php: -------------------------------------------------------------------------------- 1 | getFontName()); 26 | 27 | if ($command->getFontSize()) { 28 | $font .= ",".(int) $command->getFontSize(); 29 | } 30 | 31 | yield $font; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/Concerns/Invert.php: -------------------------------------------------------------------------------- 1 | isInverted()) { 26 | yield 'II'; 27 | } 28 | } 29 | 30 | public function resetInvert(Command $command) 31 | { 32 | /** @var Command|Invertible $command */ 33 | if ($command->isInverted()) { 34 | yield 'NI'; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/Concerns/Magnify.php: -------------------------------------------------------------------------------- 1 | getMagnificationWidth() > 1 || $command->getMagnificationHeight() > 1) { 26 | yield "MAG {$command->getMagnificationHeight()},{$command->getMagnificationWidth()}"; 27 | } 28 | } 29 | 30 | public function resetMagnify(Command $command) 31 | { 32 | /** @var Command|Magnifiable $command */ 33 | if ($command->getMagnificationWidth() > 1 || $command->getMagnificationHeight() > 1) { 34 | yield \sprintf('MAG %d,%d', 1, 1); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/Concerns/Position.php: -------------------------------------------------------------------------------- 1 | getX()},{$command->getY()}"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/Concerns/Rotate.php: -------------------------------------------------------------------------------- 1 | getRotation()->equals(Angle::_0())) { 28 | yield \sprintf('DIR %d', $command->getRotation()->getValue() + 1); 29 | } 30 | } 31 | 32 | public function resetRotate(Command $command) 33 | { 34 | /** @var Command|Rotatable $command */ 35 | if (!$command->getRotation()->equals(Angle::_0())) { 36 | yield 'DIR '.Fingerprint::DEFAULT_DIRECTION; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/FingerprintBarcode.php: -------------------------------------------------------------------------------- 1 | position($command); 34 | 35 | yield from $this->rotate($command); 36 | 37 | yield from $this->align($command); 38 | 39 | yield \sprintf('BT "%s"', $command->getType()); 40 | 41 | // TODO: ratio 42 | //yield \sprintf('BR %d,%d', $ratio['wide'], $ratio['narrow']); 43 | 44 | $magnification = $command->getMagnification(); 45 | 46 | if ($magnification > 0 && $magnification !== Fingerprint::DEFAULT_BARCODE_MAGNIFICATION) { 47 | yield "BM {$command->getMagnification()}"; 48 | } 49 | 50 | if ($command->getHeight() > 0) { 51 | yield \sprintf('BH %d', $command->getHeight()); 52 | } 53 | 54 | yield 'BF '.($command->isReadable() ? 'ON' : 'OFF'); 55 | 56 | yield \sprintf('PB "%s"', $command->getData()); 57 | 58 | // reset 59 | yield from $this->resetAlign($command); 60 | yield from $this->resetRotate($command); 61 | 62 | if ($command->getHeight() !== Fingerprint::DEFAULT_BARCODE_HEIGHT) { 63 | yield \sprintf('BH %d', Fingerprint::DEFAULT_BARCODE_HEIGHT); 64 | } 65 | 66 | if ($magnification > 0 && $magnification !== Fingerprint::DEFAULT_BARCODE_MAGNIFICATION) { 67 | yield \sprintf('BM %d', Fingerprint::DEFAULT_BARCODE_MAGNIFICATION); 68 | } 69 | 70 | if (! $command->isReadable()) { 71 | yield 'BF ON'; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/FingerprintBitmap.php: -------------------------------------------------------------------------------- 1 | position($command); 30 | 31 | $data = RllCodec::create($command->getCanvas())->encode(); 32 | 33 | $stream = \pack('n*', self::PRBUF_HEADER, $command->getWidth(), $command->getHeight()).$data; 34 | 35 | yield \sprintf("PRBUF %d\n", \strlen($stream)).$stream; 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/FingerprintClear.php: -------------------------------------------------------------------------------- 1 | getField() ? " {$command->getField()}%" : ''); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/FingerprintExternalImage.php: -------------------------------------------------------------------------------- 1 | position($command); 31 | 32 | yield from $this->invert($command); 33 | 34 | yield from $this->rotate($command); 35 | 36 | yield \sprintf("PRBUF %d\n", \strlen($data = $command->getData())).$data; 37 | 38 | // reset 39 | yield from $this->resetInvert($command); 40 | yield from $this->resetRotate($command); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/FingerprintInternalImage.php: -------------------------------------------------------------------------------- 1 | position($command); 33 | 34 | yield $this->rotate($command); 35 | 36 | yield $this->magnify($command); 37 | 38 | yield "PM {$command->getName()}"; 39 | 40 | // reset 41 | yield from $this->resetInvert($command); 42 | yield from $this->resetRotate($command); 43 | yield from $this->resetMagnify($command); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/FingerprintRaw.php: -------------------------------------------------------------------------------- 1 | getData(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/FingerprintTextBlock.php: -------------------------------------------------------------------------------- 1 | shouldEmulate()) { 38 | $image = $this->emulate( 39 | $command, 40 | $command->getBoxWidth(), 41 | $command->getBoxHeight(), 42 | $command->getSpacing() 43 | ); 44 | 45 | yield from (new FingerprintBitmap())->translate( 46 | new Bitmap($command->getX(), $command->getY(), $image) 47 | ); 48 | 49 | return; 50 | } 51 | 52 | yield from $this->position($command); 53 | 54 | yield from $this->rotate($command); 55 | yield from $this->invert($command); 56 | 57 | yield from $this->align($command); 58 | 59 | yield from $this->font($command); 60 | 61 | yield \vsprintf('PX %d,%d,%d,"%s",0,%d', [ 62 | $command->getBoxHeight(), 63 | $command->getBoxWidth(), 64 | $command->getBoxBorder(), 65 | $command->getText(), 66 | $command->getSpacing(), 67 | ]); 68 | 69 | // reset 70 | yield from $this->resetAlign($command); 71 | yield from $this->resetInvert($command); 72 | yield from $this->resetRotate($command); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Language/Fingerprint/FingerprintTextLine.php: -------------------------------------------------------------------------------- 1 | shouldEmulate()) { 40 | $image = $this->emulate($command, $command->getMaxWidth()); 41 | 42 | yield from (new FingerprintBitmap())->translate( 43 | new Bitmap($command->getX(), $command->getY(), $image) 44 | ); 45 | 46 | return; 47 | } 48 | 49 | yield from $this->position($command); 50 | 51 | yield from $this->rotate($command); 52 | 53 | yield from $this->invert($command); 54 | 55 | yield from $this->align($command); 56 | 57 | yield from $this->font($command); 58 | 59 | yield \sprintf('PT "%s"', $command->getText()); 60 | 61 | // reset 62 | yield from $this->resetAlign($command); 63 | yield from $this->resetInvert($command); 64 | yield from $this->resetRotate($command); 65 | yield from $this->resetMagnify($command); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Language/Tspl.php: -------------------------------------------------------------------------------- 1 | Handlers\TsplRaw::class, 38 | Clear::class => Handlers\TsplClear::class, 39 | Bitmap::class => Handlers\TsplBitmap::class, 40 | Barcode::class => Handlers\TsplBarcode::class, 41 | TextLine::class => Handlers\TsplTextLine::class, 42 | TextBlock::class => Handlers\TsplTextBlock::class, 43 | InternalImage::class => Handlers\TsplInternalImage::class, 44 | ]; 45 | 46 | public function compileDeclaration(Label $label): iterable 47 | { 48 | yield from $this->translateMedia($label->getMedia()); 49 | yield from $this->translateCharset($label->getCharset()); 50 | } 51 | 52 | public function isSupport(Command $command): bool 53 | { 54 | return isset(self::HANDLERS[\get_class($command)]); 55 | } 56 | 57 | public function compilePrint(int $copies): iterable 58 | { 59 | if ($copies <= 0) { 60 | throw new \InvalidArgumentException('Number of copies must be greather than 0.'); 61 | } 62 | 63 | yield "PRINT 1,{$copies}".self::EOC; 64 | } 65 | 66 | public function compileCommand(Command $command): iterable 67 | { 68 | if ($this->isSupport($command)) { 69 | $handler = self::HANDLERS[\get_class($command)]; 70 | 71 | foreach ((new $handler())->translate($command) as $instruction) { 72 | yield $instruction.self::EOC; 73 | } 74 | } else { 75 | // throw exception 76 | yield null; 77 | } 78 | } 79 | 80 | private function translateCharset(?Charset $charset): iterable 81 | { 82 | if ($charset) { 83 | yield \sprintf('CODEPAGE %s', $charset->getValue()).self::EOC; 84 | } 85 | } 86 | 87 | private function translateMedia(Media $media): iterable 88 | { 89 | if ($media->getWidth() && $media->getHeight() === null) { 90 | yield \sprintf('SIZE %s'.self::EOC, $this->valueWithUnit($media->getWidth(), $media->getUnit())); 91 | 92 | return; 93 | } 94 | 95 | if ($media->getWidth() && $media->getHeight()) { 96 | yield \vsprintf('SIZE %s,%s'.self::EOC, [ 97 | $this->valueWithUnit($media->getWidth(), $media->getUnit()), 98 | $this->valueWithUnit($media->getHeight(), $media->getUnit()), 99 | ]); 100 | } 101 | } 102 | 103 | private function valueWithUnit(float $value, Unit $unit): string 104 | { 105 | if ($unit->equals(Unit::IN())) { 106 | return (string) $value; 107 | } 108 | 109 | return "{$value} {$unit->getValue()}"; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Language/Tspl/TsplBarcode.php: -------------------------------------------------------------------------------- 1 | getX(), 25 | $command->getY(), 26 | $command->getType(), 27 | $command->getHeight(), 28 | ]); 29 | 30 | // human readable 31 | $instruction .= ','.(int) $command->isReadable(); 32 | 33 | // rotation 34 | $instruction .= ','.$command->getRotation()->getDegrees(); 35 | 36 | // width of narrow element 37 | $instruction .= ','.(int) $command->getRatio()['narrow']; 38 | 39 | // width of wide element 40 | $instruction .= ','.(int) $command->getRatio()['wide']; 41 | 42 | // alingment (Rongta RP 4xx/Atol BP4x doesn't support alignment and prints void) 43 | if ($command->getAnchor()) { 44 | $instruction .= ','.$command->getAnchor()->getValue(); 45 | } 46 | 47 | yield $instruction.\sprintf(',"%s"', $command->getData()); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Language/Tspl/TsplBitmap.php: -------------------------------------------------------------------------------- 1 | getX(), 26 | $command->getY(), 27 | (int) \ceil($command->getWidth() / 8), 28 | $command->getHeight(), 29 | ]).PbmCodec::create($command->getCanvas())->encode(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Language/Tspl/TsplClear.php: -------------------------------------------------------------------------------- 1 | getName())) { 24 | $instruction = \vsprintf('PUTBMP %d,%d,"%s"', [ 25 | $command->getX(), 26 | $command->getY(), 27 | $command->getName(), 28 | ]); 29 | 30 | if ($command->getBpp()) { 31 | $instruction .= ','.$command->getBpp(); 32 | } 33 | 34 | if ($command->getContrast()) { 35 | $instruction .= ','.$command->getContrast(); 36 | } 37 | 38 | yield $instruction; 39 | 40 | return; 41 | } 42 | 43 | if (\preg_match('~\.pcx$~i', $command->getName())) { 44 | yield \vsprintf('PUTPCX %d,%d,"%s"', [ 45 | $command->getX(), 46 | $command->getY(), 47 | $command->getName(), 48 | ]); 49 | 50 | return; 51 | } 52 | 53 | throw new \InvalidArgumentException(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Language/Tspl/TsplRaw.php: -------------------------------------------------------------------------------- 1 | getData(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Language/Tspl/TsplTextBlock.php: -------------------------------------------------------------------------------- 1 | shouldEmulate()) { 33 | $image = $this->emulate( 34 | $command, 35 | $command->getBoxWidth(), 36 | $command->getBoxHeight(), 37 | $command->getSpacing() 38 | ); 39 | 40 | yield from (new TsplBitmap())->translate( 41 | new Bitmap($command->getX(), $command->getY(), $image) 42 | ); 43 | 44 | return; 45 | } 46 | 47 | $size = $command->getFontSize(); 48 | 49 | if (\is_string($size)) { 50 | $size += 0; 51 | } 52 | 53 | if (empty($size)) { 54 | $size = 1; 55 | } 56 | 57 | $specifier = \is_float($size) ? '%.2F' : '%d'; 58 | 59 | $format = "%d,%d,%d,%d,\"%s\",%d,{$specifier},{$specifier},0,%d,0,\"%s\""; 60 | 61 | yield \vsprintf('BLOCK '.$format, [ 62 | $command->getX(), 63 | $command->getY(), 64 | $command->getBoxWidth(), 65 | $command->getBoxHeight(), 66 | $command->getFontName(), 67 | $command->getRotation()->getDegrees(), 68 | $size, 69 | $size, 70 | $command->getSpacing() ?? 0, 71 | $command->getAnchor() ? $command->getAnchor()->getValue() : 0, 72 | \str_replace(['"', "\n", "\r"], ['\["]', '\[L]', '\[R]'], $command->getText()), 73 | ]); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Language/Tspl/TsplTextLine.php: -------------------------------------------------------------------------------- 1 | shouldEmulate()) { 28 | $image = $this->emulate($command, $command->getMaxWidth()); 29 | 30 | yield from (new TsplBitmap())->translate( 31 | new Bitmap($command->getX(), $command->getY(), $image) 32 | ); 33 | 34 | return; 35 | } 36 | 37 | $size = $command->getFontSize(); 38 | 39 | if (\is_string($size)) { 40 | $size += 0; 41 | } 42 | 43 | if (empty($size)) { 44 | $size = 1; 45 | } 46 | 47 | $specifier = \is_float($size) ? '%.2F' : '%d'; 48 | 49 | $format = ['%d', '%d', '"%s"', '%d', $specifier, $specifier]; 50 | 51 | // common parameters 52 | $values = [ 53 | $command->getX(), 54 | $command->getY(), 55 | $command->getFontName(), 56 | $command->getRotation()->getDegrees(), 57 | $size, 58 | $size, 59 | ]; 60 | 61 | // Rongta RP 4xx/Atol BP4x doesn't support alignment and print void 62 | if ($command->getAnchor()) { 63 | $format[] = '%d'; 64 | $values[] = (($command->getAnchor()->getValue() % 3) ?: 3); 65 | } 66 | 67 | // text 68 | $format[] = '"%s"'; 69 | $values[] = \str_replace('"', '\["]', $command->getText()); 70 | 71 | yield \vsprintf('TEXT '.\implode(',', $format), $values); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Printer.php: -------------------------------------------------------------------------------- 1 | connector = $connector; 31 | $this->compiler = $compiler; 32 | } 33 | 34 | public function print(Job $job): void 35 | { 36 | if ($this->compiler === null) { 37 | throw new \DomainException( 38 | 'The Printer object should be constructed with Compiler instance for printing.' 39 | ); 40 | } 41 | 42 | $this->connector->write($this->compiler->compile($job)); 43 | } 44 | 45 | public function send($payload): void 46 | { 47 | $this->connector->write($payload); 48 | } 49 | 50 | public function ask(string $message) 51 | { 52 | $this->connector->write($message); 53 | 54 | return $this->connector->read(); 55 | } 56 | } 57 | --------------------------------------------------------------------------------