"""Compare canonical encodings of one accented letter; write PNG to cwd.""" from pathlib import Path import unicodedata import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.patches import FancyBboxPatch def box(ax, x, y, width, label, fill, fontsize=12): ax.add_patch(FancyBboxPatch( (x, y), width, 44, boxstyle="round,pad=0.02,rounding_size=5", facecolor=fill, edgecolor="#627183", linewidth=1.1, )) ax.text(x + width / 2, y + 22, label, ha="center", va="center", fontsize=fontsize, family="DejaVu Sans Mono", color="#172330") def main(): precomposed = "\u00e9" decomposed = "e\u0301" assert unicodedata.normalize("NFC", decomposed) == precomposed assert unicodedata.normalize("NFD", precomposed) == decomposed assert [len(s.encode("utf-8")) for s in [precomposed, decomposed]] == [2, 3] fig = plt.figure(figsize=(9, 3.6), dpi=100, facecolor="white") ax = fig.add_axes([0, 0, 1, 1]) ax.set(xlim=(0, 900), ylim=(0, 360)) ax.axis("off") ax.text(30, 332, "One grapheme cluster, different stored representations", fontsize=17, weight="bold", color="#172330") for x, text in [(30, "Representation"), (230, "Unicode code points"), (530, "UTF-8 bytes (hex)"), (790, "Letter")]: ax.text(x, 286, text, fontsize=12, color="#34465b") for title, text, y in [("Precomposed", precomposed, 205), ("Decomposed", decomposed, 103)]: ax.text(30, y + 28, title, fontsize=13, va="center", color="#172330") count = len(text) ax.add_patch(FancyBboxPatch( (216, y - 12), 276, 68, boxstyle="round,pad=0.02,rounding_size=8", facecolor="#edf5ff", edgecolor="#3c6e9b", linewidth=1.1, )) start = 230 if count == 2 else 300 for i, char in enumerate(text): box(ax, start + i * 128, y, 116, f"U+{ord(char):04X}", "white") for i, byte in enumerate(text.encode("utf-8")): box(ax, 530 + i * 67, y, 57, f"{byte:02X}", "#f8f1df") # Show the abstract letter, not a benchmark of Matplotlib's shaping. ax.text(812, y + 22, precomposed, fontsize=34, ha="center", va="center", color="#172330") ax.text(350, y - 29, "one extended grapheme cluster", ha="center", fontsize=10, color="#3c6e9b") ax.text(30, 28, "Canonical normalization relates the rows; neither byte count nor code-point count counts the clusters.", fontsize=10.5, color="#34465b") fig.savefig(Path.cwd() / "grapheme-cluster-codepoints.png", dpi=100, facecolor="white", transparent=False) plt.close(fig) if __name__ == "__main__": main()