Exemplo n.º 1
0
    def fix(self):
        if 'DSIG' in self.font:
            return False

        try:
            from fontTools.ttLib.tables.D_S_I_G_ import SignatureRecord
        except ImportError:
            error_message = ("The '{}' font does not have an existing"
                             " digital signature proving its authenticity,"
                             " so Fontbakery needs to add one. To do this"
                             " requires version 2.3 or later of Fonttools"
                             " to be installed. Please upgrade at"
                             " https://pypi.python.org/pypi/FontTools/2.4")
            logger.error(error_message.format(os.path.basename(self.fontpath)))
            return False


        newDSIG = ttLib.newTable("DSIG")
        newDSIG.ulVersion = 1
        newDSIG.usFlag = 1
        newDSIG.usNumSigs = 1
        sig = SignatureRecord()
        sig.ulLength = 20
        sig.cbSignature = 12
        sig.usReserved2 = 0
        sig.usReserved1 = 0
        sig.pkcs7 = '\xd3M4\xd3M5\xd3M4\xd3M4'
        sig.ulFormat = 1
        sig.ulOffset = 20
        newDSIG.signatureRecords = [sig]
        self.font.tables["DSIG"] = newDSIG
        return True
Exemplo n.º 2
0
    def getOrCreateNameRecord(self, nameId, val):
        logger.error('NAMEID {}: "{}"'.format(nameId, val))
        return

        result_namerec = None
        for k, p in [[1, 0], [3, 1]]:
            result_namerec = self.font['name'].getName(nameId, k, p)
            if result_namerec:
                result_namerec.string = (val or '').encode(
                    result_namerec.getEncoding())

        if result_namerec:
            return result_namerec

        ot_namerecord = NameRecord()
        ot_namerecord.nameID = nameId
        ot_namerecord.platformID = 3
        ot_namerecord.langID = 0x409
        # When building a Unicode font for Windows, the platform ID
        # should be 3 and the encoding ID should be 1
        ot_namerecord.platEncID = 1
        ot_namerecord.string = (val or '').encode(ot_namerecord.getEncoding())

        self.font['name'].names.append(ot_namerecord)
        return ot_namerecord
Exemplo n.º 3
0
    def addFailure(self, test, err):
        super(BakeryTestResult, self).addFailure(test, err)

        import copy
        failTest = copy.copy(test)
        _, _err_exception, _ = err
        failTest._err = err
        failTest._err_msg = _err_exception.message

        logger.error(self._format_test_output(failTest, 'FAIL'))

        test_method = getattr(test, test._testMethodName)

        if hasattr(test_method, 'autofix'):
            if not self.restart:
                self.callmethod(test_method.autofix_method, failTest)
                if hasattr(self.fl, 'append'):
                    self.fl.append(failTest)
                    self.restart = True  # mark next test as restarted
                    test.run(result=self)
                    self.restart = False  # reset restart state
                    return
            else:
                if hasattr(self.ff, 'append'):
                    self.ff.append(failTest)
        elif hasattr(self.fl, 'append'):
            self.fl.append(failTest)
Exemplo n.º 4
0
def run(command, cwd=None):
    """ Wrapper for subprocess.Popen with custom logging support.

        :param command: shell command to run, required
        :param cwd: - current working dir, required
        :param log: - logging object with .write() method, required

    """
    # Start the command
    env = os.environ.copy()

    logger.info('$ %s\n' %
                command.replace(os_origin.getcwd() + os.path.sep, ''))
    env.update({'PYTHONPATH': os_origin.pathsep.join(sys.path)})
    process = subprocess.Popen(command,
                               shell=True,
                               cwd=cwd or os_origin.getcwd(),
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               close_fds=True,
                               env=env)
    out, err = process.communicate()
    if out.strip():
        logger.info(out.strip())
    if err.strip():
        logger.error(err.strip())
    return out
Exemplo n.º 5
0
    def fix(self, value=15):
        if 'gasp' not in self.font.tables:
            logger.error('no table gasp')
            return

        self.font['gasp'].gaspRange[65535] = value
        return True
Exemplo n.º 6
0
    def fix(self):
        if 'DSIG' in self.font:
            return False

        try:
            from fontTools.ttLib.tables.D_S_I_G_ import SignatureRecord
        except ImportError:
            error_message = ("The '{}' font does not have an existing"
                             " digital signature proving its authenticity,"
                             " so Fontbakery needs to add one. To do this"
                             " requires version 2.3 or later of Fonttools"
                             " to be installed. Please upgrade at"
                             " https://pypi.python.org/pypi/FontTools/2.4")
            logger.error(error_message.format(os.path.basename(self.fontpath)))
            return False

        newDSIG = ttLib.newTable("DSIG")
        newDSIG.ulVersion = 1
        newDSIG.usFlag = 1
        newDSIG.usNumSigs = 1
        sig = SignatureRecord()
        sig.ulLength = 20
        sig.cbSignature = 12
        sig.usReserved2 = 0
        sig.usReserved1 = 0
        sig.pkcs7 = '\xd3M4\xd3M5\xd3M4\xd3M4'
        sig.ulFormat = 1
        sig.ulOffset = 20
        newDSIG.signatureRecords = [sig]
        self.font.tables["DSIG"] = newDSIG
        return True
