Esempio n. 1
0
 def __init__(self, font, config=Config.default):
     if not isinstance(font, Font):
         font = Font.load(font)
     self.font = font
     self.config = config
     self._fonts_in_collection = None
     self._spacings = []
Esempio n. 2
0
    def _add_feature(self, font: Font, table: otTables.GPOS, feature_tag: str,
                     lookup_indices: List[int]) -> None:
        logger.debug('Adding "%s" to: "%s" %s', feature_tag, font,
                     self._to_str(glyph_ids=True))
        assert not Font._has_ottable_feature(table, feature_tag)
        features = table.FeatureList.FeatureRecord
        feature_index = len(features)
        logger.info('Adding Feature "%s" at index %d for lookup %s',
                    feature_tag, feature_index, lookup_indices)
        feature_record = otTables.FeatureRecord()
        feature_record.FeatureTag = feature_tag
        feature_record.Feature = otTables.Feature()
        feature_record.Feature.LookupListIndex = lookup_indices
        feature_record.Feature.LookupCount = len(lookup_indices)
        features.append(feature_record)

        scripts = table.ScriptList.ScriptRecord
        for script_record in scripts:
            default_lang_sys = script_record.Script.DefaultLangSys
            if default_lang_sys:
                logger.debug(
                    "Adding Feature index %d to script '%s' DefaultLangSys",
                    feature_index, script_record.ScriptTag)
                default_lang_sys.FeatureIndex.append(feature_index)
            for lang_sys in script_record.Script.LangSysRecord:
                logger.debug(
                    "Adding Feature index %d to script '%s' LangSys '%s'",
                    feature_index, script_record.ScriptTag,
                    lang_sys.LangSysTag)
                lang_sys.LangSys.FeatureIndex.append(feature_index)
Esempio n. 3
0
        def __init__(self, font: Font, glyph_sets: 'GlyphSets') -> None:
            glyph_sets.assert_glyphs_are_disjoint()
            self.left, self.right, self.middle, self.space, self.na_left, self.na_right = (
                tuple(font.glyph_names(sorted(glyphs.glyph_id_set)))
                for glyphs in (glyph_sets.left, glyph_sets.right,
                               glyph_sets.middle, glyph_sets.space,
                               glyph_sets.na_left, glyph_sets.na_right))

            em = font.fullwidth_advance
            # When `em` is an odd number, ceil the advance. To do this, use
            # floor to compute the adjustment of the advance and the offset.
            # e.g., "ZCOOL QingKe HuangYou".
            half_em = math.floor(em / 2)
            assert half_em > 0
            quad_em = math.floor(half_em / 2)
            if font.is_vertical:
                self.left_value = buildValue({"YAdvance": -half_em})
                self.right_value = buildValue({
                    "YPlacement": half_em,
                    "YAdvance": -half_em
                })
                self.middle_value = buildValue({
                    "YPlacement": quad_em,
                    "YAdvance": -half_em
                })
            else:
                self.left_value = buildValue({"XAdvance": -half_em})
                self.right_value = buildValue({
                    "XPlacement": -half_em,
                    "XAdvance": -half_em
                })
                self.middle_value = buildValue({
                    "XPlacement": -quad_em,
                    "XAdvance": -half_em
                })
Esempio n. 4
0
 def expand_dir(cls, path: pathlib.Path):
     assert path.is_dir()
     for child in path.iterdir():
         if child.is_dir():
             yield from cls.expand_dir(child)
         elif Font.is_font_extension(child.suffix):
             yield child
Esempio n. 5
0
    def add_to_font(self, font: Font) -> bool:
        self.assert_font(font)
        self.assert_glyphs_are_disjoint()
        if not self._can_add_to_table:
            if self._add_glyphs_count:
                logger.warning('Skipped because no pairs: "%s"', font)
            return False

        table = font.gpos_ottable(create=True)
        lookups = table.LookupList.Lookup
        pos = GlyphSets.PosValues(font, self)
        logger.info('Adding Lookups for %dL, %dR, %dM, %dS', len(pos.left),
                    len(pos.right), len(pos.middle), len(pos.space))

        feature_tag = 'vhal' if font.is_vertical else 'halt'
        if not Font._has_ottable_feature(table, feature_tag):
            lookup_index = self._build_halt_lookup(font, lookups, pos)
            self._add_feature(font, table, feature_tag, [lookup_index])

        feature_tag = 'vchw' if font.is_vertical else 'chws'
        lookup_indices = self._build_chws_lookup(font, lookups, pos)
        self._add_feature(font, table, feature_tag, lookup_indices)
        return True
