Ermöglicht einfache Ausdrucke auf in Windows installierte Drucker.
Version vom 21. März 2007:
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""
***************************************************************************
* Description:  Vereinfacht das Drucken unter Windows durch Kapselung
*               der wichtigsten Funktionen der Windows-API.
*               
*               Ermöglicht einfache Ausdrucke auf in Windows installierte
*               Drucker. Bitmaps können auch ausgedruckt werden.
*
* Created:      2005-04-22 by Gerold
*
* Requirements: Python 2.4:  http://www.python.org/
*               pywin32:     http://sourceforge.net/projects/pywin32/
*
* Beispiel: 
*   import simplewinprint
*   text = \
*      "Das ist umgebrochener Text.\n" + \
*      "Dieser wird auch im Ausdruck umgebrochen sein."
*   p = simplewinprint.Printer()
*   p.startdoc()
*   p.default_font_name = "Arial"
*   p.default_font_size = 11
*   p.startdoc()
*   p.print_textblock(
*      text = "Überschrift",
*      font_size = 16,
*      font_weight = 700,
*      lineheight_percent = 150
*   )   
*   p.print_textblock(text)   
*   p.print_bitmapfile(r"<Pfad zu einem Bitmap-File>", padding_top = 200)
*   p.print_textblock("(Das ist die Bildunterschrift)", font_size = 8)
*   p.print_textblock()
*   p.print_textblock(text)   
*   p.enddoc()
*
***************************************************************************
"""
#*********************************************
# Konstanten fuer GetDeviceCaps 
#*********************************************
# HORZRES / VERTRES = printable area
HORZRES = 8
VERTRES = 10
# LOGPIXELS = dots per inch
LOGPIXELSX = 88
LOGPIXELSY = 90
# PHYSICALWIDTH/HEIGHT = total area
PHYSICALWIDTH = 110
PHYSICALHEIGHT = 111
# PHYSICALOFFSETX/Y = left / top margin
PHYSICALOFFSETX = 112
PHYSICALOFFSETY = 113
#----------------------------------------------------------------------
class Printer(object):
   """
   Diese Klasse stellt das Drucker-Objekt dar und kümmert sich darum,
   dass die übergebenen Daten korrekt ausgedruckt werden.
   """
   
   #----------------------------------------------------------------------
   def twips2pixels(
      self, 
      twips,
      horizontal = True,
   ):
      """
      Konvertiert Twips in Pixel
      twips:      
         Zu konvertierende Twips
      horizontal:
         Gibt an, ob es sich um horizontale Twips handelt.
            True = horizontal
            False = vertikal
      """
      
      nTwipsPerInch = 1440
      
      if horizontal:
         pixels_per_inch = self.pixels_per_inch_x
      else:
         pixels_per_inch = self.pixels_per_inch_y
      
      return (twips / nTwipsPerInch) * pixels_per_inch
   #----------------------------------------------------------------------
   def pixels2twips(
      self,
      pixels,
      horizontal = True,
   ):
      """
      Konvertiert Pixel in Twips
      pixels:
         Zu konvertierende Pixel
      horizontal:
         Gibt an, ob es sich um horizontale Pixel handelt.
            True = horizontal
            False = vertikal
      """
      nTwipsPerInch = 1440.0
      
      if horizontal:
         pixels_per_inch = self.pixels_per_inch_x
      else:
         pixels_per_inch = self.pixels_per_inch_y
      
      return float(pixels) / pixels_per_inch * nTwipsPerInch
   #----------------------------------------------------------------------
   def __init__(
      self, 
      printer_name = "Default",
      lineheight_percent = 100,
      margin_left = 800,
      margin_top = 800,
      margin_right = 800,
      margin_bottom = 800,
      document_name = "SimpleWinPrint"
   ):
      """
      Initialisiert die Klasse "Printer". Dabei werden die 
      Einstellungen (Rand, Druckbereich,...) herausgefunden.
      printer_name:
         Wenn "Default", dann wird der Standarddrucker verwendet. Ansonsten
         wird der übergebene Drucker(name) verwendet.
      lineheight_percent:
         Zeilenhöhe in Prozent in Bezug zur Schrifthöhe. 
      margin_xxx:
         Rand links, oben, rechts und unten in Twips.
      document_name:
         Name, mit dem der Druckjob im Spooler angelegt wird.
      """
      
      import win32ui
      import time
      import win32print
      import win32con
      # Zeilenhoehe in Prozent
      self.lineheight_percent = float(lineheight_percent)
      self._lineheight_twips = 0
      
      # Dokumentname
      self.document_name = document_name
      
      # Rand
      self.margin_left = margin_left
      self.margin_top = margin_top
      self.margin_right = margin_right
      self.margin_bottom = margin_bottom
      
      # vpos = aktuelle Zeile = vertikale Position von oben als negative Zahl
      # Wird mit None initialisiert
      self.vpos = None
      
      # hpos = Aktuelle Position von Links
      self.hpos = self.margin_left
      
      # Standardschrift festlegen
      self.default_font_name = ""
      self.default_font_size = 0
      self.default_font_weight = 0
      
      # Wurde der Druck abgebrochen?
      self.canceled = False
      
      # Gerätekontext holen und Einheit auf TWIPS einstellen
      self.hdc = win32ui.CreateDC()
      if printer_name == "Default":
         self.hdc.CreatePrinterDC(win32print.GetDefaultPrinter())
      else:
         self.hdc.CreatePrinterDC(printer_name)
      self.hdc.SetMapMode(win32con.MM_TWIPS)
         
      # Pixel pro Zoll herausfinden
      # Wird später zur Umrechnung von Pixel in Twips benötigt
      self.pixels_per_inch_x = float(self.hdc.GetDeviceCaps(LOGPIXELSX))
      self.pixels_per_inch_y = float(self.hdc.GetDeviceCaps(LOGPIXELSY))
      
      # Bedruckbarer Bereich in Pixel
      self.printable_area = [
         self.hdc.GetDeviceCaps(HORZRES), 
         self.hdc.GetDeviceCaps(VERTRES)
      ]
      self.printable_area[0] = self.pixels2twips(self.printable_area[0], True)
      self.printable_area[1] = self.pixels2twips(self.printable_area[1], False)
      # Physikalische Breite und Höhe der Seite in Pixel
      self.printer_size = [
         self.hdc.GetDeviceCaps(PHYSICALWIDTH), 
         self.hdc.GetDeviceCaps(PHYSICALHEIGHT)
      ]
      self.printer_size[0] = self.pixels2twips(self.printer_size[0], True)
      self.printer_size[1] = self.pixels2twips(self.printer_size[1], False)
      # Rand in Pixel
      self.printer_margins = [
         self.hdc.GetDeviceCaps(PHYSICALOFFSETX), 
         self.hdc.GetDeviceCaps(PHYSICALOFFSETY)
      ]
      self.printer_margins[0] = self.pixels2twips(self.printer_margins[0], True)
      self.printer_margins[1] = self.pixels2twips(self.printer_margins[1], False)
   
   #----------------------------------------------------------------------
   def __del__(self):
      """
      Destruktor
      """
      
      try: 
         self.hdc.AbortDoc()
      except:
         pass
   #----------------------------------------------------------------------
   def startdoc(self):
      """
      Versetzt den Druckerspooler in den Bereitschaftsmodus und
      startet eine neue Seite.
      """
      
      import time
      new_docname = "%s %s" % (
         self.document_name, 
         time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
      )
      
      self.hdc.StartDoc(new_docname)
      self.hdc.StartPage ()
   #----------------------------------------------------------------------
   def nextpage(self):
      """
      Erzwingt eine neue Seite
      """
      
      self.hdc.EndPage ()
      self.hdc.StartPage ()
      self.vpos = None
   #----------------------------------------------------------------------
   def print_textblock(
      self, 
      text = "", 
      align_str = "left",
      break_long_lines = 'words',
      on_linebreak_lstrip = True,
      font_name = "",
      font_size = 0, 
      font_weight = 0,
      font_italic = False,
      font_underline = False,
      hpos = None,
      vpos = None, 
      lineheight_percent = None
   ):
      """
      Schreibt einen Textblock. Dieser kann auch aus mehreren Zeilen bestehen.
      Zeilenumbrüche (\n) im Text werden umgebrochen.
      Unterstützt auch das automatische Umbrechen von zu langem Text 
      in mehrere Zeilen. Dabei kann unterschieden werden, 
      ob der Text bei jedem Zeichen oder bei Wörtern umgebrochen wird.
      text: 
         Zu schreibender Text
      align_str: 
         Textausrichtung als String:
            'left'
            'right'
      break_long_lines:
         Gibt an, ob der Text automatisch am Zeilenende umgebrochen werden soll.
            'no': Kein automatischer Zeilenumbruch
            'words': Umbruch nur nach einem Wort
            'chars': Umbruch nach jedem Zeichen
      on_linebreak_lstrip: 
         Wenn True, dann werden nach einem automatischen Zeilenumbruch, die
         Leerzeichen der nächsten Zeile mit "lstrip()" entfernt.
      font_name:
         Name der zu verwendenden Schrift
      font_size:
         Schriftgröße der zu verwendenden Schrift
      font_weight:
         Schriftgewichtung (Fett) der zu verwendenden Schrift
            FW_DONTCARE 0         FW_SEMIBOLD 600
            FW_THIN 100           FW_DEMIBOLD 600 
            FW_EXTRALIGHT 200     FW_BOLD 700 
            FW_ULTRALIGHT 200     FW_EXTRABOLD 800 
            FW_LIGHT 300          FW_ULTRABOLD 800 
            FW_NORMAL 400         FW_HEAVY 900 
            FW_REGULAR 400        FW_BLACK 900 
            FW_MEDIUM 500 
      font_italic: 
         Wenn True, dann wird die Schrift kursiv gedruckt
      font_underline:
         Wenn True, dann wird die Schrift unterstrichen gedruckt
      hpos:
         Überschreibt die horizontale Druckposition in Twips
      vpos: 
         Überschreibt die vertikale Druckposition in Twips
      lineheight_percent:
         Überschreibt für die aktuelle Zeile die Zeilenhöhe in Prozent
      """
      
      import win32ui
      import win32con
      import textwrap
      
      # Wenn Unicode-`text` --> umwandeln
      if isinstance(text, unicode):
         text = text.encode("iso-8859-1", "replace")
      
      # Leerzeile drucken ->> "" durch " " ersetzen
      if (text is None) or (text == ""):
         text = " "
         
      # Schrift
      if not(font_weight):
         font_weight = self.default_font_weight
      font_dict = {
         "weight": font_weight
      }
      if font_name:
         font_dict["name"] = font_name
      else:
         if self.default_font_name:
            font_dict["name"] = self.default_font_name
      if font_size:
         font_dict["height"] = int(
            self.pixels2twips(
               (font_size * self.pixels_per_inch_y), False
            ) / 72 * -1
         )
      else:
         if self.default_font_size:
            font_dict["height"] = int(
               self.pixels2twips(
                  (self.default_font_size * self.pixels_per_inch_y), False
               ) / 72 * -1
            )
      if font_italic:
         font_dict["italic"] = True
      if font_underline:
         font_dict["underline"] = True
      font_object = win32ui.CreateFont(font_dict)
      self.hdc.SelectObject(font_object)
      
      # Zeilenhöhe errechnen
      if lineheight_percent is None:
         lineheight_percent = self.lineheight_percent
      self._lineheight_twips = int(
         float(self.hdc.GetTextMetrics()["tmHeight"]) \
         / 100.0 * lineheight_percent
      ) + 1
      
      # Rand Links
      if hpos is None:
         self.hpos = self.margin_left
      else:
         self.hpos = hpos
      
      # Zeile in druckbare Bereiche aufteilen
      if len(text) > 1:
         max_width_twips = \
            self.printable_area[0] - self.hpos - self.margin_right
         new_line = ""
         textlist = []
         if break_long_lines == "chars":
            # Zeilenumbruch bei jedem Zeichen
            enforced_linebreak = False
            for item in text:
               new_line += item
               
               if on_linebreak_lstrip and enforced_linebreak:
                  new_line = new_line.lstrip()
               if item == "\n":
                  textlist.append(new_line[:-1])
                  new_line = item
                  enforced_linebreak = False
               else:
                  if self.hdc.GetTextExtent(new_line)[0] > max_width_twips:
                     textlist.append(new_line[:-1])
                     new_line = item
                     enforced_linebreak = True
               
            if len(new_line) > 0:
               textlist.append(new_line)
               
         elif break_long_lines == "words":
            # Zeilenumbruch nach Wörtern
            
            slice_begin = 0
            korrektur = 0
            enforced_linebreak = False
            for i in xrange(len(text) * 3):
               slice_end = i + korrektur
               new_line = text[slice_begin:slice_end]
               if new_line.endswith("\n"):
                  textlist.append(new_line[:-1].replace("\r", ""))
                  if on_linebreak_lstrip and enforced_linebreak:
                     textlist[-1] = textlist[-1].lstrip()
                  slice_begin = slice_begin + len(new_line)
                  korrektur = slice_begin - i
                  enforced_linebreak = False
               else:
                  if self.hdc.GetTextExtent(new_line)[0] >= max_width_twips:
                     slice_end -= 1
                     new_line = text[slice_begin:slice_end + 100]
                     new_line = textwrap.wrap(new_line, slice_end - slice_begin)[0]
                     textlist.append(new_line)
                     if on_linebreak_lstrip and enforced_linebreak:
                        textlist[-1] = textlist[-1].lstrip()
                        
                     slice_begin = slice_begin + len(new_line)
                     korrektur = slice_begin - i
                     enforced_linebreak = True
                  
                  if slice_begin >= len(text):
                     break
               
            if len(new_line) > 0:
               textlist.append(new_line)
               if on_linebreak_lstrip and enforced_linebreak:
                  textlist[-1] = textlist[-1].lstrip()
            
         else:
            textlist = [text]
      else:
         textlist = [text]
      
      # Jeden Eintrag der Textliste durchlaufen
      for item in textlist:
      
         # Neue Zeilenposition setzen (wichtig für neue Seitenanfänge)
         #------------------------------------------------------------
         def __new_rowpos_first(vpos):
            if vpos is None:
               if self.vpos is None:
                  self.vpos = self.margin_top * -1
            else:
               if vpos > 0:
                  self.vpos = vpos * -1
               else:
                  self.vpos = vpos
         __new_rowpos_first(vpos)
         
         # Seitenumbruch
         max_height_twips = self.printable_area[1] - self.margin_bottom
         if (self.vpos * -1) >= max_height_twips:
            self.nextpage()
            __new_rowpos_first(vpos)
         # Ausrichtung
         if align_str == "right":
            align = win32con.DT_RIGHT
            hpos_real = self.printable_area[0] - self.hpos
            if on_linebreak_lstrip:
               item = item.rstrip()
         else:
            align = win32con.DT_LEFT
            hpos_real = self.hpos
         self.hdc.SetTextAlign(align)
         
         # Zeile drucken
         self.hdc.TextOut(
            int(hpos_real), 
            int(self.vpos), 
            item
         )
         
         # Neue Zeilenposition setzen (nur wenn bereits gesetzt)
         if vpos is None:
            self.vpos -= self._lineheight_twips
         else:
            if vpos > 0:
               self.vpos = vpos * -1
            else:
               self.vpos = vpos
   #----------------------------------------------------------------------
   def print_rawtext(
      self, 
      text = "", 
      font_name = None,
      hpos = None,
      vpos = None, 
      auto_pagebreak = False
   ):
      """
      Diese Funktion ist dafür geeignet, einem Drucker 
      (z.B. Bondrucker) Steuercodes und reinen Text zu schicken. 
      Die Schriftgroesse kann nicht umgestellt werden.
      Rufen Sie vorher die Funktion "startdoc" und nachher "enddoc" auf.
      text: 
         Zu schreibender Text
      font_name:
         Schriftname
      hpos:
         Überschreibt die horizontale Druckposition in Twips
      vpos: 
         Überschreibt die vertikale Druckposition in Twips
      auto_pagebreak:
         Wenn True, dann werden Seitenumbrüche automatisch durchgeführt
      """
      
      import win32ui
      import win32con
      
      # Leerzeile drucken ->> "" durch " " ersetzen
      if (text is None) or (text == ""):
         text = " "
      
      # Wenn Unicode-`text` --> umwandeln
      if isinstance(text, unicode):
         text = text.encode("iso-8859-1", "replace")
      
      # Schrift
      if not(font_name):
         if self.default_font_name:
            font_name = self.default_font_name
      font_dict = {
         "name": font_name,
         "width": 0,
         "height": 0,
         "weight": 0,
      }
      font_object = win32ui.CreateFont(font_dict)
      self.hdc.SelectObject(font_object)
      # Zeilenhöhe in Twips errechnen
      self._lineheight_twips = int(
         self.hdc.GetTextMetrics()["tmHeight"]
      ) + 1
      
      # Umschalten in den reinen Textmodus
      old_mode = self.hdc.SetMapMode(win32con.MM_TEXT)
      
      # Zeilenhöhe im Textmodus herausfinden
      lineheight_textmode = self.hdc.GetTextMetrics()["tmHeight"]
      
      # Divident für Umrechnung der Zeilenhöhe ausrechnen
      lineheight_divident = self._lineheight_twips / lineheight_textmode
      # Rand Links
      if hpos is None:
         self.hpos = self.margin_left
      else:
         self.hpos = hpos
      # Neue Zeilenposition setzen (wichtig für neue Seitenanfänge)
      #------------------------------------------------------------
      def __new_rowpos_first(vpos):
         if vpos is None:
            if self.vpos == None:
               self.vpos = self.margin_top * -1
         else:
            if vpos > 0:
               self.vpos = vpos * -1
            else:
               self.vpos = vpos
      __new_rowpos_first(vpos)
      
      # Seitenumbruch
      if auto_pagebreak:
         max_height_twips = self.printable_area[1] - self.margin_bottom
         if (self.vpos * -1) >= max_height_twips:
            self.nextpage()
            __new_rowpos_first(vpos)
      # Zeile drucken
      self.hdc.TextOut(
         int(int(float(self.hpos) / float(lineheight_divident))), 
         int(int(float(self.vpos) / float(lineheight_divident))) * -1, 
         text
      )
      # In den alten Modus umschalten
      new_mode = self.hdc.SetMapMode(old_mode)
      # Neue Zeilenposition setzen (nur wenn bereits gesetzt)
      if vpos is None:
         self.vpos -= self._lineheight_twips
      else:
         if vpos > 0:
            self.vpos = vpos * -1
         else:
            self.vpos = vpos
   #----------------------------------------------------------------------
   def print_bitmapfile(
      self,
      file_name,
      hpos = None,
      vpos = None,
      padding_top = 0,
      padding_left = 0,
      padding_bottom = 0,
      paramlist = []
   ):
      """
      Druckt ein Bitmapfile an der aktuellen Zeilenposition aus.
      file_name:
         Name und Pfad des auszudruckenden Bitmaps.
      hpos:
         Überschreibt die horizontale Druckposition in Twips.
      vpos: 
         Überschreibt die vertikale Druckposition in Twips.
      padding_xxx:
         Abstand des Bildes.
      paramlist:
         Wenn dieser Parameter übergeben wurde, dann werden
         die anderen Parameter (ausgenommen file_name) mit dem Inhalt 
         dieser Liste überschrieben.
         [hpos, vpos, padding_top, padding_left, padding_bottom]
      """
      
      import win32gui
      import win32con
      import win32ui
      
      
      # Leerzeichen vor und nach dem Dateinamen abschneiden
      file_name = file_name.strip()
      # Parameterliste aufteilen
      if paramlist:
         hpos = paramlist[0]
         if len(paramlist) >= 2:
            if paramlist[1] is not None:
               vpos = paramlist[1]
         if len(paramlist) >= 3:
            if paramlist[2] is not None:
               padding_top = paramlist[2]
         if len(paramlist) >= 4:
            if paramlist[3] is not None:
               padding_left = paramlist[3]
         if len(paramlist) >= 5:
            if paramlist[4] is not None:
               padding_bottom = paramlist[4]
      # Bild laden
      img = win32gui.LoadImage(
         0, 
         file_name, 
         win32con.IMAGE_BITMAP, 
         0, 
         0, 
         win32con.LR_LOADFROMFILE
      )
      
      # Zum Drucker kompatiblen DC für das Bild erstellen
      mem_dc = self.hdc.CreateCompatibleDC()
      
      # Skalierung ausrechnen
      mem_dc_pixels_per_inch_x = float(mem_dc.GetDeviceCaps(LOGPIXELSX))
      mem_dc_pixels_per_inch_y = float(mem_dc.GetDeviceCaps(LOGPIXELSY))
      mem_dc_scale_x = self.pixels_per_inch_x / mem_dc_pixels_per_inch_x
      mem_dc_scale_y = self.pixels_per_inch_x / mem_dc_pixels_per_inch_y
      
      # Bitmap aus Handle erstellen und in DC holen
      bmp = win32ui.CreateBitmapFromHandle(img)
      mem_dc.SelectObject(bmp)
      
      # Größe des Bildes in Pixel
      x, y = bmp.GetSize()
      if not(hpos is None):
         self.hpos = hpos
      # Neue Zeilenposition setzen (wichtig für neue Seitenanfänge)
      if vpos is None:
         if self.vpos is None:
            self.vpos = int(self.margin_top) * -1
      else:
         if vpos > 0:
            self.vpos = vpos * -1
         else:
            self.vpos = vpos
      
      # Bild in den Drucker-DC kopieren und dabei skalieren
      trylist = [
         win32con.SRCPAINT,
         win32con.PATPAINT,
         win32con.SRCCOPY,
         win32con.PATCOPY,
         win32con.MERGECOPY,
         win32con.SRCAND,
      ]
      for rop in trylist:
         try:
            self.hdc.StretchBlt(
               (
                  int(self.hpos + padding_left), 
                  int(self.vpos - padding_top)
               ),
               (
                  int(self.pixels2twips(float(x), True) * mem_dc_scale_x), 
                  int(self.pixels2twips(float(y), False) * mem_dc_scale_y) * -1
               ),
               mem_dc,
               (0, 0),
               (x, y),
               rop #win32con.PATPAINT #win32con.SRCCOPY
            )
            break
         except:
            continue
      # Neue Zeilenposition setzen
      self.vpos -= int(
         (self.pixels2twips(float(y), False) * mem_dc_scale_y) + \
         padding_top + padding_bottom + 10 # 10 ist nur geschätzt
      )
   #----------------------------------------------------------------------
   def abortdoc(self):
      """
      Bricht den Druckauftrag ab.
      """
      self.canceled = True
      self.hdc.AbortDoc()
   #----------------------------------------------------------------------
   def enddoc(self):
      """
      Schliesst den Druckauftrag ab. 
      Der Druckauftrag wird jetzt ausgedruckt. 
      """
      
      if self.canceled:
         try:
            self.hdc.AportDoc()
         except:
            pass
      else:
         self.hdc.EndPage ()
         self.hdc.EndDoc ()Gerold
:-)








