codexgrapheme-cluster-codepoints.py
grapheme-cluster-codepoints.py
"""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()