Esempio n. 6
0
    async def main():
        parser = argparse.ArgumentParser()
        parser.add_argument("path")
        parser.add_argument("-i", "--index", type=int, default=0)
        parser.add_argument("-v",
                            "--verbose",
                            help="increase output verbosity",
                            action="count",
                            default=0)
        args = parser.parse_args()
        init_logging(args.verbose)

        font = Font.load(args.path)
        if font.is_collection:
            font = font.fonts_in_collection[args.index]
        spacing = EastAsianSpacing()
        config = Config.default
        await spacing.add_glyphs(font, config)

        spacing.save_glyphs(sys.stdout)
Esempio n. 7
0
 async def main():
     parser = argparse.ArgumentParser()
     parser.add_argument("path")
     parser.add_argument("-i", "--index", type=int, default=-1)
     parser.add_argument("-v",
                         "--verbose",
                         help="increase output verbosity",
                         action="count",
                         default=0)
     args = parser.parse_args()
     if args.verbose:
         logging.basicConfig(level=logging.DEBUG)
     else:
         logging.basicConfig(level=logging.INFO)
     font = Font.load(args.path)
     if args.index >= 0:
         font = font.fonts_in_collection[args.index]
     config = Config.default
     await EastAsianSpacingTester(font, config).test()
     logging.info('All tests pass')
Esempio n. 8
0
 async def ensure_fullwidth_advance(font: Font, config: Config) -> bool:
     if font.has_custom_fullwidth_advance:
         return True
     # If `fullwidth_advance_text` is not set, use `units_per_em`.
     advance = config.fullwidth_advance
     if not advance:
         logger.debug('fullwidth_advance=%d (units_per_em) for "%s"',
                      font.units_per_em, font)
         return True
     if isinstance(advance, int):
         font.fullwidth_advance = advance
         logger.debug('fullwidth_advance=%d (units_per_em=%d) for "%s"',
                      font.fullwidth_advance, font.units_per_em, font)
         return True
     assert isinstance(advance, str)
     features = ['fwid', 'vert'] if font.is_vertical else ['fwid']
     shaper = Shaper(font, features=features)
     if await shaper.compute_fullwidth_advance(text=advance):
         logger.debug('fullwidth_advance=%d (units_per_em=%d) for "%s"',
                      font.fullwidth_advance, font.units_per_em, font)
         return True
     return False
Esempio n. 9
0
async def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("font_path")
    parser.add_argument("-f", "--feature")
    parser.add_argument("-i", "--index", type=int, default=0)
    parser.add_argument("text")
    parser.add_argument("-l", "--language")
    parser.add_argument("-s", "--script")
    parser.add_argument("-v",
                        "--verbose",
                        help="increase output verbosity",
                        action="count",
                        default=0)
    args = parser.parse_args()
    show_dump_images()
    utils.init_logging(args.verbose)
    _init_shaper()  # Re-initialize to show logging.
    font = Font.load(args.font_path)
    if font.is_collection:
        font = font.fonts_in_collection[args.index]
    if args.feature:
        features = args.feature.split(',')
        if 'vert' in features:
            font = font.vertical_font
    else:
        features = None
    shaper = Shaper(font,
                    language=args.language,
                    script=args.script,
                    features=features)
    print(f'fullwidth={await shaper.compute_fullwidth_advance()}, '
          f'upem={font.units_per_em}')
    result = await shaper.shape(args.text)
    result.set_text(args.text)
    result.compute_ink_parts(font)
    for g in result:
        print(g)
Esempio n. 10
0
 def save(self,
          output: Optional[pathlib.Path] = None,
          stem_suffix: Optional[str] = None,
          glyph_out: Optional[Union[pathlib.Path, str, TextIO]] = None,
          glyph_comment: int = 0,
          print_path: bool = False) -> pathlib.Path:
     assert self.has_spacings
     font = self.font
     path_before_save = font.path
     output = calc_output_path(path_before_save,
                               output,
                               stem_suffix=stem_suffix,
                               is_file=output
                               and Font.is_font_extension(output.suffix))
     logger.info('Saving to "%s"', output)
     font.save(output)
     paths = [output, path_before_save]
     if glyph_out:
         glyphs_path = self.save_glyphs(glyph_out, comment=glyph_comment)
         paths.append(glyphs_path)
     if print_path:
         print('\t'.join(str(path) for path in paths),
               flush=True)  # Flush, for better parallelism when piping.
     return output
