├── .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 | animate 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 | blue background 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 | yellow box 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 | available space 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 | company name 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 | name split 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 | calculations 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 | descender 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 | tall name 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 | short name 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 | short name 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 | sheet 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 | 3 | 4 | 5 | 6 | 7 | 8 | Caslon Foundry 9 | 10 | 11 | 12 | 13 | William 14 | 15 | 16 | 17 | 18 | Caslon 19 | 20 | 21 | 22 | 23 | 24 | 25 | Caslon Foundry 26 | 27 | 28 | 29 | 30 | William 31 | 32 | 33 | 34 | 35 | Caslon 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Claude 57 | 58 | 59 | 60 | 61 | Garamond 62 | 63 | 64 | 65 | 66 | 67 | 68 | Claude 69 | 70 | 71 | 72 | 73 | Garamond 74 | 75 | 76 | 77 | 78 | 79 | 80 | Aldine Press 81 | 82 | 83 | 84 | 85 | Francesco 86 | 87 | 88 | 89 | 90 | Griffo 91 | 92 | 93 | 94 | 95 | 96 | 97 | Aldine Press 98 | 99 | 100 | 101 | 102 | Francesco 103 | 104 | 105 | 106 | 107 | Griffo 108 | 109 | 110 | -------------------------------------------------------------------------------- /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 | 3 | 4 | -------------------------------------------------------------------------------- /images/company.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/company.png -------------------------------------------------------------------------------- /images/company.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /images/final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/final.png -------------------------------------------------------------------------------- /images/final.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | 11 | 12 | David 13 | 14 | 15 | 16 | 17 | Jonathan 18 | 19 | 20 | 21 | 22 | Ross 23 | 24 | 25 | -------------------------------------------------------------------------------- /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 | 3 | 4 | -------------------------------------------------------------------------------- /images/guidelines_company.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_company.png -------------------------------------------------------------------------------- /images/guidelines_company.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | -------------------------------------------------------------------------------- /images/guidelines_name_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_name_0.png -------------------------------------------------------------------------------- /images/guidelines_name_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | 11 | 12 | 13 | 14 | David 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /images/guidelines_name_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_name_1.png -------------------------------------------------------------------------------- /images/guidelines_name_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | 11 | 12 | 13 | 14 | David 15 | 16 | 17 | 18 | 19 | 20 | Jonathan 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /images/guidelines_name_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_name_2.png -------------------------------------------------------------------------------- /images/guidelines_name_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | 11 | 12 | 13 | 14 | David 15 | 16 | 17 | 18 | 19 | 20 | Jonathan 21 | 22 | 23 | 24 | 25 | 26 | Ross 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /images/guidelines_yellow_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/guidelines_yellow_box.png -------------------------------------------------------------------------------- /images/guidelines_yellow_box.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/name_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_0.png -------------------------------------------------------------------------------- /images/name_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | 11 | 12 | David 13 | 14 | 15 | -------------------------------------------------------------------------------- /images/name_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_1.png -------------------------------------------------------------------------------- /images/name_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | 11 | 12 | David 13 | 14 | 15 | 16 | 17 | Jonathan 18 | 19 | 20 | -------------------------------------------------------------------------------- /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 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | 11 | 12 | David 13 | 14 | 15 | 16 | 17 | Ross 18 | 19 | 20 | -------------------------------------------------------------------------------- /images/name_vertical.02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/name_vertical.02.png -------------------------------------------------------------------------------- /images/name_vertical.02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | The Font Bureau, Inc. 8 | 9 | 10 | 11 | 12 | David 13 | 14 | 15 | 16 | 17 | Jonathan 18 | 19 | 20 | 21 | 22 | Bodoni- 23 | 24 | 25 | 26 | 27 | Ross 28 | 29 | 30 | -------------------------------------------------------------------------------- /images/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/djrrb/BadgeBot/9365dbd80e1cfb729cb2ff6e7963f3e76868f6ad/images/page.png -------------------------------------------------------------------------------- /images/page.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Caslon Foundry 9 | 10 | 11 | 12 | 13 | William 14 | 15 | 16 | 17 | 18 | Caslon 19 | 20 | 21 | 22 | 23 | 24 | 25 | Caslon Foundry 26 | 27 | 28 | 29 | 30 | William 31 | 32 | 33 | 34 | 35 | Caslon 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Claude 57 | 58 | 59 | 60 | 61 | Garamond 62 | 63 | 64 | 65 | 66 | 67 | 68 | Claude 69 | 70 | 71 | 72 | 73 | Garamond 74 | 75 | 76 | 77 | 78 | 79 | 80 | Aldine Press 81 | 82 | 83 | 84 | 85 | Francesco 86 | 87 | 88 | 89 | 90 | Griffo 91 | 92 | 93 | 94 | 95 | 96 | 97 | Aldine Press 98 | 99 | 100 | 101 | 102 | Francesco 103 | 104 | 105 | 106 | 107 | Griffo 108 | 109 | 110 | -------------------------------------------------------------------------------- /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 | 3 | 4 | 5 | --------------------------------------------------------------------------------