From e3e06a7ccf280d05438cdd206c060262551be161 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 6 Dec 2018 11:55:01 +0100 Subject: [PATCH 1/4] Update the font_table example. Print characters corresponding to codepoints >0xff. Add comments explaining font charmaps (briefly). Don't use pyplot. --- .../font_table_sgskip.py | 104 ++++++++++++++++++ .../font_table_ttf_sgskip.py | 66 ----------- 2 files changed, 104 insertions(+), 66 deletions(-) create mode 100644 examples/text_labels_and_annotations/font_table_sgskip.py delete mode 100644 examples/text_labels_and_annotations/font_table_ttf_sgskip.py diff --git a/examples/text_labels_and_annotations/font_table_sgskip.py b/examples/text_labels_and_annotations/font_table_sgskip.py new file mode 100644 index 000000000000..3f25f00ed8db --- /dev/null +++ b/examples/text_labels_and_annotations/font_table_sgskip.py @@ -0,0 +1,104 @@ +""" +========== +Font table +========== + +Matplotlib's font support is provided by the FreeType library. + +Here, we use `~.Axes.table` build a font table that shows the glyphs by Unicode +codepoint, and print the glyphs corresponding to codepoints beyond 0xff. + +Download this script and run it to investigate a font by running :: + + python font_table_sgskip.py /path/to/font/file +""" + +import unicodedata + +import matplotlib.font_manager as fm +from matplotlib.ft2font import FT2Font +import matplotlib.pyplot as plt +import numpy as np + + +def draw_font_table(path): + """ + Parameters + ---------- + path : str or None + The path to the font file. If None, use Matplotlib's default font. + """ + + if path is None: + path = fm.findfont(fm.FontProperties()) # The default font. + + font = FT2Font(path) + # A charmap is a mapping of "character codes" (in the sense of a character + # encoding, e.g. latin-1) to glyph indices (i.e. the internal storage table + # of the font face). + # In FreeType>=2.1, a Unicode charmap (i.e. mapping Unicode codepoints) + # is selected by default. Moreover, recent versions of FreeType will + # automatically synthesize such a charmap if the font does not include one + # (this behavior depends on the font format; for example it is present + # since FreeType 2.0 for Type 1 fonts but only since FreeType 2.8 for + # TrueType (actually, SFNT) fonts). + # The code below (specifically, the ``chr(char_code)`` call) assumes that + # we have indeed selected a Unicode charmap. + codes = font.get_charmap().items() + + labelc = ["{:X}".format(i) for i in range(16)] + labelr = ["{:02X}".format(16 * i) for i in range(16)] + chars = [["" for c in range(16)] for r in range(16)] + non_8bit = [] + + for char_code, glyph_index in codes: + char = chr(char_code) + if char_code >= 256: + non_8bit.append(( + str(glyph_index), + char, + unicodedata.name( + char, + f"{char_code:#x} ({font.get_glyph_name(glyph_index)})"), + )) + continue + r, c = divmod(char_code, 16) + chars[r][c] = char + if non_8bit: + indices, *_ = zip(*non_8bit) + max_indices_len = max(map(len, indices)) + print("The font face contains the following glyphs corresponding to " + "code points beyond 0xff:") + for index, char, name in non_8bit: + print(f"{index:>{max_indices_len}} {char} {name}") + + ax = plt.figure(figsize=(8, 4), dpi=120).subplots() + ax.set_title(path) + ax.set_axis_off() + + table = ax.table( + cellText=chars, + rowLabels=labelr, + colLabels=labelc, + rowColours=["palegreen"] * 16, + colColours=["palegreen"] * 16, + cellColours=[[".95" for c in range(16)] for r in range(16)], + cellLoc='center', + loc='upper left', + ) + for key, cell in table.get_celld().items(): + row, col = key + if row > 0 and col > -1: # Beware of table's idiosyncratic indexing... + cell.set_text_props(fontproperties=fm.FontProperties(fname=path)) + + plt.show() + + +if __name__ == "__main__": + from argparse import ArgumentParser + + parser = ArgumentParser() + parser.add_argument("path", nargs="?", help="Path to the font file.") + args = parser.parse_args() + + draw_font_table(args.path) diff --git a/examples/text_labels_and_annotations/font_table_ttf_sgskip.py b/examples/text_labels_and_annotations/font_table_ttf_sgskip.py deleted file mode 100644 index 6de73e68dea3..000000000000 --- a/examples/text_labels_and_annotations/font_table_ttf_sgskip.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -============== -Font Table TTF -============== - -Matplotlib has support for FreeType fonts. Here's a little example -using the 'table' command to build a font table that shows the glyphs -by character code. - -Usage python font_table_ttf.py somefile.ttf - -""" - -import sys -import os - -import matplotlib -from matplotlib.ft2font import FT2Font -from matplotlib.font_manager import FontProperties -import matplotlib.pyplot as plt - -# the font table grid - -labelc = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F'] -labelr = ['00', '10', '20', '30', '40', '50', '60', '70', '80', '90', - 'A0', 'B0', 'C0', 'D0', 'E0', 'F0'] - -if len(sys.argv) > 1: - fontname = sys.argv[1] -else: - fontname = os.path.join(matplotlib.get_data_path(), - 'fonts', 'ttf', 'DejaVuSans.ttf') - -font = FT2Font(fontname) -codes = sorted(font.get_charmap().items()) - -# a 16,16 array of character strings -chars = [['' for c in range(16)] for r in range(16)] -colors = [[(0.95, 0.95, 0.95) for c in range(16)] for r in range(16)] - -plt.figure(figsize=(8, 4), dpi=120) -for ccode, glyphind in codes: - if ccode >= 256: - continue - r, c = divmod(ccode, 16) - s = chr(ccode) - chars[r][c] = s - -lightgrn = (0.5, 0.8, 0.5) -plt.title(fontname) -tab = plt.table(cellText=chars, - rowLabels=labelr, - colLabels=labelc, - rowColours=[lightgrn] * 16, - colColours=[lightgrn] * 16, - cellColours=colors, - cellLoc='center', - loc='upper left') - -for key, cell in tab.get_celld().items(): - row, col = key - if row > 0 and col > 0: - cell.set_text_props(fontproperties=FontProperties(fname=fontname)) -plt.axis('off') -plt.show() From 91560e64c169e1f312c68eb6f50e5ece1864e0fc Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 9 Dec 2018 12:13:25 +0100 Subject: [PATCH 2/4] Include font table figure in sphinx-gallery --- .../{font_table_sgskip.py => font_table.py} | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) rename examples/text_labels_and_annotations/{font_table_sgskip.py => font_table.py} (76%) diff --git a/examples/text_labels_and_annotations/font_table_sgskip.py b/examples/text_labels_and_annotations/font_table.py similarity index 76% rename from examples/text_labels_and_annotations/font_table_sgskip.py rename to examples/text_labels_and_annotations/font_table.py index 3f25f00ed8db..f0147527bc7a 100644 --- a/examples/text_labels_and_annotations/font_table_sgskip.py +++ b/examples/text_labels_and_annotations/font_table.py @@ -5,12 +5,13 @@ Matplotlib's font support is provided by the FreeType library. -Here, we use `~.Axes.table` build a font table that shows the glyphs by Unicode -codepoint, and print the glyphs corresponding to codepoints beyond 0xff. +Here, we use `~.Axes.table` to draw a table that shows the glyphs by Unicode +codepoint. For brevity, the table only contains the first 256 glyphs. -Download this script and run it to investigate a font by running :: +The example is a full working script. You can download it and use it to +investigate a font by running :: - python font_table_sgskip.py /path/to/font/file + python font_table.py /path/to/font/file """ import unicodedata @@ -18,15 +19,17 @@ import matplotlib.font_manager as fm from matplotlib.ft2font import FT2Font import matplotlib.pyplot as plt -import numpy as np -def draw_font_table(path): +def draw_font_table(path, print_non_latin1=False): """ Parameters ---------- path : str or None The path to the font file. If None, use Matplotlib's default font. + print_non_latin1 : bool + Print non-latin1 chars (i.e. chars with codepoints beyond 255) to + stdout. """ if path is None: @@ -62,9 +65,9 @@ def draw_font_table(path): f"{char_code:#x} ({font.get_glyph_name(glyph_index)})"), )) continue - r, c = divmod(char_code, 16) - chars[r][c] = char - if non_8bit: + row, col = divmod(char_code, 16) + chars[row][col] = char + if non_8bit and print_non_latin1: indices, *_ = zip(*non_8bit) max_indices_len = max(map(len, indices)) print("The font face contains the following glyphs corresponding to " @@ -72,7 +75,7 @@ def draw_font_table(path): for index, char, name in non_8bit: print(f"{index:>{max_indices_len}} {char} {name}") - ax = plt.figure(figsize=(8, 4), dpi=120).subplots() + fig, ax = plt.subplots(figsize=(8, 4)) ax.set_title(path) ax.set_axis_off() @@ -91,14 +94,17 @@ def draw_font_table(path): if row > 0 and col > -1: # Beware of table's idiosyncratic indexing... cell.set_text_props(fontproperties=fm.FontProperties(fname=path)) + fig.tight_layout() plt.show() if __name__ == "__main__": from argparse import ArgumentParser - parser = ArgumentParser() + parser = ArgumentParser(description="Display a font table.") parser.add_argument("path", nargs="?", help="Path to the font file.") + parser.add_argument("--print-non-latin1", action="store_true", + help="Print non-latin1 chars to stdout.") args = parser.parse_args() - draw_font_table(args.path) + draw_font_table(args.path, args.print_non_latin1) From 90851cb2afb337216fda9adc6a7107ced80b2ead Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 9 Dec 2018 13:02:24 +0100 Subject: [PATCH 3/4] Factor out printing of the glyphs to stdout --- .../text_labels_and_annotations/font_table.py | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/examples/text_labels_and_annotations/font_table.py b/examples/text_labels_and_annotations/font_table.py index f0147527bc7a..1a5cc29b5e4d 100644 --- a/examples/text_labels_and_annotations/font_table.py +++ b/examples/text_labels_and_annotations/font_table.py @@ -21,21 +21,39 @@ import matplotlib.pyplot as plt -def draw_font_table(path, print_non_latin1=False): +def print_glyphs(font): + """Print the all the glyphs in the given FT2Font font to stdout.""" + charmap = font.get_charmap() + max_indices_len = len(str(max(charmap.values()))) + + print("The font face contains the following glyphs:") + for char_code, glyph_index in charmap.items(): + char = chr(char_code) + name = unicodedata.name( + char, + f"{char_code:#x} ({font.get_glyph_name(glyph_index)})") + print(f"{glyph_index:>{max_indices_len}} {char} {name}") + + +def draw_font_table(path, print_all=False): """ + Draw a font table of the first 255 chars of the given font. + Parameters ---------- path : str or None The path to the font file. If None, use Matplotlib's default font. - print_non_latin1 : bool - Print non-latin1 chars (i.e. chars with codepoints beyond 255) to - stdout. + print_all : bool + Additionally print all chars to stdout. """ if path is None: path = fm.findfont(fm.FontProperties()) # The default font. font = FT2Font(path) + if print_all: + print_glyphs(font) + # A charmap is a mapping of "character codes" (in the sense of a character # encoding, e.g. latin-1) to glyph indices (i.e. the internal storage table # of the font face). @@ -52,28 +70,12 @@ def draw_font_table(path, print_non_latin1=False): labelc = ["{:X}".format(i) for i in range(16)] labelr = ["{:02X}".format(16 * i) for i in range(16)] chars = [["" for c in range(16)] for r in range(16)] - non_8bit = [] for char_code, glyph_index in codes: - char = chr(char_code) if char_code >= 256: - non_8bit.append(( - str(glyph_index), - char, - unicodedata.name( - char, - f"{char_code:#x} ({font.get_glyph_name(glyph_index)})"), - )) continue row, col = divmod(char_code, 16) - chars[row][col] = char - if non_8bit and print_non_latin1: - indices, *_ = zip(*non_8bit) - max_indices_len = max(map(len, indices)) - print("The font face contains the following glyphs corresponding to " - "code points beyond 0xff:") - for index, char, name in non_8bit: - print(f"{index:>{max_indices_len}} {char} {name}") + chars[row][col] = chr(char_code) fig, ax = plt.subplots(figsize=(8, 4)) ax.set_title(path) @@ -103,8 +105,8 @@ def draw_font_table(path, print_non_latin1=False): parser = ArgumentParser(description="Display a font table.") parser.add_argument("path", nargs="?", help="Path to the font file.") - parser.add_argument("--print-non-latin1", action="store_true", - help="Print non-latin1 chars to stdout.") + parser.add_argument("--print-all", action="store_true", + help="Additionally, print all chars to stdout.") args = parser.parse_args() - draw_font_table(args.path, args.print_non_latin1) + draw_font_table(args.path, args.print_all) From 2a8ef8a73bd02428dca6028c84b97c4518c87cae Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 9 Dec 2018 17:49:15 +0100 Subject: [PATCH 4/4] Completely separate printing of glyphs --- .../text_labels_and_annotations/font_table.py | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/examples/text_labels_and_annotations/font_table.py b/examples/text_labels_and_annotations/font_table.py index 1a5cc29b5e4d..2cfbd366f652 100644 --- a/examples/text_labels_and_annotations/font_table.py +++ b/examples/text_labels_and_annotations/font_table.py @@ -21,8 +21,20 @@ import matplotlib.pyplot as plt -def print_glyphs(font): - """Print the all the glyphs in the given FT2Font font to stdout.""" +def print_glyphs(path): + """ + Print the all glyphs in the given font file to stdout. + + Parameters + ---------- + path : str or None + The path to the font file. If None, use Matplotlib's default font. + """ + if path is None: + path = fm.findfont(fm.FontProperties()) # The default font. + + font = FT2Font(path) + charmap = font.get_charmap() max_indices_len = len(str(max(charmap.values()))) @@ -35,7 +47,7 @@ def print_glyphs(font): print(f"{glyph_index:>{max_indices_len}} {char} {name}") -def draw_font_table(path, print_all=False): +def draw_font_table(path): """ Draw a font table of the first 255 chars of the given font. @@ -43,17 +55,11 @@ def draw_font_table(path, print_all=False): ---------- path : str or None The path to the font file. If None, use Matplotlib's default font. - print_all : bool - Additionally print all chars to stdout. """ - if path is None: path = fm.findfont(fm.FontProperties()) # The default font. font = FT2Font(path) - if print_all: - print_glyphs(font) - # A charmap is a mapping of "character codes" (in the sense of a character # encoding, e.g. latin-1) to glyph indices (i.e. the internal storage table # of the font face). @@ -109,4 +115,6 @@ def draw_font_table(path, print_all=False): help="Additionally, print all chars to stdout.") args = parser.parse_args() - draw_font_table(args.path, args.print_all) + if args.print_all: + print_glyphs(args.path) + draw_font_table(args.path)