Tags 943761 +patch Thanks It seems upstream has already fixed this issue, I applied the upstream commit as a quilt patch and it resulted in a package that installed successfully. I also added the missing breaks/replaces.
Debdiff attatched, no immediate intent to NMU.
diff -Nru nototools-0.2.0/debian/changelog nototools-0.2.0/debian/changelog --- nototools-0.2.0/debian/changelog 2019-10-21 11:04:18.000000000 +0000 +++ nototools-0.2.0/debian/changelog 2019-12-11 00:44:06.000000000 +0000 @@ -1,3 +1,12 @@ +nototools (0.2.0-1.1) UNRELEASED; urgency=medium + + * Non-maintainer upload. + * Apply upstream commit c4a79c79f0da2e6385eab0d89bf5c6546b15e373 + "More updates to make Python 3 compatible" + * Add breaks and replaces on python-nototools. + + -- Peter Michael Green <plugw...@debian.org> Wed, 11 Dec 2019 00:44:06 +0000 + nototools (0.2.0-1) experimental; urgency=medium * New upstream release diff -Nru nototools-0.2.0/debian/control nototools-0.2.0/debian/control --- nototools-0.2.0/debian/control 2019-10-21 11:04:18.000000000 +0000 +++ nototools-0.2.0/debian/control 2019-12-11 00:44:06.000000000 +0000 @@ -20,6 +20,8 @@ Depends: ${misc:Depends}, ${python3:Depends}, unicode-data +Breaks: python-nototools +Replaces: python-nototools Description: font support tools from the Noto Fonts project Noto is a collection of font families, each visually harmonized across scripts. diff -Nru nototools-0.2.0/debian/patches/more-python3-fixes.patch nototools-0.2.0/debian/patches/more-python3-fixes.patch --- nototools-0.2.0/debian/patches/more-python3-fixes.patch 1970-01-01 00:00:00.000000000 +0000 +++ nototools-0.2.0/debian/patches/more-python3-fixes.patch 2019-12-11 00:43:51.000000000 +0000 @@ -0,0 +1,1866 @@ +commit c4a79c79f0da2e6385eab0d89bf5c6546b15e373 +Author: punchcutter <zarijos...@gmail.com> +Date: Wed Oct 23 21:51:41 2019 -0700 + + More updates to make Python 3 compatible + Added Wancho, Indic Siyaq Numbers, and Mayan Numerals to noto lint + +diff --git a/nototools/android_patches.py b/nototools/android_patches.py +index 67ccc98..fa5f257 100755 +--- a/nototools/android_patches.py ++++ b/nototools/android_patches.py +@@ -24,6 +24,7 @@ from os import path + import shutil + import tempfile + ++from nototools.py23 import unichr + from nototools import subset + from nototools import coverage + from nototools import fix_khmer_and_lao_coverage as merger +@@ -115,9 +116,9 @@ def _remove_cjk_emoji(cjk_font_names, srcdir, dstdir): + + EMOJI = ( + [0x26BD, 0x26BE, 0x1F18E] +- + range(0x1F191, 0x1F19A+1) ++ + list(range(0x1F191, 0x1F19A+1)) + + [0x1F201, 0x1F21A, 0x1F22F] +- + range(0x1F232, 0x1F236+1) ++ + list(range(0x1F232, 0x1F236+1)) + + [0x1F238, 0x1F239, 0x1F23A, 0x1F250, 0x1F251] + ) + +diff --git a/nototools/autofix_for_phase3.py b/nototools/autofix_for_phase3.py +index e50d709..0a083ee 100755 +--- a/nototools/autofix_for_phase3.py ++++ b/nototools/autofix_for_phase3.py +@@ -27,7 +27,6 @@ hinted. We also put more info into the version string. + + import argparse + import datetime +-import glob + import os + from os import path + import re +@@ -35,6 +34,7 @@ import subprocess + + from fontTools import ttLib + ++from nototools.py23 import basestring + from nototools import font_data + from nototools import noto_data + from nototools import noto_fonts +@@ -249,7 +249,7 @@ def get_new_version(font, relfont, nversion): + return rversion + else: + n_mm, n_is_phase_2 = _version_str_to_mm(nversion) +- if n_is_phase2: ++ if n_is_phase_2: + raise Exception('bad phase 3 minor version ("%s")' % nversion) + if rversion is not None: + if n_mm < r_mm: +diff --git a/nototools/chart/chart.py b/nototools/chart/chart.py +index 239735d..c0a2d41 100755 +--- a/nototools/chart/chart.py ++++ b/nototools/chart/chart.py +@@ -76,7 +76,7 @@ num_rows = len(rows) + width = NUM_COLS * CELL_SIZE + 2 * (2 * MARGIN + LABEL_WIDTH) + height = num_rows * CELL_SIZE + 2 * MARGIN + +-print "Generating %s at %.3gx%.3gin" % (outfile, width/72., height/72.) ++print("Generating %s at %.3gx%.3gin" % (outfile, width/72., height/72.)) + if outfile.endswith(".pdf"): + surface = cairo.PDFSurface(outfile, width, height) + elif outfile.endswith(".ps"): +diff --git a/nototools/chart/pycairoft.py b/nototools/chart/pycairoft.py +index e62a09e..6cb938a 100644 +--- a/nototools/chart/pycairoft.py ++++ b/nototools/chart/pycairoft.py +@@ -34,7 +34,7 @@ def create_cairo_font_face_for_file (filename, faceindex=0, loadoptions=0): + # initialize freetype + _ft_lib = ctypes.c_void_p () + if FT_Err_Ok != _freetype_so.FT_Init_FreeType (ctypes.byref (_ft_lib)): +- raise "Error initialising FreeType library." ++ raise Exception("Error initialising FreeType library.") + + _surface = cairo.ImageSurface (cairo.FORMAT_A8, 0, 0) + +diff --git a/nototools/cldr_data.py b/nototools/cldr_data.py +index a5a0d45..38b6b8e 100755 +--- a/nototools/cldr_data.py ++++ b/nototools/cldr_data.py +@@ -22,6 +22,7 @@ import re + from nototools import unicode_data + import xml.etree.cElementTree as ElementTree + ++from nototools.py23 import unichr, unicode + from nototools import extra_locale_data + + TOOLS_DIR = path.abspath(path.join(path.dirname(__file__), os.pardir)) +@@ -49,7 +50,7 @@ def _parse_likely_subtags(): + from_tag = tag.get('from').replace('_', '-') + to_tag = tag.get('to').split('_') + _LIKELY_SUBTAGS[from_tag] = to_tag +- # print 'likely subtag from %s -> %s' % (from_tag, to_tag) ++ # print('likely subtag from %s -> %s' % (from_tag, to_tag)) + + _LIKELY_SUBTAGS.update(extra_locale_data.LIKELY_SUBTAGS) + +@@ -161,7 +162,7 @@ def _parse_supplemental_data(): + + if _USE_EXTRA_LOCALE_DATA: + # Supplement lang to script mapping with extra locale data +- for lang, scripts in extra_locale_data.LANG_TO_SCRIPTS.iteritems(): ++ for lang, scripts in extra_locale_data.LANG_TO_SCRIPTS.items(): + _LANG_TO_SCRIPTS[lang] |= set(scripts) + + # Use extra locale data's likely subtag info to change the supplemental +@@ -194,7 +195,7 @@ def _parse_supplemental_data(): + _REGION_TO_LANG_SCRIPTS[region].add(lang_script) + _LANG_TO_REGIONS[lang].add(region) + +- for tup in extra_locale_data.REGION_TO_LANG_SCRIPTS.iteritems(): ++ for tup in extra_locale_data.REGION_TO_LANG_SCRIPTS.items(): + territory, lang_scripts = tup + _REGION_TO_LANG_SCRIPTS[territory] |= set(lang_scripts) + for lang_script in lang_scripts: +@@ -695,7 +696,7 @@ def _init_lang_scr_to_lit_pops(): + + # make it a bit more useful by sorting the value list in order of decreasing + # population and converting the list to a tuple +- for lang_scr, values in tmp_map.iteritems(): ++ for lang_scr, values in tmp_map.items(): + _lang_scr_to_lit_pops[lang_scr] = tuple( + sorted(values, key=lambda x: (-x[1], x[0]))) + +diff --git a/nototools/cmap_block_coverage.py b/nototools/cmap_block_coverage.py +index 214aae7..71fe0a1 100755 +--- a/nototools/cmap_block_coverage.py ++++ b/nototools/cmap_block_coverage.py +@@ -196,7 +196,7 @@ def _summarize_block(block, block_count, defined_count, script_counts): + + lower_limit = int(defined_count / 10) + groups = collections.defaultdict(list) +- for script, count in script_counts.iteritems(): ++ for script, count in script_counts.items(): + groupnum = int(count / 5) * 5 + if groupnum < lower_limit: + groupnum = 0 +diff --git a/nototools/collect_cldr_punct.py b/nototools/collect_cldr_punct.py +index 87bf924..56c328f 100755 +--- a/nototools/collect_cldr_punct.py ++++ b/nototools/collect_cldr_punct.py +@@ -27,6 +27,7 @@ from os import path + import sys + import xml.etree.cElementTree as ET + ++from nototools.py23 import unichr + from nototools import cldr_data + from nototools import tool_utils + from nototools import unicode_data +diff --git a/nototools/compare_cmap_data.py b/nototools/compare_cmap_data.py +index 319b0eb..2eb0674 100755 +--- a/nototools/compare_cmap_data.py ++++ b/nototools/compare_cmap_data.py +@@ -227,7 +227,7 @@ def report_compare(compare_result, detailed=True): + target_map = cmap_data.create_map_from_table(target_cmap_data.table) + + inverted_target = collections.defaultdict(set) +- for script, row in target_map.iteritems(): ++ for script, row in target_map.items(): + cps = tool_utils.parse_int_ranges(row.ranges) + for cp in cps: + inverted_target[cp].add(script) +diff --git a/nototools/create_image.py b/nototools/create_image.py +index 1125df1..8573bdf 100755 +--- a/nototools/create_image.py ++++ b/nototools/create_image.py +@@ -26,11 +26,13 @@ from os import path + import string + + from nototools import notoconfig ++from nototools.py23 import basestring + + import cairo + import pango + import pangocairo + ++ + _fonts_conf_template = """<?xml version="1.0"?> + <!DOCTYPE fontconfig SYSTEM "fonts.dtd"> + <fontconfig> +diff --git a/nototools/data/family_name_info_p3.xml b/nototools/data/family_name_info_p3.xml +index 2a546ae..62e8959 100644 +--- a/nototools/data/family_name_info_p3.xml ++++ b/nototools/data/family_name_info_p3.xml +@@ -100,6 +100,7 @@ + <info family="sans-maka" family_name_style="short" include_regular="t" use_preferred="t" /> + <info family="sans-mand" family_name_style="short" include_regular="t" use_preferred="t" /> + <info family="sans-mani" family_name_style="very short" include_regular="t" use_preferred="t" /> ++ <info family="sans-manu" family_name_style="very short" include_regular="t" use_preferred="t" /> + <info family="sans-marc" family_name_style="short" include_regular="t" use_preferred="t" /> + <info family="sans-medf" family_name_style="very short" include_regular="t" use_preferred="t" /> + <info family="sans-mend" family_name_style="short" include_regular="t" use_preferred="t" /> +@@ -149,6 +150,7 @@ + <info family="sans-sind" family_name_style="very short" include_regular="t" use_preferred="t" /> + <info family="sans-sinh" family_name_style="short" include_regular="t" use_preferred="t" /> + <info family="sans-sinh-ui" family_name_style="very short" include_regular="t" use_preferred="t" /> ++ <info family="sans-siyq" family_name_style="very short" include_regular="t" use_preferred="t" /> + <info family="sans-sogd" family_name_style="short" include_regular="t" use_preferred="t" /> + <info family="sans-sogo" family_name_style="very short" include_regular="t" use_preferred="t" /> + <info family="sans-sora" family_name_style="short" include_regular="t" use_preferred="t" /> +@@ -180,6 +182,7 @@ + <info family="sans-tirh" family_name_style="short" include_regular="t" use_preferred="t" /> + <info family="sans-ugar" family_name_style="short" include_regular="t" use_preferred="t" /> + <info family="sans-vaii" family_name_style="short" include_regular="t" use_preferred="t" /> ++ <info family="sans-wcho" family_name_style="short" include_regular="t" use_preferred="t" /> + <info family="sans-wara" family_name_style="very short" include_regular="t" use_preferred="t" /> + <info family="sans-xpeo" family_name_style="very short" include_regular="t" use_preferred="t" /> + <info family="sans-xsux" family_name_style="very short" include_regular="t" use_preferred="t" /> +diff --git a/nototools/data/familyname_and_styles.txt b/nototools/data/familyname_and_styles.txt +index 5c92410..e685f8c 100644 +--- a/nototools/data/familyname_and_styles.txt ++++ b/nototools/data/familyname_and_styles.txt +@@ -13,6 +13,8 @@ NotoSerif + NotoSerifDisplay + -- TRBH -- + NotoSansSymbols ++NotoSansIndicSiyaqNumbers ++NotoSansMayanNumerals + -- R -- + NotoSansSymbols2 + NotoSansMath +@@ -114,6 +116,7 @@ NotoSansSiddham + NotoSansSignwriting + NotoSansTakri + NotoSansTirhuta ++NotoSansWancho + NotoSansWarangCiti + NotoSerifAhom + NotoSansAnatolianHieroglyphs +diff --git a/nototools/data/noto_cmap_phase3.xml b/nototools/data/noto_cmap_phase3.xml +index b7d9b3f..1c2330c 100644 +--- a/nototools/data/noto_cmap_phase3.xml ++++ b/nototools/data/noto_cmap_phase3.xml +@@ -9,7 +9,7 @@ + <th>script,name,count,ranges,xcount,xranges</th> + <tr>Adlm,Adlam,97,0000 000d 0020-0021 00a0 061f 0640 204f 25cc 2e41 1e900-1e94a 1e950-1e959 1e95e-1e95f,-1,</tr> + <tr>Aghb,Caucasian Albanian,76,0000 000d 0020 00a0 0304 0331 25cc fe20-fe2f 10530-10563 1056f,-1,</tr> +- <tr>Ahom,Ahom,62,0000 000d 0020 00a0 25cc 11700-11719 1171d-1172b 11730-1173f,-1,</tr> ++ <tr>Ahom,Ahom,63,0000 000d 0020 00a0 25cc 11700-1171a 1171d-1172b 11730-1173f,-1,</tr> + <tr>Arab,Arabic,1160,0000 000d 0020-0021 002c-002e 0030-003a 003f 00a0 00ab 00bb 034f 0600-061c 061e-06ff 0750-077f 08a0-08b4 08b6-08bd 08d4-08ff 200b-2011 204f 25cc 2e41 fb50-fbc1 fbd3-fd3f fd50-fd8f fd92-fdc7 fdf0-fdfd fe70-fe74 fe76-fefc,11,0022 0027-0029 2018-2019 201c-201d 2026 2039-203a</tr> + <tr>Aran,(Urdu),281,0000 000d 0020-0021 0028-0029 002b-003a 003d 005b-005d 007b-007d 00a0 00ab 00b7 00bb 00d7 00f7 0600-0604 0609-0614 061b-061c 061e-0659 065d-0673 0679-0681 0683-068f 0691 0693 0696 0698-069a 069e 06a1 06a4 06a6 06a9 06ab 06af-06b1 06b3 06b7 06ba-06bc 06be 06c0-06c4 06c6-06c7 06ca 06cc-06cd 06d0 06d2-06d5 06dd-06de 06e0-06e1 06e9 06ee-06f9 06ff 0759 075c 0763 0767-077d 08ff 200b-2011 2013-2014 2018-2019 201c-201d 2039-203a 2212 25cc fbb2-fbc1 fd3e-fd3f fdf2 fdf4 fdfa-fdfd,0,</tr> + <tr>Armi,Imperial Aramaic,35,0000 000d 0020 00a0 10840-10855 10857-1085f,0,</tr> +@@ -45,7 +45,7 @@ + <tr>Gong,Gunjala Gondi,70,0000 000d 0020 00a0 0964-0965 25cc 11d60-11d65 11d67-11d68 11d6a-11d8e 11d90-11d91 11d93-11d98 11da0-11da9,-1,</tr> + <tr>Gonm,Masaram Gondi,82,0000 000d 0020 00a0 0964-0965 25cc 11d00-11d06 11d08-11d09 11d0b-11d36 11d3a 11d3c-11d3d 11d3f-11d47 11d50-11d59,-1,</tr> + <tr>Goth,Gothic,36,0000 000d 0020 00a0 0304-0305 0308 0331 25cc 10330-1034a,2,003a 00b7</tr> +- <tr>Gran,Grantha,120,0000 000d 0020 00a0 0951-0952 0964-0965 0baa 0bb5 0be6-0bf2 1cd0 1cd2-1cd3 1cf2-1cf4 1cf8-1cf9 200c-200d 20f0 25cc 11300-11303 11305-1130c 1130f-11310 11313-11328 1132a-11330 11332-11333 11335-11339 1133c-11344 11347-11348 1134b-1134d 11350 11357 1135d-11363 11366-1136c 11370-11374,-1,</tr> ++ <tr>Gran,Grantha,121,0000 000d 0020 00a0 0951-0952 0964-0965 0baa 0bb5 0be6-0bf2 1cd0 1cd2-1cd3 1cf2-1cf4 1cf8-1cf9 200c-200d 20f0 25cc 11300-11303 11305-1130c 1130f-11310 11313-11328 1132a-11330 11332-11333 11335-11339 1133b-11344 11347-11348 1134b-1134d 11350 11357 1135d-11363 11366-1136c 11370-11374,-1,</tr> + <tr>Gujr,Gujarati,158,0000 000d 0020-0023 0025 0027-003f 005b-005f 007b-007e 00a0 00ad 00d7 00f7 0951-0952 0964-0965 0a81-0a83 0a85-0a8d 0a8f-0a91 0a93-0aa8 0aaa-0ab0 0ab2-0ab3 0ab5-0ab9 0abc-0ac5 0ac7-0ac9 0acb-0acd 0ad0 0ae0-0ae3 0ae6-0af1 0af9 200b-200d 2010 2013-2014 2018-2019 201c-201d 2026 20b9 2212 25cc a830-a839,-1,</tr> + <tr>Guru,Gurmukhi,153,0000 000d 0020-0023 0025 0027-003f 005b-005f 007b-007e 00a0 00ad 00d7 00f7 0951-0952 0964-0965 0a01-0a03 0a05-0a0a 0a0f-0a10 0a13-0a28 0a2a-0a30 0a32-0a33 0a35-0a36 0a38-0a39 0a3c 0a3e-0a42 0a47-0a48 0a4b-0a4d 0a51 0a59-0a5c 0a5e 0a66-0a75 200b-200d 2010 2013-2014 2018-2019 201c-201d 2026 20b9 2212 25cc 262c a830-a839,0,</tr> + <tr>Hano,Hanunoo,31,0000 000d 0020 00a0 1720-1736 200b-200d 25cc,10,00d7 2012-2015 2022 25fb-25fe</tr> +@@ -78,6 +78,7 @@ + <tr>Maka,Makasar,29,0000 000d 0020 25cc 11ee0-11ef8,-1,</tr> + <tr>Mand,Mandaic,37,0000 000d 0020 00a0 0640 0840-085b 085e 200c-200d 25cc,0,</tr> + <tr>Mani,Manichaean,59,0000 000d 0020 00a0 0640 200c-200d 25cc 10ac0-10ae6 10aeb-10af6,-1,</tr> ++ <tr>Manu,Mayan Numerals,25,0000 000d 0020 00a0 1d2e0-1d2f3,-1,</tr> + <tr>Marc,Marchen,73,0000 000d 0020 00a0 25cc 11c70-11c8f 11c92-11ca7 11ca9-11cb6,-1,</tr> + <tr>Medf,Medefaidrin,96,0000 000d 0020 00a0 25cc 16e40-16e9a,-1,</tr> + <tr>Mend,Mende Kikakui,218,0000 000d 0020 00a0 25cc 1e800-1e8c4 1e8c7-1e8d6,-1,</tr> +@@ -123,6 +124,7 @@ + <tr>Sidd,Siddham,99,0000 000d 0020 00a0 200c-200d 25cc 11580-115b5 115b8-115dd,-1,</tr> + <tr>Sind,Khudawadi,93,0000 000d 0020 002e 003a-003b 00a0 0964-0965 200c-200d 2013-2014 25cc a830-a839 112b0-112ea 112f0-112f9,-1,</tr> + <tr>Sinh,Sinhala,169,0000 000d 0020-0023 0025 0027-003f 005b-005f 007b-007e 00a0 00ad 00d7 00f7 0964-0965 0d82-0d83 0d85-0d96 0d9a-0db1 0db3-0dbb 0dbd 0dc0-0dc6 0dca 0dcf-0dd4 0dd6 0dd8-0ddf 0de6-0def 0df2-0df4 200b-200d 2013-2014 2018-2019 201c-201d 2026 2212 25cc 111e1-111f4,-1,</tr> ++ <tr>Siyq,Indic Siyaq Numbers,95,0000 000d 0020 00a0 0627 1ec71-1ecb4 0660-0669 06f0-06f9,-1,</tr> + <tr>Sogd,Sogdian,47,0000 000d 0020 00a0 25cc 10f30-10f59,-1,</tr> + <tr>Sogo,Old Sogdian,44,0000 000d 0020 00a0 10f00-10f27,-1,</tr> + <tr>Sora,Sora Sompeng,47,0000 000d 0020-0021 0028-0029 002c-002e 003b 00a0 2010 110d0-110e8 110f0-110f9,-1,</tr> +@@ -146,6 +148,7 @@ + <tr>Tirh,Tirhuta,101,0000 000d 0020 00a0 0964-0965 200c-200d 25cc a830-a839 11480-114c7 114d0-114d9,-1,</tr> + <tr>Ugar,Ugaritic,35,0000 000d 0020 00a0 10380-1039d 1039f,0,</tr> + <tr>Vaii,Vai,304,0000 000d 0020 00a0 a500-a62b,8,0022 0027 002c-002d 2018-2019 201c-201d</tr> ++ <tr>Wcho,Wancho,79,0000 000d 0020 0022 0027-0029 002c-002f 005b-005d 007b 007d 00a0 201c-201d 25cc 1e2c0-1e2f9 1e2ff,-1,</tr> + <tr>Wara,Warang Citi,101,0000 000d 0020-0021 0028-0029 002c-002e 003a-003b 003f 00a0 2013-2014 201c-201d 118a0-118f2 118ff,-1,</tr> + <tr>Xpeo,Old Persian,54,0000 000d 0020 00a0 103a0-103c3 103c8-103d5,0,</tr> + <tr>Xsux,Cuneiform,1238,0000 000d 0020 00a0 12000-12399 12400-1246e 12470-12474 12480-12543,0,</tr> +diff --git a/nototools/extract_ohchr_attributions.py b/nototools/extract_ohchr_attributions.py +index e094f09..fdd980d 100755 +--- a/nototools/extract_ohchr_attributions.py ++++ b/nototools/extract_ohchr_attributions.py +@@ -69,12 +69,12 @@ + + + import argparse +-import codecs +-import HTMLParser as html ++import html.parser as html + import re + + from nototools import tool_utils + ++ + class ParseOhchr(html.HTMLParser): + def __init__(self, trace=False): + html.HTMLParser.__init__(self) +diff --git a/nototools/font_data.py b/nototools/font_data.py +index 489754f..e6d0cf5 100755 +--- a/nototools/font_data.py ++++ b/nototools/font_data.py +@@ -18,6 +18,7 @@ + + __author__ = 'rooz...@google.com (Roozbeh Pournader)' + ++from nototools.py23 import unicode + from nototools import opentype_data + + from fontTools.ttLib.tables._n_a_m_e import NameRecord +@@ -219,7 +220,7 @@ def add_to_cmap(font, mapping): + cmap_table = font['cmap'] + for table in cmap_table.tables: + if (table.format, table.platformID, table.platEncID) in UNICODE_CMAPS: +- for code, glyph in mapping.iteritems(): ++ for code, glyph in mapping.items(): + table.cmap[code] = glyph + + +diff --git a/nototools/generate_coverage_data.py b/nototools/generate_coverage_data.py +index ab2b2d6..0244c08 100755 +--- a/nototools/generate_coverage_data.py ++++ b/nototools/generate_coverage_data.py +@@ -61,7 +61,7 @@ def _create_metadata(**kwargs): + date = str(kwargs.pop('date', datetime.date.today())) + program = str(kwargs.pop('program', 'generate_coverage_data')) + arglist = [ +- (k, v) for k, v in sorted(kwargs.iteritems()) ++ (k, v) for k, v in sorted(kwargs.items()) + if v is not None] + return MetaData(date, program, arglist) + +diff --git a/nototools/generate_dingbats_html.py b/nototools/generate_dingbats_html.py +index c2b80b2..8079932 100755 +--- a/nototools/generate_dingbats_html.py ++++ b/nototools/generate_dingbats_html.py +@@ -30,6 +30,7 @@ from nototools import cmap_data + from nototools import font_data + from nototools import tool_utils + from nototools import unicode_data ++from nototools.py23 import basestring + + """Generate html comparison of codepoints in various fonts.""" + +@@ -869,7 +870,7 @@ def _flagged_name(cp, flag_sets): + except: + raise Exception('no name for %04X' % cp) + flags = [] +- for k, v in sorted(flag_sets.iteritems()): ++ for k, v in sorted(flag_sets.items()): + if (cp in v[0]) == v[1]: + flags.append(k) + if flags: +diff --git a/nototools/generate_sample_from_exemplar.py b/nototools/generate_sample_from_exemplar.py +index 83c2c20..293c5e4 100755 +--- a/nototools/generate_sample_from_exemplar.py ++++ b/nototools/generate_sample_from_exemplar.py +@@ -26,6 +26,7 @@ import re + import shutil + import xml.etree.cElementTree as ElementTree + ++from nototools.py23 import unichr + from nototools import cldr_data + from nototools import create_image + from nototools import extra_locale_data +@@ -423,7 +424,7 @@ def generate_sample_for_script(script, loc_map): + num_locales = len(loc_map) + + if num_locales == 1: +- tag, info = loc_map.iteritems().next() ++ tag, info = loc_map.items().next() + exemplars = info[2] + ex_len = len(exemplars) + info = '%s (1 locale)\nfrom exemplars for %s (%s%d chars)' % ( +diff --git a/nototools/generate_sample_text.py b/nototools/generate_sample_text.py +index a970df3..bc0a7d6 100755 +--- a/nototools/generate_sample_text.py ++++ b/nototools/generate_sample_text.py +@@ -19,11 +19,14 @@ + __author__ = 'rooz...@google.com (Roozbeh Pournader)' + + import sys ++from nototools.py23 import unichr ++ + + def char_rep_to_code(char_rep): + """Converts a character representation in hex to its code.""" + return int(char_rep, 16) + ++ + def main(argv): + """Outputs a space-separated list of characters based on input ranges.""" + chars = [] +@@ -36,7 +39,11 @@ def main(argv): + else: + chars.append(char_rep_to_code(arg)) + chars = u' '.join([unichr(code) for code in chars]) +- print(chars.encode('UTF-8')) ++ if sys.version_info >= (2, 7): ++ print(chars) ++ else: ++ print(chars.encode('UTF-8')) ++ + + if __name__ == '__main__': + main(sys.argv) +diff --git a/nototools/generate_sample_text_html.py b/nototools/generate_sample_text_html.py +index ff86900..1f9168d 100755 +--- a/nototools/generate_sample_text_html.py ++++ b/nototools/generate_sample_text_html.py +@@ -48,7 +48,7 @@ def generate_table(filename): + f.write('<table>\n') + f.write('<tr><th>Script<br/>BCP<th>name<th>type<th>text\n') + +- for script, samples in sorted(script_to_samples.iteritems()): ++ for script, samples in sorted(script_to_samples.items()): + script_en = cldr_data.get_english_script_name(script) + f.write('<tr><th colspan=4>%s\n' % script_en) + for bcp, sample_type, sample_text in samples: +@@ -95,14 +95,14 @@ def _get_script_to_samples(): + continue + script_to_samples[script].append((bcp, sample_type)) + +- for script, samples in sorted(script_to_samples.iteritems()): ++ for script, samples in sorted(script_to_samples.items()): + pref = {} + for bcp, sample_type in samples: + if bcp not in pref or sample_type == 'udhr': + pref[bcp] = sample_type + + full_samples = [] +- for bcp, sample_type in sorted(pref.iteritems()): ++ for bcp, sample_type in sorted(pref.items()): + filename = '%s_%s.txt' % (bcp, sample_type) + filepath = path.join(sample_dir, filename) + with codecs.open(filepath, 'r', 'utf-8') as f: +diff --git a/nototools/generate_samples.py b/nototools/generate_samples.py +index 5aa2eb8..9de9e58 100755 +--- a/nototools/generate_samples.py ++++ b/nototools/generate_samples.py +@@ -18,6 +18,8 @@ import argparse + import codecs + import re + ++from nototools.py23 import unichr, unicode ++ + """Generate samples from a description file.""" + + USAGE = """ +@@ -104,8 +106,9 @@ sequences within an group. + # about working with non-bmp unicode on narrow builds. + + # constants +-_LEAD_OFFSET = 0xD800 - (0x10000 >> 10); +-_SURROGATE_OFFSET = 0x10000 - (0xD800 << 10) - 0xDC00; ++_LEAD_OFFSET = 0xD800 - (0x10000 >> 10) ++_SURROGATE_OFFSET = 0x10000 - (0xD800 << 10) - 0xDC00 ++ + + def cp_to_str(cp): + if cp < 0x10000: +@@ -115,7 +118,7 @@ def cp_to_str(cp): + + def surrogate_pair_to_cp(low, high): + # assumes low and high are proper surrogate values +- return (low << 10) + high + _SURROGATE_OFFSET; ++ return (low << 10) + high + _SURROGATE_OFFSET + + + def prev_cp(ustr, index): +diff --git a/nototools/generate_website_2_data.py b/nototools/generate_website_2_data.py +index 9abd96f..886c114 100755 +--- a/nototools/generate_website_2_data.py ++++ b/nototools/generate_website_2_data.py +@@ -37,6 +37,7 @@ import xml.etree.cElementTree as ElementTree + + from fontTools import ttLib + ++from nototools.py23 import unicode + from nototools import cldr_data + from nototools import coverage + from nototools import create_image +@@ -74,7 +75,7 @@ Built on %s from the following noto repositor%s: + + def check_families(family_map): + # ensure the count of fonts in a family is what we expect +- for family_id, family in sorted(family_map.iteritems()): ++ for family_id, family in sorted(family_map.items()): + hinted_members = family.hinted_members + unhinted_members = family.unhinted_members + +@@ -178,7 +179,7 @@ def get_family_id_to_lang_scrs(lang_scrs, script_to_family_ids): + if not jpan_lang_scrs: + break; + +- for f, ls in sorted(family_id_to_lang_scrs.iteritems()): ++ for f, ls in sorted(family_id_to_lang_scrs.items()): + if not ls: + print('!family %s has no lang' % f) + +@@ -282,7 +283,7 @@ def get_family_id_to_regions(family_id_to_lang_scr_to_sample_key): + + family_id_to_regions = collections.defaultdict(set) + warnings = set() +- for tup in family_id_to_lang_scr_to_sample_key.iteritems(): ++ for tup in family_id_to_lang_scr_to_sample_key.items(): + family_id, lang_scr_to_sample_key = tup + for lang_scr in lang_scr_to_sample_key: + if lang_scr in lang_scr_to_regions: +@@ -301,7 +302,7 @@ def get_family_id_to_regions(family_id_to_lang_scr_to_sample_key): + + def get_region_to_family_ids(family_id_to_regions): + region_to_family_ids = collections.defaultdict(set) +- for family_id, regions in family_id_to_regions.iteritems(): ++ for family_id, regions in family_id_to_regions.items(): + for region in regions: + region_to_family_ids[region].add(family_id) + return region_to_family_ids +@@ -500,7 +501,7 @@ def get_family_id_to_default_lang_scr(family_id_to_lang_scrs, families): + """ + + family_id_to_default_lang_scr = {} +- for family_id, lang_scrs in family_id_to_lang_scrs.iteritems(): ++ for family_id, lang_scrs in family_id_to_lang_scrs.items(): + script_key = families[family_id].rep_member.script + primary_script = noto_fonts.script_key_to_primary_script(script_key) + +@@ -758,7 +759,7 @@ class WebGen(object): + + def build_zips(self, families): + zip_info = {} +- for key, family_data in families.iteritems(): ++ for key, family_data in families.items(): + zip_info[key] = self.build_family_zips(key, family_data) + return zip_info + +@@ -821,7 +822,7 @@ class WebGen(object): + + def build_css(self, families): + css_info = {} +- for key, family_data in families.iteritems(): ++ for key, family_data in families.items(): + css_info[key] = self.build_family_css(key, family_data) + return css_info + +@@ -887,7 +888,7 @@ class WebGen(object): + + # get inverse map from lang_scr to family_id + lang_scr_to_family_ids = collections.defaultdict(set) +- for family_id, lang_scrs in family_id_to_lang_scr_to_sample_key.iteritems(): ++ for family_id, lang_scrs in family_id_to_lang_scr_to_sample_key.items(): + for lang_scr in lang_scrs: + lang_scr_to_family_ids[lang_scr].add(family_id) + +@@ -998,7 +999,7 @@ class WebGen(object): + family_id_to_regions, family_css_info, + lang_scr_sort_order): + for family_id, lang_scrs_map in sorted( +- family_id_to_lang_scr_to_sample_key.iteritems()): ++ family_id_to_lang_scr_to_sample_key.items()): + family = families[family_id] + regions = family_id_to_regions[family_id] + css_info = family_css_info[family_id] +@@ -1115,7 +1116,7 @@ class WebGen(object): + # name them based on the language. But most of the samples with the + # same font and text will be the same, because the fonts generally + # only customize for a few language tags. Sad! +- for lang_scr, sample_key in sorted(lang_scr_to_sample_key.iteritems()): ++ for lang_scr, sample_key in sorted(lang_scr_to_sample_key.items()): + sample_text, attrib, _ = sample_key_to_info[sample_key] + self.build_family_images( + family, lang_scr, sample_text, attrib, sample_key) +@@ -1228,7 +1229,7 @@ class WebGen(object): + if 'families' in self.debug: + print('\n#debug families') + print('%d found' % len(families)) +- for i, (family_id, family) in enumerate(sorted(families.iteritems())): ++ for i, (family_id, family) in enumerate(sorted(families.items())): + print('%2d] %s (%s, %s)' % ( + i, family_id, family.name, noto_fonts.get_family_filename(family))) + if family.hinted_members: +@@ -1243,7 +1244,7 @@ class WebGen(object): + print('\n#debug script to family ids') + print('%d found' % len(script_to_family_ids)) + for i, (script, family_ids) in enumerate( +- sorted(script_to_family_ids.iteritems())): ++ sorted(script_to_family_ids.items())): + print('%2d] %s: %s' % (i, script, ', '.join(sorted(family_ids)))) + + all_lang_scrs = set(['und-' + script for script in script_to_family_ids]) +@@ -1264,7 +1265,7 @@ class WebGen(object): + if 'lang_scr_to_sample_infos' in self.debug: + print('\n#debug lang+script to sample infos') + print('%d found' % len(lang_scr_to_sample_infos)) +- for lang_scr, info_list in sorted(lang_scr_to_sample_infos.iteritems()): ++ for lang_scr, info_list in sorted(lang_scr_to_sample_infos.items()): + for info in info_list: + print('%s: %s, %s, len %d' % ( + lang_scr, info[2], info[1], len(info[0]))) +@@ -1275,7 +1276,7 @@ class WebGen(object): + print('\n#debug family id to list of lang+script') + print('%d found' % len(family_id_to_lang_scrs)) + for i, (family_id, lang_scrs) in enumerate( +- sorted(family_id_to_lang_scrs.iteritems())): ++ sorted(family_id_to_lang_scrs.items())): + print('%3d] %s: (%d) %s' % ( + i, family_id, len(lang_scrs), ' '.join(sorted(lang_scrs)))) + +@@ -1286,16 +1287,16 @@ class WebGen(object): + print('\n#debug family id to map from lang+script to sample key') + print('%d found' % len(family_id_to_lang_scr_to_sample_key)) + for i, (family_id, lang_scr_to_sample_key) in enumerate( +- sorted(family_id_to_lang_scr_to_sample_key.iteritems())): ++ sorted(family_id_to_lang_scr_to_sample_key.items())): + print('%2d] %s (%d):' % (i, family_id, len(lang_scr_to_sample_key))) + for j, (lang_scr, sample_key) in enumerate( +- sorted(lang_scr_to_sample_key.iteritems())): ++ sorted(lang_scr_to_sample_key.items())): + print(' [%2d] %s: %s' % (j, lang_scr, sample_key)) + if 'sample_key_to_info' in self.debug: + print('\n#debug sample key to sample info') + print('%d found' % len(sample_key_to_info)) + for i, (sample_key, info) in enumerate( +- sorted(sample_key_to_info.iteritems())): ++ sorted(sample_key_to_info.items())): + print('%2d] %s: %s, len %d' % ( + i, sample_key, info[1], len(info[0]))) + +@@ -1305,7 +1306,7 @@ class WebGen(object): + print('\n#debug family id to regions') + print('%d found' % len(family_id_to_regions)) + for i, (family_id, regions) in enumerate( +- sorted(family_id_to_regions.iteritems())): ++ sorted(family_id_to_regions.items())): + print('%2d] %s: (%d) %s' % ( + i, family_id, len(regions), ', '.join(sorted(regions)))) + +@@ -1314,7 +1315,7 @@ class WebGen(object): + print('\n#debug region to family ids') + print('%d found' % len(region_to_family_ids)) + for i, (region, family_ids) in enumerate( +- sorted(region_to_family_ids.iteritems())): ++ sorted(region_to_family_ids.items())): + print('%2d] %s: (%d) %s' % ( + i, region, len(family_ids), ', '.join(sorted(family_ids)))) + +@@ -1324,7 +1325,7 @@ class WebGen(object): + print('\n#debug family id to default lang scr') + print('%d found' % len(family_id_to_default_lang_scr)) + for i, (family_id, lang_scr) in enumerate( +- sorted(family_id_to_default_lang_scr.iteritems())): ++ sorted(family_id_to_default_lang_scr.items())): + print('%2d] %s: %s' % (i, family_id, lang_scr)) + + region_data = get_region_lat_lng_data(region_to_family_ids.keys()) +@@ -1458,7 +1459,7 @@ def get_repo_info(skip_checks): + message = '\n'.join(msg_lines) + repo_info[repo_name] = message + +- for rname, v in sorted(repo_info.iteritems()): ++ for rname, v in sorted(repo_info.items()): + print('--%s--\n%s' % (rname, v)) + if errors: + raise Exception('Some repos are not clean\n' + '\n'.join(errors)) +diff --git a/nototools/generate_website_data.py b/nototools/generate_website_data.py +index 57697c8..dc0efb8 100755 +--- a/nototools/generate_website_data.py ++++ b/nototools/generate_website_data.py +@@ -36,6 +36,7 @@ import xml.etree.cElementTree as ElementTree + + from fontTools import ttLib + ++from nototools.py23 import unichr, unicode + from nototools import coverage + from nototools import create_image + from nototools import extra_locale_data +diff --git a/nototools/grab_adobe_download.py b/nototools/grab_adobe_download.py +index 6b8725e..11a0672 100755 +--- a/nototools/grab_adobe_download.py ++++ b/nototools/grab_adobe_download.py +@@ -56,8 +56,9 @@ import shutil + import sys + import zipfile + +-import notoconfig +-import grab_download ++from nototools import notoconfig ++from nototools import grab_download ++ + + def unzip_to_directory_tree(drop_dir, filepath): + skip_re = re.compile('.*/OTF-Fallback/.*') +@@ -88,7 +89,7 @@ def unzip_to_directory_tree(drop_dir, filepath): + def main(): + params = { + 'default_srcdir': os.path.expanduser('~/Downloads'), +- 'default_dstdir': notoconfig.values.get('adobe_data'), ++ 'default_dstdir': notoconfig._values.get('adobe_data'), + 'default_regex': r'Noto_Sans_CJK-\d{4}-\d{2}-\d{2}\.zip' + } + grab_download.invoke_main( +diff --git a/nototools/grab_mt_download.py b/nototools/grab_mt_download.py +index a37481a..775bb0c 100755 +--- a/nototools/grab_mt_download.py ++++ b/nototools/grab_mt_download.py +@@ -43,7 +43,7 @@ built by this tool. + __author__ = "dougf...@google.com (Doug Felt)" + + import argparse +-import cStringIO ++from io import BytesIO + import os + import os.path + import re +@@ -53,8 +53,9 @@ import zipfile + + from fontTools import ttLib + +-import grab_download +-import notoconfig ++from nototools import grab_download ++from nototools import notoconfig ++ + + def write_data_to_file(data, root, subdir, filename): + dstdir = os.path.join(root, subdir) +@@ -107,7 +108,7 @@ def unzip_to_directory_tree(drop_dir, filepath): + # it in the same subdir the .ttf file went into. + # else we put it at drop_dir (no subdir). + if name.endswith('.ttf'): +- blobfile = cStringIO.StringIO(data) ++ blobfile = BytesIO(data) + font = ttLib.TTFont(blobfile) + subdir = 'hinted' if font.get('fpgm') or font.get('prep') else 'unhinted' + write_data_to_file(data, drop_dir, subdir, name) +@@ -137,7 +138,7 @@ def unzip_to_directory_tree(drop_dir, filepath): + def main(): + params = { + 'default_srcdir': os.path.expanduser('~/Downloads'), +- 'default_dstdir': notoconfig.values.get('monotype_data'), ++ 'default_dstdir': notoconfig._values.get('monotype_data'), + 'default_regex': r'Noto.*_\d{8}.zip', + } + grab_download.invoke_main( +diff --git a/nototools/hb_input.py b/nototools/hb_input.py +index 00e3815..ba8a924 100644 +--- a/nototools/hb_input.py ++++ b/nototools/hb_input.py +@@ -15,7 +15,6 @@ + + from __future__ import division, print_function + +-from fontTools.ttLib import TTFont + from nototools import summary + from nototools.py23 import unichr + +diff --git a/nototools/lang_data.py b/nototools/lang_data.py +index dbec17b..52b2e56 100755 +--- a/nototools/lang_data.py ++++ b/nototools/lang_data.py +@@ -310,13 +310,13 @@ def main(): + print() + print('lang_script to names') + lang_script_to_names = _get_lang_script_to_names() +- for t in sorted(lang_script_to_names.iteritems()): ++ for t in sorted(lang_script_to_names.items()): + print('%s: %s' % t) + + print() + print('script to default lang') + script_to_default_lang = _get_script_to_default_lang() +- for t in sorted(script_to_default_lang.iteritems()): ++ for t in sorted(script_to_default_lang.items()): + print('%s: %s' % t) + + +diff --git a/nototools/lint_cmap_reqs.py b/nototools/lint_cmap_reqs.py +index a4488ce..bb3abbd 100755 +--- a/nototools/lint_cmap_reqs.py ++++ b/nototools/lint_cmap_reqs.py +@@ -19,6 +19,7 @@ + import argparse + import sys + ++from nototools.py23 import unicode + from nototools import lint_config + from nototools import noto_data + from nototools import opentype_data +@@ -53,8 +54,8 @@ def _symbol_set(): + + def _math_set(): + """Returns set of characters that should be supported in Noto Math.""" +- ranges = unicode_data._parse_code_ranges(noto_data.MATH_RANGES_TXT) +- return _code_range_to_set(ranges) ++ ranges = unicode_data._parse_code_ranges(noto_data.MATH_RANGES_TXT) ++ return _code_range_to_set(ranges) + + + def _cjk_set(): +@@ -214,7 +215,8 @@ def main(): + sys.stderr.write('writing %s\n' % args.outfile) + cmap_data.write_cmap_data_file(cmapdata, args.outfile, pretty=True) + else: +- print(cmap_data.write_cmap_data(cmapdata, pretty=True)) ++ print(unicode(cmap_data.write_cmap_data(cmapdata, pretty=True), "utf-8")) ++ + + if __name__ == "__main__": + main() +diff --git a/nototools/lint_config.py b/nototools/lint_config.py +index a0cd8f6..d44bc36 100755 +--- a/nototools/lint_config.py ++++ b/nototools/lint_config.py +@@ -30,6 +30,7 @@ + import argparse + import re + ++from nototools.py23 import basestring + + spec_format = """ + A spec defines a list of conditions to be run in sequence. A condition consists of +@@ -308,7 +309,7 @@ class FontCondition(object): + fn = value[0] + val = value[1] + cond_name = None +- for fn_text, fn_obj in FontCondition.fn_map.iteritems(): ++ for fn_text, fn_obj in FontCondition.fn_map.items(): + if fn == fn_obj: + cond_name = fn_text + break +@@ -320,7 +321,7 @@ class FontCondition(object): + cond_value = str(val) + return '%s %s' % (cond_name, cond_value) + +- output = ['\n %s: %s' % (k,value_str(v)) for k,v in self.__dict__.iteritems() if v] ++ output = ['\n %s: %s' % (k,value_str(v)) for k,v in self.__dict__.items() if v] + return 'condition:%s' % ''.join(output) + + +diff --git a/nototools/match_font_names.py b/nototools/match_font_names.py +index 7be5cb5..0fc8c3f 100644 +--- a/nototools/match_font_names.py ++++ b/nototools/match_font_names.py +@@ -13,6 +13,7 @@ import re + + from nototools import tool_utils + ++ + def _build_regex(names): + parts = [] + for name in names: +@@ -71,15 +72,15 @@ def _collect_names(names): + + + def main(): +- parser = argparse.ArgumentParser(); ++ parser = argparse.ArgumentParser() + parser.add_argument( + '-f', '--files', help='list of names and/or files (prefixed with \'@\'', + metavar='name', required=True, nargs='+') + parser.add_argument( + '-s', '--src_dir', help='directory under which to search for files', + metavar='dir', required=True) +- args = parser.parse_args(); +- _print_list(match_files(args.src_dir, _collect_names(args.files))); ++ args = parser.parse_args() ++ _print_list(match_files(args.src_dir, _collect_names(args.files))) + + + if __name__ == '__main__': +diff --git a/nototools/merge_fonts.py b/nototools/merge_fonts.py +index 8703c3c..0cb26c8 100755 +--- a/nototools/merge_fonts.py ++++ b/nototools/merge_fonts.py +@@ -126,14 +126,14 @@ def build_valid_filenames(files=files, directory=directory): + for f in files: + valid_file = directory + '/' + f + if not os.path.isfile(valid_file): +- log.warn('can not find %s, skipping it.' % valid_file) ++ log.warning('can not find %s, skipping it.' % valid_file) + else: + valid_files.append(valid_file) + + if len(valid_files) == 0: + return valid_files + if os.path.basename(valid_files[0]) != files[0]: +- log.warn('can not find the font %s to read line metrics from. Line ' ++ log.warning('can not find the font %s to read line metrics from. Line ' + + 'metrics in the result might be wrong.' % files[0]) + return valid_files + +@@ -153,7 +153,7 @@ def main(): + + valid_files = build_valid_filenames(directory=args.directory) + if len(valid_files) <= 1: +- log.warn('expecting at least two fonts to merge, but only got %d ' ++ log.warning('expecting at least two fonts to merge, but only got %d ' + + 'font(s).', len(valid_files)) + sys.exit(-1) + +diff --git a/nototools/merge_noto.py b/nototools/merge_noto.py +index 5ea6a38..c1b3bf5 100755 +--- a/nototools/merge_noto.py ++++ b/nototools/merge_noto.py +@@ -17,6 +17,7 @@ + """Merges Noto fonts.""" + import os.path + import tempfile ++from nototools.py23 import unicode + + from fontTools import merge + from fontTools import ttLib +@@ -51,6 +52,7 @@ def has_gsub_table(fontfile): + font = ttLib.TTFont(fontfile) + return 'GSUB' in font + ++ + SCRIPT_TO_OPENTYPE_SCRIPT_TAG = { + 'CypriotSyllabary': 'cprt', + 'Deseret': 'dsrt', +diff --git a/nototools/missing_coverage.py b/nototools/missing_coverage.py +index e941fb5..65fc88d 100755 +--- a/nototools/missing_coverage.py ++++ b/nototools/missing_coverage.py +@@ -60,7 +60,7 @@ def display_missing(cmap_file): + + + def main(): +- default_cmap_name = 'noto_cmap_phase3.xml' ++ default_cmap_name = 'data/noto_cmap_phase3.xml' + + parser = argparse.ArgumentParser() + parser.add_argument( +@@ -70,5 +70,6 @@ def main(): + + display_missing(args.filename) + ++ + if __name__ == '__main__': + main() +diff --git a/nototools/mti_cmap_data.py b/nototools/mti_cmap_data.py +index f29098c..9a1b0bd 100755 +--- a/nototools/mti_cmap_data.py ++++ b/nototools/mti_cmap_data.py +@@ -101,8 +101,8 @@ def get_script_to_cmaps(csvdata): + except: + raise ValueError('error in col %d of row %d: "%s"' % ( + i, n, v)) +- return { script: (cmap, xcmap) +- for script, cmap, xcmap in zip(header, data, xdata) } ++ return {script: (cmap, xcmap) ++ for script, cmap, xcmap in zip(header, data, xdata)} + + + def cmap_data_from_csv( +@@ -111,7 +111,7 @@ def cmap_data_from_csv( + metadata = cmap_data.create_metadata('mti_cmap_data', args) + script_to_cmaps = get_script_to_cmaps(csvdata) + if scripts or exclude_scripts: +- script_list = script_to_cmap.keys() ++ script_list = script_to_cmaps.keys() + for script in script_list: + if scripts and script not in scripts: + del script_to_cmaps[script] +diff --git a/nototools/noto_cmap_reqs.py b/nototools/noto_cmap_reqs.py +index f35ecee..536ac60 100755 +--- a/nototools/noto_cmap_reqs.py ++++ b/nototools/noto_cmap_reqs.py +@@ -42,6 +42,7 @@ import argparse + import collections + import sys + ++from nototools.py23 import unichr + from nototools import cldr_data + from nototools import cmap_data + from nototools import compare_cmap_data +@@ -59,7 +60,7 @@ _MERGED_SCRIPTS_BY_TARGET = { + def _invert_script_to_chars(script_to_chars): + """Convert script_to_chars to char_to_scripts and return.""" + char_to_scripts = collections.defaultdict(set) +- for script, cps in script_to_chars.iteritems(): ++ for script, cps in script_to_chars.items(): + for cp in cps: + char_to_scripts[cp].add(script) + return char_to_scripts +@@ -321,14 +322,14 @@ def _build_block_to_primary_script(): + num += 1 + max_script = None + max_script_count = 0 +- for script, count in script_counts.iteritems(): ++ for script, count in script_counts.items(): + if count > max_script_count: + max_script = script + max_script_count = count + if num == 0: + max_script = 'EXCL' # exclude + elif float(max_script_count) / num < 0.8: +- info = sorted(script_counts.iteritems(), key=lambda t: (-t[1], t[0])) ++ info = sorted(script_counts.items(), key=lambda t: (-t[1], t[0])) + block_info = '%s %s' % (block, ', '.join('%s/%d' % t for t in info)) + if block in assigned_primaries: + max_script = assigned_primaries[block] +@@ -425,7 +426,7 @@ def _unassign_latin(cmap_ops): + + def _assign_cldr_punct(cmap_ops): + """Assigns cldr punctuation to scripts.""" +- for script, punct in collect_cldr_punct.script_to_punct().iteritems(): ++ for script, punct in collect_cldr_punct.script_to_punct().items(): + if script != 'CURRENCY': + cmap_ops.phase('assign cldr punct for ' + script) + cmap_ops.ensure_script(script) +@@ -449,7 +450,7 @@ def _reassign_scripts(cmap_ops, scripts, new_script): + + def _reassign_merged_scripts(cmap_ops): + """Reassign merged scripts.""" +- for target, scripts in sorted(_MERGED_SCRIPTS_BY_TARGET.iteritems()): ++ for target, scripts in sorted(_MERGED_SCRIPTS_BY_TARGET.items()): + cmap_ops.phase('reassign to ' + target) + _reassign_scripts(cmap_ops, scripts, target) + +@@ -615,7 +616,7 @@ def _remove_empty(cmap_ops): + """Remove any empty scripts (Braille should be one).""" + cmap_ops.phase('remove empty') + script_to_chars = cmap_ops.create_script_to_chars() +- for script, chars in script_to_chars.iteritems(): ++ for script, chars in script_to_chars.items(): + if not chars: + cmap_ops.delete_script(script) + +@@ -2623,7 +2624,7 @@ def _regen_script_required(): + for script, comment, data in _SCRIPT_REQUIRED + } + scripts = set(unicode_data.all_scripts()) +- for to_script, from_scripts in _MERGED_SCRIPTS_BY_TARGET.iteritems(): ++ for to_script, from_scripts in _MERGED_SCRIPTS_BY_TARGET.items(): + scripts.add(to_script) + scripts -= set(from_scripts) + # keep extra script data, e.g. 'Aran' +@@ -2688,7 +2689,7 @@ def _assign_script_required(cmap_ops): + def _assign_script_special_chars(cmap_ops): + """Assign special characters listed in opentype_data.""" + cmap_ops.phase('assign special chars') +- for script, chars in opentype_data.SPECIAL_CHARACTERS_NEEDED.iteritems(): ++ for script, chars in opentype_data.SPECIAL_CHARACTERS_NEEDED.items(): + cmap_ops.add_all(frozenset(chars), script) + + +@@ -2698,7 +2699,7 @@ def _assign_legacy_phase2(cmap_ops): + legacy_map = cmap_data.create_map_from_table(legacy_data.table) + legacy_script_to_chars = { + script: tool_utils.parse_int_ranges(row.ranges) +- for script, row in legacy_map.iteritems()} ++ for script, row in legacy_map.items()} + + # The default is to include all legacy characters, except for the chars + # listed for these scripts, for some default chars, and for some scripts. +@@ -2774,7 +2775,7 @@ def _assign_bidi_mirroring(cmap_ops): + cmap_ops.phase('bidi mirroring') + script_to_chars = cmap_ops.create_script_to_chars() + mirrored = unicode_data.mirrored_chars() +- for script, cps in sorted(script_to_chars.iteritems()): ++ for script, cps in sorted(script_to_chars.items()): + mirrored_in_script = cps & mirrored + if not mirrored_in_script: + continue +@@ -3141,7 +3142,7 @@ def _assign_dotted_circle(cmap_ops): + # circle, but as using dotted circle is the convention used by Unicode in + # their code charts we'll require it for Arabic too. + script_to_chars = cmap_ops.create_script_to_chars() +- for script, charset in sorted(script_to_chars.iteritems()): ++ for script, charset in sorted(script_to_chars.items()): + if script == 'EXCL': + continue + nsm = frozenset(cp for cp in charset if is_combining(cp)) +diff --git a/nototools/noto_data.py b/nototools/noto_data.py +index 9f92ffc..37c7600 100755 +--- a/nototools/noto_data.py ++++ b/nototools/noto_data.py +@@ -184,13 +184,13 @@ def _char_set(compact_set_text): + if sep_index == -1: + cp = int(part, base=16) + assert cp > prev +- # print '%04x' % cp ++ # print('%04x' % cp) + result.add(cp) + prev = cp + else: + start = int(part[:sep_index], base=16) + end = int(part[sep_index + 2:], base=16) +- # print '%04x..%04x' % (start, end) ++ # print('%04x..%04x' % (start, end)) + assert start > prev + assert end > start + for cp in range(start, end + 1): +diff --git a/nototools/noto_font_cmaps.py b/nototools/noto_font_cmaps.py +index 1799dcb..cea16c4 100755 +--- a/nototools/noto_font_cmaps.py ++++ b/nototools/noto_font_cmaps.py +@@ -18,21 +18,12 @@ + + import argparse + import collections +-import datetime +-import os +-from os import path + import sys + +-from fontTools import ttLib +- +-from nototools import cldr_data ++from nototools.py23 import unicode + from nototools import cmap_data + from nototools import lint_config +-from nototools import noto_data + from nototools import noto_fonts +-from nototools import noto_lint +-from nototools import opentype_data +-from nototools import unicode_data + + + def report_set_differences(name_to_cpset, out=sys.stderr): +@@ -45,30 +36,29 @@ def report_set_differences(name_to_cpset, out=sys.stderr): + while len(name_to_cpset): + common = None + if len(name_to_cpset) > 1: +- for name, cpset in name_to_cpset.iteritems(): ++ for name, cpset in name_to_cpset.items(): + if common == None: + common = cpset.copy() + else: + common &= cpset + if common: + name = ', '.join(sorted(name_to_cpset)) +- print >> out, '%d%s in common among %s:' % ( +- len(common), additional, name) +- print >> out, lint_config.write_int_ranges(common) ++ out.write('%d%s in common among %s:\n' % (len(common), additional, name)) ++ out.write('%s\n' % lint_config.write_int_ranges(common)) + +- for name, cpset in sorted(name_to_cpset.iteritems()): ++ for name, cpset in sorted(name_to_cpset.items()): + extra = cpset - common + if extra: + name_to_cpset[name] = extra + else: +- print >> out, '%s has no additional' % name ++ out.write('%s has no additional\n' % name) + del name_to_cpset[name] + additional = ' additional' + continue + +- for name, cpset in sorted(name_to_cpset.iteritems()): +- print >> out, '%s has %d%s:' % (name, len(cpset), additional) +- print >> out, lint_config.write_int_ranges(cpset) ++ for name, cpset in sorted(name_to_cpset.items()): ++ out.write('%s has %d%s:\n' % (name, len(cpset), additional)) ++ out.write('%s\n' % lint_config.write_int_ranges(cpset)) + break + + +@@ -97,10 +87,10 @@ def font_cmap_data(paths): + script_to_data[script].append(ScriptData(family_name, script, cpset)) + + def report_data_error(index, script_data): +- print >> sys.stderr, ' %d: %s, %d, %s' % ( ++ sys.stderr.write(' %d: %s, %d, %s\n' % ( + index, script_data.family_name, script_data.script, + len(script_data.cpset), +- lint_config.write_int_ranges(script_data.cpset)) ++ lint_config.write_int_ranges(script_data.cpset))) + + script_to_cmap = {} + for script in sorted(script_to_data): +@@ -116,7 +106,7 @@ def font_cmap_data(paths): + if len(test_data.cpset) > len(selected_cpset): + selected_cpset = test_data.cpset + if differ: +- print >> sys.stderr, '\nscript %s cmaps differ' % script ++ sys.stderr.write('\nscript %s cmaps differ\n' % script) + differences = {i.family_name: i.cpset for i in data} + report_set_differences(differences) + script_to_cmap[script] = selected_cpset +@@ -143,7 +133,7 @@ def main(): + if args.outfile: + cmap_data.write_cmap_data_file(cmapdata, args.outfile, pretty=True) + else: +- print(cmap_data.write_cmap_data(cmapdata, pretty=True)) ++ print(unicode(cmap_data.write_cmap_data(cmapdata, pretty=True), "utf-8")) + + + if __name__ == "__main__": +diff --git a/nototools/noto_font_coverage.py b/nototools/noto_font_coverage.py +index afe7c2d..37ecc7e 100755 +--- a/nototools/noto_font_coverage.py ++++ b/nototools/noto_font_coverage.py +@@ -43,8 +43,8 @@ def codepoints(cp_list): + temp = low + low = high + high = temp +- for cp in range(low, high + 1): +- result.add(cp) ++ for cp2 in range(low, high + 1): ++ result.add(cp2) + else: + result.add(int(cp, 16)) + return result +@@ -129,7 +129,7 @@ def run(args, families): + else: + missing.add(cp) + if result: +- for k, v in sorted(result.iteritems()): ++ for k, v in sorted(result.items()): + print(' %s: %s' % (k, to_ranges_str(v))) + if missing: + print(' not supported: %s' % to_ranges_str(missing)) +@@ -162,5 +162,6 @@ def main(): + families = noto_fonts.get_families(fonts) + run(args, families) + ++ + if __name__ == '__main__': + main() +diff --git a/nototools/noto_fonts.py b/nototools/noto_fonts.py +index 8af1286..f2fc9bd 100644 +--- a/nototools/noto_fonts.py ++++ b/nototools/noto_fonts.py +@@ -169,7 +169,7 @@ def get_noto_font(filepath, family_name='Arimo|Cousine|Tinos|Noto', + slope, fmt) = match.groups() + else: + if _EXT_REGEX.match(filename): +- print >> sys.stderr, '%s did not match font regex' % filename ++ sys.stderr.write('%s did not match font regex\n' % filename) + return None + + is_cjk = filedir.endswith('noto-cjk') +@@ -194,7 +194,7 @@ def get_noto_font(filepath, family_name='Arimo|Cousine|Tinos|Noto', + is_mono = mono == 'Mono' + + if width not in [None, '', 'Condensed', 'SemiCondensed', 'ExtraCondensed']: +- print >> sys.stderr, 'noto_fonts: Unexpected width "%s"' % width ++ sys.stderr.write('noto_fonts: Unexpected width "%s"\n' % (width)) + if width in ['SemiCond', 'Narrow']: + width = 'SemiCondensed' + elif width == 'Cond': +@@ -223,7 +223,7 @@ def get_noto_font(filepath, family_name='Arimo|Cousine|Tinos|Noto', + try: + script = convert_to_four_letter(script) + except ValueError: +- print >> sys.stderr, 'unknown script: %s for %s' % (script, filename) ++ sys.stderr.write('unknown script: %s for %s\n' % (script, filename)) + return None + + if not weight: +@@ -448,11 +448,13 @@ def get_noto_fonts(paths=NOTO_FONT_PATHS): + """Scan paths for fonts, and create a NotoFont for each one, returning a list + of these. 'paths' defaults to the standard noto font paths, using notoconfig.""" + +- font_dirs = filter(None, [tool_utils.resolve_path(p) for p in paths]) ++ font_dirs = list(filter(None, [tool_utils.resolve_path(p) for p in paths])) + print('Getting fonts from: %s' % font_dirs) + + all_fonts = [] + for font_dir in font_dirs: ++ if not os.path.exists(font_dir): ++ continue + for filename in os.listdir(font_dir): + if not _EXT_REGEX.match(filename): + continue +@@ -508,7 +510,7 @@ def get_families(fonts): + family_id = noto_font_to_family_id(font) + family_id_to_fonts[family_id].add(font) + +- for family_id, fonts in family_id_to_fonts.iteritems(): ++ for family_id, fonts in family_id_to_fonts.items(): + hinted_members = [] + unhinted_members = [] + rep_member = None +diff --git a/nototools/noto_lint.py b/nototools/noto_lint.py +index f668f30..7f6290a 100755 +--- a/nototools/noto_lint.py ++++ b/nototools/noto_lint.py +@@ -53,6 +53,11 @@ from nototools import render + from nototools import tool_utils + from nototools import unicode_data + ++try: ++ from future_builtins import filter ++except ImportError: ++ pass ++ + # from wikipedia windows 1252 page. As of windows 98. + WIN_ANSI_CODEPOINTS = ( + '0000-007f 00A0-00ff 20ac 201a 0192 201e 2026 2020 2021 02c6 2030 0160 2039 0152 017d' +@@ -373,7 +378,7 @@ def _build_cmap_dict(filename): + data = cmap_data.read_cmap_data_file(filename) + script_to_rowdata = cmap_data.create_map_from_table(data.table) + return {script: frozenset(tool_utils.parse_int_ranges(rd.ranges)) +- for script, rd in script_to_rowdata.iteritems()} ++ for script, rd in script_to_rowdata.items()} + + + _phase_2_map = None +@@ -733,7 +738,7 @@ def check_font(font_props, filename_error, + + if char_filter: + # old_needed_size = len(needed_chars) +- needed_chars = set(itertools.ifilter(char_filter[1].accept, needed_chars)) ++ needed_chars = set(filter(char_filter[1].accept, needed_chars)) + # TODO(dougfelt): figure out how to make this info available without messing up output + # print('filter needed char size: %d -> %d' % (old_needed_size, len(needed_chars)) + +@@ -751,7 +756,7 @@ def check_font(font_props, filename_error, + return + unexpected_chars = set(cmap) - expected_chars + if char_filter and unexpected_chars: +- unexpected_chars = set(itertools.ifilter(char_filter[1].accept, unexpected_chars)) ++ unexpected_chars = set(filter(char_filter[1].accept, unexpected_chars)) + if unexpected_chars: + warn("cmap/script_unexpected", "Chars", + "The following %d chars were not expected in the font: %s" +@@ -863,7 +868,7 @@ def check_font(font_props, filename_error, + + if tests.check('cmap/non_characters'): + non_characters = frozenset( +- range(0xFDD0, 0xFDEF + 1) ++ list(range(0xFDD0, 0xFDEF + 1)) + + [0xFFFE + plane_no * 0x10000 for plane_no in range(0, 17)] + + [0xFFFF + plane_no * 0x10000 for plane_no in range(0, 17)]) + non_characters_in_cmap = non_characters & set(cmap.keys()) +@@ -1769,7 +1774,7 @@ def check_font(font_props, filename_error, + if tests.check('advances/whitespace'): + if font_props.is_mono: + space_width = get_horizontal_advance(space_char) +- cps = [tab_char, nbsp_char] + range(0x2000, 0x200B) ++ cps = [tab_char, nbsp_char] + list(range(0x2000, 0x200B)) + for cp in cps: + if cp in cmap: + expect_width(cp, space_width) +@@ -1823,7 +1828,7 @@ def check_font(font_props, filename_error, + # FIXME: Add support for Arabic, Syriac, Mongolian, Phags-Pa, + # Devanagari, Bengali, etc + joins_to_right = set(range(0x1680, 0x169B + 1)) +- joins_to_left = set(range(0x1680, 0x169A + 1) + [0x169C]) ++ joins_to_left = set(list(range(0x1680, 0x169A + 1)) + [0x169C]) + all_joining = joins_to_right | joins_to_left + + glyf_table = font['glyf'] +diff --git a/nototools/noto_names.py b/nototools/noto_names.py +index 55c31d8..819d231 100755 +--- a/nototools/noto_names.py ++++ b/nototools/noto_names.py +@@ -38,7 +38,6 @@ file names that follow noto conventions, and generates the corresponding + name table names. So it is not useful for non-noto fonts. + """ + +-from __future__ import print_function + import argparse + import collections + import datetime +@@ -218,7 +217,7 @@ def _preferred_parts(noto_font): + parts_pair = _preferred_cjk_parts(noto_font) + else: + parts_pair = _preferred_non_cjk_parts(noto_font) +- return filter(None, parts_pair[0]), filter(None, parts_pair[1]) ++ return list(filter(None, parts_pair[0])), list(filter(None, parts_pair[1])) + + + def _shift_parts(family_parts, subfamily_parts, stop_fn): +@@ -426,7 +425,7 @@ def _postscript_name(preferred_family, preferred_subfamily, include_regular): + result = re.sub('CJK(JP|KR|SC|TC)', repl_fn, result) + + if len(result) > 63: +- print('postscript name longer than 63 characters:\n"%s"' % (result), file=sys.stderr) ++ sys.stderr.write('postscript name longer than 63 characters:\n"%s"\n' % (result)) + return result + + +@@ -587,7 +586,7 @@ def name_table_data(noto_font, family_to_name_info, phase): + try: + info = family_to_name_info[family_id] + except KeyError: +- print('no family name info for "%s"' % family_id, file=sys.stderr) ++ sys.stderr.write('no family name info for "%s"\n' % family_id) + return None + + family_parts, subfamily_parts = _wws_parts(*_preferred_parts(noto_font)) +@@ -596,9 +595,9 @@ def name_table_data(noto_font, family_to_name_info, phase): + ['Bold'], + ['Italic'], + ['Bold', 'Italic']]: +- print('Error in family name info: %s requires preferred names, but info says none are required.' +- % path.basename(noto_font.filepath), file=sys.stderr) +- print(subfamily_parts, file=sys.stderr) ++ sys.stderr.write('Error in family name info: %s requires preferred names, but info says none are required.\n' ++ % path.basename(noto_font.filepath)) ++ sys.stderr.write('%s\n' % subfamily_parts) + return None + + # for phase 3 we'll now force include_regular +@@ -707,13 +706,13 @@ def create_family_to_name_info(notofonts, phase, extra_styles): + continue + seen_ids.add(family_id) + preferred_family, _ = _preferred_parts(noto_font) +- preferred_subfamily = filter(None, [ ++ preferred_subfamily = list(filter(None, [ + 'Mono' if noto_font.is_mono else None, + 'UI' if noto_font.is_UI else None, + 'Display' if noto_font.is_display else None, + 'ExtraCondensed', # longest width name +- 'ExtraLight', # longest weight name +- 'Italic']) # longest slope name ++ 'ExtraLight', # longest weight name ++ 'Italic'])) # longest slope name + _, subfamily_parts = _wws_parts(preferred_family, preferred_subfamily) + family_to_parts[family_id].update(subfamily_parts) + family_parts, _ = _original_parts(preferred_family, preferred_subfamily) +@@ -723,7 +722,7 @@ def create_family_to_name_info(notofonts, phase, extra_styles): + + + result = {} +- for family_id, part_set in family_to_parts.iteritems(): ++ for family_id, part_set in family_to_parts.items(): + # Even through CJK mono fonts are in their own families and have only + # bold and regular weights, they behave like they have more weights like + # the rest of CJK. +@@ -860,7 +859,7 @@ def _dump_name_data(name_data): + if attr == 'original_family' and len(value) > ORIGINAL_FAMILY_LIMIT: + print('## family too long (%2d): %s' % (len(value), value)) + err = True +- print(' %20s: %s' % (attr, value)) ++ print(' %20s: %s' % (attr, value)) + else: + print(' %20s: <none>' % attr) + return err +diff --git a/nototools/py23.py b/nototools/py23.py +index 1a87b51..17e22e9 100644 +--- a/nototools/py23.py ++++ b/nototools/py23.py +@@ -9,3 +9,7 @@ try: + except NameError: + unichr = chr + ++try: ++ basestring = basestring ++except NameError: ++ basestring = str +diff --git a/nototools/report_coverage_data.py b/nototools/report_coverage_data.py +index ee243d2..cbaaabc 100755 +--- a/nototools/report_coverage_data.py ++++ b/nototools/report_coverage_data.py +@@ -13,6 +13,7 @@ + # 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. ++from __future__ import print_function + + import argparse + import codecs +@@ -21,17 +22,14 @@ import math + from os import path + import sys + +-from nototools import cmap_data +-from nototools import coverage + from nototools import generate_coverage_data + from nototools import tool_utils + from nototools import unicode_data + +-from fontTools import ttLib +- + default_version=6.0 + default_coverage_file = 'noto_cmap_phase2.xml' + ++ + def get_defined_cps(version=default_version, exclude_ranges=None): + defined_cps = unicode_data.defined_characters(version) + if exclude_ranges: +@@ -143,7 +141,7 @@ def write_block_coverage_html(block_data, names, msg, out_file=sys.stdout): + + def write_block_coverage_text(block_data, names, msg, out_file=sys.stdout): + block_data.sort() +- print >> out_file, msg ++ print(msg, file=out_file) + name_len = max(len(t[2]) for t in block_data) + fmt_str = '%%%ds' % name_len + fmt_str = '%13s ' + fmt_str + ' %5s' +@@ -153,7 +151,7 @@ def write_block_coverage_text(block_data, names, msg, out_file=sys.stdout): + header_parts.append(fmt_str % ('range', 'block name', 'count')) + for fmt, name in zip(header_fmts, names): + header_parts.append(fmt % name) +- print >> out_file, ' '.join(header_parts) ++ print(' '.join(header_parts), file=out_file) + for start, end, name, block_cps, block_covered_cps_list in block_data: + line_parts = [] + range_str = '%04x-%04x' % (start, end) +@@ -164,7 +162,7 @@ def write_block_coverage_text(block_data, names, msg, out_file=sys.stdout): + pct = '%d%%' % int(100.0 * num_covered / num_in_block) + part_str = '%5d %4s' % (num_covered, pct) + line_parts.append(fmt % part_str) +- print >> out_file, ' '.join(line_parts) ++ print(' '.join(line_parts), file=out_file) + out_file.flush() + + +@@ -266,5 +264,6 @@ def main(): + write_block_coverage( + block_data, names, args.message, args.format, args.output_file) + ++ + if __name__ == '__main__': + main() +diff --git a/nototools/sample_with_font.py b/nototools/sample_with_font.py +index b1df256..cb96e5d 100755 +--- a/nototools/sample_with_font.py ++++ b/nototools/sample_with_font.py +@@ -21,14 +21,14 @@ Replicating the sample text they describe can be a bit tedious. This + lets you interactively search characters in the font by name to assemble + a string and save it to a file.""" + ++from builtins import input + import argparse + import codecs +-import readline + ++from nototools.py23 import unichr + from nototools import coverage + from nototools import unicode_data + +-from fontTools import ttLib + + def _help(): + print ('enter a string to match or one of the following:\n' +@@ -44,7 +44,7 @@ def _build_text(name_map, initial_text=''): + text = initial_text + print('build text using map of length %d' % len(name_map)) + while True: +- line = raw_input('> ') ++ line = input('> ') + if not line: + continue + if line == 'quit': +@@ -64,13 +64,13 @@ def _build_text(name_map, initial_text=''): + text = '' + continue + if line == 'write': +- line = raw_input('file name> ') ++ line = input('file name> ') + if line: + _write_text(line, text) + continue + + matches = [] +- for name, cp in sorted(name_map.iteritems()): ++ for name, cp in sorted(name_map.items()): + if line in name: + matches.append(name) + if not matches: +@@ -104,7 +104,7 @@ def _build_text(name_map, initial_text=''): + print('multiple matches:\n ' + '\n '.join( + '[%2d] %s' % (i, n) for i, n in enumerate(matches))) + while True: +- line = raw_input('0-%d or q to skip> ' % (len(matches) - 1)) ++ line = input('0-%d or q to skip> ' % (len(matches) - 1)) + if line == 'q': + select_multiple = False + break +diff --git a/nototools/subset.py b/nototools/subset.py +index cb9ac96..a9a86ef 100755 +--- a/nototools/subset.py ++++ b/nototools/subset.py +@@ -22,7 +22,7 @@ import sys + + from fontTools import subset + +-import coverage ++from nototools import coverage + + + def subset_font(source_file, target_file, +@@ -59,7 +59,7 @@ def subset_font(source_file, target_file, + opt.drop_tables = ['+TTFA'] + + if options is not None: +- for name, value in options.iteritems(): ++ for name, value in options.items(): + setattr(opt, name, value) + + if include is not None: +diff --git a/nototools/subset_symbols.py b/nototools/subset_symbols.py +index af2ca10..ed64147 100755 +--- a/nototools/subset_symbols.py ++++ b/nototools/subset_symbols.py +@@ -20,7 +20,7 @@ __author__ = 'rooz...@google.com (Roozbeh Pournader)' + + import sys + +-import subset ++from nototools import subset + + + def main(argv): +diff --git a/nototools/swat_license.py b/nototools/swat_license.py +index 5b5c078..2263a5d 100755 +--- a/nototools/swat_license.py ++++ b/nototools/swat_license.py +@@ -362,7 +362,7 @@ def _construct_ttc_fonts(fonts, dst_root, dry_run): + basename = path.basename(font.filepath) + basename_to_fonts[basename].append(font) + +- for ttcfont, components in sorted(_ttc_fonts.iteritems()): ++ for ttcfont, components in sorted(_ttc_fonts.items()): + rel_filepath = _noto_relative_path(ttcfont.filepath) + print('-----\nBuilding %s' % rel_filepath) + +@@ -373,7 +373,7 @@ def _construct_ttc_fonts(fonts, dst_root, dry_run): + possible_components = basename_to_fonts.get(component) + if not possible_components: + print('! no match for component named %s in %s' % ( +- component, rel_path)) ++ component, rel_filepath)) + component_list = [] + break + +@@ -383,7 +383,7 @@ def _construct_ttc_fonts(fonts, dst_root, dry_run): + if matched_possible_component: + print('! already matched possible component %s for %s' % ( + matched_possible_component.filename, +- possible_component_filename)) ++ possible_component.filename)) + matched_possible_component = None + break + matched_possible_component = possible_component +@@ -393,7 +393,7 @@ def _construct_ttc_fonts(fonts, dst_root, dry_run): + break + component_list.append(matched_possible_component) + if not component_list: +- print('! cannot generate ttc font %s' % rel_path) ++ print('! cannot generate ttc font %s' % rel_filepath) + continue + + print('components:\n ' + '\n '.join( +diff --git a/nototools/test_vertical_extents.py b/nototools/test_vertical_extents.py +index 22e2a6b..27288e0 100755 +--- a/nototools/test_vertical_extents.py ++++ b/nototools/test_vertical_extents.py +@@ -37,8 +37,8 @@ import re + import sys + import xml.etree.ElementTree + +-import coverage +- ++from nototools.py23 import unichr, unicode ++from nototools import coverage + from nototools import font_caching + from nototools import render + +diff --git a/nototools/tool_utils.py b/nototools/tool_utils.py +index fe351cc..81a2be1 100644 +--- a/nototools/tool_utils.py ++++ b/nototools/tool_utils.py +@@ -166,7 +166,7 @@ def generate_zip_with_7za_from_filepairs(pairs, archive_path): + if source_root not in pair_map: + pair_map[source_root] = set() + pair_map[source_root].add(dest) +- for source_root, dest_set in pair_map.iteritems(): ++ for source_root, dest_set in pair_map.items(): + generate_zip_with_7za(source_root, sorted(dest_set), archive_path) + + +@@ -339,7 +339,7 @@ def git_head_commit(repo): + text = subprocess.check_output( + ['git', 'show', '-s', '--date=format:%Y-%m-%d %H:%M:%S', + '--no-expand-tabs', '--pretty=format:%H\t%cd\t%s', 'HEAD']) +- return tuple(text.strip().split('\t', 2)) ++ return tuple(text.strip().split(b'\t', 2)) + + + def git_check_remote_commit(repo, commit, remote='upstream', branch='master'): +diff --git a/nototools/ttc_utils.py b/nototools/ttc_utils.py +index 37a4453..753fcc4 100755 +--- a/nototools/ttc_utils.py ++++ b/nototools/ttc_utils.py +@@ -25,6 +25,7 @@ import subprocess + + from fontTools.ttLib.tables._n_a_m_e import table__n_a_m_e as NameTable + ++from nototools.py23 import unicode + from nototools import tool_utils + + _ttcHeader = '>4sLL' +diff --git a/nototools/unicode_data.py b/nototools/unicode_data.py +index e3a9d66..e91b861 100755 +--- a/nototools/unicode_data.py ++++ b/nototools/unicode_data.py +@@ -32,9 +32,8 @@ import collections + import os + from os import path + import re +-import sys + +-from nototools.py23 import unichr, unicode ++from nototools.py23 import unichr, unicode, basestring + try: + import unicodedata2 as unicodedata # Unicode 8 compliant native lib + except ImportError: +@@ -1051,7 +1050,7 @@ def _load_emoji_group_data(): + group_list.extend(_read_emoji_test_data(_SUPPLEMENTAL_EMOJI_GROUP_DATA)) + for i, (seq, group, subgroup, name) in enumerate(group_list): + if seq in _emoji_group_data: +- print('seq %s alredy in group data as %s' % (seq_to_string(seq), _emoji_group_data[seq])) ++ print('seq %s already in group data as %s' % (seq_to_string(seq), _emoji_group_data[seq])) + print(' new value would be %s' % str((i, group, subgroup, name))) + _emoji_group_data[seq] = (i, group, subgroup, name) + +diff --git a/nototools/unittests/font_tests.py b/nototools/unittests/font_tests.py +index 8ee0835..ac6a9a8 100644 +--- a/nototools/unittests/font_tests.py ++++ b/nototools/unittests/font_tests.py +@@ -684,7 +684,7 @@ class TestGlyphAreas(unittest.TestCase): + errors = [] + for other in glyph_sets[1:]: + other_pen = GlyphAreaPen(other) +- for name, area in areas.iteritems(): ++ for name, area in areas.items(): + if name in self.whitelist: + continue + other[name].draw(other_pen) +diff --git a/nototools/update_alpha.py b/nototools/update_alpha.py +index c6155bd..153ae23 100755 +--- a/nototools/update_alpha.py ++++ b/nototools/update_alpha.py +@@ -34,8 +34,8 @@ import shutil + import subprocess + import sys + +-import notoconfig +-import compare_summary ++from nototools import notoconfig ++from nototools import compare_summary + + class RedirectStdout(object): + """Redirect stdout to file.""" +@@ -112,7 +112,7 @@ def push_to_noto_alpha(alphadir, srcdir, dry_run): + new_label = 'h/u' + if new_label: + name_info[root_name] = new_label +- names = ', '.join(sorted(['%s(%s)' % (k, v) for k, v in name_info.iteritems()])) ++ names = ', '.join(sorted(['%s(%s)' % (k, v) for k, v in name_info.items()])) + + # get date of the drop from srcdir + result = re.search(r'\d{4}_\d{2}_\d{2}', srcdir) +diff --git a/nototools/update_cldr.py b/nototools/update_cldr.py +index 261b07d..a5a4d47 100755 +--- a/nototools/update_cldr.py ++++ b/nototools/update_cldr.py +@@ -24,8 +24,8 @@ import shutil + import string + import subprocess + +-import notoconfig +-import tool_utils ++from nototools import notoconfig ++from nototools import tool_utils + + CLDR_SUBDIRS = [ + 'common/main', +diff --git a/nototools/update_udhr_samples.py b/nototools/update_udhr_samples.py +index 5ebaf7a..698952a 100755 +--- a/nototools/update_udhr_samples.py ++++ b/nototools/update_udhr_samples.py +@@ -24,17 +24,21 @@ import codecs + import collections + import datetime + import difflib +-import generate_website_data + import os + import re + import shutil +-import unicode_data +-import urllib ++ ++try: ++ from urllib.request import urlretrieve ++except: ++ from urllib import urlretrieve ++ + import xml.etree.ElementTree as ET +-import zipfile + +-from nototools import cldr_data ++from nototools.py23 import unicode ++from nototools import generate_website_data + from nototools import tool_utils ++from nototools import unicode_data + + DIR_URL = 'http://unicode.org/udhr/d' + UDHR_XML_ZIP_NAME = 'udhr_xml.zip' +@@ -44,7 +48,7 @@ def fetch_udhr(fetch_dir): + """Fetch UDHR xml bundle from unicode.org to fetch_dir.""" + fetch_dir = tool_utils.ensure_dir_exists(fetch_dir) + dstfile = os.path.join(fetch_dir, UDHR_XML_ZIP_NAME) +- result = urllib.urlretrieve(UDHR_XML_ZIP_URL, dstfile) ++ result = urlretrieve(UDHR_XML_ZIP_URL, dstfile) + print('Fetched: ' + result[0]) + + +@@ -324,7 +328,7 @@ def add_likely_scripts(bcp_to_code): + """Add script subtags where they are not present in the bcp code. If + we don't know the script""" + result= {} +- for bcp, code in bcp_to_code.iteritems(): ++ for bcp, code in bcp_to_code.items(): + if code in CODE_TO_BCP: + new_bcp = CODE_TO_BCP[code] + else: +@@ -361,7 +365,7 @@ EXCLUDE_CODES = frozenset([ + + def filter_bcp_to_code(bcp_to_code): + """Exclude entries for samples improved in noto/sample_texts and for bad samples.""" +- return {k: v for k, v in bcp_to_code.iteritems() ++ return {k: v for k, v in bcp_to_code.items() + if k not in EXCLUDE_BCP and v not in EXCLUDE_CODES} + + +@@ -481,7 +485,7 @@ def get_bcp_to_code_attrib_sample(src_dir, ohchr_dir): + def print_bcp_to_code_attrib_sample(bcp_to_code_attrib_sample): + print('index size: %s' % len(bcp_to_code_attrib_sample)) + for bcp, (code, attrib, sample) in sorted( +- bcp_to_code_attrib_sample.iteritems()): ++ bcp_to_code_attrib_sample.items()): + print('%s: %s, %s\n "%s"' % (bcp, code, attrib, sample)) + + +@@ -656,7 +660,7 @@ def update_samples( + sample_attrib_list = [] + sample_dir = tool_utils.ensure_dir_exists(sample_dir) + count = 0 +- for bcp, (code, attrib, sample) in bcp_to_code_attrib_sample.iteritems(): ++ for bcp, (code, attrib, sample) in bcp_to_code_attrib_sample.items(): + dst_file = '%s_udhr.txt' % bcp + dst_path = os.path.join(sample_dir, dst_file) + if in_repo and os.path.isfile(dst_path) and dst_file not in tool_samples: +@@ -817,7 +821,7 @@ def compare_samples(base_dir, trg_dir, trg_to_base_name=lambda x: x, opts=None): + with codecs.open(trg_path, 'r', 'utf8') as f: + trg_text = f.read() + if not base_text: +- print('base text (%s) is empty' % k) ++ print('base text (%s) is empty' % base_path) + continue + if not trg_text: + print('target text is empty: %s' % trg_path) +@@ -954,7 +958,7 @@ def main(): + in_repo, args.no_stage) + + if args.mapping: +- print_bcp_to_code_attrib(bcp_to_code_attrib) ++ print_bcp_to_code_attrib_sample(bcp_to_code_attrib_sample) + + if args.base_sample_dir: + compare_samples( diff -Nru nototools-0.2.0/debian/patches/series nototools-0.2.0/debian/patches/series --- nototools-0.2.0/debian/patches/series 2019-10-21 11:04:18.000000000 +0000 +++ nototools-0.2.0/debian/patches/series 2019-12-11 00:44:06.000000000 +0000 @@ -1 +1,2 @@ test_vertical_extensions-Correctly-use-local-imports.patch +more-python3-fixes.patch