Exemplo n.º 7
0
    def getOrCreateNameRecord(self, nameId, val):
        logger.error('NAMEID {}: "{}"'.format(nameId, val))
        return

        result_namerec = None
        for k, p in [[1, 0], [3, 1]]:
            result_namerec = self.font['name'].getName(nameId, k, p)
            if result_namerec:
                if result_namerec.isUnicode():
                    result_namerec.string = (val or '').encode("utf-16-be")
                else:
                    result_namerec.string = val or ''
        if result_namerec:
            return result_namerec

        ot_namerecord = NameRecord()
        ot_namerecord.nameID = nameId
        ot_namerecord.platformID = 3
        ot_namerecord.langID = 0x409
        # When building a Unicode font for Windows, the platform ID
        # should be 3 and the encoding ID should be 1
        ot_namerecord.platEncID = 1
        if ot_namerecord.isUnicode():
            ot_namerecord.string = (val or '').encode("utf-16-be")
        else:
            ot_namerecord.string = val or ''

        self.font['name'].names.append(ot_namerecord)
        return ot_namerecord
Exemplo n.º 8
0
    def addFailure(self, test, err):
        super(BakeryTestResult, self).addFailure(test, err)

        import copy
        failTest = copy.copy(test)
        _, _err_exception, _ = err
        failTest._err = err
        failTest._err_msg = _err_exception.message

        logger.error(self._format_test_output(failTest, 'FAIL'))

        test_method = getattr(test, test._testMethodName)

        if hasattr(test_method, 'autofix'):
            if not self.restart:
                self.callmethod(test_method.autofix_method, failTest)
                if hasattr(self.fl, 'append'):
                    self.fl.append(failTest)
                    self.restart = True  # mark next test as restarted
                    test.run(result=self)
                    self.restart = False  # reset restart state
                    return
            else:
                if hasattr(self.ff, 'append'):
                    self.ff.append(failTest)
        elif hasattr(self.fl, 'append'):
            self.fl.append(failTest)
Exemplo n.º 9
0
 def func(*args, **kwargs):
     logger.debug('\n$ ' + shell_cmd_repr(value, args))
     try:
         result = getattr(cls.__originmodule__, value)(*args, **kwargs)
         return result
     except Exception as e:
         logger.error('Error: %s' % e.message)
         raise e
Exemplo n.º 10
0
 def fix(self, path, value=15):
     try:
         table = self.font.get('gasp')
         table.gaspRange[65535] = value
         return True
     except:
         logger.error('ER: {}: no table gasp'.format(path))
         return
Exemplo n.º 11
0
 def func(*args, **kwargs):
     logger.debug('\n$ ' + shell_cmd_repr(value, args))
     try:
         result = getattr(cls.__originmodule__, value)(*args, **kwargs)
         return result
     except Exception as e:
         logger.error('Error: %s' % e.message)
         raise e
Exemplo n.º 12
0
 def fix(self, path, value=15):
     try:
         table = self.font.get('gasp')
         table.gaspRange[65535] = value
         return True
     except:
         logger.error('ER: {}: no table gasp'.format(path))
         return
Exemplo n.º 13
0
    def show(self):
        if 'gasp' not in self.font.tables:
            logger.error('no table gasp')
            return

        try:
            logger.info(self.font['gasp'].gaspRange[65535])
        except IndexError:
            logger.error('no index 65535')
Exemplo n.º 14
0
    def addError(self, test, err):
        super(BakeryTestResult, self).addError(test, err)
        _, _err_exception, _ = err
        test._err = err
        test._err_msg = _err_exception.message
        if hasattr(self.el, 'append'):
            self.el.append(test)

        if hasattr(test, 'operator'):
            logger.error(self._format_test_output(test, 'ERROR'))
Exemplo n.º 15
0
    def addError(self, test, err):
        super(BakeryTestResult, self).addError(test, err)
        _, _err_exception, _ = err
        test._err = err
        test._err_msg = _err_exception.message
        if hasattr(self.el, 'append'):
            self.el.append(test)

        if hasattr(test, 'operator'):
            logger.error(self._format_test_output(test, 'ERROR'))
Exemplo n.º 16
0
    def show(self, path):
        try:
            table = self.font.get('gasp')
        except:
            logger.error('ER: {}: no table gasp'.format(path))
            return

        try:
            logger.info(self.font.get('gasp').gaspRange[65535])
        except IndexError:
            logger.error('ER: {}: no index 65535'.format(path))
