def build_font(srcs, metadata, filename): ascent = 880 descent = 120 upem = ascent + descent scale = upem / 360.0 transform = Transform(scale, 0, 0, -scale, 0, ascent) glyphs = collect_glyphs(srcs, transform=transform) builder = FontBuilder(1000, isTTF=False) builder.setupGlyphOrder([glyph.name for glyph in glyphs]) builder.setupCharacterMap({0: ".notdef"}) psname = metadata["psName"] builder.setupCFF(psname, {"FullName": psname}, {glyph.name: glyph.charstring for glyph in glyphs}, {}) builder.setupHorizontalMetrics( {glyph.name: glyph.get_hmetrics() for glyph in glyphs}) builder.setupHorizontalHeader(ascent=ascent, descent=-descent) builder.setupNameTable({}) builder.setupOS2() builder.setupPost() builder.setupVerticalMetrics( {glyph.name: glyph.get_vmetrics(ascent=ascent) for glyph in glyphs}) builder.setupVerticalOrigins({}, ascent) builder.setupVerticalHeader(ascent=ascent, descent=-descent) builder.save(filename)
def make_font(file_paths, out_dir, revision, gsub_path, gpos_path, uvs_lst): cmap, gorder, validated_fpaths = {}, deque(), [] # build glyph order for fpath in file_paths: # derive glyph name from file name gname = os.path.splitext(os.path.basename(fpath))[0] # trim extension # validate glyph name if not glyph_name_is_valid(gname, fpath): continue # skip any duplicates and 'space' if gname in gorder or gname == 'space': log.warning("Skipped file '{}'. The glyph name derived from it " "is either a duplicate or 'space'.".format(fpath)) continue # limit the length of glyph name to 31 chars if len(gname) > 31: num = 0 trimmed_gname = get_trimmed_glyph_name(gname, num) while trimmed_gname in gorder: num += 1 trimmed_gname = get_trimmed_glyph_name(trimmed_gname, num) gorder.append(trimmed_gname) log.warning("Glyph name '{}' was trimmed to 31 characters: " "'{}'".format(gname, trimmed_gname)) else: gorder.append(gname) validated_fpaths.append(fpath) # add to cmap if RE_UNICODE.match(gname): uni_int = int(gname[1:], 16) # trim leading 'u' cmap[uni_int] = gname fb = FontBuilder(UPM, isTTF=False) fb.font['head'].fontRevision = float(revision) fb.font['head'].lowestRecPPEM = 12 cs_dict = {} cs_cache = {} for i, svg_file_path in enumerate(validated_fpaths): svg_file_realpath = os.path.realpath(svg_file_path) if svg_file_realpath not in cs_cache: svg_size = get_svg_size(svg_file_realpath) if svg_size is None: cs_dict[gorder[i]] = SPACE_CHARSTRING continue pen = T2CharStringPen(EMOJI_H_ADV, None) svg = SVGPath(svg_file_realpath, transform=(EMOJI_SIZE / svg_size, 0, 0, -EMOJI_SIZE / svg_size, (EMOJI_H_ADV * .5) - (EMOJI_SIZE * .5), EMOJI_H_ADV * ABOVE_BASELINE)) svg.draw(pen) cs = pen.getCharString() cs_cache[svg_file_realpath] = cs else: cs = cs_cache.get(svg_file_realpath) cs_dict[gorder[i]] = cs # add '.notdef', 'space' and zero-width joiner pen = T2CharStringPen(EMOJI_H_ADV, None) draw_notdef(pen) gorder.extendleft(reversed(['.notdef', 'space', 'ZWJ'])) cs_dict.update({ '.notdef': pen.getCharString(), 'space': SPACE_CHARSTRING, 'ZWJ': SPACE_CHARSTRING, }) cmap.update({ 32: 'space', # U+0020 160: 'space', # U+00A0 8205: 'ZWJ', # U+200D }) # add TAG LATIN LETTER glyphs and mappings for cdpt in TAG_LAT_LETTR: tag_gname = f'u{cdpt}' gorder.append(tag_gname) cs_dict[tag_gname] = SPACE_CHARSTRING cmap[int(cdpt, 16)] = tag_gname fb.setupGlyphOrder(list(gorder)) # parts of FontTools require a list fb.setupCharacterMap(cmap, uvs=uvs_lst) fb.setupCFF( PS_NAME, { 'version': revision, 'Notice': TRADEMARK, 'Copyright': COPYRIGHT, 'FullName': FULL_NAME, 'FamilyName': FAMILY_NAME, 'Weight': STYLE_NAME }, cs_dict, {}) glyphs_bearings = {} for gname, cs in cs_dict.items(): gbbox = cs.calcBounds(None) if gbbox: xmin, ymin, _, ymax = gbbox if ymax > ASCENT: log.warning("Top of glyph '{}' may get clipped. " "Glyph's ymax={}; Font's ascent={}".format( gname, ymax, ASCENT)) if ymin < DESCENT: log.warning("Bottom of glyph '{}' may get clipped. " "Glyph's ymin={}; Font's descent={}".format( gname, ymin, DESCENT)) lsb = xmin tsb = EMOJI_V_ADV - ymax - EMOJI_H_ADV * (1 - ABOVE_BASELINE) glyphs_bearings[gname] = (lsb, tsb) else: glyphs_bearings[gname] = (0, 0) h_metrics = {} v_metrics = {} for gname in gorder: h_metrics[gname] = (EMOJI_H_ADV, glyphs_bearings[gname][0]) v_metrics[gname] = (EMOJI_V_ADV, glyphs_bearings[gname][1]) fb.setupHorizontalMetrics(h_metrics) fb.setupVerticalMetrics(v_metrics) fb.setupHorizontalHeader(ascent=ASCENT, descent=DESCENT) v_ascent = EMOJI_H_ADV // 2 v_descent = EMOJI_H_ADV - v_ascent fb.setupVerticalHeader(ascent=v_ascent, descent=-v_descent, caretSlopeRun=1) VERSION_STRING = 'Version {};{}'.format(revision, VENDOR) UNIQUE_ID = '{};{};{}'.format(revision, VENDOR, PS_NAME) name_strings = dict( copyright=COPYRIGHT, # ID 0 familyName=FAMILY_NAME, # ID 1 styleName=STYLE_NAME, # ID 2 uniqueFontIdentifier=UNIQUE_ID, # ID 3 fullName=FULL_NAME, # ID 4 version=VERSION_STRING, # ID 5 psName=PS_NAME, # ID 6 trademark=TRADEMARK, # ID 7 manufacturer=MANUFACTURER, # ID 8 designer=DESIGNER, # ID 9 vendorURL=VENDOR_URL, # ID 11 designerURL=DESIGNER_URL, # ID 12 licenseDescription=LICENSE, # ID 13 licenseInfoURL=LICENSE_URL, # ID 14 ) fb.setupNameTable(name_strings, mac=False) fb.setupOS2( fsType=FSTYPE, achVendID=VENDOR, fsSelection=0x0040, # REGULAR usWinAscent=ASCENT, usWinDescent=-DESCENT, sTypoAscender=ASCENT, sTypoDescender=DESCENT, sCapHeight=ASCENT, ulCodePageRange1=(1 << 1)) # set 1st CP bit if gsub_path: addOpenTypeFeatures(fb.font, gsub_path, tables=['GSUB']) if gpos_path: addOpenTypeFeatures(fb.font, gpos_path, tables=['GPOS']) fb.setupPost(isFixedPitch=1, underlinePosition=UNDERLINE_POSITION, underlineThickness=UNDERLINE_THICKNESS) fb.setupDummyDSIG() fb.save(os.path.join(out_dir, '{}.otf'.format(PS_NAME)))