What would be a full example, going from nothing to ending up with a bitmap in memory, of opening a particular .ttf
file and rendering some text using that font, using the native Windows API? I'm currently slogging through the windows API, so it's a race between me and the rest of stackoverflow.
Asked
Active
Viewed 4,421 times
8

Claudiu
- 224,032
- 165
- 485
- 680
-
Do you *have* to use the native Windows API? If not, you can use [PIL](http://www.pythonware.com/products/pil/). – Håvard Apr 21 '11 at 23:31
-
@Harpyon: aye, see my [other](http://stackoverflow.com/questions/5747739/python-render-non-anti-aliased-font-to-internal-image/5747805#5747805) [questions](http://stackoverflow.com/questions/5748973/why-is-my-truetype-font-of-size-11-rendering-different-than-windows) – Claudiu Apr 21 '11 at 23:35
2 Answers
9
Done and done for rendering a font (requires PyWin32):
import ctypes
import struct
import win32con
import win32gui
import win32ui
from PIL import Image
def RGB(r, g, b):
return r | (g << 8) | (b << 16)
def native_bmp_to_pil(hdc, bitmap_handle, width, height):
bmpheader = struct.pack("LHHHH", struct.calcsize("LHHHH"),
width, height, 1, 24) #w,h, planes=1, bitcount)
c_bmpheader = ctypes.c_buffer(bmpheader)
#3 bytes per pixel, pad lines to 4 bytes
c_bits = ctypes.c_buffer(" " * (height * ((width*3 + 3) & -4)))
res = ctypes.windll.gdi32.GetDIBits(
hdc, bitmap_handle, 0, height,
c_bits, c_bmpheader,
win32con.DIB_RGB_COLORS)
if not res:
raise IOError("native_bmp_to_pil failed: GetDIBits")
im = Image.frombuffer(
"RGB", (width, height), c_bits,
"raw", "BGR", (width*3 + 3) & -4, -1)
return im
class Win32Font:
def __init__(self, name, height, weight=win32con.FW_NORMAL,
italic=False, underline=False):
self.font = win32ui.CreateFont({
'name': name, 'height': height,
'weight': weight, 'italic': italic, 'underline': underline})
#create a compatible DC we can use to draw:
self.desktopHwnd = win32gui.GetDesktopWindow()
self.desktopDC = win32gui.GetWindowDC(self.desktopHwnd)
self.mfcDC = win32ui.CreateDCFromHandle(self.desktopDC)
self.drawDC = self.mfcDC.CreateCompatibleDC()
#initialize it
self.drawDC.SelectObject(self.font)
def renderText(self, text):
"""render text to a PIL image using the windows API."""
self.drawDC.SetTextColor(RGB(255,0,0))
#create the compatible bitmap:
w,h = self.drawDC.GetTextExtent(text)
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(self.mfcDC, w, h)
self.drawDC.SelectObject(saveBitMap)
#draw it
self.drawDC.DrawText(text, (0, 0, w, h), win32con.DT_LEFT)
#convert to PIL image
im = native_bmp_to_pil(self.drawDC.GetSafeHdc(), saveBitMap.GetHandle(), w, h)
#clean-up
win32gui.DeleteObject(saveBitMap.GetHandle())
return im
def __del__(self):
self.mfcDC.DeleteDC()
self.drawDC.DeleteDC()
win32gui.ReleaseDC(self.desktopHwnd, self.desktopDC)
win32gui.DeleteObject(self.font.GetSafeHandle())
def __del__(self):
win32gui.DeleteObject(self.font.GetSafeHandle())
usage:
>>> f = Win32Font("Arial", 15)
>>> im = f.renderText("this is just a test")
>>> im.save("c:/hope.png")
result:
brilliant!!!
To render a particular .ttf
file I'll need to dig around more.
UPDATE: Updated to calculate the bmp size:
-
-
2
-
fonts are often made up of multiple ttf files. Generally you want to render a font face, e.g. Arial. Look and see the different ttf files that make up arial. – David Heffernan Apr 22 '11 at 19:57
-
1you should just install the font and get on with it. It's what you are expected to do. – David Heffernan Apr 22 '11 at 20:13
-
@David: that's fine, but how do i get the font face from the font file? – Claudiu Apr 22 '11 at 20:14
-
where does the file come from? How come you don't know the face name? – David Heffernan Apr 22 '11 at 20:18
-
i want to make a program to try to discover what font is being used by taking a screenshot and matching the text up. i could just enumerate all installed fonts, i guess, and try them all. my app would come with a set of fonts to try (if an app uses a less common one). ideally i could just get the font faces programmatically so i dont have to hard-code them and so i can add fonts to the set (e.g. if someone runs the app and they installed 10000 random fonts i might not want to waste time trying all the wingding variations) – Claudiu Apr 22 '11 at 20:28
-
this program is doomed to failure. There are so many different factors beyond font face. – David Heffernan Apr 22 '11 at 20:32
-
@David: hmm indeed considering how many attempts it took me to match notepad's output.. – Claudiu Apr 22 '11 at 20:37
-
you're really got absolutely no hope whatsoever! This would be an entire university research department's project for 10 years! Maybe I'm being dramatic but if it were me I'd run away. – David Heffernan Apr 22 '11 at 20:41
-
@David: hehe aye i dont mean to do it perfectly. i misspoke - the ultimate goal is to OCR the font, not to find the font face. just if it so happens that my technique works then i'll use it. otherwise i will ocr the letters by hand. just seeing if i can save myself some trouble. – Claudiu Apr 22 '11 at 20:51
-
You can upload the image directly to SO, you don't have to hotlink somewhere. – Prof. Falken Jun 18 '13 at 13:42
-
MS docs on CreateFont function are [here](http://msdn.microsoft.com/en-us/library/windows/desktop/dd183499%28v=vs.85%29.aspx). – pythonlarry May 07 '14 at 16:31
-
Guys, please help. I try to use this code and get this trace: File "C:/develop/pypdf417/render.py", line 20, in native_bmp_to_pil c_bits = str(ctypes.c_buffer(" " * (height * ((width * 3 + 3) & -4)))) File "C:\Python34\lib\ctypes\__init__.py", line 70, in c_buffer return create_string_buffer(init, size) File "C:\Python34\lib\ctypes\__init__.py", line 63, in create_string_buffer raise TypeError(init) TypeError: What am I doing wrong? – Vladimir Feb 13 '15 at 06:07
2
Here is updated version for Python 3 of the accepted answer (full credit to @Claudiu). I'm using "Segoe UI" as an example to show how we can perfectly match the native Windows user interface:
import ctypes, struct, win32con, win32gui, win32ui, PIL.Image
def native_bmp_to_pil(hdc, bitmap_handle, width, height):
bmpheader = struct.pack("LHHHH", struct.calcsize("LHHHH"), width, height, 1, 24)
c_bmpheader = ctypes.c_buffer(bmpheader)
c_bits = ctypes.c_buffer(b" " * (height * ((width*3 + 3) & -4)))
res = ctypes.windll.gdi32.GetDIBits(hdc, bitmap_handle, 0, height, c_bits, c_bmpheader, win32con.DIB_RGB_COLORS)
if not res:
raise IOError("native_bmp_to_pil failed: GetDIBits")
im = PIL.Image.frombuffer("RGB", (width, height), c_bits, "raw", "BGR", (width*3 + 3) & -4, -1)
return im
class Win32Font:
def __init__(self, name, height, weight=win32con.FW_NORMAL, italic=False, underline=False):
self.font = win32ui.CreateFont({'name': name, 'height': height, 'weight': weight, 'italic': italic, 'underline': underline})
self.desktopHwnd = win32gui.GetDesktopWindow()
self.desktopDC = win32gui.GetWindowDC(self.desktopHwnd)
self.mfcDC = win32ui.CreateDCFromHandle(self.desktopDC)
self.drawDC = self.mfcDC.CreateCompatibleDC()
self.drawDC.SelectObject(self.font)
def renderText(self, text):
self.drawDC.SetTextColor(0)
w,h = self.drawDC.GetTextExtent(text)
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(self.mfcDC, w, h)
self.drawDC.SelectObject(saveBitMap)
self.drawDC.DrawText(text, (0, 0, w, h), win32con.DT_LEFT)
im = native_bmp_to_pil(self.drawDC.GetSafeHdc(), saveBitMap.GetHandle(), w, h)
win32gui.DeleteObject(saveBitMap.GetHandle())
return im
def __del__(self):
self.mfcDC.DeleteDC()
self.drawDC.DeleteDC()
win32gui.ReleaseDC(self.desktopHwnd, self.desktopDC)
win32gui.DeleteObject(self.font.GetSafeHandle())
f = Win32Font("Segoe UI", 15)
im = f.renderText("Hello World")
im.save("test.png")

Basj
- 41,386
- 99
- 383
- 673