Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add full unicode support to Font.metrics #3328

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ankith26
Copy link
Member

@ankith26 ankith26 commented Feb 4, 2025

Inspired by a conversation in #3326

@ankith26 ankith26 requested a review from a team as a code owner February 4, 2025 05:23
@ankith26
Copy link
Member Author

ankith26 commented Feb 5, 2025

To test this PR I wrote a script (albeit with some help from gpt) to test font rendering with a wide range of unicode points and tested that metrics works on all of them.

Code used
import pygame

# Initialize Pygame
pygame.init()

FONT_SIZE = 30

# Set up the screen
WIDTH, HEIGHT = 1000, 800
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Pygame Font Metrics Demo (Noto Fonts)")

# Define font-character mapping
noto_font_samples = {
    "Noto Sans": ["A", "é", "ñ", "Ω", "Ж"],
    "Noto Serif": ["A", "ß", "Ж", "Σ", "λ"],
    "Noto Sans CJK SC": ["字", "汉", "日本", "韓國", "學"],
    "Noto Sans Arabic": ["العربية", "ب", "ك", "م", "ن"],
    "Noto Sans Devanagari": ["नमस्ते", "श", "क", "त्र", "ध"],
    "Noto Music": ["𝄞", "𝄢", "𝅘", "𝅘𝅥", "𝆑"],
    "Noto Color Emoji": ["🤖", "🔥", "💡", "🎵", "🚀"],
    "Noto Sans Math": ["∑", "∞", "∂", "∫", "∇"],
    "Noto Sans Old Persian": ["𐎠", "𐎢", "𐎴", "𐎽", "𐎹"],
    "Noto Sans Symbols": ["⚛", "♻", "⚔", "☯", "⚰"],
    "Noto Sans Symbols 2": ["⯑", "⯒", "⯓", "⯔", "⯕"],
}


noto_font_to_script = {
    "Noto Sans CJK SC": ("Hani", pygame.DIRECTION_LTR),
    "Noto Sans Arabic": ("Arab", pygame.DIRECTION_RTL),
    "Noto Sans Devanagari": ("Deva", pygame.DIRECTION_LTR),
    "Noto Sans Old Persian": ("Xpeo", pygame.DIRECTION_LTR),
}


# Function to load a specific Noto font
def load_noto_font(font_name, size):
    """
    Tries to load a font from the system by name.
    """
    font_path = pygame.font.match_font(font_name)
    if not font_path:
        raise FileNotFoundError(font_name)

    font = pygame.font.Font(font_path, size)
    font_script_and_direction = noto_font_to_script.get(font_name)
    if font_script_and_direction:
        font.set_script(font_script_and_direction[0])
        font.set_direction(font_script_and_direction[1])

    return font


# Load fonts
loaded_fonts = {name: load_noto_font(name, FONT_SIZE) for name in noto_font_samples}

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)

# Main loop
running = True
while running:
    screen.fill(WHITE)

    y_offset = 20  # Vertical spacing
    for font_name, chars in noto_font_samples.items():
        font = loaded_fonts.get(font_name)
        # Display font name
        title_surface = pygame.font.Font(None, 30).render(f"{font_name}:", True, BLACK)
        screen.blit(title_surface, (20, y_offset))

        x_offset = 250  # Horizontal spacing
        for char in chars:
            # Render character
            text_surface = font.render(char, True, BLACK)
            screen.blit(text_surface, (x_offset, y_offset))

            # Get font metrics
            metrics = font.metrics(char)
            if metrics:  # Ensure valid metric data
                tot_advance = 0

                for metric in metrics:
                    if not metric:
                        continue

                    min_x, max_x, min_y, max_y, x_advance = metric

                    pygame.draw.rect(
                        screen,
                        RED,
                        (
                            x_offset + tot_advance,
                            y_offset + min_y,
                            max_x - min_x,
                            max_y - min_y,
                        ),
                        1,
                    )
                    tot_advance += x_advance

            x_offset += 150  # Move right for the next character

        y_offset += 70  # Move down for the next font

    # Event handling
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    pygame.display.flip()

pygame.quit()

For me this gives the output
Screenshot From 2025-02-05 13-13-20

Some issues I see with this results (technically none of these are faults of this PR)

  • metrics returns bad min_x for all combinations I tested, resulting all the boxes to have the origin shifted from where they should be.
  • Color emoji fonts do not respect the font size (and IDK if it's a freetype issue, harfbuzz issue, or an issue with the font itself)
  • Scripts like Arabic (RTL and has character combining) and Hindi (does character combining but is LTR) render properly now that we can use set_script and set_direction but the metrics method still does not respect the proper shaping.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant