| #!/usr/bin/env python |
| |
| # Copyright (C) 2018 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import contextlib |
| import itertools |
| import io |
| import os |
| import sys |
| |
| from fontTools import ttLib |
| from PIL import Image |
| |
| def to_hex_str(value): |
| """Converts given int value to hex without the 0x prefix""" |
| return format(value, 'X') |
| |
| def codepoint_to_string(codepoints): |
| """Converts a list of codepoints into a string separated with space.""" |
| return '_'.join([to_hex_str(x) for x in codepoints]) |
| |
| def read_cmap12(ttf, glyph_to_codepoint_map, codepoint_map): |
| cmap = ttf['cmap'] |
| for table in cmap.tables: |
| if table.format == 12 and table.platformID == 3 and table.platEncID == 10: |
| for codepoint, glyph_name in table.cmap.iteritems(): |
| glyph_to_codepoint_map[glyph_name] = codepoint |
| codepoint_map[codepoint_to_string([codepoint])] = glyph_name |
| # self.update_emoji_data([codepoint], glyph_name) |
| return table |
| raise ValueError("Font doesn't contain cmap with format:12, platformID:3 and platEncID:10") |
| |
| def read_gsub(ttf, glyph_to_codepoint_map, codepoint_map): |
| gsub = ttf['GSUB'] |
| ligature_subtables = [] |
| context_subtables = [] |
| # this code is font dependent, implementing all gsub rules is out of scope of EmojiCompat |
| # and would be expensive with little value |
| for lookup in gsub.table.LookupList.Lookup: |
| for subtable in lookup.SubTable: |
| if subtable.LookupType == 5: |
| context_subtables.append(subtable) |
| elif subtable.LookupType == 4: |
| ligature_subtables.append(subtable) |
| |
| for subtable in context_subtables: |
| add_gsub_context_subtable(subtable, gsub.table.LookupList, glyph_to_codepoint_map, codepoint_map) |
| |
| for subtable in ligature_subtables: |
| add_gsub_ligature_subtable(subtable, glyph_to_codepoint_map, codepoint_map) |
| |
| def add_gsub_context_subtable(subtable, lookup_list, glyph_to_codepoint_map, codepoint_map): |
| for sub_class_set in subtable.SubClassSet: |
| if sub_class_set: |
| for sub_class_rule in sub_class_set.SubClassRule: |
| subs_list = len(sub_class_rule.SubstLookupRecord) * [None] |
| for record in sub_class_rule.SubstLookupRecord: |
| subs_list[record.SequenceIndex] = get_substitutions(lookup_list, |
| record.LookupListIndex) |
| combinations = list(itertools.product(*subs_list)) |
| for seq in combinations: |
| glyph_names = [x["input"] for x in seq] |
| codepoints = [glyph_to_codepoint_map[x] for x in glyph_names] |
| outputs = [x["output"] for x in seq if x["output"]] |
| nonempty_outputs = filter(lambda x: x.strip() , outputs) |
| if len(nonempty_outputs) == 0: |
| print("Warning: no output glyph is set for " + str(glyph_names)) |
| continue |
| elif len(nonempty_outputs) > 1: |
| print( |
| "Warning: multiple glyph is set for " |
| + str(glyph_names) + ", will use the first one") |
| |
| glyph = nonempty_outputs[0] |
| codepoint_map[codepoint_to_string(codepoints)] = glyph |
| |
| def get_substitutions(lookup_list, index): |
| result = [] |
| for x in lookup_list.Lookup[index].SubTable: |
| for input, output in x.mapping.iteritems(): |
| result.append({"input": input, "output": output}) |
| return result |
| |
| def add_gsub_ligature_subtable(subtable, glyph_to_codepoint_map, codepoint_map): |
| for name, ligatures in subtable.ligatures.iteritems(): |
| for ligature in ligatures: |
| glyph_names = [name] + ligature.Component |
| codepoints = [glyph_to_codepoint_map[x] for x in glyph_names] |
| codepoint_map[codepoint_to_string(codepoints)] = ligature.LigGlyph |
| |
| def read_cbdt(ttf): |
| cbdt = ttf['CBDT'] |
| glyph_to_image = {} |
| for strike_data in cbdt.strikeData: |
| for key, data in strike_data.iteritems(): |
| data.decompile |
| glyph_to_image[key] = Image.open(io.BytesIO(data.imageData)) |
| return glyph_to_image |
| |
| |
| rgba_map = {} |
| |
| def similar_img(img1, img2): |
| # return if images are the same with accepting some changes |
| if img1 is None and img2 is None: return True |
| if img1 is None or img2 is None: return False |
| if not img1.size == img2.size: return False |
| |
| pixels1 = rgba_map.get(img1, img1.convert('L').getdata()) |
| pixels2 = rgba_map.get(img2, img2.convert('L').getdata()) |
| pixels = itertools.izip(pixels1, pixels2) |
| diff = 0 |
| for px1, px2 in pixels: |
| diff = diff + abs(px1-px2) |
| pixel_count = 1.0 * img1.size[0] * img1.size[1] |
| normalized_diff = diff / pixel_count / 255.0 * 100.0 |
| if normalized_diff <= 0.5: return True |
| return False |
| |
| def main(argv): |
| codepoint_map_1 = {} |
| codepoint_map_2 = {} |
| glyph_to_codepoint_map_1 = {} |
| glyph_to_codepoint_map_2 = {} |
| |
| with contextlib.closing(ttLib.TTFont(argv[1])) as ttf: |
| font1_cbdt = read_cbdt(ttf) |
| read_cmap12(ttf, glyph_to_codepoint_map_1, codepoint_map_1) |
| read_gsub(ttf, glyph_to_codepoint_map_1, codepoint_map_1) |
| with contextlib.closing(ttLib.TTFont(argv[2])) as ttf: |
| font2_cbdt = read_cbdt(ttf) |
| read_cmap12(ttf, glyph_to_codepoint_map_2, codepoint_map_2) |
| read_gsub(ttf, glyph_to_codepoint_map_2, codepoint_map_2) |
| |
| glyphs1 = set(font1_cbdt.keys()) |
| glyphs2 = set(font2_cbdt.keys()) |
| |
| codepoints_set1 = set(codepoint_map_1.keys()) |
| codepoints_set2 = set(codepoint_map_2.keys()) |
| |
| if codepoints_set1 != codepoints_set2: |
| print "Codepoints set has changed: : %s" % (codepoints_set1 ^ codepoints_set2) |
| |
| all_codepoints = set(codepoint_map_1.keys()).union(codepoint_map_2.keys()) |
| |
| for key in all_codepoints: |
| glyph1 = codepoint_map_1[key] if key in codepoint_map_1 else None |
| glyph2 = codepoint_map_2[key] if key in codepoint_map_2 else None |
| |
| image1 = font1_cbdt[glyph1] if glyph1 and glyph1 in font1_cbdt else None |
| image2 = font2_cbdt[glyph2] if glyph2 and glyph2 in font2_cbdt else None |
| |
| if not similar_img(image1, image2): |
| print 'Glyph %s has different image' % key |
| if image1: |
| with open(os.path.join(argv[3], '%s_old.png' % key), 'w') as f: |
| image1.save(f) |
| if image2: |
| with open(os.path.join(argv[3], '%s_new.png' % key), 'w') as f: |
| image2.save(f) |
| |
| |
| def print_usage(): |
| """Prints how to use the script.""" |
| print("usage: old_font new_font output_dir") |
| |
| if __name__ == '__main__': |
| if len(sys.argv) < 3: |
| print_usage() |
| sys.exit(1) |
| main(sys.argv) |