Exemplo n.º 17
0
    def show(self, path):
        try:
            table = self.font.get('gasp')
        except:
            logger.error('ER: {}: no table gasp'.format(path))
            return

        try:
            logger.info(self.font.get('gasp').gaspRange[65535])
        except IndexError:
            logger.error('ER: {}: no index 65535'.format(path))
Exemplo n.º 18
0
 def load_config(self, config):
     """ Loading settings from yaml bake configuration. """
     if isinstance(config, dict):
         self.config = config or {}
     else:
         try:
             configfile = open(config, 'r')
         except OSError:
             configfile = open(BAKERY_CONFIGURATION_DEFAULTS, 'r')
             logger.error(('Cannot read configuration file.'
                           ' Using defaults'))
         self.config = yaml.safe_load(configfile)
Exemplo n.º 19
0
 def load_config(self, config):
     """ Loading settings from yaml bake configuration. """
     if isinstance(config, dict):
         self.config = config or {}
     else:
         try:
             configfile = open(config, 'r')
         except OSError:
             configfile = open(BAKERY_CONFIGURATION_DEFAULTS, 'r')
             logger.error(('Cannot read configuration file.'
                           ' Using defaults'))
         self.config = yaml.safe_load(configfile)
Exemplo n.º 20
0
    def is_valid(self):
        content = {}
        with io.open(self.fontpath, 'r', encoding="utf-8") as fp:
            content = json.load(fp, object_pairs_hook=collections.OrderedDict)

        if len(content.get('designer', '').split()) > 4:
            logger.error(
                'ER: Designer key is too long. Fix to "Multiple Designer"')
            return False
        if ' and ' in content.get('designer', ''):
            logger.error(
                'ER: Several designers in designer key. Fix to "Multiple Designer"'
            )
            return False
        if ',' in content.get('designer', ''):
            logger.error(
                'ER: Several designers in designer key. Fix to "Multiple Designer"'
            )
            return False
        if '.' in content.get('designer', ''):
            logger.error(
                'ER: Several designers in designer key. Fix to "Multiple Designer"'
            )
            return False

        logger.info(u'OK: Designer "{}"'.format(content.get('designer', '')))
        return True
Exemplo n.º 21
0
    def fix(self, check=False):
        val = self.font['OS/2'].fsType
        fontfile = os.path.basename(self.fontpath)

        if val == 0:
            from bakery_cli.bakery import Bakery
            logger.info('OK: {}'.format(fontfile))
            return

        if check:
            logger.error('ER: {} {}: Change to 0'.format(fontfile, val))
        else:
            logger.error('ER: {} {}: Fixed to 0'.format(fontfile, val))
            self.font['OS/2'].fsType = 0
        return True
Exemplo n.º 22
0
    def fix(self, check=False):
        val = self.font['OS/2'].fsType
        fontfile = os.path.basename(self.fontpath)

        if val == 0:
            from bakery_cli.bakery import Bakery
            logger.info('OK: {}'.format(fontfile))
            return

        if check:
            logger.error('ER: {} {}: Change to 0'.format(fontfile, val))
        else:
            logger.error('ER: {} {}: Fixed to 0'.format(fontfile, val))
            self.font['OS/2'].fsType = 0
        return True
Exemplo n.º 23
0
    def walk(self):
        l = len(self.upstream_path)
        exclude = [
            'build_info',
        ]
        for root, dirs, files in os_origin.walk(self.upstream_path,
                                                topdown=True):
            dirs[:] = [d for d in dirs if d not in exclude]
            for f in files:
                fullpath = op.join(root, f)

                if f[-4:].lower() == '.ttx':
                    try:
                        doc = defusedxml.lxml.parse(fullpath)
                        el = doc.xpath('//ttFont[@sfntVersion]')
                        if not el:
                            continue
                    except Exception as exc:
                        msg = 'Failed to parse "{}". Error: {}'
                        logger.error(msg.format(fullpath, exc))
                        continue
                    self.TTX.append(fullpath[l:].strip('/'))

                if op.basename(f).lower() == 'metadata.json':
                    self.METADATA.append(fullpath[l:].strip('/'))

                if f[-4:].lower() in ['.ttf', '.otf']:
                    self.BIN.append(fullpath[l:].strip('/'))

                if f[-4:].lower() == '.sfd':
                    self.SFD.append(fullpath[l:].strip('/'))

                if f[-4:].lower() in ['.txt', '.markdown', '.md', '.LICENSE']:
                    self.TXT.append(fullpath[l:].strip('/'))

                if op.basename(f).lower() in UpstreamDirectory.ALL_LICENSES:
                    self.LICENSE.append(fullpath[l:].strip('/'))

            for d in dirs:
                fullpath = op.join(root, d)
                if op.splitext(fullpath)[1].lower() == '.ufo':
                    self.UFO.append(fullpath[l:].strip('/'))
