ptouch 1.1.0
Python library for Brother P-touch label printers
Requires Python: >=3.11
Keywords: brother, p-touch, label, printer, raster

# ptouch

[![Tests](https://github.com/nbuchwitz/python3-ptouch/actions/workflows/pytest.yml/badge.svg)](https://github.com/nbuchwitz/python3-ptouch/actions/workflows/pytest.yml)
[![Linting](https://github.com/nbuchwitz/python3-ptouch/actions/workflows/lint.yml/badge.svg)](https://github.com/nbuchwitz/python3-ptouch/actions/workflows/lint.yml)
[![Documentation Status](https://readthedocs.org/projects/ptouch/badge/?version=latest)](https://ptouch.readthedocs.io/en/latest/?badge=latest)

A Python library for Brother P-touch label printers.

## Features

- Support for Brother P-touch label printers (PT-E550W, PT-P750W, PT-P900, PT-P900W, PT-P910BT, PT-P950NW)
- Network (TCP/IP) and USB connections
- Text labels with customizable fonts and alignment
- Image label printing
- Multi-label printing with half-cut support (saves tape)
- Heat shrink tube support (HSe 2:1 and 3:1 series on PT-E550W/P750W/P900/P900W/P950NW)
- High resolution mode support
- TIFF compression for efficient data transfer

## Installation

```bash
pip install ptouch
```

### Dependencies

- Python 3.11+
- Pillow
- packbits

For USB support:
- pyusb

## Documentation

Comprehensive documentation is available at [ptouch.readthedocs.io](https://ptouch.readthedocs.io/) including:

- [Quick Start Guide](https://ptouch.readthedocs.io/en/latest/quickstart.html)
- [User Guide](https://ptouch.readthedocs.io/en/latest/userguide.html)
- [Examples](https://ptouch.readthedocs.io/en/latest/examples.html)
- [API Reference](https://ptouch.readthedocs.io/en/latest/api/index.html)
- [Troubleshooting](https://ptouch.readthedocs.io/en/latest/troubleshooting.html)

## Supported Devices

### Printers

| Printer | Resolution | High-Res | Pins | Max Tape Width | Class |
|---------|------------|----------|------|----------------|-------|
| PT-E550W | 180 DPI | 360 DPI | 128 | 24mm | `PTE550W` |
| PT-P750W | 180 DPI | 360 DPI | 128 | 24mm | `PTP750W` |
| PT-P900 | 360 DPI | 720 DPI | 560 | 36mm | `PTP900` |
| PT-P900W | 360 DPI | 720 DPI | 560 | 36mm | `PTP900W` |
| PT-P910BT | 360 DPI | 720 DPI | 560 | 36mm | `PTP910BT` |
| PT-P950NW | 360 DPI | 720 DPI | 560 | 36mm | `PTP950NW` |

### Tapes

| Type | Widths | Class | Notes |
|------|--------|-------|-------|
| TZe (Laminated) | 3.5mm, 6mm, 9mm, 12mm, 18mm, 24mm, 36mm | `Tape*mm` | All printers |
| HSe 2:1 (Heat Shrink) | 5.8mm, 8.8mm, 11.7mm, 17.7mm, 23.6mm | `HeatShrinkTube*mm` | E550W/P750W/P900/P900W/P950NW |
| HSe 3:1 (Heat Shrink) | 5.2mm, 9.0mm, 11.2mm, 21.0mm, 31.0mm | `HeatShrinkTube3_1_*mm` | E550W/P750W: up to 21mm; P900: all |

## Adding Support for New Devices

### Adding a New Printer

To add support for a new P-touch printer, create a subclass of `LabelPrinter` in `ptouch/printers.py`:

```python
from ptouch.printer import LabelPrinter, TapeConfig
from ptouch.tape import Tape12mm, Tape24mm  # etc.

class PTP710BT(LabelPrinter):
    """Brother PT-P710BT label printer."""

    # USB product ID (required for USB connections)
    USB_PRODUCT_ID = 0x20XX   # Replace with actual product ID

    # Print head specifications (from Brother raster command reference)
    TOTAL_PINS = 128          # Total pins in print head
    BYTES_PER_LINE = 16       # TOTAL_PINS / 8
    RESOLUTION_DPI = 180      # Base resolution
    RESOLUTION_DPI_HIGH = 360 # High resolution (0 if not supported)

    # Capability flags - what the printer supports
    SUPPORTS_AUTO_CUT = True
    SUPPORTS_HALF_CUT = True
    SUPPORTS_PAGE_NUMBER_CUTS = True

    # Default values for each feature (used when not specified at print time)
    DEFAULT_USE_COMPRESSION = True
    DEFAULT_AUTO_CUT = True
    DEFAULT_HALF_CUT = True
    DEFAULT_HIGH_RESOLUTION = False
    DEFAULT_PAGE_NUMBER_CUTS = False

    # Pin configuration for each tape width
    # Values from Brother raster command reference PDF
    PIN_CONFIGS = {
        Tape12mm: TapeConfig(left_pins=29, print_pins=70, right_pins=29),
        Tape24mm: TapeConfig(left_pins=0, print_pins=128, right_pins=0),
        # Add more tape sizes as needed
    }
```

The pin configuration values (`left_pins`, `print_pins`, `right_pins`) can be found in the
Brother raster command reference documentation for your printer model.

### Adding a New Tape Type

To add a new tape type, create a subclass of `Tape` in `ptouch/tape.py`:

```python
from ptouch.tape import Tape

class Tape48mm(Tape):
    """48mm tape."""
    width_mm = 48
```

Then add the corresponding `TapeConfig` entries to each printer class that supports the tape

## Usage

### Command Line

When the package is installed, the `ptouch` command is available (or use `python -m ptouch` when running from source):

```bash
# Print text label via network (uses PIL default font)
ptouch "Hello World" --host 192.168.1.100 --printer P900 --tape-width 36

# Print with custom font
ptouch "Hello World" --host 192.168.1.100 --printer P900 \
    --tape-width 36 --font /path/to/font.ttf

# Print multiple labels (half-cut between, full cut after last)
ptouch "Label 1" "Label 2" "Label 3" --host 192.168.1.100 \
    --printer P900 --tape-width 12

# Print multiple labels with full cuts between each
ptouch "Label 1" "Label 2" --full-cut --host 192.168.1.100 \
    --printer P900 --tape-width 12

# Print image label via USB
ptouch --image logo.png --usb --printer E550W --tape-width 12

# Print with fixed font size (disables auto-sizing)
ptouch "Test" --host 192.168.1.100 --printer P900 --tape-width 24 \
    --font-size 48 --high-resolution --align left top --margin 5

# Print 5 copies of a label
ptouch "Asset Tag" --copies 5 --host 192.168.1.100 \
    --printer P900 --tape-width 12

# Print label with fixed width (50mm)
ptouch "Short" --width 50 --host 192.168.1.100 \
    --printer P900 --tape-width 12
```

### Python API

#### Text Labels

```python
from ptouch import (
    ConnectionNetwork,
    PTP900,
    TextLabel,
    Tape36mm,
)

# Connect to printer
connection = ConnectionNetwork("192.168.1.100")
printer = PTP900(connection, high_resolution=True)

# Create and print text label
label = TextLabel(
    "Hello World",
    Tape36mm,
    font="/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
    align=TextLabel.Align.CENTER,
)
printer.print(label)

# Or use a pre-loaded ImageFont (auto-sized by default)
from PIL import ImageFont
font = ImageFont.truetype("/path/to/font.ttf", size=48)
label = TextLabel("Custom Font", Tape36mm, font=font)

# Use ImageFont with its built-in size (disable auto-sizing)
label = TextLabel("Fixed Size", Tape36mm, font=font, auto_size=False)

# For quick testing, use the default font (requires Pillow 10.1+)
label = TextLabel("Quick Test", Tape36mm, font=ImageFont.load_default())
```

#### Image Labels

```python
from PIL import Image
from ptouch import ConnectionNetwork, PTP900, Label, Tape36mm

connection = ConnectionNetwork("192.168.1.100")
printer = PTP900(connection)

image = Image.open("label.png")
label = Label(image, Tape36mm)
printer.print(label, margin_mm=3.0)
```

#### USB Connection

```python
from ptouch import ConnectionUSB, PTE550W, TextLabel, Tape12mm

connection = ConnectionUSB()
printer = PTE550W(connection)

label = TextLabel(
    "USB Label",
    Tape12mm,
    font="/path/to/font.ttf",
)
printer.print(label)
```

#### Multi-Label Printing

Print multiple labels in a single job with half-cuts between labels to save tape:

```python
from ptouch import ConnectionNetwork, PTP900, TextLabel, Tape12mm

connection = ConnectionNetwork("192.168.1.100")
printer = PTP900(connection)

labels = [
    TextLabel("Label 1", Tape12mm, font="/path/to/font.ttf"),
    TextLabel("Label 2", Tape12mm, font="/path/to/font.ttf"),
    TextLabel("Label 3", Tape12mm, font="/path/to/font.ttf"),
]

# Half-cuts between labels (default), full cut after last
printer.print_multi(labels)

# Or use full cuts between all labels
printer.print_multi(labels, half_cut=False)
```

### Alignment Options

Text alignment can be combined using the `|` operator:

```python
from ptouch import TextLabel

# Horizontal: LEFT, HCENTER, RIGHT
# Vertical: TOP, VCENTER, BOTTOM
# Combined: CENTER (= HCENTER | VCENTER)

align = TextLabel.Align.LEFT | TextLabel.Align.TOP
align = TextLabel.Align.RIGHT | TextLabel.Align.BOTTOM
align = TextLabel.Align.CENTER  # centered both ways
```

Note: `Align` is also available as a backwards-compatible alias at package level.

## CLI Options

```
usage: ptouch [-h] [--image FILE] (--host IP | --usb) --printer {E550W,P750W,P900,P900W,P950NW}
              --tape-width {3.5,6,9,12,18,24,36} [--font PATH] [--font-size PX]
              [--align H V] [--high-resolution] [--margin MM] [--no-compression]
              [--full-cut] [--copies N] [--width MM] [text ...]

positional arguments:
  text                  Text to print. Multiple strings create multiple labels
                        with half-cut between (required unless --image is used)

options:
  --image, -i FILE      Image file to print instead of text
  --host, -H IP         Printer IP address for network connection
  --usb                 Use USB connection
  --printer, -p         Printer model
  --tape-width, -t      Tape width in mm
  --font, -f PATH       Path to TrueType font file (uses PIL default if not specified)
  --font-size PX        Font size in pixels (disables auto-sizing to 80% of print height)
  --align, -a H V       Horizontal and vertical alignment (default: center center)
  --high-resolution     Enable high resolution mode
  --margin, -m MM       Margin in mm (default: 2mm)
  --no-compression      Disable TIFF compression
  --full-cut            Use full cuts between labels instead of half-cuts
  --copies, -c N        Number of copies to print (default: 1)
  --width, -w MM        Fixed label width in mm (default: auto-sized to content)
```

## Error Handling

The library provides a hierarchy of exception classes for targeted error handling:

```python
from ptouch import (
    PrinterConnectionError,     # Base exception for all connection errors
    PrinterNotFoundError,        # Printer device not found
    PrinterPermissionError,      # Permission denied (USB requires sudo/udev rules)
    PrinterNetworkError,         # Network connection/communication errors
    PrinterTimeoutError,         # Connection or operation timeout
    PrinterWriteError,           # Failed to write data to printer
)

try:
    printer.print(label)
except PrinterPermissionError:
    print("Permission denied. Try running with sudo or configure udev rules.")
except PrinterTimeoutError:
    print("Printer not responding. Check connection and power.")
except PrinterNotFoundError:
    print("Printer not found. Check if device is connected.")
except PrinterConnectionError:
    print("General connection error occurred.")
```

All specific exceptions inherit from `PrinterConnectionError`, allowing you to catch all connection-related errors with a single except clause, or handle specific error types individually for more targeted error handling.

## License

LGPL-2.1-or-later - see [LICENSE](LICENSE) for details.

## Author

Nicolai Buchwitz <nb@tipi-net.de>