Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Copilot Instructions for NiimPrintX

## Project Overview
NiimPrintX is a Python library for interfacing with NiimBot label printers via Bluetooth. It provides both CLI and GUI interfaces for designing and printing labels.

## Requirements
- Python 3.12 or later
- ImageMagick library
- Poetry for dependency management

## Setup for Testing

### 1. Install System Dependencies

**Linux (Ubuntu/Debian):**
```bash
sudo apt-get update
sudo apt-get install -y imagemagick libmagickwand-dev python3-tk xvfb libcairo2-dev pkg-config
```

**macOS:**
```bash
brew install libffi glib gobject-introspection cairo pkg-config imagemagick
export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig"
export LDFLAGS="-L/usr/local/opt/libffi/lib"
export CFLAGS="-I/usr/local/opt/libffi/include"
```

### 2. Install Python Dependencies

```bash
# Using pip (Linux/CI)
pip install -r requirements-ci.txt

# Using pip (macOS - includes pyobjc packages)
pip install -r requirements.txt

# Or using Poetry (recommended)
python -m venv venv
poetry install
```

**Note:** `requirements.txt` includes macOS-specific packages (pyobjc-*) which will fail on Linux. Use `requirements-ci.txt` for Linux/CI environments.

### 3. Running the Application

**GUI Application:**
```bash
python -m NiimPrintX.ui
```

**CLI Application:**
```bash
# Print command
python -m NiimPrintX.cli print -m d110 -d 3 -n 1 -r 90 -i path/to/image.png

# Info command
python -m NiimPrintX.cli info -m d110
```

## Testing Guidelines

### UI Testing (Headless Environment)

For GUI testing in CI/CD or headless environments:

```bash
# Start virtual display
Xvfb :99 -screen 0 1024x768x24 &
export DISPLAY=:99

# Run the UI
python -m NiimPrintX.ui
```

### Linting

```bash
# Install flake8
pip install flake8

# Run linter (critical errors only)
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics

# Run full lint (warnings as info)
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
```

### Security Scanning

Use CodeQL for security analysis:
```bash
# CodeQL analysis is configured in GitHub Actions
# Check .github/workflows/python-app.yml
```

## Supported Printer Models
- D11
- D110
- B21
- B1
- B18

## Key Modules

### UI Components
- `NiimPrintX/ui/main.py` - Main application window
- `NiimPrintX/ui/widget/TextTab.py` - Text input tab
- `NiimPrintX/ui/widget/TextOperation.py` - Text rendering operations
- `NiimPrintX/ui/widget/PrintOption.py` - Print settings

### CLI Components
- `NiimPrintX/cli/__main__.py` - CLI entry point

### Printer Interface
- `NiimPrintX/nimmy/printer.py` - Printer communication

## Testing Multi-line Text Feature

```python
import tkinter as tk
from NiimPrintX.ui.main import LabelPrinterApp

# Create app
app = LabelPrinterApp()
app.load_resources()

# Access text tab
text_tab = app.text_tab

# Test multi-line input
multi_line_text = "Line 1\nLine 2\nLine 3"
text_tab.content_entry.delete("1.0", tk.END)
text_tab.content_entry.insert("1.0", multi_line_text)

# Add to canvas
text_tab.add_button.invoke()

# Verify
assert len(app.app_config.text_items) > 0
```

## Common Issues

### Missing tkinter
```bash
sudo apt-get install python3-tk # Linux
# tkinter is included with Python on macOS/Windows
```

### Missing ImageMagick
```bash
# Linux
sudo apt-get install imagemagick libmagickwand-dev

# macOS
brew install imagemagick
```

### Display not available (for testing)
```bash
# Use Xvfb for headless testing
Xvfb :99 -screen 0 1024x768x24 &
export DISPLAY=:99
```

## CI/CD Integration

The project uses GitHub Actions with the following workflows:
- `python-app.yml` - Linting and testing
- `tag.yaml` - Release builds
- Build workflows for Linux, macOS, and Windows

See `.github/workflows/` for configuration details.
44 changes: 44 additions & 0 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python application

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v3
with:
python-version: "3.12"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y imagemagick libmagickwand-dev python3-tk xvfb libcairo2-dev pkg-config
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
# Install core dependencies (excluding macOS-specific packages)
if [ -f requirements-ci.txt ]; then pip install -r requirements-ci.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest || echo "No tests found, skipping"
15 changes: 8 additions & 7 deletions NiimPrintX/ui/widget/TextOperation.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ def create_text_image(self, font_props, text):
draw.text_kerning = font_props["kerning"]
draw.fill_color = Color('black') # Set font color to black
draw.resolution = (300, 300) # 300 DPI for high quality text rendering
metrics = draw.get_font_metrics(WandImage(width=1, height=1), text, multiline=False)
metrics = draw.get_font_metrics(WandImage(width=1, height=1), text, multiline=True)
text_width = int(metrics.text_width) + 5
text_height = int(metrics.text_height) + 5