Exemplo n.º 24
0
    def is_valid(self):
        has_errors = False
        regex = re.compile(r'-(.*?)Italic\.ttf')
        match = regex.search(self.fontpath)
        if match:
            ttfont = ttLib.TTFont(self.fontpath)

            f = '{:#010b}'.format(ttfont['head'].macStyle)
            if match.group(1) != 'Bold':
                if not bool(ttfont['head'].macStyle & 0b10):
                    logger.error(
                        'ER: HEAD macStyle is {} should be 00000010'.format(f))
                    has_errors = True
            elif not bool(ttfont['head'].macStyle & 0b11):
                logger.error(
                    'ER: HEAD macStyle is {} should be 00000011'.format(f))
                has_errors = True

            if ttfont['post'].italicAngle == 0:
                logger.error('ER: POST italicAngle is 0 should be -13')
                has_errors = True
            # Check NAME table contains correct names for Italic
            if ttfont['OS/2'].fsSelection & 0b1:
                logger.info('OK: OS/2 fsSelection')
            else:
                logger.error('ER: OS/2 fsSelection')
            for name in ttfont['name'].names:
                if name.nameID not in [2, 4, 6, 17]:
                    continue

                string = name.toUnicode()

                if string.endswith('Italic'):
                    logger.info('OK: NAME ID{}:\t{}'.format(
                        name.nameID, string))
                else:
                    logger.error('ER: NAME ID{}:\t{}'.format(
                        name.nameID, string))
        else:
            pass
Exemplo n.º 25
0
    def walk(self):
        l = len(self.upstream_path)
        exclude = ['build_info', ]
        for root, dirs, files in os_origin.walk(self.upstream_path, topdown=True):
            dirs[:] = [d for d in dirs if d not in exclude]
            for f in files:
                fullpath = op.join(root, f)

                if f[-4:].lower() == '.ttx':
                    try:
                        doc = defusedxml.lxml.parse(fullpath)
                        el = doc.xpath('//ttFont[@sfntVersion]')
                        if not el:
                            continue
                    except Exception as exc:
                        msg = 'Failed to parse "{}". Error: {}'
                        logger.error(msg.format(fullpath, exc))
                        continue
                    self.TTX.append(fullpath[l:].strip('/'))

                if op.basename(f).lower() == 'metadata.json':
                    self.METADATA.append(fullpath[l:].strip('/'))

                if f[-4:].lower() in ['.ttf', '.otf']:
                    self.BIN.append(fullpath[l:].strip('/'))

                if f[-4:].lower() == '.sfd':
                    self.SFD.append(fullpath[l:].strip('/'))

                if f[-4:].lower() in ['.txt', '.markdown', '.md', '.LICENSE']:
                    self.TXT.append(fullpath[l:].strip('/'))

                if op.basename(f).lower() in UpstreamDirectory.ALL_LICENSES:
                    self.LICENSE.append(fullpath[l:].strip('/'))

            for d in dirs:
                fullpath = op.join(root, d)
                if op.splitext(fullpath)[1].lower() == '.ufo':
                    self.UFO.append(fullpath[l:].strip('/'))
Exemplo n.º 26
0
    def is_valid(self):
        has_errors = False
        regex = re.compile(r'-(.*?)Italic\.ttf')
        match = regex.search(self.fontpath)
        if match:
            ttfont = ttLib.TTFont(self.fontpath)

            f = '{:#010b}'.format(ttfont['head'].macStyle)
            if match.group(1) != 'Bold':
                if not bool(ttfont['head'].macStyle & 0b10):
                    logger.error('ER: HEAD macStyle is {} should be 00000010'.format(f))
                    has_errors = True
            elif not bool(ttfont['head'].macStyle & 0b11):
                    logger.error('ER: HEAD macStyle is {} should be 00000011'.format(f))
                    has_errors = True

            if ttfont['post'].italicAngle == 0:
                logger.error('ER: POST italicAngle is 0 should be -13')
                has_errors = True
            # Check NAME table contains correct names for Italic
            if ttfont['OS/2'].fsSelection & 0b1:
                logger.info('OK: OS/2 fsSelection')
            else:
                logger.error('ER: OS/2 fsSelection')
            for name in ttfont['name'].names:
                if name.nameID not in [2, 4, 6, 17]:
                    continue

                if name.isUnicode():
                    string = name.string.decode('utf-16-be')
                else:
                    string = name.string
                if string.endswith('Italic'):
                    logger.info('OK: NAME ID{}:\t{}'.format(name.nameID, string))
                else:
                    logger.error('ER: NAME ID{}:\t{}'.format(name.nameID, string))
        else:
            pass
