2424 lines
80 KiB
Python
2424 lines
80 KiB
Python
import copy
|
|
|
|
import pyglet
|
|
from pyglet.font import base
|
|
from pyglet.util import debug_print
|
|
from pyglet.image.codecs.wic import IWICBitmap, GUID_WICPixelFormat32bppBGR, WICDecoder, GUID_WICPixelFormat32bppBGRA, \
|
|
GUID_WICPixelFormat32bppPBGRA
|
|
|
|
from pyglet import image
|
|
import ctypes
|
|
import math
|
|
from pyglet.libs.win32 import com
|
|
from pyglet.libs.win32 import _kernel32 as kernel32
|
|
from pyglet.libs.win32 import _ole32 as ole32
|
|
from pyglet.libs.win32.constants import *
|
|
from pyglet.libs.win32.types import *
|
|
from ctypes import *
|
|
import os
|
|
import platform
|
|
|
|
try:
|
|
dwrite = 'dwrite'
|
|
|
|
# System32 and SysWOW64 folders are opposite perception in Windows x64.
|
|
# System32 = x64 dll's | SysWOW64 = x86 dlls
|
|
# By default ctypes only seems to look in system32 regardless of Python architecture, which has x64 dlls.
|
|
if platform.architecture()[0] == '32bit':
|
|
if platform.machine().endswith('64'): # Machine is 64 bit, Python is 32 bit.
|
|
dwrite = os.path.join(os.environ['WINDIR'], 'SysWOW64', 'dwrite.dll')
|
|
|
|
dwrite_lib = ctypes.windll.LoadLibrary(dwrite)
|
|
except OSError as err:
|
|
# Doesn't exist? Should stop import of library.
|
|
pass
|
|
|
|
_debug_font = debug_print('debug_font')
|
|
|
|
|
|
def DWRITE_MAKE_OPENTYPE_TAG(a, b, c, d):
|
|
return ord(d) << 24 | ord(c) << 16 | ord(b) << 8 | ord(a)
|
|
|
|
|
|
DWRITE_FACTORY_TYPE = UINT
|
|
DWRITE_FACTORY_TYPE_SHARED = 0
|
|
DWRITE_FACTORY_TYPE_ISOLATED = 1
|
|
|
|
DWRITE_FONT_WEIGHT = UINT
|
|
DWRITE_FONT_WEIGHT_THIN = 100
|
|
DWRITE_FONT_WEIGHT_EXTRA_LIGHT = 200
|
|
DWRITE_FONT_WEIGHT_ULTRA_LIGHT = 200
|
|
DWRITE_FONT_WEIGHT_LIGHT = 300
|
|
DWRITE_FONT_WEIGHT_SEMI_LIGHT = 350
|
|
DWRITE_FONT_WEIGHT_NORMAL = 400
|
|
DWRITE_FONT_WEIGHT_REGULAR = 400
|
|
DWRITE_FONT_WEIGHT_MEDIUM = 500
|
|
DWRITE_FONT_WEIGHT_DEMI_BOLD = 600
|
|
DWRITE_FONT_WEIGHT_SEMI_BOLD = 600
|
|
DWRITE_FONT_WEIGHT_BOLD = 700
|
|
DWRITE_FONT_WEIGHT_EXTRA_BOLD = 800
|
|
DWRITE_FONT_WEIGHT_ULTRA_BOLD = 800
|
|
DWRITE_FONT_WEIGHT_BLACK = 900
|
|
DWRITE_FONT_WEIGHT_HEAVY = 900
|
|
DWRITE_FONT_WEIGHT_EXTRA_BLACK = 950
|
|
|
|
name_to_weight = {"thin": DWRITE_FONT_WEIGHT_THIN,
|
|
"extralight": DWRITE_FONT_WEIGHT_EXTRA_LIGHT,
|
|
"ultralight": DWRITE_FONT_WEIGHT_ULTRA_LIGHT,
|
|
"light": DWRITE_FONT_WEIGHT_LIGHT,
|
|
"semilight": DWRITE_FONT_WEIGHT_SEMI_LIGHT,
|
|
"normal": DWRITE_FONT_WEIGHT_NORMAL,
|
|
"regular": DWRITE_FONT_WEIGHT_REGULAR,
|
|
"medium": DWRITE_FONT_WEIGHT_MEDIUM,
|
|
"demibold": DWRITE_FONT_WEIGHT_DEMI_BOLD,
|
|
"semibold": DWRITE_FONT_WEIGHT_SEMI_BOLD,
|
|
"bold": DWRITE_FONT_WEIGHT_BOLD,
|
|
"extrabold": DWRITE_FONT_WEIGHT_EXTRA_BOLD,
|
|
"ultrabold": DWRITE_FONT_WEIGHT_ULTRA_BOLD,
|
|
"black": DWRITE_FONT_WEIGHT_BLACK,
|
|
"heavy": DWRITE_FONT_WEIGHT_HEAVY,
|
|
"extrablack": DWRITE_FONT_WEIGHT_EXTRA_BLACK,
|
|
}
|
|
|
|
DWRITE_FONT_STRETCH = UINT
|
|
DWRITE_FONT_STRETCH_UNDEFINED = 0
|
|
DWRITE_FONT_STRETCH_ULTRA_CONDENSED = 1
|
|
DWRITE_FONT_STRETCH_EXTRA_CONDENSED = 2
|
|
DWRITE_FONT_STRETCH_CONDENSED = 3
|
|
DWRITE_FONT_STRETCH_SEMI_CONDENSED = 4
|
|
DWRITE_FONT_STRETCH_NORMAL = 5
|
|
DWRITE_FONT_STRETCH_MEDIUM = 5
|
|
DWRITE_FONT_STRETCH_SEMI_EXPANDED = 6
|
|
DWRITE_FONT_STRETCH_EXPANDED = 7
|
|
DWRITE_FONT_STRETCH_EXTRA_EXPANDED = 8
|
|
|
|
name_to_stretch = {"undefined": DWRITE_FONT_STRETCH_UNDEFINED,
|
|
"ultracondensed": DWRITE_FONT_STRETCH_ULTRA_CONDENSED,
|
|
"extracondensed": DWRITE_FONT_STRETCH_EXTRA_CONDENSED,
|
|
"condensed": DWRITE_FONT_STRETCH_CONDENSED,
|
|
"semicondensed": DWRITE_FONT_STRETCH_SEMI_CONDENSED,
|
|
"normal": DWRITE_FONT_STRETCH_NORMAL,
|
|
"medium": DWRITE_FONT_STRETCH_MEDIUM,
|
|
"semiexpanded": DWRITE_FONT_STRETCH_SEMI_EXPANDED,
|
|
"expanded": DWRITE_FONT_STRETCH_EXPANDED,
|
|
"extraexpanded": DWRITE_FONT_STRETCH_EXTRA_EXPANDED,
|
|
}
|
|
|
|
DWRITE_GLYPH_IMAGE_FORMATS = c_int
|
|
|
|
DWRITE_GLYPH_IMAGE_FORMATS_NONE = 0x00000000
|
|
DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE = 0x00000001
|
|
DWRITE_GLYPH_IMAGE_FORMATS_CFF = 0x00000002
|
|
DWRITE_GLYPH_IMAGE_FORMATS_COLR = 0x00000004
|
|
DWRITE_GLYPH_IMAGE_FORMATS_SVG = 0x00000008
|
|
DWRITE_GLYPH_IMAGE_FORMATS_PNG = 0x00000010
|
|
DWRITE_GLYPH_IMAGE_FORMATS_JPEG = 0x00000020
|
|
DWRITE_GLYPH_IMAGE_FORMATS_TIFF = 0x00000040
|
|
DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8 = 0x00000080
|
|
|
|
DWRITE_MEASURING_MODE = UINT
|
|
DWRITE_MEASURING_MODE_NATURAL = 0
|
|
DWRITE_MEASURING_MODE_GDI_CLASSIC = 1
|
|
DWRITE_MEASURING_MODE_GDI_NATURAL = 2
|
|
|
|
DWRITE_GLYPH_IMAGE_FORMATS_ALL = DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE | \
|
|
DWRITE_GLYPH_IMAGE_FORMATS_CFF | \
|
|
DWRITE_GLYPH_IMAGE_FORMATS_COLR | \
|
|
DWRITE_GLYPH_IMAGE_FORMATS_SVG | \
|
|
DWRITE_GLYPH_IMAGE_FORMATS_PNG | \
|
|
DWRITE_GLYPH_IMAGE_FORMATS_JPEG | \
|
|
DWRITE_GLYPH_IMAGE_FORMATS_TIFF | \
|
|
DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8
|
|
|
|
DWRITE_FONT_STYLE = UINT
|
|
DWRITE_FONT_STYLE_NORMAL = 0
|
|
DWRITE_FONT_STYLE_OBLIQUE = 1
|
|
DWRITE_FONT_STYLE_ITALIC = 2
|
|
|
|
name_to_style = {"normal": DWRITE_FONT_STYLE_NORMAL,
|
|
"oblique": DWRITE_FONT_STYLE_OBLIQUE,
|
|
"italic": DWRITE_FONT_STYLE_ITALIC}
|
|
|
|
UINT8 = c_uint8
|
|
UINT16 = c_uint16
|
|
INT16 = c_int16
|
|
INT32 = c_int32
|
|
UINT32 = c_uint32
|
|
UINT64 = c_uint64
|
|
|
|
|
|
class D2D_POINT_2F(Structure):
|
|
_fields_ = (
|
|
('x', FLOAT),
|
|
('y', FLOAT),
|
|
)
|
|
|
|
|
|
class D2D1_RECT_F(Structure):
|
|
_fields_ = (
|
|
('left', FLOAT),
|
|
('top', FLOAT),
|
|
('right', FLOAT),
|
|
('bottom', FLOAT),
|
|
)
|
|
|
|
|
|
class D2D1_COLOR_F(Structure):
|
|
_fields_ = (
|
|
('r', FLOAT),
|
|
('g', FLOAT),
|
|
('b', FLOAT),
|
|
('a', FLOAT),
|
|
)
|
|
|
|
|
|
class DWRITE_TEXT_METRICS(ctypes.Structure):
|
|
_fields_ = (
|
|
('left', FLOAT),
|
|
('top', FLOAT),
|
|
('width', FLOAT),
|
|
('widthIncludingTrailingWhitespace', FLOAT),
|
|
('height', FLOAT),
|
|
('layoutWidth', FLOAT),
|
|
('layoutHeight', FLOAT),
|
|
('maxBidiReorderingDepth', UINT32),
|
|
('lineCount', UINT32),
|
|
)
|
|
|
|
|
|
class DWRITE_FONT_METRICS(ctypes.Structure):
|
|
_fields_ = (
|
|
('designUnitsPerEm', UINT16),
|
|
('ascent', UINT16),
|
|
('descent', UINT16),
|
|
('lineGap', INT16),
|
|
('capHeight', UINT16),
|
|
('xHeight', UINT16),
|
|
('underlinePosition', INT16),
|
|
('underlineThickness', UINT16),
|
|
('strikethroughPosition', INT16),
|
|
('strikethroughThickness', UINT16),
|
|
)
|
|
|
|
|
|
class DWRITE_GLYPH_METRICS(ctypes.Structure):
|
|
_fields_ = (
|
|
('leftSideBearing', INT32),
|
|
('advanceWidth', UINT32),
|
|
('rightSideBearing', INT32),
|
|
('topSideBearing', INT32),
|
|
('advanceHeight', UINT32),
|
|
('bottomSideBearing', INT32),
|
|
('verticalOriginY', INT32),
|
|
)
|
|
|
|
|
|
class DWRITE_GLYPH_OFFSET(ctypes.Structure):
|
|
_fields_ = (
|
|
('advanceOffset', FLOAT),
|
|
('ascenderOffset', FLOAT),
|
|
)
|
|
|
|
def __repr__(self):
|
|
return f"DWRITE_GLYPH_OFFSET({self.advanceOffset}, {self.ascenderOffset})"
|
|
|
|
|
|
class DWRITE_CLUSTER_METRICS(ctypes.Structure):
|
|
_fields_ = (
|
|
('width', FLOAT),
|
|
('length', UINT16),
|
|
('canWrapLineAfter', UINT16, 1),
|
|
('isWhitespace', UINT16, 1),
|
|
('isNewline', UINT16, 1),
|
|
('isSoftHyphen', UINT16, 1),
|
|
('isRightToLeft', UINT16, 1),
|
|
('padding', UINT16, 11),
|
|
)
|
|
|
|
|
|
class IDWriteFontFace(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetType',
|
|
com.STDMETHOD()),
|
|
('GetFiles',
|
|
com.STDMETHOD()),
|
|
('GetIndex',
|
|
com.STDMETHOD()),
|
|
('GetSimulations',
|
|
com.STDMETHOD()),
|
|
('IsSymbolFont',
|
|
com.STDMETHOD()),
|
|
('GetMetrics',
|
|
com.METHOD(c_void, POINTER(DWRITE_FONT_METRICS))),
|
|
('GetGlyphCount',
|
|
com.METHOD(UINT16)),
|
|
('GetDesignGlyphMetrics',
|
|
com.STDMETHOD(POINTER(UINT16), UINT32, POINTER(DWRITE_GLYPH_METRICS), BOOL)),
|
|
('GetGlyphIndices',
|
|
com.STDMETHOD(POINTER(UINT32), UINT32, POINTER(UINT16))),
|
|
('TryGetFontTable',
|
|
com.STDMETHOD(UINT32, c_void_p, POINTER(UINT32), c_void_p, POINTER(BOOL))),
|
|
('ReleaseFontTable',
|
|
com.METHOD(c_void)),
|
|
('GetGlyphRunOutline',
|
|
com.STDMETHOD()),
|
|
('GetRecommendedRenderingMode',
|
|
com.STDMETHOD()),
|
|
('GetGdiCompatibleMetrics',
|
|
com.STDMETHOD()),
|
|
('GetGdiCompatibleGlyphMetrics',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
IID_IDWriteFontFace1 = com.GUID(0xa71efdb4, 0x9fdb, 0x4838, 0xad, 0x90, 0xcf, 0xc3, 0xbe, 0x8c, 0x3d, 0xaf)
|
|
|
|
|
|
class IDWriteFontFace1(IDWriteFontFace, com.pIUnknown):
|
|
_methods_ = [
|
|
('GetMetric1',
|
|
com.STDMETHOD()),
|
|
('GetGdiCompatibleMetrics1',
|
|
com.STDMETHOD()),
|
|
('GetCaretMetrics',
|
|
com.STDMETHOD()),
|
|
('GetUnicodeRanges',
|
|
com.STDMETHOD()),
|
|
('IsMonospacedFont',
|
|
com.STDMETHOD()),
|
|
('GetDesignGlyphAdvances',
|
|
com.METHOD(c_void, POINTER(DWRITE_FONT_METRICS))),
|
|
('GetGdiCompatibleGlyphAdvances',
|
|
com.STDMETHOD()),
|
|
('GetKerningPairAdjustments',
|
|
com.STDMETHOD(UINT32, POINTER(UINT16), POINTER(INT32))),
|
|
('HasKerningPairs',
|
|
com.METHOD(BOOL)),
|
|
('GetRecommendedRenderingMode1',
|
|
com.STDMETHOD()),
|
|
('GetVerticalGlyphVariants',
|
|
com.STDMETHOD()),
|
|
('HasVerticalGlyphVariants',
|
|
com.STDMETHOD())
|
|
]
|
|
|
|
|
|
class DWRITE_GLYPH_RUN(ctypes.Structure):
|
|
_fields_ = (
|
|
('fontFace', IDWriteFontFace),
|
|
('fontEmSize', FLOAT),
|
|
('glyphCount', UINT32),
|
|
('glyphIndices', POINTER(UINT16)),
|
|
('glyphAdvances', POINTER(FLOAT)),
|
|
('glyphOffsets', POINTER(DWRITE_GLYPH_OFFSET)),
|
|
('isSideways', BOOL),
|
|
('bidiLevel', UINT32),
|
|
)
|
|
|
|
|
|
DWRITE_SCRIPT_SHAPES = UINT
|
|
DWRITE_SCRIPT_SHAPES_DEFAULT = 0
|
|
|
|
|
|
class DWRITE_SCRIPT_ANALYSIS(ctypes.Structure):
|
|
_fields_ = (
|
|
('script', UINT16),
|
|
('shapes', DWRITE_SCRIPT_SHAPES),
|
|
)
|
|
|
|
|
|
DWRITE_FONT_FEATURE_TAG = UINT
|
|
|
|
|
|
class DWRITE_FONT_FEATURE(ctypes.Structure):
|
|
_fields_ = (
|
|
('nameTag', DWRITE_FONT_FEATURE_TAG),
|
|
('parameter', UINT32),
|
|
)
|
|
|
|
|
|
class DWRITE_TYPOGRAPHIC_FEATURES(ctypes.Structure):
|
|
_fields_ = (
|
|
('features', POINTER(DWRITE_FONT_FEATURE)),
|
|
('featureCount', UINT32),
|
|
)
|
|
|
|
|
|
class DWRITE_SHAPING_TEXT_PROPERTIES(ctypes.Structure):
|
|
_fields_ = (
|
|
('isShapedAlone', UINT16, 1),
|
|
('reserved1', UINT16, 1),
|
|
('canBreakShapingAfter', UINT16, 1),
|
|
('reserved', UINT16, 13),
|
|
)
|
|
|
|
def __repr__(self):
|
|
return f"DWRITE_SHAPING_TEXT_PROPERTIES({self.isShapedAlone}, {self.reserved1}, {self.canBreakShapingAfter})"
|
|
|
|
|
|
class DWRITE_SHAPING_GLYPH_PROPERTIES(ctypes.Structure):
|
|
_fields_ = (
|
|
('justification', UINT16, 4),
|
|
('isClusterStart', UINT16, 1),
|
|
('isDiacritic', UINT16, 1),
|
|
('isZeroWidthSpace', UINT16, 1),
|
|
('reserved', UINT16, 9),
|
|
)
|
|
|
|
|
|
DWRITE_READING_DIRECTION = UINT
|
|
DWRITE_READING_DIRECTION_LEFT_TO_RIGHT = 0
|
|
|
|
|
|
class IDWriteTextAnalysisSource(com.IUnknown):
|
|
_methods_ = [
|
|
('GetTextAtPosition',
|
|
com.METHOD(HRESULT, c_void_p, UINT32, POINTER(c_wchar_p), POINTER(UINT32))),
|
|
('GetTextBeforePosition',
|
|
com.STDMETHOD(UINT32, c_wchar_p, POINTER(UINT32))),
|
|
('GetParagraphReadingDirection',
|
|
com.METHOD(DWRITE_READING_DIRECTION)),
|
|
('GetLocaleName',
|
|
com.STDMETHOD(c_void_p, UINT32, POINTER(UINT32), POINTER(c_wchar_p))),
|
|
('GetNumberSubstitution',
|
|
com.STDMETHOD(UINT32, POINTER(UINT32), c_void_p)),
|
|
]
|
|
|
|
|
|
class IDWriteTextAnalysisSink(com.IUnknown):
|
|
_methods_ = [
|
|
('SetScriptAnalysis',
|
|
com.STDMETHOD(c_void_p, UINT32, UINT32, POINTER(DWRITE_SCRIPT_ANALYSIS))),
|
|
('SetLineBreakpoints',
|
|
com.STDMETHOD(UINT32, UINT32, c_void_p)),
|
|
('SetBidiLevel',
|
|
com.STDMETHOD(UINT32, UINT32, UINT8, UINT8)),
|
|
('SetNumberSubstitution',
|
|
com.STDMETHOD(UINT32, UINT32, c_void_p)),
|
|
]
|
|
|
|
|
|
class Run:
|
|
def __init__(self):
|
|
self.text_start = 0
|
|
self.text_length = 0
|
|
self.glyph_start = 0
|
|
self.glyph_count = 0
|
|
self.script = DWRITE_SCRIPT_ANALYSIS()
|
|
self.bidi = 0
|
|
self.isNumberSubstituted = False
|
|
self.isSideways = False
|
|
|
|
self.next_run = None
|
|
|
|
def ContainsTextPosition(self, textPosition):
|
|
return textPosition >= self.text_start and textPosition < self.text_start + self.text_length
|
|
|
|
|
|
class TextAnalysis(com.COMObject):
|
|
_interfaces_ = [IDWriteTextAnalysisSource, IDWriteTextAnalysisSink]
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._textstart = 0
|
|
self._textlength = 0
|
|
self._glyphstart = 0
|
|
self._glyphcount = 0
|
|
self._ptrs = []
|
|
|
|
self._script = None
|
|
self._bidi = 0
|
|
# self._sideways = False
|
|
|
|
def GenerateResults(self, analyzer, text, text_length):
|
|
self._text = text
|
|
self._textstart = 0
|
|
self._textlength = text_length
|
|
self._glyphstart = 0
|
|
self._glyphcount = 0
|
|
self._ptrs.clear()
|
|
|
|
self._start_run = Run()
|
|
self._start_run.text_length = text_length
|
|
|
|
self._current_run = self._start_run
|
|
|
|
analyzer.AnalyzeScript(self, 0, text_length, self)
|
|
|
|
def SetScriptAnalysis(self, this, textPosition, textLength, scriptAnalysis):
|
|
# textPosition - The index of the first character in the string that the result applies to
|
|
# textLength - How many characters of the string from the index that the result applies to
|
|
# scriptAnalysis - The analysis information for all glyphs starting at position for length.
|
|
self.SetCurrentRun(textPosition)
|
|
self.SplitCurrentRun(textPosition)
|
|
|
|
while textLength > 0:
|
|
run, textLength = self.FetchNextRun(textLength)
|
|
|
|
run.script.script = scriptAnalysis[0].script
|
|
run.script.shapes = scriptAnalysis[0].shapes
|
|
|
|
self._script = run.script
|
|
|
|
return 0
|
|
# return 0x80004001
|
|
|
|
def GetTextBeforePosition(self, this, textPosition, textString, textLength):
|
|
raise Exception("Currently not implemented.")
|
|
|
|
def GetTextAtPosition(self, this, textPosition, textString, textLength):
|
|
# This method will retrieve a substring of the text in this layout
|
|
# to be used in an analysis step.
|
|
# Arguments:
|
|
# textPosition - The index of the first character of the text to retrieve.
|
|
# textString - The pointer to the first character of text at the index requested.
|
|
# textLength - The characters available at/after the textString pointer (string length).
|
|
|
|
if textPosition >= self._textlength:
|
|
self._no_ptr = c_wchar_p(None)
|
|
textString[0] = self._no_ptr
|
|
textLength[0] = 0
|
|
else:
|
|
ptr = c_wchar_p(self._text[textPosition:])
|
|
self._ptrs.append(ptr)
|
|
textString[0] = ptr
|
|
textLength[0] = self._textlength - textPosition
|
|
|
|
return 0
|
|
|
|
def GetParagraphReadingDirection(self):
|
|
return 0
|
|
|
|
def GetLocaleName(self, this, textPosition, textLength, localeName):
|
|
self.__local_name = c_wchar_p("") # TODO: Add more locales.
|
|
localeName[0] = self.__local_name
|
|
textLength[0] = self._textlength - textPosition
|
|
return 0
|
|
|
|
def GetNumberSubstitution(self):
|
|
return 0
|
|
|
|
def SetCurrentRun(self, textPosition):
|
|
if self._current_run and self._current_run.ContainsTextPosition(textPosition):
|
|
return
|
|
|
|
def SplitCurrentRun(self, textPosition):
|
|
if not self._current_run:
|
|
return
|
|
|
|
if textPosition <= self._current_run.text_start:
|
|
# Already first start of the run.
|
|
return
|
|
|
|
new_run = copy.copy(self._current_run)
|
|
|
|
new_run.next_run = self._current_run.next_run
|
|
self._current_run.next_run = new_run
|
|
|
|
splitPoint = textPosition - self._current_run.text_start
|
|
new_run.text_start += splitPoint
|
|
new_run.text_length -= splitPoint
|
|
|
|
self._current_run.text_length = splitPoint
|
|
self._current_run = new_run
|
|
|
|
def FetchNextRun(self, textLength):
|
|
original_run = self._current_run
|
|
|
|
if (textLength < self._current_run.text_length):
|
|
self.SplitCurrentRun(self._current_run.text_start + textLength)
|
|
else:
|
|
self._current_run = self._current_run.next_run
|
|
|
|
textLength -= original_run.text_length
|
|
|
|
return original_run, textLength
|
|
|
|
|
|
class IDWriteTextAnalyzer(com.pIUnknown):
|
|
_methods_ = [
|
|
('AnalyzeScript',
|
|
com.STDMETHOD(POINTER(IDWriteTextAnalysisSource), UINT32, UINT32, POINTER(IDWriteTextAnalysisSink))),
|
|
('AnalyzeBidi',
|
|
com.STDMETHOD()),
|
|
('AnalyzeNumberSubstitution',
|
|
com.STDMETHOD()),
|
|
('AnalyzeLineBreakpoints',
|
|
com.STDMETHOD()),
|
|
('GetGlyphs',
|
|
com.STDMETHOD(c_wchar_p, UINT32, IDWriteFontFace, BOOL, BOOL, POINTER(DWRITE_SCRIPT_ANALYSIS),
|
|
c_wchar_p, c_void_p, POINTER(POINTER(DWRITE_TYPOGRAPHIC_FEATURES)), POINTER(UINT32),
|
|
UINT32, UINT32, POINTER(UINT16), POINTER(DWRITE_SHAPING_TEXT_PROPERTIES),
|
|
POINTER(UINT16), POINTER(DWRITE_SHAPING_GLYPH_PROPERTIES), POINTER(UINT32))),
|
|
('GetGlyphPlacements',
|
|
com.STDMETHOD(c_wchar_p, POINTER(UINT16), POINTER(DWRITE_SHAPING_TEXT_PROPERTIES), UINT32, POINTER(UINT16),
|
|
POINTER(DWRITE_SHAPING_GLYPH_PROPERTIES), UINT32, IDWriteFontFace, FLOAT, BOOL, BOOL,
|
|
POINTER(DWRITE_SCRIPT_ANALYSIS), c_wchar_p, POINTER(DWRITE_TYPOGRAPHIC_FEATURES),
|
|
POINTER(UINT32), UINT32, POINTER(FLOAT), POINTER(DWRITE_GLYPH_OFFSET))),
|
|
('GetGdiCompatibleGlyphPlacements',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteLocalizedStrings(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetCount',
|
|
com.METHOD(UINT32)),
|
|
('FindLocaleName',
|
|
com.STDMETHOD(c_wchar_p, POINTER(UINT32), POINTER(BOOL))),
|
|
('GetLocaleNameLength',
|
|
com.STDMETHOD(UINT32, POINTER(UINT32))),
|
|
('GetLocaleName',
|
|
com.STDMETHOD(UINT32, c_wchar_p, UINT32)),
|
|
('GetStringLength',
|
|
com.STDMETHOD(UINT32, POINTER(UINT32))),
|
|
('GetString',
|
|
com.STDMETHOD(UINT32, c_wchar_p, UINT32)),
|
|
]
|
|
|
|
|
|
class IDWriteFontList(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetFontCollection',
|
|
com.STDMETHOD()),
|
|
('GetFontCount',
|
|
com.STDMETHOD()),
|
|
('GetFont',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteFontFamily(IDWriteFontList, com.pIUnknown):
|
|
_methods_ = [
|
|
('GetFamilyNames',
|
|
com.STDMETHOD(POINTER(IDWriteLocalizedStrings))),
|
|
('GetFirstMatchingFont',
|
|
com.STDMETHOD(DWRITE_FONT_WEIGHT, DWRITE_FONT_STRETCH, DWRITE_FONT_STYLE, c_void_p)),
|
|
('GetMatchingFonts',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteFontFamily1(IDWriteFontFamily, IDWriteFontList, com.pIUnknown):
|
|
_methods_ = [
|
|
('GetFontLocality',
|
|
com.STDMETHOD()),
|
|
('GetFont1',
|
|
com.STDMETHOD()),
|
|
('GetFontFaceReference',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteFontFile(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetReferenceKey',
|
|
com.STDMETHOD()),
|
|
('GetLoader',
|
|
com.STDMETHOD()),
|
|
('Analyze',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteFont(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetFontFamily',
|
|
com.STDMETHOD(POINTER(IDWriteFontFamily))),
|
|
('GetWeight',
|
|
com.STDMETHOD()),
|
|
('GetStretch',
|
|
com.STDMETHOD()),
|
|
('GetStyle',
|
|
com.STDMETHOD()),
|
|
('IsSymbolFont',
|
|
com.STDMETHOD()),
|
|
('GetFaceNames',
|
|
com.STDMETHOD(POINTER(IDWriteLocalizedStrings))),
|
|
('GetInformationalStrings',
|
|
com.STDMETHOD()),
|
|
('GetSimulations',
|
|
com.STDMETHOD()),
|
|
('GetMetrics',
|
|
com.STDMETHOD()),
|
|
('HasCharacter',
|
|
com.STDMETHOD(UINT32, POINTER(BOOL))),
|
|
('CreateFontFace',
|
|
com.STDMETHOD(POINTER(IDWriteFontFace))),
|
|
]
|
|
|
|
|
|
class IDWriteFont1(IDWriteFont, com.pIUnknown):
|
|
_methods_ = [
|
|
('GetMetrics1',
|
|
com.STDMETHOD()),
|
|
('GetPanose',
|
|
com.STDMETHOD()),
|
|
('GetUnicodeRanges',
|
|
com.STDMETHOD()),
|
|
('IsMonospacedFont',
|
|
com.STDMETHOD())
|
|
]
|
|
|
|
|
|
class IDWriteFontCollection(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetFontFamilyCount',
|
|
com.STDMETHOD()),
|
|
('GetFontFamily',
|
|
com.STDMETHOD(UINT32, POINTER(IDWriteFontFamily))),
|
|
('FindFamilyName',
|
|
com.STDMETHOD(c_wchar_p, POINTER(UINT), POINTER(BOOL))),
|
|
('GetFontFromFontFace',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteFontCollection1(IDWriteFontCollection, com.pIUnknown):
|
|
_methods_ = [
|
|
('GetFontSet',
|
|
com.STDMETHOD()),
|
|
('GetFontFamily1',
|
|
com.STDMETHOD(POINTER(IDWriteFontFamily1))),
|
|
]
|
|
|
|
|
|
DWRITE_TEXT_ALIGNMENT = UINT
|
|
DWRITE_TEXT_ALIGNMENT_LEADING = 1
|
|
DWRITE_TEXT_ALIGNMENT_TRAILING = 2
|
|
DWRITE_TEXT_ALIGNMENT_CENTER = 3
|
|
DWRITE_TEXT_ALIGNMENT_JUSTIFIED = 4
|
|
|
|
|
|
class IDWriteTextFormat(com.pIUnknown):
|
|
_methods_ = [
|
|
('SetTextAlignment',
|
|
com.STDMETHOD(DWRITE_TEXT_ALIGNMENT)),
|
|
('SetParagraphAlignment',
|
|
com.STDMETHOD()),
|
|
('SetWordWrapping',
|
|
com.STDMETHOD()),
|
|
('SetReadingDirection',
|
|
com.STDMETHOD()),
|
|
('SetFlowDirection',
|
|
com.STDMETHOD()),
|
|
('SetIncrementalTabStop',
|
|
com.STDMETHOD()),
|
|
('SetTrimming',
|
|
com.STDMETHOD()),
|
|
('SetLineSpacing',
|
|
com.STDMETHOD()),
|
|
('GetTextAlignment',
|
|
com.STDMETHOD()),
|
|
('GetParagraphAlignment',
|
|
com.STDMETHOD()),
|
|
('GetWordWrapping',
|
|
com.STDMETHOD()),
|
|
('GetReadingDirection',
|
|
com.STDMETHOD()),
|
|
('GetFlowDirection',
|
|
com.STDMETHOD()),
|
|
('GetIncrementalTabStop',
|
|
com.STDMETHOD()),
|
|
('GetTrimming',
|
|
com.STDMETHOD()),
|
|
('GetLineSpacing',
|
|
com.STDMETHOD()),
|
|
('GetFontCollection',
|
|
com.STDMETHOD()),
|
|
('GetFontFamilyNameLength',
|
|
com.STDMETHOD(UINT32, POINTER(UINT32))),
|
|
('GetFontFamilyName',
|
|
com.STDMETHOD(UINT32, c_wchar_p, UINT32)),
|
|
('GetFontWeight',
|
|
com.STDMETHOD()),
|
|
('GetFontStyle',
|
|
com.STDMETHOD()),
|
|
('GetFontStretch',
|
|
com.STDMETHOD()),
|
|
('GetFontSize',
|
|
com.STDMETHOD()),
|
|
('GetLocaleNameLength',
|
|
com.STDMETHOD()),
|
|
('GetLocaleName',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteTypography(com.pIUnknown):
|
|
_methods_ = [
|
|
('AddFontFeature',
|
|
com.STDMETHOD(DWRITE_FONT_FEATURE)),
|
|
('GetFontFeatureCount',
|
|
com.METHOD(UINT32)),
|
|
('GetFontFeature',
|
|
com.STDMETHOD())
|
|
]
|
|
|
|
|
|
class DWRITE_TEXT_RANGE(ctypes.Structure):
|
|
_fields_ = (
|
|
('startPosition', UINT32),
|
|
('length', UINT32),
|
|
)
|
|
|
|
|
|
class DWRITE_OVERHANG_METRICS(ctypes.Structure):
|
|
_fields_ = (
|
|
('left', FLOAT),
|
|
('top', FLOAT),
|
|
('right', FLOAT),
|
|
('bottom', FLOAT),
|
|
)
|
|
|
|
|
|
class IDWriteTextLayout(IDWriteTextFormat, com.pIUnknown):
|
|
_methods_ = [
|
|
('SetMaxWidth',
|
|
com.STDMETHOD()),
|
|
('SetMaxHeight',
|
|
com.STDMETHOD()),
|
|
('SetFontCollection',
|
|
com.STDMETHOD()),
|
|
('SetFontFamilyName',
|
|
com.STDMETHOD()),
|
|
('SetFontWeight', # 30
|
|
com.STDMETHOD()),
|
|
('SetFontStyle',
|
|
com.STDMETHOD()),
|
|
('SetFontStretch',
|
|
com.STDMETHOD()),
|
|
('SetFontSize',
|
|
com.STDMETHOD()),
|
|
('SetUnderline',
|
|
com.STDMETHOD()),
|
|
('SetStrikethrough',
|
|
com.STDMETHOD()),
|
|
('SetDrawingEffect',
|
|
com.STDMETHOD()),
|
|
('SetInlineObject',
|
|
com.STDMETHOD()),
|
|
('SetTypography',
|
|
com.STDMETHOD(IDWriteTypography, DWRITE_TEXT_RANGE)),
|
|
('SetLocaleName',
|
|
com.STDMETHOD()),
|
|
('GetMaxWidth', # 40
|
|
com.METHOD(FLOAT)),
|
|
('GetMaxHeight',
|
|
com.METHOD(FLOAT)),
|
|
('GetFontCollection2',
|
|
com.STDMETHOD()),
|
|
('GetFontFamilyNameLength2',
|
|
com.STDMETHOD(UINT32, POINTER(UINT32), c_void_p)),
|
|
('GetFontFamilyName2',
|
|
com.STDMETHOD(UINT32, c_wchar_p, UINT32, c_void_p)),
|
|
('GetFontWeight2',
|
|
com.STDMETHOD(UINT32, POINTER(DWRITE_FONT_WEIGHT), POINTER(DWRITE_TEXT_RANGE))),
|
|
('GetFontStyle2',
|
|
com.STDMETHOD()),
|
|
('GetFontStretch2',
|
|
com.STDMETHOD()),
|
|
('GetFontSize2',
|
|
com.STDMETHOD()),
|
|
('GetUnderline',
|
|
com.STDMETHOD()),
|
|
('GetStrikethrough',
|
|
com.STDMETHOD(UINT32, POINTER(BOOL), POINTER(DWRITE_TEXT_RANGE))),
|
|
('GetDrawingEffect',
|
|
com.STDMETHOD()),
|
|
('GetInlineObject',
|
|
com.STDMETHOD()),
|
|
('GetTypography', # Always returns NULL without SetTypography being called.
|
|
com.STDMETHOD(UINT32, POINTER(IDWriteTypography), POINTER(DWRITE_TEXT_RANGE))),
|
|
('GetLocaleNameLength1',
|
|
com.STDMETHOD()),
|
|
('GetLocaleName1',
|
|
com.STDMETHOD()),
|
|
('Draw',
|
|
com.STDMETHOD()),
|
|
('GetLineMetrics',
|
|
com.STDMETHOD()),
|
|
('GetMetrics',
|
|
com.STDMETHOD(POINTER(DWRITE_TEXT_METRICS))),
|
|
('GetOverhangMetrics',
|
|
com.STDMETHOD(POINTER(DWRITE_OVERHANG_METRICS))),
|
|
('GetClusterMetrics',
|
|
com.STDMETHOD(POINTER(DWRITE_CLUSTER_METRICS), UINT32, POINTER(UINT32))),
|
|
('DetermineMinWidth',
|
|
com.STDMETHOD(POINTER(FLOAT))),
|
|
('HitTestPoint',
|
|
com.STDMETHOD()),
|
|
('HitTestTextPosition',
|
|
com.STDMETHOD()),
|
|
('HitTestTextRange',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteTextLayout1(IDWriteTextLayout, IDWriteTextFormat, com.pIUnknown):
|
|
_methods_ = [
|
|
('SetPairKerning',
|
|
com.STDMETHOD()),
|
|
('GetPairKerning',
|
|
com.STDMETHOD()),
|
|
('SetCharacterSpacing',
|
|
com.STDMETHOD()),
|
|
('GetCharacterSpacing',
|
|
com.STDMETHOD(UINT32, POINTER(FLOAT), POINTER(FLOAT), POINTER(FLOAT), POINTER(DWRITE_TEXT_RANGE))),
|
|
]
|
|
|
|
|
|
class IDWriteFontFileEnumerator(com.IUnknown):
|
|
_methods_ = [
|
|
('MoveNext',
|
|
com.STDMETHOD(c_void_p, POINTER(BOOL))),
|
|
('GetCurrentFontFile',
|
|
com.STDMETHOD(c_void_p, c_void_p)),
|
|
]
|
|
|
|
|
|
class IDWriteFontCollectionLoader(com.IUnknown):
|
|
_methods_ = [
|
|
('CreateEnumeratorFromKey',
|
|
com.STDMETHOD(c_void_p, c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileEnumerator)))),
|
|
]
|
|
|
|
|
|
class IDWriteFontFileStream(com.IUnknown):
|
|
_methods_ = [
|
|
('ReadFileFragment',
|
|
com.STDMETHOD(c_void_p, POINTER(c_void_p), UINT64, UINT64, POINTER(c_void_p))),
|
|
('ReleaseFileFragment',
|
|
com.STDMETHOD(c_void_p, c_void_p)),
|
|
('GetFileSize',
|
|
com.STDMETHOD(c_void_p, POINTER(UINT64))),
|
|
('GetLastWriteTime',
|
|
com.STDMETHOD(c_void_p, POINTER(UINT64))),
|
|
]
|
|
|
|
|
|
class MyFontFileStream(com.COMObject):
|
|
_interfaces_ = [IDWriteFontFileStream]
|
|
|
|
def __init__(self, data):
|
|
self._data = data
|
|
self._size = len(data)
|
|
self._ptrs = []
|
|
|
|
def AddRef(self, this):
|
|
return 1
|
|
|
|
def Release(self, this):
|
|
return 1
|
|
|
|
def QueryInterface(self, this, refiid, tester):
|
|
return 0
|
|
|
|
def ReadFileFragment(self, this, fragmentStart, fileOffset, fragmentSize, fragmentContext):
|
|
if fileOffset + fragmentSize > self._size:
|
|
return 0x80004005 # E_FAIL
|
|
|
|
fragment = self._data[fileOffset:]
|
|
buffer = (ctypes.c_ubyte * len(fragment)).from_buffer(bytearray(fragment))
|
|
ptr = cast(buffer, c_void_p)
|
|
|
|
self._ptrs.append(ptr)
|
|
fragmentStart[0] = ptr
|
|
fragmentContext[0] = None
|
|
return 0
|
|
|
|
def ReleaseFileFragment(self, this, fragmentContext):
|
|
return 0
|
|
|
|
def GetFileSize(self, this, fileSize):
|
|
fileSize[0] = self._size
|
|
return 0
|
|
|
|
def GetLastWriteTime(self, this, lastWriteTime):
|
|
return 0x80004001 # E_NOTIMPL
|
|
|
|
|
|
class IDWriteFontFileLoader(com.IUnknown):
|
|
_methods_ = [
|
|
('CreateStreamFromKey',
|
|
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
|
|
]
|
|
|
|
|
|
class LegacyFontFileLoader(com.COMObject):
|
|
_interfaces_ = [IDWriteFontFileLoader]
|
|
|
|
def __init__(self):
|
|
self._streams = {}
|
|
|
|
def QueryInterface(self, this, refiid, tester):
|
|
return 0
|
|
|
|
def AddRef(self, this):
|
|
return 1
|
|
|
|
def Release(self, this):
|
|
return 1
|
|
|
|
def CreateStreamFromKey(self, this, fontfileReferenceKey, fontFileReferenceKeySize, fontFileStream):
|
|
convert_index = cast(fontfileReferenceKey, POINTER(c_uint32))
|
|
|
|
self._ptr = ctypes.cast(self._streams[convert_index.contents.value]._pointers[IDWriteFontFileStream],
|
|
POINTER(IDWriteFontFileStream))
|
|
fontFileStream[0] = self._ptr
|
|
return 0
|
|
|
|
def SetCurrentFont(self, index, data):
|
|
self._streams[index] = MyFontFileStream(data)
|
|
|
|
|
|
class MyEnumerator(com.COMObject):
|
|
_interfaces_ = [IDWriteFontFileEnumerator]
|
|
|
|
def __init__(self, factory, loader):
|
|
self.factory = cast(factory, IDWriteFactory)
|
|
self.key = "pyglet_dwrite"
|
|
self.size = len(self.key)
|
|
self.current_index = -1
|
|
|
|
self._keys = []
|
|
self._font_data = []
|
|
self._font_files = []
|
|
self._current_file = None
|
|
|
|
self._font_key_ref = create_unicode_buffer("none")
|
|
self._font_key_len = len(self._font_key_ref)
|
|
|
|
self._file_loader = loader
|
|
|
|
def AddFontData(self, fonts):
|
|
self._font_data = fonts
|
|
|
|
def MoveNext(self, this, hasCurrentFile):
|
|
|
|
self.current_index += 1
|
|
if self.current_index != len(self._font_data):
|
|
font_file = IDWriteFontFile()
|
|
|
|
self._file_loader.SetCurrentFont(self.current_index, self._font_data[self.current_index])
|
|
|
|
key = self.current_index
|
|
|
|
if not self.current_index in self._keys:
|
|
buffer = pointer(c_uint32(key))
|
|
|
|
ptr = cast(buffer, c_void_p)
|
|
|
|
self._keys.append(ptr)
|
|
|
|
self.factory.CreateCustomFontFileReference(self._keys[self.current_index],
|
|
sizeof(buffer),
|
|
self._file_loader,
|
|
byref(font_file))
|
|
|
|
self._font_files.append(font_file)
|
|
|
|
hasCurrentFile[0] = 1
|
|
else:
|
|
hasCurrentFile[0] = 0
|
|
|
|
pass
|
|
|
|
def GetCurrentFontFile(self, this, fontFile):
|
|
fontFile = cast(fontFile, POINTER(IDWriteFontFile))
|
|
fontFile[0] = self._font_files[self.current_index]
|
|
return 0
|
|
|
|
|
|
class LegacyCollectionLoader(com.COMObject):
|
|
_interfaces_ = [IDWriteFontCollectionLoader]
|
|
|
|
def __init__(self, factory, loader):
|
|
self._enumerator = MyEnumerator(factory, loader)
|
|
|
|
def AddFontData(self, fonts):
|
|
self._enumerator.AddFontData(fonts)
|
|
|
|
def AddRef(self, this):
|
|
self._i = 1
|
|
return 1
|
|
|
|
def Release(self, this):
|
|
self._i = 0
|
|
return 1
|
|
|
|
def QueryInterface(self, this, refiid, tester):
|
|
return 0
|
|
|
|
def CreateEnumeratorFromKey(self, this, factory, key, key_size, enumerator):
|
|
self._ptr = ctypes.cast(self._enumerator._pointers[IDWriteFontFileEnumerator],
|
|
POINTER(IDWriteFontFileEnumerator))
|
|
|
|
enumerator[0] = self._ptr
|
|
return 0
|
|
|
|
|
|
IID_IDWriteFactory = com.GUID(0xb859ee5a, 0xd838, 0x4b5b, 0xa2, 0xe8, 0x1a, 0xdc, 0x7d, 0x93, 0xdb, 0x48)
|
|
|
|
|
|
class IDWriteRenderingParams(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetGamma',
|
|
com.METHOD(FLOAT)),
|
|
('GetEnhancedContrast',
|
|
com.METHOD(FLOAT)),
|
|
('GetClearTypeLevel',
|
|
com.METHOD(FLOAT)),
|
|
('GetPixelGeometry',
|
|
com.METHOD(UINT)),
|
|
('GetRenderingMode',
|
|
com.METHOD(UINT)),
|
|
]
|
|
|
|
|
|
class IDWriteFactory(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetSystemFontCollection',
|
|
com.STDMETHOD(POINTER(IDWriteFontCollection), BOOL)),
|
|
('CreateCustomFontCollection',
|
|
com.STDMETHOD(POINTER(IDWriteFontCollectionLoader), c_void_p, UINT32, POINTER(IDWriteFontCollection))),
|
|
('RegisterFontCollectionLoader',
|
|
com.STDMETHOD(POINTER(IDWriteFontCollectionLoader))),
|
|
('UnregisterFontCollectionLoader',
|
|
com.STDMETHOD(POINTER(IDWriteFontCollectionLoader))),
|
|
('CreateFontFileReference',
|
|
com.STDMETHOD(c_wchar_p, c_void_p, POINTER(IDWriteFontFile))),
|
|
('CreateCustomFontFileReference',
|
|
com.STDMETHOD(c_void_p, UINT32, POINTER(IDWriteFontFileLoader), POINTER(IDWriteFontFile))),
|
|
('CreateFontFace',
|
|
com.STDMETHOD()),
|
|
('CreateRenderingParams',
|
|
com.STDMETHOD(POINTER(IDWriteRenderingParams))),
|
|
('CreateMonitorRenderingParams',
|
|
com.STDMETHOD()),
|
|
('CreateCustomRenderingParams',
|
|
com.STDMETHOD(FLOAT, FLOAT, FLOAT, UINT, UINT, POINTER(IDWriteRenderingParams))),
|
|
('RegisterFontFileLoader',
|
|
com.STDMETHOD(c_void_p)), # Ambigious as newer is a pIUnknown and legacy is IUnknown.
|
|
('UnregisterFontFileLoader',
|
|
com.STDMETHOD(POINTER(IDWriteFontFileLoader))),
|
|
('CreateTextFormat',
|
|
com.STDMETHOD(c_wchar_p, IDWriteFontCollection, DWRITE_FONT_WEIGHT, DWRITE_FONT_STYLE, DWRITE_FONT_STRETCH,
|
|
FLOAT, c_wchar_p, POINTER(IDWriteTextFormat))),
|
|
('CreateTypography',
|
|
com.STDMETHOD(POINTER(IDWriteTypography))),
|
|
('GetGdiInterop',
|
|
com.STDMETHOD()),
|
|
('CreateTextLayout',
|
|
com.STDMETHOD(c_wchar_p, UINT32, IDWriteTextFormat, FLOAT, FLOAT, POINTER(IDWriteTextLayout))),
|
|
('CreateGdiCompatibleTextLayout',
|
|
com.STDMETHOD()),
|
|
('CreateEllipsisTrimmingSign',
|
|
com.STDMETHOD()),
|
|
('CreateTextAnalyzer',
|
|
com.STDMETHOD(POINTER(IDWriteTextAnalyzer))),
|
|
('CreateNumberSubstitution',
|
|
com.STDMETHOD()),
|
|
('CreateGlyphRunAnalysis',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
IID_IDWriteFactory1 = com.GUID(0x30572f99, 0xdac6, 0x41db, 0xa1, 0x6e, 0x04, 0x86, 0x30, 0x7e, 0x60, 0x6a)
|
|
|
|
|
|
class IDWriteFactory1(IDWriteFactory, com.pIUnknown):
|
|
_methods_ = [
|
|
('GetEudcFontCollection',
|
|
com.STDMETHOD()),
|
|
('CreateCustomRenderingParams1',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteFontFallback(com.pIUnknown):
|
|
_methods_ = [
|
|
('MapCharacters',
|
|
com.STDMETHOD(POINTER(IDWriteTextAnalysisSource), UINT32, UINT32, IDWriteFontCollection, c_wchar_p,
|
|
DWRITE_FONT_WEIGHT, DWRITE_FONT_STYLE, DWRITE_FONT_STRETCH, POINTER(UINT32),
|
|
POINTER(IDWriteFont),
|
|
POINTER(FLOAT))),
|
|
]
|
|
|
|
|
|
class IDWriteFactory2(IDWriteFactory1, com.pIUnknown):
|
|
_methods_ = [
|
|
('GetSystemFontFallback',
|
|
com.STDMETHOD(POINTER(IDWriteFontFallback))),
|
|
('CreateFontFallbackBuilder',
|
|
com.STDMETHOD()),
|
|
('TranslateColorGlyphRun',
|
|
com.STDMETHOD()),
|
|
('CreateCustomRenderingParams2',
|
|
com.STDMETHOD()),
|
|
('CreateGlyphRunAnalysis',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteFontSet(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetFontCount',
|
|
com.STDMETHOD()),
|
|
('GetFontFaceReference',
|
|
com.STDMETHOD()),
|
|
('FindFontFaceReference',
|
|
com.STDMETHOD()),
|
|
('FindFontFace',
|
|
com.STDMETHOD()),
|
|
('GetPropertyValues',
|
|
com.STDMETHOD()),
|
|
('GetPropertyOccurrenceCount',
|
|
com.STDMETHOD()),
|
|
('GetMatchingFonts',
|
|
com.STDMETHOD()),
|
|
('GetMatchingFonts',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteFontSetBuilder(com.pIUnknown):
|
|
_methods_ = [
|
|
('AddFontFaceReference',
|
|
com.STDMETHOD()),
|
|
('AddFontFaceReference',
|
|
com.STDMETHOD()),
|
|
('AddFontSet',
|
|
com.STDMETHOD()),
|
|
('CreateFontSet',
|
|
com.STDMETHOD(POINTER(IDWriteFontSet))),
|
|
]
|
|
|
|
|
|
class IDWriteFontSetBuilder1(IDWriteFontSetBuilder, com.pIUnknown):
|
|
_methods_ = [
|
|
('AddFontFile',
|
|
com.STDMETHOD(IDWriteFontFile)),
|
|
]
|
|
|
|
|
|
class IDWriteFactory3(IDWriteFactory2, com.pIUnknown):
|
|
_methods_ = [
|
|
('CreateGlyphRunAnalysis',
|
|
com.STDMETHOD()),
|
|
('CreateCustomRenderingParams3',
|
|
com.STDMETHOD()),
|
|
('CreateFontFaceReference',
|
|
com.STDMETHOD()),
|
|
('CreateFontFaceReference',
|
|
com.STDMETHOD()),
|
|
('GetSystemFontSet',
|
|
com.STDMETHOD()),
|
|
('CreateFontSetBuilder',
|
|
com.STDMETHOD(POINTER(IDWriteFontSetBuilder))),
|
|
('CreateFontCollectionFromFontSet',
|
|
com.STDMETHOD(IDWriteFontSet, POINTER(IDWriteFontCollection1))),
|
|
('GetSystemFontCollection3',
|
|
com.STDMETHOD()),
|
|
('GetFontDownloadQueue',
|
|
com.STDMETHOD()),
|
|
# ('GetSystemFontSet',
|
|
# com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteColorGlyphRunEnumerator1(com.pIUnknown):
|
|
_methods_ = [
|
|
('MoveNext',
|
|
com.STDMETHOD()),
|
|
('GetCurrentRun',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteFactory4(IDWriteFactory3, com.pIUnknown):
|
|
_methods_ = [
|
|
('TranslateColorGlyphRun4', # Renamed to prevent clash from previous factories.
|
|
com.STDMETHOD(D2D_POINT_2F, DWRITE_GLYPH_RUN, c_void_p, DWRITE_GLYPH_IMAGE_FORMATS, DWRITE_MEASURING_MODE,
|
|
c_void_p, UINT32, POINTER(IDWriteColorGlyphRunEnumerator1))),
|
|
('ComputeGlyphOrigins_',
|
|
com.STDMETHOD()),
|
|
('ComputeGlyphOrigins',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class IDWriteInMemoryFontFileLoader(com.pIUnknown):
|
|
_methods_ = [
|
|
('CreateStreamFromKey',
|
|
com.STDMETHOD()),
|
|
('CreateInMemoryFontFileReference',
|
|
com.STDMETHOD(IDWriteFactory, c_void_p, UINT, c_void_p, POINTER(IDWriteFontFile))),
|
|
('GetFileCount',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
IID_IDWriteFactory5 = com.GUID(0x958DB99A, 0xBE2A, 0x4F09, 0xAF, 0x7D, 0x65, 0x18, 0x98, 0x03, 0xD1, 0xD3)
|
|
|
|
|
|
class IDWriteFactory5(IDWriteFactory4, IDWriteFactory3, IDWriteFactory2, IDWriteFactory1, IDWriteFactory,
|
|
com.pIUnknown):
|
|
_methods_ = [
|
|
('CreateFontSetBuilder1',
|
|
com.STDMETHOD(POINTER(IDWriteFontSetBuilder1))),
|
|
('CreateInMemoryFontFileLoader',
|
|
com.STDMETHOD(POINTER(IDWriteInMemoryFontFileLoader))),
|
|
('CreateHttpFontFileLoader',
|
|
com.STDMETHOD()),
|
|
('AnalyzeContainerType',
|
|
com.STDMETHOD())
|
|
]
|
|
|
|
|
|
DWriteCreateFactory = dwrite_lib.DWriteCreateFactory
|
|
DWriteCreateFactory.restype = HRESULT
|
|
DWriteCreateFactory.argtypes = [DWRITE_FACTORY_TYPE, com.REFIID, POINTER(com.pIUnknown)]
|
|
|
|
|
|
class ID2D1Resource(com.pIUnknown):
|
|
_methods_ = [
|
|
('GetFactory',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class ID2D1Brush(ID2D1Resource, com.pIUnknown):
|
|
_methods_ = [
|
|
('SetOpacity',
|
|
com.STDMETHOD()),
|
|
('SetTransform',
|
|
com.STDMETHOD()),
|
|
('GetOpacity',
|
|
com.STDMETHOD()),
|
|
('GetTransform',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
class ID2D1SolidColorBrush(ID2D1Brush, ID2D1Resource, com.pIUnknown):
|
|
_methods_ = [
|
|
('SetColor',
|
|
com.STDMETHOD()),
|
|
('GetColor',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
D2D1_TEXT_ANTIALIAS_MODE = UINT
|
|
D2D1_TEXT_ANTIALIAS_MODE_DEFAULT = 0
|
|
D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE = 1
|
|
D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE = 2
|
|
D2D1_TEXT_ANTIALIAS_MODE_ALIASED = 3
|
|
|
|
D2D1_RENDER_TARGET_TYPE = UINT
|
|
D2D1_RENDER_TARGET_TYPE_DEFAULT = 0
|
|
D2D1_RENDER_TARGET_TYPE_SOFTWARE = 1
|
|
D2D1_RENDER_TARGET_TYPE_HARDWARE = 2
|
|
|
|
D2D1_FEATURE_LEVEL = UINT
|
|
D2D1_FEATURE_LEVEL_DEFAULT = 0
|
|
|
|
D2D1_RENDER_TARGET_USAGE = UINT
|
|
D2D1_RENDER_TARGET_USAGE_NONE = 0
|
|
D2D1_RENDER_TARGET_USAGE_FORCE_BITMAP_REMOTING = 1
|
|
D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE = 2
|
|
|
|
DXGI_FORMAT = UINT
|
|
DXGI_FORMAT_UNKNOWN = 0
|
|
|
|
D2D1_ALPHA_MODE = UINT
|
|
D2D1_ALPHA_MODE_UNKNOWN = 0
|
|
D2D1_ALPHA_MODE_PREMULTIPLIED = 1
|
|
D2D1_ALPHA_MODE_STRAIGHT = 2
|
|
D2D1_ALPHA_MODE_IGNORE = 3
|
|
|
|
D2D1_DRAW_TEXT_OPTIONS = UINT
|
|
D2D1_DRAW_TEXT_OPTIONS_NO_SNAP = 0x00000001
|
|
D2D1_DRAW_TEXT_OPTIONS_CLIP = 0x00000002
|
|
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT = 0x00000004
|
|
D2D1_DRAW_TEXT_OPTIONS_DISABLE_COLOR_BITMAP_SNAPPING = 0x00000008
|
|
D2D1_DRAW_TEXT_OPTIONS_NONE = 0x00000000
|
|
D2D1_DRAW_TEXT_OPTIONS_FORCE_DWORD = 0xffffffff
|
|
|
|
|
|
class D2D1_PIXEL_FORMAT(Structure):
|
|
_fields_ = (
|
|
('format', DXGI_FORMAT),
|
|
('alphaMode', D2D1_ALPHA_MODE),
|
|
)
|
|
|
|
|
|
class D2D1_RENDER_TARGET_PROPERTIES(Structure):
|
|
_fields_ = (
|
|
('type', D2D1_RENDER_TARGET_TYPE),
|
|
('pixelFormat', D2D1_PIXEL_FORMAT),
|
|
('dpiX', FLOAT),
|
|
('dpiY', FLOAT),
|
|
('usage', D2D1_RENDER_TARGET_USAGE),
|
|
('minLevel', D2D1_FEATURE_LEVEL),
|
|
)
|
|
|
|
|
|
DXGI_FORMAT_B8G8R8A8_UNORM = 87
|
|
|
|
pixel_format = D2D1_PIXEL_FORMAT()
|
|
pixel_format.format = DXGI_FORMAT_UNKNOWN
|
|
pixel_format.alphaMode = D2D1_ALPHA_MODE_UNKNOWN
|
|
|
|
default_target_properties = D2D1_RENDER_TARGET_PROPERTIES()
|
|
default_target_properties.type = D2D1_RENDER_TARGET_TYPE_DEFAULT
|
|
default_target_properties.pixelFormat = pixel_format
|
|
default_target_properties.dpiX = 0.0
|
|
default_target_properties.dpiY = 0.0
|
|
default_target_properties.usage = D2D1_RENDER_TARGET_USAGE_NONE
|
|
default_target_properties.minLevel = D2D1_FEATURE_LEVEL_DEFAULT
|
|
|
|
|
|
class ID2D1RenderTarget(ID2D1Resource, com.pIUnknown):
|
|
_methods_ = [
|
|
('CreateBitmap',
|
|
com.STDMETHOD()),
|
|
('CreateBitmapFromWicBitmap',
|
|
com.STDMETHOD()),
|
|
('CreateSharedBitmap',
|
|
com.STDMETHOD()),
|
|
('CreateBitmapBrush',
|
|
com.STDMETHOD()),
|
|
('CreateSolidColorBrush',
|
|
com.STDMETHOD(POINTER(D2D1_COLOR_F), c_void_p, POINTER(ID2D1SolidColorBrush))),
|
|
('CreateGradientStopCollection',
|
|
com.STDMETHOD()),
|
|
('CreateLinearGradientBrush',
|
|
com.STDMETHOD()),
|
|
('CreateRadialGradientBrush',
|
|
com.STDMETHOD()),
|
|
('CreateCompatibleRenderTarget',
|
|
com.STDMETHOD()),
|
|
('CreateLayer',
|
|
com.STDMETHOD()),
|
|
('CreateMesh',
|
|
com.STDMETHOD()),
|
|
('DrawLine',
|
|
com.STDMETHOD()),
|
|
('DrawRectangle',
|
|
com.STDMETHOD()),
|
|
('FillRectangle',
|
|
com.STDMETHOD()),
|
|
('DrawRoundedRectangle',
|
|
com.STDMETHOD()),
|
|
('FillRoundedRectangle',
|
|
com.STDMETHOD()),
|
|
('DrawEllipse',
|
|
com.STDMETHOD()),
|
|
('FillEllipse',
|
|
com.STDMETHOD()),
|
|
('DrawGeometry',
|
|
com.STDMETHOD()),
|
|
('FillGeometry',
|
|
com.STDMETHOD()),
|
|
('FillMesh',
|
|
com.STDMETHOD()),
|
|
('FillOpacityMask',
|
|
com.STDMETHOD()),
|
|
('DrawBitmap',
|
|
com.STDMETHOD()),
|
|
('DrawText',
|
|
com.STDMETHOD(c_wchar_p, UINT, IDWriteTextFormat, POINTER(D2D1_RECT_F), ID2D1Brush, D2D1_DRAW_TEXT_OPTIONS,
|
|
DWRITE_MEASURING_MODE)),
|
|
('DrawTextLayout',
|
|
com.METHOD(c_void, D2D_POINT_2F, IDWriteTextLayout, ID2D1Brush, UINT32)),
|
|
('DrawGlyphRun',
|
|
com.METHOD(c_void, D2D_POINT_2F, POINTER(DWRITE_GLYPH_RUN), ID2D1Brush, UINT32)),
|
|
('SetTransform',
|
|
com.METHOD(c_void)),
|
|
('GetTransform',
|
|
com.STDMETHOD()),
|
|
('SetAntialiasMode',
|
|
com.METHOD(c_void, D2D1_TEXT_ANTIALIAS_MODE)),
|
|
('GetAntialiasMode',
|
|
com.STDMETHOD()),
|
|
('SetTextAntialiasMode',
|
|
com.METHOD(c_void, D2D1_TEXT_ANTIALIAS_MODE)),
|
|
('GetTextAntialiasMode',
|
|
com.STDMETHOD()),
|
|
('SetTextRenderingParams',
|
|
com.STDMETHOD(IDWriteRenderingParams)),
|
|
('GetTextRenderingParams',
|
|
com.STDMETHOD()),
|
|
('SetTags',
|
|
com.STDMETHOD()),
|
|
('GetTags',
|
|
com.STDMETHOD()),
|
|
('PushLayer',
|
|
com.STDMETHOD()),
|
|
('PopLayer',
|
|
com.STDMETHOD()),
|
|
('Flush',
|
|
com.STDMETHOD(c_void_p, c_void_p)),
|
|
('SaveDrawingState',
|
|
com.STDMETHOD()),
|
|
('RestoreDrawingState',
|
|
com.STDMETHOD()),
|
|
('PushAxisAlignedClip',
|
|
com.STDMETHOD()),
|
|
('PopAxisAlignedClip',
|
|
com.STDMETHOD()),
|
|
('Clear',
|
|
com.METHOD(c_void, POINTER(D2D1_COLOR_F))),
|
|
('BeginDraw',
|
|
com.METHOD(c_void)),
|
|
('EndDraw',
|
|
com.STDMETHOD(c_void_p, c_void_p)),
|
|
('GetPixelFormat',
|
|
com.STDMETHOD()),
|
|
('SetDpi',
|
|
com.STDMETHOD()),
|
|
('GetDpi',
|
|
com.STDMETHOD()),
|
|
('GetSize',
|
|
com.STDMETHOD()),
|
|
('GetPixelSize',
|
|
com.STDMETHOD()),
|
|
('GetMaximumBitmapSize',
|
|
com.STDMETHOD()),
|
|
('IsSupported',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
IID_ID2D1Factory = com.GUID(0x06152247, 0x6f50, 0x465a, 0x92, 0x45, 0x11, 0x8b, 0xfd, 0x3b, 0x60, 0x07)
|
|
|
|
|
|
class ID2D1Factory(com.pIUnknown):
|
|
_methods_ = [
|
|
('ReloadSystemMetrics',
|
|
com.STDMETHOD()),
|
|
('GetDesktopDpi',
|
|
com.STDMETHOD()),
|
|
('CreateRectangleGeometry',
|
|
com.STDMETHOD()),
|
|
('CreateRoundedRectangleGeometry',
|
|
com.STDMETHOD()),
|
|
('CreateEllipseGeometry',
|
|
com.STDMETHOD()),
|
|
('CreateGeometryGroup',
|
|
com.STDMETHOD()),
|
|
('CreateTransformedGeometry',
|
|
com.STDMETHOD()),
|
|
('CreatePathGeometry',
|
|
com.STDMETHOD()),
|
|
('CreateStrokeStyle',
|
|
com.STDMETHOD()),
|
|
('CreateDrawingStateBlock',
|
|
com.STDMETHOD()),
|
|
('CreateWicBitmapRenderTarget',
|
|
com.STDMETHOD(IWICBitmap, POINTER(D2D1_RENDER_TARGET_PROPERTIES), POINTER(ID2D1RenderTarget))),
|
|
('CreateHwndRenderTarget',
|
|
com.STDMETHOD()),
|
|
('CreateDxgiSurfaceRenderTarget',
|
|
com.STDMETHOD()),
|
|
('CreateDCRenderTarget',
|
|
com.STDMETHOD()),
|
|
]
|
|
|
|
|
|
d2d_lib = ctypes.windll.d2d1
|
|
|
|
D2D1_FACTORY_TYPE = UINT
|
|
D2D1_FACTORY_TYPE_SINGLE_THREADED = 0
|
|
D2D1_FACTORY_TYPE_MULTI_THREADED = 1
|
|
|
|
D2D1CreateFactory = d2d_lib.D2D1CreateFactory
|
|
D2D1CreateFactory.restype = HRESULT
|
|
D2D1CreateFactory.argtypes = [D2D1_FACTORY_TYPE, com.REFIID, c_void_p, c_void_p]
|
|
|
|
# We need a WIC factory to make this work. Make sure one is in the initialized decoders.
|
|
wic_decoder = None
|
|
for decoder in pyglet.image.codecs.get_decoders():
|
|
if isinstance(decoder, WICDecoder):
|
|
wic_decoder = decoder
|
|
|
|
if not wic_decoder:
|
|
raise Exception("Cannot use DirectWrite without a WIC Decoder")
|
|
|
|
|
|
class DirectWriteGlyphRenderer(base.GlyphRenderer):
|
|
antialias_mode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT
|
|
draw_options = D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT if WINDOWS_8_1_OR_GREATER else D2D1_DRAW_TEXT_OPTIONS_NONE
|
|
measuring_mode = DWRITE_MEASURING_MODE_NATURAL
|
|
|
|
def __init__(self, font):
|
|
self._render_target = None
|
|
self._bitmap = None
|
|
self._brush = None
|
|
self._bitmap_dimensions = (0, 0)
|
|
super(DirectWriteGlyphRenderer, self).__init__(font)
|
|
self.font = font
|
|
|
|
self._analyzer = IDWriteTextAnalyzer()
|
|
self.font._write_factory.CreateTextAnalyzer(byref(self._analyzer))
|
|
|
|
self._text_analysis = TextAnalysis()
|
|
|
|
def render_to_image(self, text, width, height):
|
|
"""This process takes Pyglet out of the equation and uses only DirectWrite to shape and render text.
|
|
This may allows more accurate fonts (bidi, rtl, etc) in very special circumstances."""
|
|
text_buffer = create_unicode_buffer(text)
|
|
|
|
text_layout = IDWriteTextLayout()
|
|
self.font._write_factory.CreateTextLayout(
|
|
text_buffer,
|
|
len(text_buffer),
|
|
self.font._text_format,
|
|
width, # Doesn't affect bitmap size.
|
|
height,
|
|
byref(text_layout)
|
|
)
|
|
|
|
layout_metrics = DWRITE_TEXT_METRICS()
|
|
text_layout.GetMetrics(byref(layout_metrics))
|
|
|
|
width, height = int(math.ceil(layout_metrics.width)), int(math.ceil(layout_metrics.height))
|
|
|
|
bitmap = IWICBitmap()
|
|
wic_decoder._factory.CreateBitmap(
|
|
width,
|
|
height,
|
|
GUID_WICPixelFormat32bppPBGRA,
|
|
WICBitmapCacheOnDemand,
|
|
byref(bitmap)
|
|
)
|
|
|
|
rt = ID2D1RenderTarget()
|
|
d2d_factory.CreateWicBitmapRenderTarget(bitmap, default_target_properties, byref(rt))
|
|
|
|
# Font aliasing rendering quality.
|
|
rt.SetTextAntialiasMode(self.antialias_mode)
|
|
|
|
if not self._brush:
|
|
self._brush = ID2D1SolidColorBrush()
|
|
|
|
rt.CreateSolidColorBrush(white, None, byref(self._brush))
|
|
|
|
rt.BeginDraw()
|
|
|
|
rt.Clear(transparent)
|
|
|
|
rt.DrawTextLayout(no_offset,
|
|
text_layout,
|
|
self._brush,
|
|
self.draw_options)
|
|
|
|
rt.EndDraw(None, None)
|
|
|
|
rt.Release()
|
|
|
|
image_data = wic_decoder.get_image(bitmap)
|
|
|
|
return image_data
|
|
|
|
def get_string_info(self, text, font_face):
|
|
"""Converts a string of text into a list of indices and advances used for shaping."""
|
|
text_length = len(text.encode('utf-16-le')) // 2
|
|
|
|
# Unicode buffer splits each two byte chars into separate indices.
|
|
text_buffer = create_unicode_buffer(text, text_length)
|
|
|
|
# Analyze the text.
|
|
# noinspection PyTypeChecker
|
|
self._text_analysis.GenerateResults(self._analyzer, text_buffer, len(text_buffer))
|
|
|
|
# Formula for text buffer size from Microsoft.
|
|
max_glyph_size = int(3 * text_length / 2 + 16)
|
|
|
|
length = text_length
|
|
clusters = (UINT16 * length)()
|
|
text_props = (DWRITE_SHAPING_TEXT_PROPERTIES * length)()
|
|
indices = (UINT16 * max_glyph_size)()
|
|
glyph_props = (DWRITE_SHAPING_GLYPH_PROPERTIES * max_glyph_size)()
|
|
actual_count = UINT32()
|
|
|
|
self._analyzer.GetGlyphs(
|
|
text_buffer,
|
|
length,
|
|
font_face,
|
|
False, # sideways
|
|
False, # righttoleft
|
|
self._text_analysis._script, # scriptAnalysis
|
|
None, # localName
|
|
None, # numberSub
|
|
None, # typo features
|
|
None, # feature range length
|
|
0, # feature range
|
|
max_glyph_size, # max glyph size
|
|
clusters, # cluster map
|
|
text_props, # text props
|
|
indices, # glyph indices
|
|
glyph_props, # glyph pops
|
|
byref(actual_count) # glyph count
|
|
)
|
|
|
|
advances = (FLOAT * length)()
|
|
offsets = (DWRITE_GLYPH_OFFSET * length)()
|
|
self._analyzer.GetGlyphPlacements(
|
|
text_buffer,
|
|
clusters,
|
|
text_props,
|
|
text_length,
|
|
indices,
|
|
glyph_props,
|
|
actual_count,
|
|
font_face,
|
|
self.font._font_metrics.designUnitsPerEm,
|
|
False, False,
|
|
self._text_analysis._script,
|
|
self.font.locale,
|
|
None,
|
|
None,
|
|
0,
|
|
advances,
|
|
offsets
|
|
)
|
|
|
|
return text_buffer, actual_count.value, indices, advances, offsets, clusters
|
|
|
|
def get_glyph_metrics(self, font_face, indices, count):
|
|
"""Returns a list of tuples with the following metrics per indice:
|
|
(glyph width, glyph height, lsb, advanceWidth)
|
|
"""
|
|
glyph_metrics = (DWRITE_GLYPH_METRICS * count)()
|
|
font_face.GetDesignGlyphMetrics(indices, count, glyph_metrics, False)
|
|
|
|
metrics_out = []
|
|
for metric in glyph_metrics:
|
|
glyph_width = (metric.advanceWidth - metric.leftSideBearing - metric.rightSideBearing)
|
|
|
|
# width must have a minimum of 1. For example, spaces are actually 0 width, still need glyph bitmap size.
|
|
if glyph_width == 0:
|
|
glyph_width = 1
|
|
|
|
glyph_height = (metric.advanceHeight - metric.topSideBearing - metric.bottomSideBearing)
|
|
|
|
lsb = metric.leftSideBearing
|
|
|
|
bsb = metric.bottomSideBearing
|
|
|
|
advance_width = metric.advanceWidth
|
|
|
|
metrics_out.append((glyph_width, glyph_height, lsb, advance_width, bsb))
|
|
|
|
return metrics_out
|
|
|
|
def _get_single_glyph_run(self, font_face, size, indices, advances, offsets, sideways, bidi):
|
|
run = DWRITE_GLYPH_RUN(
|
|
font_face,
|
|
size,
|
|
1,
|
|
indices,
|
|
advances,
|
|
offsets,
|
|
sideways,
|
|
bidi
|
|
)
|
|
return run
|
|
|
|
def is_color_run(self, run):
|
|
"""Will return True if the run contains a colored glyph."""
|
|
enumerator = IDWriteColorGlyphRunEnumerator1()
|
|
try:
|
|
color = self.font._write_factory.TranslateColorGlyphRun4(no_offset,
|
|
run,
|
|
None,
|
|
DWRITE_GLYPH_IMAGE_FORMATS_ALL,
|
|
self.measuring_mode,
|
|
None,
|
|
0,
|
|
enumerator)
|
|
|
|
return True
|
|
except OSError:
|
|
# HRESULT returns -2003283956 (DWRITE_E_NOCOLOR) if no color run is detected.
|
|
pass
|
|
|
|
return False
|
|
|
|
def render_single_glyph(self, font_face, indice, advance, offset, metrics):
|
|
"""Renders a single glyph using D2D DrawGlyphRun"""
|
|
glyph_width, glyph_height, glyph_lsb, glyph_advance, glyph_bsb = metrics # We use a shaped advance instead of the fonts.
|
|
|
|
# Slicing an array turns it into a python object. Maybe a better way to keep it a ctypes value?
|
|
new_indice = (UINT16 * 1)(indice)
|
|
new_advance = (FLOAT * 1)(advance)
|
|
|
|
run = self._get_single_glyph_run(
|
|
font_face,
|
|
self.font._real_size,
|
|
new_indice, # indice,
|
|
new_advance, # advance,
|
|
pointer(offset), # offset,
|
|
False,
|
|
0
|
|
)
|
|
|
|
# If it's colored, return to render it using layout.
|
|
if self.draw_options & D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT and self.is_color_run(run):
|
|
return None
|
|
|
|
# Use the glyph's advance as a width as bitmap width.
|
|
# Some characters such as diacritics (̃) may have 0 advance width. In that case, just use glyph_width
|
|
if glyph_advance:
|
|
render_width = int(math.ceil(glyph_advance * self.font.font_scale_ratio))
|
|
else:
|
|
render_width = int(math.ceil(glyph_width * self.font.font_scale_ratio))
|
|
|
|
render_offset_x = 0
|
|
if glyph_lsb < 0:
|
|
# Negative LSB: we shift the offset, otherwise the glyph will be cut off.
|
|
render_offset_x = glyph_lsb * self.font.font_scale_ratio
|
|
|
|
# Create new bitmap.
|
|
# TODO: We can probably adjust bitmap/baseline to reduce the whitespace and save a lot of texture space.
|
|
# Note: Floating point precision makes this a giant headache, will need to be solved for this approach.
|
|
self._create_bitmap(render_width + 1, # Add 1, sometimes AA can add an extra pixel or so.
|
|
int(math.ceil(self.font.max_glyph_height)))
|
|
|
|
# Glyphs are drawn at the baseline, and with LSB, so we need to offset it based on top left position.
|
|
baseline_offset = D2D_POINT_2F(-render_offset_x - offset.advanceOffset,
|
|
self.font.ascent + offset.ascenderOffset)
|
|
|
|
self._render_target.BeginDraw()
|
|
|
|
self._render_target.Clear(transparent)
|
|
|
|
self._render_target.DrawGlyphRun(baseline_offset,
|
|
run,
|
|
self._brush,
|
|
self.measuring_mode)
|
|
|
|
self._render_target.EndDraw(None, None)
|
|
image = wic_decoder.get_image(self._bitmap)
|
|
|
|
glyph = self.font.create_glyph(image)
|
|
|
|
glyph.set_bearings(-self.font.descent, render_offset_x,
|
|
advance,
|
|
offset.advanceOffset,
|
|
offset.ascenderOffset)
|
|
|
|
return glyph
|
|
|
|
def render_using_layout(self, text):
|
|
"""This will render text given the built in DirectWrite layout. This process allows us to take
|
|
advantage of color glyphs and fallback handling that is built into DirectWrite.
|
|
This can also handle shaping and many other features if you want to render directly to a texture."""
|
|
text_layout = self.font.create_text_layout(text)
|
|
|
|
layout_metrics = DWRITE_TEXT_METRICS()
|
|
text_layout.GetMetrics(byref(layout_metrics))
|
|
|
|
width = int(math.ceil(layout_metrics.width))
|
|
height = int(math.ceil(layout_metrics.height))
|
|
|
|
if width == 0 or height == 0:
|
|
return None
|
|
|
|
self._create_bitmap(width, height)
|
|
|
|
# This offsets the characters if needed.
|
|
point = D2D_POINT_2F(0, 0)
|
|
|
|
self._render_target.BeginDraw()
|
|
|
|
self._render_target.Clear(transparent)
|
|
|
|
self._render_target.DrawTextLayout(point,
|
|
text_layout,
|
|
self._brush,
|
|
self.draw_options)
|
|
|
|
self._render_target.EndDraw(None, None)
|
|
|
|
image = wic_decoder.get_image(self._bitmap)
|
|
|
|
glyph = self.font.create_glyph(image)
|
|
glyph.set_bearings(-self.font.descent, 0, int(math.ceil(layout_metrics.width)))
|
|
return glyph
|
|
|
|
def create_zero_glyph(self):
|
|
"""Zero glyph is a 1x1 image that has a -1 advance. This is to fill in for ligature substitutions since
|
|
font system requires 1 glyph per character in a string."""
|
|
self._create_bitmap(1, 1)
|
|
image = wic_decoder.get_image(self._bitmap)
|
|
|
|
glyph = self.font.create_glyph(image)
|
|
glyph.set_bearings(-self.font.descent, 0, -1)
|
|
return glyph
|
|
|
|
def _create_bitmap(self, width, height):
|
|
"""Creates a bitmap using Direct2D and WIC."""
|
|
# Create a new bitmap, try to re-use the bitmap as much as we can to minimize creations.
|
|
if self._bitmap_dimensions[0] != width or self._bitmap_dimensions[1] != height:
|
|
# If dimensions aren't the same, release bitmap to create new ones.
|
|
if self._bitmap:
|
|
self._bitmap.Release()
|
|
|
|
self._bitmap = IWICBitmap()
|
|
wic_decoder._factory.CreateBitmap(width, height,
|
|
GUID_WICPixelFormat32bppPBGRA,
|
|
WICBitmapCacheOnDemand,
|
|
byref(self._bitmap))
|
|
|
|
self._render_target = ID2D1RenderTarget()
|
|
d2d_factory.CreateWicBitmapRenderTarget(self._bitmap, default_target_properties, byref(self._render_target))
|
|
|
|
# Font aliasing rendering quality.
|
|
self._render_target.SetTextAntialiasMode(self.antialias_mode)
|
|
|
|
if not self._brush:
|
|
self._brush = ID2D1SolidColorBrush()
|
|
self._render_target.CreateSolidColorBrush(white, None, byref(self._brush))
|
|
|
|
|
|
class Win32DirectWriteFont(base.Font):
|
|
# To load fonts from files, we need to produce a custom collection.
|
|
_custom_collection = None
|
|
|
|
# Shared loader values
|
|
_write_factory = None # Factory required to run any DirectWrite interfaces.
|
|
_font_loader = None
|
|
|
|
# Windows 10 loader values.
|
|
_font_builder = None
|
|
_font_set = None
|
|
|
|
# Legacy loader values
|
|
_font_collection_loader = None
|
|
_font_cache = []
|
|
_font_loader_key = None
|
|
|
|
_default_name = 'Segoe UI' # Default font for Win7/10.
|
|
|
|
_glyph_renderer = None
|
|
_empty_glyph = None
|
|
_zero_glyph = None
|
|
|
|
glyph_renderer_class = DirectWriteGlyphRenderer
|
|
texture_internalformat = pyglet.gl.GL_RGBA
|
|
|
|
def __init__(self, name, size, bold=False, italic=False, stretch=False, dpi=None, locale=None):
|
|
self._advance_cache = {} # Stores glyph's by the indice and advance.
|
|
|
|
super(Win32DirectWriteFont, self).__init__()
|
|
|
|
if not name:
|
|
name = self._default_name
|
|
|
|
self._font_index, self._collection = self.get_collection(name)
|
|
assert self._collection is not None, "Font: {} not found in loaded or system font collection.".format(name)
|
|
|
|
self._name = name
|
|
self.bold = bold
|
|
self.size = size
|
|
self.italic = italic
|
|
self.stretch = stretch
|
|
self.dpi = dpi
|
|
self.locale = locale
|
|
|
|
if self.locale is None:
|
|
self.locale = ""
|
|
self.rtl = False # Right to left should be handled by pyglet?
|
|
# TODO: Use system locale string?
|
|
|
|
if self.dpi is None:
|
|
self.dpi = 96
|
|
|
|
# From DPI to DIP (Device Independent Pixels) which is what the fonts rely on.
|
|
self._real_size = (self.size * self.dpi) // 72
|
|
|
|
if self.bold:
|
|
if type(self.bold) is str:
|
|
self._weight = name_to_weight[self.bold]
|
|
else:
|
|
self._weight = DWRITE_FONT_WEIGHT_BOLD
|
|
else:
|
|
self._weight = DWRITE_FONT_WEIGHT_NORMAL
|
|
|
|
if self.italic:
|
|
if type(self.italic) is str:
|
|
self._style = name_to_style[self.italic]
|
|
else:
|
|
self._style = DWRITE_FONT_STYLE_ITALIC
|
|
else:
|
|
self._style = DWRITE_FONT_STYLE_NORMAL
|
|
|
|
if self.stretch:
|
|
if type(self.stretch) is str:
|
|
self._stretch = name_to_stretch[self.stretch]
|
|
else:
|
|
self._stretch = DWRITE_FONT_STRETCH_EXPANDED
|
|
else:
|
|
self._stretch = DWRITE_FONT_STRETCH_NORMAL
|
|
|
|
# Create the text format this font will use permanently.
|
|
# Could technically be recreated, but will keep to be inline with other font objects.
|
|
self._text_format = IDWriteTextFormat()
|
|
self._write_factory.CreateTextFormat(
|
|
self._name,
|
|
self._collection,
|
|
self._weight,
|
|
self._style,
|
|
self._stretch,
|
|
self._real_size,
|
|
create_unicode_buffer(self.locale),
|
|
byref(self._text_format)
|
|
)
|
|
|
|
# All this work just to get a font face and its metrics!
|
|
font_family = IDWriteFontFamily1()
|
|
self._collection.GetFontFamily(self._font_index, byref(font_family))
|
|
|
|
write_font = IDWriteFont()
|
|
font_family.GetFirstMatchingFont(
|
|
self._weight,
|
|
self._stretch,
|
|
self._style,
|
|
byref(write_font)
|
|
)
|
|
|
|
font_face = IDWriteFontFace()
|
|
write_font.CreateFontFace(byref(font_face))
|
|
|
|
self.font_face = IDWriteFontFace1()
|
|
font_face.QueryInterface(IID_IDWriteFontFace1, byref(self.font_face))
|
|
|
|
self._font_metrics = DWRITE_FONT_METRICS()
|
|
self.font_face.GetMetrics(byref(self._font_metrics))
|
|
|
|
self.font_scale_ratio = (self._real_size / self._font_metrics.designUnitsPerEm)
|
|
|
|
self.ascent = math.ceil(self._font_metrics.ascent * self.font_scale_ratio)
|
|
self.descent = -round(self._font_metrics.descent * self.font_scale_ratio)
|
|
self.max_glyph_height = (self._font_metrics.ascent + self._font_metrics.descent) * self.font_scale_ratio
|
|
|
|
self.line_gap = self._font_metrics.lineGap * self.font_scale_ratio
|
|
|
|
self._fallback = None
|
|
if WINDOWS_8_1_OR_GREATER:
|
|
self._fallback = IDWriteFontFallback()
|
|
self._write_factory.GetSystemFontFallback(byref(self._fallback))
|
|
else:
|
|
assert _debug_font("Windows 8.1+ is required for font fallback. Colored glyphs cannot be omitted.")
|
|
|
|
@property
|
|
def name(self):
|
|
return self._name
|
|
|
|
def render_to_image(self, text, width=10000, height=80):
|
|
"""This process takes Pyglet out of the equation and uses only DirectWrite to shape and render text.
|
|
This may allow more accurate fonts (bidi, rtl, etc) in very special circumstances at the cost of
|
|
additional texture space.
|
|
|
|
:Parameters:
|
|
`text` : str
|
|
String of text to render.
|
|
|
|
:rtype: `ImageData`
|
|
:return: An image of the text.
|
|
"""
|
|
if not self._glyph_renderer:
|
|
self._glyph_renderer = self.glyph_renderer_class(self)
|
|
|
|
return self._glyph_renderer.render_to_image(text, width, height)
|
|
|
|
def copy_glyph(self, glyph, advance, offset):
|
|
"""This takes the existing glyph texture and puts it into a new Glyph with a new advance.
|
|
Texture memory is shared between both glyphs."""
|
|
new_glyph = base.Glyph(glyph.x, glyph.y, glyph.z, glyph.width, glyph.height, glyph.owner)
|
|
new_glyph.set_bearings(
|
|
glyph.baseline,
|
|
glyph.lsb,
|
|
advance,
|
|
offset.advanceOffset,
|
|
offset.ascenderOffset
|
|
)
|
|
return new_glyph
|
|
|
|
def _render_layout_glyph(self, text_buffer, i, clusters, check_color=True):
|
|
# Some glyphs can be more than 1 char. We use the clusters to determine how many of an index exist.
|
|
text_length = clusters.count(i)
|
|
|
|
# Amount of glyphs don't always match 1:1 with text as some can be substituted or omitted. Get
|
|
# actual text buffer index.
|
|
text_index = clusters.index(i)
|
|
|
|
# Get actual text based on the index and length.
|
|
actual_text = text_buffer[text_index:text_index + text_length]
|
|
|
|
# Since we can't store as indice 0 without overriding, we have to store as text
|
|
if actual_text not in self.glyphs:
|
|
glyph = self._glyph_renderer.render_using_layout(text_buffer[text_index:text_index + text_length])
|
|
if glyph:
|
|
if check_color and self._glyph_renderer.draw_options & D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT:
|
|
fb_ff = self._get_fallback_font_face(text_index, text_length)
|
|
if fb_ff:
|
|
glyph.colored = self.is_fallback_str_colored(fb_ff, actual_text)
|
|
else:
|
|
glyph = self._empty_glyph
|
|
|
|
self.glyphs[actual_text] = glyph
|
|
|
|
return self.glyphs[actual_text]
|
|
|
|
def is_fallback_str_colored(self, font_face, text):
|
|
indice = UINT16()
|
|
code_points = (UINT32 * len(text))(*[ord(c) for c in text])
|
|
|
|
font_face.GetGlyphIndices(code_points, len(text), byref(indice))
|
|
|
|
new_indice = (UINT16 * 1)(indice)
|
|
new_advance = (FLOAT * 1)(100) # dummy
|
|
offset = (DWRITE_GLYPH_OFFSET * 1)()
|
|
|
|
run = self._glyph_renderer._get_single_glyph_run(
|
|
font_face,
|
|
self._real_size,
|
|
new_indice, # indice,
|
|
new_advance, # advance,
|
|
offset, # offset,
|
|
False,
|
|
False
|
|
)
|
|
|
|
return self._glyph_renderer.is_color_run(run)
|
|
|
|
def _get_fallback_font_face(self, text_index, text_length):
|
|
if WINDOWS_8_1_OR_GREATER:
|
|
out_length = UINT32()
|
|
fb_font = IDWriteFont()
|
|
scale = FLOAT()
|
|
|
|
self._fallback.MapCharacters(
|
|
self._glyph_renderer._text_analysis,
|
|
text_index,
|
|
text_length,
|
|
None,
|
|
None,
|
|
self._weight,
|
|
self._style,
|
|
self._stretch,
|
|
byref(out_length),
|
|
byref(fb_font),
|
|
byref(scale)
|
|
)
|
|
|
|
if fb_font:
|
|
fb_font_face = IDWriteFontFace()
|
|
fb_font.CreateFontFace(byref(fb_font_face))
|
|
|
|
return fb_font_face
|
|
|
|
return None
|
|
|
|
def get_glyphs_no_shape(self, text):
|
|
"""This differs in that it does not attempt to shape the text at all. May be useful in cases where your font
|
|
has no special shaping requirements, spacing is the same, or some other reason where faster performance is
|
|
wanted and you can get away with this."""
|
|
if not self._glyph_renderer:
|
|
self._glyph_renderer = self.glyph_renderer_class(self)
|
|
self._empty_glyph = self._glyph_renderer.render_using_layout(" ")
|
|
|
|
glyphs = []
|
|
for c in text:
|
|
if c == '\t':
|
|
c = ' '
|
|
|
|
if c not in self.glyphs:
|
|
self.glyphs[c] = self._glyph_renderer.render_using_layout(c)
|
|
if not self.glyphs[c]:
|
|
self.glyphs[c] = self._empty_glyph
|
|
|
|
glyphs.append(self.glyphs[c])
|
|
|
|
return glyphs
|
|
|
|
def get_glyphs(self, text):
|
|
if not self._glyph_renderer:
|
|
self._glyph_renderer = self.glyph_renderer_class(self)
|
|
self._empty_glyph = self._glyph_renderer.render_using_layout(" ")
|
|
self._zero_glyph = self._glyph_renderer.create_zero_glyph()
|
|
|
|
text_buffer, actual_count, indices, advances, offsets, clusters = self._glyph_renderer.get_string_info(text,
|
|
self.font_face)
|
|
|
|
metrics = self._glyph_renderer.get_glyph_metrics(self.font_face, indices, actual_count)
|
|
|
|
formatted_clusters = list(clusters)
|
|
|
|
# Convert to real sizes.
|
|
for i in range(actual_count):
|
|
advances[i] *= self.font_scale_ratio
|
|
|
|
for i in range(actual_count):
|
|
offsets[i].advanceOffset *= self.font_scale_ratio
|
|
offsets[i].ascenderOffset *= self.font_scale_ratio
|
|
|
|
glyphs = []
|
|
|
|
# Pyglet expects 1 glyph for every string. However, ligatures can combine 1 or more glyphs, leading
|
|
# to issues with multilines producing wrong output.
|
|
substitutions = {}
|
|
for idx in clusters:
|
|
ct = formatted_clusters.count(idx)
|
|
if ct > 1:
|
|
substitutions[idx] = ct-1
|
|
|
|
for i in range(actual_count):
|
|
indice = indices[i]
|
|
|
|
if indice == 0:
|
|
# If an indice is 0, it will return no glyph. In this case we attempt to render leveraging
|
|
# the built in text layout from MS. Which depending on version can use fallback fonts and other tricks
|
|
# to possibly get something of use.
|
|
glyph = self._render_layout_glyph(text_buffer, i, formatted_clusters)
|
|
glyphs.append(glyph)
|
|
else:
|
|
advance_key = (indice, advances[i], offsets[i].advanceOffset, offsets[i].ascenderOffset)
|
|
|
|
# Glyphs can vary depending on shaping. We will cache it by indice, advance, and offset.
|
|
# Possible to just cache without offset and set them each time. This may be faster?
|
|
if indice in self.glyphs:
|
|
if advance_key in self._advance_cache:
|
|
glyph = self._advance_cache[advance_key]
|
|
else:
|
|
glyph = self.copy_glyph(self.glyphs[indice], advances[i], offsets[i])
|
|
self._advance_cache[advance_key] = glyph
|
|
else:
|
|
glyph = self._glyph_renderer.render_single_glyph(self.font_face, indice, advances[i], offsets[i],
|
|
metrics[i])
|
|
if glyph is None: # Will only return None if a color glyph is found. Use DW to render it directly.
|
|
glyph = self._render_layout_glyph(text_buffer, i, formatted_clusters, check_color=False)
|
|
glyph.colored = True
|
|
|
|
self.glyphs[indice] = glyph
|
|
self._advance_cache[advance_key] = glyph
|
|
|
|
glyphs.append(glyph)
|
|
|
|
if i in substitutions:
|
|
for _ in range(substitutions[i]):
|
|
glyphs.append(self._zero_glyph)
|
|
|
|
return glyphs
|
|
|
|
def create_text_layout(self, text):
|
|
text_buffer = create_unicode_buffer(text)
|
|
|
|
text_layout = IDWriteTextLayout()
|
|
hr = self._write_factory.CreateTextLayout(text_buffer,
|
|
len(text_buffer),
|
|
self._text_format,
|
|
10000, # Doesn't affect bitmap size.
|
|
80,
|
|
byref(text_layout)
|
|
)
|
|
|
|
return text_layout
|
|
|
|
@classmethod
|
|
def _initialize_direct_write(cls):
|
|
""" All direct write fonts needs factory access as well as the loaders."""
|
|
if WINDOWS_10_CREATORS_UPDATE_OR_GREATER:
|
|
cls._write_factory = IDWriteFactory5()
|
|
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, IID_IDWriteFactory5, byref(cls._write_factory))
|
|
else:
|
|
# Windows 7 and 8 we need to create our own font loader, collection, enumerator, file streamer... Sigh.
|
|
cls._write_factory = IDWriteFactory()
|
|
DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, IID_IDWriteFactory, byref(cls._write_factory))
|
|
|
|
@classmethod
|
|
def _initialize_custom_loaders(cls):
|
|
"""Initialize the loaders needed to load custom fonts."""
|
|
if WINDOWS_10_CREATORS_UPDATE_OR_GREATER:
|
|
# Windows 10 finally has a built in loader that can take data and make a font out of it w/ COMs.
|
|
cls._font_loader = IDWriteInMemoryFontFileLoader()
|
|
cls._write_factory.CreateInMemoryFontFileLoader(byref(cls._font_loader))
|
|
cls._write_factory.RegisterFontFileLoader(cls._font_loader)
|
|
|
|
# Used for grouping fonts together.
|
|
cls._font_builder = IDWriteFontSetBuilder1()
|
|
cls._write_factory.CreateFontSetBuilder1(byref(cls._font_builder))
|
|
else:
|
|
cls._font_loader = LegacyFontFileLoader()
|
|
|
|
# Note: RegisterFontLoader takes a pointer. However, for legacy we implement our own callback interface.
|
|
# Therefore we need to pass to the actual pointer directly.
|
|
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader])
|
|
|
|
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
|
|
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
|
|
|
|
cls._font_loader_key = cast(create_unicode_buffer("legacy_font_loader"), c_void_p)
|
|
|
|
@classmethod
|
|
def add_font_data(cls, data):
|
|
if not cls._write_factory:
|
|
cls._initialize_direct_write()
|
|
|
|
if not cls._font_loader:
|
|
cls._initialize_custom_loaders()
|
|
|
|
if WINDOWS_10_CREATORS_UPDATE_OR_GREATER:
|
|
font_file = IDWriteFontFile()
|
|
hr = cls._font_loader.CreateInMemoryFontFileReference(cls._write_factory,
|
|
data,
|
|
len(data),
|
|
None,
|
|
byref(font_file))
|
|
|
|
hr = cls._font_builder.AddFontFile(font_file)
|
|
if hr != 0:
|
|
raise Exception("This font file data is not not a font or unsupported.")
|
|
|
|
# We have to rebuild collection everytime we add a font.
|
|
# No way to add fonts to the collection once the FontSet and Collection are created.
|
|
# Release old one and renew.
|
|
if cls._custom_collection:
|
|
cls._font_set.Release()
|
|
cls._custom_collection.Release()
|
|
|
|
cls._font_set = IDWriteFontSet()
|
|
cls._font_builder.CreateFontSet(byref(cls._font_set))
|
|
|
|
cls._custom_collection = IDWriteFontCollection1()
|
|
cls._write_factory.CreateFontCollectionFromFontSet(cls._font_set, byref(cls._custom_collection))
|
|
else:
|
|
cls._font_cache.append(data)
|
|
|
|
# If a collection exists, we need to completely remake the collection, delete everything and start over.
|
|
if cls._custom_collection:
|
|
cls._custom_collection = None
|
|
|
|
cls._write_factory.UnregisterFontCollectionLoader(cls._font_collection_loader)
|
|
cls._write_factory.UnregisterFontFileLoader(cls._font_loader)
|
|
|
|
cls._font_loader = LegacyFontFileLoader()
|
|
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
|
|
|
|
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
|
|
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader])
|
|
|
|
cls._font_collection_loader.AddFontData(cls._font_cache)
|
|
|
|
cls._custom_collection = IDWriteFontCollection()
|
|
|
|
cls._write_factory.CreateCustomFontCollection(cls._font_collection_loader,
|
|
cls._font_loader_key,
|
|
sizeof(cls._font_loader_key),
|
|
byref(cls._custom_collection))
|
|
|
|
@classmethod
|
|
def get_collection(cls, font_name):
|
|
"""Returns which collection this font belongs to (system or custom collection), as well as its index in the
|
|
collection."""
|
|
if not cls._write_factory:
|
|
cls._initialize_direct_write()
|
|
|
|
"""Returns a collection the font_name belongs to."""
|
|
font_index = UINT()
|
|
font_exists = BOOL()
|
|
|
|
# Check custom loaded font collections.
|
|
if cls._custom_collection:
|
|
cls._custom_collection.FindFamilyName(create_unicode_buffer(font_name),
|
|
byref(font_index),
|
|
byref(font_exists))
|
|
|
|
if font_exists.value:
|
|
return font_index.value, cls._custom_collection
|
|
|
|
# Check if font is in the system collection.
|
|
# Do not cache these values permanently as system font collection can be updated during runtime.
|
|
if not font_exists.value:
|
|
sys_collection = IDWriteFontCollection()
|
|
cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1)
|
|
sys_collection.FindFamilyName(create_unicode_buffer(font_name),
|
|
byref(font_index),
|
|
byref(font_exists))
|
|
|
|
if font_exists.value:
|
|
return font_index.value, sys_collection
|
|
|
|
# Font does not exist in either custom or system.
|
|
return None, None
|
|
|
|
@classmethod
|
|
def have_font(cls, name):
|
|
if cls.get_collection(name)[0] is not None:
|
|
return True
|
|
|
|
return False
|
|
|
|
@classmethod
|
|
def get_font_face(cls, name):
|
|
# Check custom collection.
|
|
collection = None
|
|
font_index = UINT()
|
|
font_exists = BOOL()
|
|
|
|
# Check custom collection.
|
|
if cls._custom_collection:
|
|
cls._custom_collection.FindFamilyName(create_unicode_buffer(name),
|
|
byref(font_index),
|
|
byref(font_exists))
|
|
|
|
collection = cls._custom_collection
|
|
|
|
if font_exists.value == 0:
|
|
sys_collection = IDWriteFontCollection()
|
|
cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1)
|
|
sys_collection.FindFamilyName(create_unicode_buffer(name),
|
|
byref(font_index),
|
|
byref(font_exists))
|
|
|
|
collection = sys_collection
|
|
|
|
if font_exists:
|
|
font_family = IDWriteFontFamily()
|
|
collection.GetFontFamily(font_index, byref(font_family))
|
|
|
|
write_font = IDWriteFont()
|
|
font_family.GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL,
|
|
DWRITE_FONT_STRETCH_NORMAL,
|
|
DWRITE_FONT_STYLE_NORMAL,
|
|
byref(write_font))
|
|
|
|
font_face = IDWriteFontFace1()
|
|
write_font.CreateFontFace(byref(font_face))
|
|
|
|
return font_face
|
|
|
|
return None
|
|
|
|
|
|
d2d_factory = ID2D1Factory()
|
|
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_ID2D1Factory, None, byref(d2d_factory))
|
|
|
|
WICBitmapCreateCacheOption = UINT
|
|
WICBitmapNoCache = 0
|
|
WICBitmapCacheOnDemand = 0x1
|
|
WICBitmapCacheOnLoad = 0x2
|
|
|
|
transparent = D2D1_COLOR_F(0.0, 0.0, 0.0, 0.0)
|
|
white = D2D1_COLOR_F(1.0, 1.0, 1.0, 1.0)
|
|
no_offset = D2D_POINT_2F(0, 0)
|
|
|
|
# If we are not shaping, monkeypatch to no shape function.
|
|
if pyglet.options["win32_disable_shaping"]:
|
|
Win32DirectWriteFont.get_glyphs = Win32DirectWriteFont.get_glyphs_no_shape
|