def draw(self, saving=False, saveTo=None, fmt="pdf", layers=[], fill=None): savingToFont = isinstance(fmt, defcon.Font) if saving: db.newDrawing() self.saving = True else: self.saving = False db.newPage(*self.animation.dimensions) self.page = Rect.page() self.bps = {} for l in layers: self.bps[l] = RichBezier() with db.savedState(): if fill and not saveTo: with db.savedState(): db.fill(*fill) db.rect(*self.page) self.layers = layers self.animation.fn(self) self.layers = None if self.animation.burn: box = self.page.take(64, Edge.MinY).take(120, Edge.MaxX).offset(-24, 24) db.fontSize(20) db.lineHeight(20) db.font("Menlo-Bold") db.fill(0, 0.8) db.rect(*box.inset(-14, -14).offset(0, 2)) db.fill(1) db.textBox("{:07.2f}\n{:04d}\n{:%H:%M:%S}".format( self.time, self.i, datetime.datetime.now()), box, align="center") for k, bez in self.bps.items(): with db.savedState(): db.fill(*bez.fill) db.drawPath(bez.bp) if saving: if savingToFont: for k, bez in self.bps.items(): g = defcon.Glyph() g.name = "frame_" + str(self.i) g.unicode = self.i + 48 # to get to 0 g.width = self.animation.dimensions[0] bez.bp.drawToPen(g.getPen()) fmt.insertGlyph(g) else: db.saveImage(f"{saveTo}/{self.i}.{fmt}") db.endDrawing() self.saving = False
def kerningHeatMap(kerning, glyphNames, isFirstVertical): corrections = [kerning[pair] for pair in product(glyphNames, repeat=2)] corrections.sort() minCorrection, maxCorrection = abs(corrections[0]), abs(corrections[-1]) if minCorrection < maxCorrection: reference = maxCorrection else: reference = minCorrection for jj, glyphY in enumerate(glyphNames): # vertical captions with savedState(): translate(-CELL_SIZE, jj * CELL_SIZE) typeQualities() text(f'{glyphY}', (CELL_SIZE * .5, CELL_SIZE * .2), align='center') # drawing the row for ii, glyphX in enumerate(glyphNames): pair = (glyphY, glyphX) if isFirstVertical else (glyphX, glyphY) correction = kerning[pair] with savedState(): translate(ii * CELL_SIZE, jj * CELL_SIZE) # horizontal captions if jj == 0: typeQualities() text(f'{glyphX}', (CELL_SIZE * .5, -CELL_SIZE * .8), align='center') # draw the cells factor = .5 + .5 * abs(correction) / reference if correction == 0: rectClr = BLACK typeClr = WHITE elif correction < 0: rectClr = lerpRGB(WHITE, RED, factor) typeClr = WHITE else: rectClr = lerpRGB(WHITE, GREEN, factor) typeClr = BLACK shapeQualities(rectClr) rect(0, 0, CELL_SIZE, CELL_SIZE) if correction != 0: corrStr = f'{abs(correction)}' # just a check for body size if textSize(corrStr)[0] > CELL_SIZE: print(f'[WARNING] {pair} text is too big!') typeQualities(clr=typeClr) text(corrStr, (CELL_SIZE * .5, CELL_SIZE * .2), align='center')
def hslDonut(rings, ringThickness, holeRadius, fixedValue, isLuminosityConst, captions=True): for angle in range(FULL_CIRCLE): for eachRing in range(rings): ringFactor = eachRing / (rings - 1) radius = holeRadius + eachRing * ringThickness if isLuminosityConst: rgbClr = hls_to_rgb(angle / FULL_CIRCLE, fixedValue, ringFactor) else: rgbClr = hls_to_rgb(angle / FULL_CIRCLE, ringFactor, fixedValue) lineQualities(rgbClr, ringThickness) newPath() arc((0, 0), radius, angle - .5, angle + .5, clockwise=False) drawPath() with savedState(): if angle % 10 == 0 and captions: captionRadius = radius + ringThickness rotate(angle) lineQualities(thickness=.5) line((captionRadius - 2, 0), (captionRadius + 2, 0)) typeQualities() text(f'{angle}', (radius + ringThickness + 6, 0))
def feelingSlide(canvasWidth, canvasHeight, polarity): db.newPage(canvasWidth, canvasHeight) background_fill = polarityBackground(polarity) db.fill(*background_fill) db.frameDuration(4) db.rect(0, 0, canvasWidth, canvasHeight) background_images = os.listdir('background_images/') background_image_path = 'background_images/' + background_images[(int)( len(background_images) * random.random())] # https://forum.drawbot.com/topic/180/how-do-i-size-an-image-with-the-imageobject-or-without/4 srcWidth, srcHeight = db.imageSize(background_image_path) dstWidth, dstHeight = canvasWidth - 50, canvasHeight - 50 factorWidth = dstWidth / srcWidth factorHeight = dstHeight / srcHeight with db.savedState(): db.translate(25, 25) with db.savedState(): db.scale(factorWidth, factorHeight) db.image(background_image_path, (0, 0)) dril_feels_text = db.FormattedString() dril_feels_text.append("@dril feels", font="Calibri-Bold", fontSize=150, fill=1, align='center', stroke=background_fill, strokeWidth=0.5) db.shadow((0, 0), 50, background_fill) db.text(dril_feels_text, (canvasWidth / 2, canvasHeight - 300)) if polarity < -0.1: drils_feeling = "angry" db.font("LucidaBlackletter", 250) elif polarity < 0.25: drils_feeling = "neutral" db.font("Helvetica", 180) else: drils_feeling = "happy" db.font("Cortado", 250) db.fill(1) db.shadow((0, 0), 50, background_fill) db.text(drils_feeling, (canvasWidth / 2, 250), align='center')
def drawCell(glyphName, cellWidth, cellHeight, cellLegendSize): # Cell outline db.fill(None) db.stroke(0) db.strokeWidth(0.25) db.rect(0, 0, cellWidth, cellHeight) charArea = cellWidth / 3.5 fontSize = charArea * 0.7 charStartingOffset = (cellWidth * 0.5) - (charArea * 1.5) # Glyph sample for iH, aH in enumerate(angles): for iV, aV in enumerate(reversed(angles)): locStr = "%s %s" % (aH, aV) f = instances[locStr] if glyphName in f: g = f[glyphName] with db.savedState(): db.translate(charStartingOffset, charStartingOffset ) # Center the nine glyphs in the cell db.translate( iH * charArea, iV * charArea) # Move to the current glyph in the cell db.translate(charArea * 0.5, 0) # Offset to center glyph in cell db.translate(0, cellLegendSize * 3) # Leave room for the legend # Draw db.fill(0) db.stroke(None) db.scale(fontSize / 1000) drawGlyph(g) # Legend db.fill(None) db.stroke(0) db.strokeWidth(0.25) db.lineCap("round") db.line((cellLegendSize, cellLegendSize * 3), (cellWidth - cellLegendSize, cellLegendSize * 3)) unicodeValueStr = "" if g.unicode: unicodeValueStr = hex(g.unicode) legendText = "%s\n%s" % (g.name, unicodeValueStr) fs = db.FormattedString(legendText, font="Tilt Neon", fontSize=cellLegendSize, tracking=1, lineHeight=cellLegendSize) db.text(fs, (cellLegendSize, cellLegendSize * 1.7))
def backgroundImage(canvasWidth, canvasHeight): background_images = os.listdir('background_images/') background_image_path = 'background_images/' + background_images[(int)( len(background_images) * random.random())] # https://forum.drawbot.com/topic/180/how-do-i-size-an-image-with-the-imageobject-or-without/4 srcWidth, srcHeight = db.imageSize(background_image_path) dstWidth, dstHeight = canvasWidth, canvasHeight factorWidth = dstWidth / srcWidth factorHeight = dstHeight / srcHeight with db.savedState(): db.scale(factorWidth, factorHeight) db.image(background_image_path, (0, 0))
def drawGlyph(g): # Get glyph outline f = g.font pen = CocoaPen(f) g.draw(pen) bezierPath = pen.path # Draw with db.savedState(): db.translate(-g.width * 0.5, 450) # Center the glyph path = db.BezierPath() path.setNSBezierPath(bezierPath) db.drawPath(path)
def startNewPage(pageWidth, pageHeight, margin, header): db.newPage(pageWidth, pageHeight) # Draw the header with db.savedState(): fs = db.FormattedString("%s Glyph Overview\n" % HEADER_STYLENAME, font="Tilt Neon", fontSize=15) fs.append("by Andy Clymer\n%s" % HEADER_URL, font="Tilt Neon", fontSize=8) db.translate(margin, db.height() - header - margin) db.textBox(fs, (0, 0, db.width() - (margin * 2), header))
def drawFolds(foldWidth, currentPage): ticks = 0 for y in range(0, int(canvasHeight * 1.3), int(foldWidth * 0.6)): with db.savedState(): yCopy = y db.rotate(-30, center=(0, yCopy)) xOffset = -foldWidth for x in range(xOffset, int(canvasWidth * 1.3), int(foldWidth * 1.8)): yCopy -= foldWidth * 0.03 db.rotate(math.sqrt((x - xOffset + 1) * 0.1), center=(x, yCopy)) fold(x, yCopy, foldWidth, currentPage) ticks += 1 if (ticks > 2000): raise Exception('Maximum depth reached')
def test_distribute_on_path(self): mistral = Font.Cacheable("~/Type/fonts/fonts/_script/MistralD.otf") with test_image(self, "test_distribute.png", Rect(1000, 1000)) as (i, r): s = (StyledString("Hello", Style(mistral, 300)).pens().f( hsl(0.3, s=1)).align(r).chain(dbdraw)) with db.savedState(): db.fill(None) db.stroke(0) db.strokeWidth(1) db.rect(*s.ambit()) circle = DraftingPen().oval(r.inset(200)).reverse().rotate(0) s2 = (s.copy().zero_translate().distribute_on_path(circle).chain( dbdraw)) self.assertEqual(s.f(), s2.f())
def image(self, src=None, opacity=1, rect=None, rotate=0, repeating=False, scale=True): bounds = self.dat.bounds() src = str(src) if not rect: rect = bounds try: img_w, img_h = db.imageSize(src) except ValueError: print("DrawBotPen: No image") return x = bounds.x y = bounds.y if repeating: x_count = bounds.w / rect.w y_count = bounds.h / rect.h else: x_count = 1 y_count = 1 _x = 0 _y = 0 while x <= (bounds.w+bounds.x) and _x < x_count: _x += 1 while y <= (bounds.h+bounds.y) and _y < y_count: _y += 1 with db.savedState(): r = Rect(x, y, rect.w, rect.h) #db.fill(1, 0, 0.5, 0.05) #db.oval(*r) if scale == True: db.scale(rect.w/img_w, center=r.point("SW")) elif scale: try: db.scale(scale[0], scale[1], center=r.point("SW")) except TypeError: db.scale(scale, center=r.point("SW")) db.rotate(rotate) db.image(src, (r.x, r.y), alpha=opacity) y += rect.h y = 0 x += rect.w
def answerSlide(canvasWidth, canvasHeight, answer, polarity): background_fill = polarityBackground(polarity) db.newPage(canvasWidth, canvasHeight) db.fill(*background_fill) db.rect(0, 0, canvasWidth, canvasHeight) db.frameDuration(4) background_images = os.listdir('background_images/') background_image_path = 'background_images/' + background_images[(int)( len(background_images) * random.random())] # https://forum.drawbot.com/topic/180/how-do-i-size-an-image-with-the-imageobject-or-without/4 srcWidth, srcHeight = db.imageSize(background_image_path) dstWidth, dstHeight = canvasWidth - 50, canvasHeight - 50 factorWidth = dstWidth / srcWidth factorHeight = dstHeight / srcHeight with db.savedState(): db.translate(25, 25) with db.savedState(): db.scale(factorWidth, factorHeight) db.image(background_image_path, (0, 0)) db.fill(*rgba(*background_fill, 0.1)) box_width = 0.7 * canvasWidth box_height = canvasHeight * 0.7 x_0 = (canvasWidth - box_width) / 2 y_0 = (canvasHeight - box_height) / 2 - 100 text_box_margin = 40 text_box_width = box_width - text_box_margin * 2 text_box_height = box_height - text_box_margin * 2 current_font_size = 10 db.font('Calibri-Bold', current_font_size) # this is not efficient. Don't show anyone I made this while True: db.fontSize(current_font_size) current_font_size += 1 _, current_text_height = db.textSize(answer, 'left', width=text_box_width) if (current_font_size > 150): break elif (current_text_height > text_box_height): current_font_size -= 2 break db.fontSize(current_font_size) db.stroke(*background_fill) db.strokeWidth(0.5) db.fill(*rgb(255, 252, 61)) db.textBox(answer, (x_0, y_0, box_width, box_height), 'left') # dril says d_says = db.FormattedString() d_says.append("@dril says:", font="Calibri-Bold", fontSize=100, fill=rgb(255, 252, 61), stroke=background_fill, strokeWidth=2) # db.shadow((0,0), 50, background_fill) db.text(d_says, (x_0, y_0 + box_height + 30))
def draw(self, scale=2, style=None): with db.savedState(): db.scale(scale) for attrs, attr in self.findStyledAttrs(style): self.applyDATAttribute(attrs, attr) db.drawPath(self.bp)
### Variables discs = 16 rings = 22 ringThickness = 5 holeRadius = 45 ### Instructions if __name__ == '__main__': newDrawing() newPage(952, 488) translate(width() * .27, height() * .25) save() for eachDisc in range(discs): with savedState(): scale(1, .65) hslDonut(rings, ringThickness, holeRadius, fixedValue=eachDisc / (discs - 1), isLuminosityConst=True, captions=False) translate(0, 16) restore() translate(width() * .44, 0) save() for eachDisc in range(discs): with savedState(): scale(1, .65)
import drawBot drawBot.newDrawing() drawBot.size(200, 200) testData = [ ((25, 25, 50, 50), "rotate", (20,), (25, 25)), ((125, 25, 50, 50), "skew", (10, 10), (175, 25)), ((25, 125, 50, 50), "scale", (1.2, 1.4), (25, 175)), ] for r, op, args, center in testData: drawBot.fill(0) bez = drawBot.BezierPath() bez.rect(*r) drawBot.drawPath(bez) with drawBot.savedState(): drawBot.fill(1, 0, 0, 0.5) bez = drawBot.BezierPath() bez.rect(*r) getattr(bez, op)(*args, center=center) drawBot.drawPath(bez)
def doExport(self, f, savePath): # Export an image of the glyphs # Collect the glyph names from this font glyphNames = [] glyphChoice = self.w.exportBox.glyphChoice.get() if glyphChoice == 0: glyphNames = list(f.selection) elif glyphChoice == 1: if 'public.glyphOrder' in f.lib.keys(): allNames = f.lib['public.glyphOrder'] else: allNames = list(f.keys()) allNames.sort() # Only keep glyphs that have art for gn in allNames: g = f[gn] if len(g.contours): glyphNames.append(gn) # Find the maximum glyph bounds, to figure out the grid spacing xMax = 0 yMax = 0 for gn in glyphNames: g = f[gn] bounds = g.bounds if bounds: thisXMax = bounds[2] - bounds[0] thisYMax = bounds[3] - bounds[1] if thisXMax > xMax: xMax = thisXMax if thisYMax > yMax: yMax = thisYMax xMax = int(math.ceil(xMax)) * self.pageScale yMax = int(math.ceil(yMax)) * self.pageScale # Figure out how many rows of glyphs we'll need, to lay them out in a square grid if len(glyphNames) < 1: rowCount = 1 else: rowCount = math.ceil(math.sqrt(len(glyphNames))) colCount = math.ceil(len(glyphNames) / rowCount) # Work out the page dimensions, with glyphs centered up on a grid of xMax and yMax with a buffer to give a little space to work with pageWidth = ((colCount + 1) * xMax * self.bufferFactor) pageHeight = ((rowCount + 1) * yMax * self.bufferFactor) # Start drawing, keeping track of where each glyph was placed glyphLocations = [] db.newDrawing() db.newPage(pageWidth, pageHeight) gIdx = 0 for rowNumber in range(rowCount): for colNumber in range(colCount): if gIdx < len(glyphNames): gridID = "%s-%s" % (colNumber, rowNumber) gName = glyphNames[gIdx] glyph = f[gName] # Find the position to draw the glyph # The glyph will be drawn centered on the center of each grid location locX = ((colNumber * xMax * self.bufferFactor) + xMax * self.bufferFactor) locY = (pageHeight - (rowNumber * yMax * self.bufferFactor) - (yMax * self.bufferFactor)) # Adjust the grid location to center the glyph drawing locX += -glyph.width * 0.5 * self.pageScale locY += -yMax * 0.5 glyphLocations.append((gName, gridID)) # Draw the glyph with db.savedState(): db.translate(locX, locY) db.scale(self.pageScale, self.pageScale) pen = CocoaPen(f) glyph.draw(pen) glyphPath = pen.path path = db.BezierPath() path.setNSBezierPath(glyphPath) db.drawPath(path) # Draw the glyph bounding box, for testing if False: db.fill(None) db.stroke(0) db.strokeWidth(10) db.rect(0, 0, xMax, yMax) gIdx += 1 # Prepare a settings document to save, with the number of rows, columns, spacing, scale, and glyph positions drawingData = {} drawingData["pageScale"] = self.pageScale drawingData["bufferFactor"] = self.bufferFactor drawingData["pageWidth"] = pageWidth drawingData["pageHeight"] = pageHeight drawingData["xMax"] = xMax drawingData["yMax"] = yMax drawingData["rowCount"] = rowCount drawingData["colCount"] = colCount drawingData["glyphLocations"] = glyphLocations # Save db.saveImage(savePath) plistPath = os.path.splitext(savePath)[0] + ".plist" with open(plistPath, "wb") as plistFile: dump(drawingData, plistFile)
for background_image in background_images: background_image_paths.append('img/tiles-copy/' + background_image) # print(background_image_paths) db.newPage(1240, 496) tileSize = 124 images = [['i' for i in range(4)] for j in range(10)] srcWidth, srcHeight = db.imageSize(background_image_paths[0]) dstWidth, dstHeight = tileSize, tileSize factorWidth = dstWidth / srcWidth factorHeight = dstHeight / srcHeight for x in range(0, 1240, tileSize): for y in range(0, 496, tileSize): with db.savedState(): stepX = int(x/tileSize) stepY = int(y/tileSize) validPath = False while not validPath: path = random.choice(background_image_paths) if(stepX > 0): if(images[stepX-1][stepY] == path): continue if(stepY > 0): if(images[stepX][stepY-1] == path): continue validPath = True db.translate(x, y) db.scale(factorWidth, factorHeight) db.image(path, (0, 0) )