Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
  • 10 commits
  • 8 files changed
  • 0 commit comments
  • 2 contributors
Commits on Nov 09, 2009
@jpf jpf Adding information on which labels we use
f14f604
Commits on Feb 19, 2011
@stighackvan stighackvan added test script to display output instead of printing it (apt-get i…
…nstall qiv)
7031462
@stighackvan stighackvan a bit of refactoring in print_badge, qr-codes added to code (apt-get …
…python-vobject qrencode)
31ad004
@stighackvan stighackvan imperfect version of word wrapping code...misses the wrap when there …
…is just one space, but does great with many spaces...improvement on the way
f080403
@stighackvan stighackvan code cleanup in the word-wrapping code...
431f05e
Commits on Mar 19, 2011
@stighackvan stighackvan disable HTTPS server by default (no certs in github code, so it fails…
… by default)
1a995b2
@stighackvan stighackvan use MECARD instead of VCARD for better qrcodes, dont try to generate …
…qr_codes unless specified in json config, document label media, stretch background.png to 760px wide
2b4d6c6
@stighackvan stighackvan allow simple integer addition in json config files to simplify editin…
…g of print_badge.config.json, tweak badge layout to accommodate qr_codes and larger PNG canvas
f0a915c
@stighackvan stighackvan re-enabled HTTPS code in core.py, but wrapped it in a try ... except …
…and added a print statement to suggest ^D if HTTPS is not needed/wanted...
b7c9427
Commits on Jan 15, 2013
@jpf jpf PEP8
12216a2
View
3  .gitignore
@@ -0,0 +1,3 @@
+*.pyc
+*~
+badge/completed/*.png
View
1  badge/configuration/README
@@ -0,0 +1 @@
+The badge configuration contained in print_badge.config.json assumes that the printer is stocked with "DYMO 30324 White Diskette/Name Badge Label" labels: http://www.amazon.com/DYMO-30324-White-Diskette-Badge/dp/B00002QUKX/
View
BIN  badge/configuration/background.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
59 badge/configuration/print_badge.config.json
@@ -5,58 +5,71 @@
"temporary_dir" : "/tmp/"
},
"badge" : {
- "height" : 464,
- "width" : 600,
+ "comment" : "dymo diskette labels have a printable area of 2.10in x 2.54in @ 300 dpi = 630 x 762px",
+ "height" : 630,
+ "width" : 760,
"file" : "badge.png",
"font_path" : "./configuration/fonts/",
"remove_accents" : true,
"color" : [255, 255, 255],
- "design" : ["background","sponsor","shdh_number","first_name","last_name","tags"]
+ "design" : ["background", "sponsor", "qr_code", "shdh_number","first_name","last_name","tags"]
},
"background" : {
"file" : "./configuration/background.png",
+ "comment" : "configuration/background.png PNG 770x60 770x60+0+0 8-bit DirectClass 4.82KB 0.000u 0:00.000",
"x" : 0,
"y" : 0
},
- "sponsor" : {
- "file" : "./configuration/sponsor.png",
- "x" : 390,
- "y" : 344
- },
"shdh_number" : {
"height" : 110,
"width" : 110,
"string" : "42",
"font" : "MyriadPro-Bold.otf",
"color" : [255, 255, 255],
- "x" : 495,
+ "x" : 625,
"y" : -5
},
"first_name" : {
- "height" : 160,
- "width" : 580,
+ "height" : 200,
+ "width" : 740,
"string" : "",
- "font" : "MyriadPro-Semibold.otf",
+ "font" : "MyriadPro-BoldCond.otf",
"color" : [0, 0, 0],
- "x" : 20,
+ "x" : 10,
"y" : 70
},
"last_name" : {
- "height" : 60,
- "width" : 570,
+ "height" : 90,
+ "width" : 540,
"string" : "",
- "font" : "MyriadPro-Regular.otf",
+ "font" : "MyriadPro-Semibold.otf",
"color" : [0, 0, 0],
- "x" : 30,
+ "x" : 10,
"y" : null
},
"tags" : {
- "height" : 35,
- "width" : 590,
"string" : "",
- "font" : "MyriadPro-BoldCond.otf",
+ "wraptext" : 1,
+ "font" : "MyriadPro-Regular.otf",
"color" : [0, 0, 0],
"x" : 10,
- "y" : 305
- }
-}
+ "y" : 350,
+ "height" : 620-350,
+ "width" : 740-240
+ },
+ "sponsor" : {
+ "file" : "./configuration/sponsor.png",
+ "height" : 380-270,
+ "width" : 240,
+ "x" : 760-240,
+ "y" : 270
+ },
+ "qr_code" : {
+ "comment" : "qrencode --size=5 :: 88 char MECARD =~ PNG 185x185, 140 char MECARD =~ PNG 225x225",
+ "string" : "",
+ "height" : 240,
+ "width" : 240,
+ "x" : 760-240,
+ "y" : 390
+ }
+}
View
316 badge/print_badge
@@ -29,115 +29,273 @@
# OTHER DEALINGS IN THE SOFTWARE.
#
# TODO:
-# - Give helpful error messages when the configuration file is missing parts that we expect.
+# - Give helpful error messages when the configuration file is
+# missing parts that we expect.
# - Add fancypants python documentation
# - Add a library or wrapper for CUPS, send the PNG directly to CUPS
-# - Get the job ID from CUPS, wait for the CUPS job to complete before exiting, fail if the CUPS job fails
+# - Get the job ID from CUPS,
+# wait for the CUPS job to complete before exiting,
+# fail if the CUPS job fails
#
-import gd, os, sys, subprocess, getopt, unicodedata, time, simplejson as json
+import joelgd as gd
+#import gd
+import os
+import sys
+import subprocess
+import getopt
+import unicodedata
+import simplejson as json
+
# set the working directory to the actual path of the script
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
CONFIGURATION_FILE = './configuration/print_badge.config.json'
+
+def print_image(file):
+ # NOTE: we print with -o fitplot because the pngs we create
+ # are 72dpi but the printer is 300dpi. We could also
+ # use imageMagick to change DPI:
+ # 'convert foo.png -density 300 foo2.png'
+ # Dymo note: the diskette labels we use have a printable area of:
+ # 2.10in x 2.54in @ 300 dpi, that's 630 x 762
+ print_file = ('lp -d DYMO-LabelWriter-400 '
+ '-o landscape -o fitplot'.split() + [file])
+ # print_file = ('lp -H test -d DYMO_LabelWriter_400_USB_1 '
+ # '-o landscape -o fitplot'.split() + [file])
+
+ # n:"no print", q:"quick view"
+ opts, args = getopt.getopt(sys.argv[1:], "nq")
+ if opts and '-n' in opts[0]:
+ print "Not executing command:", print_file
+ elif opts and '-q' in opts[0]:
+ try:
+ subprocess.call(['qiv', file])
+ except:
+ print "can't find quick image viewer: apt-get install qiv"
+ else:
+ subprocess.call(print_file)
+
+
+def wrapstring(string, nlines):
+ if (nlines == 1):
+ return string
+ # find index of first wrapping point in string
+ opt_wrap_pt = len(string)/nlines
+ prev_space = string.rfind(' ', 0, opt_wrap_pt) # -1 if not found
+ next_space = string.find(' ', opt_wrap_pt) # -1 if not found
+ # choose the space that is closest to the optimal wrapping point
+ if (next_space > 0) and ((opt_wrap_pt - prev_space) >
+ (next_space - opt_wrap_pt)):
+ split_point = next_space
+ else:
+ split_point = prev_space
+ if split_point < 0:
+ return string
+ ns = "%s\n%s" % (string[:split_point],
+ wrapstring(string[split_point+1:], nlines - 1))
+ #print nlines,':',ns,';'
+ #print ns
+ return ns
+
# Shorthand for image positions.
# B = Bottom, T = Top, L = Left, R = Right, X = x, Y = y
-(BL_X,BL_Y,BR_X,BR_Y,TL_X,TL_Y,TR_X,TR_Y) = (0,1,2,3,4,5,6,7)
+(BL_X, BL_Y, BR_X, BR_Y, TL_X, TL_Y, TR_X, TR_Y) = (0, 1, 2, 3, 4, 5, 6, 7)
-def print_image(file):
- print_file = 'lp -d DYMO_LabelWriter_400_USB_1 -o landscape -o fitplot'.split() + [file]
- # print_file = 'lp -H test -d DYMO_LabelWriter_400_USB_1 -o landscape -o fitplot'.split() + [file]
- opts, args = getopt.getopt(sys.argv[1:], "n") # "no print"
- if opts and '-n' in opts[0]:
- print "Not executing command:",print_file
- else:
- subprocess.call(print_file)
+def get_ideal_text_size(im, width, height, font, text):
+ size = 100 # arbitrary initial value, scaled to fit below
+ print "get_bounding_rect(", font, size, 0, (0, 0), text
+ p = im.get_bounding_rect(font, size, 0, (0, 0), text)
+ print "get_bounding_rect results: ", p
+ width_ratio = (float(width) / float(p[BR_X] - p[BL_X]))
+ height_ratio = (float(height) / float(p[BR_Y] - p[TR_Y]))
+ return_size = size * min(width_ratio, height_ratio)
+ #print "\ntext:", text, "\nsize:", return_size
+ return return_size
+
# Implement next:
# - Margins: top, bottom, left, right
# - Alignment: left, right, center
-def place(image,(x,y),item):
- input = {}
- input['x'],input['y'],(input['width'],input['height']) = x,y,image.size()
-
- for val in ["x","y","width","height"]:
- try: item[val]
- except KeyError: pass
- if item[val]: input[val] = item[val]
-
- color = image.colorAllocate(item["color"])
- im = gd.image((input["width"],input["height"]))
-
- x,y = 0,0 # since we are making a temporary image, set these to origin
-
- # one pass calculation for ideal string size!
- size = 100
- p = im.get_bounding_rect(item["font"],size,0,(x,y),item["string"])
- size = min(
- (float(input["width"]) / float(p[BR_X] - p[BL_X]) ) * size, # width ratio
- (float(input["height"]) / float(p[BR_Y] - p[TR_Y])) * size # height ratio
- )
- p = im.get_bounding_rect(item["font"],size,0,(x,y),item["string"])
- y += p[TR_Y]*-1
-
- rv = image.string_ttf(item["font"],size,0,(input["x"],input["y"] + y),item["string"],color)
- return (input["x"],rv[BR_Y])
+def place_text(image, (x, y), item):
+ input = {}
+ input['x'] = x
+ input['y'] = y
+ input['wraptext'] = 0
+ (input['width'], input['height']) = image.size()
+ for val in ["x", "y", "width", "height", "wraptext"]:
+ try:
+ item[val]
+ except KeyError:
+ continue
+ if item[val]:
+ input[val] = item[val]
-def main():
- configuration_file = open(CONFIGURATION_FILE)
- conf = json.load(configuration_file)
+ color = image.colorAllocate(item["color"])
- # read card data in from stdin
- stdin = sys.stdin.read()
- card = json.loads(stdin)
+ bb_wid = input["width"]
+ bb_ht = input["height"]
+ font = item["font"]
+ text = item["string"]
- # merge in the card data we just got from STDIN into the default configuration
- for key,value in card.iteritems():
- if conf.has_key(key):
- conf[key]["string"] = value
+ if input['wraptext']:
+ best_size = -1
+ nlines = 1
+ # if the line of text is too wide, it'll be shrunk to fit.
+ # when we split it, the font size will increase until height
+ # becomes the dominating constraint, so we keep wrapping long as
+ # the font size keeps increasing
+ while 1:
+ wraptext = wrapstring(text, nlines)
+ size = get_ideal_text_size(image, bb_wid, bb_ht, font, wraptext)
+ if size <= best_size:
+ break
+ best_size = size
+ best_text = wraptext
+ nlines += 1
- conf["badge"]["file"] = ''.join(('./completed/',card["key"],"_shdh",card["shdh_number"],'.png'))
+ size = best_size
+ text = best_text
+ else:
+ size = get_ideal_text_size(image, bb_wid, bb_ht, font, text)
- try: conf["badge"]["remove_accents"]
- except NameError: conf["badge"]["remove_accents"] = None
+ p = image.get_bounding_rect(font, size, 0, (0, 0), text)
+ y = p[TR_Y]*-1
- os.environ["GDFONTPATH"] = conf["badge"]["font_path"]
-
- im = gd.image((conf["badge"]["width"],conf["badge"]["height"]))
- white = im.colorAllocate(conf["badge"]["color"])
+ str_x = input["x"]
+ str_y = input["y"] + y
+ rv = image.string_ttf(font, size, 0, (str_x, str_y), text, color)
+ return (input["x"], rv[BR_Y])
- x,y = 0,0
- for item in conf["badge"]["design"]:
- # There has to be a better way to test for if a variable is defined
- try: conf[item]["string"]
- except KeyError: conf[item]["string"] = None
+def make_qrcode_png(qrfile, fname, lname, email, note):
+ #print "qrmake: %s %s %s %s %s" % (qrfile,fname,lname,email,note)
- try: conf[item]["file"]
- except KeyError: conf[item]["file"] = None
+ # vcard format is too verbose...
+ # especially the rigorous version created by python's vobject.vCard()
+ #import vobject
+ #j = vobject.vCard()
+ #j.add('n')
+ #j.n.value = vobject.vcard.Name(given=fname, family=lname)
+ #j.add('fn')
+ #j.fn.value = fname + " " + lname
+ #j.add('email')
+ #j.email.value = email
+ #j.email.type_param = 'INTERNET'
+ #j.add('note')
+ #j.note.value = note
- if conf[item]["string"]:
- if conf["badge"]["remove_accents"]:
- conf[item]["string"] = unicodedata.normalize('NFKD', conf[item]["string"]).encode('ascii','ignore')
- (x,y) = place(im,(x,y),conf[item])
- elif conf[item]["file"]:
- temporary = gd.image(conf[item]["file"])
- temporary.copyTo(im,(conf[item]["x"],conf[item]["y"]))
- else:
- print "ERROR"
+ #bad_text = j.serialize()
+ ## android's barcode reader app doesn't like commas to be escaped
+ #good_text = bad_text.replace("\\,", ",")
+
+ #print "qr.vc: %d, %s\n" % (len(good_text), good_text)
+
+ #subprocess.call(['qrencode', '--size=3', '-m0', '-o', qrfile , good_text])
+ #subprocess.call(['identify', qrfile])
+
+ mecard = "MECARD:N:%s,%s;EMAIL:%s;NOTE:%s;;" % (
+ lname.replace(',', ''),
+ fname.replace(',', ''),
+ email,
+ note.replace(';', ','))
+
+ subprocess.call(['qrencode', '--size=5', '-m0', '-o', qrfile, mecard])
+ print "qr.me: %d, %s\n" % (len(mecard), mecard)
+ subprocess.call(['identify', qrfile])
+
+
+def main():
+ configuration_file = open(CONFIGURATION_FILE)
+ conf = json.load(configuration_file)
+
+ # read card data in from stdin
+ stdin = sys.stdin.read()
+ card = json.loads(stdin)
+
+ # merge in the card data we just got from STDIN
+ # into the default configuration
+ for key, value in card.iteritems():
+ if key in conf:
+ conf[key]["string"] = value
+
+ filename = "./completed/%s_shdh%s" % (card["key"], card["shdh_number"])
+ conf["badge"]["file"] = "%s.png" % filename
+
+ if 'qr_code' in conf["badge"]["design"]:
+ try:
+ qrfile = "%s.qrcode.png" % filename
+ make_qrcode_png(qrfile,
+ card['first_name'],
+ card['last_name'],
+ card['key'],
+ 'SHDH %(shdh_number)s -- %(tags)s' % card)
+ conf["qr_code"]["file"] = qrfile
+ except:
+ message = ("oh well, no qr code... "
+ "try: 'apt-get install qrencode' \n python says: ")
+ print message, sys.exc_info()
+
+ try:
+ conf["badge"]["remove_accents"]
+ except KeyError:
+ conf["badge"]["remove_accents"] = None
+
+ os.environ["GDFONTPATH"] = conf["badge"]["font_path"]
+
+ im = gd.image((conf["badge"]["width"], conf["badge"]["height"]))
+ print im
+ im.colorAllocate(conf["badge"]["color"])
+
+ x, y = 0, 0
- badge=open(conf["badge"]["file"],"w")
- im.writePng(badge)
- badge.close()
+ for item in conf["badge"]["design"]:
+ # There has to be a better way to test for if a variable is defined
+ try:
+ conf[item]["string"]
+ except KeyError:
+ conf[item]["string"] = None
+ try:
+ conf[item]["file"]
+ except KeyError:
+ conf[item]["file"] = None
- print_image(conf["badge"]["file"])
+ if conf[item]["file"]:
+ temporary = gd.image(conf[item]["file"])
+ ix, iy = conf[item]["x"], conf[item]["y"]
+ try:
+ iw, ih = conf[item]["width"], conf[item]["height"]
+ (tw, th) = temporary.size()
+ if (tw > iw) or (th > ih):
+ #### it would be better to preserve the aspect ratio
+ print "ooops, sloppy shrinking of image in progress..."
+ temporary.copyResizedTo(im, (ix, iy), (0, 0), (iw, ih))
+ else:
+ newx = ix + ((iw - tw) / 2)
+ newy = iy + ((ih - th) / 2)
+ temporary.copyTo(im, (newx, newy))
+ except KeyError:
+ temporary.copyTo(im, (ix, iy))
+ elif conf[item]["string"]:
+ if conf["badge"]["remove_accents"]:
+ normalized = unicodedata.normalize('NFKD',
+ conf[item]["string"])
+ as_ascii = normalized.encode('ascii', 'ignore')
+ conf[item]["string"] = as_ascii
+ (x, y) = place_text(im, (x, y), conf[item])
+ else:
+ print "ERROR"
+ badge = open(conf["badge"]["file"], "w")
+ im.writePng(badge)
+ badge.close()
+ print_image(conf["badge"]["file"])
if __name__ == "__main__":
-# t1 = time.time()
- main()
-# t2 = time.time()
-# print 'took %0.3f ms' % ((t2-t1)*1000.0)
+ #t1 = time.time()
+ main()
+ #t2 = time.time()
+ #print 'took %0.3f ms' % ((t2-t1)*1000.0)
View
6 badge/simplejson/scanner.py
@@ -9,7 +9,7 @@
__all__ = ['make_scanner']
NUMBER_RE = re.compile(
- r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
+ r'(-?(?:0|[1-9]\d*))(\s*[-+]\s*\d+)?(\.\d+)?([eE][-+]?\d+)?',
(re.VERBOSE | re.MULTILINE | re.DOTALL))
def py_make_scanner(context):
@@ -45,9 +45,11 @@ def _scan_once(string, idx):
m = match_number(string, idx)
if m is not None:
- integer, frac, exp = m.groups()
+ integer, plusminus, frac, exp = m.groups()
if frac or exp:
res = parse_float(integer + (frac or '') + (exp or ''))
+ elif plusminus:
+ res= eval(integer+plusminus);
else:
res = parse_int(integer)
return res, m.end()
View
35 badge/test
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+
+echo == wrap two lines, more qr:
+
+./print_badge -q <<END
+{
+ "first_name": "J Random",
+ "last_name": "Hacker",
+ "tags": "Haskell, ForGreatGood",
+ "shdh_number": "42",
+ "key": "jrandom@gmail.com",
+ "badge_icons": null,
+ "attended_shdh_42": 1229171859,
+ "event_key": "shdh_42"
+}
+END
+identify completed/*qrcode.png
+
+echo ''
+echo == wrap three lines, even more qr:
+
+./print_badge -q <<END
+{
+ "first_name": "J Random",
+ "last_name": "Hacker",
+ "tags": "Python, Haskell, QR Codes in devhouse badge code, schmoozing and flirting",
+ "shdh_number": "42",
+ "key": "jrandom@gmail.com",
+ "badge_icons": null,
+ "attended_shdh_42": 1229171859,
+ "event_key": "shdh_42"
+}
+END
+identify completed/*qrcode.png
View
8 core.py
@@ -158,9 +158,13 @@ def onAttend(key):
if os.environ.get('INSECURE',False):
reactor.listenTCP(10081, server.Site(sroot))
- reactor.listenSSL(10443, server.Site(sroot), \
+ print "just type ^D if you don't want to use the HTTPS port...it's optional..."
+ try:
+ reactor.listenSSL(10443, server.Site(sroot), \
secure.ServerContextFactory(myKey='certs/server.pem', trustedCA='certs/certificate_authority/shdh-ca.pem'))
-
+ except:
+ print "couldn't start SSL server... missing certs/server.pem???\n python says:", sys.exc_info()
+
reactor.listenTCP(10080, server.Site(iroot))
log.msg("It's a piece of cake to break a pretty snake. [SYSTEM ONLINE]")

No commit comments for this range

Something went wrong with that request. Please try again.