Пример #1
0
class CornersRounder(BaseWindowController):

    layerNames = []
    targetLayerName = None
    sourceLayerName = None

    roundingsData = None

    selectedFont = None
    allFonts = None

    def __init__(self):
        super(CornersRounder, self).__init__()

        self._initLogger()
        self.rounderLogger.info('we are on air! start: __init__()')

        self._updateFontsAttributes()
        if self.allFonts != []:
            self.selectedFont = self.allFonts[0]

        self._initRoundingsData()
        if self.selectedFont is not None:
            self.layerNames = ['foreground'] + self.selectedFont.layerOrder
            self.sourceLayerName = self.layerNames[0]
            self.targetLayerName = self.layerNames[0]

            if PLUGIN_LIB_NAME in self.selectedFont.lib:
                self.roundingsData = pullRoundingsDataFromFont(
                    self.selectedFont)

        self.w = Window((0, 0, PLUGIN_WIDTH, PLUGIN_HEIGHT), PLUGIN_TITLE)

        jumpingY = MARGIN_VER
        self.w.fontPopUp = PopUpButton(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            [os.path.basename(item.path) for item in self.allFonts],
            callback=self.fontPopUpCallback)

        jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + MARGIN_VER
        self.w.sepLineOne = HorizontalLine(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['HorizontalLineThickness']))

        jumpingY += MARGIN_VER
        for eachI in range(LABELS_AMOUNT):
            singleLabel = Label((MARGIN_HOR, jumpingY, NET_WIDTH,
                                 vanillaControlsSize['EditTextRegularHeight']),
                                attachCallback=self.attachCallback,
                                labelName='')

            setattr(self.w, 'label{:d}'.format(eachI), singleLabel)
            jumpingY += MARGIN_ROW + vanillaControlsSize[
                'EditTextRegularHeight']
        self._fromRoundingsData2LabelCtrls()

        jumpingY += MARGIN_ROW
        self.w.sepLineTwo = HorizontalLine(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['HorizontalLineThickness']))
        jumpingY += MARGIN_ROW * 2

        # tables
        labelListWdt = 78
        marginTable = 1
        angleListWdt = (NET_WIDTH - labelListWdt - marginTable * 3) // 3
        tableLineHeight = 16
        tableHgt = LABELS_AMOUNT * tableLineHeight + 33

        captionY = jumpingY
        captionOffset = 12
        jumpingY += vanillaControlsSize['TextBoxSmallHeight'] + MARGIN_ROW

        labelColumnDesc = [{"title": "labelName", 'editable': True}]
        jumpingX = MARGIN_HOR

        self.w.labelNameList = List(
            (jumpingX, jumpingY, labelListWdt, tableHgt), [],
            columnDescriptions=labelColumnDesc,
            showColumnTitles=True,
            editCallback=self.labelNameListCallback,
            rowHeight=tableLineHeight,
            drawHorizontalLines=True,
            drawVerticalLines=True,
            autohidesScrollers=True,
            allowsMultipleSelection=False)

        anglesColumnDesc = [{
            "title": "rad",
            'editable': True
        }, {
            "title": "bcp",
            'editable': True
        }]

        jumpingX += labelListWdt + marginTable
        self.w.fortyFiveCaption = TextBox(
            (jumpingX + captionOffset, captionY, angleListWdt,
             vanillaControlsSize['TextBoxSmallHeight']),
            u'45°',
            sizeStyle='small')

        self.w.fortyFiveList = List(
            (jumpingX, jumpingY, angleListWdt, tableHgt), [],
            columnDescriptions=anglesColumnDesc,
            showColumnTitles=True,
            rowHeight=tableLineHeight,
            editCallback=self.fortyFiveListCallback,
            drawHorizontalLines=True,
            drawVerticalLines=True,
            autohidesScrollers=True,
            allowsMultipleSelection=False)

        jumpingX += angleListWdt + marginTable
        self.w.ninetyCaption = TextBox(
            (jumpingX + captionOffset, captionY, angleListWdt,
             vanillaControlsSize['TextBoxSmallHeight']),
            u'90°',
            sizeStyle='small')

        self.w.ninetyList = List((jumpingX, jumpingY, angleListWdt, tableHgt),
                                 [],
                                 columnDescriptions=anglesColumnDesc,
                                 showColumnTitles=True,
                                 rowHeight=tableLineHeight,
                                 editCallback=self.ninetyListCallback,
                                 drawHorizontalLines=True,
                                 drawVerticalLines=True,
                                 autohidesScrollers=True,
                                 allowsMultipleSelection=False)

        jumpingX += angleListWdt + marginTable
        self.w.hundredThirtyFiveCaption = TextBox(
            (jumpingX + captionOffset, captionY, angleListWdt,
             vanillaControlsSize['TextBoxSmallHeight']),
            u'135°',
            sizeStyle='small')

        self.w.hundredThirtyFiveList = List(
            (jumpingX, jumpingY, angleListWdt, tableHgt), [],
            columnDescriptions=anglesColumnDesc,
            showColumnTitles=True,
            rowHeight=tableLineHeight,
            editCallback=self.hundredThirtyFiveListCallback,
            drawHorizontalLines=True,
            drawVerticalLines=True,
            autohidesScrollers=True,
            allowsMultipleSelection=False)
        self._fromRoundingsData2Lists()
        jumpingY += tableHgt + MARGIN_ROW * 2

        rgtX = MARGIN_HOR + NET_WIDTH * .52
        midWdt = NET_WIDTH * .48
        self.w.pushButton = SquareButton(
            (MARGIN_HOR, jumpingY, midWdt,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Push Data',
            callback=self.pushButtonCallback)
        self.w.clearLibButton = SquareButton(
            (rgtX, jumpingY, midWdt,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Clear Lib',
            callback=self.clearLibButtonCallback)

        jumpingY += vanillaControlsSize[
            'ButtonRegularHeight'] * 1.5 + MARGIN_ROW * 2
        self.w.sepLineThree = HorizontalLine(
            (MARGIN_HOR, jumpingY, NET_WIDTH,
             vanillaControlsSize['HorizontalLineThickness']))
        jumpingY += MARGIN_ROW * 2

        self.w.sourceLayerCaption = TextBox(
            (MARGIN_HOR, jumpingY, midWdt,
             vanillaControlsSize['TextBoxRegularHeight']), 'source layer')
        self.w.targetLayerCaption = TextBox(
            (rgtX, jumpingY, midWdt,
             vanillaControlsSize['TextBoxRegularHeight']), 'target layer')

        jumpingY += vanillaControlsSize['TextBoxRegularHeight'] + MARGIN_ROW
        self.w.sourceLayerPopUp = PopUpButton(
            (MARGIN_HOR, jumpingY, midWdt,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            self.layerNames,
            callback=self.sourceLayerPopUpCallback)
        if self.layerNames and self.sourceLayerName:
            self.w.sourceLayerPopUp.set(
                self.layerNames.index(self.sourceLayerName))

        self.w.targetLayerCombo = ComboBox(
            (rgtX, jumpingY, midWdt,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            self.layerNames,
            callback=self.targetLayerComboCallback)
        if self.layerNames and self.targetLayerName:
            self.w.targetLayerCombo.set(self.targetLayerName)

        jumpingY += vanillaControlsSize[
            'PopUpButtonRegularHeight'] + MARGIN_ROW * 4
        self.w.roundGlyphButton = SquareButton(
            (MARGIN_HOR, jumpingY, midWdt,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            u'Round Glyph (⌘+R)',
            callback=self.roundGlyphButtonCallback)
        self.w.roundGlyphButton.bind('r', ['command'])

        self.w.roundFontButton = SquareButton(
            (rgtX, jumpingY, midWdt,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Round Font',
            callback=self.roundFontButtonCallback)
        jumpingY += vanillaControlsSize[
            'ButtonRegularHeight'] * 1.5 + MARGIN_VER * 2

        self.w.resize(PLUGIN_WIDTH, jumpingY)

        self._checkPushButton()
        self._checkRoundButtons()
        self.setUpBaseWindowBehavior()
        addObserver(self, 'fontDidOpenCallback', 'fontDidOpen')
        addObserver(self, 'fontDidCloseCallback', 'fontDidClose')
        addObserver(self, '_keyDown', 'keyDown')
        self.w.open()

    # private methods
    def _initLogger(self):
        # create a logger
        self.rounderLogger = logging.getLogger('rounderLogger')
        self.rounderLogger.setLevel(logging.INFO)
        # create file handler which logs info messages
        fileHandle = logging.FileHandler('cornersRounderLogger.log')
        fileHandle.setLevel(logging.INFO)
        # create console handler with a higher log level, only errors
        # create formatter and add it to the handlers
        formatter = logging.Formatter(u'%(asctime)s – %(message)s')
        fileHandle.setFormatter(formatter)
        # add the handlers to the logger
        self.rounderLogger.addHandler(fileHandle)

    def _initRoundingsData(self):
        self.roundingsData = [
            makeRoundingsDataEmptyDict() for ii in range(LABELS_AMOUNT)
        ]

    def _updateFontsAttributes(self):
        self.allFonts = AllFonts()
        if self.allFonts == []:
            self.selectedFont = None

    def _updateRoundingsLabels(self, labels):
        for indexLabel, eachLabel in enumerate(labels):
            self.roundingsData[indexLabel]['labelName'] = '{}'.format(
                eachLabel['labelName'])

    def _updateRoundingsNumbers(self, data, keyStart):
        for indexRow, eachRow in enumerate(data):

            try:
                self.roundingsData[indexRow]['{}Rad'.format(keyStart)] = int(
                    eachRow['rad'])
            except ValueError:
                self.roundingsData[indexRow]['{}Rad'.format(keyStart)] = ''

            try:
                self.roundingsData[indexRow]['{}Bcp'.format(keyStart)] = int(
                    eachRow['bcp'])
            except ValueError:
                self.roundingsData[indexRow]['{}Bcp'.format(keyStart)] = ''

    def _fromRoundingsData2LabelCtrls(self):
        for eachI in range(LABELS_AMOUNT):
            try:
                labelName = self.roundingsData[eachI]['labelName']
            except IndexError as e:
                labelName = ''
            getattr(self.w, 'label{:d}'.format(eachI)).setLabelName(labelName)

    def _updateLayersCtrls(self):
        self.layerNames = ['foreground'] + self.selectedFont.layerOrder

        currentName = self.w.sourceLayerPopUp.getItems()[
            self.w.sourceLayerPopUp.get()]
        self.w.sourceLayerPopUp.setItems(self.layerNames)
        if currentName in self.layerNames:
            self.w.sourceLayerPopUp.set(self.layerNames.index(currentName))

        currentName = self.w.targetLayerCombo.get()
        self.w.targetLayerCombo.setItems(self.layerNames)
        if currentName in self.layerNames:
            self.w.targetLayerCombo.set(currentName)

    def _extractDataFromRoundings(self, keyTitle):
        listData = [{
            'rad': aDict['{}Rad'.format(keyTitle)],
            'bcp': aDict['{}Bcp'.format(keyTitle)]
        } for aDict in self.roundingsData]
        return listData

    def _fromRoundingsData2Lists(self):
        labelNameListData = [{
            'labelName': aDict['labelName']
        } for aDict in self.roundingsData]
        self.w.labelNameList.set(labelNameListData)
        fortyFiveListData = self._extractDataFromRoundings('fortyFive')
        self.w.fortyFiveList.set(fortyFiveListData)
        ninetyListData = self._extractDataFromRoundings('ninety')
        self.w.ninetyList.set(ninetyListData)
        hundredThirtyFiveListData = self._extractDataFromRoundings(
            'hundredThirtyFive')
        self.w.hundredThirtyFiveList.set(hundredThirtyFiveListData)

    def _roundCurrentGlyph(self):
        if self.sourceLayerName == self.targetLayerName:
            self.showMessage(
                'ERROR',
                u'source layer name and target layer name should be different')
            return None

        currentGlyph = CurrentGlyph()
        if currentGlyph is not None:
            self.rounderLogger.info(
                'start: _roundCurrentGlyph(), glyph {} from {} {}'.format(
                    currentGlyph.name, self.selectedFont.info.familyName,
                    self.selectedFont.info.styleName))
            selectedFont = currentGlyph.getParent()
            roundingsData = pullRoundingsDataFromFont(selectedFont)

            if roundingsData is not None:
                makeGlyphRound(currentGlyph,
                               roundingsData,
                               sourceLayerName=self.sourceLayerName,
                               targetLayerName=self.targetLayerName)
                UpdateCurrentGlyphView()
                self.rounderLogger.info(
                    'end: _roundCurrentGlyph(), glyph {} from {} {}'.format(
                        currentGlyph.name, selectedFont.info.familyName,
                        selectedFont.info.styleName))
        elif currentGlyph is not None:
            self.showMessage('ERROR', NO_DATA_INTO_FONT)
            self.rounderLogger.error(NO_DATA_INTO_FONT)

        else:
            self.showMessage('ERROR', NO_GLYPH_TO_ROUND)
            self.rounderLogger.error(NO_GLYPH_TO_ROUND)

    # observers callbacks
    def _keyDown(self, notification):
        glyph = notification['glyph']
        pressedKeys = notification['event'].charactersIgnoringModifiers()
        modifierFlags = notification['event'].modifierFlags()
        if modifierFlags in MODIFIERS and MODIFIERS[
                modifierFlags] == 'CMD_LEFT' and pressedKeys == 'r':
            self._roundCurrentGlyph()

    def fontDidOpenCallback(self, notification):
        if self.allFonts != []:
            currentName = os.path.basename(
                self.allFonts[self.w.fontPopUp.get()].path)
        else:
            currentName = None
        self._updateFontsAttributes()
        newNames = [os.path.basename(item.path) for item in self.allFonts]
        self.w.fontPopUp.setItems(newNames)

        if currentName is not None:
            self.w.fontPopUp.set(newNames.index(currentName))

    def fontDidCloseCallback(self, notification):
        self._updateFontsAttributes()
        newNames = [os.path.basename(item.path) for item in self.allFonts]
        self.w.fontPopUp.setItems(newNames)

        if self.allFonts != []:
            self.selectedFont = self.allFonts[0]
            self.roundingsData = pullRoundingsDataFromFont(self.selectedFont)
            if self.roundingsData is None:
                self._initRoundingsData()
            self._fromRoundingsData2Lists()
            self._fromRoundingsData2LabelCtrls()
        else:
            self.selectedFont = None
            self.roundingsData = None

    def windowCloseCallback(self, sender):
        removeObserver(self, 'fontDidOpen')
        removeObserver(self, 'fontDidClose')
        removeObserver(self, 'keyDown')
        self.rounderLogger.info('that\'s all folks!')

    # standard callbacks
    def fontPopUpCallback(self, sender):
        self.selectedFont = self.allFonts[sender.get()]
        self.roundingsData = pullRoundingsDataFromFont(self.selectedFont)
        if self.roundingsData is None:
            self._initRoundingsData()
        self._fromRoundingsData2Lists()
        self._fromRoundingsData2LabelCtrls()
        self._updateLayersCtrls()
        self._checkPushButton()

    def attachCallback(self, sender):
        labelName = sender.get()
        attachLabelToSelectedPoints(labelName)

    def _checkPushButton(self):
        if hasattr(self.w, 'pushButton') is True:
            if PLUGIN_LIB_NAME not in self.selectedFont.lib or self.roundingsData != self.selectedFont.lib[
                    PLUGIN_LIB_NAME]:
                self.w.pushButton.enable(True)
            else:
                self.w.pushButton.enable(False)

    def labelNameListCallback(self, sender):
        self._updateRoundingsLabels(sender.get())
        self._checkPushButton()
        self._checkRoundButtons()

    def _checkRoundButtons(self):
        if hasattr(self.w, 'roundGlyphButton') is True and hasattr(
                self.w, 'roundFontButton'):
            if (PLUGIN_LIB_NAME in self.selectedFont.lib and self.roundingsData
                    != self.selectedFont.lib[PLUGIN_LIB_NAME]
                ) or self.roundingsData == DUMMY_ROUNDINGS:
                self.w.roundGlyphButton.enable(False)
                self.w.roundFontButton.enable(False)
            else:
                self.w.roundGlyphButton.enable(True)
                self.w.roundFontButton.enable(True)

    def fortyFiveListCallback(self, sender):
        self._updateRoundingsNumbers(sender.get(), 'fortyFive')
        self._checkPushButton()
        self._checkRoundButtons()

    def ninetyListCallback(self, sender):
        self._updateRoundingsNumbers(sender.get(), 'ninety')
        self._checkPushButton()
        self._checkRoundButtons()

    def hundredThirtyFiveListCallback(self, sender):
        self._updateRoundingsNumbers(sender.get(), 'hundredThirtyFive')
        self._checkPushButton()
        self._checkRoundButtons()

    def pushButtonCallback(self, sender):
        thisFont = self.selectedFont
        if thisFont is not None:
            pushRoundingsDataIntoFont(thisFont, self.roundingsData)
            self.showMessage(
                'INFO',
                DATA_PUSHED.format(familyName=thisFont.info.familyName,
                                   styleName=thisFont.info.styleName))
            self.rounderLogger.info(
                DATA_PUSHED.format(familyName=thisFont.info.familyName,
                                   styleName=thisFont.info.styleName))
        else:
            self.showMessage('ERROR', NO_FONT_TO_PUSH)
            self.rounderLogger.error(NO_FONT_TO_PUSH)
        self._checkPushButton()
        self._checkRoundButtons()

    def clearLibButtonCallback(self, sender):
        if PLUGIN_LIB_NAME in self.selectedFont.lib:
            del self.selectedFont.lib[PLUGIN_LIB_NAME]
        self._initRoundingsData()
        self._fromRoundingsData2Lists()
        self._fromRoundingsData2LabelCtrls()
        self._checkPushButton()
        self._checkRoundButtons()

    def sourceLayerPopUpCallback(self, sender):
        self.sourceLayerName = self.layerNames[sender.get()]

    def targetLayerComboCallback(self, sender):
        self.targetLayerName = sender.get()

    def roundGlyphButtonCallback(self, sender):
        if self.selectedFont == CurrentFont():
            self._roundCurrentGlyph()
        else:
            self.showMessage('ERROR', FONT_MISMATCH)
            self.rounderLogger.error(FONT_MISMATCH)

    def roundFontButtonCallback(self, sender):
        if self.sourceLayerName == self.targetLayerName:
            self.showMessage('ERROR', LAYERS_MATCH)
            self.rounderLogger.error(LAYERS_MATCH)
            return None

        if self.selectedFont == CurrentFont():
            self.rounderLogger.info(
                'start: roundFontButtonCallback(), {} {}'.format(
                    selectedFont.info.familyName, selectedFont.info.styleName))
            for eachGlyph in self.selectedFont:
                makeGlyphRound(eachGlyph,
                               self.roundingsData,
                               sourceLayerName=self.sourceLayerName,
                               targetLayerName=self.targetLayerName)
            self.rounderLogger.info(
                'end: roundFontButtonCallback(), {} {}'.format(
                    selectedFont.info.familyName, selectedFont.info.styleName))

        else:
            self.showMessage('ERROR', FONT_MISMATCH)
            self.rounderLogger.error(FONT_MISMATCH)
Пример #2
0
class VisualReporter(BaseWindowController):

    fontNames = []

    def __init__(self):
        super(VisualReporter, self).__init__()

        self.allFonts = AllFonts()
        self.sortAllFonts()
        self.collectFontNames()

        self.w = Window((0, 0, PLUGIN_WIDTH, 1), PLUGIN_TITLE)

        jumpingY = UI_MARGIN
        self.w.fontsPopUp = PopUpButton(
            (UI_MARGIN, jumpingY, NET_WIDTH,
             vanillaControlsSize['PopUpButtonRegularHeight']),
            self.fontNames,
            callback=None)

        jumpingY += vanillaControlsSize['PopUpButtonRegularHeight'] + UI_MARGIN
        self.w.reportButton = SquareButton(
            (UI_MARGIN, jumpingY, NET_WIDTH,
             vanillaControlsSize['ButtonRegularHeight'] * 1.5),
            'Generate PDF Report',
            callback=self.reportButtonCallback)
        jumpingY += vanillaControlsSize['ButtonRegularHeight'] * 1.5 + UI_MARGIN

        self.setUpBaseWindowBehavior()
        addObserver(self, 'updateFontOptions', "newFontDidOpen")
        addObserver(self, 'updateFontOptions', "fontDidOpen")
        addObserver(self, 'updateFontOptions', "fontWillClose")
        self.w.bind("close", self.windowCloseCallback)
        self.w.resize(PLUGIN_WIDTH, jumpingY)
        self.w.open()

    def collectFontNames(self):
        self.fontNames = ['All Fonts'] + [
            os.path.basename(item.path) for item in self.allFonts
        ]

    def updateFontOptions(self, notification):
        self.allFonts = AllFonts()
        self.sortAllFonts()
        self.collectFontNames()
        previousFontName = self.w.fontsPopUp.get()
        self.w.fontsPopUp.setItems(self.fontNames)
        if previousFontName in self.fontNames:
            self.w.fontsPopUp.set(self.fontNames.index(previousFontName))

    def sortAllFonts(self):
        self.allFonts = sorted(
            self.allFonts, key=lambda x: STYLES_ORDER.index(x.info.styleName))

    def reportButtonCallback(self, sender):
        popUpIndex = self.w.fontsPopUp.get()
        if popUpIndex == 0:  # all fonts
            fontsToProcess = self.allFonts
        else:
            fontsToProcess = [self.allFonts[popUpIndex]]

        PDF_folder = getFolder('Choose where to save the reports')[0]
        templateFont = OpenFont(TEMPLATE_FONT_PATH, showUI=False)
        justNow = datetime.now()

        self._drawReport(
            referenceFont=templateFont,
            someFonts=fontsToProcess,
            glyphNames=templateFont.glyphOrder,
            reportPath=os.path.join(
                PDF_folder,
                '{:0>4d}{:0>2d}{:0>2d}_templateCompliant.pdf'.format(
                    justNow.year, justNow.month, justNow.day)),
            caption='Template Compliant')

        allGlyphNames = set()
        for eachFont in fontsToProcess:
            allGlyphNames = allGlyphNames | set(eachFont.glyphOrder)
        leftovers = allGlyphNames - set(templateFont.glyphOrder)

        self._drawReport(referenceFont=templateFont,
                         someFonts=fontsToProcess,
                         glyphNames=leftovers,
                         reportPath=os.path.join(
                             PDF_folder,
                             '{:0>4d}{:0>2d}{:0>2d}_extraTemplate.pdf'.format(
                                 justNow.year, justNow.month, justNow.day)),
                         caption='Extra Template')

    def windowCloseCallback(self, sender):
        removeObserver(self, "newFontDidOpen")
        removeObserver(self, "fontDidOpen")
        removeObserver(self, "fontWillClose")
        super(VisualReporter, self).windowCloseCallback(sender)

    # drawing routines
    def _initPage(self, fontStyles):
        db.newPage('A3Landscape')
        db.fontSize(BODY_SIZE_TEXT)
        quota = db.height() - PDF_MARGIN
        self._drawHeader(quota, fontStyles)
        db.font('LucidaGrande')
        quota -= TAB_LINE_HEIGHT * 2
        return quota

    def _drawHeader(self, quota, fontStyles):
        fontTitles = {}

        colIndex = int(COLS['template'] / TAB_WIDTH)
        for eachFontStyle in fontStyles:
            colIndex += 1
            fontTitles[eachFontStyle] = colIndex * TAB_WIDTH

        headers = {k: v for (k, v) in COLS.items() + fontTitles.items()}
        db.font('LucidaGrande-Bold')
        for eachTitle, eachX in headers.items():
            db.text(eachTitle, (PDF_MARGIN + eachX, quota))

    def _drawReport(self, referenceFont, someFonts, glyphNames, reportPath,
                    caption):
        assert isinstance(reportPath, str) or isinstance(
            reportPath, unicode), 'this should be a string or unicode'
        assert isinstance(someFonts, list), 'this should be a list of RFont'

        prog = ProgressWindow(text='{}: drawing glyphs...'.format(caption),
                              tickCount=len(glyphNames))

        try:
            db.newDrawing()

            twoLinesFontStyles = [
                ff.info.styleName.replace(' ', '\n') for ff in someFonts
            ]
            quota = self._initPage(twoLinesFontStyles)
            for indexName, eachGlyphName in enumerate(glyphNames):
                db.save()
                db.translate(PDF_MARGIN, quota)

                # set name
                for eachSetName, eachGroup in SMART_SETS:
                    if eachGlyphName in eachGroup:
                        setName = eachSetName[3:].replace('.txt', '').replace(
                            '_', ' ')
                        break
                else:
                    setName = ''
                db.text(setName, (COLS['set name'], 0))

                # line number
                db.fill(*BLACK)
                db.text('{:0>4d}'.format(indexName), (COLS['line'], 0))

                # unicode hex
                if eachGlyphName in referenceFont and referenceFont[
                        eachGlyphName].unicode:
                    uniIntValue = referenceFont[eachGlyphName].unicode
                elif eachGlyphName in someFonts[0] and someFonts[0][
                        eachGlyphName].unicode:
                    uniIntValue = someFonts[0][eachGlyphName].unicode
                else:
                    uniIntValue = None

                if uniIntValue:
                    uniHexValue = 'U+{:04X}'.format(uniIntValue)
                    db.fill(*BLACK)
                    db.text(uniHexValue, (COLS['unicode'], 0))

                # os char
                if uniIntValue:
                    txt = db.FormattedString()
                    txt.fontSize(BODY_SIZE_GLYPH)
                    txt.fill(*GRAY)
                    txt += 'H'
                    txt.fill(*BLACK)
                    txt += unichr(uniIntValue)
                    txt.fill(*GRAY)
                    txt += 'p'
                    db.text(txt, (COLS['char'], 0))

                # glyphname
                db.fontSize(BODY_SIZE_TEXT)
                db.text(eachGlyphName, (COLS['glyph name'], 0))

                # glyphs
                db.translate(COLS['template'], 0)
                for eachFont in [referenceFont] + someFonts:
                    if eachGlyphName in eachFont:
                        eachGlyph = eachFont[eachGlyphName]
                        lftRefGL = eachFont['H']
                        rgtRefGL = eachFont['p']

                        db.save()
                        db.scale(BODY_SIZE_GLYPH / eachFont.info.unitsPerEm)
                        db.fill(*GRAY)
                        db.drawGlyph(lftRefGL)
                        db.translate(lftRefGL.width, 0)
                        db.fill(*BLACK)
                        db.drawGlyph(eachGlyph)
                        db.translate(eachGlyph.width, 0)
                        db.fill(*GRAY)
                        db.drawGlyph(rgtRefGL)
                        db.restore()
                    db.translate(TAB_WIDTH, 0)
                db.restore()
                prog.update()

                quota -= TAB_LINE_HEIGHT
                if quota <= PDF_MARGIN:
                    quota = self._initPage(twoLinesFontStyles)

            prog.setTickCount(value=None)
            prog.update(text='{}: saving PDF...'.format(caption))
            db.saveImage(reportPath)
            db.endDrawing()

        except Exception as error:
            prog.close()
            raise error

        prog.close()