Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

pygame.font.Font.render() turns a string into a new pygame.Surface. It does not place text on the screen by itself: create a font, render the line, position the returned surface with a Rect, and blit it to your display surface.

import pygame

pygame.init()
screen = pygame.display.set_mode((640, 360))
font = pygame.font.Font(None, 40)
text_surface = font.render("Hello, Pygame!", True, (255, 255, 255))
text_rect = text_surface.get_rect(center=screen.get_rect().center)

screen.fill((30, 30, 30))
screen.blit(text_surface, text_rect)
pygame.display.flip()

# Keep the window alive in a real application.
for event in pygame.event.get():
    if event.type == pygame.QUIT:
        pygame.quit()

The method creates the pixels; blit performs the drawing. That separation explains why the return value is a Surface rather than a position or a widget.

What Font.render returns

The method signature is Font.render(text, antialias, color, background=None). It creates a new surface containing one line of rendered text. The surface dimensions are sized to hold that line, and you can use all normal surface operations on it before drawing.

  • text: a single-line string. A null character raises an error.
  • antialias: a Boolean. True smooths glyph edges; False uses a non-antialiased mode.
  • color: the foreground color, commonly an RGB tuple such as (255, 255, 255).
  • background: optional. Leave it out for transparent pixels around the glyphs, or provide a color for a solid text rectangle.
  • return value: a pygame.Surface. Rendering an empty string returns a zero-width surface with the font’s height.

Rendering and positioning are deliberately separate. The returned object has no knowledge of your window, camera, layout, or desired anchor point.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A complete window example

This runnable example creates a window, renders a title, centers it, and keeps the event loop active. The None filename asks Pygame to use its default font; replace it with a font file path when you need a specific typeface.

import pygame

pygame.init()
screen = pygame.display.set_mode((800, 450))
pygame.display.set_caption("Pygame text")
font = pygame.font.Font(None, 56)
clock = pygame.time.Clock()
running = True

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((24, 28, 36))
    title = font.render("Rendered with pygame.font.Font.render", True, (240, 240, 240))
    title_rect = title.get_rect(center=screen.get_rect().center)
    screen.blit(title, title_rect)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Install Pygame first if necessary with python -m pip install pygame. Call pygame.init() before creating fonts, and create the display before presenting frames.

Positioning, centering, and anchors

Surface.get_rect() gives the rendered surface a rectangle with the correct width and height. Set an attribute on that rectangle, then pass it to blit.

label = font.render("Score: 1200", True, (255, 220, 80))

# Top-left corner
screen.blit(label, label.get_rect(topleft=(20, 20)))

# Center of the window
screen.blit(label, label.get_rect(center=screen.get_rect().center))

# Center horizontally, fixed y position
centered = label.get_rect(centerx=screen.get_rect().centerx, y=100)
screen.blit(label, centered)

# Bottom-right with a margin
margin = 16
bottom_right = label.get_rect(
    right=screen.get_width() - margin,
    bottom=screen.get_height() - margin
)
screen.blit(label, bottom_right)

Use the destination rectangle rather than guessing character widths. Font metrics, antialiasing, and the actual glyphs all affect the surface size.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Antialiasing, transparency, and backgrounds

Choosing antialiasing

Pass True when smooth edges are important, especially for larger UI text or diagonal strokes. Pass False for a deliberately pixelated style or when you want the non-antialiased rendering mode.

Transparent text

With background=None (the default), pixels outside the glyphs remain transparent, so the text can be blitted over a changing game scene:

text = font.render("Transparent label", True, (255, 255, 255))
screen.blit(text, (32, 32))

Solid text rectangles

Supply a background color when the text should include a solid rectangular fill:

text = font.render("Paused", True, (255, 255, 255), (0, 0, 0))
screen.blit(text, text.get_rect(center=screen.get_rect().center))

The Pygame font documentation notes that antialiased text can use per-pixel alpha. When a destination always has one known solid background, passing that background color can be faster because the result can use colorkey-style transparency instead of alpha values. Measure in your own scene if this choice matters.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rendering multiple lines

Font.render handles one line. A newline character is not a layout instruction; it is treated as an unknown character rather than producing a second line. Split the text, render each line, and advance the y-coordinate by the font’s line spacing.

message = "First linenSecond linenThird line"
lines = message.splitlines()
x = 24
y = 24
line_color = (235, 235, 235)

for line in lines:
    line_surface = font.render(line, True, line_color)
    screen.blit(line_surface, (x, y))
    y += font.get_linesize()

Use line_surface.get_height() instead of get_linesize() when you want to pack lines according to the actual rendered surface. For centered paragraphs, calculate each line’s rectangle independently:

message = "Center each line independently"
for index, line in enumerate(message.splitlines()):
    line_surface = font.render(line, True, (255, 255, 255))
    line_rect = line_surface.get_rect(
        centerx=screen.get_rect().centerx,
        y=80 + index * font.get_linesize()
    )
    screen.blit(line_surface, line_rect)

Automatic word wrapping is also application code: split words, measure candidate lines with font.size(), and start a new line when the measured width exceeds your limit.

Fonts, sizing, and measurement

Loading a font file

Pass a path and point size to pygame.font.Font:

font = pygame.font.Font("assets/DejaVuSans.ttf", 32)

A size is not a guaranteed pixel height for every glyph. Use the rendered surface’s rectangle, font.get_height(), and font.get_linesize() for layout decisions.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Measuring before rendering

When you only need dimensions, font.size(text) reports the space the font would use. You still need render to obtain pixels for display.

caption = "Resizable heading"
width, height = font.size(caption)
box = pygame.Rect(0, 0, width + 24, height + 16)
box.center = screen.get_rect().center
surface = font.render(caption, True, (255, 255, 255))
screen.blit(surface, surface.get_rect(center=box.center))

Performance patterns

Render static text once

Each call creates a new surface. Render labels that do not change—menu items, HUD headings, and instructions—when the state changes, not on every frame.

# During setup or when the score changes
score_surface = font.render("Score: 1200", True, (255, 255, 255))

# Every frame
screen.blit(score_surface, (20, 20))

Cache repeated values

For changing values such as a timer, cache surfaces by the displayed string or update at the rate the value changes. Do not create a new font object inside the main loop; construct the font once and reuse it.

Choose the right background mode

Transparent antialiased text is flexible over arbitrary scenes. A supplied background can reduce blending work when the destination is known and uniform. The best option depends on how often the text changes and what it is drawn over.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

pygame.font versus pygame.freetype

Both APIs render text, but their call patterns differ:

API Return or drawing behavior When it fits
pygame.font.Font.render Returns one text Surface; your code blits it. The standard Pygame font workflow.
pygame.freetype.Font.render Returns a (Surface, Rect) tuple. When you want the bounding rectangle alongside the surface.
pygame.freetype.Font.render_to Draws directly onto an existing surface. When a direct-rendering path or freetype features suit your layout.

Do not unpack the result of pygame.font.Font.render as two values; it returns only the surface.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

Nothing appears

  • Confirm that you call screen.blit(text_surface, destination).
  • Make sure the blit occurs after screen.fill(); filling afterward covers the text.
  • Call pygame.display.flip() or pygame.display.update() after drawing.
  • Check that the text color contrasts with the background and that the destination coordinates are inside the window.

Text is in the wrong place

Rendering does not center or align anything automatically. Obtain a rectangle with get_rect() and set center, topleft, midtop, or another anchor explicitly.

A newline does not create a second line

Split the string with splitlines(), render each line separately, and advance by font.get_linesize() or the previous surface’s height.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

TypeError or invalid arguments

Pass a string for text, a Boolean for antialias, and a valid color value such as an RGB tuple. A null character in the string is invalid. Ensure the font module has been initialized through pygame.init() or pygame.font.init().

Font file cannot be loaded

Verify the path, filename, and working directory. Use an absolute path temporarily to diagnose path issues, then package the asset with your project and construct paths relative to your application.

Edges look jagged or text looks blurry

Toggle the antialias argument, check the font size, and avoid repeatedly scaling a rendered surface. Render at the size you intend to display whenever possible.

Or skip the browser setup

If your separate task is capturing a web page image rather than drawing Pygame text, ScreenshotNeo provides a one-request screenshot API and an MCP server for AI clients. Its cleanup steps accept cookie or consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use the API documentation at https://screenshotneo.com/docs/. This cURL request saves a WebP image:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The equivalent Python call is:

import requests
r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And in Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo supports full-page and element captures, device and viewport settings, retina scale, PDF output, custom CSS and JavaScript, waits, request blocking, headers, cookies, geolocation, caching, signed links, asynchronous jobs, bulk capture, and a usage API. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Frequently Asked Questions

Can I render text without opening a Pygame window?

Yes. Create a font and render to a Surface; a display surface is only required when you want to present the result on screen.

Why does an empty string still have a height?

Pygame returns a zero-width surface with the font’s height, which lets empty labels participate predictably in vertical layout.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I use pygame.freetype instead?

Use it when its (Surface, Rect) return value or render_to direct-drawing method better matches your layout; otherwise pygame.font.Font.render is the conventional workflow.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.