Esempio n. 11
0
 async def main():
     parser = argparse.ArgumentParser()
     parser.add_argument("path", nargs="+")
     parser.add_argument("--diff",
                         type=pathlib.Path,
                         help="source font or directory "
                         "to compute differences against.")
     parser.add_argument("-f",
                         "--features",
                         action="store_true",
                         help="dump GPOS/GSUB feature names.")
     parser.add_argument("-o",
                         "--output",
                         type=pathlib.Path,
                         help="output directory.")
     parser.add_argument("-r",
                         "--ref",
                         type=Dump.References,
                         help="reference directory.")
     parser.add_argument("-s",
                         "--sort",
                         default="tag",
                         choices=["offset", "tag"],
                         help="sort order.")
     parser.add_argument("--ttx",
                         action="store_true",
                         help="create TTX files in addition to table list.")
     parser.add_argument("-v",
                         "--verbose",
                         help="increase output verbosity.",
                         action="count",
                         default=0)
     args = parser.parse_args()
     init_logging(args.verbose, main=logger)
     if args.output:
         args.output.mkdir(exist_ok=True, parents=True)
     dump_file_name = args.output is None and len(args.path) > 1
     for i, (path, diff_src, glyphs) in enumerate(Dump.expand_paths(args)):
         if dump_file_name:
             if i: print()
             print(f'File: {path}')
         if diff_src:
             diffs = await Dump.diff_font(path,
                                          diff_src,
                                          diff_out=args.output)
             if args.ref:
                 if not isinstance(diffs[0], os.PathLike):
                     raise Exception('`-r` requires `-o`')
                 if glyphs:
                     diffs.append(glyphs)
                 await args.ref.diff_with_references(diffs)
             continue
         font = Font.load(path)
         await Dump.dump_font(font, **vars(args))
         logger.debug("dump %d completed: %s", i, font)
     if args.ref and args.ref.has_any:
         args.ref.print_stats()
         script = args.output / 'update-ref.sh'
         args.ref.write_update_script(script)
         if args.ref.has_any_diffs:
             print(
                 f'Created a script to update reference files at "{script}".'
             )
     logger.debug("main completed")
Esempio n. 12
0
    async def diff_font(font, src_font, diff_out=None, dump_dir=None):
        logger.info('diff_font %s src=%s', font, src_font)

        if isinstance(dump_dir, str):
            dump_dir = pathlib.Path(dump_dir)
        if isinstance(diff_out, str):
            diff_out = pathlib.Path(diff_out)
        if isinstance(diff_out, os.PathLike):
            diff_out.mkdir(exist_ok=True, parents=True)
            if dump_dir is None:
                dump_dir = diff_out / 'dump'
            src_dump_dir = diff_out / 'src'
        elif dump_dir is None:
            # If `diff_out` is a file but without `dump_dir`, dump to a
            # temporary directory.
            with tempfile.TemporaryDirectory() as temp_dir:
                return await Dump.diff_font(font,
                                            src_font,
                                            diff_out=diff_out,
                                            dump_dir=temp_dir)
        else:
            if diff_out is None:
                diff_out = sys.stdout
            src_dump_dir = dump_dir / 'src'
        dump_dir.mkdir(exist_ok=True, parents=True)
        src_dump_dir.mkdir(exist_ok=True, parents=True)

        if not isinstance(font, Font):
            font = Font.load(font)
        if not isinstance(src_font, Font):
            if isinstance(src_font, str):
                src_font = pathlib.Path(src_font)
            if src_font.is_dir():
                src_font = src_font / font.path.name
            src_font = Font.load(src_font)

        # Create tables files and diff them.
        entries = TableEntry.read_font(font)
        tables_path = Dump.dump_tables(font,
                                       entries=entries,
                                       out_file=dump_dir)
        src_entries = TableEntry.read_font(src_font)
        src_tables_path = Dump.dump_tables(src_font,
                                           entries=src_entries,
                                           out_file=src_dump_dir)
        tables_diff = await Dump.diff(src_tables_path, tables_path, diff_out)
        diff_paths = [tables_diff]

        logger.info('%d / %d tables found', len(entries), len(src_entries))
        entries, src_entries = TableEntry.filter_entries_by_binary_diff(
            entries, src_entries)
        logger.info('%d / %d tables after binary diff', len(entries),
                    len(src_entries))

        # Dump TTX files.
        ttx_paths, src_ttx_paths = await asyncio.gather(
            Dump.dump_ttx(font, dump_dir, entries),
            Dump.dump_ttx(src_font, src_dump_dir, src_entries))

        # Diff TTX files.
        assert len(ttx_paths) == len(
            src_ttx_paths), f'dst={ttx_paths}\nsrc={src_ttx_paths}'
        for ttx_path, src_ttx_path in zip(ttx_paths, src_ttx_paths):
            tables = Dump.read_split_table_ttx(ttx_path)
            src_tables = Dump.read_split_table_ttx(src_ttx_path)
            for table_name in set(tables.keys()).union(src_tables.keys()):
                table = tables.get(table_name)
                src_table = src_tables.get(table_name)
                ttx_diff = await Dump.diff(src_table,
                                           table,
                                           diff_out,
                                           ignore_line_numbers=True)
                if isinstance(ttx_diff, os.PathLike):
                    if not Dump.has_table_diff(ttx_diff, table_name):
                        logger.debug('No diff for %s', table_name)
                        ttx_diff.unlink()
                        continue
                logger.debug('Diff found for %s', table_name)
                diff_paths.append(ttx_diff)
        logger.debug("diff completed: %s", font)
        return diff_paths
