Coverage for pyilper/pilhp2225b.py: 93%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1 #!/usr/bin/python3
2# -*- coding: utf-8 -*-
3# pyILPER 1.2.4 for Linux
4#
5# An emulator for virtual HP-IL devices for the PIL-Box
6# derived from ILPER 1.4.5 for Windows
7# Copyright (c) 2008-2013 Jean-Francois Garnier
8# C++ version (c) 2013 Christoph Gießelink
9# Python Version (c) 2015 Joachim Siebold
10#
11# This program is free software; you can redistribute it and/or
12# modify it under the terms of the GNU General Public License
13# as published by the Free Software Foundation; either version 2
14# of the License, or (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24#
25# HP2225B virtual device classes ---------------------------------------------
26#
27# Changelog
28# 30.12.2018 jsi:
29# - initial version
30# 02.01.2018 jsi:
31# - added support for ^N and ^O for bold mode on and off
32# - allow ESC&kS in addition to ESC&k0S to switch to normal character pitch
33# 04.01.2018 jsi:
34# - support zero length graphics chunks
35# - switchable print color
36#
37import copy
38import queue
39import threading
40import re
41from math import floor
42from PySide6 import QtCore, QtWidgets, QtGui, QtPrintSupport
43from .pilcore import UPDATE_TIMER, PDF_ORIENTATION_PORTRAIT
44from .pilconfig import PILCONFIG
45from .pilcharconv import charconv, barrconv, CHARSET_HP2225
46from .pildevbase import cls_pildevbase
47from .pilwidgets import cls_tabgeneric, LogCheckboxWidget, T_INTEGER, O_DEFAULT, T_STRING
48from .pilcore import *
50#
51# constants --------------------------------------------------------------
52#
54PDF_LINES=70 # number of lines in pdf output
55PDF_MARGINS=50 # margins (top,bot,left,right) of pdf output
56PDF_MAX_COLS=3 # max number of columns in pdf output
57PDF_COLUMN_SPACING=80 # spacing between columns
58PDF_LINE_SPACING=0 # linespacing in (relative) pixel
60# GUI commands
61CMD_LF_PRESSED= 0
62CMD_LF_RELEASED= 1
63CMD_FF_PRESSED= 2
64CMD_CLEAR= 3
66# HPIL-Thread commands
67REMOTECMD_CLEAR=0 # clear device
68REMOTECMD_LOG=1 # log something
69REMOTECMD_STATUS=2 # printer status information:
70REMOTECMD_TEXT=3 # print characters according to current status
71REMOTECMD_GRAPHICS=4 # print graphics according to current status
72REMOTECMD_CR=5 # carriage return
73REMOTECMD_LF=6 # line feed
74REMOTECMD_HLF=7 # half line feed
75REMOTECMD_BS=8 # backspace
76REMOTECMD_FF=9 # form feed
77REMOTECMD_TERMGRAPHICS=10 # end of graphics
79ELEMENT_FF=0
80ELEMENT_TEXT=1
81ELEMENT_GRAPHICS=2
83# Printer constants
84BUFFER_LINE_H=2 # number of dots for a buffer line height
85PRINTER_WIDTH_HIGH= 1280 # width in dots for high resolution
86PRINTER_WIDTH_LOW= 640 # width in dots for low resolution
87HP2225B_FONT_PIXELSIZE=28 # pixel size of the font used
88HP2225B_MAX_LINES= 69 # maximum number of lines for a page
90# Font widths
91# Print mode norm : 80 chars/line, character width 16 dots
92# Print mode expand : 40 chars/line, character width 32 dots
93# Print mode compressed : 142 chars/line, character width 9 dots
94# Print mode expand/compressed : 71 chars/line, character width 18 dots
95FONT_WIDTH= [16, 32, 9, 18 ]
96#
97# this is a hack because Qt on Macos does not display expanded fonts correctly
98#
99if isMACOS():
100 FONT_STRETCH= [100, 110, 56, 110]
101else:
102 FONT_STRETCH= [100, 200, 56, 113]
104BUFFER_SIZE_NAMES=["5 Pages", "10 Pages","20 Pages","50 Pages"]
105BUFFER_SIZE_VALUES=[2500, 5000, 10000, 25000]
106#
107# Print colors
108#
109HP2225_COLOR_BLACK=0
110HP2225_COLOR_RED=1
111HP2225_COLR_BLUE=2
112HP2225_COLOR_GREEN=3
113COLOR_NAMES= [ "black", "red", "blue", "green" ]
114HP2225_COLORS=[QtCore.Qt.black, QtCore.Qt.red, QtCore.Qt.blue, QtCore.Qt.green]
116#
117# HP2225B tab widget ---------------------------------------------------------
118#
119class cls_tabhp2225b(cls_tabgeneric):
122 def __init__(self,parent,name):
123 super().__init__(parent,name)
124 self.name=name
125#
126# this parameter is global
127#
128 self.papersize=PILCONFIG.get("pyilper","papersize")
129#
130# init local parameter
131#
132 self.screenwidth=PILCONFIG.get(self.name,"hp2225b_screenwidth",-1)
133 self.scrollupbuffersize=PILCONFIG.get(self.name,"hp2225b_scrollupbuffersize",1)
134 self.printcolor=PILCONFIG.get(self.name,"hp2225b_printcolor",HP2225_COLOR_BLACK)
135#
136# create Printer GUI object
137#
138 self.guiobject=cls_hp2225bWidget(self,self.name,self.papersize)
139#
140# add gui object
141#
142 self.add_guiobject(self.guiobject)
143#
144# add cascading config menu
145#
146 self.add_configwidget()
147#
148# add local config option
149#
150 self.cBut.add_option("Screen width","hp2225b_screenwidth",T_INTEGER,[O_DEFAULT,640,960,1280])
151 self.cBut.add_option("Buffer size","hp2225b_scrollupbuffersize",T_STRING,BUFFER_SIZE_NAMES)
152 self.cBut.add_option("Print color","hp2225b_printcolor",T_STRING,COLOR_NAMES)
153#
154# add logging control widget
155#
156 self.add_logging()
157#
158# create IL-Interface object, notify printer processor object
159#
160 self.pildevice= cls_pilhp2225b(self.guiobject)
161 self.guiobject.set_pildevice(self.pildevice)
162 self.cBut.config_changed_signal.connect(self.do_tabconfig_changed)
163#
164# handle changes of tab config options
165#
166 def do_tabconfig_changed(self):
167 self.loglevel= PILCONFIG.get(self.name,"loglevel",0)
168 self.guiobject.reconfigure()
169 super().do_tabconfig_changed()
170#
171# reconfigure: reconfigure the gui object
172#
173 def reconfigure(self):
174 self.guiobject.reconfigure()
175#
176# enable pildevice and gui object
177#
178 def enable(self):
179 super().enable()
180 self.parent.commthread.register(self.pildevice,self.name)
181 self.pildevice.setactive(self.active)
182 self.pildevice.enable()
183 self.guiobject.enable()
184#
185# disable pildevice and gui object
186#
187 def disable(self):
188 self.pildevice.disable()
189 self.guiobject.disable()
190 super().disable()
191#
192# active/inactive: enable/disable GUI controls
193#
194 def toggle_active(self):
195 super().toggle_active()
196 self.guiobject.toggle_active()
197#
198# becomes visible, refresh content, activate update
199#
200 def becomes_visible(self):
201 self.guiobject.becomes_visible()
202 return
203#
204# becomes invisible, deactivate update
205#
206 def becomes_invisible(self):
207 self.guiobject.becomes_invisible()
208 return
209#
210#
211# hp2225b widget classes - GUI component of the HP2225B HP-IL printer
212#
213class cls_hp2225bWidget(QtWidgets.QWidget):
215 def __init__(self,parent,name,papersize):
216 super().__init__()
217 self.name= name
218 self.parent= parent
219 self.papersize= papersize
220 self.pildevice= None
221#
222# printer status that controls the appearance of the printer output
223#
224 self.pdf_rows=480 # text length in rows
225 self.char_attr=0 # character pitch
226 self.char_bold=False # bold mode
227 self.char_underline=False # underline mode
228 self.hiRes=False # high resolution of graphics output
229 self.lpi6=True # lines/inch
230 self.wrapEOL=False # EOL wrap
231#
232# line coordinates
233#
234 self.pos_y=0 # in 4 dots (1280dpi) steps
235 self.pos_x=0 # in 1280dpi steps
237 self.graphics_counter=0 # number of graphics lines
238#
239# create user interface of printer widget
240#
241 self.hbox=QtWidgets.QHBoxLayout()
242 self.hbox.addStretch(1)
243#
244# scrolled printer view
245#
246 self.printview=cls_ScrolledHp2225bView(self,self.name,self.papersize)
247 self.hbox.addWidget(self.printview)
248 self.vbox=QtWidgets.QVBoxLayout()
249#
250# Clear Button
251#
252 self.clearButton= QtWidgets.QPushButton("Clear")
253 self.clearButton.setEnabled(False)
254 self.clearButton.setAutoDefault(False)
255 self.vbox.addWidget(self.clearButton)
256 self.clearButton.clicked.connect(self.do_clear)
257#
258# LF Button
259#
260 self.LFButton= QtWidgets.QPushButton("LF")
261 self.LFButton.setEnabled(False)
262 self.LFButton.setAutoDefault(False)
263 self.vbox.addWidget(self.LFButton)
264 self.LFButton.pressed.connect(self.do_LF_pressed)
265 self.LFButton.released.connect(self.do_LF_released)
266#
267# FF Button
268#
269 self.FFButton= QtWidgets.QPushButton("FF")
270 self.FFButton.setEnabled(False)
271 self.FFButton.setAutoDefault(False)
272 self.vbox.addWidget(self.FFButton)
273 self.FFButton.pressed.connect(self.do_FF_pressed)
274#
275# PDF Button
276#
277 self.pdfButton= QtWidgets.QPushButton("PDF")
278 self.pdfButton.setEnabled(False)
279 self.pdfButton.setAutoDefault(False)
280 self.vbox.addWidget(self.pdfButton)
281 self.pdfButton.clicked.connect(self.do_pdf)
283 self.vbox.addStretch(1)
284 self.hbox.addLayout(self.vbox)
285 self.hbox.addStretch(1)
286 self.setLayout(self.hbox)
287#
288# initialize GUI command queue and lock
289#
290 self.gui_queue= queue.Queue()
291 self.gui_queue_lock= threading.Lock()
292#
293# initialize refresh timer
294#
295 self.UpdateTimer=QtCore.QTimer()
296 self.UpdateTimer.setSingleShot(True)
297 self.UpdateTimer.timeout.connect(self.process_queue)
298#
299# initialize timer for the repeated pressed LF action
300#
301 self.repeatedLFpressedTimer=QtCore.QTimer()
302 self.repeatedLFpressedTimer.timeout.connect(self.repeated_LFpressed)
303 self.repeatedLFpressedTimer.setInterval(1500)
304#
305# set HP-IL device object
306#
307 def set_pildevice(self,pildevice):
308 self.pildevice=pildevice
309#
310# enable: start timer, send mode to virtual device, update check boxes
311#
312 def enable(self):
313 self.UpdateTimer.start(UPDATE_TIMER)
314 self.toggle_active()
315 return
316#
317# disable, clear the GUI queue, stop the timer
318#
319 def disable(self):
320 self.gui_queue_lock.acquire()
321 while True:
322 try:
323 self.gui_queue.get_nowait()
324 self.gui_queue.task_done()
325 except queue.Empty:
326 break
327 self.gui_queue_lock.release()
328 self.UpdateTimer.stop()
329 return
330#
331# becomes visible
332#
333 def becomes_visible(self):
334 self.printview.becomes_visible()
335#
336# becomes invisible, do nothing
337#
338 def becomes_invisible(self):
339 pass
340#
341# active/inactive: enable/disable GUI controls
342#
343 def toggle_active(self):
344 if self.parent.active:
345 self.clearButton.setEnabled(True)
346 self.LFButton.setEnabled(True)
347 self.FFButton.setEnabled(True)
348 self.pdfButton.setEnabled(True)
349 else:
350 self.clearButton.setEnabled(False)
351 self.LFButton.setEnabled(False)
352 self.FFButton.setEnabled(False)
353 self.pdfButton.setEnabled(False)
354#
355# reconfigure
356#
357 def reconfigure(self):
358 self.printview.reconfigure()
359 return
360#
361# action scripts
362#
363 def do_clear(self):
364 self.printview.reset()
365 self.pildevice.put_cmd(CMD_CLEAR)
366 return
368 def do_FF_pressed(self):
369 self.put_cmd([REMOTECMD_FF])
370 return
372 def do_LF_pressed(self):
373 self.repeatedLFpressedTimer.start()
374 self.put_cmd([REMOTECMD_LF])
375 return
377 def do_LF_released(self):
378 self.repeatedLFpressedTimer.stop()
379 return
381 def do_pdf(self):
382 filename=cls_PdfOptions.getPdfOptions()
383 if filename== "":
384 return
385 self.printview.pdf(filename,self.pdf_rows)
386 return
387#
388# put command into the GUI-command queue, this is called by the thread component
389#
390 def put_cmd(self,item):
391 self.gui_queue_lock.acquire()
392 self.gui_queue.put(item)
393 self.gui_queue_lock.release()
394#
395# repeated LF pressed action
396#
397 def repeated_LFpressed(self):
398 self.put_cmd([REMOTECMD_LF])
399#
400# process commands in the GUI command queue, this is called by a timer event
401#
402 def process_queue(self):
403 items=[]
404 self.gui_queue_lock.acquire()
405 while True:
406 try:
407 i=self.gui_queue.get_nowait()
408 items.append(i)
409 self.gui_queue.task_done()
410 except queue.Empty:
411 break
412 self.gui_queue_lock.release()
413 if len(items):
414 for c in items:
415 self.process(c)
416 self.UpdateTimer.start(UPDATE_TIMER)
417 return
418#
419# GUI command processing, commands issued by the HP-IL thread
420#
421# Printer coordinate system
422#
423# The internal scene coordinate systems of this program is dots at
424# a resolution of 192 dpi. The HP2225B operates
425# either at 96x96dpi or 96x192dpi resolution. Thus we have always even
426# values for the y coordinate.
427#
428# Constants for movement and positioning
429# Dots per line: 1280
430# Print mode norm : 80 chars/line, character width 16 dots
431# Print mode expand : 40 chars/line, character width 32 dots
432# Print mode compressed : 142 chars/line, character width 9 dots
433# Print mode expand/compressed : 71 chars/line, character width 18 dots
434# Character height always 16 dots
435# Line spacing 8 dots at 8 lines/inch, 16 dots at 6 lines/inch
436# Line feed is 24 dots at 8 lines/inch, 32 dots at 6 lines/inc
437# Half line feed is 12 dots at 8 lines/inch and 16 dots at 6 lines/inch
438# Graphics line: 16 dots height, 1280 dots width
439# A graphics dot is 2x2 dots at low res an 2x1 dot at high res
440# Graphics data is 80 bytes at low res and 160 bytes at high res
441#
442# Here we use the following coordinate system:
443# x= 1 dot (resolution 1280)
444# y= 4 dots (resolution 1280)
445# LF= 6 / 8 , half LF= 3 / 4
446#
447 def process(self,item):
448 cmd= item[0]
449#
450# clear graphhics views
451#
452 if cmd== REMOTECMD_CLEAR:
453 self.printview.reset()
454 self.pos_x=0
455# print("GUI: reset")
456 self.graphics_counter=0
457#
458# carriage return go to beginning of the current line
459#
460 elif cmd== REMOTECMD_CR:
461# print("GUI: cr")
462 self.pos_x=0
463 self.graphics_counter=0
464#
465# line feed advance according to line spacing
466#
467 elif cmd== REMOTECMD_LF:
468# print("GUI: lf")
469 if self.lpi6:
470 self.printview.advance(8)
471 else:
472 self.printview.advance(6)
473 self.graphics_counter=0
474#
475# Form feed, we need that for the PDF output later
476#
477 elif cmd== REMOTECMD_FF:
478# print("GUI: ff")
479 if self.lpi6:
480 self.printview.advance(8)
481 else:
482 self.printview.advance(6)
483 self.graphics_counter=0
484 self.printview.add([ELEMENT_FF])
485#
486# advance one half line feed
487#
488 elif cmd== REMOTECMD_HLF:
489# print("GUI: half lf")
490 self.graphics_counter=0
491 if self.lpi6:
492 self.printview.advance(4)
493 else:
494 self.printview.advance(3)
495#
496# Backspace, go back one character, use current font width
497#
498 elif cmd== REMOTECMD_BS:
499# print("GUI: bs")
500 self.graphics_counter=0
501 l= FONT_WIDTH[self.char_attr]
502 self.pos_x-=l
503 if self.pos_x < 0:
504 self.pos_x=0
505#
506# update configuration triggered by an escape sequence
507#
508 elif cmd== REMOTECMD_STATUS:
509# print("GUI status", item[1])
510 self.pdf_rows=item[1][0]
511 self.char_attr=item[1][1]
512 self.char_bold=item[1][2]
513 self.char_underline=item[1][3]
514 self.hiRes=item[1][4]
515 self.lpi6=item[1][5]
516 self.wrapEOL=item[1][6]
517#
518# text element, we do not support EOL wrap at the moment and ignore any text
519# that exceeds a sinlge line
520#
521 elif cmd== REMOTECMD_TEXT:
522 self.graphics_counter=0
523# print("GUI text", self.pos_x, item[1])
524 txt_list= item[1]
526 while txt_list is not None:
527#
528# new length of row
529#
530 newlen=self.pos_x+len(txt_list)*FONT_WIDTH[self.char_attr]
531#
532# exceeds row
533#
534 if newlen> PRINTER_WIDTH_HIGH:
535 fit_in_row=len(txt_list)- round((newlen-PRINTER_WIDTH_HIGH)/
536 FONT_WIDTH[self.char_attr])
537#
538# txt contains the characters that fit in the current row
539#
540 txt=bytearray(txt_list[:fit_in_row])
541#
542# if eolWrap is off we throw away the remaining content, otherwise
543# keep it
544#
545 if self.wrapEOL:
546 txt_list= txt_list[fit_in_row:]
547 else:
548 txt_list= None
549 else:
550#
551# text fits into current row
552#
553 fit_in_row= len(txt_list)
554 txt=bytearray(txt_list)
555 txt_list= None
556#
557# add it to the current line in the view
558#
559 self.printview.add([ELEMENT_TEXT,self.pos_x,self.char_attr,
560 self.char_bold,self.char_underline,barrconv(txt,CHARSET_HP2225)])
561 self.pos_x+= fit_in_row* FONT_WIDTH[self.char_attr]
562#
563# if we have remaining text in txt_list then do a cr/lf
564#
565 if txt_list is not None:
566 if self.lpi6:
567 self.printview.advance(8)
568 else:
569 self.printview.advance(6)
570 self.graphics_counter=0
571 self.pos_x=0
572#
573# graphics, we can have only 2 graphics lines at a single printer rows
574#
575 elif cmd== REMOTECMD_GRAPHICS:
576 self.pos_x=0
577# print("GUI: graphics",self.graphics_counter, item[1])
578 self.printview.add([ELEMENT_GRAPHICS,self.graphics_counter,self.hiRes,item[1]])
579 self.graphics_counter+=1
580 if self.graphics_counter == 2:
581 self.graphics_counter=0
582 self.printview.advance(1)
583#
584# terminate graphics, advance 4 dots
585#
586 elif cmd== REMOTECMD_TERMGRAPHICS:
587 self.graphics_counter=0
588 self.printview.advance(1)
589#
590# log line
591#
592 elif cmd== REMOTECMD_LOG:
593 self.parent.cbLogging.logWrite(item[1])
594 self.parent.cbLogging.logFlush()
595#
596# custom class for scrolled hp2225b output widget ----------------------------
597#
598class cls_ScrolledHp2225bView(QtWidgets.QWidget):
600 def __init__(self,parent,name,papersize):
601 super().__init__(parent)
602 self.parent=parent
603 self.name=name
604#
605# create window and scrollbars
606#
607 self.hbox= QtWidgets.QHBoxLayout()
608 self.scrollbar= QtWidgets.QScrollBar()
609 self.hp2225bwidget= cls_hp2225bView(self,self.name,papersize)
610 self.hp2225bwidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
611 self.hp2225bwidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
612 self.hbox.addWidget(self.hp2225bwidget)
613 self.hbox.addWidget(self.scrollbar)
614 self.setLayout(self.hbox)
615#
616# Initialize scrollbar
617#
618 self.scrollbar.valueChanged.connect(self.do_scrollbar)
619 self.scrollbar.setEnabled(True)
620 self.reset()
621#
622# scrollbar value changed action
623#
624 def do_scrollbar(self):
625 self.hp2225bwidget.do_scroll(self.scrollbar.value())
626#
627# reset output window
628#
629 def reset(self):
630 self.hp2225bwidget.reset()
631 self.scrollbar.setMinimum(0)
632 self.scrollbar.setMaximum(0)
633 self.scrollbar.setSingleStep(1)
634#
635# generate pdf output
636#
637 def pdf(self,filename,pdf_rows):
638 self.hp2225bwidget.pdf(filename,pdf_rows)
639#
640# becomes visible/invisible: nothing to do
641#
642 def becomes_visible(self):
643 return
645 def becomes_invisible(self):
646 return
647#
648# reconfigure
649#
650 def reconfigure(self):
651 self.hp2225bwidget.reconfigure()
652 return
653#
654# add elements
655#
656 def add(self,element):
657 self.hp2225bwidget.add(element)
658#
659# advance
660#
661 def advance(self,n):
662 self.hp2225bwidget.advance(n)
664#
665# custom class for hp2225b output -----------------------------------------
666#
667class cls_hp2225bView(QtWidgets.QGraphicsView):
669 def __init__(self,parent,name,papersize):
670 super().__init__()
671 self.parent=parent
672 self.name= name
673 self.screenwidth= -1
674 self.printcolor= QtCore.Qt.black
675 self.w=-1
676 self.h=-1
677 self.rows= 0
679 self.linebuffersize= -1
680 self.papersize= papersize
681#
682# set the font and font size
684 self.font=QtGui.QFont(FONT)
686 self.font.setPixelSize(HP2225B_FONT_PIXELSIZE)
687 metrics=QtGui.QFontMetrics(self.font)
688#
689# Initialize line bitmap buffer
690#
691 self.lb= [ ]
692 self.lb_current= 0
693 self.lb_anz=0
694 self.lb_position=0
696 self.printscene=None
697 self.reconfigure()
698 return
701 def reconfigure(self):
702#
703# re/configure the printview widget
704#
705 tmp=BUFFER_SIZE_VALUES[PILCONFIG.get(self.name,"hp2225b_scrollupbuffersize")]
706 if tmp != self.linebuffersize:
707 self.linebuffersize=tmp
708 if self.printscene is not None:
709 self.printscene.reset()
710 self.lb= [None]* self.linebuffersize
711 self.lb_current= 0
712 self.lb_anz=0
713 self.lb_position=0
715 tmp=PILCONFIG.get_dual(self.name,"hp2225b_screenwidth")
716 if tmp != self.screenwidth:
717 self.screenwidth=tmp
718 self.w=self.screenwidth
719#
720# set fixed width
721#
722 self.setFixedWidth(self.w)
723#
724# reconfigure scene if it exists
725#
726 if self.printscene is not None:
727 self.printscene.reconfigure(self.screenwidth,self.printcolor)
728 self.do_resize()
729#
730# print color
731#
732 tmp=HP2225_COLORS[PILCONFIG.get(self.name,"hp2225b_printcolor")]
733 if tmp != self.printcolor:
734 self.printcolor=tmp
735 if self.printscene is not None:
736 self.printscene.reconfigure(self.screenwidth,self.printcolor)
737 self.do_resize()
738#
739# initialize scene if it does not exist
740#
741 if self.printscene is None:
742 self.printscene= cls_hp2225b_scene(self,self.font, self.screenwidth,self.printcolor)
743 self.setScene(self.printscene)
744 self.reset()
745 return
746#
747# reset output window
748#
749 def reset(self):
750 for i in range(0,self.linebuffersize):
751 if self.lb[i] is not None:
752 self.lb[i]= None
753 self.lb_current= 0
754 self.lb_anz=0
755 self.lb_position=0
756 self.printscene.reset()
757#
758# resize event, adjust the scene size, reposition everything and redraw
759#
760 def resizeEvent(self,event):
761 self.do_resize()
763 def do_resize(self):
764 h=self.height()
765#
766# compute the number of rows that will fit into the current window size
767#
768 self.rows= floor(h /BUFFER_LINE_H /2 * PRINTER_WIDTH_HIGH /self.screenwidth)
770# print("resize view dimensions ",self.screenwidth,h);
771# print("resize view rows: ",self.rows)
772# print("resize view: fit in view", PRINTER_WIDTH_HIGH, self.rows*2*BUFFER_LINE_H))
773#
774# adjust the size of the print scene
775#
776 self.printscene.set_scenesize(self.rows)
777#
778# now transform the scene into the current view, force transformation
779# to identity if we use a screen width of 1280
780#
781 if self.screenwidth != PRINTER_WIDTH_HIGH:
782 self.fitInView(0,0,PRINTER_WIDTH_HIGH,self.rows*2*BUFFER_LINE_H)
783 else:
784 self.resetTransform()
785#
786# now adjust the scroll bar parameters
787#
788 scroll_max=self.lb_current- self.rows
789 if scroll_max < 0:
790 scroll_max=0
791# print("scrollbar adjustment: ", self.lb_current,scroll_max)
792# print("---")
793 self.parent.scrollbar.setMaximum(scroll_max)
794 self.parent.scrollbar.setPageStep(self.rows)
795 self.printscene.update_scene()
796 return
797#
798# PDF output. Text length configuration is not supported at the moment
799#
800 def pdf(self,filename,pdf_rows):
802 self.printer=QtPrintSupport.QPrinter (QtPrintSupport.QPrinter.HighResolution)
803 self.printer.setPageOrientation(QtGui.QPageLayout.Portrait)
804 self.printer.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat)
805 self.pdfscene=QtWidgets.QGraphicsScene()
806#
807# page set up, we use 192 dpi dots as scene units and set the left
808# and right margins so that we get a print width of 6.7 inches
809# The height of 60 lines is 10 inches
810# DINA4: 0,79 inches= 151 dots
811# Letter: 0.9 inches = 173 dots
812#
813#
814# A4 format is 8,27 inches x 11,7 inches
815#
816 if self.papersize== PDF_FORMAT_A4:
817 self.printer.setPageSize(QtGui.QPageSize.A4)
818 lmargin= 151
819 tmargin= 163
820 scene_w= 1280 + lmargin*2
821 scene_h= 1920 + tmargin*2
822 self.pdfscene.setSceneRect(0,0,scene_w,scene_h)
823 else:
824#
825# Letter format is 8.5 inches x 11 inches
826#
827 self.printer.setPageSize(QtGui.QPageSize.Letter)
828 lmargin= 173
829 tmargin= 96
830 scene_w= 1280 + lmargin*2
831 scene_h= 1920 + tmargin*2
832 self.pdfscene.setSceneRect(0,0,scene_w,scene_h)
834 self.painter= QtGui.QPainter()
836 self.printer.setOutputFileName(filename)
837 self.painter.begin(self.printer)
839 pdfitems=[]
840 anzitems=0
841 delta= BUFFER_LINE_H*2
842 horizontal_margin=floor((480-pdf_rows)/2)*delta
844 rowcount=0
845 y=tmargin + horizontal_margin
846#
847# print all items to pdf
848#
849 for i in range(0,self.lb_anz):
850 s= self.lb[i]
851#
852# we have a form feed element, issue new page
853#
854 if s is not None:
855 if s[0][0]== ELEMENT_FF:
856# print("FF ",rowcount, pdf_rows)
857 self.pdfscene.render(self.painter)
858 self.printer.newPage()
859 for l in reversed(range(anzitems)):
860 self.pdfscene.removeItem(pdfitems[l])
861 del pdfitems[-1]
862 anzitems=0
863 y=tmargin + horizontal_margin
864 rowcount=0
865# print("reset y to ",y,tmargin,horizontal_margin)
866 item=cls_hp2225b_line(s,self.font,self.printcolor)
867 pdfitems.append(item)
868 self.pdfscene.addItem(item)
869 item.setPos(lmargin,y)
870# print("pdf item added ",rowcount,y,s)
871 anzitems+=1
872# else:
873# print("none element")
874 rowcount+=1
875 y+= delta
876#
877# does the next line fit into the page, if not issue page break
878# The character height is always 16px.
879#
880 if rowcount > pdf_rows:
881# print("page break ",rowcount, pdf_rows)
882 self.pdfscene.render(self.painter)
883 self.printer.newPage()
884 for l in reversed(range(anzitems)):
885 self.pdfscene.removeItem(pdfitems[l])
886 del pdfitems[-1]
887 anzitems=0
888 rowcount=0
889 y=tmargin + horizontal_margin
890# print("reset y to ",y,tmargin,horizontal_margin)
891#
892# output remaining data and terminate printing
893#
894 if anzitems > 0:
895 self.pdfscene.render(self.painter)
896 for l in reversed(range(anzitems)):
897 self.pdfscene.removeItem(pdfitems[l])
898 del pdfitems[-1]
899 self.painter.end()
900#
901#
902# Mouse wheel event
903#
904 def wheelEvent(self,event):
905 numDegrees= event.angleDelta()/8
906 delta=0
907 step= round (8 * self.w / PRINTER_WIDTH_HIGH)
908 if numDegrees.y() is not None:
909 if numDegrees.y() < 0:
910 delta=step
911 if numDegrees.y() > 0:
912 delta=-step
913 event.accept()
914 if self.lb_current < self.rows:
915 return
916 if self.lb_position+delta < 0:
917 delta=-self.lb_position
918 if self.lb_position+delta+self.rows > self.lb_current:
919 delta=self.lb_current-(self.lb_position + self.rows )
920 self.lb_position+=delta
921 self.parent.scrollbar.setValue(self.lb_position)
922 self.printscene.update_scene()
923 return
924#
925# external methods
926#
927# add element
928#
929 def add(self,elem):
930# print("View add element: ",self.lb_current,elem)
931 if self.lb[self.lb_current] is None:
932 self.lb[self.lb_current]= [ ]
933 self.lb[self.lb_current].append(elem)
934 self.printscene.update_scene()
935 return
936#
937# advance
938#
939 def advance(self,n):
940 if self.lb_anz+n < self.linebuffersize:
941 self.lb_anz+=n
942 self.lb_current+=n
943 else:
944 self.lb=self.lb[n:] + self.lb[:n]
945 for i in range (0,n):
946 self.lb[i-n]=None
948 self.lb_position= self.lb_current- (self.rows)
949 if self.lb_position < 0:
950 self.lb_position=0
951# print("View advance: ",n,self.lb_current, self.lb_position)
952 self.parent.scrollbar.setMaximum(self.lb_position)
953 self.parent.scrollbar.setValue(self.lb_position)
954 self.printscene.update_scene()
955 return
956#
957# scroll bar action
958#
959 def do_scroll(self,value):
960 self.lb_position=value
961 self.printscene.update_scene()
962#
963# pdf output
964#
965 def do_pdf(self,filename):
966 return
967#
968# custom class for HP2225B graphics scene
969#
970class cls_hp2225b_scene(QtWidgets.QGraphicsScene):
972 def __init__(self,parent,font,screenwidth,printcolor):
973 super().__init__()
974 self.rows= 0
975 self.w=0
976 self.h=0
977 self.parent=parent
978 self.si= None
979 self.font=font
980 self.reconfigure(screenwidth,printcolor)
981 return
982#
983# re/configure graphics scene
984#
985 def reconfigure(self,screenwidth,printcolor):
986 self.screenwidth=screenwidth
987 self.w= PRINTER_WIDTH_HIGH
988 self.h= BUFFER_LINE_H *2
989 self.printcolor=printcolor
990 return
991#
992# set or change the size of the scene
993#
994 def set_scenesize(self,rows):
995 self.reset()
996 self.rows= rows
997 self.si= [None] * rows
998 self.setSceneRect(0,0,self.w,(self.h*(self.rows)))
999# print("Scene size ",self.w,self.h*self.rows)
1000#
1001# clear window and reset
1002#
1003 def reset(self):
1004 for i in range(0,self.rows):
1005 if self.si[i] is not None:
1006 self.removeItem(self.si[i])
1007 self.si[i]=None
1008#
1009# update graphics scene
1010#
1011 def update_scene(self):
1012 for i in range(0,self.rows):
1013 if self.si[i] is not None:
1014 self.removeItem(self.si[i])
1015 self.si[i]=None
1016 start= self.parent.lb_position
1017 end= start+self.rows
1018 if end >= self.parent.lb_anz:
1019 end=self.parent.lb_anz
1020 y=0
1021 j=0
1022 for i in range(start,end):
1023 self.si[j]=self.parent.lb[i]
1024 if self.parent.lb[i] is not None:
1025 self.si[j]=cls_hp2225b_line(self.parent.lb[i], self.font,self.printcolor)
1026 self.addItem(self.si[j])
1027 self.si[j].setPos(0,y)
1028 y+=self.h
1029 j+=1
1030# print("Scene updated: ",start,end)
1032#
1033# custum class HP2225 print line
1034#
1035class cls_hp2225b_line(QtWidgets.QGraphicsItem):
1037 def __init__(self,itemlist, font, color):
1038 super().__init__()
1039 self.itemlist= itemlist
1040 self.font=font
1041 self.color=color
1042 metrics=QtGui.QFontMetrics(self.font)
1043 self.font_height=metrics.height()
1044 self.rect= QtCore.QRectF(0,0,PRINTER_WIDTH_HIGH,self.font_height)
1045# self.flags=QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop | QtCore.Qt.TextDontClip
1046# self.flags=QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop
1049 def setPos(self,x,y):
1050 super().setPos(x,y)
1052 def boundingRect(self):
1053 return self.rect
1054#
1055# paint elements
1056#
1057 def paint(self,painter,option,widget):
1059 posx=0
1060 for item in self.itemlist:
1061#
1062# Ignore element form feed
1063#
1064 if item[0]== ELEMENT_FF:
1065 continue
1066#
1067# Paint text, align each character so that we get exactly the
1068# number of characters per row as the original printer
1069#
1070 elif item[0]== ELEMENT_TEXT:
1071 painter.setPen(self.color)
1072 posx=item[1]
1073 self.font.setBold(item[3])
1074 self.font.setUnderline(item[4])
1075 self.font.setStretch(FONT_STRETCH[item[2]])
1076 posy=self.font_height-12
1078 painter.setFont(self.font)
1079 for c in item[5]:
1080 painter.drawText(posx,posy,c)
1081# bounding_rect= QtCore.QRect(posx,0,posx+ (FONT_WIDTH[item[2]]),self.font_height)
1082# painter.drawText(bounding_rect, self.flags, c)
1083 posx+= FONT_WIDTH[item[2]]
1084 continue
1085#
1086# Paint raster graphics elements. They always begin at column 0
1087# We have at most two graphics row elements. The y resolution
1088# is always 96dpi and the x resolution may bei either 96 or 192
1089# dpi according to the hiRes mode
1090#
1091 elif item[0]==ELEMENT_GRAPHICS:
1092 painter.setPen(self.color)
1093 posy=item[1]*2
1094 hiRes=item[2]
1095 posx=0
1096 for i in item[3]:
1097 mask=0x80
1098 if posx>=PRINTER_WIDTH_HIGH:
1099 break
1100 for j in range (0,8):
1101 if hiRes:
1102 if i & mask:
1103 painter.fillRect(posx,posy,1,2,self.color)
1104 posx+=1
1105 else:
1106 if i & mask:
1107 painter.fillRect(posx,posy,2,2,self.color)
1108 posx+=2
1109 mask= mask >> 1
1110 return
1112#
1113# custom class open pdf output file and set options
1114#
1115class cls_PdfOptions(QtWidgets.QDialog):
1117 def __init__(self):
1118 super().__init__()
1119 self.filename="hp2225b.pdf"
1120 self.setWindowTitle('HP2225B PDF output')
1121 self.vlayout = QtWidgets.QVBoxLayout()
1122 self.setLayout(self.vlayout)
1123 self.glayout = QtWidgets.QGridLayout()
1124 self.vlayout.addLayout(self.glayout)
1126 self.glayout.addWidget(QtWidgets.QLabel("PDF Output Options"),0,0,1,3)
1127 self.glayout.addWidget(QtWidgets.QLabel("Output file:"),1,0)
1128 self.filename="hp2225b.pdf"
1129 self.lfilename=QtWidgets.QLabel(self.filename)
1130 self.glayout.addWidget(self.lfilename,1,1)
1131 self.butchange=QtWidgets.QPushButton("Change")
1132 self.butchange.setFixedWidth(60)
1133 self.glayout.addWidget(self.butchange,1,2)
1135 self.buttonBox = QtWidgets.QDialogButtonBox()
1136 self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
1137 self.buttonBox.setCenterButtons(True)
1138 self.buttonBox.accepted.connect(self.do_ok)
1139 self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
1140 self.buttonBox.rejected.connect(self.do_cancel)
1141 self.hlayout = QtWidgets.QHBoxLayout()
1142 self.hlayout.addWidget(self.buttonBox)
1143 self.vlayout.addLayout(self.hlayout)
1144 self.butchange.clicked.connect(self.change_pdffile)
1146 def get_pdfFilename(self):
1147 dialog=QtWidgets.QFileDialog()
1148 dialog.setWindowTitle("Enter PDF file name")
1149 dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
1150 dialog.setFileMode(QtWidgets.QFileDialog.AnyFile)
1151 dialog.setDefaultSuffix("pdf")
1152 dialog.setNameFilters( ["PDF (*.pdf )", "All Files (*)"] )
1153 dialog.setOptions(QtWidgets.QFileDialog.DontUseNativeDialog)
1154 if dialog.exec():
1155 return dialog.selectedFiles()
1157 def change_pdffile(self):
1158 flist= self.get_pdfFilename()
1159 if flist is None:
1160 return
1161 self.filename= flist [0]
1162 self.lfilename.setText(self.filename)
1163 self.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
1166 def do_ok(self):
1167 super().accept()
1169 def do_cancel(self):
1170 super().reject()
1172 @staticmethod
1173 def getPdfOptions():
1174 dialog= cls_PdfOptions()
1175 result= dialog.exec()
1176 if result== QtWidgets.QDialog.Accepted:
1177 return dialog.lfilename.text()
1178 else:
1179 return ""
1180#
1181# HP2225B emulator (thread component) --------------------------------------
1182#
1184class cls_hp2225b(QtCore.QObject):
1186 BUF_EMPTY=0
1187 BUF_TEXT=1
1188 BUF_GRAPHICS=2
1190 def __init__(self,parent,guiobject):
1191 super().__init__()
1192 self.pildevice=parent
1193 self.guiobject= guiobject
1195 self.esc= False # escape mode
1196 self.esc_seq="" # escape sequence
1197 self.esc_prefix="" # prefix of combined esc sequences
1198 self.num_graphics=-1 # number of graphics bytes
1199 self.ignore_crlf=False # flag to ignore cr/lf between graphics chunks
1200 self.apgot=False # flag avoid printing graphics over text
1201#
1202# printer status that controls the appearance of the printer output and
1203# is therefore handled in the GUI component
1204#
1205 self.text_legnth= 60 # text length given in lines
1206 self.char_attr=0 # character pitch
1207 self.char_bold=False # bold mode
1208 self.char_underline=False # underline mode
1209 self.hiRes=False # high resolution of graphics output
1210 self.lpi6=False # lines/inch
1211 self.pdf_rows=480 # number of rows for pdf output
1212 self.wrapEOL=False # EOL wrap
1213 self.empty_line=False # detect empty text lines, this disables
1214 # ignoring cr/lf.
1215#
1216# printer status which is handled here
1217#
1218 self.ltermMode=0 # line termination mode
1219 self.altMode= False # alternate control mode
1220 self.displayFunctions=False # display functions mode
1221#
1222# buffer for accumulated text and graphics data b e t w e e n control
1223# characters or escape sequences
1224#
1225 self.buf_status=self.BUF_EMPTY # buffer status
1226 self.buf_data= [ ]
1227 self.log_line=""
1228 self.reset()
1229#
1230#
1231 def reset(self):
1232#
1233# reset variables to default
1234#
1235 self.esc= False
1236 self.esc_seq=""
1237 self.esc_prefix=""
1238 self.num_graphics=-1
1239 self.ignore_crlf=False
1240 self.apgot=False
1241 self.text_length=60
1242 self.pdf_rows=480
1243 self.char_attr=0
1244 self.char_bold= False
1245 self.char_underline=False
1246 self.ltermMode=0
1247 self.hiRes=False
1248 self.lpi6=True
1249 self.wrapEOL=False
1251 self.displayFunctions=False
1252 self.log_line=""
1253 self.empty_line=False
1254 self.buf_clear()
1256#
1257# send clear command to GUI
1258#
1259 self.guiobject.put_cmd([REMOTECMD_CLEAR])
1260 self.put_status()
1261 return
1262#
1263# clear data buffer
1264#
1265 def buf_clear(self):
1266 self.buf_status= self.BUF_EMPTY
1267 self.buf_data= [ ]
1268#
1269# flush buffer, send data to printer
1270#
1271 def buf_flush(self):
1272 if self.buf_status == self.BUF_EMPTY:
1273 return
1274 data_copy= copy.deepcopy(self.buf_data)
1275 if self.buf_status == self.BUF_TEXT:
1276 self.guiobject.put_cmd([REMOTECMD_TEXT,data_copy])
1277# print("put cmd text",data_copy)
1278 else:
1279 self.guiobject.put_cmd([REMOTECMD_GRAPHICS,data_copy])
1280# print("put cmd graphics",data_copy)
1281 self.buf_clear()
1282#
1283# send status
1284#
1285 def put_status(self):
1286#
1287# if we have data in the buffer, clear it first
1288#
1289 self.buf_flush()
1290#
1291# send printer status to GUI
1292#
1293 self.guiobject.put_cmd([REMOTECMD_STATUS,[self.pdf_rows,self.char_attr,self.char_bold,self.char_underline,self.hiRes,self.lpi6,self.wrapEOL]])
1295#
1296# set/clear alternate control mode (has no effect)
1297#
1298 def setAltMode(self,mode):
1299 self.altMode= mode
1300 if self.altMode:
1301 print("hp2225: entering ESC/P command mode. This mode is not supported and the emulator will ignore all data.")
1302 else:
1303 print("hp2225: returning to PCL command mode.")
1304 return
1305#
1306# process escaape sequences HP command set
1307#
1308 def process_esc_hp(self):
1309#
1310# normal width
1311#
1312 if self.esc_seq=="&k0S" or self.esc_seq=="&kS":
1313 self.char_attr= (self.char_attr & 0x0C)
1314 self.put_status()
1315 return
1316#
1317# expanded width
1318#
1319 elif self.esc_seq=="&k1S":
1320 self.char_attr= (self.char_attr & 0x0C) | 0x01
1321 self.put_status()
1322 return
1323#
1324# compressed width
1325#
1326 elif self.esc_seq=="&k2S":
1327 self.char_attr= (self.char_attr & 0x0C) | 0x02
1328 self.put_status()
1329 return
1330#
1331# expanded-compressed width
1332#
1333 elif self.esc_seq=="&k3S":
1334 self.char_attr= (self.char_attr & 0x0C) | 0x03
1335 self.put_status()
1336 return
1337#
1338# bold mode on
1339#
1340 elif self.esc_seq=="(s1B":
1341 self.char_bold= True
1342 self.put_status()
1343 return
1344#
1345# bold mode off
1346#
1347 elif self.esc_seq=="(s0B":
1348 self.char_bold=False
1349 self.put_status()
1350 return
1351#
1352# underline mode on
1353#
1354 elif self.esc_seq=="&dD":
1355 self.char_underline=True
1356 self.put_status()
1357 return
1358#
1359# underline mode off
1360#
1361 elif self.esc_seq=="&d@":
1362 self.char_underline=False
1363 self.put_status()
1364 return
1365#
1366# 6 lines/inch
1367#
1368 elif self.esc_seq=="&l6D":
1369 self.lpi6=True
1370 self.put_status()
1371 return
1372#
1373# 8 lines/inch
1374#
1375 elif self.esc_seq=="&l8D":
1376 self.lpi6=False
1377 self.put_status()
1378 return
1379#
1380# perforation skip on (has no effect)
1381#
1382 elif self.esc_seq=="&l1L":
1383 return
1384#
1385# perforation skip off (has no effect)
1386#
1387 elif self.esc_seq=="&l0L":
1388 return
1389#
1390# EOL wrap on
1391#
1392 elif self.esc_seq=="&s0C":
1393 self.wrapEOL=True
1394 self.put_status()
1395 return
1396#
1397# EOL wrap off
1398#
1399 elif self.esc_seq=="&s1C":
1400 self.wrapEOL=False
1401 self.put_status()
1402 return
1403#
1404# display functions on
1405#
1406 elif self.esc_seq=="Y":
1407 self.displayFunctions=True
1408 return
1409#
1410# display functions off
1411#
1412 elif self.esc_seq=="Z":
1413 self.displayFunctions=False
1414 return
1415#
1416# unidirectional printing (has no effect)
1417#
1418 elif self.esc_seq=="&k0W":
1419 return
1420#
1421# biidirectional printing (has no effect)
1422#
1423 elif self.esc_seq=="&k1W":
1424 return
1425#
1426# half line feed
1427#
1428 elif self.esc_seq=="=":
1429 self.half_lf()
1430 return
1431#
1432# avoid printing graphics over text
1433#
1434 elif self.esc_seq=="*rA":
1435 self.apgot= True
1436 return
1437#
1438# terminate graphics
1439#
1440 elif self.esc_seq=="*rB":
1441 self.ignore_crlf= False
1442 self.guiobject.put_cmd([REMOTECMD_TERMGRAPHICS])
1443 return
1444#
1445# line termination mode 0
1446#
1447 elif self.esc_seq=="&k0G":
1448 self.ltermMode=0
1449 return
1450#
1451# line termination mode 1
1452#
1453 elif self.esc_seq=="&k1G":
1454 self.ltermMode=1
1455 return
1456#
1457# line termination mode 2
1458#
1459 elif self.esc_seq=="&k2G":
1460 self.ltermMode=2
1461 return
1462#
1463# line termination mode 3
1464#
1465 elif self.esc_seq=="&k3G":
1466 self.ltermMode=3
1467 return
1468#
1469# self test (has no effect)
1470#
1471 elif self.esc_seq=="z":
1472 return
1473#
1474# reset printer
1475#
1476 elif self.esc_seq=="E":
1477 self.reset()
1478 return
1479#
1480# Graphics dot row
1481#
1482 elif self.esc_seq.startswith("*b") and self.esc_seq.endswith("W"):
1483 ret=re.findall(r"\d+",self.esc_seq)
1484 if ret== []:
1485 return
1486 try:
1487 n=int(ret[0])
1488 except ValueError:
1489 return
1490 if n<0 or n> 255:
1491 return
1492 self.num_graphics=n
1493 self.begin_graphics()
1494 return
1495#
1496# graphics resolution
1497#
1498 elif self.esc_seq.startswith("*r") and self.esc_seq.endswith("S"):
1499 ret=re.findall(r"\d+",self.esc_seq)
1500 if ret== []:
1501 return
1502 try:
1503 n=int(ret[0])
1504 except ValueError:
1505 return
1506 if n<=640:
1507 self.hiRes=False
1508 else:
1509 self.hiRes=True
1510 self.put_status()
1511 return
1512#
1513# page length (has no effect)
1514#
1515 elif self.esc_seq.startswith("&l") and self.esc_seq.endswith("P"):
1516 return
1517#
1518# Text length
1519#
1520 elif self.esc_seq.startswith("&l") and self.esc_seq.endswith("F"):
1521 ret=re.findall(r"\d+",self.esc_seq)
1522 if ret== []:
1523 return
1524 try:
1525 n=int(ret[0])
1526 except ValueError:
1527 return
1528 self.text_length=n
1529#
1530# the text length can not exceed 80 lines at 8lpi or 60 lines at
1531# 6lpi. The maximum print area is limited to 10 inches in this
1532# emulator. We now compute the numbes of rows the new text length
1533# will occupy in the pdf file
1534#
1535 if self.text_length < 1:
1536 self.text_length=1
1537 if self.lpi6:
1538 if self.text_length> 60:
1539 self.text_length=60
1540 self.pdf_rows= self.text_length* 8
1541 else:
1542 if self.text_length> 80:
1543 self.text_length=80
1544 self.pdf_rows= self.text_length * 6
1545 self.pdf_rows-=1
1546 self.put_status()
1547 return
1548 else:
1549 print("hp2225: illegal escape sequence ignored: ", self.esc_seq)
1550 return
1551#
1552# begin graphics
1553#
1554 def begin_graphics(self):
1555#
1556# flush any pending text and go to BOL
1557#
1558# print("begin new graphics ",self.num_graphics)
1559 if self.buf_status== self.BUF_TEXT:
1560 self.cr()
1561#
1562# if the avoid printing graphics over text command was issued do a linefeed
1563#
1564 if self.apgot:
1565 self.lf()
1566 self.apgot= False
1567 self.ignore_crlf= True
1568 self.buf_status= self.BUF_GRAPHICS
1569 self.empty_line= False
1570 return
1572#
1573# printer data processor HP command set
1574#
1575 def process_char_hp(self,ch):
1576#
1577# if there are graphics data, append it to buffer and return
1578#
1579 if self.num_graphics > 0:
1580 self.num_graphics-=1
1581 self.buf_data.append(ch)
1582 return
1583#
1584# last byte of graphics received, flush buffer and proceed
1585#
1586 if self.num_graphics==0:
1587 self.buf_flush()
1588# print("graphics flushed ", self.buf_status)
1589 self.num_graphics= -1
1590#
1591# process ESC sequences
1592#
1593 if (self.esc== False) and (ch== 0x1B):
1594 self.esc= True
1595 self.esc_seq=""
1596 self.esc_prefix=""
1597 self.empty_line=False
1598 return
1599 if self.esc:
1600#
1601# ESC | or escape sequence terminated with capital letter
1602#
1603# if ch == 0x7c or (ch >= 0x41 and ch <= 0x5A):
1604 self.empty_line=False
1605 if chr(ch) in "SBD@LPFCYZW=AGzE":
1606 self.esc_seq+= chr(ch)
1607 if self.esc_prefix!="":
1608 self.esc_seq= self.esc_prefix+self.esc_seq
1609 self.process_esc_hp()
1610 self.esc= False
1611 self.esc_seq=""
1612 self.esc_prefix=""
1613 return
1614#
1615# repeated escape sequence terminated with lowercase letter
1616#
1617 if chr(ch) in "sdlpfcwabg" and len(self.esc_seq)>2:
1618 if self.esc_prefix == "":
1619 self.esc_prefix= self.esc_seq[:2]
1620 self.esc_seq= self.esc_seq[2:]
1621 self.esc_seq= self.esc_prefix+self.esc_seq+chr(ch).upper()
1622 self.process_esc_hp()
1623 self.esc_seq=""
1624 return
1625#
1626# still in escape sequence, accumulate characters
1627#
1628 self.esc_seq+= chr(ch)
1629 return
1630#
1631# not in escape sequence, process control and data characters
1632#
1633# Backspace:
1634#
1635 if (ch == 0x08):
1636 if self.ignore_crlf:
1637 return
1638 self.buf_flush()
1639 self.guiobject.put_cmd([REMOTECMD_BS])
1640#
1641# CR
1642#
1643 elif (ch == 0x0D):
1644 if self.ignore_crlf:
1645 return
1646 self.cr()
1647 if self.ltermMode & 0x01:
1648 self.lf()
1649#
1650# LF
1651#
1652 elif (ch == 0x0A):
1653 if self.empty_line:
1654 if self.ignore_crlf:
1655 self.ignore_crlf= False
1656 self.empty_line= True
1657 if self.ignore_crlf:
1658 return
1659 if self.ltermMode & 0x02:
1660 self.cr()
1661 self.lf()
1662#
1663# FF
1664#
1665 elif (ch == 0x0C):
1666 if self.ltermMode & 0x02 or self.ltermMode & 0x03:
1667 self.cr()
1668 self.ff()
1669#
1670# bold mode on ^N
1671#
1672 elif (ch == 0x0E):
1673 self.empty_line= False
1674 self.char_bold=True
1675 self.put_status()
1676#
1677# bold mode off ^O
1678#
1679 elif (ch == 0x0F):
1680 self.empty_line= False
1681 self.char_bold=False
1682 self.put_status()
1683#
1684# normal character
1685#
1686 else:
1687 self.empty_line= False
1688 self.ignore_crlf=False
1689 if ((ch >=0x20 and ch < 127) or (ch > 159 and ch< 255)):
1690 self.buf_status= self.BUF_TEXT
1691 assert self.buf_status== self.BUF_EMPTY or self.buf_status== self.BUF_TEXT
1692 self.buf_data.append(ch)
1693 self.log_line+= charconv(chr(ch), CHARSET_HP2225)
1694#
1695# process printer data for display functions mode
1696#
1697 def process_char_df(self,ch):
1698 if self.esc:
1699 self.esc= False
1700 if ch== 0x5A: # ESC Z terminates display functions mode
1701 self.displayFunctions= False
1702 if ch == 0x1B:
1703 self.esc=True
1704 self.buf_status= self.BUF_TEXT
1705 self.buf_data.append(ch)
1706 self.log_line+= charconv(chr(ch), CHARSET_HP2225)
1707 if (ch == 0x0A):
1708 self.cr()
1709 self.lf()
1710 return
1711#
1712# process printer data for the alternate control mode (not implemented!)
1713#
1714 def process_char_alt(self,ch):
1715 return
1716#
1717# process printer data
1718#
1719 def process_char(self,ch):
1720 if self.altMode:
1721 self.process_char_alt(ch)
1722 else:
1723 if self.displayFunctions:
1724 self.process_char_df(ch)
1725 else:
1726 self.process_char_hp(ch)
1727 return
1728#
1729# line positioning: cr, lf, ff
1730#
1731 def cr(self):
1732 self.buf_flush()
1733 self.guiobject.put_cmd([REMOTECMD_CR])
1734 return
1736 def lf(self):
1737 self.buf_flush()
1738 self.guiobject.put_cmd([REMOTECMD_LF])
1739 if self.log_line != "":
1740 self.log_line+="\n"
1741 self.guiobject.put_cmd([REMOTECMD_LOG,self.log_line])
1742 self.log_line=""
1743 return
1745 def half_lf(self):
1746 self.buf_flush()
1747 self.guiobject.put_cmd([REMOTECMD_HLF])
1748 return
1751 def ff(self):
1752 self.buf_flush()
1753 self.guiobject.put_cmd([REMOTECMD_FF])
1754 if self.log_line != "":
1755 self.log_line+="\n"
1756 self.guiobject.put_cmd([REMOTECMD_LOG,self.log_line])
1757 self.log_line=""
1758 return
1759#
1760# process commands sent from the GUI
1761#
1762 def process(self,command):
1764 if command== CMD_CLEAR:
1765 self.reset()
1766 return
1768#
1769# HP-IL virtual HP2225B object class ---------------------------------------
1770#
1773class cls_pilhp2225b(cls_pildevbase):
1775 def __init__(self,guiobject):
1776 super().__init__()
1778#
1779# overloaded variable initialization
1780#
1781 self.__aid__ = 0x23 # accessory id
1782 self.__defaddr__ = 5 # default address alter AAU
1783 self.__did__ = "HP2225B" # device id
1784 self.__status_len__=2 # device status is 2 bytes
1785#
1786# private vars
1787#
1788 self.__ddlcmd__=False # ddl command was sent
1789#
1790# object specific variables
1791#
1792 self.__guiobject__= guiobject
1793#
1794# initialize remote command queue and lock
1795#
1796 self.__print_queue__= queue.Queue()
1797 self.__print_queue_lock__= threading.Lock()
1798#
1799# printer processor
1800#
1801 self.__printer__=cls_hp2225b(self,self.__guiobject__)
1802#
1803# the printer status is a l w a y s:
1804# - first byte : 0xA1 (everything is fine, no service request)
1805# - second byte: 0x04 (buffer empty)
1806#
1807 self.set_status(0x04A1)
1809#
1810# public (overloaded) --------
1811#
1812# enable: reset
1813#
1814 def enable(self):
1815 self.__ddlcmd__= False
1816 self.__printer__.reset()
1817 return
1818#
1819# disable: clear the printer command queue
1820#
1821 def disable(self):
1822 self.__print_queue_lock__.acquire()
1823 while True:
1824 try:
1825 self.__print_queue__.get_nowait()
1826 self.__print_queue__.task_done()
1827 except queue.Empty:
1828 break
1829 self.__print_queue_lock__.release()
1830#
1831# process frames
1832#
1833 def process(self,frame):
1835 if self.__isactive__:
1836 self.process_print_queue()
1837 frame= super().process(frame)
1838 return frame
1839#
1840# process the printer command queue
1841#
1842 def process_print_queue(self):
1843 items=[]
1844 self.__print_queue_lock__.acquire()
1845 while True:
1846 try:
1847 i=self.__print_queue__.get_nowait()
1848 items.append(i)
1849 self.__print_queue__.task_done()
1850 except queue.Empty:
1851 break
1852 self.__print_queue_lock__.release()
1853 if len(items):
1854 for c in items:
1855 self.__printer__.process(c)
1856 return
1858#
1859# put command into the print-command queue
1860#
1861 def put_cmd(self,item):
1862 self.__print_queue_lock__.acquire()
1863 self.__print_queue__.put(item)
1864 self.__print_queue_lock__.release()
1865#
1866# set status (2 byte status information)
1867#
1868 def set_status(self,s):
1869 self.__setstatus__(s)
1870 return s
1871#
1872# get status byte (2 byte status information)
1873#
1874 def get_status(self):
1875 s=self.__getstatus__()
1876 return s
1878#
1879# private (overloaded) --------
1880#
1881#
1882# output character to HP2225B
1883#
1884 def __indata__(self,frame):
1885 n= frame & 0xFF
1886#
1887 if self.__ddlcmd__:
1888 if n== 18:
1889 self.__printer__.setAltMode(True)
1890 if n==0:
1891 self.__printer__.setAltMode(False)
1892 self.__ddlcmd__= False
1893 else:
1894 self.__printer__.process_char(n)
1895#
1896# ddl command implementation
1897#
1898 def __cmd_ext__(self,frame):
1899 n= frame & 0xFF
1900 t= n>>5
1901 if t==5: # DDL
1902 n=n & 31
1903 if (self.__ilstate__ & 0xC0) == 0x80: # are we listener?
1904 self.__devl__= n & 0xFF
1905 if n== 6:
1906 self.__ddlcmd__= True
1907 return(frame)
1908#
1909# clear device: reset printer
1910#
1911 def __clear_device__(self):
1912 super().__clear_device__()
1913#
1914# clear printer queue
1915#
1916 self.__print_queue_lock__.acquire()
1917 while True:
1918 try:
1919 self.__print_queue__.get_nowait()
1920 self.__print_queue__.task_done()
1921 except queue.Empty:
1922 break
1923 self.__print_queue_lock__.release()
1924#
1925# reset device
1926#
1927 self.__printer__.reset()