def __init__(self, x=None, y=None, w=None, h=None, name=None, template=None, fill=None, stroke=None, strokeWidth=0, pt=None, pr=None, pb=None, pl=None): self.x = x or 0 # (x, y) position of the element from bottom left of parent. self.y = y or 0 self.w = w # Width and height of the element bounding box self.h = h self.fill = color(fill) # Default is drawing a black rectangle. self.stroke = color(stroke) # Default is drawing no stroke frame self.strokeWidth = strokeWidth self.padding = pt, pr, pb, pl # Initialize the padding self.elements = [] # Storage in case there are child elements # Optional name, e.g. for template or element finding. Defaults to class name. self.name = name or self.__class__.__name__ self.template = template # Optional template function for this element. # Allow elements, pages and templates to initialize themselves # by implementing the self.initialize method. self.initialize()
def _extractColor(self, layer): fillColor = noColor if layer.style.fills: fill = layer.style.fills[0] if fill.isEnabled: # In Sketch colors can be defined, and still be disabled. sketchColor = fill.color fillColor = color(r=sketchColor.red, g=sketchColor.green, b=sketchColor.blue, a=sketchColor.alpha) strokeColor = noColor strokeWidth = 0 sketchBorders = layer.style.borders if sketchBorders: # TODO: Extract element border info here too sketchBorder = sketchBorders[0] if sketchBorder.isEnabled: # In Sketch colors can be defined, and still be disabled. sketchColor = sketchBorder.color strokeColor = color(r=sketchColor.red, g=sketchColor.green, b=sketchColor.blue, a=sketchColor.alpha) strokeWidth = pt(sketchBorder.thickness) return fillColor, strokeColor, strokeWidth
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 fromBabelString(self, bs): """ >>> bs = BabelString('abcd', style=dict(font='Roboto-Regular', fontSize=pt(18))) >>> context = SketchContext() >>> sas1 = context.fromBabelString(bs) >>> sas1 <SketchAttributedString> >>> sas2 = context.fromBabelString(bs) >>> sas1 == sas2 True """ assert isinstance(bs, BabelString) ALIGNMENTS = {LEFT: 0, RIGHT: 1, CENTER: 2, JUSTIFIED: None} cIndex = 0 sas = SketchAttributedString() sas.string = '' style = None attrs = sas.attributes for run in bs.runs: if style is None or run.style is not None: style = run.style ssa = SketchStringAttribute() ssa.location = cIndex ssa.length = len(run.s) sas.string += run.s cIndex += ssa.length ssa.attributes.MSAttributedStringFontAttribute = SketchFontDescriptor( ) fd = ssa.attributes.MSAttributedStringFontAttribute.attributes fontName = run.style.get('fontName') font = run.style.get('font') if fontName is None: if isinstance(font, str): fontName = font elif isinstance(font, Font): fontName = font.name if fontName is None: fontName = DEFAULT_FONT fd.name = fontName fd.size = upt(run.style.get('fontSize', 12)) tc = run.style.get('textFill', color(0)) # Leading is a special thing in Sketch! ssa.attributes = SketchAttributes() ssa.attributes.kerning = upt(style.get('tracking', 0), base=fd.size) ssa.attributes.textStyleVerticalAlignmentKey = 0 # ??? ssa.attributes.paragraphStyle = SketchParagraphStyle() ssa.attributes.paragraphStyle.alignment = ALIGNMENTS.get( style.get('xAlign', JUSTIFIED)) ssa.attributes.MSAttributedStringColorAttribute = SketchColor( red=tc.r, green=tc.g, blue=tc.b, alpha=tc.a) attrs.append(ssa) return sas
def compose(self, doc, parent=None): """Compose the cell as background color, with recipes text block on top. """ label = self._getLabel() if self.layout == SPOTSAMPLE: # Mark approximated color recipes by parenthesis. # They are not an exact match, but closest known value for this color. bs = BabelString(label, self.style) tw, th = bs.textSize # Used padding-bottom (self.pb) also as gutter between color rectangle and labels e = Rect(x=self.pl, y=th+self.pb, w=self.pw, h=self.h-th-self.pb, fill=self.c) self.addElement(e) e = Text(bs, x=self.w/2, y=th-self.style.get('fontSize') + self.pb, w=self.w, h=self.h) self.addElement(e) else: # Default layout is OVERLAY. # Check the text color to be enough contrast with the background. # Otherwise flip between black and white. style = copy(self.style) # Copy as we are going to alter it if self.c.gray < 0.33: # Dark color? labelText = color(1) # White label text else: labelText = color(0) # Black label text style['fill'] = labelText bs = BabelString(label, style) tw, th = bs.textSize e = Rect(x=self.pl, y=self.pb, w=self.pw, h=self.ph, fill=self.c) self.addElement(e) e = Text(bs, x=self.w/2, y=th-self.style.get('fontSize')*2/3 + self.pb, w=self.w, h=self.h) self.addElement(e)
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: # Make sure is it a Color instance. if not isinstance(c, Color): 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)
def asBabelString(self, sas): """Convert the SketchAttributedString skText into a generic BabelString. * https://developer.apple.com/documentation/foundation/nsattributedstring * https://developer.apple.com/documentation/coretext/ctframesetter-2eg >>> import pysketch >>> from pysketch.sketchapi import SketchApi >>> path = getResourcesPath() + '/sketch/TemplateText.sketch' >>> context = SketchContext(path) >>> skTextBox = context.b.artboards[0].layers[0] # Find the Sketch text box >>> sas = skTextBox.attributedString # SketchText inside the box >>> sas <SketchAttributedString> >>> len(sas.attributes) 3 >>> bs = context.asBabelString(sas) # Convert to generic BabelString >>> len(bs.runs) # We originally had 3 typographic parameter runs 3 >>> bs # Represented by joining text strings of all runs $Type & sty...$ >>> bs.runs[0].s, bs.runs[0].style['font'], bs.runs[0].style['fontSize'] ('Type ', <Font PageBot-Book>, 90) >>> bs.runs[1].s, bs.runs[1].style['font'], bs.runs[1].style['fontSize'] ('&', <Font PageBot-Book>, 200) """ """ >>> sas2 = context.fromBabelString(bs) # New conversion >>> #sas == sas2 # Bi-directional conversion works True >>> # Now change the BabelString >>> bs.runs[0].style['font'] = 'Verdana-Bold' >>> bs.runs[1].style['font'] = 'Verdana-Italic' >>> bs.runs[1].style['textFill'] = color(1, 0, 0) >>> bs.runs[2].style['textFill'] = color(1, 0, 0.5) >>> bs.runs[2].s = ' changed' # Change text of the run >>> sas2 = context.fromBabelString(bs) # New conversion >>> skTextBox.attributedString = sas2 >>> from pagebot.filepaths import getExportPath >>> context.save('%s/TemplateTextChanged.sketch' % getExportPath()) # Save as other document >>> bs2 = context.asBabelString(sas2) # Convert back to pbs >>> bs == bs2 # This should be identical, after bi-directional conversion. True """ assert isinstance(sas, SketchAttributedString), "%s.asBabelString: @sas has class %s" % ( self.__class__.__name__, sas.__class__.__name__) ALIGNMENTS = {0: LEFT, 1: RIGHT, 2: CENTER, None: JUSTIFIED} bs = None for attrs in sas.attributes: # Font, fontSize and tracking are easy to extract. # More difficult is the leading, as Skype does not really keep # runs with styles and leading. fd = attrs.attributes.MSAttributedStringFontAttribute.attributes font = findFont(fd.name) if font is None: # If not found (e.g. OSX name, then keep the name) print('### Font not found or not supported type (.ttc) "%s", using "%s" instead' % (fd.name, DEFAULT_FONT)) font = findFont(DEFAULT_FONT) fontSize = fd.size tracking = em(attrs.attributes.kerning/fontSize) # Wrong Sketch name for tracking #print('----', attrs) # attrs = SketchStringAttribute # location # length # attributes = SketchAttributes # MSAttributedStringFontAttribute # MSAttributedStringColorAttribute # textStyleVerticalAlignmentKey # kerning # paragraphStyle = SketchParagraphStyle # alignment # minimumLineHeight # maximumLineHeight # paragraphSpacing #print('----', sas.string[attrs.location:attrs.location+attrs.length]) #print('fontSize:', fd.name, fd.size) #print('minimumLineHeight:', attrs.attributes.paragraphStyle.minimumLineHeight) #print('maximumLineHeight:', attrs.attributes.paragraphStyle.maximumLineHeight) #print('paragraphSpacing:', attrs.attributes.paragraphStyle.paragraphSpacing) #print('...') #print('--d-d-d-', verticalAlignment) #paragraphStyle.maximumLineHeight) #print('3-3-3-', paragraphStyle.alignment) paragraphStyle = attrs.attributes.paragraphStyle leading = em(paragraphStyle.maximumLineHeight/fontSize) #minLeading = paragraphStyle.minimumLineHeight #maxLeading = paragraphStyle.maximumLineHeight #paragraphSpacing = paragraphStyle.paragraphSpacing #print('vvvvvv', font, fontSize, leading, paragraphStyle, tracking) #print('xxxxxx', minLeading, maxLeading, paragraphSpacing) # Fill color of the this run. cc = attrs.attributes.MSAttributedStringColorAttribute textFill = color(r=cc.red, g=cc.green, b=cc.blue, a=cc.alpha) # 0 = TOP, verticalAlignment = attrs.attributes.textStyleVerticalAlignmentKey # Construct the run style from the extracted parameters. style = dict(font=font, fontSize=fontSize, textFill=textFill, tracking=tracking, yAlign=BASELINE, leading=leading, xAlign=ALIGNMENTS.get('alignment', LEFT) ) # Get the string, using the location and length in the full string. s = sas.string[attrs.location:attrs.location+attrs.length] if bs is None: bs = self.newString(s, style) else: bs.add(s, style) #bs.MIN = paragraphStyle.minimumLineHeight #bs.MAX = paragraphStyle.maximumLineHeight return bs