Browse Source

First commit

Rechner 1 year ago
commit
8489a88707
5 changed files with 656 additions and 0 deletions
  1. 3
    0
      README.md
  2. 74
    0
      main.py
  3. 539
    0
      printing.py
  4. 9
    0
      resources/nametag/date/date.conf
  5. 31
    0
      resources/nametag/date/default.html

+ 3
- 0
README.md View File

@@ -0,0 +1,3 @@
1
+Note about printing
2
+===================
3
+* Page size calibrated for lowest DPI setting (189)

+ 74
- 0
main.py View File

@@ -0,0 +1,74 @@
1
+import wx
2
+import paho.mqtt.client as paho
3
+
4
+import printing
5
+
6
+class Controls(wx.Frame):
7
+    def __init__(self, *args, **kwargs):
8
+        wx.Frame.__init__(self, *args, **kwargs)
9
+
10
+        self.Bind(wx.EVT_CLOSE, self.close)
11
+
12
+        sizer = wx.BoxSizer(wx.VERTICAL)
13
+        self.printBtn = wx.Button(self, wx.ID_ANY, "Print Date Label")
14
+        self.printBtn.SetFont(wx.Font(20, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.BOLD, False))
15
+        self.printBtn.Bind(wx.EVT_BUTTON, self.print_date)
16
+
17
+        self.kitchenBtn = wx.Button(self, wx.ID_ANY, "Kitchen Lights")
18
+        self.kitchenBtn.SetFont(wx.Font(20, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.NORMAL, False))
19
+        self.kitchenBtn.Bind(wx.EVT_BUTTON, self.toggle_kitchen)
20
+
21
+        self.diningBtn = wx.Button(self, wx.ID_ANY, "Dining Lights")
22
+        self.diningBtn.SetFont(wx.Font(20, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.NORMAL, False))
23
+        self.diningBtn.Bind(wx.EVT_BUTTON, self.toggle_dining)
24
+
25
+
26
+        self.SetBackgroundColour("black")
27
+        self.ShowFullScreen(True)
28
+
29
+        sizer.Add(self.printBtn, 2, wx.ALL|wx.EXPAND|wx.ALIGN_CENTRE, 20)
30
+
31
+        hsizer = wx.BoxSizer(wx.HORIZONTAL)
32
+
33
+        sizer.Add(hsizer, 1, wx.ALL|wx.EXPAND, 20)
34
+        hsizer.Add(self.kitchenBtn, -1, wx.ALL|wx.EXPAND|wx.ALIGN_CENTRE, 0)
35
+        hsizer.Add(self.diningBtn, -1, wx.ALL|wx.EXPAND|wx.ALIGN_CENTRE, 0)
36
+        self.SetSizer(sizer)
37
+
38
+        self.printer = printing.Main(False)
39
+
40
+        self.mqtt = paho.Client()
41
+        self.mqtt.connect("10.0.10.11")
42
+        self.mqtt.on_publish = Controls.on_publish
43
+        self.mqtt.loop_start()
44
+
45
+    def print_date(self, event):
46
+        self.printer.nametag(theme='date')
47
+        self.printer.printout(printer='Zebra_2824')
48
+        #self.printer.preview()
49
+        print(self.printer.pdf)
50
+        wx.CallLater(4000, self.printer.cleanup, [self.printer.pdf,])
51
+    
52
+    @staticmethod
53
+    def on_publish(client, userdata, result):
54
+        print("Published: {0}: {1}".format(userdata, result))
55
+  
56
+    def toggle_kitchen(self, event):
57
+        print("Kitchen light toggle")
58
+        self.mqtt.publish("house/switch/kitchen", "toggle")
59
+
60
+    def toggle_dining(self, event):
61
+        print("Dining light toggle")
62
+        self.mqtt.publish("house/switch/dining_room", "toggle")
63
+
64
+
65
+    def close(self, event):
66
+        self.mqtt.loop_stop()
67
+        self.mqtt.disconnect()
68
+        self.Destroy()
69
+
70
+if __name__ == '__main__':
71
+    app = wx.App()
72
+    frame = Controls(None, title="Kitchen", size=(480, 320))
73
+    frame.Show()
74
+    app.MainLoop()

