This crate provides raster printing capability for Brother P-Touch QL series label printers connected as USB device.
It allows to print programmatically generated label images.
Bunch of labels are represented as a struct implemnting Iterator trait which allows lazy generation.
The label data is represented as a two dimensional array, Vec<Vec<u8>>, conversion from other image formats can be easily done.
This driver supports printing with multiple printers at a same time.
- Support USB connection
- Print multiple labels at once.
- High resolution printing support.
- Improved print completion handling with smart status monitoring
- Two colors printing support (QL-820NWB).
- Support multiple printers on one computer.
Here are some examples of labels printed with this library:
The samples show:
- Top two labels: Single-color black printing with Rust logo and various font sizes
- Bottom label: Two-color printing (black and red) demonstrating the library's capabilities on QL-820NWB
Choose a media tape you want to use and install it in the printer, then specify the matching media.
let media = ql_label::Media::Continuous(ContinuousType::Continuous62);Theare are two types of media tape, Continuous and DieCut, each one has several size variations. In this example we choose Continuous tape with 62mm width.
You can inspect USB ports by lsusb -v which will show something like follows where iProduct and iSerial are what we need.
Bus 001 Device 003: ID 04f9:209b Brother Industries, Ltd
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 (Defined at Interface level)
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 64
idVendor 0x04f9 Brother Industries, Ltd
idProduct 0x209b
bcdDevice 1.00
iManufacturer 1 Brother
iProduct 2 QL-800
iSerial 3 000G0Z000000
To avoid hardcoding sensitive printer serial numbers in your code, use environment variables:
- Copy the example environment file:
cp .env.example .env- Edit
.envwith your printer information:
# Set your actual printer information
DEFAULT_MODEL=QL820NWB
SERIAL=your_actual_serial_here- Examples automatically load configuration: All examples now read printer configuration from environment variables, so you don't need to modify code with your serial numbers.
With these information we can initialize our configurations as follows.
let config: Config = Config::new(Model::QL800, "000G0Z000000".to_string(), media)
.high_resolution(false)
.cut_at_end(true)
.two_colors(false)
.enable_auto_cut(1);These are default settings, high_resolution and two_colors options work but you need to provide appropriate data.
For two-color printing with red and black colors, enable the two_colors option and use compatible red/black tape:
let config: Config = Config::new(Model::QL820NWB, "serial".to_string(), media)
.two_colors(true) // Enable two-color printing
.high_resolution(false)
.cut_at_end(true)
.enable_auto_cut(1);This part is tricky, since this crate provides only printing capabilities, label data must be prepared with compatible format. As shown in the printer manual, QL series expects image data with a 1bit index bitmap split by lines in an appropriate orders. Please see the manual for more detail.
In this example we create an image data with 720 x 400 px size by using an application. Then using image crate to read and convert it to a grayscale data. Then using step_filter_normal function, which is supplied with this crate, we binalize and pack bits 90 bytes width vector data.
let file = "examples/rust-logo.png";
let image: image::DynamicImage = image::open(file).unwrap();
let (_, length) = image.dimensions();
let gray = image.grayscale();
let mut buffer = image::DynamicImage::new_luma8(ql_label::WIDE_PRINTER_WIDTH, length);
buffer.invert();
buffer.copy_from(&gray, 0, 0).unwrap();
buffer.invert();
let bytes = buffer.to_bytes();
let bw = ql_label::utils::step_filter_normal(80, length, bytes);For two-color printing, you can either:
- Convert RGB images automatically:
let rgb_img = image::open("image.png")?.to_rgb8();
let (width, height) = rgb_img.dimensions();
let two_color_data = ql_label::convert_rgb_to_two_color(width, height, rgb_img.as_raw())?;- Create TwoColorMatrix manually:
let two_color_data = ql_label::TwoColorMatrix::new(black_matrix, red_matrix)?;The color detection automatically identifies:
- Red pixels: R > 200, G < 100, B < 100
- Black pixels: Brightness < 128 (excluding red pixels)
- White pixels: Everything else (not printed)
In this crate, the width of image data must be 720px, which is the number of pins the printer have. The length varies depending on the label media. For the DieCut labels, there is a specif value. In case of the Continous labels, you can choose any length between 150px to 11811px for normal resolution (for 300 dpi). If you are specifying high_resolution or two_clolors options, it must be halved. After determing the size, place your contets in the area where actual labels go through. If you are using 62mm media, full width will be printed. But for 29mm media, you need to give an offset of 408 pixel on the left side then place content in 306 pixel width. You can check the details of media specification in the manual.
Once you get the bitmap data, you can supply them as a Vec.
Single-color printing:
match Printer::new(config) {
Ok(printer) => {
printer.print(vec![bw.clone()]).unwrap();
}
Err(err) => panic!("Printer Error {}", err),
}Two-color printing:
match Printer::new(config.two_colors(true)) {
Ok(printer) => {
printer.print_two_color(vec![two_color_data].into_iter()).unwrap();
}
Err(err) => panic!("Printer Error {}", err),
}If the configuration value is invalid the new function will return an error.
Note: When sending a long label, rusb will timeout and return error. The maximum length is around 1000mm for continuous labels.
The library now features improved print completion handling with adaptive status monitoring. When a print job is sent, the library:
- Monitors printer status transitions intelligently
- Detects errors immediately during printing
- Waits for proper completion (Printing → Receiving state transition)
- Provides detailed debug logging for troubleshooting
- Includes timeout protection to prevent indefinite waiting
This improvement reduces unnecessary waiting time and provides better error detection compared to the previous fixed retry approach.
The following models are tested by myself.
- QL-720NW
- QL-800
- QL-820NWB
Another printers listed in the ql_label::Model should also work but we might need some tweaking.
In the example, there is a small tool to read the printer status.
RUST_LOG=debug cargo run --example read_status
Test two-color printing with built-in test patterns:
RUST_LOG=debug cargo run --example print_two_color test
Print RGB images as two-color labels:
RUST_LOG=debug cargo run --example print_two_color image path/to/your/image.png
This will show something like follows.
[2020-10-27T06:05:45Z DEBUG ql_label::printer] Raw status code: [80, 20, 42, 34, 38, 30, 0, 0, 0, 0, 1D, A, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
[2020-10-27T06:05:45Z DEBUG ql_label::printer] Parsed Status struct: Status { model: QL800, error: UnknownError(0), media: Some(Continuous(Continuous29)), mode: 0, status_type: ReplyToRequest, phase: Receiving, notification: NotAvailable }
Some gathered data are saved in printer_status.txt.
- Two-Color Printing: Full support for red and black printing on QL-820NWB
- TwoColorMatrix: New data structure for handling black and red image data separately
- RGB Image Conversion: Built-in function
convert_rgb_to_two_colorfor automatic color separation - Smart Color Detection: Automatic identification of red (R>200,G<100,B<100) and black (brightness<128) pixels
- Print Command Enhancement: New
print_two_color()method with alternating raster line processing - Correct Image Orientation: Fixed pixel ordering to match existing single-color printing behavior
- Example Application: Complete two-color printing example with test patterns and RGB image support
- QL-800 Compression Override: Automatic disabling of compression mode for QL-800 model due to hardware limitations
- Warning Logging: Added warning message when compression is requested for QL-800 but disabled automatically
- Smart Status Monitoring: Replaced fixed 3-retry approach with adaptive status polling
- Improved Error Detection: Immediate detection of printer errors during print jobs
- State Transition Verification: Proper monitoring of Printing → Receiving state transitions
- Reduced Latency: Optimized waiting times and eliminated unnecessary delays
- Enhanced Debugging: More detailed logging for print job monitoring and troubleshooting
The library now includes enhanced error types for better debugging:
PrintTimeout: Triggered when print completion takes longer than expectedUnexpectedPhase: Indicates unexpected printer state transitionsPrinterError: Immediate detection of hardware-level errors (cover open, media issues, etc.)
- Better error handling and reporting for print completion
- [] Better error handling for when label ends
- [] Binalization with dithering support
- Two colors printing support
For my system with ubuntu 18.04 on Raspberry Pi 4, I got an error about permission to /dev/usb/lp0. To fix that I needed to add something like follows.
Add the current user to plugdev group.
$ sudo gpasswd -a $USER plugdevAdd a new rule as /etc/udev/udev/rules.d/65-ptouch.rules. Here the number in filename must be higher than 60 to be effective.
SUBSYSTEMS=="usb", ATTRS{idVendor}=="04f9", ATTRS{idProduct}=="209b|209c|209d", MODE="664", GROUP="plugdev"
Reload the rules.
sudo udevadm trigger
sudo udevadm control --reload-rules
Also if you are using Ubuntu 21.10, we need to install extra packages as follows.
sudo apt install linux-modules-extra-raspi
sudo reboot
https://gill.net.in/posts/reverse-engineering-a-usb-device-with-rust/
MIT
