Repository: fonttools/fonttools

Issue:
[ttx] Use ttx file modification time for font "modified" timestamp
I like to add a mode to ttx, such that when converting a ttx file to a font,
the timestamp of the the ttx file be used as the "modified" field in the font
head table.  Currently the "current time" is used.

With that mode, compiling the same ttx file without modifying it, will always
result in the same ttf file.  That's useful at least for testing.

Not sure whether this should be default.  Asked people on the list for their preference.



Discussion:
So far seems like people are supportive of this change.

Currently the timestamp will be again modified by default. There is no option to use the font "modified" timestamp. Need to reopen the issue?

Resolving patch:
diff --git a/Lib/fontTools/ttx.py b/Lib/fontTools/ttx.py
--- a/Lib/fontTools/ttx.py
+++ b/Lib/fontTools/ttx.py
@@ -77,6 +77,7 @@
        file as-is.
     --recalc-timestamp Set font 'modified' timestamp to current time.
        By default, the modification time of the TTX file will be used.
+    --no-recalc-timestamp Keep the original font 'modified' timestamp.
     --flavor <type> Specify flavor of output font file. May be 'woff'
       or 'woff2'. Note that WOFF2 requires the Brotli Python extension,
       available at https://github.com/google/brotli
@@ -123,7 +124,7 @@ class Options(object):
 	bitmapGlyphDataFormat = 'raw'
 	unicodedata = None
 	newlinestr = None
-	recalcTimestamp = False
+	recalcTimestamp = None
 	flavor = None
 	useZopfli = False
 
@@ -204,6 +205,8 @@ def __init__(self, rawOptions, numFiles):
 						% (value, ", ".join(map(repr, validOptions))))
 			elif option == "--recalc-timestamp":
 				self.recalcTimestamp = True
+			elif option == "--no-recalc-timestamp":
+				self.recalcTimestamp = False
 			elif option == "--flavor":
 				self.flavor = value
 			elif option == "--with-zopfli":
@@ -282,7 +285,7 @@ def ttCompile(input, output, options):
 			allowVID=options.allowVID)
 	ttf.importXML(input)
 
-	if not options.recalcTimestamp and 'head' in ttf:
+	if options.recalcTimestamp is None and 'head' in ttf:
 		# use TTX file modification time for head "modified" timestamp
 		mtime = os.path.getmtime(input)
 		ttf['head'].modified = timestampSinceEpoch(mtime)


Test patch:
diff --git a/Tests/ttx/ttx_test.py b/Tests/ttx/ttx_test.py
--- a/Tests/ttx/ttx_test.py
+++ b/Tests/ttx/ttx_test.py
@@ -476,6 +476,11 @@ def test_options_recalc_timestamp():
     assert tto.recalcTimestamp is True
 
 
+def test_options_recalc_timestamp():
+    tto = ttx.Options([("--no-recalc-timestamp", "")], 1)
+    assert tto.recalcTimestamp is False
+
+
 def test_options_flavor():
     tto = ttx.Options([("--flavor", "woff")], 1)
     assert tto.flavor == "woff"
@@ -789,6 +794,14 @@ def test_ttcompile_timestamp_calcs(inpath, outpath1, outpath2, tmpdir):
     ttf = TTFont(str(outttf2))
     assert ttf["head"].modified > epochtime
 
+    # --no-recalc-timestamp will keep original timestamp
+    options.recalcTimestamp = False
+    ttx.ttCompile(inttx, str(outttf2), options)
+    assert outttf2.check(file=True)
+    inttf = TTFont()
+    inttf.importXML(inttx)
+    assert inttf["head"].modified == TTFont(str(outttf2))["head"].modified
+
 
 # -------------------------
 # ttx.ttList function tests