Commits

anatoly techtonik committed ca0cdda

Working piece of code that gets all available font names.

  • Participants

Comments (0)

Files changed (1)

File fontquery.py

+"""
+Query system Windows fonts from Python.
+
+The task: Get monospace font for the application in the order of
+preference.
+
+The problem: Windows only allows to get the list of font names and
+their characteristics. It doesn't allow to list font files. From
+two different files with the same font name you'll get only one.
+Windows also doesn't allow getting only monospace fonts. You need
+to query all available fonts and filter them yourself.
+
+Use cases:
+ [ ] get the list of all system fonts available
+ [ ] get the list of families available
+ [ ] get the list of all fonts for specific family
+ [ ] check if specific font is available
+   [ ] check that family matched if another font is returned
+
+Considerations:
+ - performance of querying all system fonts is not measured
+ - Windows doesn't allow to get filenames of the fonts, so if there
+   are two fonts with the same name, one will be missing
+
+MSDN:
+
+    If you request a font named Palatino, but no such font is available
+on the system, the font mapper will substitute a font that has similar
+attributes but a different name.
+
+    To get the appropriate font, call EnumFontFamiliesEx with the
+desired font characteristics in the LOGFONT structure, then retrieve the
+appropriate typeface name and create the font using CreateFont or
+CreateFontIndirect.
+
+Wikipedia:
+
+    Because of the additional text processing and resolution independence
+capabilities in GDI+, text rendering is performed by the CPU [2] and it
+is nearly an order of magnitude slower than in hardware accelerated GDI.[3]
+Chris Jackson published some tests indicating that a piece of text
+rendering code he had written could render 99,000 glyphs per second in GDI,
+but the same code using GDI+ rendered 16,600 glyphs per second.
+
+"""
+
+#-- CHAPTER 1: GET ALL SYSTEM FONTS USING EnumFontFamiliesEx FROM GDI --
+#
+# Essential info about font metrics http://support.microsoft.com/kb/32667
+# And about logical units at http://www.winprog.org/tutorial/fonts.html
+
+import ctypes
+from ctypes import wintypes
+
+user32 = ctypes.windll.user32
+gdi32 = ctypes.windll.gdi32
+
+# --- define necessary data structures from wingdi.h
+
+# for calling ANSI functions of Windows API (end with A) TCHAR is
+# defined as single char, for Unicode ones (end witn W) it is WCHAR
+TCHAR = wintypes.CHAR
+
+class LOGFONT(ctypes.Structure):
+  # EnumFontFamiliesEx examines only 3 fields:
+  #  - lfCharSet
+  #  - lfFaceName  - empty string enumerates one font in each available
+  #                  typeface name, valid typeface name gets all fonts
+  #                  with that name
+  #  - lfPitchAndFamily - must be set to 0 [ ]
+  _fields_ = [
+    ('lfHeight', wintypes.LONG),
+      # value > 0  specifies the largest size of *char cell* to match
+      #            char cell = char height + internal leading
+      # value = 0  makes matched use default height for search
+      # value < 0  specifies the largest size of *char height* to match
+    ('lfWidth',  wintypes.LONG),
+      # average width also in *logical units*, which are pixels in
+      # default _mapping mode_ (MM_TEXT) for device
+    ('lfEscapement',  wintypes.LONG),
+      # string baseline rotation in tenths of degrees
+    ('lfOrientation', wintypes.LONG),
+      # character rotation in tenths of degrees
+    ('lfWeight', wintypes.LONG),
+      # 0 through 1000  400 is normal, 700 is bold, 0 is default
+    ('lfItalic', wintypes.BYTE),
+    ('lfUnderline', wintypes.BYTE),
+    ('lfStrikeOut', wintypes.BYTE),
+    ('lfCharSet',   wintypes.BYTE),
+      # 0    ANSI_CHARSET
+      # 186  BALTIC_CHARSET
+      # 136  CHINESEBIG5_CHARSET
+      # 1    DEFAULT_CHARSET     - charset for current system locale -
+      #                       means function can be called several times
+      #                       for the single font (for each charset) [ ]
+      # 238  EASTEUROPE_CHARSET
+      # 134  GB2312_CHARSET
+      # 161  GREEK_CHARSET
+      # 129  HANGUL_CHARSET
+      # 77   MAC_CHARSET
+      # 255  OEM_CHARSET         - OS dependent system charset
+      # 204  RUSSIAN_CHARSET
+      # 128  SHIFTJIS_CHARSET
+      # 2    SYMBOL_CHARSET
+      # 162  TURKISH_CHARSET
+      # 163  VIETNAMESE_CHARSET
+    ('lfOutPrecision',  wintypes.BYTE),
+      # many constants how the output must match height, width, pitch etc.
+      # OUT_DEFAULT_PRECIS
+      # [ ] TODO
+    ('lfClipPrecision', wintypes.BYTE),
+      # how to clip characters, no useful properties, leave default value
+      # CLIP_DEFAULT_PRECIS
+    ('lfQuality', wintypes.BYTE),
+      # ANTIALIASED_QUALITY
+      # CLEARTYPE_QUALITY
+      # DEFAULT_QUALITY 
+      # DRAFT_QUALITY
+      # NONANTIALIASED_QUALITY
+      # PROOF_QUALITY
+    ('lfPitchAndFamily', wintypes.BYTE),
+      # DEFAULT_PITCH
+      # FIXED_PITCH
+      # VARIABLE_PITCH
+      #    stacked any of with
+      # FF_DECORATIVE   - novelty
+      # FF_DONTCARE     - default font
+      # FF_MODERN       - monospace
+      # FF_ROMAN        - proportional (variable char width) with serifs
+      # FF_SCRIPT       - handwritten
+      # FF_SWISS        - proportional without serifs
+    ('lfFaceName', TCHAR*32)]
+      # typeface name of the font - null-terminated string
+      # [ ] TODO check size constraint / string conversion
+
+
+class FONTSIGNATURE(ctypes.Structure):
+  # supported code pages and Unicode subranges for the font
+  # needed for NEWTEXTMETRICEX structure
+  _fields_ = [
+    ('sUsb', wintypes.DWORD*4),  # 128-bit Unicode subset bitfield (USB)
+    ('sCsb', wintypes.DWORD*2)]  # 64-bit, code-page bitfield (CPB)
+
+class NEWTEXTMETRIC(ctypes.Structure):
+  # physical font attributes for True Type fonts
+  # needed for NEWTEXTMETRICEX structure
+  _fields_ = [
+    ('tmHeight', wintypes.LONG),
+    ('tmAscent', wintypes.LONG),
+    ('tmDescent', wintypes.LONG),
+    ('tmInternalLeading', wintypes.LONG),
+    ('tmExternalLeading', wintypes.LONG),
+    ('tmAveCharWidth', wintypes.LONG),
+    ('tmMaxCharWidth', wintypes.LONG),
+    ('tmWeight', wintypes.LONG),
+    ('tmOverhang', wintypes.LONG),
+    ('tmDigitizedAspectX', wintypes.LONG),
+    ('tmDigitizedAspectY', wintypes.LONG),
+    ('mFirstChar', TCHAR),
+    ('mLastChar', TCHAR),
+    ('mDefaultChar', TCHAR),
+    ('mBreakChar', TCHAR),
+    ('tmItalic', wintypes.BYTE),
+    ('tmUnderlined', wintypes.BYTE),
+    ('tmStruckOut', wintypes.BYTE),
+    ('tmPitchAndFamily', wintypes.BYTE),
+    ('tmCharSet', wintypes.BYTE),
+    ('tmFlags', wintypes.DWORD),
+    ('ntmSizeEM', wintypes.UINT),
+    ('ntmCellHeight', wintypes.UINT),
+    ('ntmAvgWidth', wintypes.UINT)]
+
+class NEWTEXTMETRICEX(ctypes.Structure):
+  # physical font attributes for True Type fonts
+  # needed for FONTENUMPROC callback function
+  _fields_ = [
+    ('ntmTm', NEWTEXTMETRIC),
+    ('ntmFontSig', FONTSIGNATURE)]
+
+
+# callback function/filter for EnumFontFamiliesExA
+FONTENUMPROC = ctypes.WINFUNCTYPE(
+  ctypes.c_int,  # return non-0 to continue enumeration, 0 to stop
+  ctypes.POINTER(LOGFONT), 
+  ctypes.POINTER(NEWTEXTMETRICEX),
+  wintypes.DWORD,  # font type, a combination of
+                   #   DEVICE_FONTTYPE
+                   #   RASTER_FONTTYPE 
+                   #   TRUETYPE_FONTTYPE 
+  wintypes.LPARAM
+);
+
+def enumerator(logfont, textmetricex, fonttype, param):
+  lf = logfont.contents
+  print(lf.lfFaceName)
+  return 1
+
+fontenumproc = FONTENUMPROC(enumerator)
+
+
+# 1. Get device context of the entire screen
+
+hdc = user32.GetDC(None)
+
+# 2. Call EnumFontFamiliesExA
+
+# [r] font name should be less than 32 symbols
+# [ ] check empty name and DEFAULT_CHARSET - how many time a font is
+#     returned
+# [ ] check double purpose - enumerate all available font names
+#      - enumerate all available charsets for a single font
+#      - other params?
+logfont = LOGFONT(0, 0, 0, 0, 0, 0, 0, 0, 0x0, 0, 0, 0,
+         0, b'\0')
+res = gdi32.EnumFontFamiliesExA(
+        hdc,   # handle to device context
+        ctypes.byref(logfont),
+        fontenumproc, # pointer to callback function
+        0,  # lParam  - application-supplied data
+        0)  # dwFlags - reserved = 0
+print(res)
+
+# 3. Release DC
+
+user32.ReleaseDC(None, hdc)
+
+# x. Convert desired font size from points into logical units (pixels)
+
+# By default logical for the screen units are pixels. This is defined
+# by default MM_TEXT mapping mode.
+
+# Point is ancient unit of measurement for physical size of a font.
+# 10pt is equal to 3.527mm. To make sure a char on screen has physical
+# size equal to 3.527mm, we need to know display size to calculate how
+# many pixels are in 3.527mm, and then fetch font that best matches
+# this size.
+
+# Essential info about conversion http://support.microsoft.com/kb/74299
+
+# x.1 Get pixels per inch using GetDeviceCaps() or ...
+