|
| 1 | +# This file is part of Slice. |
| 2 | +# |
| 3 | +# Slice is free software: you can redistribute it and/or modify |
| 4 | +# it under the terms of the GNU General Public License as published by |
| 5 | +# the Free Software Foundation, either version 3 of the License, or |
| 6 | +# (at your option) any later version. |
| 7 | +# |
| 8 | +# Slice is distributed in the hope that it will be useful, |
| 9 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | +# GNU General Public License for more details. |
| 12 | +# |
| 13 | +# You should have received a copy of the GNU General Public License |
| 14 | +# along with Slice. If not, see <https://www.gnu.org/licenses/>. |
| 15 | + |
| 16 | +import datetime |
| 17 | +import sys |
| 18 | +import traceback |
| 19 | + |
| 20 | +from fontTools.misc.textTools import num2binary |
| 21 | +from fontTools.ttLib.ttFont import TTFont |
| 22 | +from fontTools.varLib.mutator import instantiateVariableFont |
| 23 | +from PyQt5.QtCore import QObject, QRunnable, pyqtSignal, pyqtSlot |
| 24 | + |
| 25 | + |
| 26 | +class InstanceWorkerSignals(QObject): |
| 27 | + finished = pyqtSignal() # no return type, only signal that complete |
| 28 | + error = pyqtSignal(str) # returns the error message |
| 29 | + result = pyqtSignal(str) # returns file path for the new file write |
| 30 | + |
| 31 | + |
| 32 | +class InstanceWorker(QRunnable): |
| 33 | + def __init__( |
| 34 | + self, |
| 35 | + outpath=None, |
| 36 | + font_model=None, |
| 37 | + axis_model=None, |
| 38 | + name_model=None, |
| 39 | + bit_model=None, |
| 40 | + ): |
| 41 | + super().__init__() |
| 42 | + self.signals = InstanceWorkerSignals() |
| 43 | + self.outpath = outpath |
| 44 | + self.font_model = font_model |
| 45 | + self.axis_model = axis_model |
| 46 | + self.name_model = name_model |
| 47 | + self.bit_model = bit_model |
| 48 | + self.ttfont = None |
| 49 | + |
| 50 | + @pyqtSlot() |
| 51 | + def run(self): |
| 52 | + try: |
| 53 | + # Debugging in stdout |
| 54 | + print(f"\n\n{datetime.datetime.now()}") |
| 55 | + self.ttfont = TTFont(self.font_model.fontpath) |
| 56 | + # instantiate |
| 57 | + self.instantiate_variable_font() |
| 58 | + # edit name table records |
| 59 | + self.edit_name_table() |
| 60 | + # edit bit flags |
| 61 | + self.edit_bit_flags() |
| 62 | + # write to disk |
| 63 | + self.ttfont.save(self.outpath) |
| 64 | + except Exception as e: |
| 65 | + self.signals.error.emit(f"{e}") |
| 66 | + sys.stderr.write(f"{traceback.format_exc()}\n") |
| 67 | + else: |
| 68 | + # returns the file out file path on success |
| 69 | + self.signals.result.emit(self.outpath) |
| 70 | + self.signals.finished.emit() |
| 71 | + |
| 72 | + def instantiate_variable_font(self): |
| 73 | + axis_instance_data = self.axis_model.get_instance_data() |
| 74 | + instantiateVariableFont(self.ttfont, axis_instance_data, inplace=True) |
| 75 | + print("\nAXIS INSTANCE VALUES") |
| 76 | + print( |
| 77 | + f"Instantiated variable font with axis definitions:\n{axis_instance_data}" |
| 78 | + ) |
| 79 | + |
| 80 | + def edit_name_table(self): |
| 81 | + # string, nameID, platformID, platEncID, langID |
| 82 | + name_record_plat_enc_lang = (3, 1, 1033) |
| 83 | + name_instance_data = self.name_model.get_instance_data() |
| 84 | + name_table = self.ttfont["name"] |
| 85 | + # set 3, 1, 1033 name records (only!) |
| 86 | + # mandatory writes |
| 87 | + name_table.setName(name_instance_data["nameID1"], 1, *name_record_plat_enc_lang) |
| 88 | + name_table.setName(name_instance_data["nameID2"], 2, *name_record_plat_enc_lang) |
| 89 | + name_table.setName(name_instance_data["nameID3"], 3, *name_record_plat_enc_lang) |
| 90 | + name_table.setName(name_instance_data["nameID4"], 4, *name_record_plat_enc_lang) |
| 91 | + name_table.setName(name_instance_data["nameID6"], 6, *name_record_plat_enc_lang) |
| 92 | + |
| 93 | + # optional writes |
| 94 | + # Approach: |
| 95 | + # (1) if user text data exists, write it |
| 96 | + # (2) if user text data does not exist but record does, delete it |
| 97 | + # (3) otherwise do nothing |
| 98 | + if name_instance_data["nameID16"] != "": |
| 99 | + name_table.setName( |
| 100 | + name_instance_data["nameID16"], 16, *name_record_plat_enc_lang |
| 101 | + ) |
| 102 | + elif name_table.getName(16, *name_record_plat_enc_lang): |
| 103 | + name_table.removeNames(16, *name_record_plat_enc_lang) |
| 104 | + |
| 105 | + if name_instance_data["nameID17"] != "": |
| 106 | + name_table.setName( |
| 107 | + name_instance_data["nameID17"], 17, *name_record_plat_enc_lang |
| 108 | + ) |
| 109 | + elif name_table.getName(17, *name_record_plat_enc_lang): |
| 110 | + name_table.removeNames(17, *name_record_plat_enc_lang) |
| 111 | + |
| 112 | + if name_instance_data["nameID21"] != "": |
| 113 | + name_table.setName( |
| 114 | + name_instance_data["nameID21"], 21, *name_record_plat_enc_lang |
| 115 | + ) |
| 116 | + elif name_table.getName(21, *name_record_plat_enc_lang): |
| 117 | + name_table.removeNames(21, *name_record_plat_enc_lang) |
| 118 | + |
| 119 | + if name_instance_data["nameID22"] != "": |
| 120 | + name_table.setName( |
| 121 | + name_instance_data["nameID22"], 22, *name_record_plat_enc_lang |
| 122 | + ) |
| 123 | + elif name_table.getName(22, *name_record_plat_enc_lang): |
| 124 | + name_table.removeNames(22, *name_record_plat_enc_lang) |
| 125 | + |
| 126 | + # update name table data |
| 127 | + self.ttfont["name"] = name_table |
| 128 | + |
| 129 | + # print name table report |
| 130 | + print("\nNAME TABLE EDITS") |
| 131 | + print("Name records at write time:\n") |
| 132 | + print(f"nameID1: {self.ttfont['name'].getName(1, *name_record_plat_enc_lang)}") |
| 133 | + print(f"nameID2: {self.ttfont['name'].getName(2, *name_record_plat_enc_lang)}") |
| 134 | + print(f"nameID3: {self.ttfont['name'].getName(3, *name_record_plat_enc_lang)}") |
| 135 | + print(f"nameID4: {self.ttfont['name'].getName(4, *name_record_plat_enc_lang)}") |
| 136 | + print(f"nameID6: {self.ttfont['name'].getName(6, *name_record_plat_enc_lang)}") |
| 137 | + print( |
| 138 | + f"nameID16: {self.ttfont['name'].getName(16, *name_record_plat_enc_lang)}" |
| 139 | + ) |
| 140 | + print( |
| 141 | + f"nameID17: {self.ttfont['name'].getName(17, *name_record_plat_enc_lang)}" |
| 142 | + ) |
| 143 | + print( |
| 144 | + f"nameID21: {self.ttfont['name'].getName(21, *name_record_plat_enc_lang)}" |
| 145 | + ) |
| 146 | + print( |
| 147 | + f"nameID22: {self.ttfont['name'].getName(22, *name_record_plat_enc_lang)}" |
| 148 | + ) |
| 149 | + |
| 150 | + def edit_bit_flags(self): |
| 151 | + # edit the OS/2.fsSelection bit flag |
| 152 | + pre_os2_fsselection_int = self.ttfont["OS/2"].fsSelection |
| 153 | + edited_os2_fsselection_int = self.bit_model.edit_os2_fsselection_bits( |
| 154 | + pre_os2_fsselection_int |
| 155 | + ) |
| 156 | + # edit OS/2.fsSelection in the TTFont attribute |
| 157 | + self.ttfont["OS/2"].fsSelection = edited_os2_fsselection_int |
| 158 | + |
| 159 | + # edit head.macstyle bit flag |
| 160 | + pre_head_macstyle_int = self.ttfont["head"].macStyle |
| 161 | + edited_head_macstyle_int = self.bit_model.edit_head_macstyle_bits( |
| 162 | + pre_head_macstyle_int |
| 163 | + ) |
| 164 | + self.ttfont["head"].macStyle = edited_head_macstyle_int |
| 165 | + |
| 166 | + # bit flag debugging stdout report |
| 167 | + print("\nBIT FLAGS") |
| 168 | + print( |
| 169 | + f"\nOS/2.fsSelection updated with the following data:\n" |
| 170 | + f"{self.bit_model.get_os2_instance_data()}" |
| 171 | + ) |
| 172 | + print(f"Pre OS/2.fsSelection: {num2binary(pre_os2_fsselection_int, bits=16)}") |
| 173 | + print( |
| 174 | + f"Post OS/2.fsSelection: {num2binary(self.ttfont['OS/2'].fsSelection, bits=16)}" |
| 175 | + ) |
| 176 | + print( |
| 177 | + f"\nhead.macStyle bit flag updated with the following data:\n" |
| 178 | + f"{self.bit_model.get_head_instance_data()}" |
| 179 | + ) |
| 180 | + print(f"Pre head.macStyle: {num2binary(pre_head_macstyle_int, bits=16)}") |
| 181 | + print( |
| 182 | + f"Post head.macStyle: {num2binary(self.ttfont['head'].macStyle, bits=16)}" |
| 183 | + ) |
0 commit comments