+ 539
- 0
printing.py View File

@@ -0,0 +1,539 @@
1
+#!/usr/bin/env python
2
+#-*- coding:utf-8 -*-
3
+# vim: set ts=4 sts=4
4
+
5
+# For now the internal nametag templates will be hardcoded.  At least a [default]
6
+# section is required; if a named section is missing the HTML will be ignored,
7
+# otherwise the following sections and files are allowed:
8
+#   - [parent]
9
+#   - [report]
10
+#   - [volunteer]
11
+
12
+# TODO: respect locale's date/time format when doing substitution. Hard coded for now.
13
+# TODO: abstract barcode generation in themes. Hard coded for now.
14
+# TODO: a bunch of caching and restructuring.
15
+# TODO: code for multiple copies, if needed.
16
+# TODO: speed up batch printing with multi-page patched wkhtmltopdf?
17
+
18
+"""Handles generation of HTML for nametags, saving/reading printer config, etc"""
19
+
20
+import os
21
+import sys
22
+import re
23
+import platform
24
+import logging
25
+import subprocess
26
+import tempfile
27
+import datetime
28
+from configobj import ConfigObj
29
+
30
+#from codebar import codebar
31
+PRINT_MODE = 'pdf'
32
+
33
+# Platforms using the CUPS printing system (UNIX):
34
+unix = ['Linux', 'linux2', 'Darwin']
35
+wkhtmltopdf = '/usr/bin/wkhtmltopdf' #path to wkhtmltopdf binary
36
+#TODO: Option to select native or builtin wkhtmltopdf
37
+
38
+#TODO: Determine whether to use ~/.taxidi/resources/ (config.ini in ~/.taxidi/)
39
+#      or $PWD/resources/ (config.ini in pwd).
40
+script_path = os.path.dirname(os.path.realpath(__file__))
41
+nametags = os.path.join(script_path, 'resources', 'nametag') #path where html can be found.
42
+#For distribution this may be moved to /usr/share/taxidi/ in UNIX, but should be
43
+#copied to user's ~/.taxidi/ as write access is needed.
44
+lpr = '/usr/bin/lp' #path to LPR program (CUPS/Unix only).
45
+
46
+class Printer:
47
+    def __init__(self, local=False):
48
+        self.log = logging.getLogger(__name__)
49
+        if local:
50
+            self.con = _DummyPrinter()
51
+        else:
52
+            if platform.system() in unix:
53
+                self.log.info("System is type UNIX. Using CUPS.")
54
+                self.con = _CUPS()
55
+            elif platform.system() == 'win32':
56
+                self.log.info("System is type WIN32. Using GDI.")
57
+                #TODO implement win32 printing code
58
+            else:
59
+                self.log.warning("Unsupported platform. Printing by preview only.")
60
+
61
+    def buildArguments(self, config, section='default'):
62
+        """Returns list of arguments to pass to wkhtmltopdf. Requires config
63
+        dictionary and section name to read (if None reads [default]).
64
+        Works all in lowercase. If section name does not exist, will use
65
+        values from [default] instead.
66
+        """
67
+
68
+        try: #attempt to read passed section. If invalid, use default instead.
69
+            self.log.debug('Attempting to read print configuration for section {0}...'
70
+                .format(section))
71
+            piece = config[section]
72
+        except KeyError:
73
+            self.log.warn('No section for {0}: using default instead.'.format(section))
74
+            piece = config['default']
75
+
76
+        args = []
77
+        if len(piece) == 0:
78
+            return []  #No options in section
79
+        for arg in piece.keys():
80
+            if arg.lower() == 'zoom':
81
+                args.append('--zoom')
82
+                args.append(piece[arg])
83
+            elif arg.lower() == 'size':
84
+                args.append('-s')
85
+                args.append(piece[arg])
86
+            elif arg.lower() == 'height':
87
+                args.append('--page-height')
88
+                args.append(piece[arg])
89
+            elif arg.lower() == 'width':
90
+                args.append('--page-width')
91
+                args.append(piece[arg])
92
+            elif arg.lower() == 'left':
93
+                args.append('--margin-left')
94
+                args.append(piece[arg])
95
+            elif arg.lower() == 'right':
96
+                args.append('--margin-right')
97
+                args.append(piece[arg])
98
+            elif arg.lower() == 'top':
99
+                args.append('--margin-top')
100
+                args.append(piece[arg])
101
+            elif arg.lower() == 'bottom':
102
+                args.append('--margin-bottom')
103
+                args.append(piece[arg])
104
+            elif arg.lower() == 'orientation':
105
+                args.append('--orientation')
106
+                args.append(piece[arg].lower())
107
+            else:
108
+                self.log.warning("Unexpected key encountered in {0}: {1} = {2}"
109
+                    .format(section, arg, piece[arg]))
110
+        return args
111
+
112
+    def writePdf(self, args, html, copies=1, collate=True):
113
+        """
114
+        Calls wkhtmltopdf and generates a pdf. Accepts args as a list, path
115
+        to html file, and returns path to temporary file.  Temp file
116
+        should be unlinked when no longer needed.
117
+        Also accepts copies=1, collate=True
118
+        """
119
+        #Build arguments
120
+        if copies < 0: copies = 1
121
+        if copies != 1:
122
+            args.append("--copies")
123
+            args.append(copies)
124
+        if collate:
125
+            args.append("--collate")
126
+
127
+        if type(html) == list:
128
+          args += html
129
+        else:
130
+          args.append(html)
131
+
132
+        #create temp file to write to
133
+        out = tempfile.NamedTemporaryFile(delete=False)
134
+        out.close()
135
+        args.append(out.name) #append output file name
136
+
137
+        self.log.debug('Calling {0} with arguments <'.format(wkhtmltopdf))
138
+        self.log.debug('{0} >'.format(args))
139
+
140
+        if args[0] != wkhtmltopdf: args.insert(0, wkhtmltopdf) #prepend program name
141
+
142
+        ret = subprocess.check_call(args)
143
+        if ret != 0:
144
+            self.log.error('Called process error:')
145
+            self.log.error('Program returned exit code {0}'.
146
+                format(ret))
147
+            return 0
148
+        self.log.debug('Generated pdf {0}'.format(out.name))
149
+        return out.name
150
+
151
+    def preview(self, fileName):
152
+        """Opens a file using the default application. (Print preview)"""
153
+        if os.name == 'posix':
154
+            if sys.platform == 'darwin': #OS X
155
+                self.log.debug('Attempting to preview file {0} via open'
156
+                    .format(fileName))
157
+                ret = subprocess.call(['/usr/bin/open', fileName])
158
+                if ret != 0:
159
+                    self.log.error('open returned non-zero exit code {0}'.format(ret))
160
+                    return 1
161
+                return 0
162
+            elif sys.platform == 'linux2': #linux
163
+                try: #attempt to use evince-previewer instead
164
+                    self.log.debug('Attempting to preview file {0} with evince.'
165
+                        .format(fileName))
166
+                    ret=subprocess.Popen(('/usr/bin/evince-previewer', fileName))
167
+                    if ret != 0:
168
+                        self.log.error('evince returned non-zero exit code {0}'
169
+                            .format(ret))
170
+                        return 1
171
+                    return 0
172
+                except OSError:
173
+                    self.log.debug('evince unavailable.')
174
+
175
+                self.log.debug('Attempting to preview file {0} via xdg-open'
176
+                    .format(fileName))
177
+                ret = subprocess.call(('/usr/bin/xdg-open', fileName))
178
+                if ret != 0:
179
+                    self.log.error('xdg-open returned non-zero exit code {0}'.format(ret))
180
+                    return 1
181
+                return 0
182
+            else:
183
+                self.log.warning('Unable to determine default viewer for POSIX platform {0}'.format(sys.platform))
184
+                self.log.warning('Please file a bug and attach a copy of this log.')
185
+
186
+        if os.name == 'nt': #UNTESTED
187
+            self.log.debug('Attempting to preview file {0} via win32.startfile'
188
+                .format(fileName))
189
+            os.startfile(filepath)
190
+            return 0
191
+
192
+    def printout(self, filename, printer=None, orientation=None):
193
+        if os.name == 'posix' or os.name == 'mac':  #use CUPS/lpr
194
+            self.con.printout(filename, printer, orientation)
195
+
196
+    #---- printing proxy methods -----
197
+
198
+    def listPrinters(self):
199
+        """Returns list of names of available system printers (proxy)."""
200
+        return self.con.listPrinters()
201
+
202
+    def getPrinters(self):
203
+        """
204
+        Returns dictionary of printer name, description, location, and URI (proxy)
205
+        """
206
+        return self.con.getPrinters()
207
+
208
+class Nametag:
209
+    def __init__(self, barcode=False):
210
+        """
211
+        Format nametags with data using available HTML templates.  Will
212
+        fetch barcode encoding options from global config; otherwise accepts
213
+        barcode=False as the initialization argument.
214
+        """
215
+        #TODO: source config options for barcode, etc. from global config.
216
+        self.barcodeEnable = barcode
217
+        self.log = logging.getLogger(__name__)
218
+
219
+        #Setup and compile regex replacements:
220
+        self.date_re = re.compile(r'%DATE%', re.IGNORECASE)       #date
221
+        self.time_re = re.compile(r'%TIME%', re.IGNORECASE)       #time
222
+        self.room_re = re.compile(r'%ROOM%', re.IGNORECASE)       #room
223
+        self.first_re = re.compile(r'%FIRST%', re.IGNORECASE)     #name
224
+        self.name_re = re.compile(r'%NAME%', re.IGNORECASE)     #name
225
+        self.last_re = re.compile(r'%LAST%', re.IGNORECASE)       #surname
226
+        self.medical_re = re.compile(r'%MEDICAL%', re.IGNORECASE) #medical
227
+        self.code_re = re.compile(r'%CODE%', re.IGNORECASE)       #paging code
228
+        self.secure_re = re.compile(r'%S%', re.IGNORECASE)        #security code
229
+        self.title_re = re.compile(r'%TITLE%', re.IGNORECASE)        #security code
230
+        self.number_re = re.compile(r'%NUMBER%', re.IGNORECASE)        #security code
231
+        self.level_re = re.compile(r'%LEVEL%', re.IGNORECASE)        #security code
232
+        self.age_re = re.compile(r'%AGE%', re.IGNORECASE)         #age
233
+
234
+        self.medicalIcon_re = re.compile(
235
+            r"window\.onload\s=\shide\('medical'\)\;", re.IGNORECASE)
236
+        self.volunteerIcon_re = re.compile(
237
+            r"window\.onload\s=\shide\('volunteer'\)\;", re.IGNORECASE)
238
+
239
+    def listTemplates(self, directory=nametags):
240
+        """Returns a list of the installed nametag themes"""
241
+        #TODO: add more stringent validation against config and html.
242
+        directory = os.path.abspath(directory)
243
+        self.log.debug("Searching for html templates in {0}".format(directory))
244
+        try:
245
+            resource = os.listdir(directory)
246
+        except OSError as e:
247
+            logger.error('({0})'.format(e))
248
+            return []
249
+
250
+        themes = []
251
+        #Remove any files from themes[] that are not directories:
252
+        for i in resource:
253
+            if os.path.isdir(os.path.join(directory, i)):
254
+                themes.append(i)
255
+
256
+        valid = []
257
+        #Check that each folder has the corresponding .conf file:
258
+        for i in themes:
259
+            if os.path.isfile(self._getTemplateFile(i)):
260
+                valid.append(i)
261
+
262
+        valid.sort()
263
+        del resource, themes
264
+        self.log.debug("Found templates {0}".format(valid))
265
+        return valid
266
+
267
+    def _getTemplateFile(self, theme, directory=nametags):
268
+        """
269
+        Returns full path to .conf file for an installed template pack and
270
+        check it exists.
271
+        """
272
+        directory = os.path.abspath(directory)
273
+        path = os.path.join(directory, theme, '{0}.conf'.format(theme))
274
+        #TODO: more stringent checks (is there a matching HTML file?)
275
+        if os.path.isfile(path):
276
+            return path
277
+        else:
278
+            self.log.warning("{0} does not exist or is not file.".format(path))
279
+
280
+    def _getTemplatePath(self, theme, directory=nametags):
281
+        """Returns absolute path only of template pack"""
282
+        return os.path.join(directory, theme)
283
+
284
+
285
+    def readConfig(self, theme):
286
+        """
287
+        Reads the configuration for a specified template pack. Returns
288
+        dictionary.
289
+        """
290
+        inifile = self._getTemplateFile(theme)
291
+        self.log.info("Reading template configuration from '{0}'"
292
+            .format(inifile))
293
+        config = ConfigObj(inifile)
294
+        try: #check for default section
295
+            default = config['default']
296
+        except KeyError as e:
297
+            self.log.error(e)
298
+            self.log.error("{0} contains no [default] section and is invalid."
299
+                .format(inifile))
300
+            raise KeyError("{0} contains no [default] section and is invalid."
301
+                .format(inifile))
302
+        del default
303
+        return config
304
+
305
+    def nametag(self, template='apis', name='', number='', title='', level='', age='', barcode=False):
306
+    #def nametag(self, template='default', room='', first='', last='', medical='',
307
+    #            code='', secure='', barcode=True):
308
+
309
+        #Check that theme specified is valid:
310
+        if template != 'default':
311
+            themes = self.listTemplates()
312
+            if template not in themes:
313
+                self.log.error("Bad theme specified.  Using default instead.")
314
+                template = 'default'
315
+        #Read in the HTML from template.
316
+        try:
317
+            directory = self._getTemplatePath(template)
318
+        except KeyError:
319
+            self.log.error("Unable to process template '{0}'. Aborting."
320
+                .format(template))
321
+            return None
322
+
323
+        #read in the HTML
324
+        self.log.debug('Generating nametag with nametag()')
325
+        self.log.debug('Reading {0}...'.format(os.path.join(directory,
326
+            'default.html')))
327
+        f = open(os.path.join(directory, 'default.html'))
328
+        html = f.read()
329
+        f.close()
330
+
331
+        if len(html) == 0: #template file is empty: use default instead.
332
+            self.log.warn('HTML template file {0} is blank.'.format(
333
+                          os.path.join(directory, 'default.html')))
334
+
335
+        #generate barcode of secure code, code128:
336
+        if barcode:
337
+            try:
338
+                if secure == '':
339
+                    codebar.gen('code128', os.path.join(directory,
340
+                        'default-secure.png'), code)
341
+                else:
342
+                    codebar.gen('code128', os.path.join(directory,
343
+                        'default-secure.png'), secure)
344
+            except NotImplementedError as e:
345
+                self.log.error('Unable to generate barcode: {0}'.format(e))
346
+                html = html.replace(u'default-secure.png', u'white.gif')
347
+        else:
348
+            #replace barcode image with white.gif
349
+            html = html.decode('utf-8').replace(u'default-secure.png', u'white.gif')
350
+            self.log.debug("Disabled barcode.")
351
+
352
+        #get the current date/time
353
+        now = datetime.datetime.now()
354
+
355
+        #Perform substitutions:
356
+        #html = self.date_re.sub(now.strftime("%a %d %b %Y"), html)
357
+        html = self.date_re.sub(now.strftime("%a<br>%Y-%m-%d"), html)
358
+        html = self.time_re.sub(now.strftime("%H:%M:%S"), html)
359
+        #Fix for if database returns None instead of empty string:
360
+        html = self.name_re.sub(name.encode('utf-8'), html.encode('utf-8'))
361
+        html = self.level_re.sub(str(level), html)
362
+        html = self.title_re.sub(str(title), html)
363
+        html = self.number_re.sub(str(number), html)
364
+        html = self.age_re.sub(str(age), html)
365
+
366
+        return html
367
+
368
+class _DummyPrinter:
369
+    def __init__(self):
370
+        self.con = None
371
+    def listPrinters(self):
372
+        return []
373
+    def getPrinters(self):
374
+        return {}
375
+    def returnDefault(self):
376
+        return ''
377
+    def printout(self, filename, printer=None, orientation=None):
378
+        raise PrinterError('No printer system available')
379
+
380
+class _CUPS:
381
+    def __init__(self):
382
+        self.log = logging.getLogger(__name__)
383
+        self.log.info("Connecting to CUPS server on localhost...")
384
+        try:
385
+            import cups
386
+        except ImportError as e:
387
+            self.log.error("CUPS module not available.  Is CUPS installed? {0}".format(e))
388
+        self.con = cups.Connection()
389
+
390
+    def listPrinters(self):
391
+        """
392
+        Returns a list of the names of available system printers.
393
+        """
394
+        return self.con.getPrinters().keys()
395
+
396
+    def getPrinters(self):
397
+        """
398
+        Returns dictionary of printer name, description, location, and URI.
399
+        """
400
+        a = dict()
401
+        printers = self.con.getPrinters()
402
+        for item in printers:
403
+            info = printers[item]['printer-info']
404
+            location = printers[item]['printer-location']
405
+            uri = printers[item]['device-uri']
406
+            a[item] = { 'info' : info, 'location' : location, 'uri' : uri}
407
+        return a
408
+
409
+    def getDefault(self):
410
+        """Determines the user's default system or personal printer."""
411
+        return self.con.getDests()[None, None].name
412
+
413
+    def printout(self, filename, printer=None, orientation=None):
414
+        if printer != None: #set printer; if none, then use default (leave argument blank)
415
+            if printer not in self.listPrinters():
416
+                raise PrinterError('Specified printer is not available on this system')
417
+            printArgs = ['-P', printer]
418
+            printArgs = []
419
+        else:
420
+            printArgs = []
421
+
422
+        if orientation: #set orientation option
423
+            if orientation not in ['landscape', 'portrait']:
424
+                raise PrinterError('Bad orientation specification: {0}'.format(orientation))
425
+            #printArgs.append('-o')
426
+            #printArgs.append(orientation)
427
+
428
+        try: #see if file exists
429
+            f = open(filename)
430
+        except IOError:
431
+            raise PrinterError('The specified file does not exist: {0}'.format(filename))
432
+        else:
433
+            f.close()
434
+        printArgs.append("-o")
435
+        printArgs.append("2.00x1.00\"")
436
+        printArgs.append(filename) #append file to print.
437
+
438
+        ret = subprocess.check_call([lpr,] + printArgs)
439
+        if ret != 0:
440
+            raise PrinterError('{0} exited non-zero ({1}).  Error spooling.'.format(lpr, ret))
441
+
442
+
443
+class PrinterError(Exception):
444
+    def __init__(self, value=''):
445
+        if value == '':
446
+            self.error = 'Generic spooling error.'
447
+        else:
448
+            self.error = value
449
+    def __str__(self):
450
+        return repr(self.error)
451
+
452
+
453
+class Main:
454
+    def __init__(self, local=False):
455
+        self.log = logging.getLogger(__name__)
456
+        self.con = Printer(False)
457
+        self.tag = Nametag(True)
458
+        self.section = ''
459
+
460
+    def nametag(self, theme='apis', name='', number='', title='', level='', barcode=False, section='default'):
461
+    #def nametag(self, theme='default', room='', first='', last='', medical='',
462
+    #            code='', secure='', barcode=True, section='default'):
463
+        """Note: section= not fully implemented in Nametag.nametag method"""
464
+        self.section = section
465
+        self.conf = self.tag.readConfig(theme) #theme
466
+        self.args = self.con.buildArguments(self.conf, section) #section
467
+
468
+        stuff = self.tag.nametag(name=name, number=number, title=title, template=theme, level=level)
469
+        temp_path = self.tag._getTemplatePath(theme)
470
+        html = tempfile.NamedTemporaryFile(delete=False, dir=temp_path, suffix='.html')
471
+        html.write(stuff.encode('utf-8'))
472
+        html.close()
473
+
474
+        self.pdf = self.con.writePdf(self.args, html.name)
475
+        os.unlink(html.name)
476
+        return self.pdf
477
+
478
+    def nametags(self, tags, theme='apis', section='default'):
479
+        self.section = section
480
+        self.conf = self.tag.readConfig(theme) #theme
481
+        self.args = self.con.buildArguments(self.conf, section) #section
482
+
483
+        html_files = []
484
+        for data in tags:
485
+            stuff = self.tag.nametag(
486
+                    name=data['name'], number=data['number'],
487
+                    title=data['title'], template=theme, level=data['level'],
488
+                    age=data['age']
489
+            )
490
+            temp_path = self.tag._getTemplatePath(theme)
491
+            html = tempfile.NamedTemporaryFile(delete=False, dir=temp_path, suffix='.html')
492
+            html.write(stuff)
493
+            html.close()
494
+            html_files.append(html.name)
495
+
496
+        self.pdf = self.con.writePdf(self.args, html_files)
497
+        for tmpname in html_files:
498
+            #os.unlink(tmpname)
499
+            pass
500
+        return self.pdf
501
+
502
+
503
+    def preview(self, filename=None):
504
+        if filename == None:
505
+            filename = self.pdf
506
+        self.con.preview(filename)
507
+
508
+    def printout(self, filename=None, printer=None, orientation=None):
509
+        if filename == None:
510
+            filename = self.pdf
511
+        if orientation == None:
512
+            orientation = self.conf[self.section]['orientation']
513
+        self.con.printout(filename, printer, orientation)
514
+
515
+    def cleanup(self, trash=None, *dt):
516
+        if trash != None:
517
+            for item in trash:
518
+                os.unlink(item)
519
+        else:
520
+            os.unlink(self.pdf)
521
+        self.section = ''
522
+
523
+if __name__ == '__main__':
524
+
525
+    tags =  [
526
+            { 'name' : "Barkley Woofington", 'number' : "S-6969", 'level' : "Top Dog", 'title' : '' , 'age' : 20},
527
+            { 'name' : "Rechner Foxer", 'number' : "S-0001", 'level' : "Foxo", 'title' : '' , 'age' : 12}
528
+    ]
529
+
530
+    con = Main(False)
531
+    con.nametags(tags, theme='apis')
532
+    #con.nametag(theme='apis', name="Some Kind Of Horse", number="S-0000", title="Staff", level="Player")
533
+
534
+    print(con.pdf)
535
+    con.preview()
536
+    #con.printout(printer="LabelWriter-450-Turbo")
537
+
538
+    raw_input(">")
539
+    con.cleanup()

+ 9
- 0
resources/nametag/date/date.conf View File

@@ -0,0 +1,9 @@
1
+[default]
2
+zoom = 1  #;zoom factor
3
+height = 50  #;size of page in mm, while in portrait mode
4
+width = 24
5
+left = 0
6
+top = 0
7
+bottom = 0
8
+right = 0
9
+orientation = landscape

+ 31
- 0
resources/nametag/date/default.html View File

@@ -0,0 +1,31 @@
1
+<!doctype html>
2
+<html>
3
+<head>
4
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
5
+    <title></title>
6
+    <style>
7
+      body {
8
+        font-family: sans;
9
+      }
10
+      .container {
11
+        width: 100%;
12
+      }
13
+
14
+      .date {
15
+        font-size: 30px;
16
+        font-weight: bold;
17
+        text-align: center;
18
+      }
19
+
20
+      .sub {
21
+        text-align: center;
22
+      }
23
+    </style>
24
+</head>
25
+<body>
26
+  <div class="container">
27
+    <div class="sub">In Fridge Since</div>
28
+    <div class="date">%DATE%</div>
29
+  </div>
30
+</body>
31
+</html>