Exemplo n.º 27
0
def run(command, cwd=None):
    """ Wrapper for subprocess.Popen with custom logging support.

        :param command: shell command to run, required
        :param cwd: - current working dir, required
        :param log: - logging object with .write() method, required

    """
    # Start the command
    env = os.environ.copy()

    logger.info('$ %s\n' % command.replace(os_origin.getcwd() + os.path.sep, ''))
    env.update({'PYTHONPATH': os_origin.pathsep.join(sys.path)})
    process = subprocess.Popen(command, shell=True, cwd=cwd or os_origin.getcwd(),
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE,
                               close_fds=True, env=env)
    out, err = process.communicate()
    if out.strip():
        logger.info(out.strip())
    if err.strip():
        logger.error(err.strip())
    return out
Exemplo n.º 28
0
    def fix(self):
        # Convert huge and complex fontTools to config python dict
        fontdata = fontTools_to_dict(self.font)

        fontdata = clean_name_values(fontdata)
        familyname = ''
        for rec in fontdata['names']:
            if rec['nameID'] == 1:
                familyname = rec['string']
                break
        fontdata = fix_all_names(fontdata, familyname)

        logger.error('```')
        logger.error(os.path.basename(self.fontpath))
        logger.error('')
        for field in fontdata['names']:
            self.getOrCreateNameRecord(field['nameID'], field['string'])
        logger.error('```')
        return True
Exemplo n.º 29
0
    def fix(self):
        # Convert huge and complex fontTools to config python dict
        fontdata = fontTools_to_dict(self.font)

        fontdata = clean_name_values(fontdata)
        familyname = ''
        for rec in fontdata['names']:
            if rec['nameID'] == 1:
                familyname = rec['string']
                break
        fontdata = fix_all_names(fontdata, familyname)

        logger.error('```')
        logger.error(os.path.basename(self.fontpath))
        logger.error('')
        for field in fontdata['names']:
            self.getOrCreateNameRecord(field['nameID'], field['string'])
        logger.error('```')
        return True
Exemplo n.º 30
0
    def is_valid(self):
        content = {}
        with io.open(self.fontpath, 'r', encoding="utf-8") as fp:
            content = json.load(fp, object_pairs_hook=collections.OrderedDict)

        if len(content.get('designer', '').split()) > 4:
            logger.error('ER: Designer key is too long. Fix to "Multiple Designer"')
            return False
        if ' and ' in content.get('designer', ''):
            logger.error('ER: Several designers in designer key. Fix to "Multiple Designer"')
            return False
        if ',' in content.get('designer', ''):
            logger.error('ER: Several designers in designer key. Fix to "Multiple Designer"')
            return False
        if '.' in content.get('designer', ''):
            logger.error('ER: Several designers in designer key. Fix to "Multiple Designer"')
            return False

        logger.info(u'OK: Designer "{}"'.format(content.get('designer', '')))
        return True
Exemplo n.º 31
0
def run_bakery(path, verbose=False):
    # fontbakery-build supports passing arguments of directory or
    # concrete bakery.y[a]ml files. In case of passing directory
    # it looks at existing bakery.yml or bakery.yaml and runs on
    # first matched filepath

    # There can also be cases when directory does not contain any
    # bakery.y[a]ml or passed bakery.yml file does not exist. Then
    # fontbakery-build loads default configuration.
    bakery_yml_file = None
    sourcedir = path

    if os.path.isdir(path):
        for filename in ['bakery.yml', 'bakery.yaml']:
            if os.path.exists(os.path.join(path, filename)):
                bakery_yml_file = os.path.join(path, filename)
                break
    else:
        bakery_yml_file = path
        sourcedir = os.path.dirname(path)

    try:
        if bakery_yml_file:
            config = yaml.safe_load(open(bakery_yml_file, 'r'))
        else:
            raise IOError
    except IOError:
        bakery_yml_file = os.path.join(sourcedir, 'bakery.yml')
        config = yaml.safe_load(open(BAKERY_CONFIGURATION_DEFAULTS))

    try:
        builddir = 'build'
        if GITPYTHON_INSTALLED:
            try:
                repo = Repo(sourcedir)
                builddir = repo.git.rev_parse('HEAD', short=True)
            except git.exc.InvalidGitRepositoryError:
                pass
        builddir = os.environ.get('TRAVIS_COMMIT', builddir)

        if 'process_files' not in config:
            directory = UpstreamDirectory(sourcedir)
            # normalize process_files path
            config['process_files'] = directory.get_fonts()

        create_bakery_config(bakery_yml_file, config)

        b = Bakery('', sourcedir, 'builds', builddir)
        b.addLoggingToFile()
        b.load_config(bakery_yml_file)
        b.run()

        if not ttfautohint_installed():
            msg = ('Command line tool `ttfautohint` is required. Install it with'
                   ' `apt-get install ttfautohint` or `brew install ttfautohint`')
            logger.error(msg)
    except:
        logger.error('BUILD FAILED')
        if verbose or config.get('verbose'):
            raise
        logger.error('Run with --verbose to get stacktrace info.')
        sys.exit(1)