Esempio n. 13
0
    async def main():
        parser = argparse.ArgumentParser()
        parser.add_argument("inputs", nargs="+")
        parser.add_argument("-i",
                            "--index",
                            help="font index, or a list of font indices"
                            " for a font collection (TTC)")
        parser.add_argument("--debug", help="names of debug logs")
        parser.add_argument("--em",
                            help="fullwidth advance, "
                            "or characters to compute fullwidth advance from")
        parser.add_argument("-g", "--glyph-out", help="output glyph list")
        parser.add_argument("-G",
                            "--glyph-comment",
                            type=int,
                            default=1,
                            help="comment level for the glyph list")
        parser.add_argument("-l",
                            "--language",
                            help="language if the font is language-specific,"
                            " or a comma separated list of languages"
                            " for a font collection (TTC)")
        parser.add_argument("--no-monospace",
                            action="store_true",
                            help="Skip ASCII-monospace fonts")
        parser.add_argument("-o",
                            "--output",
                            default="build",
                            type=pathlib.Path,
                            help="output directory")
        parser.add_argument("-p",
                            "--print-path",
                            action="store_true",
                            help="print the file paths to stdout")
        parser.add_argument("-s",
                            "--suffix",
                            help="suffix to add to the output file name")
        parser.add_argument("--test",
                            type=int,
                            default=1,
                            help="0=no tests, 1=smoke tests, 2=full tests")
        parser.add_argument("-v",
                            "--verbose",
                            help="increase output verbosity",
                            action="count",
                            default=0)
        args = parser.parse_args()
        init_logging(args.verbose, main=logger, debug=args.debug)
        if args.em is not None:
            with contextlib.suppress(ValueError):
                args.em = int(args.em)
        if args.glyph_out:
            if args.glyph_out == '-':
                args.glyph_out = sys.stdout
            else:
                args.glyph_out = pathlib.Path(args.glyph_out)
                args.glyph_out.mkdir(exist_ok=True, parents=True)
        for input in Builder.expand_paths(args.inputs):
            font = Font.load(input)
            if font.is_collection:
                config = Config.for_collection(font,
                                               languages=args.language,
                                               indices=args.index)
            else:
                config = Config.default
                if args.language:
                    assert ',' not in args.language
                    config = config.for_language(args.language)
            if args.no_monospace:
                config = config.with_skip_monospace_ascii(True)
            if args.em is not None:
                config = config.with_fullwidth_advance(args.em)

            builder = Builder(font, config)
            output = await builder.build_and_save(
                args.output,
                stem_suffix=args.suffix,
                glyph_out=args.glyph_out,
                glyph_comment=args.glyph_comment,
                print_path=args.print_path)
            if not output:
                logger.info('Skipped saving due to no changes: "%s"', input)
                continue
            if args.test:
                await builder.test(smoke=(args.test == 1))