├── rust-toolchain ├── bridges └── node │ ├── README.md │ ├── .gitignore │ ├── native │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ ├── package.json │ └── lib │ └── index.js ├── Makefile ├── .gitignore ├── examples ├── stdout.rs ├── fromlp0.rs ├── simple.rs ├── usb.rs ├── raster.rs └── image.rs ├── .travis.yml ├── Cargo.toml ├── tests └── simple.rs ├── RELEASES.md ├── LICENSE ├── README.md └── src ├── lib.rs ├── img.rs ├── consts.rs ├── device.rs └── printer.rs /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.83.0 2 | -------------------------------------------------------------------------------- /bridges/node/README.md: -------------------------------------------------------------------------------- 1 | # node 2 | 3 | Node bridge for escposify-rs 4 | -------------------------------------------------------------------------------- /bridges/node/.gitignore: -------------------------------------------------------------------------------- 1 | native/target 2 | native/index.node 3 | **/*~ 4 | **/node_modules 5 | **/.DS_Store 6 | -------------------------------------------------------------------------------- /bridges/node/native/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "node" 3 | version = "0.1.0" 4 | authors = ["Qian Linfeng "] 5 | license = "MIT" 6 | 7 | [lib] 8 | name = "node" 9 | crate-type = ["dylib"] 10 | 11 | [dependencies] 12 | neon = "0.1" 13 | escposify = {path = "../../../"} 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | fmt: 2 | cargo fmt --all -- --check 3 | 4 | clippy: 5 | RUSTFLAGS='-D warnings' cargo clippy --all --tests --all-features 6 | 7 | example: 8 | cargo build --examples 9 | 10 | test: 11 | RUSTFLAGS='-D warnings' RUST_BACKTRACE=full cargo test --all --all-features 12 | 13 | ci: fmt clippy example test 14 | git diff --exit-code Cargo.lock 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | *~ 7 | *.swp 8 | *\# 9 | 10 | # Executables 11 | *.exe 12 | 13 | # Generated by Cargo 14 | /target/ 15 | 16 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 17 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 18 | Cargo.lock 19 | target 20 | -------------------------------------------------------------------------------- /examples/stdout.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, stdout}; 2 | 3 | use escposify::printer::Printer; 4 | 5 | fn main() -> io::Result<()> { 6 | let mut printer = Printer::new(stdout(), None, None); 7 | 8 | printer 9 | .chain_feed(2)? 10 | .chain_text("The quick brown fox jumps over the lazy dog")? 11 | .chain_text("敏捷的棕色狐狸跳过懒狗")? 12 | .chain_feed(1)? 13 | .flush() 14 | } 15 | -------------------------------------------------------------------------------- /bridges/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node", 3 | "version": "0.1.0", 4 | "description": "Node bridge for escposify-rs", 5 | "main": "lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/local-group/escposify-rs" 9 | }, 10 | "author": "Qian Linfeng ", 11 | "license": "MIT", 12 | "dependencies": { 13 | "neon-cli": "^0.1.7" 14 | }, 15 | "scripts": { 16 | "install": "neon build", 17 | "require": "node -e 'require(\"./\")'" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/fromlp0.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io; 3 | 4 | use escposify::printer::Printer; 5 | 6 | fn main() -> io::Result<()> { 7 | let device_file = File::options().append(true).open("/dev/usb/lp0").unwrap(); 8 | 9 | let file = escposify::device::File::from(device_file); 10 | let mut printer = Printer::new(file, None, None); 11 | 12 | printer 13 | .chain_size(0, 0)? 14 | .chain_text("The quick brown fox jumps over the lazy dog")? 15 | .chain_feed(1)? 16 | .chain_cut(false)? 17 | .flush() 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | before_script: 5 | - rustup component add rustfmt 6 | - rustup component add clippy 7 | script: 8 | - cargo build --all 9 | stages: 10 | - Check 11 | - Clippy 12 | - Example 13 | - Test 14 | jobs: 15 | include: 16 | - stage: Check 17 | name: Format 18 | script: 19 | - make fmt 20 | - stage: Clippy 21 | name: Clippy 22 | script: 23 | - make clippy 24 | - stage: Example 25 | name: Example 26 | script: 27 | - make example 28 | - stage: Test 29 | name: Unitest 30 | script: 31 | - make test 32 | -------------------------------------------------------------------------------- /bridges/node/native/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate neon; 3 | extern crate escposify; 4 | 5 | use neon::vm::{Call, JsResult, Module}; 6 | use neon::js::{JsString, JsUndefined}; 7 | 8 | use escposify::printer::Printer; 9 | use escposify::device::File; 10 | use escposify::device::Network; 11 | 12 | 13 | fn print(call: Call) -> JsResult { 14 | let scope = call.scope; 15 | let arguments = call.arguments; 16 | Ok(JsUndefined::new()) 17 | // println!("arguments = {:?}", arguments.get(scope, 0).unwrap()); 18 | } 19 | 20 | 21 | register_module!(m, { 22 | try!(m.export("print", print)); 23 | Ok(()) 24 | }); 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "escposify" 3 | version = "0.6.2" 4 | description = """ 5 | A ESC/POS driver for Rust 6 | 7 | ## Minimum Rust version policy (MSRV) 8 | This crate's minimum supported rustc version is 1.46.0. 9 | """ 10 | readme = "README.md" 11 | keywords = ["ESC", "POS", "printer", "driver"] 12 | repository = "https://github.com/local-group/rust-escposify" 13 | license = "MIT" 14 | authors = ["Qian Linfeng "] 15 | edition = "2021" 16 | 17 | [features] 18 | qrcode_builder = ["qrcode"] 19 | 20 | [dependencies] 21 | encoding = "0.2" 22 | byteorder = "1.4" 23 | image = "0.25.4" 24 | rusb = "0.9.3" 25 | 26 | qrcode = { version = "0.12", optional = true } 27 | 28 | [dev-dependencies] 29 | tempfile = "2.2" 30 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use escposify::device::File; 4 | use escposify::printer::Printer; 5 | 6 | use tempfile::NamedTempFileOptions; 7 | 8 | fn main() -> io::Result<()> { 9 | let tempf = NamedTempFileOptions::new().create().unwrap(); 10 | 11 | let file = File::from(tempf); 12 | let mut printer = Printer::new(file, None, None); 13 | 14 | printer 15 | .chain_font("C")? 16 | .chain_align("lt")? 17 | .chain_style("bu")? 18 | .chain_size(0, 0)? 19 | .chain_text("The quick brown fox jumps over the lazy dog")? 20 | .chain_text("敏捷的棕色狐狸跳过懒狗")? 21 | .chain_barcode("12345678", "EAN8", "", "", 0, 0)? 22 | .chain_feed(1)? 23 | .chain_cut(false)? 24 | .flush() 25 | } 26 | -------------------------------------------------------------------------------- /examples/usb.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use escposify::device::Usb; 4 | use escposify::printer::Printer; 5 | 6 | fn main() -> io::Result<()> { 7 | let product_id = 0xa700; // find the correct product_id for your printer 8 | let vendor_id = 0x0525; // find the correct vendor_id for your printer 9 | let usb = Usb::new(vendor_id, product_id)?; 10 | 11 | let mut printer = Printer::new(usb, None, None); 12 | 13 | printer 14 | .chain_feed(5)? 15 | .chain_font("C")? 16 | .chain_align("lt")? 17 | .chain_style("bu")? 18 | .chain_size(0, 0)? 19 | .chain_text("The quick brown fox jumps over the lazy dog")? 20 | .chain_barcode("12345678", "EAN8", "", "", 0, 0)? 21 | .chain_feed(5)? 22 | .chain_cut(false)? 23 | .flush() 24 | } 25 | -------------------------------------------------------------------------------- /bridges/node/lib/index.js: -------------------------------------------------------------------------------- 1 | var addon = require('../native'); 2 | 3 | function print(device, commands) { 4 | // Some arguments format stuff 5 | // ... 6 | console.log('Print done:', addon.print(device, commands)); 7 | } 8 | 9 | var path = undefined; 10 | var string = undefined; 11 | var integer = undefined; 12 | 13 | // Device information 14 | var device = {path: string}; // File 15 | var device = {host: string, port: integer}; // Network 16 | 17 | // Print command + arguments list 18 | var commands = [ 19 | {name: "font", args: ["C"]}, 20 | {name: "align", args: ["lt"]}, 21 | {name: "style", args: ["bu"]}, 22 | {name: "size", args: [0, 0]}, 23 | {name: "text", args: ["The quick brown fox jumps over the lazy dog"]}, 24 | {name: "text", args: ["敏捷的棕色狐狸跳过懒狗"]}, 25 | {name: "barcode", args: ["12345678", "EAN8", "", "", 0, 0]}, 26 | {name: "feed", args: [1]}, 27 | {name: "cut", args: [false]}, 28 | ]; 29 | 30 | // A test call 31 | print(device, commands); 32 | -------------------------------------------------------------------------------- /examples/raster.rs: -------------------------------------------------------------------------------- 1 | use escposify::device::File; 2 | use escposify::img::Image; 3 | use escposify::printer::Printer; 4 | 5 | use image::{DynamicImage, ImageBuffer}; 6 | use std::io; 7 | use tempfile::NamedTempFileOptions; 8 | 9 | fn main() -> io::Result<()> { 10 | let tempf = NamedTempFileOptions::new().create().unwrap(); 11 | 12 | let file = File::from(tempf); 13 | let mut printer = Printer::new(file, None, None); 14 | 15 | let img = ImageBuffer::from_fn(512, 512, |x, _| { 16 | if x % 2 == 0 { 17 | image::Rgb([0, 0, 0]) 18 | } else { 19 | image::Rgb([0xFF, 0xFF, 0xFF]) 20 | } 21 | }); 22 | let image = Image::from(DynamicImage::ImageRgb8(img)); 23 | printer 24 | .chain_align("ct")? 25 | .chain_raster(&image, None)? 26 | .chain_raster(&image, Some("dw"))? 27 | .chain_raster(&image, Some("dh"))? 28 | .chain_raster(&image, Some("dwdh"))? 29 | .flush() 30 | } 31 | -------------------------------------------------------------------------------- /examples/image.rs: -------------------------------------------------------------------------------- 1 | use escposify::device::File; 2 | use escposify::img::Image; 3 | use escposify::printer::Printer; 4 | 5 | use image::{DynamicImage, ImageBuffer}; 6 | use std::io; 7 | use tempfile::NamedTempFileOptions; 8 | 9 | fn main() -> io::Result<()> { 10 | let tempf = NamedTempFileOptions::new().create().unwrap(); 11 | 12 | let file = File::from(tempf); 13 | let mut printer = Printer::new(file, None, None); 14 | 15 | let img = ImageBuffer::from_fn(512, 512, |x, _| { 16 | if x % 2 == 0 { 17 | image::Rgb([0, 0, 0]) 18 | } else { 19 | image::Rgb([0xFF, 0xFF, 0xFF]) 20 | } 21 | }); 22 | let image = Image::from(DynamicImage::ImageRgb8(img)); 23 | printer 24 | .chain_align("ct")? 25 | .chain_bit_image(&image, Some("s8"))? 26 | .chain_bit_image(&image, Some("d8"))? 27 | .chain_bit_image(&image, Some("s24"))? 28 | .chain_bit_image(&image, Some("d24"))? 29 | .flush() 30 | } 31 | -------------------------------------------------------------------------------- /tests/simple.rs: -------------------------------------------------------------------------------- 1 | extern crate tempfile; 2 | 3 | extern crate escposify; 4 | 5 | use escposify::device::File; 6 | use escposify::printer::Printer; 7 | use tempfile::NamedTempFileOptions; 8 | 9 | #[test] 10 | fn simple() { 11 | let tempf = NamedTempFileOptions::new().create().unwrap(); 12 | 13 | let file = File::from(tempf); 14 | let mut printer = Printer::new(file, None, None); 15 | 16 | let _ = printer 17 | .chain_font("C") 18 | .unwrap() 19 | .chain_align("lt") 20 | .unwrap() 21 | .chain_style("bu") 22 | .unwrap() 23 | .chain_size(0, 0) 24 | .unwrap() 25 | .chain_text("The quick brown fox jumps over the lazy dog") 26 | .unwrap() 27 | .chain_text("敏捷的棕色狐狸跳过懒狗") 28 | .unwrap() 29 | .chain_barcode("12345678", "EAN8", "", "", 0, 0) 30 | .unwrap() 31 | .chain_feed(1) 32 | .unwrap() 33 | .chain_cut(false) 34 | .unwrap() 35 | .flush(); 36 | } 37 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | 2 | Version 0.3.0 3 | ============= 4 | 5 | API change 6 | ---------- 7 | * Printer methods return `io::Result` 8 | ``` rust 9 | // Old version 10 | pub fn raster(&mut self, image: &Image, mode: Option<&str>) -> &mut Self; 11 | 12 | // New version 13 | pub fn raster(&mut self, image: &Image, mode: Option<&str>) -> io::Result; 14 | ``` 15 | * Add related chain API for printer (see: `examples/simple.rs`) 16 | ``` rust 17 | // New API 18 | pub fn chain_raster(&mut self, image: &Image, mode: Option<&str>) -> io::Result<&mut Self>; 19 | 20 | // Example 21 | printer 22 | .chain_font("C")? 23 | .chain_align("lt")? 24 | .chain_style("bu")? 25 | .chain_size(0, 0)? 26 | .chain_text("The quick brown fox jumps over the lazy dog")? 27 | .chain_text("敏捷的棕色狐狸跳过懒狗")? 28 | .chain_barcode("12345678", "EAN8", "", "", 0, 0)? 29 | .chain_feed(1)? 30 | .chain_cut(false)? 31 | .flush() 32 | ``` 33 | 34 | Bug fix 35 | ------- 36 | * PR[#6](https://github.com/local-group/rust-escposify/pull/6) : Some bug fixes for image raster 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Local Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # escposify-rs 2 | A ESC/POS driver for Rust 3 | 4 | [Documentation](https://docs.rs/escposify) 5 | 6 | Most ESC/POS Printers will appear as a file. To print to the device, open a file to the location and pass this to the ```File::from``` function. 7 | 8 | To enable this in Windows, install the printer and its driver. Share the printer and specifiy a name for it (Receipt Printer in this case). The printer will then be accessable via ```\\%COMPUTERNAME%\Receipt Printer```. 9 | To test this in the command line: 10 | ``` 11 | echo "Hello World" > testfile 12 | copy testfile "\\%COMPUTERNAME%\Receipt Printer" 13 | del testfile 14 | ``` 15 | 16 | 17 | # Examples 18 | 19 | ## Rust 20 | See: [simple.rs](examples/simple.rs) 21 | 22 | ``` rust 23 | extern crate escposify; 24 | extern crate tempfile; 25 | 26 | use std::io; 27 | 28 | use escposify::device::File; 29 | use escposify::printer::Printer; 30 | 31 | use tempfile::NamedTempFileOptions; 32 | 33 | fn main() -> io::Result<()> { 34 | let tempf = NamedTempFileOptions::new().create().unwrap(); 35 | 36 | let file = File::from(tempf); 37 | let mut printer = Printer::new(file, None, None); 38 | 39 | printer 40 | .chain_font("C")? 41 | .chain_align("lt")? 42 | .chain_style("bu")? 43 | .chain_size(0, 0)? 44 | .chain_text("The quick brown fox jumps over the lazy dog")? 45 | .chain_text("敏捷的棕色狐狸跳过懒狗")? 46 | .chain_barcode("12345678", "EAN8", "", "", 0, 0)? 47 | .chain_feed(1)? 48 | .chain_cut(false)? 49 | .flush() 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! escposify - A ESC/POS driver for Rust 2 | //! 3 | //! ## Examples 4 | //! 5 | //! ### sample.rs 6 | //! 7 | //! Write to a temporary file using [device::File]. 8 | //! ```rust 9 | //! use std::io; 10 | //! 11 | //! use escposify::device::File; 12 | //! use escposify::printer::Printer; 13 | //! 14 | //! use tempfile::NamedTempFileOptions; 15 | //! 16 | //! fn main() -> io::Result<()> { 17 | //! let tempf = NamedTempFileOptions::new().create().unwrap(); 18 | //! 19 | //! let file = File::from(tempf); 20 | //! let mut printer = Printer::new(file, None, None); 21 | //! 22 | //! printer 23 | //! .chain_font("C")? 24 | //! .chain_align("lt")? 25 | //! .chain_style("bu")? 26 | //! .chain_size(0, 0)? 27 | //! .chain_text("The quick brown fox jumps over the lazy dog")? 28 | //! .chain_text("敏捷的棕色狐狸跳过懒狗")? 29 | //! .chain_barcode("12345678", "EAN8", "", "", 0, 0)? 30 | //! .chain_feed(1)? 31 | //! .chain_cut(false)? 32 | //! .flush() 33 | //! } 34 | //! ``` 35 | //! 36 | //! ### Printing to /dev/usb/lp0 37 | //! 38 | //! When writing to a file ensure that `File::options().append(true)` is set otherwise writing is not possible. 39 | //! ```no_run 40 | //! use std::fs::File; 41 | //! use std::io; 42 | //! 43 | //! use escposify::printer::Printer; 44 | //! 45 | //! fn main() -> io::Result<()> { 46 | //! let device_file = File::options().append(true).open("/dev/usb/lp0").unwrap(); 47 | //! 48 | //! let file = escposify::device::File::from(device_file); 49 | //! let mut printer = Printer::new(file, None, None); 50 | //! 51 | //! printer 52 | //! .chain_size(0,0)? 53 | //! .chain_text("The quick brown fox jumps over the lazy dog")? 54 | //! .chain_feed(1)? 55 | //! .chain_cut(false)? 56 | //! .flush() 57 | //! } 58 | //! ``` 59 | //! 60 | //! ### Writing to the stdout 61 | //! 62 | //! Understandably not all options work here (alignment, fonts, chain_cut etc.) 63 | //! but for quick debugging and prototyping this is a good option 64 | //! as it saves tons of time when working on the logic of your implementation. 65 | //! 66 | //! ```rust 67 | //! use std::io::{self, stdout}; 68 | //! use escposify::printer::Printer; 69 | //! 70 | //! fn main() -> io::Result<()> { 71 | //! 72 | //! let mut printer = Printer::new(stdout(), None, None); 73 | //! 74 | //! printer 75 | //! .chain_feed(2)? 76 | //! .chain_text("The quick brown fox jumps over the lazy dog")? 77 | //! .chain_text("敏捷的棕色狐狸跳过懒狗")? 78 | //! .chain_feed(1)? 79 | //! .flush() 80 | //! } 81 | //! ``` 82 | //! 83 | //! ### Printing to a printer via USB 84 | //! 85 | //! ```no_run 86 | //! use std::io; 87 | //! use escposify::printer::Printer; 88 | //! use escposify::device::Usb; 89 | //! 90 | //! fn main() -> io::Result<()> { 91 | //! let product_id = 0xa700; 92 | //! let vendor_id = 0x0525; 93 | //! let usb = Usb::new(vendor_id, product_id)?; 94 | //! 95 | //! let mut printer = Printer::new(usb, None, None); 96 | //! 97 | //! printer 98 | //! .chain_feed(5)? 99 | //! .chain_font("C")? 100 | //! .chain_align("lt")? 101 | //! .chain_style("bu")? 102 | //! .chain_size(0, 0)? 103 | //! .chain_text("The quick brown fox jumps over the lazy dog")? 104 | //! .chain_barcode("12345678", "EAN8", "", "", 0, 0)? 105 | //! .chain_feed(5)? 106 | //! .chain_cut(false)? 107 | //! .flush() 108 | //! } 109 | //! ``` 110 | 111 | pub mod consts; 112 | pub mod device; 113 | pub mod img; 114 | pub mod printer; 115 | -------------------------------------------------------------------------------- /src/img.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Iterator; 2 | use std::path; 3 | 4 | use image; 5 | use image::{error::ImageResult, DynamicImage, GenericImageView}; 6 | 7 | pub struct Image { 8 | pub width: u32, 9 | pub height: u32, 10 | img_buf: DynamicImage, 11 | } 12 | 13 | impl Image { 14 | pub fn new + ToString>(path: P) -> ImageResult { 15 | let img_buf = image::open(&path)?; 16 | let (width, height) = img_buf.dimensions(); 17 | Ok(Image { 18 | width, 19 | height, 20 | img_buf, 21 | }) 22 | } 23 | 24 | pub fn from(img_buf: DynamicImage) -> Image { 25 | let (width, height) = img_buf.dimensions(); 26 | Image { 27 | width, 28 | height, 29 | img_buf, 30 | } 31 | } 32 | 33 | #[cfg(feature = "qrcode_builder")] 34 | pub fn from_qr(code: &str, width: u32) -> qrcode::QrResult { 35 | use image::ImageBuffer; 36 | use qrcode::QrCode; 37 | let code = QrCode::new(code.as_bytes())?; 38 | let code_width = code.width() as u32; 39 | let point_width = width / (code_width + 2); 40 | // QR code quite zone width 41 | let quite_width = (width % (code_width + 2)) / 2 + point_width; 42 | 43 | #[allow(clippy::many_single_char_names)] 44 | let img_buf = ImageBuffer::from_fn(width, width, |x, y| { 45 | let is_white = x < quite_width 46 | || y < quite_width 47 | || x >= (width - quite_width) 48 | || y >= (width - quite_width) 49 | || code[( 50 | ((x - quite_width) / point_width) as usize, 51 | ((y - quite_width) / point_width) as usize, 52 | )] == qrcode::Color::Light; 53 | if is_white { 54 | image::Rgb([0xFF, 0xFF, 0xFF]) 55 | } else { 56 | image::Rgb([0, 0, 0]) 57 | } 58 | }); 59 | Ok(Image { 60 | width, 61 | height: width, 62 | img_buf: DynamicImage::ImageRgb8(img_buf), 63 | }) 64 | } 65 | 66 | pub fn is_blank_pixel(&self, x: u32, y: u32) -> bool { 67 | let pixel = self.img_buf.get_pixel(x, y); 68 | // full transprant OR is white 69 | pixel[3] == 0 || (pixel[0] & pixel[1] & pixel[2]) == 0xFF 70 | } 71 | 72 | pub fn bitimage_lines(&self, density: u32) -> BitimageLines { 73 | BitimageLines { 74 | line: 0, 75 | density, 76 | image: self, 77 | } 78 | } 79 | 80 | #[allow(clippy::many_single_char_names)] 81 | fn get_line(&self, num: u32, density: u32) -> Option> { 82 | let n = self.height / density; 83 | let y = num - 1; 84 | if y >= n { 85 | return None; 86 | } 87 | 88 | let c = density / 8; 89 | let mut data: Vec = vec![0; (self.width * c) as usize]; 90 | // println!(">>> num={}, density={}, n={}, y={}, c={}, data.len()={}", 91 | // num, density, n, y, c, data.len()); 92 | for x in 0..self.width { 93 | for b in 0..density { 94 | let i = x * c + (b >> 3); 95 | // println!("x={}, b={}, i={}, b>>8={}", x, b, i, b>>3); 96 | let l = y * density + b; 97 | if l < self.height && !self.is_blank_pixel(x, l) { 98 | data[i as usize] += 0x80 >> (b & 0x07); 99 | } 100 | } 101 | } 102 | Some(data.into_boxed_slice()) 103 | } 104 | 105 | #[allow(clippy::many_single_char_names)] 106 | pub fn get_raster(&self) -> Box<[u8]> { 107 | let n = (self.width + 7) / 8; // Number of bytes per line 108 | let mut data: Vec = vec![0; (n * self.height) as usize]; 109 | for y in 0..self.height { 110 | for x in 0..n { 111 | for b in 0..8 { 112 | let i = x * 8 + b; 113 | if i < self.width && !self.is_blank_pixel(i, y) { 114 | data[(y * n + x) as usize] += 0x80 >> (b & 0x7); 115 | } 116 | } 117 | } 118 | } 119 | data.into_boxed_slice() 120 | } 121 | } 122 | 123 | pub struct BitimageLines<'a> { 124 | line: u32, 125 | density: u32, 126 | image: &'a Image, 127 | } 128 | 129 | impl Iterator for BitimageLines<'_> { 130 | type Item = Box<[u8]>; 131 | 132 | fn next(&mut self) -> Option> { 133 | self.line += 1; 134 | self.image.get_line(self.line, self.density) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/consts.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | * ESC/POS Commands (Constants) 3 | */ 4 | 5 | pub const EOL: &str = "\n"; 6 | 7 | // const NUL: &[u8] = b"\x00"; 8 | // const LF: &[u8] = b"\x0a"; 9 | // const ESC: &[u8] = b"\x1b"; 10 | // const FS: &[u8] = b"\x1c"; 11 | // const FF: &[u8] = b"\x0c"; 12 | // const GS: &[u8] = b"\x1d"; 13 | // const DLE: &[u8] = b"\x10"; 14 | // const EOT: &[u8] = b"\x04"; 15 | 16 | /** 17 | * [`FEED_CONTROL_SEQUENCES` Feed control sequences] 18 | */ 19 | // .FEED_CONTROL_SEQUENCES 20 | pub const CTL_LF: &[u8] = b"\x0a"; // Print and line feed 21 | pub const CTL_FF: &[u8] = b"\x0c"; // Form feed 22 | pub const CTL_CR: &[u8] = b"\x0d"; // Carriage return 23 | pub const CTL_HT: &[u8] = b"\x09"; // Horizontal tab 24 | pub const CTL_VT: &[u8] = b"\x0b"; // Vertical tab 25 | 26 | // .LINE_SPACING 27 | pub const LS_DEFAULT: &[u8] = b"\x1b\x32"; 28 | pub const LS_SET: &[u8] = b"\x1b\x33"; 29 | 30 | /** 31 | * [`HARDWARE` Printer hardware] 32 | */ 33 | // .HARDWARE 34 | pub const HW_INIT: &[u8] = b"\x1b\x40"; // Clear data in buffer and reset modes 35 | pub const HW_SELECT: &[u8] = b"\x1b\x3d\x01"; // Printer select 36 | pub const HW_RESET: &[u8] = b"\x1b\x3f\x0a\x00"; // Reset printer hardware 37 | 38 | /** 39 | * [`CASH_DRAWER` Cash Drawer] 40 | */ 41 | // .CASH_DRAWER 42 | pub const CD_KICK_2: &[u8] = b"\x1b\x70\x00"; // Sends a pulse to pin 2 [] 43 | pub const CD_KICK_5: &[u8] = b"\x1b\x70\x01"; // Sends a pulse to pin 5 [] 44 | 45 | /** 46 | * [`PAPER` Paper] 47 | */ 48 | // .PAPER 49 | pub const PAPER_FULL_CUT: &[u8] = b"\x1d\x56\x00"; // Full cut paper 50 | pub const PAPER_PART_CUT: &[u8] = b"\x1d\x56\x01"; // Partial cut paper 51 | pub const PAPER_CUT_A: &[u8] = b"\x1d\x56\x41"; // Partial cut paper 52 | pub const PAPER_CUT_B: &[u8] = b"\x1d\x56\x42"; // Partial cut paper 53 | 54 | /** 55 | * [`TEXT_FORMAT` Text format] 56 | */ 57 | // .TEXT_FORMAT 58 | pub const TXT_NORMAL: &[u8] = b"\x1b\x21\x00"; // Normal text 59 | pub const TXT_2HEIGHT: &[u8] = b"\x1b\x21\x10"; // Double height text 60 | pub const TXT_2WIDTH: &[u8] = b"\x1b\x21\x20"; // Double width text 61 | 62 | pub const TXT_UNDERL_OFF: &[u8] = b"\x1b\x2d\x00"; // Underline font OFF 63 | pub const TXT_UNDERL_ON: &[u8] = b"\x1b\x2d\x01"; // Underline font 1-dot ON 64 | pub const TXT_UNDERL2_ON: &[u8] = b"\x1b\x2d\x02"; // Underline font 2-dot ON 65 | pub const TXT_BOLD_OFF: &[u8] = b"\x1b\x45\x00"; // Bold font OFF 66 | pub const TXT_BOLD_ON: &[u8] = b"\x1b\x45\x01"; // Bold font ON 67 | 68 | pub const TXT_FONT_A: &[u8] = b"\x1b\x4d\x00"; // Font type A 69 | pub const TXT_FONT_B: &[u8] = b"\x1b\x4d\x01"; // Font type B 70 | pub const TXT_FONT_C: &[u8] = b"\x1b\x4d\x02"; // Font type C 71 | 72 | pub const TXT_ALIGN_LT: &[u8] = b"\x1b\x61\x00"; // Left justification 73 | pub const TXT_ALIGN_CT: &[u8] = b"\x1b\x61\x01"; // Centering 74 | pub const TXT_ALIGN_RT: &[u8] = b"\x1b\x61\x02"; // Right justification 75 | 76 | /** 77 | * [`BARCODE_FORMAT` Barcode format] 78 | */ 79 | // .BARCODE_FORMAT 80 | pub const BARCODE_TXT_OFF: &[u8] = b"\x1d\x48\x00"; // HRI barcode chars OFF 81 | pub const BARCODE_TXT_ABV: &[u8] = b"\x1d\x48\x01"; // HRI barcode chars above 82 | pub const BARCODE_TXT_BLW: &[u8] = b"\x1d\x48\x02"; // HRI barcode chars below 83 | pub const BARCODE_TXT_BTH: &[u8] = b"\x1d\x48\x03"; // HRI barcode chars both above and below 84 | 85 | pub const BARCODE_FONT_A: &[u8] = b"\x1d\x66\x00"; // Font type A for HRI barcode chars 86 | pub const BARCODE_FONT_B: &[u8] = b"\x1d\x66\x01"; // Font type B for HRI barcode chars 87 | 88 | pub const BARCODE_HEIGHT: &[u8] = b"\x1d\x68\x64"; // Barcode Height [1-255] 89 | pub const BARCODE_WIDTH: &[u8] = b"\x1d\x77\x03"; // Barcode Width [2-6] 90 | 91 | pub const BARCODE_UPC_A: &[u8] = b"\x1d\x6b\x00"; // Barcode type UPC-A 92 | pub const BARCODE_UPC_E: &[u8] = b"\x1d\x6b\x01"; // Barcode type UPC-E 93 | pub const BARCODE_EAN13: &[u8] = b"\x1d\x6b\x02"; // Barcode type EAN13 94 | pub const BARCODE_EAN8: &[u8] = b"\x1d\x6b\x03"; // Barcode type EAN8 95 | pub const BARCODE_CODE39: &[u8] = b"\x1d\x6b\x04"; // Barcode type CODE39 96 | pub const BARCODE_ITF: &[u8] = b"\x1d\x6b\x05"; // Barcode type ITF 97 | pub const BARCODE_NW7: &[u8] = b"\x1d\x6b\x06"; // Barcode type NW7 98 | 99 | // .CODE2D_FORMAT 100 | pub const TYPE_PDF417: &[u8] = b"\x1dZ\x00"; // = GS + 'Z' + '\x00' 101 | pub const TYPE_DATAMATRIX: &[u8] = b"\x1dZ\x01"; // = GS + 'Z' + '\x01' 102 | pub const TYPE_QR: &[u8] = b"\x1dZ\x02"; // = GS + 'Z' + '\x02' 103 | 104 | pub const CODE2D: &[u8] = b"\x1bZ"; // = ESC + 'Z' 105 | 106 | pub const QR_LEVEL_L: &[u8] = b"L"; // correct level 7% 107 | pub const QR_LEVEL_M: &[u8] = b"M"; // correct level 15% 108 | pub const QR_LEVEL_Q: &[u8] = b"Q"; // correct level 25% 109 | pub const QR_LEVEL_H: &[u8] = b"H"; // correct level 30% 110 | 111 | /** 112 | * [`IMAGE_FORMAT` Image format] 113 | */ 114 | // .IMAGE_FORMAT 115 | pub const S_RASTER_N: &[u8] = b"\x1d\x76\x30\x00"; // Set raster image normal size 116 | pub const S_RASTER_2W: &[u8] = b"\x1d\x76\x30\x01"; // Set raster image double width 117 | pub const S_RASTER_2H: &[u8] = b"\x1d\x76\x30\x02"; // Set raster image double height 118 | pub const S_RASTER_Q: &[u8] = b"\x1d\x76\x30\x03"; // Set raster image quadruple 119 | 120 | // .BITMAP_FORMAT 121 | pub const BITMAP_S8: &[u8] = b"\x1b\x2a\x00"; // 0 : 8 dots single density,102dpi 122 | pub const BITMAP_D8: &[u8] = b"\x1b\x2a\x01"; // 1 : 8 dots double density,203dpi 123 | pub const BITMAP_S24: &[u8] = b"\x1b\x2a\x20"; // 31: 24 dots single density,102dpi 124 | pub const BITMAP_D24: &[u8] = b"\x1b\x2a\x21"; // 32: 24 dots double density,203dpi 125 | 126 | // .GSV0_FORMAT 127 | pub const GSV0_NORMAL: &[u8] = b"\x1d\x76\x30\x00"; 128 | pub const GSV0_DW: &[u8] = b"\x1d\x76\x30\x01"; 129 | pub const GSV0_DH: &[u8] = b"\x1d\x76\x30\x02"; 130 | pub const GSV0_DWDH: &[u8] = b"\x1d\x76\x30\x03"; 131 | -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io; 3 | use std::net; 4 | use std::path; 5 | 6 | use rusb::Direction; 7 | use rusb::TransferType; 8 | use rusb::UsbContext; 9 | use rusb::{Context, DeviceHandle}; 10 | 11 | pub struct Usb { 12 | _vendor_id: u16, 13 | _product_id: u16, 14 | connection: DeviceHandle, 15 | endpoint: u8, 16 | } 17 | 18 | pub struct Serial {} 19 | 20 | #[derive(Debug)] 21 | pub struct Network { 22 | _host: String, 23 | _port: u16, 24 | stream: net::TcpStream, 25 | } 26 | 27 | impl Network { 28 | pub fn new(host: &str, port: u16) -> io::Result { 29 | let stream = net::TcpStream::connect((host, port))?; 30 | Ok(Network { 31 | _host: host.to_string(), 32 | _port: port, 33 | stream, 34 | }) 35 | } 36 | } 37 | 38 | impl io::Write for Network { 39 | fn write(&mut self, buf: &[u8]) -> io::Result { 40 | self.stream.write(buf) 41 | } 42 | 43 | fn flush(&mut self) -> io::Result<()> { 44 | self.stream.flush() 45 | } 46 | } 47 | 48 | /// File device that can be written to. 49 | 50 | #[derive(Debug)] 51 | pub struct File { 52 | fobj: W, 53 | } 54 | 55 | impl File { 56 | pub fn from_path + ToString>(path: P) -> io::Result> { 57 | let fobj = fs::OpenOptions::new() 58 | .write(true) 59 | .create(true) 60 | .truncate(true) 61 | .open(&path)?; 62 | Ok(File { fobj }) 63 | } 64 | 65 | /// Create a device::File from a [std::io::Write]. 66 | /// # Example 67 | /// ```rust 68 | /// use std::fs::File; 69 | /// use tempfile::NamedTempFileOptions; 70 | /// 71 | /// let tempf = NamedTempFileOptions::new().create().unwrap(); 72 | /// let fobj = File::options().append(true).open(tempf.path()).unwrap(); 73 | /// let file = escposify::device::File::from(fobj); 74 | /// ``` 75 | pub fn from(fobj: W) -> File { 76 | File { fobj } 77 | } 78 | } 79 | 80 | impl io::Write for File { 81 | fn write(&mut self, buf: &[u8]) -> io::Result { 82 | self.fobj.write(buf) 83 | } 84 | 85 | fn flush(&mut self) -> io::Result<()> { 86 | self.fobj.flush() 87 | } 88 | } 89 | 90 | impl Usb { 91 | /// Create a new USB device. 92 | /// # Example 93 | /// ```no_run 94 | /// use std::io; 95 | /// use escposify::device::Usb; 96 | /// use escposify::printer::Printer; 97 | /// 98 | /// let product_id = 0xa700; 99 | /// let vendor_id = 0x0525; 100 | /// let usb = Usb::new(vendor_id, product_id).unwrap(); 101 | /// let mut printer = Printer::new(usb, None, None); 102 | /// ``` 103 | pub fn new(vendor_id: u16, product_id: u16) -> io::Result { 104 | let context = Context::new().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 105 | let devices = context 106 | .devices() 107 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 108 | 109 | for device in devices.iter() { 110 | let device_desc = device 111 | .device_descriptor() 112 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 113 | 114 | if device_desc.vendor_id() == vendor_id && device_desc.product_id() == product_id { 115 | let config_descriptor = device 116 | .active_config_descriptor() 117 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 118 | 119 | let endpoint = config_descriptor 120 | .interfaces() 121 | .flat_map(|interface| interface.descriptors()) 122 | .flat_map(|descriptor| descriptor.endpoint_descriptors()) 123 | .find_map(|endpoint| { 124 | if let (TransferType::Bulk, Direction::Out) = 125 | (endpoint.transfer_type(), endpoint.direction()) 126 | { 127 | Some(endpoint.number()) 128 | } else { 129 | None 130 | } 131 | }) 132 | .ok_or_else(|| { 133 | io::Error::new(io::ErrorKind::Other, "No suitable endpoint found") 134 | })?; 135 | 136 | match device.open() { 137 | Ok(dvc) => { 138 | if let Ok(active) = dvc.kernel_driver_active(0) { 139 | if active { 140 | if let Err(e) = dvc.detach_kernel_driver(0) { 141 | return Err(io::Error::new(io::ErrorKind::Other, e)); 142 | } 143 | } 144 | } else { 145 | return Err(io::Error::new( 146 | io::ErrorKind::Other, 147 | "Error checking kernel driver", 148 | )); 149 | }; 150 | 151 | return dvc 152 | .claim_interface(0) 153 | .map(|_| Usb { 154 | _vendor_id: vendor_id, 155 | _product_id: product_id, 156 | connection: dvc, 157 | endpoint, 158 | }) 159 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e)); 160 | } 161 | Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "Device busy")), 162 | } 163 | } 164 | } 165 | 166 | Err(io::Error::new(io::ErrorKind::Other, "USB not found")) 167 | } 168 | } 169 | 170 | impl io::Write for Usb { 171 | fn write(&mut self, buf: &[u8]) -> io::Result { 172 | match self 173 | .connection 174 | .write_bulk(self.endpoint, buf, std::time::Duration::from_secs(5)) 175 | { 176 | Ok(_) => Ok(buf.len()), 177 | Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)), 178 | } 179 | } 180 | 181 | fn flush(&mut self) -> io::Result<()> { 182 | Ok(()) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/printer.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Write}; 2 | 3 | use byteorder::{LittleEndian, WriteBytesExt}; 4 | use encoding::all::UTF_8; 5 | use encoding::types::{EncoderTrap, EncodingRef}; 6 | 7 | use crate::consts; 8 | use crate::img::Image; 9 | 10 | /// Allows for printing to a [device][crate::device] 11 | /// 12 | /// # Example 13 | /// ```rust 14 | /// use std::fs::File; 15 | /// use escposify::printer::Printer; 16 | /// use tempfile::NamedTempFileOptions; 17 | /// 18 | /// fn main() -> std::io::Result<()> { 19 | /// let tempf = tempfile::NamedTempFileOptions::new().create().unwrap(); 20 | /// let file = File::from(tempf); 21 | /// let mut printer = Printer::new(file, None, None); 22 | /// 23 | /// printer 24 | /// .chain_size(0,0)? 25 | /// .chain_text("The quick brown fox jumped over the lazy dog")? 26 | /// .chain_feed(1)? 27 | /// .flush() 28 | /// } 29 | /// ``` 30 | pub struct Printer { 31 | writer: io::BufWriter, 32 | codec: EncodingRef, 33 | trap: EncoderTrap, 34 | } 35 | 36 | impl Printer { 37 | pub fn new(writer: W, codec: Option, trap: Option) -> Printer { 38 | Printer { 39 | writer: io::BufWriter::new(writer), 40 | codec: codec.unwrap_or(UTF_8 as EncodingRef), 41 | trap: trap.unwrap_or(EncoderTrap::Replace), 42 | } 43 | } 44 | 45 | fn encode(&mut self, content: &str) -> io::Result> { 46 | self.codec 47 | .encode(content, self.trap) 48 | .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err.to_string())) 49 | } 50 | 51 | fn write(&mut self, buf: &[u8]) -> io::Result { 52 | self.writer.write(buf) 53 | } 54 | 55 | pub fn chain_write_u8(&mut self, n: u8) -> io::Result<&mut Self> { 56 | self.write_u8(n).map(|_| self) 57 | } 58 | pub fn write_u8(&mut self, n: u8) -> io::Result { 59 | self.write(vec![n].as_slice()) 60 | } 61 | 62 | fn write_u16le(&mut self, n: u16) -> io::Result { 63 | let mut wtr = vec![]; 64 | wtr.write_u16::(n)?; 65 | self.write(wtr.as_slice()) 66 | } 67 | 68 | pub fn flush(&mut self) -> io::Result<()> { 69 | self.writer.flush() 70 | } 71 | 72 | pub fn chain_hwinit(&mut self) -> io::Result<&mut Self> { 73 | self.hwinit().map(|_| self) 74 | } 75 | pub fn hwinit(&mut self) -> io::Result { 76 | self.write(consts::HW_INIT) 77 | } 78 | 79 | pub fn chain_hwselect(&mut self) -> io::Result<&mut Self> { 80 | self.hwselect().map(|_| self) 81 | } 82 | pub fn hwselect(&mut self) -> io::Result { 83 | self.write(consts::HW_SELECT) 84 | } 85 | 86 | pub fn chain_hwreset(&mut self) -> io::Result<&mut Self> { 87 | self.hwreset().map(|_| self) 88 | } 89 | pub fn hwreset(&mut self) -> io::Result { 90 | self.write(consts::HW_RESET) 91 | } 92 | 93 | pub fn chain_print(&mut self, content: &str) -> io::Result<&mut Self> { 94 | self.print(content).map(|_| self) 95 | } 96 | pub fn print(&mut self, content: &str) -> io::Result { 97 | // let rv = self.encode(content); 98 | let rv = self.encode(content)?; 99 | self.write(rv.as_slice()) 100 | } 101 | 102 | pub fn chain_println(&mut self, content: &str) -> io::Result<&mut Self> { 103 | self.println(content).map(|_| self) 104 | } 105 | pub fn println(&mut self, content: &str) -> io::Result { 106 | self.print(format!("{}{}", content, consts::EOL).as_ref()) 107 | } 108 | 109 | pub fn chain_text(&mut self, content: &str) -> io::Result<&mut Self> { 110 | self.text(content).map(|_| self) 111 | } 112 | pub fn text(&mut self, content: &str) -> io::Result { 113 | self.println(content) 114 | } 115 | 116 | pub fn chain_line_space(&mut self, n: i32) -> io::Result<&mut Self> { 117 | self.line_space(n).map(|_| self) 118 | } 119 | pub fn line_space(&mut self, n: i32) -> io::Result { 120 | if n >= 0 { 121 | Ok(self.write(consts::LS_SET)? + self.write_u8(n as u8)?) 122 | } else { 123 | self.write(consts::LS_DEFAULT) 124 | } 125 | } 126 | 127 | pub fn chain_feed(&mut self, n: usize) -> io::Result<&mut Self> { 128 | self.feed(n).map(|_| self) 129 | } 130 | pub fn feed(&mut self, n: usize) -> io::Result { 131 | let n = if n < 1 { 1 } else { n }; 132 | self.write(consts::EOL.repeat(n).as_ref()) 133 | } 134 | 135 | pub fn chain_control(&mut self, ctrl: &str) -> io::Result<&mut Self> { 136 | self.control(ctrl).map(|_| self) 137 | } 138 | pub fn control(&mut self, ctrl: &str) -> io::Result { 139 | let ctrl_upper = ctrl.to_uppercase(); 140 | let ctrl_value = match ctrl_upper.as_ref() { 141 | "LF" => consts::CTL_LF, 142 | "FF" => consts::CTL_FF, 143 | "CR" => consts::CTL_CR, 144 | "HT" => consts::CTL_HT, 145 | "VT" => consts::CTL_VT, 146 | _ => { 147 | return Err(io::Error::new( 148 | io::ErrorKind::InvalidData, 149 | format!("Invalid control action: {}", ctrl), 150 | )) 151 | } 152 | }; 153 | self.write(ctrl_value) 154 | } 155 | 156 | pub fn chain_align(&mut self, alignment: &str) -> io::Result<&mut Self> { 157 | self.align(alignment).map(|_| self) 158 | } 159 | pub fn align(&mut self, alignment: &str) -> io::Result { 160 | let align_upper = alignment.to_uppercase(); 161 | let align_value = match align_upper.as_ref() { 162 | "LT" => consts::TXT_ALIGN_LT, 163 | "CT" => consts::TXT_ALIGN_CT, 164 | "RT" => consts::TXT_ALIGN_RT, 165 | _ => { 166 | return Err(io::Error::new( 167 | io::ErrorKind::InvalidData, 168 | format!("Invalid alignment: {}", alignment), 169 | )) 170 | } 171 | }; 172 | self.write(align_value) 173 | } 174 | 175 | pub fn chain_font(&mut self, family: &str) -> io::Result<&mut Self> { 176 | self.font(family).map(|_| self) 177 | } 178 | pub fn font(&mut self, family: &str) -> io::Result { 179 | let family_upper = family.to_uppercase(); 180 | let family_value = match family_upper.as_ref() { 181 | "A" => consts::TXT_FONT_A, 182 | "B" => consts::TXT_FONT_B, 183 | "C" => consts::TXT_FONT_C, 184 | _ => { 185 | return Err(io::Error::new( 186 | io::ErrorKind::InvalidData, 187 | format!("Invalid font family: {}", family), 188 | )) 189 | } 190 | }; 191 | self.write(family_value) 192 | } 193 | 194 | pub fn chain_style(&mut self, kind: &str) -> io::Result<&mut Self> { 195 | self.style(kind).map(|_| self) 196 | } 197 | pub fn style(&mut self, kind: &str) -> io::Result { 198 | let kind_upper = kind.to_uppercase(); 199 | match kind_upper.as_ref() { 200 | "B" => Ok(self.write(consts::TXT_UNDERL_OFF)? + self.write(consts::TXT_BOLD_ON)?), 201 | "U" => Ok(self.write(consts::TXT_BOLD_OFF)? + self.write(consts::TXT_UNDERL_ON)?), 202 | "U2" => Ok(self.write(consts::TXT_BOLD_OFF)? + self.write(consts::TXT_UNDERL2_ON)?), 203 | "BU" => Ok(self.write(consts::TXT_BOLD_ON)? + self.write(consts::TXT_UNDERL_ON)?), 204 | "BU2" => Ok(self.write(consts::TXT_BOLD_ON)? + self.write(consts::TXT_UNDERL2_ON)?), 205 | // "NORMAL" | _ => 206 | _ => Ok(self.write(consts::TXT_BOLD_OFF)? + self.write(consts::TXT_UNDERL_OFF)?), 207 | } 208 | } 209 | 210 | pub fn chain_size(&mut self, width: usize, height: usize) -> io::Result<&mut Self> { 211 | self.size(width, height).map(|_| self) 212 | } 213 | pub fn size(&mut self, width: usize, height: usize) -> io::Result { 214 | let mut n = self.write(consts::TXT_NORMAL)?; 215 | if width == 2 { 216 | n += self.write(consts::TXT_2WIDTH)?; 217 | } 218 | if height == 2 { 219 | n += self.write(consts::TXT_2HEIGHT)?; 220 | } 221 | Ok(n) 222 | } 223 | 224 | pub fn chain_hardware(&mut self, hw: &str) -> io::Result<&mut Self> { 225 | self.hardware(hw).map(|_| self) 226 | } 227 | pub fn hardware(&mut self, hw: &str) -> io::Result { 228 | let value = match hw { 229 | "INIT" => consts::HW_INIT, 230 | "SELECT" => consts::HW_SELECT, 231 | "RESET" => consts::HW_RESET, 232 | _ => { 233 | return Err(io::Error::new( 234 | io::ErrorKind::InvalidData, 235 | format!("Invalid hardware command: {}", hw), 236 | )) 237 | } 238 | }; 239 | self.write(value) 240 | } 241 | 242 | pub fn chain_barcode( 243 | &mut self, 244 | code: &str, 245 | kind: &str, 246 | position: &str, 247 | font: &str, 248 | width: usize, 249 | height: usize, 250 | ) -> io::Result<&mut Self> { 251 | self.barcode(code, kind, position, font, width, height) 252 | .map(|_| self) 253 | } 254 | pub fn barcode( 255 | &mut self, 256 | code: &str, 257 | kind: &str, 258 | position: &str, 259 | font: &str, 260 | width: usize, 261 | height: usize, 262 | ) -> io::Result { 263 | let mut n = 0; 264 | if width >= 1 || width <= 255 { 265 | n += self.write(consts::BARCODE_WIDTH)?; 266 | } 267 | if height >= 2 || height <= 6 { 268 | n += self.write(consts::BARCODE_HEIGHT)?; 269 | } 270 | 271 | let font = font.to_uppercase(); 272 | let position = position.to_uppercase(); 273 | let kind = kind.to_uppercase().replace('-', "_"); 274 | let font_value = match font.as_ref() { 275 | "B" => consts::BARCODE_FONT_B, 276 | // "A" | _ => 277 | _ => consts::BARCODE_FONT_A, 278 | }; 279 | let txt_value = match position.as_ref() { 280 | "OFF" => consts::BARCODE_TXT_OFF, 281 | "ABV" => consts::BARCODE_TXT_ABV, 282 | "BTH" => consts::BARCODE_TXT_BTH, 283 | // "BLW" | _ => 284 | _ => consts::BARCODE_TXT_BLW, 285 | }; 286 | let kind_value = match kind.as_ref() { 287 | "UPC_A" => consts::BARCODE_UPC_A, 288 | "UPC_E" => consts::BARCODE_UPC_E, 289 | "EAN8" => consts::BARCODE_EAN8, 290 | "CODE39" => consts::BARCODE_CODE39, 291 | "ITF" => consts::BARCODE_ITF, 292 | "NW7" => consts::BARCODE_NW7, 293 | // "EAN13" | _ => 294 | _ => consts::BARCODE_EAN13, 295 | }; 296 | n += self.write(font_value)?; 297 | self.write(txt_value)?; 298 | self.write(kind_value)?; 299 | self.write(code.as_bytes())?; 300 | Ok(n) 301 | } 302 | 303 | #[cfg(feature = "qrcode")] 304 | pub fn chain_qrimage(&mut self) -> io::Result<&mut Self> { 305 | self.qrimage().map(|_| self) 306 | } 307 | #[cfg(feature = "qrcode")] 308 | pub fn qrimage(&mut self) -> io::Result { 309 | Ok(0) 310 | } 311 | 312 | #[cfg(feature = "qrcode")] 313 | pub fn chain_qrcode( 314 | &mut self, 315 | code: &str, 316 | version: Option, 317 | level: &str, 318 | size: Option, 319 | ) -> io::Result<&mut Self> { 320 | self.qrcode(code, version, level, size).map(|_| self) 321 | } 322 | #[cfg(feature = "qrcode")] 323 | pub fn qrcode( 324 | &mut self, 325 | code: &str, 326 | version: Option, 327 | level: &str, 328 | size: Option, 329 | ) -> io::Result { 330 | let level = level.to_uppercase(); 331 | let level_value = match level.as_ref() { 332 | "M" => consts::QR_LEVEL_M, 333 | "Q" => consts::QR_LEVEL_Q, 334 | "H" => consts::QR_LEVEL_H, 335 | // "L" | _ => 336 | _ => consts::QR_LEVEL_L, 337 | }; 338 | let mut n = 0; 339 | n += self.write(consts::TYPE_QR)?; 340 | n += self.write(consts::CODE2D)?; 341 | n += self.write_u8(version.unwrap_or(3) as u8)?; 342 | n += self.write(level_value)?; 343 | n += self.write_u8(size.unwrap_or(3) as u8)?; 344 | n += self.write_u16le(code.len() as u16)?; 345 | n += self.write(code.as_bytes())?; 346 | Ok(n) 347 | } 348 | 349 | pub fn chain_cashdraw(&mut self, pin: i32) -> io::Result<&mut Self> { 350 | self.cashdraw(pin).map(|_| self) 351 | } 352 | pub fn cashdraw(&mut self, pin: i32) -> io::Result { 353 | let pin_value = if pin == 5 { 354 | consts::CD_KICK_5 355 | } else { 356 | consts::CD_KICK_2 357 | }; 358 | self.write(pin_value) 359 | } 360 | 361 | pub fn chain_cut(&mut self, part: bool) -> io::Result<&mut Self> { 362 | self.cut(part).map(|_| self) 363 | } 364 | 365 | pub fn cut(&mut self, part: bool) -> io::Result { 366 | let mut n_bytes = 0; 367 | n_bytes += self.print(consts::EOL.repeat(3).as_ref())?; 368 | let paper_cut_type = if part { 369 | consts::PAPER_PART_CUT 370 | } else { 371 | consts::PAPER_FULL_CUT 372 | }; 373 | n_bytes += self.write(paper_cut_type)?; 374 | Ok(n_bytes) 375 | } 376 | 377 | pub fn chain_bit_image( 378 | &mut self, 379 | image: &Image, 380 | density: Option<&str>, 381 | ) -> io::Result<&mut Self> { 382 | self.bit_image(image, density).map(|_| self) 383 | } 384 | pub fn bit_image(&mut self, image: &Image, density: Option<&str>) -> io::Result { 385 | let density = density.unwrap_or("d24"); 386 | let density_upper = density.to_uppercase(); 387 | let header = match density_upper.as_ref() { 388 | "S8" => consts::BITMAP_S8, 389 | "D8" => consts::BITMAP_D8, 390 | "S24" => consts::BITMAP_S24, 391 | // "D24" | _ => 392 | _ => consts::BITMAP_D24, 393 | }; 394 | let n = if density == "s8" || density == "d8" { 395 | 1 396 | } else { 397 | 3 398 | }; 399 | let mut n_bytes = 0; 400 | n_bytes += self.line_space(0)?; 401 | for line in image.bitimage_lines(n * 8) { 402 | n_bytes += self.write(header)?; 403 | n_bytes += self.write_u16le((line.len() / n as usize) as u16)?; 404 | n_bytes += self.write(line.as_ref())?; 405 | n_bytes += self.feed(1)?; 406 | } 407 | Ok(n_bytes) 408 | } 409 | 410 | pub fn chain_raster(&mut self, image: &Image, mode: Option<&str>) -> io::Result<&mut Self> { 411 | self.raster(image, mode).map(|_| self) 412 | } 413 | pub fn raster(&mut self, image: &Image, mode: Option<&str>) -> io::Result { 414 | let mode_upper = mode.unwrap_or("NORMAL").to_uppercase(); 415 | let header = match mode_upper.as_ref() { 416 | "DH" => consts::GSV0_DH, 417 | "DWDH" => consts::GSV0_DWDH, 418 | "DW" => consts::GSV0_DW, 419 | // "NORMAL" | _ => 420 | _ => consts::GSV0_NORMAL, 421 | }; 422 | let mut n_bytes = 0; 423 | n_bytes += self.write(header)?; 424 | n_bytes += self.write_u16le(((image.width + 7) / 8) as u16)?; 425 | n_bytes += self.write_u16le(image.height as u16)?; 426 | n_bytes += self.write(image.get_raster().as_ref())?; 427 | Ok(n_bytes) 428 | } 429 | } 430 | --------------------------------------------------------------------------------