Exemplo n.º 32
0
    def fix(self, check=False):
        retval = False
        fontfile = os.path.basename(self.fontpath)

        space = self.getGlyph(0x0020)
        nbsp = self.getGlyph(0x00A0)
        if space not in ["space", "uni0020"]:
            logger.error(
                'ER: {}: Glyph 0x0020 is called "{}": Change to "space" or "uni0020"'
                .format(fontfile, space))
        if nbsp not in ["nbsp", "uni00A0"]:
            logger.error(
                'ER: {}: Glyph 0x00A0 is called "{}": Change to "nbsp" or "uni00A0"'
                .format(fontfile, nbsp))

        isNbspAdded = isSpaceAdded = False
        if not nbsp:
            isNbspAdded = True
            try:
                nbsp = self.addGlyph(0x00A0, 'nbsp')
            except Exception as ex:
                logger.error('ER: {}'.format(ex))
                return False
        if not space:
            isSpaceAdded = True
            try:
                space = self.addGlyph(0x0020, 'space')
            except Exception as ex:
                logger.error('ER: {}'.format(ex))
                return False

        for g in [space, nbsp]:
            if self.glyphHasInk(g):
                if check:
                    logger.error(
                        'ER: {}: Glyph "{}" has ink. Delete any contours or components'
                        .format(fontfile, g))
                else:
                    logger.error(
                        'ER: {}: Glyph "{}" has ink. Fixed: Overwritten by an empty glyph'
                        .format(fontfile, g))
                    #overwrite existing glyph with an empty one
                    self.font['glyf'].glyphs[g] = ttLib.getTableModule(
                        'glyf').Glyph()
                    retval = True

        spaceWidth = self.getWidth(space)
        nbspWidth = self.getWidth(nbsp)

        if spaceWidth != nbspWidth or nbspWidth < 0:

            self.setWidth(nbsp, min(nbspWidth, spaceWidth))
            self.setWidth(space, min(nbspWidth, spaceWidth))

            if isNbspAdded:
                if check:
                    msg = 'ER: {} space {} nbsp None: Add nbsp with advanceWidth {}'
                else:
                    msg = 'ER: {} space {} nbsp None: Added nbsp with advanceWidth {}'
                logger.error(msg.format(fontfile, spaceWidth, spaceWidth))

            if isSpaceAdded:
                if check:
                    msg = 'ER: {} space None nbsp {}: Add space with advanceWidth {}'
                else:
                    msg = 'ER: {} space None nbsp {}: Added space with advanceWidth {}'
                logger.error(msg.format(fontfile, nbspWidth, nbspWidth))

            if nbspWidth > spaceWidth and spaceWidth >= 0:
                if check:
                    msg = 'ER: {} space {} nbsp {}: Change space advanceWidth to {}'
                else:
                    msg = 'ER: {} space {} nbsp {}: Fixed space advanceWidth to {}'
                logger.error(
                    msg.format(fontfile, spaceWidth, nbspWidth, nbspWidth))
            else:
                if check:
                    msg = 'ER: {} space {} nbsp {}: Change nbsp advanceWidth to {}'
                else:
                    msg = 'ER: {} space {} nbsp {}: Fixed nbsp advanceWidth to {}'
                logger.error(
                    msg.format(fontfile, spaceWidth, nbspWidth, spaceWidth))
            return True

        logger.info('OK: {} space {} nbsp {}'.format(fontfile, spaceWidth,
                                                     nbspWidth))
        return retval
Exemplo n.º 33
0
def convert(sourceFont, ttf, otf=None):
    try:
        font = fontforge.open(sourceFont)
    except:
        logger.error("Error: Could not open font (%s)" % sourceFont)
        return

    font.selection.all()

    # Remove overlap
    try:
        font.removeOverlap()
    except:
        logger.error("Error: Could not remove overlaps")

    if otf:
        try:
            font.generate(otf)
            logger.info("OK: Generated OpenType-CFF (%s)" % otf)
        except:
            logger.error("Error: Could not generate OpenType-CFF (%s)" % otf)

    # Convert curves to quadratic (TrueType)
    try:
        font.layers["Fore"].is_quadratic = True
    except:
        logger.error("Error: Could not convert to quadratic TrueType curves")
        return

    # Simplify
    try:
        font.simplify(1, ('setstarttoextremum',
                          'removesingletonpoints',
                          'mergelines'))
    except:
        logger.error("Error: Could not simplify")

    # Correct Directions
    try:
        font.correctDirection()
    except:
        logger.error("Error: Could not correct directions")

    # Generate with DSIG and OpenType tables
    try:
        flags = ('dummy-dsig', 'opentype')
        font.generate(ttf, flags=flags)
        logger.info("Success: Generated OpenType-TTF (%s)" % ttf)
    except:
        logger.error("Error: Could not generate OpenType-TTF (%s)" % ttf)
        return
