def updateFamily(self, currentItem, previousItem): """Update the family edit box and adjust the style and size options. Arguments: currentItem -- the new list widget family item previousItem -- the previous list widget item """ family = currentItem.text() self.familyEdit.setText(family) if self.familyEdit.hasFocus(): self.familyEdit.selectAll() prevStyle = self.styleEdit.text() prevSize = self.sizeEdit.text() fontDb = QFontDatabase() styles = [style for style in fontDb.styles(family)] self.styleList.clear() self.styleList.addItems(styles) if prevStyle: try: num = styles.index(prevStyle) except ValueError: num = 0 self.styleList.setCurrentRow(num) self.styleList.scrollToItem(self.styleList.currentItem()) sizes = [repr(size) for size in fontDb.pointSizes(family)] self.sizeList.clear() self.sizeList.addItems(sizes) if prevSize: try: num = sizes.index(prevSize) except ValueError: num = 0 self.sizeList.setCurrentRow(num) self.sizeList.scrollToItem(self.sizeList.currentItem()) self.updateSample()
def findStyles(self, font): fontDatabase = QFontDatabase() currentItem = self.styleCombo.currentText() self.styleCombo.clear() for style in fontDatabase.styles(font.family()): self.styleCombo.addItem(style) styleIndex = self.styleCombo.findText(currentItem) if styleIndex == -1: self.styleCombo.setCurrentIndex(0) else: self.styleCombo.setCurrentIndex(styleIndex)
def __init__(self): super(MainWindow, self).__init__() font_id = QFontDatabase.addApplicationFont("fontawesome-webfont.ttf") if font_id is not -1: font_db = QFontDatabase() self.font_styles = font_db.styles('FontAwesome') self.font_families = QFontDatabase.applicationFontFamilies(font_id) print(self.font_styles, self.font_families) for font_family in self.font_families: self.font = font_db.font(font_family, self.font_styles[0], 18) self.home()
class TextFonts(QObject): """Provide information about available text fonts. These are exactly the fonts that can be seen by LilyPond. This is only produced upon request but then stored permanently during the program's runtime. load_fonts() will run LilyPond to determine the list of fonts, optionally reporting to a log.Log widget if given. Since this is an asynchronous process GUI elements that want to use the results have to connect to the 'loaded' signal which is emitted after LilyPond has completed and the results been parsed. A Fonts() object is immediately available as fonts.available_fonts, and its is_loaded member can be requested to test if fonts have already been loaded. """ loaded = signals.Signal() def __init__(self, lilypond_info): super(TextFonts, self).__init__() self.lilypond_info = lilypond_info self._tree_model = FontTreeModel(self) self._misc_model = MiscTreeModel(self) self.job = None self.load_fonts() def reset(self, log_widget=None): self._log = [] self._tree_model.reset() self._misc_model.reset() # needs to be reset for the LilyPond-dependent fonts self.font_db = QFontDatabase() self._is_loaded = False def log(self): return self._log def acknowledge_lily_fonts(self): """Add the OpenType fonts in LilyPond's font directory to Qt's font database. This should be relevant (untested) when the fonts are not additionally installed as system fonts.""" # TODO: Move the the filtering here. # It's not correct to first add the notation fonts to the font debug # only to filter them again later. Besides, there might be other valid # fonts caught by the filter. font_dir = os.path.join(self.lilypond_info.datadir(), 'fonts', 'otf') for lily_font in os.listdir(font_dir): self.font_db.addApplicationFont(os.path.join(font_dir, lily_font)) def add_style_to_family(self, families, family_name, input): """Parse a font face definition provided by LilyPond. There is some guesswork involved since there may be discrepancies between the fonts/styles reported by LilyPond and those available in QFontDatabase. To discuss this the function is heavily commented. See also http://lists.gnu.org/archive/html/lilypond-user/2018-07/msg00338.html """ def un_camel(style): """ Try to 'fix' a class of errors when LilyPond reports e.g. 'BoldItalic' instead of 'Bold Italic'. It is unclear if this actually fixes the source of the issue or just one arbitrary example. """ # The following regular expression would be a 'proper' # un-camel-ing, but this seems not to be relevant. #un_cameled = re.sub('([^ ])([A-Z][a-z])', r'\1 \2', style) #return re.sub('([A-Za-z])([0-9])', r'\1 \2', un_cameled) return ("Bold Italic" if style == "BoldItalic" else style) if not family_name in families.keys(): families[family_name] = {} family = families[family_name] input = input.strip().split(':') # This is a safeguard against improper entries if len(input) == 2: # The first segment has always one or two entries: # - The font family name # - The font subfamily name if it differs from the base name. # Therefore the series is always the *last* entry in the list. # We "unescape" hyphens because this escape is not necessary # for our purposes. sub_family = input[0].split(',')[-1].replace('\\-', '-') if not sub_family in family.keys(): family[sub_family] = [] qt_styles = self.font_db.styles(sub_family) lily_styles = input[1][6:].split(',') match = '' if not qt_styles: # In some cases Qt does *not* report available styles. # In these cases it seems correct to use the style reported # by LilyPond. In very rare cases it seems possible that # LilyPond reports multiple styles for such fonts, and for now # we have to simply ignore these cases so we take the first # or single style. match = un_camel(lily_styles[0]) else: # Match LilyPond's reported styles with those reported by Qt. # We need to un-camel the LilyPond-reported style name, but # this may not be a final fix (see comment to un_camel()). # If no match is found we simply hope that the style # reported by LilyPond will do. for style in lily_styles: style = un_camel(style) if style in qt_styles: match = style break if not match: match = un_camel(lily_styles[0]) if not match in family[sub_family]: family[sub_family].append(match) else: pass # TODO: issue a warning? # In my examples *some* fonts were parsed improperly # and therefore skipped. # I *think* this happens at the stage of splitting the # LilyPond log into individual lines. #print("Error when parsing font entry:") #print(name) #print(input) def flatten_log(self): """Flatten job history into flat string list.""" for line in self.job.history(): # lines in Job.history() are tuples of text and type, # we're only interested in the text. lines = line[0].split('\n') for l in lines: self._log.append(l) def is_loaded(self): return self._is_loaded def load_fonts(self, log_widget=None): """Run LilyPond to retrieve a list of available fonts. Afterwards process_results() will parse the output and build info structures to be used later. If a log.Log widget is passed as second argument this will be connected to the Job to provide realtime feedback on the process. Any caller should connect to the 'loaded' signal because this is an asynchronous task that takes long to complete.""" self.reset() self.acknowledge_lily_fonts() self.run_lilypond(log_widget) def misc_model(self): return self._misc_model def model(self): return self._tree_model def parse_entries(self): """Parse the LilyPond log and push entries to the various lists and dictionaries. Parsing the actual font style definition is deferred to add_style_to_family().""" regexp = re.compile('(.*)\\-\d+') families = {} config_files = [] config_dirs = [] font_dirs = [] last_family = None for e in self._log: if e.startswith('family'): # NOTE: output of this process is always English, # so we can hardcode the splice indices original_family = e[7:] # filter size-indexed font families basename = regexp.match(original_family) last_family = basename.groups( )[0] if basename else original_family elif last_family: # We're in the second line of a style definition if not last_family.endswith('-brace'): self.add_style_to_family(families, last_family, e) last_family = None elif e.startswith('Config files:'): config_files.append(e[14:]) elif e.startswith('Font dir:'): font_dirs.append(e[10:]) elif e.startswith('Config dir:'): config_dirs.append(e[12:]) return families, config_files, config_dirs, font_dirs def process_results(self): """Parse the job history list to dictionaries.""" self.flatten_log() families, config_files, config_dirs, font_dirs = self.parse_entries() self._tree_model.populate(families) self._misc_model.populate(config_files, config_dirs, font_dirs) self._is_loaded = True self.job = None self.loaded.emit() def run_lilypond(self, log_widget=None): """Run lilypond from info with the args list, and a job title.""" # TODO: Use the global JobQueue info = self.lilypond_info j = self.job = job.Job([info.abscommand() or info.command] + ['-dshow-available-fonts']) j.set_title(_("Available Fonts")) j.done.connect(self.process_results) if log_widget: log_widget.connectJob(j) j.start()
class GFonts(QtWidgets.QMainWindow): def __init__(self): super(GFonts, self).__init__() uic.loadUi('gfonts.ui', self) self.fTool = gFontsTool() self.threadPool = QtCore.QThreadPool() self.DDFamily.currentIndexChanged.connect(self.familySelected) self.DDWeight.currentIndexChanged.connect(self.weightSelected) self.show() worker = bgProc(self.fTool.getMetadata) worker.signals.finished.connect(self.mdLoaded) self.threadPool.start(worker) self.statusBar().showMessage('Loading metadata...') def mdLoaded(self): # metadata loaded handler self.DDFamily.addItems(self.fTool.getFamilies()) self.statusBar().showMessage('Loading metadata... Done') def familySelected(self, index): selName = self.DDFamily.currentText() self.DDWeight.clear() self.DDWeight.addItems(self.fTool.getWeights(selName)) def weightSelected(self, index): selName = self.DDFamily.currentText() selWeight = self.DDWeight.currentText() worker = bgProc(self.fTool.getCSS, selName, selWeight) worker.signals.result.connect(self.cssLoaded) self.threadPool.start(worker) self.statusBar().showMessage('Retrieving CSS') def cssLoaded(self, css): print('CSS', css) pcss = self.fTool.getParsedCSS(css) au = self.fTool.getFontAllURIs(pcss) fancyName = self.fTool.getFontFullNames(pcss.rules) sName = self.fTool.selectSimplestName(fancyName) worker = bgProc(self.fTool.getFontBitStreams, au) worker.signals.result.connect(self.bsLoaded) self.threadPool.start(worker) self.statusBar().showMessage('Retrieving font') def bsLoaded(self, bs): self.statusBar().showMessage('Font loaded') key = list(bs.keys())[0] self.ttf = QtCore.QByteArray() bio = QtCore.QBuffer(self.ttf) bio.open(QtCore.QIODevice.WriteOnly) self.fTool.mergeBitStreams(bs[key], bio) self.statusBar().showMessage('Font merged') self.fdb = QFontDatabase() insertion = self.fdb.addApplicationFontFromData(self.ttf) print('Insertion:', insertion) if insertion == -1: print('Failed to insert font into database') fName = key[0] fWeight = key[1] print('Converted bitstream to ttf for:', fName, fWeight) print('Bitstream keys:', bs.keys()) if type(fWeight) == str and fWeight[-1] == 'i': fWeight = fWeight[:-1] italic = True else: italic = False styles = self.fdb.styles(fName) print('styles', styles) print(fWeight, italic) for s in styles: print('s:', s) print('s weight:', self.fdb.weight(fName, s)) print('s italic:', self.fdb.italic(fName, s)) if self.fdb.weight(fName, s) == self.fTool.CSS2QtFontWeight( fWeight) and self.fdb.italic(fName, s) == italic: thisStyle = s break else: thisStyle = None print(thisStyle) font = self.fdb.font(fName, thisStyle, 40) self.SampleText.setFont(font)