1import copy 2 3import pyglet 4from pyglet.font import base 5from pyglet.image.codecs.wic import IWICBitmap, GUID_WICPixelFormat32bppBGR, WICDecoder, GUID_WICPixelFormat32bppBGRA, \ 6 GUID_WICPixelFormat32bppPBGRA 7 8from pyglet import image 9import ctypes 10import math 11from pyglet import com 12from pyglet.libs.win32 import _kernel32 as kernel32 13from pyglet.libs.win32 import _ole32 as ole32 14from pyglet.libs.win32.constants import * 15from pyglet.libs.win32.types import * 16from ctypes import * 17import os 18import platform 19 20try: 21 dwrite = 'dwrite' 22 23 # System32 and SysWOW64 folders are opposite perception in Windows x64. 24 # System32 = x64 dll's | SysWOW64 = x86 dlls 25 # By default ctypes only seems to look in system32 regardless of Python architecture, which has x64 dlls. 26 if platform.architecture()[0] == '32bit': 27 if platform.machine().endswith('64'): # Machine is 64 bit, Python is 32 bit. 28 dwrite = os.path.join(os.environ['WINDIR'], 'SysWOW64', 'dwrite.dll') 29 30 dwrite_lib = ctypes.windll.LoadLibrary(dwrite) 31except OSError as err: 32 # Doesn't exist? Should stop import of library. 33 pass 34 35 36def DWRITE_MAKE_OPENTYPE_TAG(a, b, c, d): 37 return ord(d) << 24 | ord(c) << 16 | ord(b) << 8 | ord(a) 38 39 40DWRITE_FACTORY_TYPE = UINT 41DWRITE_FACTORY_TYPE_SHARED = 0 42DWRITE_FACTORY_TYPE_ISOLATED = 1 43 44DWRITE_FONT_WEIGHT = UINT 45DWRITE_FONT_WEIGHT_THIN = 100 46DWRITE_FONT_WEIGHT_EXTRA_LIGHT = 200 47DWRITE_FONT_WEIGHT_ULTRA_LIGHT = 200 48DWRITE_FONT_WEIGHT_LIGHT = 300 49DWRITE_FONT_WEIGHT_SEMI_LIGHT = 350 50DWRITE_FONT_WEIGHT_NORMAL = 400 51DWRITE_FONT_WEIGHT_REGULAR = 400 52DWRITE_FONT_WEIGHT_MEDIUM = 500 53DWRITE_FONT_WEIGHT_DEMI_BOLD = 600 54DWRITE_FONT_WEIGHT_SEMI_BOLD = 600 55DWRITE_FONT_WEIGHT_BOLD = 700 56DWRITE_FONT_WEIGHT_EXTRA_BOLD = 800 57DWRITE_FONT_WEIGHT_ULTRA_BOLD = 800 58DWRITE_FONT_WEIGHT_BLACK = 900 59DWRITE_FONT_WEIGHT_HEAVY = 900 60DWRITE_FONT_WEIGHT_EXTRA_BLACK = 950 61 62name_to_weight = {"thin": DWRITE_FONT_WEIGHT_THIN, 63 "extralight": DWRITE_FONT_WEIGHT_EXTRA_LIGHT, 64 "ultralight": DWRITE_FONT_WEIGHT_ULTRA_LIGHT, 65 "light": DWRITE_FONT_WEIGHT_LIGHT, 66 "semilight": DWRITE_FONT_WEIGHT_SEMI_LIGHT, 67 "normal": DWRITE_FONT_WEIGHT_NORMAL, 68 "regular": DWRITE_FONT_WEIGHT_REGULAR, 69 "medium": DWRITE_FONT_WEIGHT_MEDIUM, 70 "demibold": DWRITE_FONT_WEIGHT_DEMI_BOLD, 71 "semibold": DWRITE_FONT_WEIGHT_SEMI_BOLD, 72 "bold": DWRITE_FONT_WEIGHT_BOLD, 73 "extrabold": DWRITE_FONT_WEIGHT_EXTRA_BOLD, 74 "ultrabold": DWRITE_FONT_WEIGHT_ULTRA_BOLD, 75 "black": DWRITE_FONT_WEIGHT_BLACK, 76 "heavy": DWRITE_FONT_WEIGHT_HEAVY, 77 "extrablack": DWRITE_FONT_WEIGHT_EXTRA_BLACK, 78 } 79 80DWRITE_FONT_STRETCH = UINT 81DWRITE_FONT_STRETCH_UNDEFINED = 0 82DWRITE_FONT_STRETCH_ULTRA_CONDENSED = 1 83DWRITE_FONT_STRETCH_EXTRA_CONDENSED = 2 84DWRITE_FONT_STRETCH_CONDENSED = 3 85DWRITE_FONT_STRETCH_SEMI_CONDENSED = 4 86DWRITE_FONT_STRETCH_NORMAL = 5 87DWRITE_FONT_STRETCH_MEDIUM = 5 88DWRITE_FONT_STRETCH_SEMI_EXPANDED = 6 89DWRITE_FONT_STRETCH_EXPANDED = 7 90DWRITE_FONT_STRETCH_EXTRA_EXPANDED = 8 91 92name_to_stretch = {"undefined": DWRITE_FONT_STRETCH_UNDEFINED, 93 "ultracondensed": DWRITE_FONT_STRETCH_ULTRA_CONDENSED, 94 "extracondensed": DWRITE_FONT_STRETCH_EXTRA_CONDENSED, 95 "condensed": DWRITE_FONT_STRETCH_CONDENSED, 96 "semicondensed": DWRITE_FONT_STRETCH_SEMI_CONDENSED, 97 "normal": DWRITE_FONT_STRETCH_NORMAL, 98 "medium": DWRITE_FONT_STRETCH_MEDIUM, 99 "semiexpanded": DWRITE_FONT_STRETCH_SEMI_EXPANDED, 100 "expanded": DWRITE_FONT_STRETCH_EXPANDED, 101 "extraexpanded": DWRITE_FONT_STRETCH_EXTRA_EXPANDED, 102 } 103 104DWRITE_FONT_STYLE = UINT 105DWRITE_FONT_STYLE_NORMAL = 0 106DWRITE_FONT_STYLE_OBLIQUE = 1 107DWRITE_FONT_STYLE_ITALIC = 2 108 109name_to_style = {"normal": DWRITE_FONT_STYLE_NORMAL, 110 "oblique": DWRITE_FONT_STYLE_OBLIQUE, 111 "italic": DWRITE_FONT_STYLE_ITALIC} 112 113UINT8 = c_uint8 114UINT16 = c_uint16 115INT16 = c_int16 116INT32 = c_int32 117UINT32 = c_uint32 118UINT64 = c_uint64 119 120 121class DWRITE_TEXT_METRICS(ctypes.Structure): 122 _fields_ = ( 123 ('left', FLOAT), 124 ('top', FLOAT), 125 ('width', FLOAT), 126 ('widthIncludingTrailingWhitespace', FLOAT), 127 ('height', FLOAT), 128 ('layoutWidth', FLOAT), 129 ('layoutHeight', FLOAT), 130 ('maxBidiReorderingDepth', UINT32), 131 ('lineCount', UINT32), 132 ) 133 134 135class DWRITE_FONT_METRICS(ctypes.Structure): 136 _fields_ = ( 137 ('designUnitsPerEm', UINT16), 138 ('ascent', UINT16), 139 ('descent', UINT16), 140 ('lineGap', INT16), 141 ('capHeight', UINT16), 142 ('xHeight', UINT16), 143 ('underlinePosition', INT16), 144 ('underlineThickness', UINT16), 145 ('strikethroughPosition', INT16), 146 ('strikethroughThickness', UINT16), 147 ) 148 149 150class DWRITE_GLYPH_METRICS(ctypes.Structure): 151 _fields_ = ( 152 ('leftSideBearing', INT32), 153 ('advanceWidth', UINT32), 154 ('rightSideBearing', INT32), 155 ('topSideBearing', INT32), 156 ('advanceHeight', UINT32), 157 ('bottomSideBearing', INT32), 158 ('verticalOriginY', INT32), 159 ) 160 161 162class DWRITE_GLYPH_OFFSET(ctypes.Structure): 163 _fields_ = ( 164 ('advanceOffset', FLOAT), 165 ('ascenderOffset', FLOAT), 166 ) 167 168 def __repr__(self): 169 return f"DWRITE_GLYPH_OFFSET({self.advanceOffset}, {self.ascenderOffset})" 170 171 172class DWRITE_CLUSTER_METRICS(ctypes.Structure): 173 _fields_ = ( 174 ('width', FLOAT), 175 ('length', UINT16), 176 ('canWrapLineAfter', UINT16, 1), 177 ('isWhitespace', UINT16, 1), 178 ('isNewline', UINT16, 1), 179 ('isSoftHyphen', UINT16, 1), 180 ('isRightToLeft', UINT16, 1), 181 ('padding', UINT16, 11), 182 ) 183 184 185class IDWriteFontFace(com.pIUnknown): 186 _methods_ = [ 187 ('GetType', 188 com.STDMETHOD()), 189 ('GetFiles', 190 com.STDMETHOD()), 191 ('GetIndex', 192 com.STDMETHOD()), 193 ('GetSimulations', 194 com.STDMETHOD()), 195 ('IsSymbolFont', 196 com.STDMETHOD()), 197 ('GetMetrics', 198 com.METHOD(c_void, POINTER(DWRITE_FONT_METRICS))), 199 ('GetGlyphCount', 200 com.STDMETHOD()), 201 ('GetDesignGlyphMetrics', 202 com.STDMETHOD(POINTER(UINT16), UINT32, POINTER(DWRITE_GLYPH_METRICS), BOOL)), 203 ('GetGlyphIndices', 204 com.STDMETHOD(POINTER(UINT32), UINT32, POINTER(UINT16))), 205 ('TryGetFontTable', 206 com.STDMETHOD(UINT32, c_void_p, POINTER(UINT32), c_void_p, POINTER(BOOL))), 207 ('ReleaseFontTable', 208 com.METHOD(c_void)), 209 ('GetGlyphRunOutline', 210 com.STDMETHOD()), 211 ('GetRecommendedRenderingMode', 212 com.STDMETHOD()), 213 ('GetGdiCompatibleMetrics', 214 com.STDMETHOD()), 215 ('GetGdiCompatibleGlyphMetrics', 216 com.STDMETHOD()), 217 ] 218 219 220IID_IDWriteFontFace1 = com.GUID(0xa71efdb4, 0x9fdb, 0x4838, 0xad, 0x90, 0xcf, 0xc3, 0xbe, 0x8c, 0x3d, 0xaf) 221 222 223class IDWriteFontFace1(IDWriteFontFace, com.pIUnknown): 224 _methods_ = [ 225 ('GetMetric1', 226 com.STDMETHOD()), 227 ('GetGdiCompatibleMetrics1', 228 com.STDMETHOD()), 229 ('GetCaretMetrics', 230 com.STDMETHOD()), 231 ('GetUnicodeRanges', 232 com.STDMETHOD()), 233 ('IsMonospacedFont', 234 com.STDMETHOD()), 235 ('GetDesignGlyphAdvances', 236 com.METHOD(c_void, POINTER(DWRITE_FONT_METRICS))), 237 ('GetGdiCompatibleGlyphAdvances', 238 com.STDMETHOD()), 239 ('GetKerningPairAdjustments', 240 com.STDMETHOD(UINT32, POINTER(UINT16), POINTER(INT32))), 241 ('HasKerningPairs', 242 com.METHOD(BOOL)), 243 ('GetRecommendedRenderingMode1', 244 com.STDMETHOD()), 245 ('GetVerticalGlyphVariants', 246 com.STDMETHOD()), 247 ('HasVerticalGlyphVariants', 248 com.STDMETHOD()) 249 ] 250 251 252DWRITE_SCRIPT_SHAPES = UINT 253DWRITE_SCRIPT_SHAPES_DEFAULT = 0 254 255 256class DWRITE_SCRIPT_ANALYSIS(ctypes.Structure): 257 _fields_ = ( 258 ('script', UINT16), 259 ('shapes', DWRITE_SCRIPT_SHAPES), 260 ) 261 262 263DWRITE_FONT_FEATURE_TAG = UINT 264 265 266class DWRITE_FONT_FEATURE(ctypes.Structure): 267 _fields_ = ( 268 ('nameTag', DWRITE_FONT_FEATURE_TAG), 269 ('parameter', UINT32), 270 ) 271 272 273class DWRITE_TYPOGRAPHIC_FEATURES(ctypes.Structure): 274 _fields_ = ( 275 ('features', POINTER(DWRITE_FONT_FEATURE)), 276 ('featureCount', UINT32), 277 ) 278 279 280class DWRITE_SHAPING_TEXT_PROPERTIES(ctypes.Structure): 281 _fields_ = ( 282 ('isShapedAlone', UINT16, 1), 283 ('reserved1', UINT16, 1), 284 ('canBreakShapingAfter', UINT16, 1), 285 ('reserved', UINT16, 13), 286 ) 287 288 def __repr__(self): 289 return f"DWRITE_SHAPING_TEXT_PROPERTIES({self.isShapedAlone}, {self.reserved1}, {self.canBreakShapingAfter})" 290 291 292class DWRITE_SHAPING_GLYPH_PROPERTIES(ctypes.Structure): 293 _fields_ = ( 294 ('justification', UINT16, 4), 295 ('isClusterStart', UINT16, 1), 296 ('isDiacritic', UINT16, 1), 297 ('isZeroWidthSpace', UINT16, 1), 298 ('reserved', UINT16, 9), 299 ) 300 301 302DWRITE_READING_DIRECTION = UINT 303DWRITE_READING_DIRECTION_LEFT_TO_RIGHT = 0 304 305 306class IDWriteTextAnalysisSource(com.IUnknown): 307 _methods_ = [ 308 ('GetTextAtPosition', 309 com.METHOD(HRESULT, c_void_p, UINT32, POINTER(c_wchar_p), POINTER(UINT32))), 310 ('GetTextBeforePosition', 311 com.STDMETHOD(UINT32, c_wchar_p, POINTER(UINT32))), 312 ('GetParagraphReadingDirection', 313 com.METHOD(DWRITE_READING_DIRECTION)), 314 ('GetLocaleName', 315 com.STDMETHOD(c_void_p, UINT32, POINTER(UINT32), POINTER(c_wchar_p))), 316 ('GetNumberSubstitution', 317 com.STDMETHOD(UINT32, POINTER(UINT32), c_void_p)), 318 ] 319 320 321class IDWriteTextAnalysisSink(com.IUnknown): 322 _methods_ = [ 323 ('SetScriptAnalysis', 324 com.STDMETHOD(c_void_p, UINT32, UINT32, POINTER(DWRITE_SCRIPT_ANALYSIS))), 325 ('SetLineBreakpoints', 326 com.STDMETHOD(UINT32, UINT32, c_void_p)), 327 ('SetBidiLevel', 328 com.STDMETHOD(UINT32, UINT32, UINT8, UINT8)), 329 ('SetNumberSubstitution', 330 com.STDMETHOD(UINT32, UINT32, c_void_p)), 331 ] 332 333 334class Run: 335 def __init__(self): 336 self.text_start = 0 337 self.text_length = 0 338 self.glyph_start = 0 339 self.glyph_count = 0 340 self.script = DWRITE_SCRIPT_ANALYSIS() 341 self.bidi = 0 342 self.isNumberSubstituted = False 343 self.isSideways = False 344 345 self.next_run = None 346 347 def ContainsTextPosition(self, textPosition): 348 return textPosition >= self.text_start and textPosition < self.text_start + self.text_length 349 350 351class TextAnalysis(com.COMObject): 352 _interfaces_ = [IDWriteTextAnalysisSource, IDWriteTextAnalysisSink] 353 354 def __init__(self): 355 super().__init__() 356 self._textstart = 0 357 self._textlength = 0 358 self._glyphstart = 0 359 self._glyphcount = 0 360 self._ptrs = [] 361 362 self._script = None 363 self._bidi = 0 364 # self._sideways = False 365 366 def GenerateResults(self, analyzer, text, text_length): 367 self._text = text 368 self._textstart = 0 369 self._textlength = text_length 370 self._glyphstart = 0 371 self._glyphcount = 0 372 373 self._start_run = Run() 374 self._start_run.text_length = text_length 375 376 self._current_run = self._start_run 377 378 analyzer.AnalyzeScript(self, 0, text_length, self) 379 380 def SetScriptAnalysis(self, this, textPosition, textLength, scriptAnalysis): 381 # textPosition - The index of the first character in the string that the result applies to 382 # textLength - How many characters of the string from the index that the result applies to 383 # scriptAnalysis - The analysis information for all glyphs starting at position for length. 384 self.SetCurrentRun(textPosition) 385 self.SplitCurrentRun(textPosition) 386 387 while textLength > 0: 388 run, textLength = self.FetchNextRun(textLength) 389 390 run.script.script = scriptAnalysis[0].script 391 run.script.shapes = scriptAnalysis[0].shapes 392 393 self._script = run.script 394 395 return 0 396 # return 0x80004001 397 398 def GetTextBeforePosition(self, this): 399 raise Exception("Currently not implemented.") 400 401 def GetTextAtPosition(self, this, textPosition, textString, textLength): 402 # This method will retrieve a substring of the text in this layout 403 # to be used in an analysis step. 404 # Arguments: 405 # textPosition - The index of the first character of the text to retrieve. 406 # textString - The pointer to the first character of text at the index requested. 407 # textLength - The characters available at/after the textString pointer (string length). 408 if textPosition >= self._textlength: 409 self._no_ptr = c_wchar_p(None) 410 textString[0] = self._no_ptr 411 textLength[0] = 0 412 else: 413 ptr = c_wchar_p(self._text[textPosition:]) 414 self._ptrs.append(ptr) 415 textString[0] = ptr 416 textLength[0] = self._textlength - textPosition 417 418 return 0 419 420 def GetParagraphReadingDirection(self): 421 return 0 422 423 def GetLocaleName(self, this, textPosition, textLength, localeName): 424 self.__local_name = c_wchar_p("") # TODO: Add more locales. 425 localeName[0] = self.__local_name 426 textLength[0] = self._textlength - textPosition 427 return 0 428 429 def GetNumberSubstitution(self): 430 return 0 431 432 def SetCurrentRun(self, textPosition): 433 if self._current_run and self._current_run.ContainsTextPosition(textPosition): 434 return 435 436 def SplitCurrentRun(self, textPosition): 437 if not self._current_run: 438 return 439 440 if textPosition <= self._current_run.text_start: 441 # Already first start of the run. 442 return 443 444 new_run = copy.copy(self._current_run) 445 446 new_run.next_run = self._current_run.next_run 447 self._current_run.next_run = new_run 448 449 splitPoint = textPosition - self._current_run.text_start 450 new_run.text_start += splitPoint 451 new_run.text_length -= splitPoint 452 453 self._current_run.text_length = splitPoint 454 self._current_run = new_run 455 456 def FetchNextRun(self, textLength): 457 original_run = self._current_run 458 459 if (textLength < self._current_run.text_length): 460 self.SplitCurrentRun(self._current_run.text_start + textLength) 461 else: 462 self._current_run = self._current_run.next_run 463 464 textLength -= original_run.text_length 465 466 return original_run, textLength 467 468 469class IDWriteTextAnalyzer(com.pIUnknown): 470 _methods_ = [ 471 ('AnalyzeScript', 472 com.STDMETHOD(POINTER(IDWriteTextAnalysisSource), UINT32, UINT32, POINTER(IDWriteTextAnalysisSink))), 473 ('AnalyzeBidi', 474 com.STDMETHOD()), 475 ('AnalyzeNumberSubstitution', 476 com.STDMETHOD()), 477 ('AnalyzeLineBreakpoints', 478 com.STDMETHOD()), 479 ('GetGlyphs', 480 com.STDMETHOD(c_wchar_p, UINT32, IDWriteFontFace, BOOL, BOOL, POINTER(DWRITE_SCRIPT_ANALYSIS), 481 c_wchar_p, c_void_p, POINTER(POINTER(DWRITE_TYPOGRAPHIC_FEATURES)), POINTER(UINT32), 482 UINT32, UINT32, POINTER(UINT16), POINTER(DWRITE_SHAPING_TEXT_PROPERTIES), 483 POINTER(UINT16), POINTER(DWRITE_SHAPING_GLYPH_PROPERTIES), POINTER(UINT32))), 484 ('GetGlyphPlacements', 485 com.STDMETHOD(c_wchar_p, POINTER(UINT16), POINTER(DWRITE_SHAPING_TEXT_PROPERTIES), UINT32, POINTER(UINT16), 486 POINTER(DWRITE_SHAPING_GLYPH_PROPERTIES), UINT32, IDWriteFontFace, FLOAT, BOOL, BOOL, 487 POINTER(DWRITE_SCRIPT_ANALYSIS), c_wchar_p, POINTER(DWRITE_TYPOGRAPHIC_FEATURES), 488 POINTER(UINT32), UINT32, POINTER(FLOAT), POINTER(DWRITE_GLYPH_OFFSET))), 489 ('GetGdiCompatibleGlyphPlacements', 490 com.STDMETHOD()), 491 ] 492 493 494class IDWriteLocalizedStrings(com.pIUnknown): 495 _methods_ = [ 496 ('GetCount', 497 com.METHOD(UINT32)), 498 ('FindLocaleName', 499 com.STDMETHOD(c_wchar_p, POINTER(UINT32), POINTER(BOOL))), 500 ('GetLocaleNameLength', 501 com.STDMETHOD(UINT32, POINTER(UINT32))), 502 ('GetLocaleName', 503 com.STDMETHOD(UINT32, c_wchar_p, UINT32)), 504 ('GetStringLength', 505 com.STDMETHOD(UINT32, POINTER(UINT32))), 506 ('GetString', 507 com.STDMETHOD(UINT32, c_wchar_p, UINT32)), 508 ] 509 510 511class IDWriteFontList(com.pIUnknown): 512 _methods_ = [ 513 ('GetFontCollection', 514 com.STDMETHOD()), 515 ('GetFontCount', 516 com.STDMETHOD()), 517 ('GetFont', 518 com.STDMETHOD()), 519 ] 520 521 522class IDWriteFontFamily(IDWriteFontList, com.pIUnknown): 523 _methods_ = [ 524 ('GetFamilyNames', 525 com.STDMETHOD(POINTER(IDWriteLocalizedStrings))), 526 ('GetFirstMatchingFont', 527 com.STDMETHOD(DWRITE_FONT_WEIGHT, DWRITE_FONT_STRETCH, DWRITE_FONT_STYLE, c_void_p)), 528 ('GetMatchingFonts', 529 com.STDMETHOD()), 530 ] 531 532 533class IDWriteFontFamily1(IDWriteFontFamily, IDWriteFontList, com.pIUnknown): 534 _methods_ = [ 535 ('GetFontLocality', 536 com.STDMETHOD()), 537 ('GetFont1', 538 com.STDMETHOD()), 539 ('GetFontFaceReference', 540 com.STDMETHOD()), 541 ] 542 543 544class IDWriteFontFile(com.pIUnknown): 545 _methods_ = [ 546 ('GetReferenceKey', 547 com.STDMETHOD()), 548 ('GetLoader', 549 com.STDMETHOD()), 550 ('Analyze', 551 com.STDMETHOD()), 552 ] 553 554 555class IDWriteFont(com.pIUnknown): 556 _methods_ = [ 557 ('GetFontFamily', 558 com.STDMETHOD(POINTER(IDWriteFontFamily))), 559 ('GetWeight', 560 com.STDMETHOD()), 561 ('GetStretch', 562 com.STDMETHOD()), 563 ('GetStyle', 564 com.STDMETHOD()), 565 ('IsSymbolFont', 566 com.STDMETHOD()), 567 ('GetFaceNames', 568 com.STDMETHOD(POINTER(IDWriteLocalizedStrings))), 569 ('GetInformationalStrings', 570 com.STDMETHOD()), 571 ('GetSimulations', 572 com.STDMETHOD()), 573 ('GetMetrics', 574 com.STDMETHOD()), 575 ('HasCharacter', 576 com.STDMETHOD(UINT32, POINTER(BOOL))), 577 ('CreateFontFace', 578 com.STDMETHOD(POINTER(IDWriteFontFace))), 579 ] 580 581 582class IDWriteFont1(IDWriteFont, com.pIUnknown): 583 _methods_ = [ 584 ('GetMetrics1', 585 com.STDMETHOD()), 586 ('GetPanose', 587 com.STDMETHOD()), 588 ('GetUnicodeRanges', 589 com.STDMETHOD()), 590 ('IsMonospacedFont', 591 com.STDMETHOD()) 592 ] 593 594 595class IDWriteFontCollection(com.pIUnknown): 596 _methods_ = [ 597 ('GetFontFamilyCount', 598 com.STDMETHOD()), 599 ('GetFontFamily', 600 com.STDMETHOD(UINT32, POINTER(IDWriteFontFamily))), 601 ('FindFamilyName', 602 com.STDMETHOD(c_wchar_p, POINTER(UINT), POINTER(BOOL))), 603 ('GetFontFromFontFace', 604 com.STDMETHOD()), 605 ] 606 607 608class IDWriteFontCollection1(IDWriteFontCollection, com.pIUnknown): 609 _methods_ = [ 610 ('GetFontSet', 611 com.STDMETHOD()), 612 ('GetFontFamily1', 613 com.STDMETHOD(POINTER(IDWriteFontFamily1))), 614 ] 615 616 617DWRITE_TEXT_ALIGNMENT = UINT 618DWRITE_TEXT_ALIGNMENT_LEADING = 1 619DWRITE_TEXT_ALIGNMENT_TRAILING = 2 620DWRITE_TEXT_ALIGNMENT_CENTER = 3 621DWRITE_TEXT_ALIGNMENT_JUSTIFIED = 4 622 623 624class IDWriteTextFormat(com.pIUnknown): 625 _methods_ = [ 626 ('SetTextAlignment', 627 com.STDMETHOD(DWRITE_TEXT_ALIGNMENT)), 628 ('SetParagraphAlignment', 629 com.STDMETHOD()), 630 ('SetWordWrapping', 631 com.STDMETHOD()), 632 ('SetReadingDirection', 633 com.STDMETHOD()), 634 ('SetFlowDirection', 635 com.STDMETHOD()), 636 ('SetIncrementalTabStop', 637 com.STDMETHOD()), 638 ('SetTrimming', 639 com.STDMETHOD()), 640 ('SetLineSpacing', 641 com.STDMETHOD()), 642 ('GetTextAlignment', 643 com.STDMETHOD()), 644 ('GetParagraphAlignment', 645 com.STDMETHOD()), 646 ('GetWordWrapping', 647 com.STDMETHOD()), 648 ('GetReadingDirection', 649 com.STDMETHOD()), 650 ('GetFlowDirection', 651 com.STDMETHOD()), 652 ('GetIncrementalTabStop', 653 com.STDMETHOD()), 654 ('GetTrimming', 655 com.STDMETHOD()), 656 ('GetLineSpacing', 657 com.STDMETHOD()), 658 ('GetFontCollection', 659 com.STDMETHOD()), 660 ('GetFontFamilyNameLength', 661 com.STDMETHOD()), 662 ('GetFontFamilyName', 663 com.STDMETHOD()), 664 ('GetFontWeight', 665 com.STDMETHOD()), 666 ('GetFontStyle', 667 com.STDMETHOD()), 668 ('GetFontStretch', 669 com.STDMETHOD()), 670 ('GetFontSize', 671 com.STDMETHOD()), 672 ('GetLocaleNameLength', 673 com.STDMETHOD()), 674 ('GetLocaleName', 675 com.STDMETHOD()), 676 ] 677 678 679class IDWriteTypography(com.pIUnknown): 680 _methods_ = [ 681 ('AddFontFeature', 682 com.STDMETHOD(DWRITE_FONT_FEATURE)), 683 ('GetFontFeatureCount', 684 com.METHOD(UINT32)), 685 ('GetFontFeature', 686 com.STDMETHOD()) 687 ] 688 689 690class DWRITE_TEXT_RANGE(ctypes.Structure): 691 _fields_ = ( 692 ('startPosition', UINT32), 693 ('length', UINT32), 694 ) 695 696 697class DWRITE_OVERHANG_METRICS(ctypes.Structure): 698 _fields_ = ( 699 ('left', FLOAT), 700 ('top', FLOAT), 701 ('right', FLOAT), 702 ('bottom', FLOAT), 703 ) 704 705 706class IDWriteTextLayout(IDWriteTextFormat, com.pIUnknown): 707 _methods_ = [ 708 ('SetMaxWidth', 709 com.STDMETHOD()), 710 ('SetMaxHeight', 711 com.STDMETHOD()), 712 ('SetFontCollection', 713 com.STDMETHOD()), 714 ('SetFontFamilyName', 715 com.STDMETHOD()), 716 ('SetFontWeight', # 30 717 com.STDMETHOD()), 718 ('SetFontStyle', 719 com.STDMETHOD()), 720 ('SetFontStretch', 721 com.STDMETHOD()), 722 ('SetFontSize', 723 com.STDMETHOD()), 724 ('SetUnderline', 725 com.STDMETHOD()), 726 ('SetStrikethrough', 727 com.STDMETHOD()), 728 ('SetDrawingEffect', 729 com.STDMETHOD()), 730 ('SetInlineObject', 731 com.STDMETHOD()), 732 ('SetTypography', 733 com.STDMETHOD(IDWriteTypography, DWRITE_TEXT_RANGE)), 734 ('SetLocaleName', 735 com.STDMETHOD()), 736 ('GetMaxWidth', # 40 737 com.METHOD(FLOAT)), 738 ('GetMaxHeight', 739 com.METHOD(FLOAT)), 740 ('GetFontCollection2', 741 com.STDMETHOD()), 742 ('GetFontFamilyNameLength2', 743 com.STDMETHOD()), 744 ('GetFontFamilyName2', 745 com.STDMETHOD()), 746 ('GetFontWeight2', 747 com.STDMETHOD(UINT32, POINTER(DWRITE_FONT_WEIGHT), POINTER(DWRITE_TEXT_RANGE))), 748 ('GetFontStyle2', 749 com.STDMETHOD()), 750 ('GetFontStretch2', 751 com.STDMETHOD()), 752 ('GetFontSize2', 753 com.STDMETHOD()), 754 ('GetUnderline', 755 com.STDMETHOD()), 756 ('GetStrikethrough', 757 com.STDMETHOD(UINT32, POINTER(BOOL), POINTER(DWRITE_TEXT_RANGE))), 758 ('GetDrawingEffect', 759 com.STDMETHOD()), 760 ('GetInlineObject', 761 com.STDMETHOD()), 762 ('GetTypography', # Always returns NULL without SetTypography being called. 763 com.STDMETHOD(UINT32, POINTER(IDWriteTypography), POINTER(DWRITE_TEXT_RANGE))), 764 ('GetLocaleNameLength1', 765 com.STDMETHOD()), 766 ('GetLocaleName1', 767 com.STDMETHOD()), 768 ('Draw', 769 com.STDMETHOD()), 770 ('GetLineMetrics', 771 com.STDMETHOD()), 772 ('GetMetrics', 773 com.STDMETHOD(POINTER(DWRITE_TEXT_METRICS))), 774 ('GetOverhangMetrics', 775 com.STDMETHOD(POINTER(DWRITE_OVERHANG_METRICS))), 776 ('GetClusterMetrics', 777 com.STDMETHOD(POINTER(DWRITE_CLUSTER_METRICS), UINT32, POINTER(UINT32))), 778 ('DetermineMinWidth', 779 com.STDMETHOD(POINTER(FLOAT))), 780 ('HitTestPoint', 781 com.STDMETHOD()), 782 ('HitTestTextPosition', 783 com.STDMETHOD()), 784 ('HitTestTextRange', 785 com.STDMETHOD()), 786 ] 787 788 789class IDWriteTextLayout1(IDWriteTextLayout, IDWriteTextFormat, com.pIUnknown): 790 _methods_ = [ 791 ('SetPairKerning', 792 com.STDMETHOD()), 793 ('GetPairKerning', 794 com.STDMETHOD()), 795 ('SetCharacterSpacing', 796 com.STDMETHOD()), 797 ('GetCharacterSpacing', 798 com.STDMETHOD(UINT32, POINTER(FLOAT), POINTER(FLOAT), POINTER(FLOAT), POINTER(DWRITE_TEXT_RANGE))), 799 ] 800 801 802class IDWriteFontFileEnumerator(com.IUnknown): 803 _methods_ = [ 804 ('MoveNext', 805 com.STDMETHOD(c_void_p, POINTER(BOOL))), 806 ('GetCurrentFontFile', 807 com.STDMETHOD(c_void_p, c_void_p)), 808 ] 809 810 811class IDWriteFontCollectionLoader(com.IUnknown): 812 _methods_ = [ 813 ('CreateEnumeratorFromKey', 814 com.STDMETHOD(c_void_p, c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileEnumerator)))), 815 ] 816 817 818class IDWriteFontFileStream(com.IUnknown): 819 _methods_ = [ 820 ('ReadFileFragment', 821 com.STDMETHOD(c_void_p, POINTER(c_void_p), UINT64, UINT64, POINTER(c_void_p))), 822 ('ReleaseFileFragment', 823 com.STDMETHOD(c_void_p, c_void_p)), 824 ('GetFileSize', 825 com.STDMETHOD(c_void_p, POINTER(UINT64))), 826 ('GetLastWriteTime', 827 com.STDMETHOD(c_void_p, POINTER(UINT64))), 828 ] 829 830 831class MyFontFileStream(com.COMObject): 832 _interfaces_ = [IDWriteFontFileStream] 833 834 def __init__(self, data): 835 self._data = data 836 self._size = len(data) 837 self._ptrs = [] 838 839 def AddRef(self, this): 840 return 1 841 842 def Release(self, this): 843 return 1 844 845 def QueryInterface(self, this, refiid, tester): 846 return 0 847 848 def ReadFileFragment(self, this, fragmentStart, fileOffset, fragmentSize, fragmentContext): 849 if fileOffset + fragmentSize > self._size: 850 return 0x80004005 # E_FAIL 851 852 fragment = self._data[fileOffset:] 853 buffer = (ctypes.c_ubyte * len(fragment)).from_buffer(bytearray(fragment)) 854 ptr = cast(buffer, c_void_p) 855 856 self._ptrs.append(ptr) 857 fragmentStart[0] = ptr 858 fragmentContext[0] = None 859 return 0 860 861 def ReleaseFileFragment(self, this, fragmentContext): 862 return 0 863 864 def GetFileSize(self, this, fileSize): 865 fileSize[0] = self._size 866 return 0 867 868 def GetLastWriteTime(self, this, lastWriteTime): 869 return 0x80004001 # E_NOTIMPL 870 871 872class IDWriteFontFileLoader(com.IUnknown): 873 _methods_ = [ 874 ('CreateStreamFromKey', 875 com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream)))) 876 ] 877 878 879class LegacyFontFileLoader(com.COMObject): 880 _interfaces_ = [IDWriteFontFileLoader] 881 882 def __init__(self): 883 self._streams = {} 884 885 def QueryInterface(self, this, refiid, tester): 886 return 0 887 888 def AddRef(self, this): 889 return 1 890 891 def Release(self, this): 892 return 1 893 894 def CreateStreamFromKey(self, this, fontfileReferenceKey, fontFileReferenceKeySize, fontFileStream): 895 convert_index = cast(fontfileReferenceKey, POINTER(c_uint32)) 896 897 self._ptr = ctypes.cast(self._streams[convert_index.contents.value]._pointers[IDWriteFontFileStream], 898 POINTER(IDWriteFontFileStream)) 899 fontFileStream[0] = self._ptr 900 return 0 901 902 def SetCurrentFont(self, index, data): 903 self._streams[index] = MyFontFileStream(data) 904 905 906class MyEnumerator(com.COMObject): 907 _interfaces_ = [IDWriteFontFileEnumerator] 908 909 def __init__(self, factory, loader): 910 self.factory = cast(factory, IDWriteFactory) 911 self.key = "pyglet_dwrite" 912 self.size = len(self.key) 913 self.current_index = -1 914 915 self._keys = [] 916 self._font_data = [] 917 self._font_files = [] 918 self._current_file = None 919 920 self._font_key_ref = create_unicode_buffer("none") 921 self._font_key_len = len(self._font_key_ref) 922 923 self._file_loader = loader 924 925 def AddFontData(self, fonts): 926 self._font_data = fonts 927 928 def MoveNext(self, this, hasCurrentFile): 929 930 self.current_index += 1 931 if self.current_index != len(self._font_data): 932 font_file = IDWriteFontFile() 933 934 self._file_loader.SetCurrentFont(self.current_index, self._font_data[self.current_index]) 935 936 key = self.current_index 937 938 if not self.current_index in self._keys: 939 buffer = pointer(c_uint32(key)) 940 941 ptr = cast(buffer, c_void_p) 942 943 self._keys.append(ptr) 944 945 self.factory.CreateCustomFontFileReference(self._keys[self.current_index], 946 sizeof(buffer), 947 self._file_loader, 948 byref(font_file)) 949 950 self._font_files.append(font_file) 951 952 hasCurrentFile[0] = 1 953 else: 954 hasCurrentFile[0] = 0 955 956 pass 957 958 def GetCurrentFontFile(self, this, fontFile): 959 fontFile = cast(fontFile, POINTER(IDWriteFontFile)) 960 fontFile[0] = self._font_files[self.current_index] 961 return 0 962 963 964class LegacyCollectionLoader(com.COMObject): 965 _interfaces_ = [IDWriteFontCollectionLoader] 966 967 def __init__(self, factory, loader): 968 self._enumerator = MyEnumerator(factory, loader) 969 970 def AddFontData(self, fonts): 971 self._enumerator.AddFontData(fonts) 972 973 def AddRef(self, this): 974 self._i = 1 975 return 1 976 977 def Release(self, this): 978 self._i = 0 979 return 1 980 981 def QueryInterface(self, this, refiid, tester): 982 return 0 983 984 def CreateEnumeratorFromKey(self, this, factory, key, key_size, enumerator): 985 self._ptr = ctypes.cast(self._enumerator._pointers[IDWriteFontFileEnumerator], 986 POINTER(IDWriteFontFileEnumerator)) 987 988 enumerator[0] = self._ptr 989 return 0 990 991 992IID_IDWriteFactory = com.GUID(0xb859ee5a, 0xd838, 0x4b5b, 0xa2, 0xe8, 0x1a, 0xdc, 0x7d, 0x93, 0xdb, 0x48) 993 994 995class IDWriteFactory(com.pIUnknown): 996 _methods_ = [ 997 ('GetSystemFontCollection', 998 com.STDMETHOD(POINTER(IDWriteFontCollection), BOOL)), 999 ('CreateCustomFontCollection', 1000 com.STDMETHOD(POINTER(IDWriteFontCollectionLoader), c_void_p, UINT32, POINTER(IDWriteFontCollection))), 1001 ('RegisterFontCollectionLoader', 1002 com.STDMETHOD(POINTER(IDWriteFontCollectionLoader))), 1003 ('UnregisterFontCollectionLoader', 1004 com.STDMETHOD(POINTER(IDWriteFontCollectionLoader))), 1005 ('CreateFontFileReference', 1006 com.STDMETHOD(c_wchar_p, c_void_p, POINTER(IDWriteFontFile))), 1007 ('CreateCustomFontFileReference', 1008 com.STDMETHOD(c_void_p, UINT32, POINTER(IDWriteFontFileLoader), POINTER(IDWriteFontFile))), 1009 ('CreateFontFace', 1010 com.STDMETHOD()), 1011 ('CreateRenderingParams', 1012 com.STDMETHOD()), 1013 ('CreateMonitorRenderingParams', 1014 com.STDMETHOD()), 1015 ('CreateCustomRenderingParams', 1016 com.STDMETHOD()), 1017 ('RegisterFontFileLoader', 1018 com.STDMETHOD(c_void_p)), # Ambigious as newer is a pIUnknown and legacy is IUnknown. 1019 ('UnregisterFontFileLoader', 1020 com.STDMETHOD(POINTER(IDWriteFontFileLoader))), 1021 ('CreateTextFormat', 1022 com.STDMETHOD(c_wchar_p, IDWriteFontCollection, DWRITE_FONT_WEIGHT, DWRITE_FONT_STYLE, DWRITE_FONT_STRETCH, 1023 FLOAT, c_wchar_p, POINTER(IDWriteTextFormat))), 1024 ('CreateTypography', 1025 com.STDMETHOD(POINTER(IDWriteTypography))), 1026 ('GetGdiInterop', 1027 com.STDMETHOD()), 1028 ('CreateTextLayout', 1029 com.STDMETHOD(c_wchar_p, UINT32, IDWriteTextFormat, FLOAT, FLOAT, POINTER(IDWriteTextLayout))), 1030 ('CreateGdiCompatibleTextLayout', 1031 com.STDMETHOD()), 1032 ('CreateEllipsisTrimmingSign', 1033 com.STDMETHOD()), 1034 ('CreateTextAnalyzer', 1035 com.STDMETHOD(POINTER(IDWriteTextAnalyzer))), 1036 ('CreateNumberSubstitution', 1037 com.STDMETHOD()), 1038 ('CreateGlyphRunAnalysis', 1039 com.STDMETHOD()), 1040 ] 1041 1042 1043IID_IDWriteFactory1 = com.GUID(0x30572f99, 0xdac6, 0x41db, 0xa1, 0x6e, 0x04, 0x86, 0x30, 0x7e, 0x60, 0x6a) 1044 1045 1046class IDWriteFactory1(IDWriteFactory, com.pIUnknown): 1047 _methods_ = [ 1048 ('GetEudcFontCollection', 1049 com.STDMETHOD()), 1050 ('CreateCustomRenderingParams', 1051 com.STDMETHOD()), 1052 ] 1053 1054 1055class IDWriteFontFallback(com.pIUnknown): 1056 _methods_ = [ 1057 ('MapCharacters', 1058 com.STDMETHOD(POINTER(IDWriteTextAnalysisSource), UINT32, UINT32, POINTER(IDWriteFontCollection), c_wchar_p, 1059 DWRITE_FONT_WEIGHT, DWRITE_FONT_STYLE, DWRITE_FONT_STRETCH, POINTER(UINT32), 1060 POINTER(IDWriteFont), 1061 POINTER(FLOAT))), 1062 ] 1063 1064 1065class IDWriteFactory2(IDWriteFactory1, com.pIUnknown): 1066 _methods_ = [ 1067 ('GetSystemFontFallback', 1068 com.STDMETHOD(POINTER(IDWriteFontFallback))), 1069 ('CreateFontFallbackBuilder', 1070 com.STDMETHOD()), 1071 ('TranslateColorGlyphRun', 1072 com.STDMETHOD()), 1073 ('CreateCustomRenderingParams', 1074 com.STDMETHOD()), 1075 ('CreateGlyphRunAnalysis', 1076 com.STDMETHOD()), 1077 ] 1078 1079 1080class IDWriteFontSet(com.pIUnknown): 1081 _methods_ = [ 1082 ('GetFontCount', 1083 com.STDMETHOD()), 1084 ('GetFontFaceReference', 1085 com.STDMETHOD()), 1086 ('FindFontFaceReference', 1087 com.STDMETHOD()), 1088 ('FindFontFace', 1089 com.STDMETHOD()), 1090 ('GetPropertyValues', 1091 com.STDMETHOD()), 1092 ('GetPropertyOccurrenceCount', 1093 com.STDMETHOD()), 1094 ('GetMatchingFonts', 1095 com.STDMETHOD()), 1096 ('GetMatchingFonts', 1097 com.STDMETHOD()), 1098 ] 1099 1100 1101class IDWriteFontSetBuilder(com.pIUnknown): 1102 _methods_ = [ 1103 ('AddFontFaceReference', 1104 com.STDMETHOD()), 1105 ('AddFontFaceReference', 1106 com.STDMETHOD()), 1107 ('AddFontSet', 1108 com.STDMETHOD()), 1109 ('CreateFontSet', 1110 com.STDMETHOD(POINTER(IDWriteFontSet))), 1111 ] 1112 1113 1114class IDWriteFontSetBuilder1(IDWriteFontSetBuilder, com.pIUnknown): 1115 _methods_ = [ 1116 ('AddFontFile', 1117 com.STDMETHOD(IDWriteFontFile)), 1118 ] 1119 1120 1121class IDWriteFactory3(IDWriteFactory2, com.pIUnknown): 1122 _methods_ = [ 1123 ('CreateGlyphRunAnalysis', 1124 com.STDMETHOD()), 1125 ('CreateCustomRenderingParams', 1126 com.STDMETHOD()), 1127 ('CreateFontFaceReference', 1128 com.STDMETHOD()), 1129 ('CreateFontFaceReference', 1130 com.STDMETHOD()), 1131 ('GetSystemFontSet', 1132 com.STDMETHOD()), 1133 ('CreateFontSetBuilder', 1134 com.STDMETHOD(POINTER(IDWriteFontSetBuilder))), 1135 ('CreateFontCollectionFromFontSet', 1136 com.STDMETHOD(IDWriteFontSet, POINTER(IDWriteFontCollection1))), 1137 ('GetSystemFontCollection3', 1138 com.STDMETHOD()), 1139 ('GetFontDownloadQueue', 1140 com.STDMETHOD()), 1141 ('GetSystemFontSet', 1142 com.STDMETHOD()), 1143 ] 1144 1145 1146class IDWriteFactory4(IDWriteFactory3, com.pIUnknown): 1147 _methods_ = [ 1148 ('TranslateColorGlyphRun', 1149 com.STDMETHOD()), 1150 ('ComputeGlyphOrigins', 1151 com.STDMETHOD()), 1152 ] 1153 1154 1155class IDWriteInMemoryFontFileLoader(com.pIUnknown): 1156 _methods_ = [ 1157 ('CreateStreamFromKey', 1158 com.STDMETHOD()), 1159 ('CreateInMemoryFontFileReference', 1160 com.STDMETHOD(IDWriteFactory, c_void_p, UINT, c_void_p, POINTER(IDWriteFontFile))), 1161 ('GetFileCount', 1162 com.STDMETHOD()), 1163 ] 1164 1165 1166IID_IDWriteFactory5 = com.GUID(0x958DB99A, 0xBE2A, 0x4F09, 0xAF, 0x7D, 0x65, 0x18, 0x98, 0x03, 0xD1, 0xD3) 1167 1168 1169class IDWriteFactory5(IDWriteFactory4, IDWriteFactory3, IDWriteFactory2, IDWriteFactory1, IDWriteFactory, 1170 com.pIUnknown): 1171 _methods_ = [ 1172 ('CreateFontSetBuilder1', 1173 com.STDMETHOD(POINTER(IDWriteFontSetBuilder1))), 1174 ('CreateInMemoryFontFileLoader', 1175 com.STDMETHOD(POINTER(IDWriteInMemoryFontFileLoader))), 1176 ('CreateHttpFontFileLoader', 1177 com.STDMETHOD()), 1178 ('AnalyzeContainerType', 1179 com.STDMETHOD()) 1180 ] 1181 1182 1183class DWRITE_GLYPH_RUN(ctypes.Structure): 1184 _fields_ = ( 1185 ('fontFace', IDWriteFontFace), 1186 ('fontEmSize', FLOAT), 1187 ('glyphCount', UINT32), 1188 ('glyphIndices', POINTER(UINT16)), 1189 ('glyphAdvances', POINTER(FLOAT)), 1190 ('glyphOffsets', POINTER(DWRITE_GLYPH_OFFSET)), 1191 ('isSideways', BOOL), 1192 ('bidiLevel', UINT32), 1193 ) 1194 1195 1196DWriteCreateFactory = dwrite_lib.DWriteCreateFactory 1197DWriteCreateFactory.restype = HRESULT 1198DWriteCreateFactory.argtypes = [DWRITE_FACTORY_TYPE, com.REFIID, POINTER(com.pIUnknown)] 1199 1200 1201class D2D_POINT_2F(Structure): 1202 _fields_ = ( 1203 ('x', FLOAT), 1204 ('y', FLOAT), 1205 ) 1206 1207 1208class D2D1_RECT_F(Structure): 1209 _fields_ = ( 1210 ('left', FLOAT), 1211 ('top', FLOAT), 1212 ('right', FLOAT), 1213 ('bottom', FLOAT), 1214 ) 1215 1216 1217class D2D1_COLOR_F(Structure): 1218 _fields_ = ( 1219 ('r', FLOAT), 1220 ('g', FLOAT), 1221 ('b', FLOAT), 1222 ('a', FLOAT), 1223 ) 1224 1225 1226class ID2D1Resource(com.pIUnknown): 1227 _methods_ = [ 1228 ('GetFactory', 1229 com.STDMETHOD()), 1230 ] 1231 1232 1233class ID2D1Brush(ID2D1Resource, com.pIUnknown): 1234 _methods_ = [ 1235 ('SetOpacity', 1236 com.STDMETHOD()), 1237 ('SetTransform', 1238 com.STDMETHOD()), 1239 ('GetOpacity', 1240 com.STDMETHOD()), 1241 ('GetTransform', 1242 com.STDMETHOD()), 1243 ] 1244 1245 1246class ID2D1SolidColorBrush(ID2D1Brush, ID2D1Resource, com.pIUnknown): 1247 _methods_ = [ 1248 ('SetColor', 1249 com.STDMETHOD()), 1250 ('GetColor', 1251 com.STDMETHOD()), 1252 ] 1253 1254 1255D2D1_TEXT_ANTIALIAS_MODE = UINT 1256D2D1_TEXT_ANTIALIAS_MODE_DEFAULT = 0 1257D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE = 1 1258D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE = 2 1259D2D1_TEXT_ANTIALIAS_MODE_ALIASED = 3 1260 1261D2D1_RENDER_TARGET_TYPE = UINT 1262D2D1_RENDER_TARGET_TYPE_DEFAULT = 0 1263D2D1_RENDER_TARGET_TYPE_SOFTWARE = 1 1264D2D1_RENDER_TARGET_TYPE_HARDWARE = 2 1265 1266D2D1_FEATURE_LEVEL = UINT 1267D2D1_FEATURE_LEVEL_DEFAULT = 0 1268 1269D2D1_RENDER_TARGET_USAGE = UINT 1270D2D1_RENDER_TARGET_USAGE_NONE = 0 1271D2D1_RENDER_TARGET_USAGE_FORCE_BITMAP_REMOTING = 1 1272D2D1_RENDER_TARGET_USAGE_GDI_COMPATIBLE = 2 1273 1274DXGI_FORMAT = UINT 1275DXGI_FORMAT_UNKNOWN = 0 1276 1277D2D1_ALPHA_MODE = UINT 1278D2D1_ALPHA_MODE_UNKNOWN = 0 1279D2D1_ALPHA_MODE_PREMULTIPLIED = 1 1280D2D1_ALPHA_MODE_STRAIGHT = 2 1281D2D1_ALPHA_MODE_IGNORE = 3 1282 1283D2D1_DRAW_TEXT_OPTIONS = UINT 1284D2D1_DRAW_TEXT_OPTIONS_NO_SNAP = 0x00000001 1285D2D1_DRAW_TEXT_OPTIONS_CLIP = 0x00000002 1286D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT = 0x00000004 1287D2D1_DRAW_TEXT_OPTIONS_DISABLE_COLOR_BITMAP_SNAPPING = 0x00000008 1288D2D1_DRAW_TEXT_OPTIONS_NONE = 0x00000000 1289D2D1_DRAW_TEXT_OPTIONS_FORCE_DWORD = 0xffffffff 1290 1291DWRITE_MEASURING_MODE = UINT 1292DWRITE_MEASURING_MODE_NATURAL = 0 1293DWRITE_MEASURING_MODE_GDI_CLASSIC = 1 1294DWRITE_MEASURING_MODE_GDI_NATURAL = 2 1295 1296 1297class D2D1_PIXEL_FORMAT(Structure): 1298 _fields_ = ( 1299 ('format', DXGI_FORMAT), 1300 ('alphaMode', D2D1_ALPHA_MODE), 1301 ) 1302 1303 1304class D2D1_RENDER_TARGET_PROPERTIES(Structure): 1305 _fields_ = ( 1306 ('type', D2D1_RENDER_TARGET_TYPE), 1307 ('pixelFormat', D2D1_PIXEL_FORMAT), 1308 ('dpiX', FLOAT), 1309 ('dpiY', FLOAT), 1310 ('usage', D2D1_RENDER_TARGET_USAGE), 1311 ('minLevel', D2D1_FEATURE_LEVEL), 1312 ) 1313 1314 1315DXGI_FORMAT_B8G8R8A8_UNORM = 87 1316 1317pixel_format = D2D1_PIXEL_FORMAT() 1318pixel_format.format = DXGI_FORMAT_UNKNOWN 1319pixel_format.alphaMode = D2D1_ALPHA_MODE_UNKNOWN 1320 1321default_target_properties = D2D1_RENDER_TARGET_PROPERTIES() 1322default_target_properties.type = D2D1_RENDER_TARGET_TYPE_DEFAULT 1323default_target_properties.pixelFormat = pixel_format 1324default_target_properties.dpiX = 0.0 1325default_target_properties.dpiY = 0.0 1326default_target_properties.usage = D2D1_RENDER_TARGET_USAGE_NONE 1327default_target_properties.minLevel = D2D1_FEATURE_LEVEL_DEFAULT 1328 1329 1330class ID2D1RenderTarget(ID2D1Resource, com.pIUnknown): 1331 _methods_ = [ 1332 ('CreateBitmap', 1333 com.STDMETHOD()), 1334 ('CreateBitmapFromWicBitmap', 1335 com.STDMETHOD()), 1336 ('CreateSharedBitmap', 1337 com.STDMETHOD()), 1338 ('CreateBitmapBrush', 1339 com.STDMETHOD()), 1340 ('CreateSolidColorBrush', 1341 com.STDMETHOD(POINTER(D2D1_COLOR_F), c_void_p, POINTER(ID2D1SolidColorBrush))), 1342 ('CreateGradientStopCollection', 1343 com.STDMETHOD()), 1344 ('CreateLinearGradientBrush', 1345 com.STDMETHOD()), 1346 ('CreateRadialGradientBrush', 1347 com.STDMETHOD()), 1348 ('CreateCompatibleRenderTarget', 1349 com.STDMETHOD()), 1350 ('CreateLayer', 1351 com.STDMETHOD()), 1352 ('CreateMesh', 1353 com.STDMETHOD()), 1354 ('DrawLine', 1355 com.STDMETHOD()), 1356 ('DrawRectangle', 1357 com.STDMETHOD()), 1358 ('FillRectangle', 1359 com.STDMETHOD()), 1360 ('DrawRoundedRectangle', 1361 com.STDMETHOD()), 1362 ('FillRoundedRectangle', 1363 com.STDMETHOD()), 1364 ('DrawEllipse', 1365 com.STDMETHOD()), 1366 ('FillEllipse', 1367 com.STDMETHOD()), 1368 ('DrawGeometry', 1369 com.STDMETHOD()), 1370 ('FillGeometry', 1371 com.STDMETHOD()), 1372 ('FillMesh', 1373 com.STDMETHOD()), 1374 ('FillOpacityMask', 1375 com.STDMETHOD()), 1376 ('DrawBitmap', 1377 com.STDMETHOD()), 1378 ('DrawText', 1379 com.STDMETHOD(c_wchar_p, UINT, IDWriteTextFormat, POINTER(D2D1_RECT_F), ID2D1Brush, D2D1_DRAW_TEXT_OPTIONS, 1380 DWRITE_MEASURING_MODE)), 1381 ('DrawTextLayout', 1382 com.METHOD(c_void, D2D_POINT_2F, IDWriteTextLayout, ID2D1Brush, UINT32)), 1383 ('DrawGlyphRun', 1384 com.METHOD(c_void, D2D_POINT_2F, POINTER(DWRITE_GLYPH_RUN), ID2D1Brush, UINT32)), 1385 ('SetTransform', 1386 com.METHOD(c_void)), 1387 ('GetTransform', 1388 com.STDMETHOD()), 1389 ('SetAntialiasMode', 1390 com.STDMETHOD()), 1391 ('GetAntialiasMode', 1392 com.STDMETHOD()), 1393 ('SetTextAntialiasMode', 1394 com.METHOD(c_void, D2D1_TEXT_ANTIALIAS_MODE)), 1395 ('GetTextAntialiasMode', 1396 com.STDMETHOD()), 1397 ('SetTextRenderingParams', 1398 com.STDMETHOD()), 1399 ('GetTextRenderingParams', 1400 com.STDMETHOD()), 1401 ('SetTags', 1402 com.STDMETHOD()), 1403 ('GetTags', 1404 com.STDMETHOD()), 1405 ('PushLayer', 1406 com.STDMETHOD()), 1407 ('PopLayer', 1408 com.STDMETHOD()), 1409 ('Flush', 1410 com.STDMETHOD()), 1411 ('SaveDrawingState', 1412 com.STDMETHOD()), 1413 ('RestoreDrawingState', 1414 com.STDMETHOD()), 1415 ('PushAxisAlignedClip', 1416 com.STDMETHOD()), 1417 ('PopAxisAlignedClip', 1418 com.STDMETHOD()), 1419 ('Clear', 1420 com.METHOD(c_void, POINTER(D2D1_COLOR_F))), 1421 ('BeginDraw', 1422 com.METHOD(c_void)), 1423 ('EndDraw', 1424 com.STDMETHOD(c_void_p, c_void_p)), 1425 ('GetPixelFormat', 1426 com.STDMETHOD()), 1427 ('SetDpi', 1428 com.STDMETHOD()), 1429 ('GetDpi', 1430 com.STDMETHOD()), 1431 ('GetSize', 1432 com.STDMETHOD()), 1433 ('GetPixelSize', 1434 com.STDMETHOD()), 1435 ('GetMaximumBitmapSize', 1436 com.STDMETHOD()), 1437 ('IsSupported', 1438 com.STDMETHOD()), 1439 ] 1440 1441 1442IID_ID2D1Factory = com.GUID(0x06152247, 0x6f50, 0x465a, 0x92, 0x45, 0x11, 0x8b, 0xfd, 0x3b, 0x60, 0x07) 1443 1444 1445class ID2D1Factory(com.pIUnknown): 1446 _methods_ = [ 1447 ('ReloadSystemMetrics', 1448 com.STDMETHOD()), 1449 ('GetDesktopDpi', 1450 com.STDMETHOD()), 1451 ('CreateRectangleGeometry', 1452 com.STDMETHOD()), 1453 ('CreateRoundedRectangleGeometry', 1454 com.STDMETHOD()), 1455 ('CreateEllipseGeometry', 1456 com.STDMETHOD()), 1457 ('CreateGeometryGroup', 1458 com.STDMETHOD()), 1459 ('CreateTransformedGeometry', 1460 com.STDMETHOD()), 1461 ('CreatePathGeometry', 1462 com.STDMETHOD()), 1463 ('CreateStrokeStyle', 1464 com.STDMETHOD()), 1465 ('CreateDrawingStateBlock', 1466 com.STDMETHOD()), 1467 ('CreateWicBitmapRenderTarget', 1468 com.STDMETHOD(IWICBitmap, POINTER(D2D1_RENDER_TARGET_PROPERTIES), POINTER(ID2D1RenderTarget))), 1469 ('CreateHwndRenderTarget', 1470 com.STDMETHOD()), 1471 ('CreateDxgiSurfaceRenderTarget', 1472 com.STDMETHOD()), 1473 ('CreateDCRenderTarget', 1474 com.STDMETHOD()), 1475 ] 1476 1477 1478d2d_lib = ctypes.windll.d2d1 1479 1480D2D1_FACTORY_TYPE = UINT 1481D2D1_FACTORY_TYPE_SINGLE_THREADED = 0 1482D2D1_FACTORY_TYPE_MULTI_THREADED = 1 1483 1484D2D1CreateFactory = d2d_lib.D2D1CreateFactory 1485D2D1CreateFactory.restype = HRESULT 1486D2D1CreateFactory.argtypes = [D2D1_FACTORY_TYPE, com.REFIID, c_void_p, c_void_p] 1487 1488# We need a WIC factory to make this work. Make sure one is in the initialized decoders. 1489wic_decoder = None 1490for decoder in pyglet.image.codecs.get_decoders(): 1491 if isinstance(decoder, WICDecoder): 1492 wic_decoder = decoder 1493 1494if not wic_decoder: 1495 raise Exception("Cannot use DirectWrite without a WIC Decoder") 1496 1497 1498class DirectWriteGlyphRenderer(base.GlyphRenderer): 1499 antialias_mode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT 1500 draw_options = D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT 1501 1502 def __init__(self, font): 1503 self._render_target = None 1504 self._bitmap = None 1505 self._brush = None 1506 self._bitmap_dimensions = (0, 0) 1507 super(DirectWriteGlyphRenderer, self).__init__(font) 1508 self.font = font 1509 1510 self._analyzer = IDWriteTextAnalyzer() 1511 self.font._write_factory.CreateTextAnalyzer(byref(self._analyzer)) 1512 1513 self._text_analysis = TextAnalysis() 1514 1515 def render_to_image(self, text, width, height): 1516 """This process takes Pyglet out of the equation and uses only DirectWrite to shape and render text. 1517 This may allows more accurate fonts (bidi, rtl, etc) in very special circumstances.""" 1518 text_buffer = create_unicode_buffer(text) 1519 1520 text_layout = IDWriteTextLayout() 1521 self.font._write_factory.CreateTextLayout( 1522 text_buffer, 1523 len(text_buffer), 1524 self.font._text_format, 1525 width, # Doesn't affect bitmap size. 1526 height, 1527 byref(text_layout) 1528 ) 1529 1530 layout_metrics = DWRITE_TEXT_METRICS() 1531 text_layout.GetMetrics(byref(layout_metrics)) 1532 1533 width, height = int(math.ceil(layout_metrics.width)), int(math.ceil(layout_metrics.height)) 1534 1535 bitmap = IWICBitmap() 1536 wic_decoder._factory.CreateBitmap(width, height, 1537 GUID_WICPixelFormat32bppPBGRA, 1538 WICBitmapCacheOnDemand, 1539 byref(bitmap)) 1540 1541 rt = ID2D1RenderTarget() 1542 d2d_factory.CreateWicBitmapRenderTarget(bitmap, default_target_properties, byref(rt)) 1543 1544 # Font aliasing rendering quality. 1545 rt.SetTextAntialiasMode(self.antialias_mode) 1546 1547 if not self._brush: 1548 self._brush = ID2D1SolidColorBrush() 1549 1550 rt.CreateSolidColorBrush(white, None, byref(self._brush)) 1551 1552 # This offsets the characters if needed. 1553 point = D2D_POINT_2F(0, 0) 1554 1555 rt.BeginDraw() 1556 1557 rt.Clear(transparent) 1558 1559 rt.DrawTextLayout(point, 1560 text_layout, 1561 self._brush, 1562 self.draw_options) 1563 1564 rt.EndDraw(None, None) 1565 1566 rt.Release() 1567 1568 image_data = wic_decoder.get_image(bitmap) 1569 1570 return image_data 1571 1572 def get_string_info(self, text): 1573 1574 """Converts a string of text into a list of indices and advances used for shaping.""" 1575 text_length = len(text.encode('utf-16-le')) // 2 1576 1577 # Unicode buffer splits each two byte chars into separate indices. 1578 text_buffer = create_unicode_buffer(text, text_length) 1579 1580 # Analyze the text. 1581 # noinspection PyTypeChecker 1582 self._text_analysis.GenerateResults(self._analyzer, text_buffer, len(text_buffer)) 1583 1584 # Formula for text buffer size from Microsoft. 1585 max_glyph_size = int(3 * text_length / 2 + 16) 1586 1587 length = text_length 1588 clusters = (UINT16 * length)() 1589 text_props = (DWRITE_SHAPING_TEXT_PROPERTIES * length)() 1590 indices = (UINT16 * max_glyph_size)() 1591 glyph_props = (DWRITE_SHAPING_GLYPH_PROPERTIES * max_glyph_size)() 1592 actual_count = UINT32() 1593 1594 self._analyzer.GetGlyphs(text_buffer, 1595 length, 1596 self.font.font_face, 1597 False, # sideways 1598 False, # righttoleft 1599 self._text_analysis._script, # scriptAnalysis 1600 None, # localName 1601 None, # numberSub 1602 None, # typo features 1603 None, # feature range length 1604 0, # feature range 1605 max_glyph_size, # max glyph size 1606 clusters, # cluster map 1607 text_props, # text props 1608 indices, # glyph indices 1609 glyph_props, # glyph pops 1610 byref(actual_count) # glyph count 1611 ) 1612 1613 advances = (FLOAT * length)() 1614 offsets = (DWRITE_GLYPH_OFFSET * length)() 1615 self._analyzer.GetGlyphPlacements(text_buffer, 1616 clusters, 1617 text_props, 1618 text_length, 1619 indices, 1620 glyph_props, 1621 actual_count, 1622 self.font.font_face, 1623 self.font._font_metrics.designUnitsPerEm, 1624 False, False, 1625 self._text_analysis._script, 1626 self.font.locale, 1627 None, 1628 None, 1629 0, 1630 advances, 1631 offsets) 1632 1633 return text_buffer, actual_count.value, indices, advances, offsets, clusters 1634 1635 def get_glyph_metrics(self, font_face, indices, count): 1636 """Returns a list of tuples with the following metrics per indice: 1637 (glyph width, glyph height, lsb, advanceWidth) 1638 """ 1639 glyph_metrics = (DWRITE_GLYPH_METRICS * count)() 1640 font_face.GetDesignGlyphMetrics(indices, count, glyph_metrics, False) 1641 1642 metrics_out = [] 1643 i = 0 1644 for metric in glyph_metrics: 1645 glyph_width = (metric.advanceWidth - metric.leftSideBearing - metric.rightSideBearing) 1646 1647 # width must have a minimum of 1. For example, spaces are actually 0 width, still need glyph bitmap size. 1648 if glyph_width == 0: 1649 glyph_width = 1 1650 1651 glyph_height = (metric.advanceHeight - metric.topSideBearing - metric.bottomSideBearing) 1652 1653 lsb = metric.leftSideBearing 1654 1655 bsb = metric.bottomSideBearing 1656 1657 advance_width = metric.advanceWidth 1658 1659 metrics_out.append((glyph_width, glyph_height, lsb, advance_width, bsb)) 1660 i += 1 1661 1662 return metrics_out 1663 1664 def _get_single_glyph_run(self, font_face, size, indices, advances, offsets, sideways, bidi): 1665 run = DWRITE_GLYPH_RUN( 1666 font_face, 1667 size, 1668 1, 1669 indices, 1670 advances, 1671 offsets, 1672 sideways, 1673 bidi 1674 ) 1675 return run 1676 1677 def render_single_glyph(self, font_face, indice, advance, offset, metrics): 1678 """Renders a single glyph using D2D DrawGlyphRun""" 1679 glyph_width, glyph_height, lsb, font_advance, bsb = metrics # We use a shaped advance instead of the fonts. 1680 1681 # Slicing an array turns it into a python object. Maybe a better way to keep it a ctypes value? 1682 new_indice = (UINT16 * 1)(indice) 1683 new_advance = (FLOAT * 1)(advance) 1684 1685 run = self._get_single_glyph_run(font_face, 1686 self.font._real_size, 1687 new_indice, # indice, 1688 new_advance, # advance, 1689 pointer(offset), # offset, 1690 False, 1691 False) 1692 1693 render_width = int(math.ceil((glyph_width) * self.font.font_scale_ratio)) 1694 render_offset_x = int(math.floor(abs(lsb * self.font.font_scale_ratio))) 1695 if lsb < 0: 1696 # Negative LSB: we shift the layout rect to the right 1697 # Otherwise we will cut the left part of the glyph 1698 render_offset_x = -(render_offset_x) 1699 1700 # Create new bitmap. 1701 # TODO: We can probably adjust bitmap/baseline to reduce the whitespace and save a lot of texture space. 1702 # Note: Floating point precision makes this a giant headache, will need to be solved for this approach. 1703 self._create_bitmap(render_width + 1, # Add 1, sometimes AA can add an extra pixel or so. 1704 int(math.ceil(self.font.max_glyph_height))) 1705 1706 # Glyphs are drawn at the baseline, and with LSB, so we need to offset it based on top left position. 1707 # Offsets are actually based on pixels somehow??? 1708 baseline_offset = D2D_POINT_2F(-render_offset_x - offset.advanceOffset, 1709 self.font.ascent + offset.ascenderOffset) 1710 1711 self._render_target.BeginDraw() 1712 1713 self._render_target.Clear(transparent) 1714 1715 self._render_target.DrawGlyphRun(baseline_offset, 1716 run, 1717 self._brush, 1718 DWRITE_MEASURING_MODE_NATURAL) 1719 1720 self._render_target.EndDraw(None, None) 1721 image = wic_decoder.get_image(self._bitmap) 1722 1723 glyph = self.font.create_glyph(image) 1724 1725 glyph.set_bearings(self.font.descent, render_offset_x, 1726 advance * self.font.font_scale_ratio, 1727 offset.advanceOffset * self.font.font_scale_ratio, 1728 offset.ascenderOffset * self.font.font_scale_ratio) 1729 1730 return glyph 1731 1732 def render_using_layout(self, text): 1733 """This will render text given the built in DirectWrite layout. This process allows us to take 1734 advantage of color glyphs and fallback handling that is built into DirectWrite. 1735 This can also handle shaping and many other features if you want to render directly to a texture.""" 1736 text_layout = self.font.create_text_layout(text) 1737 1738 layout_metrics = DWRITE_TEXT_METRICS() 1739 text_layout.GetMetrics(byref(layout_metrics)) 1740 1741 self._create_bitmap(int(math.ceil(layout_metrics.width)), 1742 int(math.ceil(layout_metrics.height))) 1743 1744 # This offsets the characters if needed. 1745 point = D2D_POINT_2F(0, 0) 1746 1747 self._render_target.BeginDraw() 1748 1749 self._render_target.Clear(transparent) 1750 1751 self._render_target.DrawTextLayout(point, 1752 text_layout, 1753 self._brush, 1754 self.draw_options) 1755 1756 self._render_target.EndDraw(None, None) 1757 1758 image = wic_decoder.get_image(self._bitmap) 1759 1760 glyph = self.font.create_glyph(image) 1761 glyph.set_bearings(self.font.descent, 0, int(math.ceil(layout_metrics.width))) 1762 return glyph 1763 1764 def _create_bitmap(self, width, height): 1765 """Creates a bitmap using Direct2D and WIC.""" 1766 # Create a new bitmap, try to re-use the bitmap as much as we can to minimize creations. 1767 if self._bitmap_dimensions[0] != width or self._bitmap_dimensions[1] != height: 1768 # If dimensions aren't the same, release bitmap to create new ones. 1769 if self._bitmap: 1770 self._bitmap.Release() 1771 1772 self._bitmap = IWICBitmap() 1773 wic_decoder._factory.CreateBitmap(width, height, 1774 GUID_WICPixelFormat32bppPBGRA, 1775 WICBitmapCacheOnDemand, 1776 byref(self._bitmap)) 1777 1778 self._render_target = ID2D1RenderTarget() 1779 d2d_factory.CreateWicBitmapRenderTarget(self._bitmap, default_target_properties, byref(self._render_target)) 1780 1781 # Font aliasing rendering quality. 1782 self._render_target.SetTextAntialiasMode(self.antialias_mode) 1783 1784 if not self._brush: 1785 self._brush = ID2D1SolidColorBrush() 1786 self._render_target.CreateSolidColorBrush(white, None, byref(self._brush)) 1787 1788 1789class Win32DirectWriteFont(base.Font): 1790 # To load fonts from files, we need to produce a custom collection. 1791 _custom_collection = None 1792 1793 # Shared loader values 1794 _write_factory = None # Factory required to run any DirectWrite interfaces. 1795 _font_loader = None 1796 1797 # Windows 10 loader values. 1798 _font_builder = None 1799 _font_set = None 1800 1801 # Legacy loader values 1802 _font_collection_loader = None 1803 _font_cache = [] 1804 _font_loader_key = None 1805 1806 _default_name = 'Segoe UI' # Default font for Win7/10. 1807 1808 _glyph_renderer = None 1809 1810 glyph_renderer_class = DirectWriteGlyphRenderer 1811 texture_internalformat = pyglet.gl.GL_RGBA 1812 1813 def __init__(self, name, size, bold=False, italic=False, stretch=False, dpi=None, locale=None): 1814 self._advance_cache = {} # Stores glyph's by the indice and advance. 1815 1816 super(Win32DirectWriteFont, self).__init__() 1817 1818 if not name: 1819 name = self._default_name 1820 1821 self._font_index, self._collection = self.get_collection(name) 1822 assert self._collection is not None, "Font: {} not found in loaded or system font collection.".format(name) 1823 1824 self._name = name 1825 self.bold = bold 1826 self.size = size 1827 self.italic = italic 1828 self.stretch = stretch 1829 self.dpi = dpi 1830 self.locale = locale 1831 1832 if self.locale is None: 1833 self.locale = "" 1834 self.rtl = False # Right to left should be handled by pyglet? 1835 # TODO: Use system locale string? 1836 1837 if self.dpi is None: 1838 self.dpi = 96 1839 1840 # From DPI to DIP (Device Independent Pixels) which is what the fonts rely on. 1841 self._real_size = (self.size * self.dpi) // 72 1842 1843 if self.bold: 1844 if type(self.bold) is str: 1845 self._weight = name_to_weight[self.bold] 1846 else: 1847 self._weight = DWRITE_FONT_WEIGHT_BOLD 1848 else: 1849 self._weight = DWRITE_FONT_WEIGHT_NORMAL 1850 1851 if self.italic: 1852 if type(self.italic) is str: 1853 self._style = name_to_style[self.italic] 1854 else: 1855 self._style = DWRITE_FONT_STYLE_ITALIC 1856 else: 1857 self._style = DWRITE_FONT_STYLE_NORMAL 1858 1859 if self.stretch: 1860 if type(self.stretch) is str: 1861 self._stretch = name_to_stretch[self.stretch] 1862 else: 1863 self._stretch = DWRITE_FONT_STRETCH_EXPANDED 1864 else: 1865 self._stretch = DWRITE_FONT_STRETCH_NORMAL 1866 1867 # Create the text format this font will use permanently. 1868 # Could technically be recreated, but will keep to be inline with other font objects. 1869 self._text_format = IDWriteTextFormat() 1870 self._write_factory.CreateTextFormat(self._name, 1871 self._collection, 1872 self._weight, 1873 self._style, 1874 self._stretch, 1875 self._real_size, 1876 create_unicode_buffer(self.locale), 1877 byref(self._text_format)) 1878 1879 # All this work just to get a font face and it's metrics! 1880 font_family = IDWriteFontFamily1() 1881 self._collection.GetFontFamily(self._font_index, byref(font_family)) 1882 1883 write_font = IDWriteFont() 1884 font_family.GetFirstMatchingFont(self._weight, 1885 self._stretch, 1886 self._style, 1887 byref(write_font)) 1888 1889 font_face = IDWriteFontFace() 1890 write_font.CreateFontFace(byref(font_face)) 1891 1892 self.font_face = IDWriteFontFace1() 1893 font_face.QueryInterface(IID_IDWriteFontFace1, byref(self.font_face)) 1894 1895 self._font_metrics = DWRITE_FONT_METRICS() 1896 self.font_face.GetMetrics(byref(self._font_metrics)) 1897 1898 self.font_scale_ratio = (self._real_size / self._font_metrics.designUnitsPerEm) 1899 1900 self.ascent = self._font_metrics.ascent * self.font_scale_ratio 1901 self.descent = self._font_metrics.descent * self.font_scale_ratio 1902 1903 self.max_glyph_height = (self._font_metrics.ascent + self._font_metrics.descent) * self.font_scale_ratio 1904 self.line_gap = self._font_metrics.lineGap * self.font_scale_ratio 1905 1906 @property 1907 def name(self): 1908 return self._name 1909 1910 def render_to_image(self, text, width=10000, height=80): 1911 """This process takes Pyglet out of the equation and uses only DirectWrite to shape and render text. 1912 This may allow more accurate fonts (bidi, rtl, etc) in very special circumstances at the cost of 1913 additional texture space. 1914 1915 :Parameters: 1916 `text` : str 1917 String of text to render. 1918 1919 :rtype: `ImageData` 1920 :return: An image of the text. 1921 """ 1922 if not self._glyph_renderer: 1923 self._glyph_renderer = self.glyph_renderer_class(self) 1924 1925 return self._glyph_renderer.render_to_image(text, width, height) 1926 1927 def copy_glyph(self, glyph, advance, offset): 1928 """This takes the existing glyph texture and puts it into a new Glyph with a new advance. 1929 Texture memory is shared between both glyphs.""" 1930 new_glyph = base.Glyph(glyph.x, glyph.y, glyph.z, glyph.width, glyph.height, glyph.owner) 1931 new_glyph.set_bearings(glyph.baseline, 1932 glyph.lsb, 1933 advance * self.font_scale_ratio, 1934 offset.advanceOffset * self.font_scale_ratio, 1935 offset.ascenderOffset * self.font_scale_ratio) 1936 return new_glyph 1937 1938 def get_glyphs(self, text): 1939 if not self._glyph_renderer: 1940 self._glyph_renderer = self.glyph_renderer_class(self) 1941 1942 text_buffer, actual_count, indices, advances, offsets, clusters = self._glyph_renderer.get_string_info(text) 1943 1944 metrics = self._glyph_renderer.get_glyph_metrics(self.font_face, indices, actual_count) 1945 1946 glyphs = [] 1947 for i in range(actual_count): 1948 indice = indices[i] 1949 if indice == 0: 1950 # If an indice is 0, it will return no glyph. In this case we attempt to render leveraging 1951 # the built in text layout from MS. Which depending on version can use fallback fonts and other tricks 1952 # to possibly get something of use. 1953 formatted_clusters = clusters[:] 1954 1955 # Some glyphs can be more than 1 char. We use the clusters to determine how many of an index exist. 1956 text_length = formatted_clusters.count(i) 1957 1958 # Amount of glyphs don't always match 1:1 with text as some can be substituted or omitted. Get 1959 # actual text buffer index. 1960 text_index = formatted_clusters.index(i) 1961 1962 # Get actual text based on the index and length. 1963 actual_text = text_buffer[text_index:text_index + text_length] 1964 1965 # Since we can't store as indice 0 without overriding, we have to store as text 1966 if actual_text not in self.glyphs: 1967 glyph = self._glyph_renderer.render_using_layout(text_buffer[text_index:text_index + text_length]) 1968 self.glyphs[actual_text] = glyph 1969 1970 glyphs.append(self.glyphs[actual_text]) 1971 else: 1972 # Glyphs can vary depending on shaping. We will cache it by indice, advance, and offset. 1973 # Possible to just cache without offset and set them each time. This may be faster? 1974 if indice in self.glyphs: 1975 advance_key = (indice, advances[i], offsets[i].advanceOffset, offsets[i].ascenderOffset) 1976 if advance_key in self._advance_cache: 1977 glyph = self._advance_cache[advance_key] 1978 else: 1979 glyph = self.copy_glyph(self.glyphs[indice], advances[i], offsets[i]) 1980 self._advance_cache[advance_key] = glyph 1981 else: 1982 glyph = self._glyph_renderer.render_single_glyph(self.font_face, indice, advances[i], offsets[i], 1983 metrics[i]) 1984 self.glyphs[indice] = glyph 1985 self._advance_cache[(indice, advances[i], offsets[i].advanceOffset, offsets[i].ascenderOffset)] = glyph 1986 1987 glyphs.append(glyph) 1988 1989 return glyphs 1990 1991 def create_text_layout(self, text): 1992 text_buffer = create_unicode_buffer(text) 1993 1994 text_layout = IDWriteTextLayout() 1995 hr = self._write_factory.CreateTextLayout(text_buffer, 1996 len(text_buffer), 1997 self._text_format, 1998 10000, # Doesn't affect bitmap size. 1999 80, 2000 byref(text_layout) 2001 ) 2002 2003 return text_layout 2004 2005 @classmethod 2006 def _initialize_direct_write(cls): 2007 """ All direct write fonts needs factory access as well as the loaders.""" 2008 if WINDOWS_10_CREATORS_UPDATE_OR_GREATER: 2009 cls._write_factory = IDWriteFactory5() 2010 DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, IID_IDWriteFactory5, byref(cls._write_factory)) 2011 else: 2012 # Windows 7 and 8 we need to create our own font loader, collection, enumerator, file streamer... Sigh. 2013 cls._write_factory = IDWriteFactory() 2014 DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, IID_IDWriteFactory, byref(cls._write_factory)) 2015 2016 @classmethod 2017 def _initialize_custom_loaders(cls): 2018 """Initialize the loaders needed to load custom fonts.""" 2019 if WINDOWS_10_CREATORS_UPDATE_OR_GREATER: 2020 # Windows 10 finally has a built in loader that can take data and make a font out of it w/ COMs. 2021 cls._font_loader = IDWriteInMemoryFontFileLoader() 2022 cls._write_factory.CreateInMemoryFontFileLoader(byref(cls._font_loader)) 2023 cls._write_factory.RegisterFontFileLoader(cls._font_loader) 2024 2025 # Used for grouping fonts together. 2026 cls._font_builder = IDWriteFontSetBuilder1() 2027 cls._write_factory.CreateFontSetBuilder1(byref(cls._font_builder)) 2028 else: 2029 cls._font_loader = LegacyFontFileLoader() 2030 2031 # Note: RegisterFontLoader takes a pointer. However, for legacy we implement our own callback interface. 2032 # Therefore we need to pass to the actual pointer directly. 2033 cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader]) 2034 2035 cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader) 2036 cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader) 2037 2038 cls._font_loader_key = cast(create_unicode_buffer("legacy_font_loader"), c_void_p) 2039 2040 @classmethod 2041 def add_font_data(cls, data): 2042 if not cls._write_factory: 2043 cls._initialize_direct_write() 2044 2045 if not cls._font_loader: 2046 cls._initialize_custom_loaders() 2047 2048 if WINDOWS_10_CREATORS_UPDATE_OR_GREATER: 2049 font_file = IDWriteFontFile() 2050 hr = cls._font_loader.CreateInMemoryFontFileReference(cls._write_factory, 2051 data, 2052 len(data), 2053 None, 2054 byref(font_file)) 2055 2056 hr = cls._font_builder.AddFontFile(font_file) 2057 if hr != 0: 2058 raise Exception("This font file data is not not a font or unsupported.") 2059 2060 # We have to rebuild collection everytime we add a font. 2061 # No way to add fonts to the collection once the FontSet and Collection are created. 2062 # Release old one and renew. 2063 if cls._custom_collection: 2064 cls._font_set.Release() 2065 cls._custom_collection.Release() 2066 2067 cls._font_set = IDWriteFontSet() 2068 cls._font_builder.CreateFontSet(byref(cls._font_set)) 2069 2070 cls._custom_collection = IDWriteFontCollection1() 2071 cls._write_factory.CreateFontCollectionFromFontSet(cls._font_set, byref(cls._custom_collection)) 2072 else: 2073 cls._font_cache.append(data) 2074 2075 # If a collection exists, we need to completely remake the collection, delete everything and start over. 2076 if cls._custom_collection: 2077 cls._custom_collection = None 2078 2079 cls._write_factory.UnregisterFontCollectionLoader(cls._font_collection_loader) 2080 cls._write_factory.UnregisterFontFileLoader(cls._font_loader) 2081 2082 cls._font_loader = LegacyFontFileLoader() 2083 cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader) 2084 2085 cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader) 2086 cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader]) 2087 2088 cls._font_collection_loader.AddFontData(cls._font_cache) 2089 2090 cls._custom_collection = IDWriteFontCollection() 2091 2092 cls._write_factory.CreateCustomFontCollection(cls._font_collection_loader, 2093 cls._font_loader_key, 2094 sizeof(cls._font_loader_key), 2095 byref(cls._custom_collection)) 2096 2097 @classmethod 2098 def get_collection(cls, font_name): 2099 """Returns which collection this font belongs to (system or custom collection), as well as it's index in the 2100 collection.""" 2101 if not cls._write_factory: 2102 cls._initialize_direct_write() 2103 2104 """Returns a collection the font_name belongs to.""" 2105 font_index = UINT() 2106 font_exists = BOOL() 2107 2108 # Check custom loaded font collections. 2109 if cls._custom_collection: 2110 cls._custom_collection.FindFamilyName(create_unicode_buffer(font_name), 2111 byref(font_index), 2112 byref(font_exists)) 2113 2114 if font_exists.value: 2115 return font_index.value, cls._custom_collection 2116 2117 # Check if font is in the system collection. 2118 # Do not cache these values permanently as system font collection can be updated during runtime. 2119 if not font_exists.value: 2120 sys_collection = IDWriteFontCollection() 2121 cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1) 2122 sys_collection.FindFamilyName(create_unicode_buffer(font_name), 2123 byref(font_index), 2124 byref(font_exists)) 2125 2126 if font_exists.value: 2127 return font_index.value, sys_collection 2128 2129 # Font does not exist in either custom or system. 2130 return None, None 2131 2132 @classmethod 2133 def have_font(cls, name): 2134 if cls.get_collection(name)[0] is not None: 2135 return True 2136 2137 return False 2138 2139 @classmethod 2140 def get_font_face(cls, name): 2141 # Check custom collection. 2142 collection = None 2143 font_index = UINT() 2144 font_exists = BOOL() 2145 2146 # Check custom collection. 2147 if cls._custom_collection: 2148 cls._custom_collection.FindFamilyName(create_unicode_buffer(name), 2149 byref(font_index), 2150 byref(font_exists)) 2151 2152 collection = cls._custom_collection 2153 2154 if font_exists.value == 0: 2155 sys_collection = IDWriteFontCollection() 2156 cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1) 2157 sys_collection.FindFamilyName(create_unicode_buffer(name), 2158 byref(font_index), 2159 byref(font_exists)) 2160 2161 collection = sys_collection 2162 2163 if font_exists: 2164 font_family = IDWriteFontFamily() 2165 collection.GetFontFamily(font_index, byref(font_family)) 2166 2167 write_font = IDWriteFont() 2168 font_family.GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL, 2169 DWRITE_FONT_STRETCH_NORMAL, 2170 DWRITE_FONT_STYLE_NORMAL, 2171 byref(write_font)) 2172 2173 font_face = IDWriteFontFace1() 2174 write_font.CreateFontFace(byref(font_face)) 2175 2176 return font_face 2177 2178 return None 2179 2180 2181d2d_factory = ID2D1Factory() 2182hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_ID2D1Factory, None, byref(d2d_factory)) 2183 2184WICBitmapCreateCacheOption = UINT 2185WICBitmapNoCache = 0 2186WICBitmapCacheOnDemand = 0x1 2187WICBitmapCacheOnLoad = 0x2 2188 2189transparent = D2D1_COLOR_F(0.0, 0.0, 0.0, 0.0) 2190white = D2D1_COLOR_F(1.0, 1.0, 1.0, 1.0) 2191