Exemplo n.º 34
0
 def is_valid_italicAngle(self):
     ttfont = ttLib.TTFont(self.fontpath)
     if ttfont['post'].italicAngle == 0:
         logger.error('ER: POST italicAngle is 0 should be -13')
         return False
     return True
Exemplo n.º 35
0
 def logging_err(self, message):
     logger.error('Error: ' + message.strip())
Exemplo n.º 36
0
 def logging_err(self, message):
     logger.error('Error: ' + message.strip())
Exemplo n.º 37
0
 def is_valid_italicAngle(self):
     ttfont = ttLib.TTFont(self.fontpath)
     if ttfont['post'].italicAngle == 0:
         logger.error('ER: POST italicAngle is 0 should be -13')
         return False
     return True
Exemplo n.º 38
0
    def fix(self, check=False):
        space = self.getGlyph(0x0020)
        nbsp = self.getGlyph(0x00A0)
        isNbspAdded = isSpaceAdded = False
        if not nbsp:
            isNbspAdded = True
            try:
                nbsp = self.addGlyph(0x00A0, 'nbsp')
            except Exception as ex:
                logger.error('ER: {}'.format(ex))
                return False
        if not space:
            isSpaceAdded = True
            try:
                space = self.addGlyph(0x0020, 'space')
            except Exception as ex:
                logger.error('ER: {}'.format(ex))
                return False

        spaceWidth = self.getWidth(space)
        nbspWidth = self.getWidth(nbsp)

        fontfile = os.path.basename(self.fontpath)
        if spaceWidth != nbspWidth or nbspWidth < 0:

            self.setWidth(nbsp, max(nbspWidth, spaceWidth))
            self.setWidth(space, max(nbspWidth, spaceWidth))

            if isNbspAdded:
                if check:
                    msg = 'ER: {} space {} nbsp N: Add nbsp'
                    logger.error(msg.format(fontfile, spaceWidth))
                else:
                    msg = 'ER: {} space {} nbsp N: Added nbsp to {}'
                    logger.error(msg.format(fontfile, spaceWidth, spaceWidth))

            if isSpaceAdded:
                if check:
                    msg = 'ER: {} space N nbsp {}: Add space'
                    logger.error(msg.format(fontfile, nbspWidth))
                else:
                    msg = 'ER: {} space N nbsp {}: Added space {}'
                    logger.error(msg.format(fontfile, nbspWidth, nbspWidth))
                
            if nbspWidth < spaceWidth:
                if check:
                    msg = 'ER: {} space {} nbsp {}: Change nbsp to {}'
                else:
                    msg = 'ER: {} space {} nbsp {}: Fixed nbsp to {}'
                logger.error(msg.format(fontfile, spaceWidth, nbspWidth, spaceWidth))
            else:
                if check:
                    msg = 'ER: {} space {} nbsp {}: Change space to {}'
                else:
                    msg = 'ER: {} space {} nbsp {}: Fixed space to {}'
                logger.error(msg.format(fontfile, spaceWidth, nbspWidth, nbspWidth))
            return True

        logger.info('OK: {} space {} nbsp {}'.format(fontfile, spaceWidth, nbspWidth))
        return
Exemplo n.º 39
0
def run_bakery(path, verbose=False):
    # fontbakery-build supports passing arguments of directory or
    # concrete bakery.y[a]ml files. In case of passing directory
    # it looks at existing bakery.yml or bakery.yaml and runs on
    # first matched filepath

    # There can also be cases when directory does not contain any
    # bakery.y[a]ml or passed bakery.yml file does not exist. Then
    # fontbakery-build loads default configuration.
    bakery_yml_file = None
    sourcedir = path

    if os.path.isdir(path):
        for filename in ["bakery.yml", "bakery.yaml"]:
            if os.path.exists(os.path.join(path, filename)):
                bakery_yml_file = os.path.join(path, filename)
                break
    else:
        bakery_yml_file = path
        sourcedir = os.path.dirname(path)

    try:
        if bakery_yml_file:
            config = yaml.safe_load(open(bakery_yml_file, "r"))
        else:
            raise IOError
    except IOError:
        bakery_yml_file = os.path.join(sourcedir, "bakery.yml")
        config = yaml.safe_load(open(BAKERY_CONFIGURATION_DEFAULTS))

    try:
        builddir = "build"
        if GITPYTHON_INSTALLED:
            try:
                repo = Repo(sourcedir)
                builddir = repo.git.rev_parse("HEAD", short=True)
            except git.exc.InvalidGitRepositoryError:
                pass
        builddir = os.environ.get("TRAVIS_COMMIT", builddir)

        if "process_files" not in config:
            directory = UpstreamDirectory(sourcedir)
            # normalize process_files path
            config["process_files"] = directory.get_fonts()

        create_bakery_config(bakery_yml_file, config)

        b = Bakery("", sourcedir, "builds", builddir)
        b.addLoggingToFile()
        b.load_config(bakery_yml_file)
        b.run()

        if not ttfautohint_installed():
            msg = (
                "Command line tool `ttfautohint` is required. Install it with"
                " `apt-get install ttfautohint` or `brew install ttfautohint`"
            )
            logger.error(msg)
    except:
        if verbose or config.get("verbose"):
            raise
        sys.exit(1)