# Create a new WandImage
with WandImage(width=text_width, height=text_height, background=Color('transparent')) as img:
draw.text(x=2, y=int(text_height / 2 + metrics.ascender / 2), body=text)
# Position text at top using ascender for proper multi-line rendering
draw.text(x=2, y=int(metrics.ascender), body=text)
draw(img)

# Ensure the image is in RGBA format
Expand All @@ -47,8 +48,8 @@ def create_text_image(self, font_props, text):
tk_image = tk.PhotoImage(data=img_blob)
return tk_image
def add_text_to_canvas(self):
# Get the current text in the content_entry Entry widget
text = self.parent.content_entry.get()
# Get the current text in the content_entry Text widget
text = self.parent.content_entry.get("1.0", "end-1c")

font_obj, font_props = self.parent.get_font_properties()
if not text:
Expand Down Expand Up @@ -87,8 +88,8 @@ def update_widgets(self, text_id):
font_prop = self.config.text_items[text_id]['font_props']
text = self.config.text_items[text_id]['content']

self.parent.content_entry.delete(0, tk.END)
self.parent.content_entry.insert(0, text)
self.parent.content_entry.delete("1.0", tk.END)
self.parent.content_entry.insert("1.0", text)

self.parent.font_family_dropdown.set(font_prop["family"])
# self.parent.font_dropdown.set(font_prop["font"])
Expand All @@ -113,7 +114,7 @@ def update_widgets(self, text_id):
self.parent.add_button.config(text="Update", command=lambda t_id=text_id: self.update_canvas_text(t_id))

def update_canvas_text(self, text_id):
text = self.parent.content_entry.get()
text = self.parent.content_entry.get("1.0", "end-1c")
self.config.text_items[text_id]['content'] = text
font_props = self.config.text_items[text_id]['font_props']
tk_image = self.create_text_image(font_props, text)
Expand Down
24 changes: 18 additions & 6 deletions NiimPrintX/ui/widget/TextTab.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,22 @@ def create_widgets(self):
elif self.config.os_system == "Windows":
default_bg = 'systemButtonFace'

tk.Label(self.frame, text="Content", bg=default_bg).grid(row=0, column=0, sticky='w')
self.content_entry = tk.Entry(self.frame, highlightbackground=default_bg)
self.content_entry.grid(row=0, column=1, sticky='ew', padx=5)
self.content_entry.insert(0, "Text")
# Content label and multi-line text entry with scrollbar
tk.Label(self.frame, text="Content", bg=default_bg).grid(row=0, column=0, sticky='nw')

# Create frame to hold text widget and scrollbar
text_frame = tk.Frame(self.frame, bg=default_bg)
text_frame.grid(row=0, column=1, sticky='ew', padx=5)

self.content_entry = tk.Text(text_frame, highlightbackground=default_bg, height=3, width=30)
self.content_entry.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

# Add scrollbar for longer text
scrollbar = tk.Scrollbar(text_frame, command=self.content_entry.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.content_entry.config(yscrollcommand=scrollbar.set)

self.content_entry.insert("1.0", "Text")

self.sample_text_label = tk.Label(self.frame, text="Sample Text", font=('Arial', 14), bg=default_bg)
self.sample_text_label.grid(row=0, column=2, sticky='w', columnspan=3)
Expand Down Expand Up @@ -96,14 +108,14 @@ def update_font_list(self, event=None):
# self.font_dropdown['values'] = list(self.fonts[font_family]["fonts"].keys())
# self.font_dropdown.current(0)

content = self.content_entry.get()
content = self.content_entry.get("1.0", "end-1c")
label_font = tk_font.Font(family=font_family, size=14)
self.sample_text_label.config(font=label_font,
text=f"{content} in {font_family}")

def update_text_properties(self, event=None, widget_name=None):
font_obj, font_props = self.get_font_properties()
content = self.content_entry.get()
content = self.content_entry.get("1.0", "end-1c")
label_font = tk_font.Font(family=font_props['family'], size=14, weight=font_props['weight'],
slant=font_props['slant'])
self.sample_text_label.config(font=label_font,
Expand Down
19 changes: 19 additions & 0 deletions requirements-ci.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Core dependencies for CI/CD (platform-agnostic)
# Excludes macOS-specific packages (pyobjc-*)

appdirs==1.4.4
asttokens==2.4.1
bleak==0.21.1
click==8.1.7
devtools==0.12.2
executing==2.0.1
loguru==0.7.2
markdown-it-py==3.0.0
mdurl==0.1.2
packaging==24.0
pillow==10.3.0
pycairo==1.26.0
Pygments==2.17.2
rich==13.7.1
six==1.16.0
Wand==0.6.13