def textColor(self, base, shade): """Answer the shade of base color that words best as text foreground on the `shade` color. >>> from pagebotnano.themes import BackToTheCity >>> theme = BackToTheCity() >>> theme.textColor(3, 0).name 'black' """ c = self.colors[base][shade] if c.averageRgb < 0.4: return color(1) return color(0)
def __init__(self, glyphName, font, lineStroke=True, lineWidth=None, textFill=None, textStroke=None, textStrokeWidth=0, pointStroke=None, pointStrokeWidth=0, pointMarkerSize=None, **kwargs): Element.__init__(self, **kwargs) assert isinstance(font, Font) self.font = font # This is a real Font instance self.glyphName = glyphName # As start assume the full height of the element as fontSize self.fontSize = self.h assert textFill is None or isinstance(textFill, Color) assert textStroke is None or isinstance(textStroke, Color) if textFill is None: textFill = self.GLYPH_FILL self.textFill = textFill self.textStroke = textStroke self.textStrokeWidth = textStrokeWidth or 0 # Flag to show horizontal metrics lines. Can be one of (None, True, False, Color) # Default is self.LINE_STROKE color(0, 0, 1) if lineStroke and not isinstance(lineStroke, Color): # In case it is "True" lineStroke = self.LINE_STROKE self.lineStroke = lineStroke self.lineWidth = lineWidth or 0 # Thickness of metrics lines # Create a style for it, so we can draw the glyph(s) as Text. style = dict(font=self.font.path, fontSize=self.fontSize, textFill=textFill or color(0), align=CENTER) self.bs = BabelString(self.glyphName, style=style) tw, th = self.bs.textSize # Get the size of the glyph(s) string to see if it fits. if self.w and tw > self.w: # If width of self is defined and string is wider # Interpolate the fontSize from the measured width to smaller scaled fontSize. self.fontSize *= self.w / tw # Multiply original fontsize by ratio # Make a new string with the fitting fontSize style['fontSize'] = self.fontSize # Adjust the existing style style['fill'] = textFill or color(0) self.bs = BabelString(self.glyphName, style=style)
class HappyHolidays(BaseTheme): NAME = 'Happy Holidays' BASE_COLORS = dict( base0=color(1, 0, 0.2), base1=color(0.7, 0.1, 0.2), base2=color(0.9, 0, 0.3), base3=color(0.5, 0.96, 0.2), base4=color(0, 1, 0), base5=color(0.55, 0.5, 0.5), )
def fill(self, c): """Set the fill mode of the context. `c` can be None, a number, a name or a Color instance. >>> context = DrawBotContext() >>> context.fill(None) >>> context.fill('red') >>> context.fill((1, 0, 0)) >>> context.fill(Color(1, 0, 0)) >>> context.fill(0.5) """ if c is None: drawBot.fill(None) else: if not isinstance(c, Color): # Make sure is it a Color instance. c = color(c) r, g, b, a = c.rgba drawBot.fill(r, g, b, a)
def stroke(self, c, strokeWidth=None): """Set the stroke mode of the context. `c` can be None, a number, a name or a Color instance. >>> context = DrawBotContext() >>> context.stroke(None) >>> context.stroke('red') >>> context.stroke((1, 0, 0)) >>> context.stroke(Color(1, 0, 0)) >>> context.stroke(0.5, 1) """ if strokeWidth is not None: self.strokeWidth(strokeWidth) if c is None: drawBot.stroke(None) else: # Make sure it is a Color instance. if not isinstance(c, Color): c = color(c) r, g, b, a = c.rgba drawBot.stroke(r, g, b, a)
# Create a new document for the given size doc = Document(w=w, h=h) # Create the first page in the document, taking over the document size and padding. page = doc.newPage() # Show a single glyph, as large as it fits (in height or width) on the page, # white on color background. # The GlyphView element has a number of attributes (guided by attributes), # to define the details that should be shown with the glyph outline. gv = GlyphPathView('G', font=f, x=page.pl, y=page.pb, w=page.pw, h=page.ph, fill=color(0.9), textFill=color(0.7), textStroke=color(1, 0, 0), textStrokeWidth=2, pointStroke=color('darkblue'), pointStrokeWidth=2, pointMarkerSize=12, pointFill=color(0.9), pointLine=True) page.addElement(gv) # Create the first page in the document, taking over the document size and padding. page = doc.newPage() # Show a single glyph, as large as it fits (in height or width) on the page, # white on color background. # The GlyphView element has a number of attributes (guided by attributes),
class GlyphView(Element): """The GlyphView show single glyphs with metrics lines, using a FormattedString to show the text in a specified font. This is faster and more direct than drawing with a GlyphPath (which is demonstrated in the Element class GlyphPathView). >>> from pagebotnano_030.document import Document >>> from pagebotnano_030.fonttoolbox.objects.font import findFont >>> f = Font('../../resources/fonts/typetr/PageBot-Light.ttf') >>> #f = findFont('PageBot-Light.ttf') >>> doc = Document(w=200, h=200) >>> page = doc.newPage() >>> pad = 10 >>> page.padding = pad >>> e = GlyphView('Hhj', f, x=pad, y=pad, w=page.pw, h=page.ph, fill=color(0.96)) >>> page.addElement(e) >>> doc.export('_export/GlyphView.pdf') """ LINE_STROKE = color(0, 0, 1) # Defaultl color of the metrics lines (if defined) GLYPH_FILL = color(0) # Default color of the glyph def __init__(self, glyphName, font, lineStroke=True, lineWidth=None, textFill=None, textStroke=None, textStrokeWidth=0, pointStroke=None, pointStrokeWidth=0, pointMarkerSize=None, **kwargs): Element.__init__(self, **kwargs) assert isinstance(font, Font) self.font = font # This is a real Font instance self.glyphName = glyphName # As start assume the full height of the element as fontSize self.fontSize = self.h assert textFill is None or isinstance(textFill, Color) assert textStroke is None or isinstance(textStroke, Color) if textFill is None: textFill = self.GLYPH_FILL self.textFill = textFill self.textStroke = textStroke self.textStrokeWidth = textStrokeWidth or 0 # Flag to show horizontal metrics lines. Can be one of (None, True, False, Color) # Default is self.LINE_STROKE color(0, 0, 1) if lineStroke and not isinstance(lineStroke, Color): # In case it is "True" lineStroke = self.LINE_STROKE self.lineStroke = lineStroke self.lineWidth = lineWidth or 0 # Thickness of metrics lines # Create a style for it, so we can draw the glyph(s) as Text. style = dict(font=self.font.path, fontSize=self.fontSize, textFill=textFill or color(0), align=CENTER) self.bs = BabelString(self.glyphName, style=style) tw, th = self.bs.textSize # Get the size of the glyph(s) string to see if it fits. if self.w and tw > self.w: # If width of self is defined and string is wider # Interpolate the fontSize from the measured width to smaller scaled fontSize. self.fontSize *= self.w / tw # Multiply original fontsize by ratio # Make a new string with the fitting fontSize style['fontSize'] = self.fontSize # Adjust the existing style style['fill'] = textFill or color(0) self.bs = BabelString(self.glyphName, style=style) def drawContent(self, ox, oy, doc, page, parent): """Draw the content of this single glyph/string fitting, with line indicators of vertical metrics. TODO: Show more font metrics and glyph metrics here. Add labels of values and names. """ # Set the context to font and fontSize, so we get the right descender back. doc.context.font( self.font.path, self.fontSize) # Set to new fontSize, so metrics do fit descender = doc.context.fontDescender( ) # Scaled descender of current font/fontSize # If the fontSize is down scaled to match the string width, then evenely # distribute the extra vertical space above and below that scaled fontSize. baseline = (self.h - self.fontSize ) / 2 - descender # Distance from baseline to bottom y y = oy + baseline # Calculate the position of the baseline. doc.context.text( self.bs, (ox + self.w / 2, y)) # Draw the glyphs on centered position. # If line color and line width are defined. if self.lineStroke and self.lineWidth: doc.context.stroke(self.lineStroke, self.lineWidth) doc.context.line((ox, y), (ox + self.w, y)) # Draw baseline xHeight = doc.context.fontXHeight() y = oy + baseline + xHeight doc.context.line((ox, y), (ox + self.w, y)) capHeight = doc.context.fontCapHeight() y = oy + baseline + capHeight doc.context.line((ox, y), (ox + self.w, y)) y = oy + baseline + descender doc.context.line((ox, y), (ox + self.w, y)) # Descender y = oy + baseline + self.fontSize + descender doc.context.line((ox, y), (ox + self.w, y)) # Descender
class GlyphPathView(GlyphView): """The GlyphPathView show single glyphs with metrics lines, using the Glyph.getGlyphPath as contour drawing. This is more flexible than drawing text as FormattedString. GlyphPathView inherits from GlyphView, because it only is different in the GlyphPathView.drawContent method. >>> from pagebotnano_030.document import Document >>> from pagebotnano_030.fonttoolbox.objects.font import findFont >>> f = findFont('PageBot-Bold.ttf') >>> doc = Document(w=595, h=842) >>> page = doc.newPage() >>> pad = 10 >>> page.padding = pad >>> e = GlyphPathView('Oi', f, x=pad, y=pad, w=page.pw, h=page.ph, fill=color(0.96), textFill=color(0.6), textStroke=color(0), textStrokeWidth=1) >>> page.addElement(e) >>> doc.export('_export/GlyphPathView.pdf') """ POINT_STROKE = color('darkblue') POINT_FILL = color(0.9) def __init__(self, glyphName, font, pointStroke=None, pointStrokeWidth=0, pointFill=None, pointSize=None, pointLine=None, **kwargs): GlyphView.__init__(self, glyphName, font, **kwargs) # Flag to show point markers. Can be one of (None, True, False, Color) # Default is self.POINT_STROKE color(1, 0, 0) if pointFill and not isinstance(pointFill, Color): # In case it is "True" pointFill = self.POINT_FILL self.pointFill = pointFill if pointStroke and not isinstance(pointStroke, Color): # In case it is "True" pointStroke = self.POINT_STROKE self.pointStroke = pointStroke self.pointStrokeWidth = pointStrokeWidth or 0 # Thickness of metrics lines self.pointSize = pointSize or 0 # Size of point markers if pointLine and not isinstance(pointLine, Color): # In case it is "True" pointLine = self.POINT_STROKE self.pointLine = pointLine # Flag or color of lines between the point markers def drawContent(self, ox, oy, doc, page, parent): """Draw the content of this single glyph/string fitting, with line indicators of vertical metrics. TODO: Show more font metrics and glyph metrics here. Add labels of values and names. """ scale = self.fontSize / self.font.info.unitsPerEm descender = scale * self.font.info.descender # Scaled descender of current font/fontSize # If the fontSize is down scaled to match the string width, then evenely # distribute the extra vertical space above and below that scaled fontSize. baseline = (self.h - self.fontSize ) / 2 - descender # Distance from baseline to bottom y y = oy + baseline # Calculate the position of the baseline. for char in self.glyphName: # Just the first glyph of the string for now. glyph = self.font[char] glyphPath = glyph.getGlyphPath(doc.context) #print(glyphPath._path) # Showing the points in a BezierPath # Draw stroke and fill separate, to overwrite the inline # side of a thick stroke. doc.context.save() doc.context.scale(scale) doc.context.translate(ox / scale, y / scale) doc.context.stroke(self.textStroke, self.textStrokeWidth) doc.context.fill(None) doc.context.drawPath(glyphPath) # Something else could draw here, between stroke-fill doc.context.fill(self.textFill) doc.context.stroke(None) doc.context.drawPath(glyphPath) doc.context.restore() # If line color and line width are defined. if self.lineStroke and self.lineWidth: doc.context.stroke(self.lineStroke, self.lineWidth) doc.context.line((ox, y), (ox + self.w, y)) # Draw baseline xHeight = scale * self.font.info.xHeight ly = oy + baseline + xHeight doc.context.line((ox, ly), (ox + self.w, ly)) capHeight = scale * self.font.info.capHeight ly = oy + baseline + capHeight doc.context.line((ox, ly), (ox + self.w, ly)) ly = oy + baseline + descender doc.context.line((ox, ly), (ox + self.w, ly)) # Descender ly = oy + baseline + self.fontSize + descender doc.context.line((ox, ly), (ox + self.w, ly)) # Descender if self.pointFill or (self.pointStroke and self.pointStrokeWidth): radius = (self.pointSize or 16) / 2 doc.context.save() doc.context.scale(scale) doc.context.translate(ox / scale, y / scale) # First draw the connecting lines between the points # Instead of using glyph.points we iterate through a list of # PointContext instances, that hold a sequence of 7 points. lineWidth = self.pointStrokeWidth or 1 doc.context.stroke(self.pointLine or self.pointStroke, lineWidth / 2) # Point context naming # O O O O O O O O O O O O # p_3 p_2 p_1 p p1 p2 p3 for pc in glyph.pointContexts: # Calculate the positions of marker + line + marker, # so the line does not overlap to the middle of the marker. doc.context.fill(None) if pc.p_1.offCurve and pc.p.onCurve: doc.context.line(pc.p_1.p, pc.p.p) if pc.p.onCurve and pc.p1.offCurve: doc.context.line(pc.p.p, pc.p1.p) for p in glyph.points: # Now draw all markers, only for the middle point doc.context.fill(self.pointFill) if p.offCurve: # Off-curves radius 60% point size doc.context.oval(p.x - radius * 0.6, p.y - radius * 0.6, radius * 2 * 0.6) else: doc.context.oval(p.x - radius, p.y - radius, 2 * radius) doc.context.restore()