| Crates.io | ql-label |
| lib.rs | ql-label |
| version | 0.2.1 |
| created_at | 2025-09-04 07:47:23.445934+00 |
| updated_at | 2025-09-04 07:47:23.445934+00 |
| description | Brother QL series label printer driver for Rust |
| homepage | https://github.com/kyasu1/ql-label |
| repository | https://github.com/kyasu1/ql-label.git |
| max_upload_size | |
| id | 1823809 |
| size | 805,682 |
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.
Here are some examples of labels printed with this library:

The samples show:
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:
cp .env.example .env
.env with your printer information:# Set your actual printer information
DEFAULT_MODEL=QL820NWB
SERIAL=your_actual_serial_here
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:
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())?;
let two_color_data = ql_label::TwoColorMatrix::new(black_matrix, red_matrix)?;
The color detection automatically identifies:
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:
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.
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.
convert_rgb_to_two_color for automatic color separationprint_two_color() method with alternating raster line processingThe 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.)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 plugdev
Add 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