Changeset 422
- Timestamp:
- 02/03/09 19:09:30 (4 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
packages/ufo2fdk/trunk/Lib/ufo2fdk/makeotfParts.py
r390 r422 5 5 from outlineOTF import OutlineOTFCompiler 6 6 from featureTableWriter import FeatureTableWriter, winStr, macStr 7 from kernFeatureWriter import KernFeatureWriter 7 8 8 9 … … 192 193 def setupFile_features(self, path): 193 194 """ 194 Make the features source file. 195 Make the features source file. If any tables 196 or the kern feature are defined in the font's 197 features, they will not be overwritten. 195 198 196 199 **This should not be called externally.** Subclasses … … 199 202 """ 200 203 # force absolute includes into the features 204 existingFeaturePath = os.path.join(self.font.path, "features.fea") 201 205 existing = forceAbsoluteIncludesInFeatures(self.font.features.text, self.font.path) 202 206 # break the features into parts 203 initialText, features, tables = breakFeaturesAndTables(existing) 204 # extract the relevant parts from the features and tables 205 gsubGpos = [initialText] 206 kernFeature = None 207 for name, text in features: 208 if name == "kern": 209 kernFeature = text 210 else: 211 gsubGpos.append(text) 212 gsubGpos = "\n".join(gsubGpos).strip() 213 if not gsubGpos: 214 gsubGpos = None 215 unknownTables = [] 216 headTable = None 217 hheaTable = None 218 os2Table = None 219 nameTable = None 220 for name, text in tables: 221 if name == "head": 222 headTable = text 223 elif name == "hhea": 224 hheaTable = text 225 elif name == "OS/2": 226 os2Table = text 227 elif name == "name": 228 nameTable = text 229 else: 230 unknownTables.append(text) 231 unknownTables = "\n".join(unknownTables) 232 # compile the new features 233 features = [ 234 self.writeFeatures_basic(gsubGpos), 235 "", 236 self.writeFeatures_kern(kernFeature), 237 "", 238 self.writeFeatures_head(headTable), 239 "", 240 self.writeFeatures_hhea(hheaTable), 241 "", 242 self.writeFeatures_OS2(os2Table), 243 "", 244 self.writeFeatures_name(nameTable), 245 "", 246 unknownTables 247 ] 248 features = "\n".join(features) 207 features, tables = extractFeaturesAndTables(existing, scannedFiles=[existingFeaturePath]) 208 # build tables that are not in the existing features 209 autoTables = {} 210 if "head" not in tables: 211 autoTables["head"] = self.writeFeatures_head() 212 if "hhea" not in tables: 213 autoTables["hhea"] = self.writeFeatures_hhea() 214 if "OS/2" not in tables: 215 autoTables["OS/2"] = self.writeFeatures_OS2() 216 if "name" not in tables: 217 autoTables["name"] = self.writeFeatures_name() 218 # build the kern feature if necessary 219 autoFeatures = {} 220 if "kern" not in features and len(self.font.kerning): 221 autoFeatures["kern"] = self.writeFeatures_kern() 222 # write the features 223 features = [existing] 224 for name, text in sorted(autoFeatures.items()): 225 features.append(text) 226 for name, text in sorted(autoTables.items()): 227 features.append(text) 228 features = "\n\n".join(features) 249 229 # write the result 250 230 f = open(path, "wb") … … 252 232 f.close() 253 233 254 def writeFeatures_basic(self, existing): 255 """ 256 Write the GSUB and GPOS features, excluding kern, 257 to a string and return it. *existing* will be 258 the existing GSUB and GPOS features in the font 259 excluding kern. 234 def writeFeatures_kern(self): 235 """ 236 Write the kern feature to a string and return it. 260 237 261 238 **This should not be called externally.** Subclasses … … 263 240 in a different way if desired. 264 241 """ 265 if existing is None: 266 return "" 267 return existing 268 269 def writeFeatures_kern(self, existing): 270 """ 271 Write the kern feature to a string and return it. 272 *existing* will be the existing kern feature in the font. 273 274 **This should not be called externally.** Subclasses 275 may override this method to handle the string creation 276 in a different way if desired. 277 """ 278 if existing is not None: 279 return existing 280 font = self.font 281 if not font.kerning.items(): 282 return "" 283 neededLeftGroups = set() 284 neededRightGroups = set() 285 noGroup = {} 286 leftGroup = {} 287 rightGroup = {} 288 bothGroup = {} 289 for (left, right), value in font.kerning.items(): 290 if left.startswith("@") and right.startswith("@"): 291 bothGroup[left, right] = _roundInt(value) 292 neededLeftGroups.add(left) 293 neededRightGroups.add(right) 294 elif left.startswith("@"): 295 leftGroup[left, right] = _roundInt(value) 296 neededLeftGroups.add(left) 297 elif right.startswith("@"): 298 rightGroup[left, right] = _roundInt(value) 299 neededRightGroups.add(right) 300 else: 301 noGroup[left, right] = _roundInt(value) 302 lines = ["feature kern {"] 303 emptyGroups = set() 304 for groupName in sorted(neededLeftGroups): 305 contents = font.groups.get(groupName, []) 306 if not contents: 307 emptyGroups.add(groupName) 308 continue 309 lines.append(" %s = [%s];" % (groupName, " ".join(contents))) 310 for groupName in sorted(neededRightGroups): 311 contents = font.groups.get(groupName, []) 312 if not contents: 313 emptyGroups.add(groupName) 314 continue 315 lines.append(" %s = [%s];" % (groupName, " ".join(contents))) 316 for (left, right), value in sorted(noGroup.items()): 317 lines.append(" pos %s %s %d;" % (left, right, value)) 318 for (left, right), value in sorted(leftGroup.items()): 319 if left in emptyGroups: 320 continue 321 lines.append(" pos %s %s %d;" % (left, right, value)) 322 for (left, right), value in sorted(rightGroup.items()): 323 if right in emptyGroups: 324 continue 325 lines.append(" pos %s %s %d;" % (left, right, value)) 326 for (left, right), value in sorted(bothGroup.items()): 327 if left in emptyGroups or right in emptyGroups: 328 continue 329 lines.append(" pos %s %s %d;" % (left, right, value)) 330 lines.append("} kern;") 331 return "\n".join(lines) 332 333 def writeFeatures_head(self, existing): 242 writer = KernFeatureWriter(self.font) 243 return writer.write() 244 245 def writeFeatures_head(self): 334 246 """ 335 247 Write the head to a string and return it. 336 *existing* will be the existing head table in the font.337 248 338 249 This gets the values for the file using the fallback … … 347 258 in a different way if desired. 348 259 """ 349 if existing is not None:350 return existing351 260 versionMajor = getattr(self.font.info, "versionMajor") 352 261 versionMinor = getattr(self.font.info, "versionMinor") … … 356 265 return writer.write() 357 266 358 def writeFeatures_hhea(self , existing):267 def writeFeatures_hhea(self): 359 268 """ 360 269 Write the hhea to a string and return it. 361 *existing* will be the existing hhea table in the font.362 270 363 271 This gets the values for the file using the fallback … … 375 283 in a different way if desired. 376 284 """ 377 if existing is not None:378 return existing379 285 ascender = getAttrWithFallback(self.font.info, "openTypeHheaAscender") 380 286 descender = getAttrWithFallback(self.font.info, "openTypeHheaDescender") … … 388 294 return writer.write() 389 295 390 def writeFeatures_name(self , existing):296 def writeFeatures_name(self): 391 297 """ 392 298 Write the name to a string and return it. 393 *existing* will be the existing name table in the font.394 299 395 300 This gets the values for the file using the fallback … … 413 318 in a different way if desired. 414 319 """ 415 if existing is not None:416 return existing417 320 idToAttr = [ 418 321 (0 , "copyright"), … … 444 347 return writer.write() 445 348 446 def writeFeatures_OS2(self , existing):349 def writeFeatures_OS2(self): 447 350 """ 448 351 Write the OS/2 to a string and return it. 449 *existing* will be the existing OS/2 table in the font.450 352 451 353 This gets the values for the file using the fallback … … 473 375 in a different way if desired. 474 376 """ 475 if existing is not None:476 return existing477 377 codePageBitTranslation = { 478 378 0 : "1252", … … 537 437 538 438 439 includeRE = re.compile( 440 "include" 441 "\(" 442 "([^\)]+)" 443 "\)" 444 ) 445 539 446 def forceAbsoluteIncludesInFeatures(text, directory): 540 447 """ … … 542 449 to absolute includes. 543 450 """ 544 includeRE = re.compile(545 "include"546 "\("547 "([^\)]+)"548 "\)"549 )550 451 for includePath in includeRE.findall(text): 551 452 currentDirectory = directory … … 605 506 ) 606 507 607 def breakFeaturesAndTables(text):508 def extractFeaturesAndTables(text, scannedFiles=[]): 608 509 # strip all comments 609 510 decommentedLines = [line.split("#")[0] for line in text.splitlines()] … … 622 523 destringedLines.append(line) 623 524 text = "\n".join(destringedLines) 525 # extract all includes 526 includes = includeRE.findall(text) 624 527 # slice off the text that comes before 625 528 # the first feature/table definition … … 636 539 broken = _textBreakRecurse(text) 637 540 # organize into tables and features 638 features = []639 tables = []541 features = {} 542 tables = {} 640 543 for text in broken: 641 544 text = text.strip() … … 652 555 featureMatch = featureNameRE.search(text) 653 556 if featureMatch is not None: 654 features .append((featureMatch.group(1), finalText))557 features[featureMatch.group(1)] = finalText 655 558 else: 656 559 tableMatch = tableNameRE.search(text) 657 tables.append((tableMatch.group(1), finalText)) 658 return precedingText, features, tables 560 tables[tableMatch.group(1)] = finalText 561 # scan all includes 562 for path in includes: 563 if path in scannedFiles: 564 continue 565 scannedFiles.append(path) 566 if os.path.exists(path): 567 f = open(path, "r") 568 text = f.read() 569 f.close() 570 f, t = extractFeaturesAndTables(text, scannedFiles) 571 features.update(f) 572 tables.update(t) 573 return features, tables 659 574 660 575 def _textBreakRecurse(text): … … 690 605 691 606 692 breakFeaturesAndTablesTestText = """607 extractFeaturesAndTablesTestText = """ 693 608 @foo = [bar]; 694 609 … … 707 622 """ 708 623 709 breakFeaturesAndTablesTestResult = ('@foo = [bar];', 710 [('fts2', 'feature fts2 {\n sub foo by bar;\n} fts2;'), 711 ('fts3', 'feature fts3 { sub a by b;} fts3;')], 712 [('tts1', 713 'table tts1 {\n nameid 1 "feature this { is not really a \\"feature that { other thing is";\n} tts1;')]) 624 #extractFeaturesAndTablesTestResult = ('@foo = [bar];', 625 # [('fts2', 'feature fts2 {\n sub foo by bar;\n} fts2;'), 626 # ('fts3', 'feature fts3 { sub a by b;} fts3;')], 627 # [('tts1', 628 # 'table tts1 {\n nameid 1 "feature this { is not really a \\"feature that { other thing is";\n} tts1;')]) 629 630 extractFeaturesAndTablesTestResult = ( 631 { 632 'fts2': 'feature fts2 {\n sub foo by bar;\n} fts2;', 633 'fts3': 'feature fts3 { sub a by b;} fts3;' 634 }, 635 { 636 'tts1': 'table tts1 {\n nameid 1 "feature this { is not really a \\"feature that { other thing is";\n} tts1;' 637 } 638 ) 714 639 715 640 def testBreakFeaturesAndTables(): 716 641 """ 717 >>> r = breakFeaturesAndTables(breakFeaturesAndTablesTestText)718 >>> r == breakFeaturesAndTablesTestResult642 >>> r = extractFeaturesAndTables(extractFeaturesAndTablesTestText) 643 >>> r == extractFeaturesAndTablesTestResult 719 644 True 720 645 """
Note: See TracChangeset
for help on using the changeset viewer.