Exemplo n.º 40
0
    return errors


for path in args.ttf_font:

    if not os.path.exists(path):
        continue

    font = TTFont(path)

    name = font['name'].getName(2, 3, 1)
    if not name:
        continue

    fontStyle = name.string.decode(name.getEncoding())

    errors = validate(font, fontStyle)
    if errors:
        for error, function, arguments in errors:
            logger.error(error)
            if args.autofix and function:
                try:
                    function(*arguments)
                except FixNotApplied as ex:
                    logger.error('ER: Fix can not be applied. See details below')
                    logger.error('\t{}'.format(ex.message))
                    continue
        font.save(path + '.fix')

Exemplo n.º 41
0
    def fix(self, check=False):
        retval = False
        fontfile = os.path.basename(self.fontpath)

        space = self.getGlyph(0x0020)
        nbsp = self.getGlyph(0x00A0)
        if space not in ["space", "uni0020"]:
            logger.error('ER: {}: Glyph 0x0020 is called "{}": Change to "space" or "uni0020"'.format(fontfile, space))
        if nbsp not in ["nbsp", "uni00A0"]:
            logger.error('ER: {}: Glyph 0x00A0 is called "{}": Change to "nbsp" or "uni00A0"'.format(fontfile, nbsp))

        isNbspAdded = isSpaceAdded = False
        if not nbsp:
            isNbspAdded = True
            try:
                nbsp = self.addGlyph(0x00A0, 'nbsp')
            except Exception as ex:
                logger.error('ER: {}'.format(ex))
                return False
        if not space:
            isSpaceAdded = True
            try:
                space = self.addGlyph(0x0020, 'space')
            except Exception as ex:
                logger.error('ER: {}'.format(ex))
                return False

        for g in [space, nbsp]:
            if self.glyphHasInk(g):
                if check:
                    logger.error('ER: {}: Glyph "{}" has ink. Delete any contours or components'.format(fontfile, g))
                else:
                    logger.error('ER: {}: Glyph "{}" has ink. Fixed: Overwritten by an empty glyph'.format(fontfile, g))
                    #overwrite existing glyph with an empty one
                    self.font['glyf'].glyphs[g] = ttLib.getTableModule('glyf').Glyph()
                    retval = True

        spaceWidth = self.getWidth(space)
        nbspWidth = self.getWidth(nbsp)

        if spaceWidth != nbspWidth or nbspWidth < 0:

            self.setWidth(nbsp, min(nbspWidth, spaceWidth))
            self.setWidth(space, min(nbspWidth, spaceWidth))

            if isNbspAdded:
                if check:
                    msg = 'ER: {} space {} nbsp None: Add nbsp with advanceWidth {}'
                else:
                    msg = 'ER: {} space {} nbsp None: Added nbsp with advanceWidth {}'
                logger.error(msg.format(fontfile, spaceWidth, spaceWidth))

            if isSpaceAdded:
                if check:
                    msg = 'ER: {} space None nbsp {}: Add space with advanceWidth {}'
                else:
                    msg = 'ER: {} space None nbsp {}: Added space with advanceWidth {}'
                logger.error(msg.format(fontfile, nbspWidth, nbspWidth))
                
            if nbspWidth > spaceWidth and spaceWidth >= 0:
                if check:
                    msg = 'ER: {} space {} nbsp {}: Change space advanceWidth to {}'
                else:
                    msg = 'ER: {} space {} nbsp {}: Fixed space advanceWidth to {}'
                logger.error(msg.format(fontfile, spaceWidth, nbspWidth, nbspWidth))
            else:
                if check:
                    msg = 'ER: {} space {} nbsp {}: Change nbsp advanceWidth to {}'
                else:
                    msg = 'ER: {} space {} nbsp {}: Fixed nbsp advanceWidth to {}'
                logger.error(msg.format(fontfile, spaceWidth, nbspWidth, spaceWidth))
            return True

        logger.info('OK: {} space {} nbsp {}'.format(fontfile, spaceWidth, nbspWidth))
        return retval