feat: add print mode configuration to support both graphics and text printing options
This commit is contained in:
parent
464bbc3067
commit
4ee383c672
@ -41,6 +41,7 @@ export class BluetoothPrinterConfig extends Component {
|
||||
paperWidthMm: 58, // Paper width in millimeters (58mm or 80mm)
|
||||
autoReconnect: true,
|
||||
timeout: 10000,
|
||||
printMode: 'graphics',
|
||||
|
||||
// UI state
|
||||
showConfiguration: false,
|
||||
@ -87,6 +88,7 @@ export class BluetoothPrinterConfig extends Component {
|
||||
this.state.paperWidthMm = config.settings.paperWidthMm || 58;
|
||||
this.state.autoReconnect = config.settings.autoReconnect !== false;
|
||||
this.state.timeout = config.settings.timeout || 10000;
|
||||
this.state.printMode = config.settings.printMode || 'graphics';
|
||||
}
|
||||
|
||||
this.state.showConfiguration = true;
|
||||
@ -265,6 +267,15 @@ export class BluetoothPrinterConfig extends Component {
|
||||
this._saveConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle print mode change
|
||||
* @param {Event} event - Change event
|
||||
*/
|
||||
onPrintModeChange(event) {
|
||||
this.state.printMode = event.target.value;
|
||||
this._saveConfiguration();
|
||||
}
|
||||
|
||||
onBypassChange(event) {
|
||||
this.state.bypassBluetooth = event.target.checked;
|
||||
this.storageManager.setBypassStatus(this.posConfigId, this.state.bypassBluetooth);
|
||||
@ -344,7 +355,8 @@ export class BluetoothPrinterConfig extends Component {
|
||||
paperWidth: this.state.paperWidth,
|
||||
paperWidthMm: this.state.paperWidthMm,
|
||||
autoReconnect: this.state.autoReconnect,
|
||||
timeout: this.state.timeout
|
||||
timeout: this.state.timeout,
|
||||
printMode: this.state.printMode
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -148,14 +148,54 @@ export class EscPosGenerator {
|
||||
return new Uint8Array(0);
|
||||
}
|
||||
|
||||
// For CP437 and similar single-byte character sets,
|
||||
// we can use a simple encoding approach
|
||||
// For production, you might want to use a proper encoding library
|
||||
const result = new Uint8Array(text.length);
|
||||
const charSet = (this.characterSet || 'CP437').toUpperCase();
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const encoded = encoder.encode(text);
|
||||
// CP437 mapping for common characters above 127
|
||||
const cp437Map = {
|
||||
'ü': 0x81, 'é': 0x82, 'â': 0x83, 'ä': 0x84, 'à': 0x85, 'å': 0x86, 'ç': 0x87, 'ê': 0x88, 'ë': 0x89, 'è': 0x8A, 'ï': 0x8B, 'î': 0x8C, 'ì': 0x8D, 'Ä': 0x8E, 'Å': 0x8F,
|
||||
'É': 0x90, 'æ': 0x91, 'Æ': 0x92, 'ô': 0x93, 'ö': 0x94, 'ò': 0x95, 'û': 0x96, 'ù': 0x97, 'ÿ': 0x98, 'Ö': 0x99, 'Ü': 0x9A, '¢': 0x9B, '£': 0x9C, '¥': 0x9D, '₧': 0x9E, 'ƒ': 0x9F,
|
||||
'á': 0xA0, 'í': 0xA1, 'ó': 0xA2, 'ú': 0xA3, 'ñ': 0xA4, 'Ñ': 0xA5, 'ª': 0xA6, 'º': 0xA7, '¿': 0xA8, '⌐': 0xA9, '¬': 0xAA, '½': 0xAB, '¼': 0xAC, '¡': 0xAD, '«': 0xAE, '»': 0xAF,
|
||||
'ß': 0xE1, 'Γ': 0xE2, 'π': 0xE3, 'Σ': 0xE4, 'σ': 0xE5, 'µ': 0xE6, 'τ': 0xE7, 'Φ': 0xE8, 'Θ': 0xE9, 'Ω': 0xEA, 'δ': 0xEB, '∞': 0xEC, 'φ': 0xED, 'ε': 0xEE, '∩': 0xEF,
|
||||
'≡': 0xF0, '±': 0xF1, '≥': 0xF2, '≤': 0xF3, '⌠': 0xF4, '⌡': 0xF5, '÷': 0xF6, '≈': 0xF7, '°': 0xF8, '∙': 0xF9, '·': 0xFA, '√': 0xFB, 'ⁿ': 0xFC, '²': 0xFD, '■': 0xFE,
|
||||
'€': 0xEE // Mapped to approximate char or standard CP437 fallback
|
||||
};
|
||||
|
||||
// CP850/CP858 mapping for common characters
|
||||
const cp850Map = {
|
||||
'ü': 0x81, 'é': 0x82, 'â': 0x83, 'ä': 0x84, 'à': 0x85, 'å': 0x86, 'ç': 0x87, 'ê': 0x88, 'ë': 0x89, 'è': 0x8A, 'ï': 0x8B, 'î': 0x8C, 'ì': 0x8D, 'Ä': 0x8E, 'Å': 0x8F,
|
||||
'É': 0x90, 'æ': 0x91, 'Æ': 0x92, 'ô': 0x93, 'ö': 0x94, 'ò': 0x95, 'û': 0x96, 'ù': 0x97, 'ÿ': 0x98, 'Ö': 0x99, 'Ü': 0x9A, 'ø': 0x9B, '£': 0x9C, 'Ø': 0x9D, '×': 0x9E, 'ƒ': 0x9F,
|
||||
'á': 0xA0, 'í': 0xA1, 'ó': 0xA2, 'ú': 0xA3, 'ñ': 0xA4, 'Ñ': 0xA5, 'ª': 0xA6, 'º': 0xA7, '¿': 0xA8, '®': 0xA9, '¬': 0xAA, '½': 0xAB, '¼': 0xAC, '¡': 0xAD, '«': 0xAE, '»': 0xAF,
|
||||
'ß': 0xE1, 'µ': 0xE6, '±': 0xF1, '÷': 0xF6, '°': 0xF8, '²': 0xFD,
|
||||
'€': 0xD5 // CP858/CP850 Euro symbol or placeholder
|
||||
};
|
||||
|
||||
// Select active lookup map
|
||||
let activeMap = cp437Map;
|
||||
if (charSet === 'CP850' || charSet === 'CP858' || charSet === 'CP852') {
|
||||
activeMap = cp850Map;
|
||||
}
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i];
|
||||
const code = char.charCodeAt(0);
|
||||
|
||||
if (code <= 127) {
|
||||
result[i] = code;
|
||||
} else if (activeMap[char] !== undefined) {
|
||||
result[i] = activeMap[char];
|
||||
} else {
|
||||
// Normalize to strip accents as a general fallback
|
||||
const normalized = char.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||||
if (normalized.length > 0 && normalized.charCodeAt(0) <= 127) {
|
||||
result[i] = normalized.charCodeAt(0);
|
||||
} else {
|
||||
result[i] = 0x3F; // '?' in ASCII
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return encoded;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -155,8 +155,14 @@ patch(PosPrinterService.prototype, {
|
||||
|
||||
try {
|
||||
// Attempt bluetooth printing
|
||||
console.log('[BluetoothPrint] Attempting bluetooth print...');
|
||||
await this._printViaBluetoothFromHtml(el, services, config);
|
||||
const printMode = config?.settings?.printMode || 'graphics';
|
||||
if (printMode === 'text') {
|
||||
console.log('[BluetoothPrint] Using TEXT MODE as configured');
|
||||
await this._printViaBluetoothTextMode(el, services);
|
||||
} else {
|
||||
console.log('[BluetoothPrint] Attempting bluetooth print in GRAPHICS MODE...');
|
||||
await this._printViaBluetoothFromHtml(el, services, config);
|
||||
}
|
||||
|
||||
console.log('[BluetoothPrint] Print completed successfully');
|
||||
return true;
|
||||
@ -401,7 +407,7 @@ patch(PosPrinterService.prototype, {
|
||||
console.log('[BluetoothPrint] Falling back to text mode...');
|
||||
|
||||
// Fallback to text mode if graphics fails
|
||||
await this._printViaBluetoothTextMode(el, services);
|
||||
await this._printViaBluetoothTextMode(el, services, config);
|
||||
}
|
||||
},
|
||||
|
||||
@ -411,13 +417,26 @@ patch(PosPrinterService.prototype, {
|
||||
* @private
|
||||
* @param {HTMLElement} el - Receipt HTML element
|
||||
* @param {Object} services - Bluetooth services
|
||||
* @param {Object} [config] - Printer configuration
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} If printing fails
|
||||
*/
|
||||
async _printViaBluetoothTextMode(el, services) {
|
||||
async _printViaBluetoothTextMode(el, services, config = null) {
|
||||
const { bluetoothManager, escposGenerator } = services;
|
||||
|
||||
console.log('[BluetoothPrint] Using TEXT MODE (fallback)');
|
||||
console.log('[BluetoothPrint] Using TEXT MODE');
|
||||
|
||||
if (!config) {
|
||||
const storage = new BluetoothPrinterStorage();
|
||||
const pos = this.env?.services?.pos;
|
||||
const posConfigId = pos?.config?.id || 1;
|
||||
config = storage.loadConfiguration(posConfigId);
|
||||
}
|
||||
|
||||
if (config && config.settings && config.settings.characterSet) {
|
||||
escposGenerator.characterSet = config.settings.characterSet;
|
||||
console.log('[BluetoothPrint] Setting character set on generator:', escposGenerator.characterSet);
|
||||
}
|
||||
|
||||
// Parse receipt data from HTML element
|
||||
console.log('[BluetoothPrint] Parsing receipt data from HTML...');
|
||||
|
||||
@ -227,6 +227,10 @@ describe('Error Handling Scenarios', () => {
|
||||
test('sendData with timeout throws TimeoutError', async () => {
|
||||
// Create a mock that takes longer than timeout
|
||||
const mockCharacteristic = {
|
||||
properties: {
|
||||
write: true,
|
||||
writeWithoutResponse: false
|
||||
},
|
||||
writeValue: jest.fn().mockImplementation(() => {
|
||||
return new Promise(resolve => setTimeout(resolve, 200));
|
||||
})
|
||||
|
||||
@ -130,6 +130,21 @@
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Print Mode -->
|
||||
<div class="form-group">
|
||||
<label for="printMode">Print Mode</label>
|
||||
<select id="printMode"
|
||||
class="form-control"
|
||||
t-model="state.printMode"
|
||||
t-on-change="onPrintModeChange">
|
||||
<option value="graphics">Graphics Mode (Exact Layout)</option>
|
||||
<option value="text">Text Mode (Extremely Compatible)</option>
|
||||
</select>
|
||||
<small class="form-text text-muted">
|
||||
Choose Graphics Mode for exact layouts, or Text Mode if print is garbled
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Paper Width (characters) - Auto-adjusted -->
|
||||
<div class="form-group">
|
||||
<label for="paperWidth">Characters Per Line</label>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user