NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

font.py (20721B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\services\font.py
      2 import blue
      3 import telemetry
      4 import types
      5 import trinity
      6 import carbonui.const as uiconst
      7 import carbonui.languageConst as languageConst
      8 from carbonui import fontconst
      9 from carbonui.text.settings import check_convert_font_size
     10 from carbonui.util.stringManip import GetAsUnicode
     11 from carbonui.util.bunch import Bunch
     12 from carbon.common.script.util.commonutils import StripTags
     13 from carbonui.uicore import uicore
     14 from eve.client.script.ui.control.eveLabel import Label
     15 
     16 class GlyphStringBoundingBox(object):
     17 
     18     def __init__(self):
     19         self.xMin = 0
     20         self.xMax = 0
     21         self.yMin = 0
     22         self.yMax = 0
     23 
     24     def Width(self):
     25         return self.xMax - self.xMin
     26 
     27     def Height(self):
     28         return self.yMax - self.yMin
     29 
     30 
     31 class Tr2GlyphString(list):
     32     __guid__ = 'font.Tr2GlyphString'
     33 
     34     def __init__(self, other = None):
     35         if other:
     36             list.__init__(self, other)
     37         self.Reset()
     38 
     39     def __iadd__(self, other):
     40         list.extend(self, other)
     41         self._textDirty = True
     42         return self
     43 
     44     def Reset(self):
     45         self.baseLine = 0
     46         self.baseHeight = 0
     47         self.last = (None, 0)
     48         self.asc = 0
     49         self.des = 0
     50         self.width = 0
     51         self.strong = 0
     52         self.shadow = None
     53         self.fontsize = 0
     54         self.text = u''
     55         self._textDirty = True
     56         while self:
     57             list.pop(self)
     58 
     59     @telemetry.ZONE_METHOD
     60     def Remove(self, startIdx, endIdx):
     61         self._textDirty = True
     62         for i in xrange(startIdx, endIdx):
     63             self.pop(startIdx)
     64 
     65     def FlushFromIndex(self, idx):
     66         self._textDirty = True
     67         while len(self) > idx:
     68             list.pop(self, -1)
     69 
     70     def FlushToIndex(self, idx):
     71         self._textDirty = True
     72         for i in xrange(idx):
     73             if len(self):
     74                 list.pop(self, 0)
     75 
     76     @telemetry.ZONE_METHOD
     77     def Insert(self, params, text, idx, align = 0):
     78         if not text:
     79             return None
     80         self._textDirty = True
     81         color = params.color or -1
     82         if type(color) != types.IntType:
     83             tricol = trinity.TriColor(*color)
     84             color = tricol.AsInt()
     85         uicore.font.ResolveFontFamily(params)
     86         dpi_wordspace = int(params.wordspace * uicore.desktop.dpiScaling + 0.5)
     87         dpi_fontsize = int(check_convert_font_size(params.fontsize) * uicore.desktop.dpiScaling + 0.5)
     88         dpi_letterspace = int(params.letterspace * uicore.desktop.dpiScaling + 0.5)
     89         self.fontsize = dpi_fontsize
     90         lastFontIndex, lastGlyphIndex = (None, None)
     91         for char in text:
     92             if char == u'\xa0':
     93                 rchar = u' '
     94             else:
     95                 rchar = char
     96             fontIndex, glyphIndex = trinity.fontMan.LookupGlyphIndex(params.font, ord(rchar))
     97             if fontIndex != lastFontIndex:
     98                 self.GetMetrics(fontIndex)
     99                 self.baseLine = max(self.baseLine, self.asc)
    100                 self.baseHeight = max(self.baseHeight, self.asc - self.des)
    101                 kern = 0
    102             else:
    103                 kern = trinity.fontMan.LookupKerningXP(fontIndex, glyphIndex, lastGlyphIndex)
    104             lastFontIndex, lastGlyphIndex = fontIndex, glyphIndex
    105             sbit = trinity.fontMan.LookupSBit(fontIndex, dpi_fontsize, dpi_fontsize, glyphIndex)
    106             advance = sbit.xadvance + dpi_letterspace + kern
    107             if rchar == u' ':
    108                 advance += dpi_wordspace
    109             self.insert(idx, (advance,
    110              align,
    111              color,
    112              sbit,
    113              char,
    114              self.asc,
    115              self.des,
    116              params.Copy()))
    117             idx += 1
    118             self.width += advance
    119 
    120     @telemetry.ZONE_METHOD
    121     def GetText(self):
    122         if self._textDirty:
    123             self.text = ''.join([ charData[4] for charData in self ])
    124             self._textDirty = False
    125         return self.text
    126 
    127     @telemetry.ZONE_METHOD
    128     def Append(self, params, text, align = 0):
    129         idx = len(self)
    130         self.Insert(params, text, idx, align)
    131 
    132     @telemetry.ZONE_METHOD
    133     def Reload(self):
    134         data = [ (params, char) for advance, align, color, sbit, char, asc, des, params in self ]
    135         del self[:]
    136         for params, char in data:
    137             self.Append(params, char)
    138 
    139     @telemetry.ZONE_METHOD
    140     def DrawToBuf(self, buf, bx0, by0, checkUnderline = 1):
    141         x = 0
    142         for t in self:
    143             advance = t[0]
    144             sbit = t[3]
    145             params = t[7]
    146             color = params.color or -1
    147             if type(color) != types.IntType:
    148                 tricol = trinity.TriColor(*color)
    149                 color = tricol.AsInt()
    150             hasUnderline = bool(params.underline)
    151             if hasUnderline:
    152                 extraSpace = advance - sbit.xadvance
    153                 sbit.ToBufferWithUnderline(buf.data, buf.width, buf.height, buf.pitch, x + bx0, by0, color, extraSpace)
    154             else:
    155                 sbit.ToBuffer(buf.data, buf.width, buf.height, buf.pitch, x + bx0, by0, color)
    156             x += t[0]
    157 
    158     @telemetry.ZONE_METHOD
    159     def GetMetrics(self, face_id):
    160         self.asc, self.des = trinity.fontMan.LookupMetrics(face_id, self.fontsize, self.fontsize)
    161 
    162     @telemetry.ZONE_METHOD
    163     def AddSpace(self, params, width = 0.0):
    164         uicore.font.ResolveFontFamily(params)
    165         fgi = trinity.fontMan.LookupGlyphIndex(params.font, ord(' '))
    166         sbit = trinity.fontMan.LookupSBit(fgi[0], self.fontsize, self.fontsize, fgi[1])
    167         self.append((width,
    168          0,
    169          -1,
    170          sbit,
    171          ' ',
    172          0,
    173          0,
    174          params))
    175         self.width = max(self.width, self.width + width)
    176 
    177     @telemetry.ZONE_METHOD
    178     def GetBBox(self):
    179         baseLine = baseHeight = 0
    180         bbox = GlyphStringBoundingBox()
    181         x = 0
    182         for t in self:
    183             sbit = t[3]
    184             bbox.xMin = min(sbit.x + sbit.xOffset + x, bbox.xMin)
    185             bbox.xMax = max(sbit.x + sbit.xOffset + x + sbit.width, bbox.xMax)
    186             bbox.yMin = min(sbit.y + sbit.yOffset, bbox.yMin)
    187             bbox.yMax = max(sbit.y + sbit.yOffset + sbit.height, bbox.yMax)
    188             baseLine = max(baseLine, t[5])
    189             baseHeight = max(baseHeight, t[5] - t[6])
    190             x += t[0]
    191 
    192         self.baseLine = baseLine
    193         self.baseHeight = baseHeight
    194         self.width = x
    195         return bbox
    196 
    197     def GetWidth(self):
    198         self.GetBBox()
    199         return self.width
    200 
    201 
    202 import re
    203 tag = re.compile('<color=0x.*?>|</color>|<right>|<center>|<left>')
    204 
    205 class FontHandler(object):
    206     defaultLabelClass = Label
    207     __notifyevents__ = ['OnUIRefresh']
    208 
    209     def __init__(self, *args, **kwds):
    210         self.Prime()
    211         sm.RegisterNotify(self)
    212 
    213     def Prime(self):
    214         self.measureCache = {}
    215         self.textMeasureCache = {}
    216         self.textLanguageIDCache = {}
    217         regexes = []
    218         for languageID, windowsLanguageID, REGEX, isExclusive in (('JA',
    219           languageConst.LANG_JAPANESE,
    220           (uiconst.JA_CHARACTERS,),
    221           True),
    222          ('ZH',
    223           languageConst.LANG_CHINESE,
    224           (uiconst.REGEX_RANGE_CJK_IDEOGRAPHS,),
    225           False),
    226          ('JA',
    227           languageConst.LANG_JAPANESE,
    228           (uiconst.REGEX_RANGE_HIRAGANA,
    229            uiconst.REGEX_RANGE_KATAKANA,
    230            uiconst.REGEX_RANGE_KATAKANA_PHONETIC_EXTENSIONS,
    231            uiconst.REGEX_RANGE_HALF_WIDTH_KATAKANA),
    232           False),
    233          ('KO',
    234           languageConst.LANG_KOREAN,
    235           (uiconst.REGEX_RANGE_HANGUL,),
    236           False)):
    237             compiledRegex = re.compile(u'([' + u']+ *|['.join(REGEX) + u']+ *)', re.UNICODE)
    238             regexes.append((languageID,
    239              windowsLanguageID,
    240              compiledRegex,
    241              isExclusive))
    242 
    243         self.regexes = regexes
    244 
    245     def OnUIRefresh(self):
    246         self.Prime()
    247 
    248     def DeTag(self, s):
    249         return tag.sub('', s)
    250 
    251     @telemetry.ZONE_METHOD
    252     def GetTextHeight(self, strng, width = 0, font = None, fontsize = None, lineSpacing = 0.0, letterspace = 0, shadow = [(1, -1, -1090519040)], uppercase = 0, specialIndent = 0, getTextObj = 0, tabs = [], fontPath = None, fontStyle = None, fontFamily = None, linespace = None, maxLines = None, bold = False, **kwds):
    253         if strng is None or strng == '':
    254             return 0
    255         fontsize = check_convert_font_size(fontsize or fontconst.DEFAULT_FONTSIZE)
    256         letterspace = letterspace or fontconst.DEFAULT_LETTERSPACE
    257         uppercase = uppercase or fontconst.DEFAULT_UPPERCASE
    258         if maxLines == 1:
    259             width = 0
    260             strng = u'\xd3gd'
    261         else:
    262             strng = GetAsUnicode(strng)
    263         cacheKey = (strng,
    264          width,
    265          font,
    266          fontsize,
    267          lineSpacing,
    268          letterspace,
    269          maxLines,
    270          uppercase,
    271          specialIndent,
    272          getTextObj,
    273          1.0,
    274          bold)
    275         cache = self.GetCache(cacheKey)
    276         if cache:
    277             return cache
    278         if self.defaultLabelClass is None:
    279             from carbonui.control.label import LabelCore
    280             self.defaultLabelClass = LabelCore
    281         t = self.defaultLabelClass(text=strng, parent=None, align=uiconst.TOPLEFT, width=width, tabs=tabs, fontsize=fontsize, lineSpacing=lineSpacing, letterspace=letterspace, maxLines=maxLines, uppercase=uppercase, specialIndent=specialIndent, fontPath=fontPath, fontStyle=fontStyle, fontFamily=fontFamily, shadow=shadow, measuringText=not getTextObj, color=None, bold=bold)
    282         if getTextObj:
    283             retval = t
    284         else:
    285             retval = t.textheight
    286         self.SetCache(cacheKey, retval)
    287         return retval
    288 
    289     @telemetry.ZONE_METHOD
    290     def GetTextWidth(self, strng, fontsize = 12, letterspace = 0, uppercase = 0, font = None, fontPath = None, fontStyle = None, fontFamily = None, bold = False):
    291         if not strng:
    292             return 0
    293         font = font
    294         fontsize = check_convert_font_size(fontsize or fontconst.DEFAULT_FONTSIZE)
    295         letterspace = letterspace or fontconst.DEFAULT_LETTERSPACE
    296         uppercase = uppercase or fontconst.DEFAULT_UPPERCASE
    297         cacheKey = (strng,
    298          fontsize,
    299          letterspace,
    300          uppercase,
    301          font,
    302          1.0,
    303          bold)
    304         if cacheKey in self.textMeasureCache:
    305             return self.textMeasureCache[cacheKey]
    306         if '<br>' in strng:
    307             val = max([ self.GetTextWidth(line, fontsize, letterspace, uppercase, font, fontPath, fontStyle, fontFamily, bold) for line in strng.split('<br>') ])
    308         else:
    309             if self.defaultLabelClass is None:
    310                 from carbonui.control.label import LabelCore
    311                 self.defaultLabelClass = LabelCore
    312             textmeasurer = self.defaultLabelClass(text=StripTags(strng, ignoredTags=['b']), parent=None, align=uiconst.TOPLEFT, fontsize=fontsize, letterspace=letterspace, uppercase=uppercase, measuringText=True, fontPath=fontPath, fontStyle=fontStyle, fontFamily=fontFamily, color=None, bold=bold)
    313             val = textmeasurer.textwidth
    314         self.textMeasureCache[cacheKey] = val
    315         return val
    316 
    317     @telemetry.ZONE_METHOD
    318     def MeasureTabstops(self, stringsData, margin = None, minwidth = 32):
    319         if margin is None:
    320             margin = uiconst.LABELTABMARGIN
    321         tabstops = []
    322         tabCount = 0
    323         for stringData in stringsData:
    324             if len(stringData) == 5:
    325                 tabSeperatedColumns, fontsize, letterSpace, shift, uppercase = stringData
    326             else:
    327                 tabSeperatedColumns, fontsize, letterSpace, shift = stringData
    328                 uppercase = 0
    329             tabSeperatedColumns = GetAsUnicode(tabSeperatedColumns)
    330             columnTexts = tabSeperatedColumns.split('<t>')
    331             columnTextsCount = len(columnTexts)
    332             if columnTextsCount > tabCount:
    333                 tabstops.extend([minwidth] * (columnTextsCount - tabCount))
    334                 tabCount = columnTextsCount
    335             i = 0
    336             for columnText in columnTexts:
    337                 width = max(0, shift - margin) + margin + self.GetTextWidth(columnText, fontsize, letterSpace, uppercase) + margin
    338                 if width > tabstops[i]:
    339                     tabstops[i] = width
    340                 i += 1
    341                 shift = 0
    342 
    343         i = 1
    344         while i < tabCount:
    345             tabstops[i] += tabstops[i - 1]
    346             i += 1
    347 
    348         return tabstops
    349 
    350     @telemetry.ZONE_METHOD
    351     def MergeTabstops(self, tabs1, tabs2):
    352         t1, t2 = self.GetTabstopsAsWidth(tabs1), self.GetTabstopsAsWidth(tabs2)
    353         if not len(t2) and len(t1):
    354             return tabs1
    355         if not len(t1) and len(t2):
    356             return tabs2
    357         if not len(t1) and not len(t2):
    358             return []
    359         newtabs = [max(t1[0], t2[0])]
    360         for i in xrange(len(t1) - 1):
    361             if len(t1) > i + 1:
    362                 _t1 = t1[i + 1]
    363             else:
    364                 _t1 = 0
    365             if len(t2) > i + 1:
    366                 _t2 = t2[i + 1]
    367             else:
    368                 _t2 = 0
    369             newtabs.append(newtabs[i] + max(_t1, _t2))
    370 
    371         return newtabs
    372 
    373     @telemetry.ZONE_METHOD
    374     def GetTabstopsAsWidth(self, tabs1):
    375         tabs = tabs1[:1]
    376         for i in xrange(len(tabs1) - 1):
    377             tabs.append(tabs1[i + 1] - tabs1[i])
    378 
    379         return tabs
    380 
    381     @telemetry.ZONE_METHOD
    382     def GetNodeCopyData(self, node, fromIdx, toIdx):
    383         glyphString = self.GetLineGlyphString(node)
    384         if glyphString:
    385             text = glyphString.GetText()
    386             if toIdx == -1:
    387                 return text[fromIdx:]
    388             else:
    389                 return text[fromIdx:toIdx]
    390         return ''
    391 
    392     def GetLineGlyphString(self, node):
    393         return node.get('glyphString', None)
    394 
    395     def GetLetterIdx(self, node, cursorXpos):
    396         i = 0
    397         glyphString = self.GetLineGlyphString(node)
    398         if glyphString:
    399             w = 0
    400             for glyph in glyphString:
    401                 if w <= cursorXpos <= w + glyph[0]:
    402                     glyphCenter = glyph[0] / 2.0
    403                     if cursorXpos > w + glyphCenter:
    404                         return (i + 1, int(w + glyph[0]))
    405                     return (i, int(w))
    406                 i += 1
    407                 w += glyph[0]
    408 
    409             if cursorXpos > w:
    410                 return (i, int(w))
    411         return (0, 0)
    412 
    413     def GetWidthToIdx(self, node, idx, getWidth = 0):
    414         glyphString = self.GetLineGlyphString(node)
    415         if glyphString:
    416             if idx < len(glyphString):
    417                 w = sum([ t[0] for t in glyphString[:idx] ])
    418                 if getWidth:
    419                     return (int(w), int(glyphString[idx][0]))
    420                 else:
    421                     return int(w)
    422             else:
    423                 w = sum([ t[0] for t in glyphString ])
    424                 if getWidth:
    425                     return (int(w), 0)
    426                 else:
    427                     return int(w)
    428         if getWidth:
    429             return (0, 0)
    430         else:
    431             return 0
    432 
    433     @telemetry.ZONE_METHOD
    434     def GetFontPathFromFontFamily(self, fontFamily, fontStyle = None, bold = False, italic = False):
    435         fontPath = None
    436         if type(fontFamily) == dict:
    437             fontStyle = fontStyle or fontconst.STYLE_DEFAULT
    438             if fontStyle in fontFamily:
    439                 fontFamily = fontFamily[fontStyle]
    440             else:
    441                 fontFamily = fontFamily[fontconst.STYLE_DEFAULT]
    442         if type(fontFamily) in types.StringTypes:
    443             fontPath = fontFamily
    444         elif type(fontFamily) in (tuple, list):
    445             variation = bold * 2 + italic * 1
    446             if len(fontFamily) > variation:
    447                 fontPath = fontFamily[variation]
    448             else:
    449                 fontPath = fontFamily[0]
    450         return fontPath or ''
    451 
    452     @telemetry.ZONE_METHOD
    453     def ResolveFontFamily(self, params):
    454         if params.fontPath:
    455             params.font = params.fontPath
    456             return params.fontPath
    457         fontFamily = params.fontFamily
    458         if not fontFamily:
    459             fontFamily = self.GetFontFamilyBasedOnClientLanguageID()
    460         params.font = self.GetFontPathFromFontFamily(fontFamily, params.fontStyle, bool(params.bold), bool(params.italic))
    461 
    462     def GuessWindowsLanguageID(self, txt):
    463         if txt in self.textLanguageIDCache:
    464             return self.textLanguageIDCache[txt]
    465         matches = []
    466         for languageID, windowsLanguageID, regex, isExclusive in self.regexes:
    467             matchingLetters = len(txt) - len(regex.sub('', txt))
    468             if matchingLetters:
    469                 if isExclusive:
    470                     matches = [(matchingLetters, languageID, windowsLanguageID)]
    471                     break
    472                 else:
    473                     matches.append((matchingLetters, languageID, windowsLanguageID))
    474             if matchingLetters == len(txt):
    475                 break
    476 
    477         matches.sort()
    478         if matches:
    479             self.textLanguageIDCache[txt] = matches[-1][-1]
    480             return matches[-1][-1]
    481 
    482     def GetWindowsLanguageIDForText(self, txt):
    483         if txt in self.textLanguageIDCache:
    484             return self.textLanguageIDCache[txt]
    485         isLatinBased = self.IsLatinBased(txt)
    486         if isLatinBased:
    487             self.textLanguageIDCache[txt] = languageConst.LANG_ENGLISH
    488             return languageConst.LANG_ENGLISH
    489         guessedLanguageID = self.GuessWindowsLanguageID(txt)
    490         self.textLanguageIDCache[txt] = guessedLanguageID
    491         return guessedLanguageID
    492 
    493     def IsLatinBased(self, txt):
    494         for codepage in ('windows-1252', 'windows-1251', 'windows-1253', 'windows-1254', 'windows-1257', 'windows-1250'):
    495             try:
    496                 txt.encode(codepage)
    497                 return True
    498             except:
    499                 pass
    500 
    501     def GetFontDefault(self):
    502         family = self.GetFontFamilyBasedOnClientLanguageID()[fontconst.STYLE_DEFAULT]
    503         regular, bold, italic, boldItalic = family
    504         return regular
    505 
    506     @telemetry.ZONE_METHOD
    507     def GetFontFamilyBasedOnClientLanguageID(self):
    508         if session:
    509             languageID = session.languageID
    510         else:
    511             languageID = 'EN'
    512         if languageID == 'ZH':
    513             return self.GetFontFamilyBasedOnWindowsLanguageID(languageConst.LANG_CHINESE)
    514         elif languageID == 'JA':
    515             return self.GetFontFamilyBasedOnWindowsLanguageID(languageConst.LANG_JAPANESE)
    516         elif languageID == 'KO':
    517             return self.GetFontFamilyBasedOnWindowsLanguageID(languageConst.LANG_KOREAN)
    518         else:
    519             return self.GetFontFamilyBasedOnWindowsLanguageID(languageConst.LANG_ENGLISH)
    520 
    521     @telemetry.ZONE_METHOD
    522     def GetFontFamilyBasedOnWindowsLanguageID(self, windowsLanguageID):
    523         if fontconst.FONT_FAMILY_OVERRIDE:
    524             return fontconst.FONT_FAMILY_OVERRIDE
    525         if windowsLanguageID in fontconst.LANGUAGES_USING_EVE_FONT:
    526             return fontconst.FONTFAMILY_PER_WINDOWS_LANGUAGEID[languageConst.LANG_ENGLISH]
    527         return fontconst.FONTFAMILY_PER_WINDOWS_LANGUAGEID.get(windowsLanguageID, None)
    528 
    529     def GetCache(self, key):
    530         cache = self.measureCache.get(key, None)
    531         if cache:
    532             return cache[1]
    533 
    534     def SetCache(self, key, value):
    535         ts = blue.os.GetWallclockTime()
    536         self.measureCache[key] = (ts, value)
    537 
    538     def InvalidateTextMeasureCache(self):
    539         t = blue.os.GetWallclockTime()
    540         ks = self.measureCache.keys()
    541         for k in ks:
    542             cacheTime = self.measureCache[k][0]
    543             if t - cacheTime > 300000000:
    544                 del self.measureCache[k]
    545 
    546     def GetParams(self):
    547         fontParams = Bunch()
    548         fontParams.__doc__ = 'Font Parameters'
    549         fontParams.fontStyle = None
    550         fontParams.fontFamily = None
    551         fontParams.fontPath = None
    552         fontParams.fontsize = fontconst.DEFAULT_FONTSIZE
    553         fontParams.letterspace = fontconst.DEFAULT_LETTERSPACE or 0
    554         fontParams.uppercase = fontconst.DEFAULT_UPPERCASE
    555         fontParams.lineSpacing = 0.0
    556         fontParams.wordspace = 0
    557         fontParams.color = -1
    558         fontParams.underline = 0
    559         fontParams.bold = 0
    560         fontParams.italic = 0
    561         fontParams.face = None
    562         return fontParams
    563 
    564     def GetGlyphString(self):
    565         return Tr2GlyphString()
    566 
    567     def AppGetSetting(self, setting, default):
    568         try:
    569             return settings.public.ui.Get(setting, default)
    570         except (AttributeError, NameError):
    571             return default
    572 
    573     def AppSetSetting(self, setting, value):
    574         try:
    575             settings.public.ui.Set(setting, value)
    576         except (AttributeError, NameError):
    577             pass