def getVariableFont(fontOrPath, location, install=True, styleName=None, normalize=True): u"""The variablesFontPath refers to the file of the source variable font. The nLocation is dictionary axis locations of the instance with values between (0, 1000), e.g. dict(wght=0, wdth=1000) or values between (0, 1), e.g. dict(wght=0.2, wdth=0.6). Set normalize to False if the values in location already are matching the axis min/max of the font. If there is a [opsz] Optical Size value defined, then store that information in the font.info.opticalSize. The optional *styleName* overwrites the *font.info.styleName* of the *ttFont* or the automatic location name.""" if isinstance(fontOrPath, basestring): varFont = Font(fontOrPath, name=path2FontName(fontOrPath)) else: varFont = fontOrPath fontName, path = generateInstance(varFont.path, location, targetDirectory=getInstancePath(), normalize=normalize) # Answer the generated Variable Font instance. Add [opsz] value if is defined in the location, otherwise None. return Font(path, name=fontName, install=install, opticalSize=location.get('opsz'), location=location, styleName=styleName)
def __repr__(self): """ >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' >>> font = getFont(path) >>> str(font) '<Font Roboto-Black>' """ return '<Font %s>' % (path2FontName(self.path) or self.name or 'Untitled').strip()
def __repr__(self): """ >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> str(font) '<Font Roboto-Black>' """ return '<Font %s>' % (path2FontName(self.path) or self.name or 'Untitled')
def weightMatch(self, weight): """Answers level of matching for the (abbreviated) weight name or number with font, in a value between 0 and 1. Currently there is only no-match (0) and full-match (1). Future implementations may give a float indicator for the level of matching, so the caller can decide on the level of threshold. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.info.weightClass 900 >>> font.weightMatch(0) # Bad match 0 >>> font.weightMatch(800) # Bad match 0 >>> font.weightMatch(900) # Exact match 0 >>> font.weightMatch(0) # Bad match - 0 >>> font.weightMatch('Black') # Black --> Exact match on 900 1.0 >>> font.weightMatch('Light') # Light --> No match on 900 0 >>> path = getTestFontsPath() + '/google/roboto/Roboto-Regular.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.info.weightClass 400 """ """ TODO: Fix these tests >>> font.weightMatch(400) # Match 1.0 >>> font.weightMatch('Regular') # Match 1.0 >>> font.weightMatch('Condensed') # Matching with width name has no match. 0 """ if isinstance(weight, (float, int)): # Comparing by numbers # Compare the weight as number as max difference to what we already have. w = self.info.weightClass if w in FONT_WEIGHT_MATCHES.get(weight, []): return 1.0 # Exact match else: # Comparing by string fileName = path2FontName(self.path) for w in FONT_WEIGHT_MATCHES.get(weight, []): if not isinstance(w, (float, int)) and (w in fileName or w in self.info.styleName): return 1.0 # Exacly match return 0 # No match
def _recursivelyCollectFontPaths(path, collectedFontPaths): u"""Recursive helper function for getFontPaths. If the fileName already exists in the fontPaths, then ignore.""" if os.path.exists(path): if os.path.isdir(path): for fileName in os.listdir(path): dirPath = path + '/' + fileName _recursivelyCollectFontPaths(dirPath, collectedFontPaths) else: fontName = path2FontName(path) # File name without extension used as key, works for Flat and DrawBot. # If fontName is None, it does not have the right extension. # Note that files with the same file name will be overwritten, we expect them to be unique in the OS. if fontName is not None: collectedFontPaths[fontName] = path
def getWeightNames(family): # Collect all weight names in increasing order. weightClasses = family.getWeights() weightNames = '' index = 0 for weightClass, fonts in sorted(weightClasses.items()): weightName = '[%d] ' % weightClass for font in fonts: if index > 24: return weightNames weightName += ' ' + path2FontName(font.path) index += 1 weightNames += ' ' + weightName return weightNames
def widthMatch(self, width): """Answers level of matching for the (abbreviated) width name or number with font. Currently there is only no-match (0) and full-match (1). Future implementations may give a float indicator for the level of matching, so the caller can decide on the level of threshold. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.info.widthClass 5 >>> font.widthMatch(0) # Bad match 0 >>> font.widthMatch(4) # Close match fails 0 >>> font.widthMatch(5) # Exact match 1.0 >>> font.widthMatch(6) # Close match fails 0 >>> font.widthMatch(10) # Bad match 0 >>> path = getTestFontsPath() + '/google/roboto/Roboto-Bold.ttf' # We know this exists in the PageBot repository >>> font = Font(path) >>> font.info.widthClass 5 >>> font.widthMatch(5) # Wrong exact match --> 1000 due to wrong font.info.widthClass 1.0 >>> font.widthMatch('Wide') # No match on "Wide" 0 >>> #font.widthMatch('Cond') # Exact match on "Cond" 1.0 """ if isinstance(width, (float, int)): # Compare the width as number as max difference to what we already have. w = self.info.widthClass if w <= 100: # Normalize to 1000 w *= 100 if w in FONT_WIDTH_MATCHES.get(width, []): return 1.0 else: # Comparing by string fileName = path2FontName(self.path) for w in FONT_WIDTH_MATCHES.get(width, []): if not isinstance(w, (float, int)) and (w in fileName or w in self.info.styleName): return 1.0 return 0
def match(self, name=None, weight=None, width=None, italic=None): """Answers a value between 0 and 1 to the amount that self matches the defined parameters. Only defined values count in the matching. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' >>> font = getFont(path) >>> font.info.widthClass, font.info.weightClass (5, 900) >>> font.match(name='Roboto') 1.0 >>> font.match(name='Robo', weight='Black') 1.0 >>> # Only match on the name. >>> font.match(name='Robo', weight='Light') 0.5 >>> font.match(name='Robo', width=5) 1.0 """ """ TODO: Fix these tests >>> font.match(name='Robo', weight=900, width=5) 1.0 >>> font.match(name='Robo', weight=900, width=5, italic=True) 0.75 >>> font.match(name='Robo', weight='Black', width=5, italic=False) 1.0 >>> font.match(name='Robo', weight='Blackish', width=5, italic=False) 0.75 """ matches = [] fontName = path2FontName(self.path) if name is not None: matches.append(self.nameMatch(name)) # Currently the matches only answer 0 or 1. In future implementations this value may vary # as float between 0 and 1. if weight is not None: matches.append(self.weightMatch(weight)) if width is not None: matches.append(self.widthMatch(width)) if italic is not None: matches.append(italic == self.isItalic()) if not matches: return 0 # Avoif division by zero return sum(matches)/len(matches) # Normalize to value between 0..1
def nameMatch(self, pattern): u"""Answer level of matching between pattern and the font file name or font.info.fullName. Pattern can be a single string or a list of string. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> path = getTestFontsPath() + '/google/roboto/Roboto-Black.ttf' # We know this exists in the PageBot repository >>> font = getFont(path) >>> font.nameMatch('Black') 1.0 >>> font.nameMatch('Blackish') 0 >>> font.nameMatch(('Roboto', 'Black')) 1.0 """ fontName = path2FontName(self.path) if not isinstance(pattern, (list, tuple)): pattern = [pattern] for part in pattern: if not (part in fontName or part in self.info.fullName): return 0 return 1.0
def isItalic(self): """Answers if this font should be considered to be italic. Currently there is only no-match (0) and full-match (1). Future implementations may give a float indicator for the level of matching, so the calling function can decide on the level of threshold. >>> from pagebot.fonttoolbox.fontpaths import getTestFontsPath >>> fontPath = getTestFontsPath() >>> path = getTestFontsPath() + '/google/roboto/Roboto-BlackItalic.ttf' >>> font = getFont(path) >>> font.isItalic() 1 >>> path = getTestFontsPath() + '/google/roboto/Roboto-Bold.ttf' >>> font = Font(path) >>> font.isItalic() 0 """ if self.info.italicAngle: return 1 for altName in FONT_ITALIC_MATCHES: if altName in path2FontName(self.path) or altName in self.info.styleName: return 1.0 return 0
def buildSpecimenPages(doc, family, pn): for font in family.getFonts(): page = doc[pn] page.padding = PADDING page.gridX = GRID_X pageTitle = path2FontName(font.path) # Add filling rectangle for background color of the old paper book. # Set z-azis != 0, to make floating elements not get stuck at the background newRect(z=-10, w=W, h=H, parent=page, fill=PAPER_COLOR) # During development, draw the template scan as background # Set z-azis != 0, to make floating elements not get stuck at the background if SHOW_TEMPLATE: newImage(ATF_PATH, x=0, y=0, z=-10, w=W, parent=page) # Centered title: family name and style name of the current font. titleBs = context.newString(pageTitle, style=dict(font=font.path, xTextAlign=CENTER, textFill=0)) titleBox = newTextBox(titleBs, parent=page, h=2*L, conditions=[Top2Top(), Fit2Width()], fill=DEBUG_COLOR0) titleBox.solve() largeSampleBox = newTextBox('', parent=page, w=C1+G/2, conditions=[Float2Top(), Left2Left(), Fit2Bottom()], fill=DEBUG_COLOR1) largeSampleBox.solve() # In order to fit different words in the fixed space, they will vary in size. # But as the variation in sizes is larger than the variation in size, we'll calculate the strings # first for the various word lengths and then sort them by size. largeSampleSizes = {} for n in (4, 5, 6, 7, 7, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10): word = getCapitalizedWord(n) if word is None: continue if len(largeSampleSizes) > 10: break sample = context.newString(word+'\n', style=dict(font=font.path, rLeading=1), w=C1, pixelFit=False) sampleFontSize = int(round(sample.fontSize)) if not sampleFontSize in largeSampleSizes: largeSampleSizes[sampleFontSize] = sample print sorted(largeSampleSizes.keys()) largeSample = context.newString('') for sampleFontSize, sample in sorted(largeSampleSizes.items(), reverse=True): label = context.newString('%d Points\n' % round(sampleFontSize), style=labelStyle) #if largeSample.h + sample.h > largeSampleBox.h: # break largeSample += label + sample largeSampleBox.setText(largeSample) for fontSize, numChars in ((12, 8), (10, 13), (8, 16)): smallSamples = context.newString(getCapWord(numChars), style=dict(font=font.path), w=C2) label = context.newString('%d Points\n' % round(smallSamples.fontSize), style=labelStyle) shortWordsSample = context.newString(getShortWordText(), style=dict(font=font.path, fontSize=smallSamples.fontSize, rLeading=1)) newTextBox(label + smallSamples + ' ' + shortWordsSample, parent=page, w=C2+G/2, h=80, ml=G/2, mb=L, conditions=[Right2Right(), Float2Top(), Float2Left()], fill=DEBUG_COLOR1) label = context.newString('%d Points\n' % fontSize, style=labelStyle) smallSamples = context.newString(blurb.getBlurb('article', noTags=True), style=dict(font=font.path, fontSize=fontSize)) newTextBox(label + smallSamples, parent=page, w=C2-2, h=80, mb=L, ml=G/2, conditions=[Right2Right(), Float2Top()], fill=DEBUG_COLOR1) glyphSetFrame = newRect(parent=page, mb=L, ml=G/2, padding=L, borders=dict(line=INLINE, stroke=0, strokeWidth=0.5), conditions=[Right2Right(), Float2Top(), Float2Left(), Fit2Right(), Fit2Bottom()], fill=DEBUG_COLOR2) glyphSet = context.newString('Subset of characters in Complete Font\n', style=dict(font=font.path, fontSize=8, xTextAlign=CENTER, rParagraphTopSpacing=0.25, rParagraphBottomSpacing=0.5)) glyphSet += context.newString(GLYPH_SET, style=dict(font=font.path, fontSize=23, xTextAlign=CENTER, leading=32)) newTextBox(glyphSet, parent=glyphSetFrame, padding=(1.5*L, L, L, L), borders=dict(line=INLINE, stroke=0, strokeWidth=0.25), conditions=[Left2Left(), Fit2Right(), Top2Top(), Fit2Bottom() ], fill=DEBUG_COLOR3) pn += 1 return pn
def _get_name(self): return path2FontName(self.font.path)
def drawFontLabel(p, varFamily, f, fIndex=None, fAxis=None): x, y = p print(f.info.styleName, f.info.weightClass, f.info.widthClass) glyphH = f[GLYPH] if not glyphH.width: print(glyphH, 'No width') return s = 0.05 * 1000 / f.info.unitsPerEm leading = 2048 / f.info.unitsPerEm stroke(None) fill(0) save() translate(x - glyphH.width / 2 * s, y - leading - 50) scale(s) drawPath(glyphH.path) restore() y -= leading + 50 save() pathLabel = '-'.join(path2FontName(f.path).split('-')[1:]) #label = path2FontName(f.path) if fAxis is not None: label = '@' + fAxis elif fIndex is None: label = '' else: label = '#%d ' % fIndex label += '%s\n(%s)\n%d' % (pathLabel.replace('.ttf', '').replace( '_', '\n').replace('-', '\n'), f.info.styleName, f.info.weightClass) bs = context.newString(label, style=dict(fontSize=10, align='center')) tw, th = bs.size c.text(bs, (x - tw / 2, y - 14)) restore() y -= leading + th - 22 # Draw marker on actual position of H.stem and H.weight as green dot stemValues = f.analyzer.stems.keys() if stemValues: # Cannot find H-stem, skip this marker stem = min(stemValues) # XOPQ (counter) + H.stem == H.width - H.stem - H.lsb - H.rsb width = glyphH.width - stem - glyphH.leftMargin - glyphH.rightMargin c.fill((0, 0.5, 0)) c.stroke(None) R = 16 weightLoc, widthLoc = stem, width / 2 c.oval(weightLoc - R / 2, widthLoc - R / 2, R, R) if fAxis is not None: label = '@' + fAxis elif fIndex is None: label = '' else: label = '#%d\n' % fIndex bs = c.newString(label + ('S:%d\nW:%d\n%d' % (weightLoc, widthLoc, f.info.weightClass)), style=dict(fontSize=10, xTextAlign='center', textFill=blackColor)) tw, th = bs.size c.text(bs, (weightLoc - tw / 2, widthLoc - 24)) if varFamily.originFont is f: # If one of these is the guessed origin font, then draw marker c.fill(None) c.stroke((0, 0.5, 0), 2) # Stroke color and width R = 23 c.oval(weightLoc - R / 2, widthLoc - R / 2, R, R) else: pass
def guessVarFamilyFromPaths(basePath, name=None): """Initialize by guessing the self._font axis locations. """ paths = findFontPaths(basePath) name = name or path2FontName(basePath) return VarFamily(name, paths)