Improve ticket legibility: larger fonts, spacing, indentation and thermal emphasized mode
This commit is contained in:
@@ -16,9 +16,13 @@ ALIGN_CENTER = ESC + b'a' + b'\x01'
|
|||||||
ALIGN_RIGHT = ESC + b'a' + b'\x02'
|
ALIGN_RIGHT = ESC + b'a' + b'\x02'
|
||||||
BOLD_ON = ESC + b'E' + b'\x01'
|
BOLD_ON = ESC + b'E' + b'\x01'
|
||||||
BOLD_OFF = ESC + b'E' + b'\x00'
|
BOLD_OFF = ESC + b'E' + b'\x00'
|
||||||
|
EMPH_ON = ESC + b'G' + b'\x01' # Double-strike / emphasized
|
||||||
|
EMPH_OFF = ESC + b'G' + b'\x00'
|
||||||
DOUBLE_HEIGHT = ESC + b'!' + b'\x10'
|
DOUBLE_HEIGHT = ESC + b'!' + b'\x10'
|
||||||
NORMAL_SIZE = ESC + b'!' + b'\x00'
|
NORMAL_SIZE = ESC + b'!' + b'\x00'
|
||||||
LARGE_SIZE = ESC + b'!' + b'\x30' # Double width + double height
|
LARGE_SIZE = ESC + b'!' + b'\x30' # Double width + double height
|
||||||
|
LINE_SPACING_140 = ESC + b'3' + b'\x28' # ~1.4x line spacing
|
||||||
|
LINE_SPACING_DEFAULT = ESC + b'2'
|
||||||
|
|
||||||
|
|
||||||
def generate_ticket(sale_data, business_info, width=80):
|
def generate_ticket(sale_data, business_info, width=80):
|
||||||
@@ -34,6 +38,8 @@ def generate_ticket(sale_data, business_info, width=80):
|
|||||||
chars = 32 if width == 58 else 48 # characters per line
|
chars = 32 if width == 58 else 48 # characters per line
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
buf += INIT
|
buf += INIT
|
||||||
|
buf += EMPH_ON
|
||||||
|
buf += LINE_SPACING_140
|
||||||
|
|
||||||
# Header: business name (centered, bold, large)
|
# Header: business name (centered, bold, large)
|
||||||
buf += ALIGN_CENTER
|
buf += ALIGN_CENTER
|
||||||
@@ -100,6 +106,8 @@ def generate_ticket(sale_data, business_info, width=80):
|
|||||||
buf += 'Gracias por su compra!\n'.encode('cp437', errors='replace')
|
buf += 'Gracias por su compra!\n'.encode('cp437', errors='replace')
|
||||||
buf += 'Nexus Autoparts POS\n'.encode('cp437', errors='replace')
|
buf += 'Nexus Autoparts POS\n'.encode('cp437', errors='replace')
|
||||||
buf += b'\n\n\n'
|
buf += b'\n\n\n'
|
||||||
|
buf += LINE_SPACING_DEFAULT
|
||||||
|
buf += EMPH_OFF
|
||||||
buf += FEED + b'\x05' # Feed 5 lines before cutting
|
buf += FEED + b'\x05' # Feed 5 lines before cutting
|
||||||
buf += CUT # Full cut
|
buf += CUT # Full cut
|
||||||
|
|
||||||
@@ -120,6 +128,8 @@ def generate_quotation_ticket(quote_data, business_info, width=80):
|
|||||||
chars = 32 if width == 58 else 48
|
chars = 32 if width == 58 else 48
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
buf += INIT
|
buf += INIT
|
||||||
|
buf += EMPH_ON
|
||||||
|
buf += LINE_SPACING_140
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
buf += ALIGN_CENTER
|
buf += ALIGN_CENTER
|
||||||
@@ -188,6 +198,8 @@ def generate_quotation_ticket(quote_data, business_info, width=80):
|
|||||||
buf += 'Precios sujetos a disponibilidad\n'.encode('cp437', errors='replace')
|
buf += 'Precios sujetos a disponibilidad\n'.encode('cp437', errors='replace')
|
||||||
buf += 'Nexus Autoparts POS\n'.encode('cp437', errors='replace')
|
buf += 'Nexus Autoparts POS\n'.encode('cp437', errors='replace')
|
||||||
buf += b'\n\n\n'
|
buf += b'\n\n\n'
|
||||||
|
buf += LINE_SPACING_DEFAULT
|
||||||
|
buf += EMPH_OFF
|
||||||
buf += FEED + b'\x05'
|
buf += FEED + b'\x05'
|
||||||
buf += CUT
|
buf += CUT
|
||||||
|
|
||||||
@@ -226,6 +238,8 @@ def generate_service_order_ticket(so_data, business_info, width=80):
|
|||||||
chars = 32 if width == 58 else 48
|
chars = 32 if width == 58 else 48
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
buf += INIT
|
buf += INIT
|
||||||
|
buf += EMPH_ON
|
||||||
|
buf += LINE_SPACING_140
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
buf += ALIGN_CENTER
|
buf += ALIGN_CENTER
|
||||||
@@ -340,6 +354,8 @@ def generate_service_order_ticket(so_data, business_info, width=80):
|
|||||||
buf += "No es comprobante fiscal\n".encode("cp437", errors="replace")
|
buf += "No es comprobante fiscal\n".encode("cp437", errors="replace")
|
||||||
buf += "Nexus Autoparts POS\n".encode("cp437", errors="replace")
|
buf += "Nexus Autoparts POS\n".encode("cp437", errors="replace")
|
||||||
buf += b"\n\n\n"
|
buf += b"\n\n\n"
|
||||||
|
buf += LINE_SPACING_DEFAULT
|
||||||
|
buf += EMPH_OFF
|
||||||
buf += FEED + b'\x05'
|
buf += FEED + b'\x05'
|
||||||
buf += CUT
|
buf += CUT
|
||||||
|
|
||||||
|
|||||||
@@ -1666,9 +1666,9 @@ const POS = (() => {
|
|||||||
const paperWidth = parseInt(rc('paper_width', '80'), 10) || 80;
|
const paperWidth = parseInt(rc('paper_width', '80'), 10) || 80;
|
||||||
const is58 = paperWidth === 58;
|
const is58 = paperWidth === 58;
|
||||||
const printWidth = is58 ? 48 : 72;
|
const printWidth = is58 ? 48 : 72;
|
||||||
const fontSize = is58 ? '7.5pt' : '9pt';
|
const fontSize = is58 ? '9.5pt' : '11pt';
|
||||||
const smallFont = is58 ? '6.5pt' : '8pt';
|
const smallFont = is58 ? '8pt' : '9.5pt';
|
||||||
const nameMax = is58 ? '18mm' : '34mm';
|
const nameMax = is58 ? '17mm' : '32mm';
|
||||||
|
|
||||||
let iframe = document.getElementById('ticketPrintFrame');
|
let iframe = document.getElementById('ticketPrintFrame');
|
||||||
if (!iframe) {
|
if (!iframe) {
|
||||||
@@ -1691,22 +1691,22 @@ const POS = (() => {
|
|||||||
<style>
|
<style>
|
||||||
@page { margin: 0; size: ${printWidth}mm auto; }
|
@page { margin: 0; size: ${printWidth}mm auto; }
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
html, body { margin: 0; padding: 0; height: auto; overflow: visible; background: #fff; color: #000; font-family: 'Courier New', Consolas, monospace; font-size: ${fontSize}; line-height: 1.25; }
|
html, body { margin: 0; padding: 0; height: auto; overflow: visible; background: #fff; color: #000; font-family: 'Courier New', Courier, 'Lucida Console', monospace; font-size: ${fontSize}; line-height: 1.55; -webkit-font-smoothing: none; -moz-osx-font-smoothing: unset; text-rendering: geometricPrecision; }
|
||||||
body { width: ${printWidth}mm; }
|
body { width: ${printWidth}mm; }
|
||||||
.ticket { width: ${printWidth}mm; padding: 2mm; border: none !important; box-shadow: none !important; }
|
.ticket { width: ${printWidth}mm; padding: 4mm 3mm 5mm; border: none !important; box-shadow: none !important; }
|
||||||
.item-line-wide { display: grid !important; grid-template-columns: 7mm 1fr auto auto !important; gap: 1.5mm; font-size: ${smallFont}; margin-bottom: 1mm; align-items: start; }
|
.item-line-wide { display: grid !important; grid-template-columns: 8mm 1fr auto auto !important; gap: 2mm; font-size: ${fontSize}; margin-bottom: 2mm; padding-left: 1mm; align-items: start; }
|
||||||
.item-line-wide .qty { text-align: center; }
|
.item-line-wide .qty { text-align: center; }
|
||||||
.item-line-wide .name { white-space: normal !important; word-break: break-word; overflow-wrap: anywhere; max-width: ${nameMax}; }
|
.item-line-wide .name { white-space: normal !important; word-break: break-word; overflow-wrap: anywhere; max-width: ${nameMax}; padding-left: 1mm; }
|
||||||
.item-line-wide .price, .item-line-wide .subtotal { text-align: right; white-space: nowrap; }
|
.item-line-wide .price, .item-line-wide .subtotal { text-align: right; white-space: nowrap; }
|
||||||
.ticket-row, .folio-line, .total-line { display: flex !important; justify-content: space-between; gap: 2mm; }
|
.ticket-row, .folio-line, .total-line { display: flex !important; justify-content: space-between; gap: 2mm; }
|
||||||
.total-line.grand { font-size: ${is58 ? '9pt' : '11pt'}; font-weight: bold; border-top: 0.5mm solid #000; padding-top: 1mm; margin-top: 1mm; }
|
.total-line.grand { font-size: ${is58 ? '11pt' : '13pt'}; font-weight: bold; border-top: 0.6mm solid #000; padding-top: 1.5mm; margin-top: 1.5mm; }
|
||||||
.store-name { font-size: ${is58 ? '10pt' : '12pt'}; font-weight: bold; text-align: center; margin-bottom: 2px; }
|
.store-name { font-size: ${is58 ? '12pt' : '14pt'}; font-weight: bold; text-align: center; margin-bottom: 3px; }
|
||||||
.store-tagline, .store-info { font-size: ${smallFont}; text-align: center; color: #333; margin-bottom: 4px; line-height: 1.3; }
|
.store-tagline, .store-info { font-size: ${smallFont}; text-align: center; color: #000; margin-bottom: 5px; line-height: 1.45; }
|
||||||
.divider { border: none; border-top: 1px dashed #999; margin: 3px 0; }
|
.divider { border: none; border-top: 1px dashed #999; margin: 5px 0; }
|
||||||
.divider-double { border: none; border-top: 2px solid #333; margin: 3px 0; }
|
.divider-double { border: none; border-top: 2px solid #333; margin: 5px 0; }
|
||||||
.footer-section { text-align: center; margin-top: 6px; font-size: ${smallFont}; color: #555; }
|
.footer-section { text-align: center; margin-top: 8px; font-size: ${smallFont}; color: #000; line-height: 1.45; }
|
||||||
.payment-section, .total-section { width: 100%; }
|
.payment-section, .total-section { width: 100%; }
|
||||||
img { max-width: 100%; height: auto; }
|
img { max-width: 100%; height: auto; image-rendering: pixelated; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>${src.innerHTML}</body>
|
<body>${src.innerHTML}</body>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// The fetch handler normalizes static asset URLs (strips ?v= query strings)
|
// The fetch handler normalizes static asset URLs (strips ?v= query strings)
|
||||||
// so templates can use cache-busting query params freely.
|
// so templates can use cache-busting query params freely.
|
||||||
|
|
||||||
const VERSION = 47;
|
const VERSION = 48;
|
||||||
const CACHE_NAME = 'nexus-pos-v' + VERSION;
|
const CACHE_NAME = 'nexus-pos-v' + VERSION;
|
||||||
|
|
||||||
const APP_SHELL = [
|
const APP_SHELL = [
|
||||||
|
|||||||
Reference in New Issue
Block a user