2
2
#
3
3
# Google Author(s): Behdad Esfahbod
4
4
5
+ from __future__ import annotations
6
+
5
7
from fontTools import config
6
8
from fontTools .misc .roundTools import otRound
7
9
from fontTools import ttLib
15
17
from fontTools .subset .cff import *
16
18
from fontTools .subset .svg import *
17
19
from fontTools .varLib import varStore , multiVarStore # For monkey-patching
18
- from fontTools .ttLib .tables ._n_a_m_e import NameRecordVisitor
20
+ from fontTools .ttLib .tables ._n_a_m_e import NameRecordVisitor , makeName
19
21
from fontTools .unicodedata import mirrored
20
22
import sys
21
23
import struct
@@ -3004,6 +3006,9 @@ def prune_pre_subset(self, font, options):
3004
3006
return True
3005
3007
3006
3008
3009
+ NAME_IDS_TO_OBFUSCATE = {1 , 2 , 3 , 4 , 6 , 16 , 17 , 18 }
3010
+
3011
+
3007
3012
@_add_method (ttLib .getTableClass ("name" ))
3008
3013
def prune_post_subset (self , font , options ):
3009
3014
visitor = NameRecordVisitor ()
@@ -3022,6 +3027,11 @@ def prune_post_subset(self, font, options):
3022
3027
self .names = [n for n in self .names if n .langID in options .name_languages ]
3023
3028
if options .obfuscate_names :
3024
3029
namerecs = []
3030
+ # Preserve names to be scrambled or dropped elsewhere so that other
3031
+ # parts of the font don't break.
3032
+ needRemapping = visitor .seen .intersection (NAME_IDS_TO_OBFUSCATE )
3033
+ if needRemapping :
3034
+ _remap_select_name_ids (font , needRemapping )
3025
3035
for n in self .names :
3026
3036
if n .nameID in [1 , 4 ]:
3027
3037
n .string = ".\x7f " .encode ("utf_16_be" ) if n .isUnicode () else ".\x7f "
@@ -3036,6 +3046,76 @@ def prune_post_subset(self, font, options):
3036
3046
return True # Required table
3037
3047
3038
3048
3049
+ def _remap_select_name_ids (font : ttLib .TTFont , needRemapping : set [int ]) -> None :
3050
+ """Remap a set of IDs so that the originals can be safely scrambled or
3051
+ dropped.
3052
+
3053
+ For each name record whose name id is in the `needRemapping` set, make a copy
3054
+ and allocate a new unused name id in the font-specific range (> 255).
3055
+
3056
+ Finally update references to these in the `fvar` and `STAT` tables.
3057
+ """
3058
+
3059
+ if "fvar" not in font and "STAT" not in font :
3060
+ return
3061
+
3062
+ name = font ["name" ]
3063
+
3064
+ # 1. Assign new IDs for names to be preserved.
3065
+ existingIds = {record .nameID for record in name .names }
3066
+ remapping = {}
3067
+ nextId = name ._findUnusedNameID () - 1 # Should skip gaps in name IDs.
3068
+ for nameId in needRemapping :
3069
+ nextId += 1 # We should have complete freedom until 32767.
3070
+ assert nextId not in existingIds , "_findUnusedNameID did not skip gaps"
3071
+ if nextId > 32767 :
3072
+ raise ValueError ("Ran out of name IDs while trying to remap existing ones." )
3073
+ remapping [nameId ] = nextId
3074
+
3075
+ # 2. Copy records to use the new ID. We can't rewrite them in place, because
3076
+ # that could make IDs 1 to 6 "disappear" from code that follows. Some
3077
+ # tools that produce EOT fonts expect them to exist, even when they're
3078
+ # scrambled. See https://github.com/fonttools/fonttools/issues/165.
3079
+ copiedRecords = []
3080
+ for record in name .names :
3081
+ if record .nameID not in needRemapping :
3082
+ continue
3083
+ recordCopy = makeName (
3084
+ record .string ,
3085
+ remapping [record .nameID ],
3086
+ record .platformID ,
3087
+ record .platEncID ,
3088
+ record .langID ,
3089
+ )
3090
+ copiedRecords .append (recordCopy )
3091
+ name .names .extend (copiedRecords )
3092
+
3093
+ # 3. Rewrite the corresponding IDs in other tables. For now, care only about
3094
+ # STAT and fvar. If more tables need to be changed, consider adapting
3095
+ # NameRecordVisitor to rewrite IDs wherever it finds them.
3096
+ fvar = font .get ("fvar" )
3097
+ if fvar is not None :
3098
+ for axis in fvar .axes :
3099
+ axis .axisNameID = remapping .get (axis .axisNameID , axis .axisNameID )
3100
+ for instance in fvar .instances :
3101
+ nameID = instance .subfamilyNameID
3102
+ instance .subfamilyNameID = remapping .get (nameID , nameID )
3103
+ nameID = instance .postscriptNameID
3104
+ instance .postscriptNameID = remapping .get (nameID , nameID )
3105
+
3106
+ stat = font .get ("STAT" )
3107
+ if stat is None :
3108
+ return
3109
+ elidedNameID = stat .table .ElidedFallbackNameID
3110
+ stat .table .ElidedFallbackNameID = remapping .get (elidedNameID , elidedNameID )
3111
+ if stat .table .DesignAxisRecord :
3112
+ for axis in stat .table .DesignAxisRecord .Axis :
3113
+ axis .AxisNameID = remapping .get (axis .AxisNameID , axis .AxisNameID )
3114
+ if stat .table .AxisValueArray :
3115
+ for value in stat .table .AxisValueArray .AxisValue :
3116
+ value .ValueNameID = remapping .get (value .ValueNameID , value .ValueNameID )
3117
+
3118
+
3039
3119
@_add_method (ttLib .getTableClass ("head" ))
3040
3120
def prune_post_subset (self , font , options ):
3041
3121
# Force re-compiling head table, to update any recalculated values.
0 commit comments