def addFontFiles(self, font_paths, monospace_only=True): """ Add a list of font files to the FontManger font search space. Each element of the font_paths list must be a valid path including the font file name. Relative paths can be used, with the current working directory being the origin. If monospace_only is True, each font file will only be added if it is a monospace font (as only monospace fonts are currently supported by TextBox). Adding fonts to the FontManager is not persistent across runs of the script, so any extra font paths need to be added each time the script starts. """ fi_list = [] for fp in font_paths: if os.path.isfile(fp) and os.path.exists(fp): face = Face(fp) if monospace_only: if face.is_fixed_width: fi_list.append(self._createFontInfo(fp, face)) else: fi_list.append(self._createFontInfo(fp, face)) self.font_family_styles.sort() return fi_list
def __init__(self, font_info, size, dpi): self.font_info = font_info self.size = size self.dpi = dpi self.id = self.getIdFromArgs(font_info, size, dpi) self._face = Face(font_info.path) self._face.set_char_size(height=self.size * 64, vres=self.dpi) self.charcode2glyph = None self.charcode2unichr = None self.charcode2displaylist = None self.max_ascender = None self.max_descender = None self.max_tile_width = None self.max_tile_height = None self.max_bitmap_size = None self.total_bitmap_area = 0 self.atlas = None
def __init__(self,font_info,size,dpi): self.font_info=font_info self.size=size self.dpi=dpi self.id=self.getIdFromArgs(font_info,size,dpi) self._face=Face(font_info.path) self._face.set_char_size(height=self.size*64,vres=self.dpi) self.charcode2glyph=None self.charcode2unichr=None self.charcode2displaylist=None self.max_ascender = None self.max_descender = None self.max_tile_width = None self.max_tile_height = None self.max_bitmap_size = None self.total_bitmap_area=0 self.atlas=None
def addFontFiles(self,font_paths,monospace_only=True): """ Add a list of font files to the FontManger font search space. Each element of the font_paths list must be a valid path including the font file name. Relative paths can be used, with the current working directory being the origin. If monospace_only is True, each font file will only be added if it is a monospace font (as only monospace fonts are currently supported by TextBox). Adding fonts to the FontManager is not persistant across runs of the script, so any extra font paths need to be added each time the script starts. """ fi_list=[] for fp in font_paths: if os.path.isfile(fp) and os.path.exists(fp): try: face = Face(fp) if monospace_only: if face.is_fixed_width: fi_list.append(self._createFontInfo(fp,face)) else: fi_list.append(self._createFontInfo(fp,face)) except FT_Exception, fte: pass except Exception, e: print print ' --- Error --- ' print 'Error opening font path:', fp print 'Loaded OK count:', len(fi_list) import traceback traceback.print_exc() return None
class MonospaceFontAtlas(object): def __init__(self,font_info,size,dpi): self.font_info=font_info self.size=size self.dpi=dpi self.id=self.getIdFromArgs(font_info,size,dpi) self._face=Face(font_info.path) self._face.set_char_size(height=self.size*64,vres=self.dpi) self.charcode2glyph=None self.charcode2unichr=None self.charcode2displaylist=None self.max_ascender = None self.max_descender = None self.max_tile_width = None self.max_tile_height = None self.max_bitmap_size = None self.total_bitmap_area=0 self.atlas=None def getID(self): return self.id @staticmethod def getIdFromArgs(font_info,size,dpi): return "%s_%d_%d"%(font_info.getID(),size,dpi) def createFontAtlas(self): if self.atlas: self.atlas.free() self.atlas=None self.charcode2glyph={} self.charcode2unichr={} self.max_ascender = None self.max_descender = None self.max_tile_width = None self.max_tile_height = None self.max_bitmap_size = None self.total_bitmap_area=0 # load font glyphs and calculate max. char size. # This is used when the altas is created to properly size the tex. # i.e. max glyph size * num glyphs # max_w,max_h=0,0 max_ascender, max_descender, max_tile_width = 0, 0, 0 face=self._face face.set_char_size(height=self.size*64,vres=self.dpi) # Create texAtlas for glyph set. x_ppem=face.size.x_ppem y_ppem=face.size.x_ppem units_ppem=self.font_info.units_per_em est_max_width=(face.bbox.xMax-face.bbox.xMin)/float(units_ppem)*x_ppem est_max_height=face.size.ascender/float(units_ppem)*y_ppem target_atlas_area=int(est_max_width*est_max_height)*face.num_glyphs # make sure it is big enough. ;) # height is trimmed before sending to video ram anyhow. target_atlas_area=target_atlas_area*3.0 pow2_area=nextPow2(target_atlas_area) atlas_width=2048 atlas_height=pow2_area/atlas_width self.atlas=TextureAtlas(atlas_width,atlas_height) charcode, gindex=face.get_first_char() while gindex: uchar = unichr(charcode) if ud.category(uchar) not in (u'Zl',u'Zp',u'Cc',u'Cf',u'Cs',u'Co',u'Cn'): self.charcode2unichr[charcode]=uchar face.load_char(uchar, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT ) bitmap = face.glyph.bitmap self.total_bitmap_area+=bitmap.width*bitmap.rows max_ascender = max( max_ascender, face.glyph.bitmap_top) max_descender = max( max_descender, bitmap.rows - face.glyph.bitmap_top ) max_tile_width = max( max_tile_width,bitmap.width) max_w=max(bitmap.width,max_w) max_h=max(bitmap.rows,max_h) x,y,w,h = self.atlas.get_region(bitmap.width+2, bitmap.rows+2) if x < 0: raise Exception("MonospaceFontAtlas.get_region failed for: {0}, requested area: {1}. Atlas Full!".format(charcode,(bitmap.width+2, bitmap.rows+2))) x,y = x+1, y+1 w,h = w-2, h-2 data = np.array(bitmap._FT_Bitmap.buffer[:(bitmap.rows*bitmap.width)],dtype=np.ubyte).reshape(h,w,1) self.atlas.set_region((x,y,w,h), data) self.charcode2glyph[charcode]=dict( offset=(face.glyph.bitmap_left, face.glyph.bitmap_top), size=(w,h), atlas_coords=(x,y,w,h), texcoords = [x, y, x + w, y + h], index=gindex, unichar=uchar ) charcode, gindex = face.get_next_char(charcode, gindex) self.max_ascender = max_ascender self.max_descender = max_descender self.max_tile_width = max_tile_width self.max_tile_height = max_ascender+max_descender self.max_bitmap_size=max_w,max_h # resize atlas height=nextPow2(self.atlas.max_y+1) self.atlas.resize(height) self.atlas.upload() self.createDisplayLists() self._face=None #print 'w_max_glyth info:',w_max_glyph #print 'h_max_glyth info:',h_max_glyph def createDisplayLists(self): glyph_count=len(self.charcode2unichr) max_tile_width,max_tile_height=self.max_tile_width,self.max_tile_height display_lists_for_chars={} base = glGenLists(glyph_count) for i,(charcode,glyph) in enumerate( self.charcode2glyph.iteritems()): dl_index=base+i uchar=self.charcode2unichr[charcode] # update tex coords to reflect earlier resize of atlas height. gx1,gy1,gx2,gy2=glyph['texcoords'] gx1=gx1/float(self.atlas.width) gy1=gy1/float(self.atlas.height) gx2=gx2/float(self.atlas.width) gy2=gy2/float(self.atlas.height) glyph['texcoords'] =[gx1,gy1,gx2,gy2] glNewList(dl_index, GL_COMPILE) if uchar not in [u'\t',u'\n']: glBegin( GL_QUADS ) x1 = glyph['offset'][0] x2 = x1+glyph['size'][0] y1=(self.max_ascender-glyph['offset'][1]) y2=y1+glyph['size'][1] glTexCoord2f( gx1, gy2 ), glVertex2f( x1,-y2 ) glTexCoord2f( gx1, gy1 ), glVertex2f( x1,-y1 ) glTexCoord2f( gx2, gy1 ), glVertex2f( x2,-y1 ) glTexCoord2f( gx2, gy2 ), glVertex2f( x2,-y2 ) glEnd( ) glTranslatef( max_tile_width,0,0) glEndList( ) display_lists_for_chars[charcode]=dl_index self.charcode2displaylist=display_lists_for_chars def saveGlyphBitmap(self,file_name=None): if file_name is None: import os #print 'CWD:',os.getcwd() file_name=os.path.join(os.getcwd(),self.getID().lower().replace(u' ',u'_')+'.png') from scipy import misc if self.atlas is None: self.loadAtlas() if self.atlas.depth==1: misc.imsave(file_name, self.atlas.data.reshape(self.atlas.data.shape[:2])) else: misc.imsave(file_name, self.atlas.data) def __del__(self): self._face=None if self.atlas.texid: glDeleteTextures(1, self.atlas.texid) self.atlas.texid=None self.atlas=None if self.charcode2displaylist: for dl in self.charcode2displaylist.values(): glDeleteLists(dl, 1) self.charcode2displaylist.clear() self.charcode2displaylist=None if self.charcode2glyph: self.charcode2glyph.clear() self.charcode2glyph=None if self.charcode2unichr: self.charcode2unichr.clear() self.charcode2unichr=None
class MonospaceFontAtlas(object): def __init__(self, font_info, size, dpi): self.font_info = font_info self.size = size self.dpi = dpi self.id = self.getIdFromArgs(font_info, size, dpi) self._face = Face(font_info.path) self._face.set_char_size(height=self.size * 64, vres=self.dpi) self.charcode2glyph = None self.charcode2unichr = None self.charcode2displaylist = None self.max_ascender = None self.max_descender = None self.max_tile_width = None self.max_tile_height = None self.max_bitmap_size = None self.total_bitmap_area = 0 self.atlas = None def getID(self): return self.id @staticmethod def getIdFromArgs(font_info, size, dpi): return "%s_%d_%d" % (font_info.getID(), size, dpi) def createFontAtlas(self): if self.atlas: self.atlas.free() self.atlas = None self.charcode2glyph = {} self.charcode2unichr = {} self.max_ascender = None self.max_descender = None self.max_tile_width = None self.max_tile_height = None self.max_bitmap_size = None self.total_bitmap_area = 0 # load font glyphs and calculate max. char size. # This is used when the altas is created to properly size the tex. # i.e. max glyph size * num glyphs max_w, max_h = 0, 0 max_ascender, max_descender, max_tile_width = 0, 0, 0 face = self._face face.set_char_size(height=self.size * 64, vres=self.dpi) # Create texAtlas for glyph set. x_ppem = face.size.x_ppem y_ppem = face.size.x_ppem units_ppem = self.font_info.units_per_em est_max_width = ((face.bbox.xMax - face.bbox.xMin) / float(units_ppem) * x_ppem) est_max_height = face.size.ascender / float(units_ppem) * y_ppem target_atlas_area = int( est_max_width * est_max_height) * face.num_glyphs # make sure it is big enough. ;) # height is trimmed before sending to video ram anyhow. target_atlas_area = target_atlas_area * 3.0 pow2_area = nextPow2(target_atlas_area) atlas_width = 2048 atlas_height = pow2_area / atlas_width self.atlas = TextureAtlas(atlas_width, atlas_height * 2) charcode, gindex = face.get_first_char() while gindex: uchar = chr(charcode) if ud.category(uchar) not in (u'Zl', u'Zp', u'Cc', u'Cf', u'Cs', u'Co', u'Cn'): self.charcode2unichr[charcode] = uchar face.load_char(uchar, FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT) bitmap = face.glyph.bitmap self.total_bitmap_area += bitmap.width * bitmap.rows max_ascender = max(max_ascender, face.glyph.bitmap_top) max_descender = max(max_descender, bitmap.rows - face.glyph.bitmap_top) max_tile_width = max(max_tile_width, bitmap.width) max_w = max(bitmap.width, max_w) max_h = max(bitmap.rows, max_h) x, y, w, h = self.atlas.get_region(bitmap.width + 2, bitmap.rows + 2) if x < 0: msg = ("MonospaceFontAtlas.get_region failed " "for: {0}, requested area: {1}. Atlas Full!") vals = charcode, (bitmap.width + 2, bitmap.rows + 2) raise Exception(msg.format(vals)) x, y = x + 1, y + 1 w, h = w - 2, h - 2 data = np.array(bitmap._FT_Bitmap.buffer[:(bitmap.rows * bitmap.width)], dtype=np.ubyte).reshape(h, w, 1) self.atlas.set_region((x, y, w, h), data) self.charcode2glyph[charcode] = dict( offset=(face.glyph.bitmap_left, face.glyph.bitmap_top), size=(w, h), atlas_coords=(x, y, w, h), texcoords=[x, y, x + w, y + h], index=gindex, unichar=uchar) charcode, gindex = face.get_next_char(charcode, gindex) self.max_ascender = max_ascender self.max_descender = max_descender self.max_tile_width = max_tile_width self.max_tile_height = max_ascender + max_descender self.max_bitmap_size = max_w, max_h # resize atlas height = nextPow2(self.atlas.max_y + 1) self.atlas.resize(height) self.atlas.upload() self.createDisplayLists() self._face = None def createDisplayLists(self): glyph_count = len(self.charcode2unichr) max_tile_width = self.max_tile_width max_tile_height = self.max_tile_height display_lists_for_chars = {} base = glGenLists(glyph_count) for i, (charcode, glyph) in enumerate(self.charcode2glyph.items()): dl_index = base + i uchar = self.charcode2unichr[charcode] # update tex coords to reflect earlier resize of atlas height. gx1, gy1, gx2, gy2 = glyph['texcoords'] gx1 = gx1 / float(self.atlas.width) gy1 = gy1 / float(self.atlas.height) gx2 = gx2 / float(self.atlas.width) gy2 = gy2 / float(self.atlas.height) glyph['texcoords'] = [gx1, gy1, gx2, gy2] glNewList(dl_index, GL_COMPILE) if uchar not in [u'\t', u'\n']: glBegin(GL_QUADS) x1 = glyph['offset'][0] x2 = x1 + glyph['size'][0] y1 = (self.max_ascender - glyph['offset'][1]) y2 = y1 + glyph['size'][1] glTexCoord2f(gx1, gy2), glVertex2f(x1, -y2) glTexCoord2f(gx1, gy1), glVertex2f(x1, -y1) glTexCoord2f(gx2, gy1), glVertex2f(x2, -y1) glTexCoord2f(gx2, gy2), glVertex2f(x2, -y2) glEnd() glTranslatef(max_tile_width, 0, 0) glEndList() display_lists_for_chars[charcode] = dl_index self.charcode2displaylist = display_lists_for_chars def saveGlyphBitmap(self, file_name=None): if file_name is None: import os file_name = os.path.join( os.getcwd(), self.getID().lower().replace(u' ', u'_') + '.png') from scipy import misc if self.atlas is None: self.loadAtlas() if self.atlas.depth == 1: misc.imsave(file_name, self.atlas.data.reshape(self.atlas.data.shape[:2])) else: misc.imsave(file_name, self.atlas.data) def __del__(self): self._face = None if self.atlas.texid is not None: #glDeleteTextures(1, self.atlas.texid) self.atlas.texid = None self.atlas = None if self.charcode2displaylist is not None: # for dl in self.charcode2displaylist.values(): # glDeleteLists(dl, 1) self.charcode2displaylist.clear() self.charcode2displaylist = None if self.charcode2glyph is not None: self.charcode2glyph.clear() self.charcode2glyph = None if self.charcode2unichr is not None: self.charcode2unichr.clear() self.charcode2unichr = None