react-native-niimblue-lib
v1.2.1
Published
React Native library for BLE printing with NIIMBOT devices
Downloads
324
Maintainers
Readme
React Native NiimBlue Library
A React Native library for Bluetooth LE printing with NIIMBOT thermal printers. Features automatic printer model detection, rich content rendering (text, QR codes, barcodes, images), and comprehensive print control.
✨ Features
- 🔍 Auto-detect printer models - Automatically selects the correct print task based on connected printer
- 📱 BLE Communication - Direct Bluetooth Low Energy connection to NIIMBOT printers
- 🎨 Rich Content Support:
- Text rendering with Skia (custom fonts, alignment, scaling)
- QR codes with error correction levels
- Barcodes (EAN13, CODE128)
- Images from buffers or URIs (PNG, JPG, BMP)
- Lines and custom pixel data
- 🖼️ Print Preview - Generate base64 PNG previews before printing
- 📏 Flexible Layout - Pixel-perfect positioning with alignment options
- 🔄 Multiple Printer Support - B1, B21, D110, D11 and more
📦 Installation
npm install react-native-niimblue-libInstall Required Peer Dependencies
Always required for Bluetooth connectivity:
npm install react-native-ble-plxOptional - only needed if you use PrintPage.addText() for text rendering:
npm install @shopify/react-native-skiaNote: If you only print QR codes, barcodes, or images without text, you don't need Skia.
Alternative: Instead of using Skia, you can render text to image/pixel data using any method (Canvas, SVG, native views, etc.) and add it via
addPixelData()oraddImageFromBuffer().Important: These dependencies have native code and require proper linking/building. They cannot work as transitive dependencies.
Expo Setup
If using Expo, add the BLE plugin to your app.json:
{
"expo": {
"plugins": [
[
"react-native-ble-plx",
{
"isBackgroundEnabled": true,
"modes": ["peripheral", "central"],
"bluetoothAlwaysPermission": "App needs Bluetooth to connect to NIIMBOT printers"
}
]
]
}
}Then create a development build:
npx expo prebuild
npx expo run:ios
# or
npx expo run:androidNote: If you use
PrintPage.addText(), Skia requires a development build and does NOT work in Expo Go. However, QR codes, barcodes, and images work fine without Skia.
iOS Setup
- Install CocoaPods dependencies:
cd ios && pod install- Add to
Info.plist:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>App needs Bluetooth to connect to NIIMBOT printers</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Required for Bluetooth device scanning</string>- Note: If you installed
@shopify/react-native-skiafor text rendering, runpod installagain after installation.
Android Setup
Add permissions to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.INTERNET" />🚀 Quick Start
Basic Printing
import { NiimbotBluetoothClient, PrintPage } from 'react-native-niimblue-lib';
// 1. Connect to printer
const client = new NiimbotBluetoothClient();
await client.connect(); // Auto-scans and connects to first NIIMBOT device
// 2. Create print task (auto-detects printer model)
client.stopHeartbeat();
client.setPacketInterval(0); // Fast printing
const task = client.createPrintTask({
totalPages: 1,
density: 3,
labelType: 1,
});
if (!task) {
throw new Error('Printer model not detected');
}
// 3. Build page content
const page = new PrintPage(400, 240); // width x height in pixels
page.addText('Hello NIIMBOT!', {
x: 192,
y: 100,
fontSize: 24,
align: 'center',
vAlign: 'middle',
});
// 4. Print
await task.printInit();
await task.printPage(page.toEncodedImage(), 1);
await task.waitForFinished();
client.startHeartbeat();📖 Usage Examples
Page Orientation
Use orientation parameter to automatically rotate all content for landscape printing:
// Portrait mode (default) - for vertical labels
const portraitPage = new PrintPage(400, 240, 'portrait');
portraitPage.addText('Product Name', { x: 200, y: 120 });
// Landscape mode - for horizontal labels on vertical paper
// Dimensions are AUTO-SWAPPED: 240x400 becomes 400x240 canvas + 90° rotation
const landscapePage = new PrintPage(240, 400, 'landscape');
landscapePage.addText('Product Name', {
x: 200, // Same coordinates work!
y: 120, // Canvas is 400x240 (swapped) + rotated 90°
});How it works:
- Physical paper: 240px width × 400px height (vertical)
new PrintPage(240, 400, 'landscape'):- ✅ Canvas dimensions: 400×240 (auto-swapped)
- ✅ Content rotated: 90° clockwise
- ✅ Result: Horizontal content on vertical paper
- ✅ Same coordinates as
new PrintPage(400, 240, 'portrait')
Text Rendering
Option 1: Using Skia (requires @shopify/react-native-skia)
const page = new PrintPage(400, 240);
// Simple text with default font
page.addText('NIIMBOT PRINTER', {
x: 192,
y: 50,
fontSize: 24,
align: 'center',
vAlign: 'middle',
});
// Bold text with manual rotation
import { Skia, FontStyle } from '@shopify/react-native-skia';
page.addText('Rotated Text', {
x: 192,
y: 100,
fontSize: 20,
fontStyle: FontStyle.Bold,
align: 'center',
vAlign: 'middle',
rotate: 45, // Additional rotation (on top of page orientation)
});
// Custom font style
page.addText('Heavy Text', {
x: 192,
y: 140,
fontSize: 18,
fontStyle: Skia.FontStyle({ weight: 800 }), // Custom weight
align: 'center',
vAlign: 'middle',
rotate: -15, // Rotate 15 degrees counter-clockwise
});
// Custom font (user loads Typeface separately)
const fontData = await Skia.Data.fromURI('file://path/to/font.ttf');
const customTypeface = Skia.Typeface.MakeFreeTypeFaceFromData(fontData);
page.addText('Custom Font Text', {
x: 100,
y: 180,
fontSize: 16,
typeface: customTypeface, // Optional
align: 'left',
});Option 2: Without Skia - Convert text to image first
// Example: Using react-native-view-shot to capture text as image
import { captureRef } from 'react-native-view-shot';
// 1. Render text in a hidden View with ref
const textRef = useRef();
// 2. Capture as base64
const uri = await captureRef(textRef, {
format: 'png',
quality: 1,
});
// 3. Convert to buffer and add to page
const response = await fetch(uri);
const arrayBuffer = await response.arrayBuffer();
const buffer = new Uint8Array(arrayBuffer);
page.addImageFromBuffer({
buffer,
x: 192,
y: 100,
align: 'center',
vAlign: 'middle',
});
// Or use any other method: Canvas, SVG to PNG, native rendering, etc.QR Code
page.addQR('https://github.com', {
x: 192,
y: 100,
width: 150,
height: 150,
align: 'center',
vAlign: 'middle',
ecl: 'M', // Error correction: L, M, Q, H
rotate: 90, // Optional: rotate 90 degrees
});Barcode
page.addBarcode('123456789012', {
encoding: 'EAN13', // or 'CODE128'
x: 192,
y: 150,
width: 200,
height: 60,
align: 'center',
vAlign: 'middle',
rotate: 180, // Optional: rotate 180 degrees
});Image from Buffer (PNG/JPG/BMP)
const imageBuffer = await fetch('https://example.com/image.jpg')
.then(res => res.arrayBuffer())
.then(buf => new Uint8Array(buf));
page.addImageFromBuffer({
buffer: imageBuffer,
x: 192,
y: 100,
width: 200,
height: 150,
align: 'center',
vAlign: 'middle',
threshold: 128, // Grayscale to binary threshold
rotate: 270, // Optional: rotate 270 degrees
});Image from URI
await page.addImageFromUri('https://example.com/logo.png', {
x: 192,
y: 100,
width: 150,
height: 150,
align: 'center',
vAlign: 'middle',
threshold: 128,
});Custom Pixel Data
const heartPixels = [
0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,
0,0,1,1,1,1,1,0,0,1,1,1,1,0,0,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,
// ... (1 = black, 0 = white)
];
page.addPixelData({
data: heartPixels,
imageWidth: 16,
imageHeight: 11,
x: 192,
y: 100,
width: 128, // Scaled width
height: 88,
align: 'center',
vAlign: 'middle',
rotate: 45, // Optional: rotate 45 degrees
});Line Drawing
page.addLine({
x: 10,
y: 100,
endX: 374,
endY: 100,
thickness: 2,
});Print Preview
const page = new PrintPage(400, 240);
page.addQR('Preview Test', { x: 192, y: 100, align: 'center', vAlign: 'middle' });
// Generate base64 PNG
const base64Uri = await page.toPreviewImage();
// Display in <Image source={{ uri: base64Uri }} />🔧 API Reference
NiimbotBluetoothClient
Methods
connect(device?: Device): Connect to printer (auto-scan or specific device)disconnect(): Disconnect from printerlistDevices(scanDurationMs: number): Scan for available printerslistConnectedDevices(): List already connected devicescreatePrintTask(options: PrintOptions): Create print task with auto-detectionsetPacketInterval(ms: number): Set delay between packets (0 = fastest)startHeartbeat()/stopHeartbeat(): Control heartbeat
PrintPage
Constructor
new PrintPage(width: number, height: number, orientation?: PageOrientation)width: number- Page width in pixelsheight: number- Page height in pixelsorientation?: 'portrait' | 'landscape'- Page orientation (default: 'portrait')'portrait': Normal orientation, canvas dimensions = (width, height)'landscape': Auto-swaps dimensions → canvas becomes (height, width) + rotates all content 90° clockwise- Perfect for printing horizontal content on vertical paper
- Example:
new PrintPage(240, 400, 'landscape')→ 400×240 canvas with 90° rotation - Use same coordinates as
new PrintPage(400, 240, 'portrait')
Methods
addText(text: string, options: TextOptions): voidaddQR(text: string, options: QROptions): voidaddBarcode(text: string, options: BarcodeOptions): voidaddPixelData(options: ImageOptions): voidaddImageFromBuffer(options: ImageFromBufferOptions): voidaddImageFromUri(uri: string, options: ImageFromBufferOptions): Promise<void>addLine(options: LineOptions): voidtoEncodedImage(): EncodedImage- Convert to printer formattoPreviewImage(): Promise<string>- Generate base64 PNG preview
Type Definitions
PageOrientation
type PageOrientation = 'portrait' | 'landscape';PrintOptions
totalPages?: number- Number of pages to printdensity?: number- Print density (1-5, default: 3, higher = darker)labelType?: number- Label type identifier (printer-specific)statusPollIntervalMs?: number- Status polling interval in ms (default: 100)statusTimeoutMs?: number- Status check timeout in ms (default: 8000)pageTimeoutMs?: number- Timeout for printing each page in ms
PrintElementOptions (Base)
All positioning options support:
x: number- X coordinate in pixelsy: number- Y coordinate in pixelswidth?: number- Optional width (auto-scales if only one dimension provided)height?: number- Optional height (auto-scales if only one dimension provided)align?: 'left' | 'center' | 'right'- Horizontal alignment relative to x coordinatevAlign?: 'top' | 'middle' | 'bottom'- Vertical alignment relative to y coordinaterotate?: number- Rotation angle in degrees (0-360), rotates around element center. This is additional rotation on top of page orientation.
TextOptions
Extends PrintElementOptions with:
fontSize?: number- Font size in pixels (default: 12)typeface?: SkTypeface- Custom Skia typeface (optional, uses system font if not provided)fontStyle?: FontStyle- Font style for weight, width, and slant (optional, uses Normal if not provided)- Use
Skia.FontStyle()to create custom styles with weight (100-900), width, and slant - Common presets:
FontStyle.Normal,FontStyle.Bold,FontStyle.Italic,FontStyle.BoldItalic
- Use
QROptions
Extends PrintElementOptions with:
ecl?: 'L' | 'M' | 'Q' | 'H'- Error correction level (default: 'M')- L: Low (~7% correction)
- M: Medium (~15% correction)
- Q: Quartile (~25% correction)
- H: High (~30% correction)
BarcodeOptions
Extends PrintElementOptions with:
encoding?: 'EAN13' | 'CODE128'- Barcode encoding format (default: 'EAN13')
ImageOptions
Extends PrintElementOptions with:
data: number[]- 1D array of pixel data (1 = black, 0 = white)imageWidth: number- Original image width in pixelsimageHeight: number- Original image height in pixels
ImageFromBufferOptions
Extends PrintElementOptions with:
buffer: Uint8Array- Image file buffer (supports PNG/JPG/BMP formats)threshold?: number- Grayscale to binary conversion threshold (0-255, default: 128, lower = darker)- Plus all
PrintElementOptions(x, y, width, height, align, vAlign, rotate)
LineOptions
x: number- Start X coordinate in pixelsy: number- Start Y coordinate in pixelsendX: number- End X coordinate in pixelsendY: number- End Y coordinate in pixelsthickness?: number- Line thickness in pixels (default: 1)
🎯 Alignment System
The library uses reference point alignment:
// Center text at position (192, 100)
page.addText('Centered', {
x: 192, // Reference X
y: 100, // Reference Y
align: 'center', // Text center aligns to x
vAlign: 'middle', // Text middle aligns to y
});
// Right-bottom align at position (350, 180)
page.addText('Corner', {
x: 350,
y: 180,
align: 'right', // Right edge at x=350
vAlign: 'bottom', // Bottom edge at y=180
});🐛 Troubleshooting
Bluetooth Connection Issues
- Problem: Cannot find or connect to printer
- Solution:
- Ensure Bluetooth is enabled on your device
- Make sure printer is powered on and in pairing mode
- Check that all required permissions are granted (Bluetooth, Location on Android)
- Try
listDevices()to scan for available printers
Text Not Rendering
- Problem: Text appears blank or crashes
- Solution:
- If using
addText(), make sure@shopify/react-native-skiais installed - Use Expo development build (not Expo Go) when using Skia
- Alternatively, use
addImageFromBuffer()with pre-rendered text images
- If using
Print Quality Issues
- Problem: Print is too light or too dark
- Solution: Adjust the
densityparameter (1-5, default 3) increatePrintTask()
Image Not Printing Correctly
- Problem: Image appears distorted or incorrect colors
- Solution:
- Ensure image dimensions are multiples of 8 pixels for width
- Adjust
thresholdparameter (default 128) inaddImageFromBuffer() - Images are converted to black & white - use high contrast images
Android Crash on Disconnect
- Problem: App crashes when disconnecting from printer on Android
- Solution: This is a known issue with
react-native-ble-plx. See workaround: https://github.com/dotintent/react-native-ble-plx/issues/1303#issuecomment-3367559459
Build Errors
- Problem: Native module errors during build
- Solution:
- Run
pod installin iOS directory after installing dependencies - For Android, sync gradle after adding dependencies
- Clean build:
cd ios && rm -rf Pods && pod installorcd android && ./gradlew clean
- Run
🔄 Migration from Web Version
Key changes from niimbluelib web version:
- No Canvas API - Use
PrintPageclass instead - Skia for Text - Requires
@shopify/react-native-skiaand Expo dev build - Auto Print Tasks - Use
createPrintTask()instead of manual model selection - Sync Text Rendering -
addText()is now synchronous (load fonts externally) - Buffer Polyfill - Uses
bufferpackage for image decoding
📄 License
MIT
🙏 Credits
Based on niimbluelib by MultiMote.
