summaryrefslogtreecommitdiffstats
path: root/contrib/python/fonttools/fontTools/diff/diff.py
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2026-03-24 22:03:23 +0300
committerrobot-piglet <[email protected]>2026-03-24 22:34:09 +0300
commit6092233e61d1dc129fe1eb007399cc192c5ceb59 (patch)
tree90522e5b7449e5cdb06bd24eafb333b9e9d3e9f1 /contrib/python/fonttools/fontTools/diff/diff.py
parentc8c3fda4b2e47ceaad9790b7a5fb192110162f15 (diff)
Intermediate changes
commit_hash:5e2a2254279501ad2bde571fbd53c1a27a00e898
Diffstat (limited to 'contrib/python/fonttools/fontTools/diff/diff.py')
-rw-r--r--contrib/python/fonttools/fontTools/diff/diff.py294
1 files changed, 294 insertions, 0 deletions
diff --git a/contrib/python/fonttools/fontTools/diff/diff.py b/contrib/python/fonttools/fontTools/diff/diff.py
new file mode 100644
index 00000000000..302f2e677ae
--- /dev/null
+++ b/contrib/python/fonttools/fontTools/diff/diff.py
@@ -0,0 +1,294 @@
+import os
+import subprocess
+import tempfile
+from contextlib import contextmanager
+from difflib import unified_diff
+from multiprocessing import Pool, cpu_count
+from typing import Any, Callable, Iterable, Iterator, List, Optional, Text, Tuple
+
+from fontTools.ttLib import TTFont # type: ignore
+
+from .utils import get_file_modtime
+
+#
+#
+# Private functions
+#
+#
+
+
+def _get_fonts_and_save_xml(
+ filepath_a: Text,
+ filepath_b: Text,
+ tmpdirpath: Text,
+ include_tables: Optional[List[Text]],
+ exclude_tables: Optional[List[Text]],
+ font_number_a: int,
+ font_number_b: int,
+ use_multiprocess: bool,
+) -> Tuple[Text, Text, Text, Text, Text, Text]:
+ post_pathname, postpath, pre_pathname, prepath = _get_pre_post_paths(
+ filepath_a, filepath_b
+ )
+ # instantiate left and right fontTools.ttLib.TTFont objects
+ tt_left = TTFont(prepath, fontNumber=font_number_a)
+ tt_right = TTFont(postpath, fontNumber=font_number_b)
+ left_ttxpath = os.path.join(tmpdirpath, "left.ttx")
+ right_ttxpath = os.path.join(tmpdirpath, "right.ttx")
+ _mp_save_ttx_xml(
+ tt_left,
+ tt_right,
+ left_ttxpath,
+ right_ttxpath,
+ exclude_tables,
+ include_tables,
+ use_multiprocess,
+ )
+ return left_ttxpath, right_ttxpath, pre_pathname, prepath, post_pathname, postpath
+
+
+def _get_pre_post_paths(
+ filepath_a: Text,
+ filepath_b: Text,
+) -> Tuple[Text, Text, Text, Text]:
+ prepath = filepath_a
+ postpath = filepath_b
+ pre_pathname = filepath_a
+ post_pathname = filepath_b
+ return post_pathname, postpath, pre_pathname, prepath
+
+
+def _mp_save_ttx_xml(
+ tt_left: Any,
+ tt_right: Any,
+ left_ttxpath: Text,
+ right_ttxpath: Text,
+ exclude_tables: Optional[List[Text]],
+ include_tables: Optional[List[Text]],
+ use_multiprocess: bool,
+) -> None:
+ if use_multiprocess and cpu_count() > 1:
+ # Use parallel fontTools.ttLib.TTFont.saveXML dump
+ # by default on multi CPU systems. This is a performance
+ # optimization. Profiling demonstrates that this can reduce
+ # execution time by up to 30% for some fonts
+ mp_args_list = [
+ (tt_left, left_ttxpath, include_tables, exclude_tables),
+ (tt_right, right_ttxpath, include_tables, exclude_tables),
+ ]
+ with Pool(processes=2) as pool:
+ pool.starmap(_ttfont_save_xml, mp_args_list)
+ else:
+ # use sequential fontTools.ttLib.TTFont.saveXML dumps
+ # when use_multiprocess is False or single CPU system
+ # detected
+ _ttfont_save_xml(tt_left, left_ttxpath, include_tables, exclude_tables)
+ _ttfont_save_xml(tt_right, right_ttxpath, include_tables, exclude_tables)
+
+
+def _ttfont_save_xml(
+ ttf: Any,
+ filepath: Text,
+ include_tables: Optional[List[Text]],
+ exclude_tables: Optional[List[Text]],
+) -> bool:
+ """Writes TTX specification formatted XML to disk on filepath."""
+ ttf.saveXML(filepath, tables=include_tables, skipTables=exclude_tables)
+ return True
+
+
+@contextmanager
+def _saved_ttx_files(
+ filepath_a: Text,
+ filepath_b: Text,
+ include_tables: Optional[List[Text]],
+ exclude_tables: Optional[List[Text]],
+ font_number_a: int,
+ font_number_b: int,
+ use_multiprocess: bool,
+) -> Iterator[Tuple[Text, Text, Text, Text, Text, Text]]:
+ with tempfile.TemporaryDirectory() as tmpdirpath:
+ yield _get_fonts_and_save_xml(
+ filepath_a,
+ filepath_b,
+ tmpdirpath,
+ include_tables,
+ exclude_tables,
+ font_number_a,
+ font_number_b,
+ use_multiprocess,
+ )
+
+
+def _diff_with_saved_ttx_files(
+ filepath_a: Text,
+ filepath_b: Text,
+ include_tables: Optional[List[Text]],
+ exclude_tables: Optional[List[Text]],
+ font_number_a: int,
+ font_number_b: int,
+ use_multiprocess: bool,
+ create_differ: Callable[[Text, Text, Text, Text, Text, Text], Iterable[Text]],
+) -> Iterator[Text]:
+ with _saved_ttx_files(
+ filepath_a,
+ filepath_b,
+ include_tables,
+ exclude_tables,
+ font_number_a,
+ font_number_b,
+ use_multiprocess,
+ ) as (
+ left_ttxpath,
+ right_ttxpath,
+ pre_pathname,
+ prepath,
+ post_pathname,
+ postpath,
+ ):
+ yield from create_differ(
+ left_ttxpath,
+ right_ttxpath,
+ pre_pathname,
+ prepath,
+ post_pathname,
+ postpath,
+ )
+
+
+#
+#
+# Public functions
+#
+#
+
+
+def u_diff(
+ filepath_a: Text,
+ filepath_b: Text,
+ context_lines: int = 3,
+ include_tables: Optional[List[Text]] = None,
+ exclude_tables: Optional[List[Text]] = None,
+ font_number_a: int = -1,
+ font_number_b: int = -1,
+ use_multiprocess: bool = True,
+) -> Iterator[Text]:
+ """Performs a unified diff on a TTX serialized data format dump of font binary data using
+ a modified version of the Python standard libary difflib module.
+
+ filepath_a: (string) pre-file local file path
+ filepath_b: (string) post-file local file path
+ context_lines: (int) number of context lines to include in the diff (default=3)
+ include_tables: (list of str) Python list of OpenType tables to include in the diff
+ exclude_tables: (list of str) Python list of OpentType tables to exclude from the diff
+ use_multiprocess: (bool) use multi-processor optimizations (default=True)
+
+ include_tables and exclude_tables are mutually exclusive arguments. Only one should
+ be defined
+
+ :returns: Generator of ordered diff line strings that include newline line endings
+ :raises: KeyError if include_tables or exclude_tables includes a mis-specified table
+ that is not included in filepath_a OR filepath_b
+ """
+
+ def _create_unified_diff(
+ left_ttxpath: Text,
+ right_ttxpath: Text,
+ pre_pathname: Text,
+ prepath: Text,
+ post_pathname: Text,
+ postpath: Text,
+ ) -> Iterable[Text]:
+ with open(left_ttxpath) as ff:
+ fromlines = ff.readlines()
+ with open(right_ttxpath) as tf:
+ tolines = tf.readlines()
+
+ fromdate = get_file_modtime(prepath)
+ todate = get_file_modtime(postpath)
+
+ yield from unified_diff(
+ fromlines,
+ tolines,
+ pre_pathname,
+ post_pathname,
+ fromdate,
+ todate,
+ n=context_lines,
+ )
+
+ yield from _diff_with_saved_ttx_files(
+ filepath_a,
+ filepath_b,
+ include_tables,
+ exclude_tables,
+ font_number_a,
+ font_number_b,
+ use_multiprocess,
+ _create_unified_diff,
+ )
+
+
+def run_external_diff(
+ diff_tool: Text,
+ diff_args: List[Text],
+ filepath_a: Text,
+ filepath_b: Text,
+ include_tables: Optional[List[Text]] = None,
+ exclude_tables: Optional[List[Text]] = None,
+ font_number_a: int = -1,
+ font_number_b: int = -1,
+ use_multiprocess: bool = True,
+) -> Iterator[Text]:
+ """Performs a unified diff on a TTX serialized data format dump of font binary data using
+ an external diff executable that is requested by the caller via `command`
+
+ diff_tool: (string) command line executable string
+ diff_args: (list of strings) arguments for the diff tool
+ filepath_a: (string) pre-file local file path
+ filepath_b: (string) post-file local file path
+ include_tables: (list of str) Python list of OpenType tables to include in the diff
+ exclude_tables: (list of str) Python list of OpentType tables to exclude from the diff
+ use_multiprocess: (bool) use multi-processor optimizations (default=True)
+
+ include_tables and exclude_tables are mutually exclusive arguments. Only one should
+ be defined
+
+ :returns: Generator of ordered diff line strings that include newline line endings
+ :raises: KeyError if include_tables or exclude_tables includes a mis-specified table
+ that is not included in filepath_a OR filepath_b
+ :raises: IOError if exception raised during execution of `command` on TTX files
+ """
+
+ def _create_external_diff(
+ left_ttxpath: Text,
+ right_ttxpath: Text,
+ _pre_pathname: Text,
+ _prepath: Text,
+ _post_pathname: Text,
+ _postpath: Text,
+ ) -> Iterable[Text]:
+ command = [diff_tool] + diff_args + [left_ttxpath, right_ttxpath]
+ process = subprocess.Popen(
+ command,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding="utf8",
+ )
+
+ for line in process.stdout:
+ yield line
+ err = process.stderr.read()
+ if err:
+ raise IOError(err)
+
+ yield from _diff_with_saved_ttx_files(
+ filepath_a,
+ filepath_b,
+ include_tables,
+ exclude_tables,
+ font_number_a,
+ font_number_b,
+ use_multiprocess,
+ _create_external_diff,
+ )