def path2ScaledImagePath(self, path, w, h, index=None, exportExtension=None): """Answers the path to the scaled image. >>> context = DrawBotContext() >>> context.path2ScaledImagePath('/xxx/yyy/zzz/This.Is.An.Image.jpg', 110, 120) ('/xxx/yyy/zzz/_scaled/', 'This.Is.An.Image.110x120.0.jpg') >>> path, fileName = context.path2ScaledImagePath('/xxx/yyy/zzz/This.Is.An.Image.jpg', 110, 120) >>> path in ('/xxx/yyy/zzz/scaled/', '/xxx/yyy/zzz/_scaled/') True >>> fileName 'This.Is.An.Image.110x120.0.jpg' """ # /_scaled will be ignored with default .gitignore settings. # If docs/images/_scaled need to be committed into Git repo, # then remove _scaled from .gitignore. cachePath = '%s/%s/' % (path2Dir(path), self.SCALED_PATH) fileNameParts = path2Name(path).split('.') if not exportExtension: # If undefined, take the original extension for exporting the cache. exportExtension = fileNameParts[-1].lower() cachedFileName = '%s.%dx%d.%d.%s' % ('.'.join( fileNameParts[:-1]), w, h, index or 0, exportExtension) return cachePath, cachedFileName
def _get_parametricAxisFonts(self): """Generate the dictionary with parametric axis fonts. Key is the parametric axis name, value is the font instance (there can only be one font per axis). If the fonts don't exist as cached files, they are created. The font currently under self.defaultFont is used a neutral, for which all delta's are 0.""" origin = self.defaultFont if origin is None: return None # Create directory for the parametric axis fonts, if it does not exist. paFontDir = path2ParentPath(origin.path) + '/_export/@axes' if not os.path.exists(paFontDir): os.makedirs(paFontDir) for axisName in self.PARAMETRIC_AXES: self._parametricAxisFonts[axisName] = [] for extreme in ('_min', '_max'): fileNameParts = path2Name(origin.path).split('.') paFontPath = paFontDir + '/' + '.'.join( fileNameParts[:-1] ) + '@' + axisName + extreme + '.' + fileNameParts[-1] if os.path.exists(paFontPath): os.remove(paFontPath) shutil.copy(origin.path, paFontPath) self._parametricAxisFonts[axisName].append(Font(paFontPath)) self._parametricAxisMetrics[axisName] = origin.analyzer.stems return self._parametricAxisFonts
def addFont(self, font, fontKey=None, fontStyle=None): if fontKey is None: fontKey = path2Name(font.path) # This must be unique in the family, used as key in self.fonts. assert fontKey not in self.fonts, ('Font "%s" already in family "%s"' % (fontKey, self.fonts.keys())) if fontStyle is None: fontStyle = font.info.styleName # It is allowed to have multiple fonts with the same style name. # Store the font under unique fontKey. self.fonts[fontKey] = font # Create list entry for fontStyle, if it does not exist. if not fontStyle in self.fontStyles: self.fontStyles[fontStyle] = [] # Keep list, there may be fonts with the same style name. self.fontStyles[fontStyle].append(font)
def path2ScaledImagePath(self, path, w, h, index=None, exportExtension=None): """Answers the path to the scaled image. >>> context = PdfLibContext() >>> context.path2ScaledImagePath('/xxx/yyy/zzz/This.Is.An.Image.jpg', 110, 120) ('/xxx/yyy/zzz/_scaled/', 'This.Is.An.Image.110x120.0.jpg') """ cachePath = '%s/_scaled/' % path2Dir(path) fileNameParts = path2Name(path).split('.') if not exportExtension: # If undefined, take the original extension for exporting the cache. exportExtension = fileNameParts[-1].lower() cachedFileName = '%s.%dx%d.%d.%s' % ('.'.join(fileNameParts[:-1]), w, h, index or 0, exportExtension) return cachePath, cachedFileName
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(path2Name(f.path).split('-')[1:]) #label = path2Name(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) fs = FormattedString(label, fontSize=10, align='center') tw, th = textSize(fs) text(fs, (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=0)) tw, th = c.textSize(bs) 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): u"""Initialize by guessing the self._font axis locations. """ paths = findFontPaths(basePath) name = name or path2Name(basePath) return VarFamily(name, paths)
def checkOutlineCompatibility(self, refFont, font, glyphName): report = [] if refFont is None or not glyphName in refFont: report.append('Glyph "%s" not in reference master "%s"' % (glyphName, path2Name(refFont.path))) if font is None or not glyphName in font: report.append('Glyph "%s" not in master "%s"' % (glyphName, path2Name(font.path))) if report: # Errors here already, then they are fatal. No need for firther checking. return report rg = refFont[glyphName] g = font[glyphName] # Compare number of contours if len(g) > len(rg): report.append( u'Glyph "%s" has more contours (%d) in "%s" than in "%s" (%d).' % (glyphName, len(g), refFont.info.styleName, font.info.styleName, len(rg))) elif len(g) < len(rg): report.append( u'Glyph "%s" has fewer contours (%d) in "%s" than in "%s" (%d).' % (glyphName, len(g), refFont.info.styleName, font.info.styleName, len(rg))) else: # Same amount of contours, we can compare them. for cIndex, contour in enumerate(g.contours): rContour = rg.contours[cIndex] if len(contour) > len(rContour): report.append( u'Glyph "%s" contour #%d has more points (%d) than “%s” (%d).' % (glyphName, cIndex, len(contour), refFont.info.styleName, len(rContour))) elif len(contour) < len(rContour): report.append( u'Glyph "%s" contour #%d has fewer points (%d) than “%s” (%d).' % (glyphName, cIndex, len(contour), refFont.info.styleName, len(rContour))) else: # Contour lengths are equal, now we can test on point types. # Test on contour clockwise directions. if g.isClockwise(contour) != rg.isClockwise(rContour): report.append( u'Glyph "%s" contour #%d has reversed direction of “%s”.' % (glyphName, cIndex, refFont.info.styleName)) else: # Now the length and directions are the same, we can test on the type of points. pIndex = 0 for p in contour: if p.onCurve != rContour[pIndex].onCurve: report.append( u'"%s" Glyph "%s" point #%d (%d,%d,%s) is not same type as point #%d (%d,%d,%s) in “%s”.' % (font.info.styleName, glyphName, pIndex, p.x, p.y, p.onCurve, pIndex, rContour[pIndex].x, rContour[pIndex].y, rContour[pIndex].onCurve, refFont.info.styleName)) pIndex += 1 components = g.components rComponents = rg.components if len(components) > len(rComponents): baseGlyphs = [] for component in components: baseName = component.baseGlyph if not baseName in font: baseName += self.DOES_NOT_EXIST baseGlyphs.append(baseName) rBaseGlyphs = [] for component in rComponents: baseName = component.baseGlyph if not baseName in refFont: baseName += self.DOES_NOT_EXIST rBaseGlyphs.append(baseName) report.append( u'Glyph "%s" has %d more components (%s) than %s (%s) in "%s".' % (glyphName, len(baseGlyphs) - len(rBaseGlyphs), ','.join(baseGlyphs), refFont.info.styleName, ','.join(rBaseGlyphs), refFont.info.styleName)) elif len(components) < len(rComponents): baseGlyphs = [] for component in components: baseName = component.baseGlyph if not baseName in font: baseName += self.DOES_NOT_EXIST baseGlyphs.append(baseName) rBaseGlyphs = [] for component in rComponents: baseName = component.baseGlyph if not baseName in refFont: baseName += self.DOES_NOT_EXIST rBaseGlyphs.append(baseName) report.append( u'Glyph "%s" has %d fewer components (%d) than %s (%s) in "%s".' % (glyphName, len(rBaseGlyphs) - len(baseGlyphs), len(components), refFont.info.styleName, len(rComponents), refFont.info.styleName)) return report
def drawFontLabel(p, varFamily, f, fIndex=None, fAxis=None): x, y = p #print f.info.styleName, f.info.weightClass, f.info.widthClass if not GLYPH in f: print('###', GLYPH, 'not in font', f.path) return glyphH = f[GLYPH] if not glyphH.width: print('###', GLYPH, 'No width', f.path) return stems = glyphH.analyzer.stems # Draw marker on actual position of H.stem and H.weight as green dot stemValues = stems.keys() s = 0.05 * 1000 / f.info.unitsPerEm leading = 2048 / f.info.unitsPerEm c.stroke(noColor) c.fill(Color(0)) save() translate(x - glyphH.width / 2 * s, y - leading - 50) scale(s) drawPath(glyphH.path) restore() y -= leading + 50 save() pathLabel = '-'.join(path2Name(f.path).split('-')[1:]) #label = path2Name(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) fs = FormattedString(label, style=dict(fontSize=10, align=CENTER)) tw, th = textSize(fs) text(fs, (x - tw / 2, y - 14)) restore() y -= leading + th - 22 if stemValues: # Cannot find H-stem, skip this marker stem = min(stemValues) stemS = '%d' % stem normalizedStemS = '%0.2f' % (stem * 1000 / f.info.unitsPerEm) # XOPQ (counter) + H.stem == H.width - H.stem - H.lsb - H.rsb width = glyphH.width - stem - glyphH.leftMargin - glyphH.rightMargin c.fill(Color(0, 0.5, 0)) c.stroke(noColor) 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=Color(0))) tw, th = c.textSize(bs) 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(noColor) c.stroke(Color(0, 0.5, 0), 2) # Stroke color and width R = 23 c.oval(weightLoc - R / 2, widthLoc - R / 2, R, R) # Find distance between left side of 2 stems of the H eqStems = glyphH.analyzer.stems.values() x1 = 10000000000 x2 = 0 for stems in eqStems: for stem in stems: x1 = min(x1, stem.parent[0]) x2 = max(x2, stem.parent[0]) else: stemS = 'No stem' normalizedStemS = '-' x1 = x2 = 0 #print 'No stem for', glyphH.font exportCSV.write('%s,%d,%s,%s,%s,%d,%d,%d,%d,%d\n' % ( f.path.split('/')[-1], f.info.unitsPerEm, stemS, normalizedStemS, x2 - x1, (x2 - x1) * 1000 / f.info.unitsPerEm, #normalizedWidth, #normalizedWeight, #guessedOS2Width, #guessedOS2Weight, f.info.widthClass, f.info.weightClass, f.info.capHeight, f.info.capHeight * 1000 / f.info.unitsPerEm))
def _get_name(self): return path2Name(self.font.path)
def findFont(self, name=None, weight=None, width=None, italic=None): u"""Answer the font that is the closest match on name, weight as name or weight as number, width as name or width as number and italic angle as name or number, if any of these are defined. In case there is one or more fonts in the family then there always is a closest match. If the family is empty, None is anwere. >>> familyName = 'Roboto' # We know this exists in the PageBot repository >>> families = guessFamiliesByPatterns(familyName) >>> familyName in families.keys() True >>> family = families[familyName] >>> len(family) 18 >>> sorted(family.keys())[1] 'Roboto-BlackItalic.ttf' >>> regularName = 'Roboto-Regular.ttf' >>> family._matchWeights('Regular', family[regularName]) # Match on name 70 >>> family._matchWeights('Normal', family[regularName]) # Match on alternative name 70 >>> family._matchWeights('Bold', family[regularName]) # No match on full name 0 >>> family._matchWidths(5, family[regularName]) # Full match in width 5 1000 >>> family._matchWidths('Normal', family[regularName]) # Full match in normal width 1000 >>> family._matchWidths(5, family[regularName]) # Full match in width 5 1000 >>> family._matchWidths(500, family[regularName]) # Full match in width 500 1000 >>> family._matchWidths(100, family[regularName]) # Closest match (but not exact) for width 100 600 >>> boldName = 'Roboto-Bold.ttf' >>> family._matchWeights('Normal', family[boldName]) # No match on alternative name with Bold 0 >>> family._matchWeights('Bold', family[boldName]) # Match on full style name 40 >>> family._matchWeights('Bd', family[boldName]) # Match on partial style name 40 >>> font1 = family.findFont(weight='Regular') >>> font2 = family.findFont(weight='Normal') >>> font1 is font2, font1.info.styleName # Found by different style names. (True, u'Regular') >>> font3 = family.findFont(weight=800, italic=False) >>> font3.info.styleName """ matchingFont = None match = 0 # Matching value for the current matchingFont for font in self.fonts.values(): thisMatch = 0 if name is not None and name in path2Name(font.path): thisMatch += len(name)**4 # Longer names have better matching thisMatch += self._matchWeights(weight or 'Regular', font)**4 thisMatch += (self._matchWidths(width or 500, font)/2)**4 thisMatch += (self._matchItalics(italic or 0, font)/4)**4 if thisMatch > match or matchingFont is None: matchingFont = font match = thisMatch return matchingFont
def installFont(self, fontPath): self._installedFonts.append(fontPath) if os.path.exists(fontPath): return path2Name(fontPath) return None
def installFont(self, fontPath): u"""Install the font in the context and answer the font (file)name.""" # TODO: To be implemented later, if there is a real need for cached fonts. return path2Name(fontPath)