1from fontTools.voltLib.error import VoltLibError 2from typing import NamedTuple 3 4 5class Pos(NamedTuple): 6 adv: int 7 dx: int 8 dy: int 9 adv_adjust_by: dict 10 dx_adjust_by: dict 11 dy_adjust_by: dict 12 13 def __str__(self): 14 res = " POS" 15 for attr in ("adv", "dx", "dy"): 16 value = getattr(self, attr) 17 if value is not None: 18 res += f" {attr.upper()} {value}" 19 adjust_by = getattr(self, f"{attr}_adjust_by", {}) 20 for size, adjustment in adjust_by.items(): 21 res += f" ADJUST_BY {adjustment} AT {size}" 22 res += " END_POS" 23 return res 24 25 26class Element(object): 27 def __init__(self, location=None): 28 self.location = location 29 30 def build(self, builder): 31 pass 32 33 def __str__(self): 34 raise NotImplementedError 35 36 37class Statement(Element): 38 pass 39 40 41class Expression(Element): 42 pass 43 44 45class VoltFile(Statement): 46 def __init__(self): 47 Statement.__init__(self, location=None) 48 self.statements = [] 49 50 def build(self, builder): 51 for s in self.statements: 52 s.build(builder) 53 54 def __str__(self): 55 return "\n" + "\n".join(str(s) for s in self.statements) + " END\n" 56 57 58class GlyphDefinition(Statement): 59 def __init__(self, name, gid, gunicode, gtype, components, location=None): 60 Statement.__init__(self, location) 61 self.name = name 62 self.id = gid 63 self.unicode = gunicode 64 self.type = gtype 65 self.components = components 66 67 def __str__(self): 68 res = f'DEF_GLYPH "{self.name}" ID {self.id}' 69 if self.unicode is not None: 70 if len(self.unicode) > 1: 71 unicodes = ",".join(f"U+{u:04X}" for u in self.unicode) 72 res += f' UNICODEVALUES "{unicodes}"' 73 else: 74 res += f" UNICODE {self.unicode[0]}" 75 if self.type is not None: 76 res += f" TYPE {self.type}" 77 if self.components is not None: 78 res += f" COMPONENTS {self.components}" 79 res += " END_GLYPH" 80 return res 81 82 83class GroupDefinition(Statement): 84 def __init__(self, name, enum, location=None): 85 Statement.__init__(self, location) 86 self.name = name 87 self.enum = enum 88 self.glyphs_ = None 89 90 def glyphSet(self, groups=None): 91 if groups is not None and self.name in groups: 92 raise VoltLibError( 93 'Group "%s" contains itself.' % (self.name), self.location 94 ) 95 if self.glyphs_ is None: 96 if groups is None: 97 groups = set({self.name}) 98 else: 99 groups.add(self.name) 100 self.glyphs_ = self.enum.glyphSet(groups) 101 return self.glyphs_ 102 103 def __str__(self): 104 enum = self.enum and str(self.enum) or "" 105 return f'DEF_GROUP "{self.name}"\n{enum}\nEND_GROUP' 106 107 108class GlyphName(Expression): 109 """A single glyph name, such as cedilla.""" 110 111 def __init__(self, glyph, location=None): 112 Expression.__init__(self, location) 113 self.glyph = glyph 114 115 def glyphSet(self): 116 return (self.glyph,) 117 118 def __str__(self): 119 return f' GLYPH "{self.glyph}"' 120 121 122class Enum(Expression): 123 """An enum""" 124 125 def __init__(self, enum, location=None): 126 Expression.__init__(self, location) 127 self.enum = enum 128 129 def __iter__(self): 130 for e in self.glyphSet(): 131 yield e 132 133 def glyphSet(self, groups=None): 134 glyphs = [] 135 for element in self.enum: 136 if isinstance(element, (GroupName, Enum)): 137 glyphs.extend(element.glyphSet(groups)) 138 else: 139 glyphs.extend(element.glyphSet()) 140 return tuple(glyphs) 141 142 def __str__(self): 143 enum = "".join(str(e) for e in self.enum) 144 return f" ENUM{enum} END_ENUM" 145 146 147class GroupName(Expression): 148 """A glyph group""" 149 150 def __init__(self, group, parser, location=None): 151 Expression.__init__(self, location) 152 self.group = group 153 self.parser_ = parser 154 155 def glyphSet(self, groups=None): 156 group = self.parser_.resolve_group(self.group) 157 if group is not None: 158 self.glyphs_ = group.glyphSet(groups) 159 return self.glyphs_ 160 else: 161 raise VoltLibError( 162 'Group "%s" is used but undefined.' % (self.group), self.location 163 ) 164 165 def __str__(self): 166 return f' GROUP "{self.group}"' 167 168 169class Range(Expression): 170 """A glyph range""" 171 172 def __init__(self, start, end, parser, location=None): 173 Expression.__init__(self, location) 174 self.start = start 175 self.end = end 176 self.parser = parser 177 178 def glyphSet(self): 179 return tuple(self.parser.glyph_range(self.start, self.end)) 180 181 def __str__(self): 182 return f' RANGE "{self.start}" TO "{self.end}"' 183 184 185class ScriptDefinition(Statement): 186 def __init__(self, name, tag, langs, location=None): 187 Statement.__init__(self, location) 188 self.name = name 189 self.tag = tag 190 self.langs = langs 191 192 def __str__(self): 193 res = "DEF_SCRIPT" 194 if self.name is not None: 195 res += f' NAME "{self.name}"' 196 res += f' TAG "{self.tag}"\n\n' 197 for lang in self.langs: 198 res += f"{lang}" 199 res += "END_SCRIPT" 200 return res 201 202 203class LangSysDefinition(Statement): 204 def __init__(self, name, tag, features, location=None): 205 Statement.__init__(self, location) 206 self.name = name 207 self.tag = tag 208 self.features = features 209 210 def __str__(self): 211 res = "DEF_LANGSYS" 212 if self.name is not None: 213 res += f' NAME "{self.name}"' 214 res += f' TAG "{self.tag}"\n\n' 215 for feature in self.features: 216 res += f"{feature}" 217 res += "END_LANGSYS\n" 218 return res 219 220 221class FeatureDefinition(Statement): 222 def __init__(self, name, tag, lookups, location=None): 223 Statement.__init__(self, location) 224 self.name = name 225 self.tag = tag 226 self.lookups = lookups 227 228 def __str__(self): 229 res = f'DEF_FEATURE NAME "{self.name}" TAG "{self.tag}"\n' 230 res += " " + " ".join(f'LOOKUP "{l}"' for l in self.lookups) + "\n" 231 res += "END_FEATURE\n" 232 return res 233 234 235class LookupDefinition(Statement): 236 def __init__( 237 self, 238 name, 239 process_base, 240 process_marks, 241 mark_glyph_set, 242 direction, 243 reversal, 244 comments, 245 context, 246 sub, 247 pos, 248 location=None, 249 ): 250 Statement.__init__(self, location) 251 self.name = name 252 self.process_base = process_base 253 self.process_marks = process_marks 254 self.mark_glyph_set = mark_glyph_set 255 self.direction = direction 256 self.reversal = reversal 257 self.comments = comments 258 self.context = context 259 self.sub = sub 260 self.pos = pos 261 262 def __str__(self): 263 res = f'DEF_LOOKUP "{self.name}"' 264 res += f' {self.process_base and "PROCESS_BASE" or "SKIP_BASE"}' 265 if self.process_marks: 266 res += " PROCESS_MARKS " 267 if self.mark_glyph_set: 268 res += f'MARK_GLYPH_SET "{self.mark_glyph_set}"' 269 elif isinstance(self.process_marks, str): 270 res += f'"{self.process_marks}"' 271 else: 272 res += "ALL" 273 else: 274 res += " SKIP_MARKS" 275 if self.direction is not None: 276 res += f" DIRECTION {self.direction}" 277 if self.reversal: 278 res += " REVERSAL" 279 if self.comments is not None: 280 comments = self.comments.replace("\n", r"\n") 281 res += f'\nCOMMENTS "{comments}"' 282 if self.context: 283 res += "\n" + "\n".join(str(c) for c in self.context) 284 else: 285 res += "\nIN_CONTEXT\nEND_CONTEXT" 286 if self.sub: 287 res += f"\n{self.sub}" 288 if self.pos: 289 res += f"\n{self.pos}" 290 return res 291 292 293class SubstitutionDefinition(Statement): 294 def __init__(self, mapping, location=None): 295 Statement.__init__(self, location) 296 self.mapping = mapping 297 298 def __str__(self): 299 res = "AS_SUBSTITUTION\n" 300 for src, dst in self.mapping.items(): 301 src = "".join(str(s) for s in src) 302 dst = "".join(str(d) for d in dst) 303 res += f"SUB{src}\nWITH{dst}\nEND_SUB\n" 304 res += "END_SUBSTITUTION" 305 return res 306 307 308class SubstitutionSingleDefinition(SubstitutionDefinition): 309 pass 310 311 312class SubstitutionMultipleDefinition(SubstitutionDefinition): 313 pass 314 315 316class SubstitutionLigatureDefinition(SubstitutionDefinition): 317 pass 318 319 320class SubstitutionReverseChainingSingleDefinition(SubstitutionDefinition): 321 pass 322 323 324class PositionAttachDefinition(Statement): 325 def __init__(self, coverage, coverage_to, location=None): 326 Statement.__init__(self, location) 327 self.coverage = coverage 328 self.coverage_to = coverage_to 329 330 def __str__(self): 331 coverage = "".join(str(c) for c in self.coverage) 332 res = f"AS_POSITION\nATTACH{coverage}\nTO" 333 for coverage, anchor in self.coverage_to: 334 coverage = "".join(str(c) for c in coverage) 335 res += f'{coverage} AT ANCHOR "{anchor}"' 336 res += "\nEND_ATTACH\nEND_POSITION" 337 return res 338 339 340class PositionAttachCursiveDefinition(Statement): 341 def __init__(self, coverages_exit, coverages_enter, location=None): 342 Statement.__init__(self, location) 343 self.coverages_exit = coverages_exit 344 self.coverages_enter = coverages_enter 345 346 def __str__(self): 347 res = "AS_POSITION\nATTACH_CURSIVE" 348 for coverage in self.coverages_exit: 349 coverage = "".join(str(c) for c in coverage) 350 res += f"\nEXIT {coverage}" 351 for coverage in self.coverages_enter: 352 coverage = "".join(str(c) for c in coverage) 353 res += f"\nENTER {coverage}" 354 res += "\nEND_ATTACH\nEND_POSITION" 355 return res 356 357 358class PositionAdjustPairDefinition(Statement): 359 def __init__(self, coverages_1, coverages_2, adjust_pair, location=None): 360 Statement.__init__(self, location) 361 self.coverages_1 = coverages_1 362 self.coverages_2 = coverages_2 363 self.adjust_pair = adjust_pair 364 365 def __str__(self): 366 res = "AS_POSITION\nADJUST_PAIR\n" 367 for coverage in self.coverages_1: 368 coverage = " ".join(str(c) for c in coverage) 369 res += f" FIRST {coverage}" 370 res += "\n" 371 for coverage in self.coverages_2: 372 coverage = " ".join(str(c) for c in coverage) 373 res += f" SECOND {coverage}" 374 res += "\n" 375 for (id_1, id_2), (pos_1, pos_2) in self.adjust_pair.items(): 376 res += f" {id_1} {id_2} BY{pos_1}{pos_2}\n" 377 res += "\nEND_ADJUST\nEND_POSITION" 378 return res 379 380 381class PositionAdjustSingleDefinition(Statement): 382 def __init__(self, adjust_single, location=None): 383 Statement.__init__(self, location) 384 self.adjust_single = adjust_single 385 386 def __str__(self): 387 res = "AS_POSITION\nADJUST_SINGLE" 388 for coverage, pos in self.adjust_single: 389 coverage = "".join(str(c) for c in coverage) 390 res += f"{coverage} BY{pos}" 391 res += "\nEND_ADJUST\nEND_POSITION" 392 return res 393 394 395class ContextDefinition(Statement): 396 def __init__(self, ex_or_in, left=None, right=None, location=None): 397 Statement.__init__(self, location) 398 self.ex_or_in = ex_or_in 399 self.left = left if left is not None else [] 400 self.right = right if right is not None else [] 401 402 def __str__(self): 403 res = self.ex_or_in + "\n" 404 for coverage in self.left: 405 coverage = "".join(str(c) for c in coverage) 406 res += f" LEFT{coverage}\n" 407 for coverage in self.right: 408 coverage = "".join(str(c) for c in coverage) 409 res += f" RIGHT{coverage}\n" 410 res += "END_CONTEXT" 411 return res 412 413 414class AnchorDefinition(Statement): 415 def __init__(self, name, gid, glyph_name, component, locked, pos, location=None): 416 Statement.__init__(self, location) 417 self.name = name 418 self.gid = gid 419 self.glyph_name = glyph_name 420 self.component = component 421 self.locked = locked 422 self.pos = pos 423 424 def __str__(self): 425 locked = self.locked and " LOCKED" or "" 426 return ( 427 f'DEF_ANCHOR "{self.name}"' 428 f" ON {self.gid}" 429 f" GLYPH {self.glyph_name}" 430 f" COMPONENT {self.component}" 431 f"{locked}" 432 f" AT {self.pos} END_ANCHOR" 433 ) 434 435 436class SettingDefinition(Statement): 437 def __init__(self, name, value, location=None): 438 Statement.__init__(self, location) 439 self.name = name 440 self.value = value 441 442 def __str__(self): 443 if self.value is True: 444 return f"{self.name}" 445 if isinstance(self.value, (tuple, list)): 446 value = " ".join(str(v) for v in self.value) 447 return f"{self.name} {value}" 448 return f"{self.name} {self.value}" 449