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 = []
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)
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 })
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
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
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)
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')
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
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)
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
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")
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
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))