├── .gitignore
├── README.md
├── badgeBot.py
├── badges.jpg
├── data.csv
└── images
├── ___page.png
├── ___page.svg
├── animate.gif
├── available_space.png
├── blue_box.png
├── blue_box.svg
├── company.png
├── company.svg
├── final.png
├── final.svg
├── final_product.jpg
├── guidelines_blue_box.png
├── guidelines_blue_box.svg
├── guidelines_company.png
├── guidelines_company.svg
├── guidelines_name_0.png
├── guidelines_name_0.svg
├── guidelines_name_1.png
├── guidelines_name_1.svg
├── guidelines_name_2.png
├── guidelines_name_2.svg
├── guidelines_yellow_box.png
├── guidelines_yellow_box.svg
├── name_0.png
├── name_0.svg
├── name_1.png
├── name_1.svg
├── name_descender.png
├── name_size.png
├── name_split.png
├── name_vertical.01.png
├── name_vertical.01.svg
├── name_vertical.02.png
├── name_vertical.02.svg
├── page.png
├── page.svg
├── yellow_box.gif
├── yellow_box.png
└── yellow_box.svg
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BadgeBot
2 |
3 |
4 |
5 | The Typographics badges were made in less than a day using Drawbot, an excellent Python-based app for creating graphics. I automated them based on a static design by Nick Sherman. The design employs Stilla SH as well as a guest appearance by my in-progress typeface. This code is released under the MIT license.
6 |
7 | ## Process
8 |
9 |
In case it is helpful in learning Drawbot, below is a breakdown of my process. This process was originally written as part of TypeLab, a multi-day hackathon for type and typography held during Typographics 2015.
10 |
11 |
12 |
13 |
14 |
15 | ### Draw the backgrounds
16 |
17 | Draw a blue background measuring 4 by 3 inches.
18 |
19 | def drawBackground(w, h):
20 | """
21 | draw a rectangle that is a background
22 | """
23 | fillColor(backgroundColor)
24 | rect(0, 0, w, h)
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Draw a yellow box inside of it. Create jaggy edges by drawing a random number of points on each edge, and allowing the positions of those points to vary a certain amount within the space. No two jaggy-edged boxes are alike (compare the front and back of a name card to verify!)
35 |
36 | def drawFrame(frameWidth, frameHeight, numberOfPointsMin=4, numberofPointsMax=6, perpindicularRandomness=2, parallelRandomness=10):
37 | """
38 | make a box with random jagginess
39 | """
40 | # for goodness sake make these variables shorter
41 | pdr = perpindicularRandomness
42 | plr = parallelRandomness
43 | # get the number of segments to use
44 | segments = random.randrange(numberOfPointsMin, numberofPointsMax)
45 | # draw the path, one direction at a time. Loop through the segment, and.
46 | # we have to subtract from segments so we don't hit the shared corners twice.
47 | fillColor(frameColor)
48 | newPath()
49 | moveTo((0, 0))
50 | for i in range(segments-1):
51 | lineTo( ( 0+random.randrange(-pdr, pdr) , (i+1)*frameHeight/segments + random.randrange(-plr, plr) ) )
52 | lineTo((0, frameHeight))
53 | for i in range(segments-1):
54 | lineTo( ( (i+1)*frameWidth/segments + random.randrange(-plr, plr) , frameHeight+random.randrange(-pdr, pdr) ) )
55 | lineTo((frameWidth, frameHeight))
56 | for i in range(segments-2, 0, -1):
57 | lineTo( ( frameWidth+random.randrange(-pdr, pdr) , (i+1)*frameHeight/segments + random.randrange(-plr, plr) ) )
58 | lineTo((frameWidth, 0))
59 | for i in range(segments-2, 0, -1):
60 | lineTo( ( (i+1)*frameWidth/segments + random.randrange(-plr, plr) , 0+random.randrange(-pdr, pdr) ) )
61 | closePath()
62 | drawPath()
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | ### Calculate the margins
75 |
76 | Using preset margins, calculate the available space in the yellow box.
77 |
78 | # set the outside margin to 1/18 of the width, which will surround the frame
79 | outsideMargin = w/18
80 | frameWidth = w-outsideMargin*2
81 | frameHeight = h-outsideMargin*2
82 |
83 | # set the box margins to 1/24 of the width, which will surround the box of text
84 | topMargin = bottomMargin = leftMargin = rightMargin = w/24
85 | boxWidth = frameWidth - leftMargin - rightMargin
86 | boxHeight = frameHeight - topMargin - bottomMargin
87 |
88 | # draw the background
89 | if background:
90 | drawBackground(w, h)
91 |
92 | # move ourselves to the frame margin, and draw the frame
93 | translate(outsideMargin, outsideMargin)
94 | drawFrame(frameWidth, frameHeight)
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | If the participant has provided a company name, allot some available space for that at the bottom.
106 |
107 | if company:
108 | drawCompany(company, companySize, frameWidth, companyHeight)
109 | boxHeight -= companyHeight
110 | bottomMargin += companyHeight
111 | else:
112 | boxHeight -= companyHeight / 3
113 | bottomMargin += companyHeight / 3
114 |
115 | Define a function that prints the company name.
116 |
117 | def drawCompany(company, companySize, companyWidth, companyHeight):
118 | """
119 | draw the company name, easy peasy.
120 | """
121 | font(companyFont, companySize)
122 | fillColor(backgroundColor)
123 | textBox(company, (0, 0, companyWidth, companyHeight), align='center')
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | ### Figure out the name
135 |
136 | Ignore the badge layout for a moment and focus on the name. Split the name into lines, using spaces and hyphens as delimiters. Get rid of the spaces, but don’t get rid of the hyphens.
137 |
138 | def getLinesFromName(name):
139 | """
140 | Split a name string into a list of lines
141 | """
142 | lines = name.split(' ')
143 | # Process hyphens without removing them. There has to be an easier way to do this, no?
144 | newLines = []
145 | for line in lines:
146 | if '-' in line:
147 | lineElements = line.split('-')
148 | for i, lineElement in enumerate(lineElements):
149 | if i != len(lineElements)-1:
150 | newLines.append(lineElement+'-')
151 | else:
152 | newLines.append(lineElement)
153 | else:
154 | newLines.append(line)
155 | lines = newLines
156 | return lines
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | For each line, calculate the font size that will let the word fill the available space. If the word is too short, set a maximum font size. Don’t draw this word yet...this is just a dress rehearsal. We need to figure out the dimensions of the whole text block before we can print anything. (code)
168 |
169 | for i, line in enumerate(lines):
170 | # this is a little hack we did to words that end in 'f', because of the huge overhang in Stilla
171 | if line and line[-1] == 'f':
172 | line += ' '
173 | # get the font size at 10 point, and then do the math to figure out the multiplier
174 | font(nameFont)
175 | fontSize(10)
176 | lineHeight(6.5)
177 | textWidth, textHeight = textSize(line)
178 | m = boxWidth / textWidth
179 | # set a ceiling for the multiplier
180 | maximumMultiplier = 10
181 | if m > maximumMultiplier:
182 | m = maximumMultiplier
183 | # resize the text box based on the multiplier to get the point size
184 | textWidth, textHeight = textWidth*m, textHeight*m
185 | pointSize = 10*m
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | Also, account for a given amount of space between each line. If the word has a descender, make a note that we should add a little more space beneath that line. Keep track of the height of the total text block that we are building.
195 |
196 | # look for descenders in a line
197 | hasDescender = False
198 | extraSpace = 0
199 | for letter in line:
200 | if letter in descenders:
201 | hasDescender = True
202 | break
203 | # if there is a descender, add a little extra space
204 | if hasDescender:
205 | extraSpace = pointSize * .175
206 |
207 | # remember our line data
208 | lineData.append((line, textWidth, textHeight, pointSize, extraSpace))
209 |
210 | # augment the total text count
211 | totalTextHeight += textHeight
212 | totalTextHeight += extraSpace
213 | # if we aren't on the last line, include the line space
214 | if i != len(lines)-1:
215 | totalTextHeight += lineSpace
216 |
217 |
218 |
219 |
220 |
221 |
222 | If the total text block is shorter than the available space, center it vertically in the available space.
223 |
224 | scaleValue = 1
225 | if boxHeight > totalTextHeight:
226 | # ideally, center the text box in the space
227 | verticalOffset = (boxHeight - totalTextHeight)/2
228 | translate(0, -verticalOffset)
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 | On the other hand, if the total text block is taller than the available space, scale it down until it fits, and center it horizontally in the available space.
240 |
241 |
242 | # otherwise, scale the whole damn thing down so it fits in the space
243 | scaleValue = boxHeight / totalTextHeight
244 | diff = boxWidth - (scaleValue * boxWidth)
245 | translate(diff/2, 0)
246 | scale(scaleValue)
247 |
248 |
249 |
250 |
251 |
252 |
253 | ### Draw the name
254 |
255 |
256 |
257 |
258 | Now, draw the text block. Draw each word of the name at the calculated font size, separated by the calculated line spacing.
259 |
260 | # run through our line data
261 | for i, (lineText, textWidth, textHeight, pointSize, extraSpace) in enumerate(lineData):
262 | fillColor(textColor)
263 | fontSize(pointSize)
264 | translate(0, -textHeight)
265 | save()
266 | translate((boxWidth-textWidth)/2, 0)
267 | text(lineText, (0, 0))
268 | restore()
269 | translate(0, -lineSpace-extraSpace)
270 |
271 |
272 |
273 |
274 | Here is the badge-drawing function for a single badge:
275 |
276 | def drawBadge(name, company=None, w=w, h=h, setSize=True, DEBUG=False):
277 | """
278 | Draw one badge. This handles the positioning, and lets other functions do the drawing.
279 | """
280 |
281 | save()
282 |
283 | if setSize:
284 | size(w, h)
285 |
286 | # set the outside margin to 1/18 of the width, which will surround the frame
287 | outsideMargin = w/18
288 | frameWidth = w-outsideMargin*2
289 | frameHeight = h-outsideMargin*2
290 |
291 | # set the box margins to 1/24 of the width, which will surround the box of text
292 | topMargin = bottomMargin = leftMargin = rightMargin = w/24
293 | boxWidth = frameWidth - leftMargin - rightMargin
294 | boxHeight = frameHeight - topMargin - bottomMargin
295 |
296 | # draw the background
297 | if background:
298 | drawBackground(w, h)
299 |
300 | # move ourselves to the frame margin, and draw the frame
301 | translate(outsideMargin, outsideMargin)
302 | drawFrame(frameWidth, frameHeight)
303 |
304 | # print the company name
305 | companyHeight = h / 7.5
306 | companySize = h / 18
307 | # if the company does exist, print that sucker, and then remove some available space
308 | # if the company doesn't exist, shift up our box a smidge anyway so it's a bit above center
309 | if company:
310 | drawCompany(company, companySize, frameWidth, companyHeight)
311 | boxHeight -= companyHeight
312 | bottomMargin += companyHeight
313 | else:
314 | boxHeight -= companyHeight / 3
315 | bottomMargin += companyHeight / 3
316 |
317 | # move ourselves to the bottom left of the remaining available space
318 | translate(leftMargin, bottomMargin)
319 | # draw the available space, in case we want to see it
320 | if DEBUG:
321 | fill(.8)
322 | rect(0, 0, boxWidth, boxHeight)
323 |
324 | # draw the name
325 | drawName(name, boxWidth, boxHeight)
326 |
327 | restore()
328 |
329 | ### Prepare for printing
330 |
331 | Read the names from a CSV file. Since our CSV file will have unicode values, use the included csvunicode module.
332 |
333 | from csvunicode import UnicodeReader
334 | def readDataFromCSV(csvPath):
335 | """
336 | populate a list with rows from a csv file
337 | """
338 | data = []
339 | with open(csvPath, 'r') as csvfile:
340 | csvreader = UnicodeReader(csvfile, delimiter=',', quotechar='"')
341 | for i, row in enumerate(csvreader):
342 | data.append(row)
343 | return data
344 |
345 |
346 | For printing, make six to a page (or three two-sided sheets, to be folded over), along with some margins and crop marks.
347 |
348 | def drawSheets(data, sheetWidth=8.5*pt, sheetHeight=11*pt, badgeWidth=w, badgeHeight=h, margin=.25*pt, multiple=2):
349 | """
350 | Make a sheet of badges for printing purposes.
351 | """
352 |
353 | # determine available space
354 | boxWidth = sheetWidth - margin * 2
355 | boxHeight = sheetHeight - margin * 2
356 | # determine number of columns and rows
357 | cols = int ( boxWidth / badgeWidth )
358 | rows = int ( boxHeight / badgeHeight )
359 |
360 | # reset the box space based on the badge size, rather than the page size
361 | boxWidth = cols * badgeWidth
362 | boxHeight = rows * badgeHeight
363 |
364 | #setup first page
365 | newPage(sheetWidth, sheetHeight)
366 | size(sheetWidth, sheetHeight)
367 | # fill the sheet with the background color, as a rudimentary bleed
368 | fillColor(backgroundColor)
369 | rect(0, 0, sheetWidth, sheetHeight)
370 | # move to the top left corner, which is where we will start
371 | translate(margin, sheetHeight-margin)
372 | # draw crop marks
373 | drawCropMarks(rows, cols, boxWidth, boxHeight, badgeWidth, badgeHeight, margin)
374 | # drop down to the bottom left corner to draw the badges
375 | translate(0, -badgeHeight)
376 |
377 | # loop through data
378 | rowTick = 0
379 | colTick = 0
380 | for i, (name, company) in enumerate(data):
381 | for m in range(multiple):
382 | # draw the badge without setting the page size
383 | drawBadge(name, company, setSize=False)
384 | translate(badgeWidth, 0)
385 |
386 | # if we have made it to the last column, translate back and start the next one
387 | if colTick == cols - 1:
388 | translate(-badgeWidth*cols, 0)
389 | translate(0, -badgeHeight)
390 | colTick = 0
391 |
392 | # if we have made it to the last row (and there is still more data), start a new page
393 | if rowTick == rows - 1 and i != len(data) - 1:
394 | # setup a new page
395 | newPage(sheetWidth, sheetHeight)
396 | # fill the sheet with the background color, as a rudimentary bleed
397 | fillColor(backgroundColor)
398 | rect(0, 0, sheetWidth, sheetHeight)
399 | # move to the top left corner, which is where we will start
400 | translate(margin, sheetHeight-margin)
401 | # draw crop marks
402 | drawCropMarks(rows, cols, boxWidth, boxHeight, badgeWidth, badgeHeight, margin)
403 | # drop down to the bottom left corner to draw the badges
404 | translate(0, -badgeHeight)
405 | rowTick = 0
406 | else:
407 | rowTick += 1
408 | else:
409 | colTick += 1
410 |
411 |
412 |
413 |
414 | ### Export PDF
415 |
416 | Use Drawbot to export a pdf, and that’s it!
417 |
418 | saveImage('/path/to/stinkin_badges.pdf')
419 |
420 |
421 |
--------------------------------------------------------------------------------
/badgeBot.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | ########
4 | BadgeBot
5 | ########
6 | by David Jonathan Ross
7 | based on a design by Nick Sherman
8 | Coded using Drawbot and, of course, Input
9 |
10 | It makes badges for awesome conferences, like @TypographicsNYC!
11 | Learn more:
12 |
13 | This was designed to be used with Stilla SH
14 | Few typefaces will match its funkiness. If you use a different typeface, it just won't be the same,
15 | and you may have to tweak things to get it looking right.
16 |
17 | I'll be the first to admit that this is quick and dirty, and stuff like how to pass variables
18 | has not been totally thought through. But I hope you find it useful and/or interesting!
19 | """
20 |
21 | import string
22 | import random
23 | import csv
24 | import os
25 |
26 | ## VARIABLES
27 | pt = 72
28 | w = 4 * pt
29 | h = 3 * pt
30 |
31 | # RGB colors for screen viewing, otherwise, use CMYK
32 | d = 255
33 | textColor = 252/d, 15/d, 60/d, 1
34 | backgroundColor = 27/d, 161/d, 236/d, 1
35 | frameColor = 254/d, 223/d, 53/d, 1
36 |
37 | # define fonts.
38 | nameFont = 'StillaSHOP-Regular'
39 | companyFont = 'StorkText-Regular'
40 |
41 | # since you probably don't have Stilla or my unpublished font, use Georgia as a fallback
42 | if nameFont not in installedFonts():
43 | nameFont = 'Georgia-BoldItalic'
44 | if companyFont not in installedFonts():
45 | companyFont = 'Georgia'
46 |
47 | # We use this to add space between the lines. This list is made for Stilla.
48 | # It may be different for other fonts!
49 | descenders = ['Q', 'J', 'f', 'g', 'j', 'p', 'q', 'y', 'z']
50 |
51 | # Set default space we will add between lines.
52 | # We are doing some funky stuff so we won't use the normal lineHeight function
53 | lineSpace = 9
54 |
55 | ## HELPERS
56 |
57 | def fillColor(c):
58 | """
59 | a helper function that processes rgba or cymka tuples.
60 | I use this because I hate having to switch back and forth between
61 | color spaces, and splitting up colors from tuples each time.
62 | """
63 | if len(c) == 5:
64 | cmykFill(c[0], c[1], c[2], c[3], c[4])
65 | elif len(c) == 4:
66 | fill(c[0], c[1], c[2], c[3])
67 | else:
68 | fill(c[0], c[1], c[2])
69 |
70 |
71 | ## BADGE FUNCTIONS
72 |
73 | def drawBackground(w, h):
74 | """
75 | draw a rectangle that is a background
76 | """
77 | fillColor(backgroundColor)
78 | rect(0, 0, w, h)
79 |
80 | def drawFrame(frameWidth, frameHeight, numberOfPointsMin=4, numberofPointsMax=6, perpindicularRandomness=2, parallelRandomness=10):
81 | """
82 | make a box with random jagginess
83 | """
84 | # for goodness sake make these variables shorter
85 | pdr = perpindicularRandomness
86 | plr = parallelRandomness
87 | # get the number of segments to use
88 | segments = random.randrange(numberOfPointsMin, numberofPointsMax)
89 | # draw the path, one direction at a time. Loop through the segment, and.
90 | # we have to subtract from segments so we don't hit the shared corners twice.
91 | fillColor(frameColor)
92 | newPath()
93 | moveTo((0, 0))
94 | for i in range(segments-1):
95 | lineTo( ( 0+random.randrange(-pdr, pdr) , (i+1)*frameHeight/segments + random.randrange(-plr, plr) ) )
96 | lineTo((0, frameHeight))
97 | for i in range(segments-1):
98 | lineTo( ( (i+1)*frameWidth/segments + random.randrange(-plr, plr) , frameHeight+random.randrange(-pdr, pdr) ) )
99 | lineTo((frameWidth, frameHeight))
100 | for i in range(segments-2, 0, -1):
101 | lineTo( ( frameWidth+random.randrange(-pdr, pdr) , (i+1)*frameHeight/segments + random.randrange(-plr, plr) ) )
102 | lineTo((frameWidth, 0))
103 | for i in range(segments-2, 0, -1):
104 | lineTo( ( (i+1)*frameWidth/segments + random.randrange(-plr, plr) , 0+random.randrange(-pdr, pdr) ) )
105 | closePath()
106 | drawPath()
107 |
108 | def drawCompany(company, companySize, companyWidth, companyHeight):
109 | """
110 | draw the company name, easy peasy.
111 | """
112 | font(companyFont, companySize)
113 | fillColor(backgroundColor)
114 | textBox(company, (0, 0, companyWidth, companyHeight), align='center')
115 |
116 | def getLinesFromName(name):
117 | """
118 | Split a name string into a list of lines
119 | """
120 | lines = name.split(' ')
121 | # Process hyphens without removing them. There has to be an easier way to do this, no?
122 | newLines = []
123 | for line in lines:
124 | if '-' in line:
125 | lineElements = line.split('-')
126 | for i, lineElement in enumerate(lineElements):
127 | if i != len(lineElements)-1:
128 | newLines.append(lineElement+'-')
129 | else:
130 | newLines.append(lineElement)
131 | else:
132 | newLines.append(line)
133 | lines = newLines
134 | return lines
135 |
136 | def drawName(name, boxWidth, boxHeight):
137 | """
138 | This function draws the name to fill a box. It could probably be simplified a whole lot.
139 | It may take some futzing, especially with vertical space, to make this look halfway decent.
140 | """
141 | save()
142 |
143 | # turn the name string into a list of lines
144 | lines = getLinesFromName(name)
145 |
146 | #################
147 | # DRESS REHEARSAL
148 | #################
149 |
150 | # remember stuff about each line, and also the total text box
151 | lineData = []
152 | totalTextHeight = 0
153 |
154 | for i, line in enumerate(lines):
155 | # this is a little hack we did to words that end in 'f', because of the huge overhang in Stilla
156 | if line and line[-1] == 'f':
157 | line += ' '
158 | # get the font size at 10 point, and then do the math to figure out the multiplier
159 | font(nameFont)
160 | fontSize(10)
161 | lineHeight(6.5)
162 | textWidth, textHeight = textSize(line)
163 | m = boxWidth / textWidth
164 | # set a ceiling for the multiplier
165 | maximumMultiplier = 10
166 | if m > maximumMultiplier:
167 | m = maximumMultiplier
168 | # resize the text box based on the multiplier to get the point size
169 | textWidth, textHeight = textWidth*m, textHeight*m
170 | pointSize = 10*m
171 |
172 | # look for descenders in a line
173 | hasDescender = False
174 | extraSpace = 0
175 | for letter in line:
176 | if letter in descenders:
177 | hasDescender = True
178 | break
179 | # if there is a descender, add a little extra space
180 | if hasDescender:
181 | extraSpace = pointSize * .175
182 |
183 | # remember our line data
184 | lineData.append((line, textWidth, textHeight, pointSize, extraSpace))
185 |
186 | # augment the total text count
187 | totalTextHeight += textHeight
188 | totalTextHeight += extraSpace
189 | # if we aren't on the last line, include the line space
190 | if i != len(lines)-1:
191 | totalTextHeight += lineSpace
192 |
193 | # go to top left
194 | translate(0, boxHeight)
195 |
196 | #################
197 | # SET VERTICAL ALIGNMENT
198 | #################
199 |
200 | # set vertical height
201 | scaleValue = 1
202 | if boxHeight > totalTextHeight:
203 | # ideally, center the text box in the space
204 | verticalOffset = (boxHeight - totalTextHeight)/2
205 | translate(0, -verticalOffset)
206 | else:
207 | # otherwise, scale the whole damn thing down so it fits in the space
208 | scaleValue = boxHeight / totalTextHeight
209 | diff = boxWidth - (scaleValue * boxWidth)
210 | translate(diff/2, 0)
211 | scale(scaleValue)
212 |
213 | #################
214 | # DRAW THE WORDS!
215 | #################
216 |
217 | # run through our line data
218 | for i, (lineText, textWidth, textHeight, pointSize, extraSpace) in enumerate(lineData):
219 | fillColor(textColor)
220 | fontSize(pointSize)
221 | lineHeight(pointSize)
222 | translate(0, -textHeight)
223 | save()
224 | translate((boxWidth-textWidth)/2, 0)
225 | text(lineText, (0, 0))
226 | restore()
227 | translate(0, -lineSpace-extraSpace)
228 | restore()
229 |
230 |
231 |
232 | def drawBadge(name, company=None, w=w, h=h, setSize=True, DEBUG=False):
233 | """
234 | Draw one badge. This handles the positioning, and lets other functions do the drawing.
235 | """
236 |
237 | save()
238 |
239 | if setSize:
240 | newPage(w, h)
241 |
242 | # set the outside margin to 1/18 of the width, which will surround the frame
243 | outsideMargin = w/18
244 | frameWidth = w-outsideMargin*2
245 | frameHeight = h-outsideMargin*2
246 |
247 | # set the box margins to 1/24 of the width, which will surround the box of text
248 | topMargin = bottomMargin = leftMargin = rightMargin = w/24
249 | boxWidth = frameWidth - leftMargin - rightMargin
250 | boxHeight = frameHeight - topMargin - bottomMargin
251 |
252 | # draw the background
253 | if backgroundColor:
254 | drawBackground(w, h)
255 |
256 | # move ourselves to the frame margin, and draw the frame
257 | translate(outsideMargin, outsideMargin)
258 | drawFrame(frameWidth, frameHeight)
259 |
260 | # print the company name
261 | companyHeight = h / 7.5
262 | companySize = h / 18
263 | # if the company does exist, print that sucker, and then remove some available space
264 | # if the company doesn't exist, shift up our box a smidge anyway so it's a bit above center
265 | if company:
266 | drawCompany(company, companySize, frameWidth, companyHeight)
267 | boxHeight -= companyHeight
268 | bottomMargin += companyHeight
269 | else:
270 | boxHeight -= companyHeight / 3
271 | bottomMargin += companyHeight / 3
272 |
273 | # move ourselves to the bottom left of the remaining available space
274 | translate(leftMargin, bottomMargin)
275 | # draw the available space, in case we want to see it
276 | if DEBUG:
277 | fill(.8)
278 | rect(0, 0, boxWidth, boxHeight)
279 |
280 | # draw the name
281 | drawName(name, boxWidth, boxHeight)
282 |
283 | restore()
284 |
285 |
286 | ## SHEET FUNCTIONS
287 |
288 | def drawCropMarks(rows, cols, boxWidth, boxHeight, badgeWidth, badgeHeight, margin):
289 | # assuming we are in the top right, draw crop marks
290 | save()
291 | stroke(1)
292 | for row in range(rows+1):
293 | line((-margin, -row*badgeHeight), (-margin/2, -row*badgeHeight))
294 | line((boxWidth+margin, -row*badgeHeight), (boxWidth+margin/2, -row*badgeHeight))
295 | for col in range(cols+1):
296 | line((col*badgeWidth, margin), (col*badgeWidth, margin/2))
297 | line((col*badgeWidth, -boxHeight-margin/2), (col*badgeWidth, -boxHeight-margin))
298 | restore()
299 |
300 | def drawSheets(data, sheetWidth=8.5*pt, sheetHeight=11*pt, badgeWidth=w, badgeHeight=h, margin=.25*pt, multiple=2):
301 | """
302 | Make a sheet of badges for printing purposes.
303 | """
304 |
305 | # determine available space
306 | boxWidth = sheetWidth - margin * 2
307 | boxHeight = sheetHeight - margin * 2
308 | # determine number of columns and rows
309 | cols = int ( boxWidth / badgeWidth )
310 | rows = int ( boxHeight / badgeHeight )
311 |
312 | # reset the box space based on the badge size, rather than the page size
313 | boxWidth = cols * badgeWidth
314 | boxHeight = rows * badgeHeight
315 |
316 | #setup first page
317 | newPage(sheetWidth, sheetHeight)
318 | # fill the sheet with the background color, as a rudimentary bleed
319 | fillColor(backgroundColor)
320 | rect(0, 0, sheetWidth, sheetHeight)
321 | # move to the top left corner, which is where we will start
322 | translate(margin, sheetHeight-margin)
323 | # draw crop marks
324 | drawCropMarks(rows, cols, boxWidth, boxHeight, badgeWidth, badgeHeight, margin)
325 | # drop down to the bottom left corner to draw the badges
326 | translate(0, -badgeHeight)
327 |
328 | # loop through data
329 | rowTick = 0
330 | colTick = 0
331 | for i, (name, company) in enumerate(data):
332 | for m in range(multiple):
333 | # draw the badge without setting the page size
334 | drawBadge(name, company, setSize=False)
335 | translate(badgeWidth, 0)
336 |
337 | # if we have made it to the last column, translate back and start the next one
338 | if colTick == cols - 1:
339 | translate(-badgeWidth*cols, 0)
340 | translate(0, -badgeHeight)
341 | colTick = 0
342 |
343 | # if we have made it to the last row (and there is still more data), start a new page
344 | if rowTick == rows - 1 and i != len(data) - 1:
345 | # setup a new page
346 | newPage(sheetWidth, sheetHeight)
347 | # fill the sheet with the background color, as a rudimentary bleed
348 | fillColor(backgroundColor)
349 | rect(0, 0, sheetWidth, sheetHeight)
350 | # move to the top left corner, which is where we will start
351 | translate(margin, sheetHeight-margin)
352 | # draw crop marks
353 | drawCropMarks(rows, cols, boxWidth, boxHeight, badgeWidth, badgeHeight, margin)
354 | # drop down to the bottom left corner to draw the badges
355 | translate(0, -badgeHeight)
356 | rowTick = 0
357 | else:
358 | rowTick += 1
359 | else:
360 | colTick += 1
361 |
362 | ## READING DATA
363 |
364 | def readDataFromCSV(csvPath):
365 | """
366 | populate a list with rows from a csv file
367 | """
368 | data = []
369 | with open(csvPath, 'r') as csvfile:
370 | csvreader = csv.reader(csvfile, delimiter=',', quotechar='"')
371 | for i, row in enumerate(csvreader):
372 | data.append(row)
373 | return data
374 |
375 |
376 | if __name__ == "__main__":
377 |
378 | # Draw a sample sheet
379 | size(w, h)
380 | drawBadge(
381 | 'First Name Last Name',
382 | 'Company',
383 | w=w,
384 | h=h,
385 | setSize=False
386 | )
387 |
388 | # load data from a csv
389 | basePath = os.path.split(__file__)[0]
390 | csvPath = os.path.join(basePath, 'data.csv')
391 | data = readDataFromCSV(csvPath)
392 |
393 | # if you don't feel like loading data from a file,
394 | # you can just make a list of tuples like this one:
395 | #data = [('David Jonathan Ross', 'The Font Bureau, Inc.')]
396 |
397 | # let's draw some sheets.
398 | # Since we are not double-sided printing, we will print each twice, side-by-side,
399 | # and fold along the middle.
400 | drawSheets(data,
401 | sheetWidth = 8.5*pt,
402 | sheetHeight = 11*pt,
403 | badgeWidth = w,
404 | badgeHeight = h,
405 | margin = .25*pt,
406 | multiple=2
407 | )
408 |
--------------------------------------------------------------------------------
/badges.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/badges.jpg
--------------------------------------------------------------------------------
/data.csv:
--------------------------------------------------------------------------------
1 | William Caslon,Caslon Foundry
2 | Claude Garamond,
3 | Francesco Griffo,Aldine Press
4 | Frederic Goudy,
5 | Giambattista Bodoni,
6 | Herb Lubalin,"Herb Lubalin, Inc."
--------------------------------------------------------------------------------
/images/___page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/___page.png
--------------------------------------------------------------------------------
/images/___page.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/animate.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/animate.gif
--------------------------------------------------------------------------------
/images/available_space.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/available_space.png
--------------------------------------------------------------------------------
/images/blue_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/blue_box.png
--------------------------------------------------------------------------------
/images/blue_box.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/company.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/company.png
--------------------------------------------------------------------------------
/images/company.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/final.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/final.png
--------------------------------------------------------------------------------
/images/final.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/final_product.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/final_product.jpg
--------------------------------------------------------------------------------
/images/guidelines_blue_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_blue_box.png
--------------------------------------------------------------------------------
/images/guidelines_blue_box.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/guidelines_company.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_company.png
--------------------------------------------------------------------------------
/images/guidelines_company.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/guidelines_name_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_name_0.png
--------------------------------------------------------------------------------
/images/guidelines_name_0.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/guidelines_name_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_name_1.png
--------------------------------------------------------------------------------
/images/guidelines_name_1.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/guidelines_name_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_name_2.png
--------------------------------------------------------------------------------
/images/guidelines_name_2.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/guidelines_yellow_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_yellow_box.png
--------------------------------------------------------------------------------
/images/guidelines_yellow_box.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/name_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_0.png
--------------------------------------------------------------------------------
/images/name_0.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/name_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_1.png
--------------------------------------------------------------------------------
/images/name_1.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/name_descender.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_descender.png
--------------------------------------------------------------------------------
/images/name_size.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_size.png
--------------------------------------------------------------------------------
/images/name_split.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_split.png
--------------------------------------------------------------------------------
/images/name_vertical.01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_vertical.01.png
--------------------------------------------------------------------------------
/images/name_vertical.01.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/name_vertical.02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_vertical.02.png
--------------------------------------------------------------------------------
/images/name_vertical.02.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/page.png
--------------------------------------------------------------------------------
/images/page.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/images/yellow_box.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/yellow_box.gif
--------------------------------------------------------------------------------
/images/yellow_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/yellow_box.png
--------------------------------------------------------------------------------
/images/yellow_box.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------