def merge(merger, self, lst): assert self.Format == 1 XCoords = [a.XCoordinate for a in lst] YCoords = [a.YCoordinate for a in lst] model = merger.model scalars = merger.scalars self.XCoordinate = otRound(model.interpolateFromMastersAndScalars(XCoords, scalars)) self.YCoordinate = otRound(model.interpolateFromMastersAndScalars(YCoords, scalars))
def merge(merger, self, lst): assert self.Format == 1 XCoords = [a.XCoordinate for a in lst] YCoords = [a.YCoordinate for a in lst] model = merger.model scalars = merger.scalars self.XCoordinate = otRound(model.interpolateFromMastersAndScalars(XCoords, scalars)) self.YCoordinate = otRound(model.interpolateFromMastersAndScalars(YCoords, scalars))
def _marksAsAST(self): return [ [ (ast.Anchor(x=otRound(anchor.x), y=otRound(anchor.y)), anchor.markClass) for anchor in sorted(component, key=lambda a: a.name) ] for component in self.marks ]
def transformAnchor(anchor, matrix): if not anchor: return anchor from fontTools.misc.fixedTools import otRound anchor.x, anchor.y = matrix.transformPoint((anchor.x, anchor.y)) anchor.x = otRound(anchor.x) anchor.y = otRound(anchor.y) return anchor
def roundDeltas(self): coordWidth = self.getCoordWidth() self.coordinates = [ None if d is None else otRound(d) if coordWidth == 1 else (otRound(d[0]), otRound(d[1])) for d in self.coordinates ]
def replace(match, factor): pos = Parser(StringIO(match.group(0))).parse_pos_() if not any(pos): return match.group(0) adv, dx, dy, adv_adjust_by, dx_adjust_by, dy_adjust_by = pos ret = "" if adv is not None: adv = otRound(adv * factor) ret += f" ADV {adv:g}" for at, adjust_by in adv_adjust_by.items(): at, adjust_by = otRound(at * factor), otRound(adjust_by * factor) ret += f" ADJUST_BY {adjust_by} AT {at}" if dx is not None: dx = otRound(dx * factor) ret += f" DX {dx:g}" for at, adjust_by in dx_adjust_by.items(): at, adjust_by = otRound(at * factor), otRound(adjust_by * factor) ret += f" ADJUST_BY {adjust_by} AT {at}" if dy is not None: dy = otRound(dy * factor) ret += f" DY {dy:g}" for at, adjust_by in dy_adjust_by.items(): at, adjust_by = otRound(at * factor), otRound(adjust_by * factor) ret += f" ADJUST_BY {adjust_by} AT {at}" return f"POS{ret} END_POS"
def build_hmtx(self): ctx = self.ctx data = self.metadataProvider self.otf["hmtx"] = hmtx = ttLib.newTable("hmtx") hmtx.metrics = {} for glyphName, glyph in self.glyphMap.items(): width = otRound(data.glyph_width(ctx, glyph)) bounds = self.glyphBoundsMap[glyphName] left = otRound(bounds.left) if not bounds.empty else 0 hmtx[glyphName] = (width, left)
def _setCoordinates(self, glyphName, coord, hMetrics, vMetrics=None): """Set coordinates and metrics for the given glyph. "coord" is an array of GlyphCoordinates which must include the "phantom points" as the last four coordinates. Both the horizontal/vertical advances and left/top sidebearings in "hmtx" and "vmtx" tables (if any) are updated from four phantom points and the glyph's bounding boxes. The "hMetrics" and vMetrics are used to propagate "phantom points" into "hmtx" and "vmtx" tables if desired. (see the "_getPhantomPoints" method). """ glyph = self[glyphName] # Handle phantom points for (left, right, top, bottom) positions. assert len(coord) >= 4 leftSideX = coord[-4][0] rightSideX = coord[-3][0] topSideY = coord[-2][1] bottomSideY = coord[-1][1] coord = coord[:-4] if glyph.isComposite(): assert len(coord) == len(glyph.components) for p, comp in zip(coord, glyph.components): if hasattr(comp, 'x'): comp.x, comp.y = p elif glyph.numberOfContours == 0: assert len(coord) == 0 else: assert len(coord) == len(glyph.coordinates) glyph.coordinates = GlyphCoordinates(coord) glyph.recalcBounds(self) horizontalAdvanceWidth = otRound(rightSideX - leftSideX) if horizontalAdvanceWidth < 0: # unlikely, but it can happen, see: # https://github.com/fonttools/fonttools/pull/1198 horizontalAdvanceWidth = 0 leftSideBearing = otRound(glyph.xMin - leftSideX) hMetrics[glyphName] = horizontalAdvanceWidth, leftSideBearing if vMetrics is not None: verticalAdvanceWidth = otRound(topSideY - bottomSideY) if verticalAdvanceWidth < 0: # unlikely but do the same as horizontal verticalAdvanceWidth = 0 topSideBearing = otRound(topSideY - glyph.yMax) vMetrics[glyphName] = verticalAdvanceWidth, topSideBearing
def get_origin_height(self, font, origin): if origin is self.Origin.BASELINE: return 0 elif origin is self.Origin.CAP_HEIGHT: return getAttrWithFallback(font.info, "capHeight") elif origin is self.Origin.HALF_CAP_HEIGHT: return otRound(getAttrWithFallback(font.info, "capHeight") / 2) elif origin is self.Origin.X_HEIGHT: return getAttrWithFallback(font.info, "xHeight") elif origin is self.Origin.HALF_X_HEIGHT: return otRound(getAttrWithFallback(font.info, "xHeight") / 2) else: raise AssertionError(origin)
def get_origin_height(self, font, origin): if origin is self.Origin.BASELINE: return 0 elif origin is self.Origin.CAP_HEIGHT: return font.info.capHeight elif origin is self.Origin.HALF_CAP_HEIGHT: return otRound(font.info.capHeight / 2) elif origin is self.Origin.X_HEIGHT: return font.info.xHeight elif origin is self.Origin.HALF_X_HEIGHT: return otRound(font.info.xHeight / 2) else: raise AssertionError(origin)
def compile(self, ttFont): metrics = [] hasNegativeAdvances = False for glyphName in ttFont.getGlyphOrder(): advanceWidth, sideBearing = self.metrics[glyphName] if advanceWidth < 0: log.error("Glyph %r has negative advance %s" % (glyphName, self.advanceName)) hasNegativeAdvances = True metrics.append([advanceWidth, sideBearing]) headerTable = ttFont.get(self.headerTag) if headerTable is not None: lastAdvance = metrics[-1][0] lastIndex = len(metrics) while metrics[lastIndex - 2][0] == lastAdvance: lastIndex -= 1 if lastIndex <= 1: # all advances are equal lastIndex = 1 break additionalMetrics = metrics[lastIndex:] additionalMetrics = [otRound(sb) for _, sb in additionalMetrics] metrics = metrics[:lastIndex] numberOfMetrics = len(metrics) setattr(headerTable, self.numberOfMetricsName, numberOfMetrics) else: # no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs numberOfMetrics = ttFont["maxp"].numGlyphs additionalMetrics = [] allMetrics = [] for advance, sb in metrics: allMetrics.extend([otRound(advance), otRound(sb)]) metricsFmt = ">" + self.longMetricFormat * numberOfMetrics try: data = struct.pack(metricsFmt, *allMetrics) except struct.error as e: if "out of range" in str(e) and hasNegativeAdvances: raise ttLib.TTLibError( "'%s' table can't contain negative advance %ss" % (self.tableTag, self.advanceName)) else: raise additionalMetrics = array.array("h", additionalMetrics) if sys.byteorder != "big": additionalMetrics.byteswap() data = data + additionalMetrics.tostring() return data
def compile(self, ttFont): metrics = [] hasNegativeAdvances = False for glyphName in ttFont.getGlyphOrder(): advanceWidth, sideBearing = self.metrics[glyphName] if advanceWidth < 0: log.error("Glyph %r has negative advance %s" % ( glyphName, self.advanceName)) hasNegativeAdvances = True metrics.append([advanceWidth, sideBearing]) headerTable = ttFont.get(self.headerTag) if headerTable is not None: lastAdvance = metrics[-1][0] lastIndex = len(metrics) while metrics[lastIndex-2][0] == lastAdvance: lastIndex -= 1 if lastIndex <= 1: # all advances are equal lastIndex = 1 break additionalMetrics = metrics[lastIndex:] additionalMetrics = [otRound(sb) for _, sb in additionalMetrics] metrics = metrics[:lastIndex] numberOfMetrics = len(metrics) setattr(headerTable, self.numberOfMetricsName, numberOfMetrics) else: # no hhea/vhea, can't store numberOfMetrics; assume == numGlyphs numberOfMetrics = ttFont["maxp"].numGlyphs additionalMetrics = [] allMetrics = [] for advance, sb in metrics: allMetrics.extend([otRound(advance), otRound(sb)]) metricsFmt = ">" + self.longMetricFormat * numberOfMetrics try: data = struct.pack(metricsFmt, *allMetrics) except struct.error as e: if "out of range" in str(e) and hasNegativeAdvances: raise ttLib.TTLibError( "'%s' table can't contain negative advance %ss" % (self.tableTag, self.advanceName)) else: raise additionalMetrics = array.array("h", additionalMetrics) if sys.byteorder != "big": additionalMetrics.byteswap() data = data + additionalMetrics.tostring() return data
def storeDeltas(self, deltas): # Pity that this exists here, since VarData_addItem # does the same. But to look into our cache, it's # good to adjust deltas here as well... deltas = [otRound(d) for d in deltas] if len(deltas) == len(self._supports) + 1: deltas = tuple(deltas[1:]) else: assert len(deltas) == len(self._supports) deltas = tuple(deltas) varIdx = self._cache.get(deltas) if varIdx is not None: return varIdx if not self._data: self._add_VarData() inner = len(self._data.Item) if inner == 0xFFFF: # Full array. Start new one. self._add_VarData() return self.storeDeltas(deltas) self._data.addItem(deltas) varIdx = (self._outer << 16) + inner self._cache[deltas] = varIdx return varIdx
def encodeDeltaRunAsBytes_(deltas, offset, stream): runLength = 0 pos = offset numDeltas = len(deltas) while pos < numDeltas and runLength < 64: value = deltas[pos] if value < -128 or value > 127: break # Within a byte-encoded run of deltas, a single zero # is best stored literally as 0x00 value. However, # if are two or more zeroes in a sequence, it is # better to start a new run. For example, the sequence # of deltas [15, 15, 0, 15, 15] becomes 6 bytes # (04 0F 0F 00 0F 0F) when storing the zero value # literally, but 7 bytes (01 0F 0F 80 01 0F 0F) # when starting a new run. if value == 0 and pos + 1 < numDeltas and deltas[pos + 1] == 0: break pos += 1 runLength += 1 assert runLength >= 1 and runLength <= 64 stream.write(bytechr(runLength - 1)) for i in range(offset, pos): stream.write(struct.pack('b', otRound(deltas[i]))) return pos
def storeDeltas(self, deltas): # Pity that this exists here, since VarData_addItem # does the same. But to look into our cache, it's # good to adjust deltas here as well... deltas = [otRound(d) for d in deltas] if len(deltas) == len(self._supports) + 1: deltas = tuple(deltas[1:]) else: assert len(deltas) == len(self._supports) deltas = tuple(deltas) varIdx = self._cache.get(deltas) if varIdx is not None: return varIdx if not self._data: self._add_VarData() inner = len(self._data.Item) if inner == 0xFFFF: # Full array. Start new one. self._add_VarData() return self.storeDeltas(deltas) self._data.addItem(deltas) varIdx = (self._outer << 16) + inner self._cache[deltas] = varIdx return varIdx
def toInt(self): if not self.isFloat(): return a = array.array("h") for n in self._a: a.append(otRound(n)) self._a = a
def toInt(self): if not self.isFloat(): return a = array.array("h") for n in self._a: a.append(otRound(n)) self._a = a
def encodeDeltaRunAsBytes_(deltas, offset, stream): runLength = 0 pos = offset numDeltas = len(deltas) while pos < numDeltas and runLength < 64: value = deltas[pos] if value < -128 or value > 127: break # Within a byte-encoded run of deltas, a single zero # is best stored literally as 0x00 value. However, # if are two or more zeroes in a sequence, it is # better to start a new run. For example, the sequence # of deltas [15, 15, 0, 15, 15] becomes 6 bytes # (04 0F 0F 00 0F 0F) when storing the zero value # literally, but 7 bytes (01 0F 0F 80 01 0F 0F) # when starting a new run. if value == 0 and pos+1 < numDeltas and deltas[pos+1] == 0: break pos += 1 runLength += 1 assert runLength >= 1 and runLength <= 64 stream.write(bytechr(runLength - 1)) for i in range(offset, pos): stream.write(struct.pack('b', otRound(deltas[i]))) return pos
def encodeDeltaRunAsWords_(deltas, offset, stream): runLength = 0 pos = offset numDeltas = len(deltas) while pos < numDeltas and runLength < 64: value = deltas[pos] # Within a word-encoded run of deltas, it is easiest # to start a new run (with a different encoding) # whenever we encounter a zero value. For example, # the sequence [0x6666, 0, 0x7777] needs 7 bytes when # storing the zero literally (42 66 66 00 00 77 77), # and equally 7 bytes when starting a new run # (40 66 66 80 40 77 77). if value == 0: break # Within a word-encoded run of deltas, a single value # in the range (-128..127) should be encoded literally # because it is more compact. For example, the sequence # [0x6666, 2, 0x7777] becomes 7 bytes when storing # the value literally (42 66 66 00 02 77 77), but 8 bytes # when starting a new run (40 66 66 00 02 40 77 77). isByteEncodable = lambda value: value >= -128 and value <= 127 if isByteEncodable(value) and pos+1 < numDeltas and isByteEncodable(deltas[pos+1]): break pos += 1 runLength += 1 assert runLength >= 1 and runLength <= 64 stream.write(bytechr(DELTAS_ARE_WORDS | (runLength - 1))) for i in range(offset, pos): stream.write(struct.pack('>h', otRound(deltas[i]))) return pos
def build_post(self): ctx = self.ctx data = self.metadataProvider self.otf["post"] = post = ttLib.newTable("post") post.formatType = 3.0 post.italicAngle = data.italicAngle(ctx) post.underlinePosition = otRound(data.post_underlinePosition(ctx)) post.underlineThickness = otRound(data.post_underlineThickness(ctx)) post.isFixedPitch = data.post_isFixedPitch(ctx) post.minMemType42 = 0 post.maxMemType42 = 0 post.minMemType1 = 0 post.maxMemType1 = 0
def merge(merger, self, lst): # All other structs are merged with self pointing to a copy of base font, # except for ValueRecords which are sometimes created later and initialized # to have 0/None members. Hence the copy. self.__dict__ = lst[0].__dict__.copy() instancer = merger.instancer # TODO Handle differing valueformats for name, tableName in [('XAdvance', 'XAdvDevice'), ('YAdvance', 'YAdvDevice'), ('XPlacement', 'XPlaDevice'), ('YPlacement', 'YPlaDevice')]: if not hasattr(self, tableName): continue dev = getattr(self, tableName) delattr(self, tableName) if dev is None: continue assert dev.DeltaFormat == 0x8000 varidx = (dev.StartSize << 16) + dev.EndSize delta = otRound(instancer[varidx]) setattr(self, name, getattr(self, name) + delta)
def merge(merger, self, lst): # Hack till we become selfless. self.__dict__ = lst[0].__dict__.copy() if self.Format != 3: return instancer = merger.instancer for v in "XY": tableName = v+'DeviceTable' if not hasattr(self, tableName): continue dev = getattr(self, tableName) delattr(self, tableName) if dev is None: continue assert dev.DeltaFormat == 0x8000 varidx = (dev.StartSize << 16) + dev.EndSize delta = otRound(instancer[varidx]) attr = v+'Coordinate' setattr(self, attr, getattr(self, attr) + delta) self.Format = 1
def interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder): charstrings = topDict.CharStrings for gname in glyphOrder: # Interpolate charstring charstring = charstrings[gname] pd = charstring.private vsindex = pd.vsindex if (hasattr(pd, 'vsindex')) else 0 num_regions = pd.getNumRegions(vsindex) numMasters = num_regions + 1 new_program = [] last_i = 0 for i, token in enumerate(charstring.program): if token == 'blend': num_args = charstring.program[i - 1] """ The stack is now: ..args for following operations num_args values from the default font num_args tuples, each with numMasters-1 delta values num_blend_args 'blend' """ argi = i - (num_args*numMasters + 1) end_args = tuplei = argi + num_args while argi < end_args: next_ti = tuplei + num_regions deltas = charstring.program[tuplei:next_ti] delta = interpolateFromDeltas(vsindex, deltas) charstring.program[argi] += otRound(delta) tuplei = next_ti argi += 1 new_program.extend(charstring.program[last_i:end_args]) last_i = i + 1 if last_i != 0: new_program.extend(charstring.program[last_i:]) charstring.program = new_program
def merge(merger, self, lst): # Hack till we become selfless. self.__dict__ = lst[0].__dict__.copy() if self.Format != 3: return instancer = merger.instancer for v in "XY": tableName = v + 'DeviceTable' if not hasattr(self, tableName): continue dev = getattr(self, tableName) if merger.deleteVariations: delattr(self, tableName) if dev is None: continue assert dev.DeltaFormat == 0x8000 varidx = (dev.StartSize << 16) + dev.EndSize delta = otRound(instancer[varidx]) attr = v + 'Coordinate' setattr(self, attr, getattr(self, attr) + delta) if merger.deleteVariations: self.Format = 1
def draw_charstring(self, glyph, private, globalSubrs): ctx = self.ctx data = self.metadataProvider layer = data.glyph_layer(ctx, glyph) width = data.layer_width(ctx, layer) defaultWidth = private.defaultWidthX nominalWidth = private.nominalWidthX if width == defaultWidth: # if width equals the default it can be omitted from charstring width = None else: # subtract the nominal width width -= nominalWidth if width is not None: width = otRound(width) pen = T2CharStringPen(width, self.glyphOrder, roundTolerance=self.roundTolerance) drawing.draw_layer(layer, pen) charString = pen.getCharString(private, globalSubrs, optimize=self.optimizeCFF) return charString
def interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder): charstrings = topDict.CharStrings for gname in glyphOrder: # Interpolate charstring charstring = charstrings[gname] pd = charstring.private vsindex = pd.vsindex if (hasattr(pd, 'vsindex')) else 0 num_regions = pd.getNumRegions(vsindex) numMasters = num_regions + 1 new_program = [] last_i = 0 for i, token in enumerate(charstring.program): if token == 'blend': num_args = charstring.program[i - 1] """ The stack is now: ..args for following operations num_args values from the default font num_args tuples, each with numMasters-1 delta values num_blend_args 'blend' """ argi = i - (num_args*numMasters + 1) end_args = tuplei = argi + num_args while argi < end_args: next_ti = tuplei + num_regions deltas = charstring.program[tuplei:next_ti] delta = interpolateFromDeltas(vsindex, deltas) charstring.program[argi] += otRound(delta) tuplei = next_ti argi += 1 new_program.extend(charstring.program[last_i:end_args]) last_i = i + 1 if last_i != 0: new_program.extend(charstring.program[last_i:]) charstring.program = new_program
def encodeDeltaRunAsWords_(deltas, offset, stream): runLength = 0 pos = offset numDeltas = len(deltas) while pos < numDeltas and runLength < 64: value = deltas[pos] # Within a word-encoded run of deltas, it is easiest # to start a new run (with a different encoding) # whenever we encounter a zero value. For example, # the sequence [0x6666, 0, 0x7777] needs 7 bytes when # storing the zero literally (42 66 66 00 00 77 77), # and equally 7 bytes when starting a new run # (40 66 66 80 40 77 77). if value == 0: break # Within a word-encoded run of deltas, a single value # in the range (-128..127) should be encoded literally # because it is more compact. For example, the sequence # [0x6666, 2, 0x7777] becomes 7 bytes when storing # the value literally (42 66 66 00 02 77 77), but 8 bytes # when starting a new run (40 66 66 00 02 40 77 77). isByteEncodable = lambda value: value >= -128 and value <= 127 if isByteEncodable( value) and pos + 1 < numDeltas and isByteEncodable( deltas[pos + 1]): break pos += 1 runLength += 1 assert runLength >= 1 and runLength <= 64 stream.write(bytechr(DELTAS_ARE_WORDS | (runLength - 1))) for i in range(offset, pos): stream.write(struct.pack('>h', otRound(deltas[i]))) return pos
def encodeFixed(f, pack=struct.pack): """For T2 only""" value = otRound(f * 65536) # convert the float to fixed point if value & 0xFFFF == 0: # check if the fractional part is zero return encodeIntT2(value >> 16) # encode only the integer part else: return b"\xff" + pack(">l", value) # encode the entire fixed point value
def encodeFixed(f, pack=struct.pack): """For T2 only""" value = otRound(f * 65536) # convert the float to fixed point if value & 0xFFFF == 0: # check if the fractional part is zero return encodeIntT2(value >> 16) # encode only the integer part else: return b"\xff" + pack(">l", value) # encode the entire fixed point value
def fix_isFixedPitch(ttfont): same_width = set() glyph_metrics = ttfont['hmtx'].metrics for character in [chr(c) for c in range(65, 91)]: same_width.add(glyph_metrics[character][0]) if len(same_width) == 1: if ttfont['post'].isFixedPitch == 1: print("Skipping isFixedPitch is set correctly") else: print("Font is monospace. Updating isFixedPitch to 0") ttfont['post'].isFixedPitch = 1 familyType = ttfont['OS/2'].panose.bFamilyType if familyType == 2: expected = 9 elif familyType == 3 or familyType == 5: expected = 3 elif familyType == 0: print( "Font is monospace but panose fields seems to be not set." " Setting values to defaults (FamilyType = 2, Proportion = 9)." ) ttfont['OS/2'].panose.bFamilyType = 2 ttfont['OS/2'].panose.bProportion = 9 expected = None else: expected = None if expected: if ttfont['OS/2'].panose.bProportion == expected: print("Skipping OS/2.panose.bProportion is set correctly") else: print(("Font is monospace." " Since OS/2.panose.bFamilyType is {}" " we're updating OS/2.panose.bProportion" " to {}").format(familyType, expected)) ttfont['OS/2'].panose.bProportion = expected widths = [m[0] for m in ttfont['hmtx'].metrics.values() if m[0] > 0] width_max = max(widths) if ttfont['hhea'].advanceWidthMax == width_max: print("Skipping hhea.advanceWidthMax is set correctly") else: print("Font is monospace. Updating hhea.advanceWidthMax to %i" % width_max) ttfont['hhea'].advanceWidthMax = width_max avg_width = otRound(sum(widths) / len(widths)) if avg_width == ttfont['OS/2'].xAvgCharWidth: print("Skipping OS/2.xAvgCharWidth is set correctly") else: print("Font is monospace. Updating OS/2.xAvgCharWidth to %i" % avg_width) ttfont['OS/2'].xAvgCharWidth = avg_width else: ttfont['post'].isFixedPitch = 0 ttfont['OS/2'].panose.bProportion = 0
def compile(self, more, haveInstructions, glyfTable): data = b"" # reset all flags we will calculate ourselves flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS | SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET | NON_OVERLAPPING | OVERLAP_COMPOUND) if more: flags = flags | MORE_COMPONENTS if haveInstructions: flags = flags | WE_HAVE_INSTRUCTIONS if hasattr(self, "firstPt"): if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255): data = data + struct.pack(">BB", self.firstPt, self.secondPt) else: data = data + struct.pack(">HH", self.firstPt, self.secondPt) flags = flags | ARG_1_AND_2_ARE_WORDS else: x = otRound(self.x) y = otRound(self.y) flags = flags | ARGS_ARE_XY_VALUES if (-128 <= x <= 127) and (-128 <= y <= 127): data = data + struct.pack(">bb", x, y) else: data = data + struct.pack(">hh", x, y) flags = flags | ARG_1_AND_2_ARE_WORDS if hasattr(self, "transform"): transform = [[fl2fi(x,14) for x in row] for row in self.transform] if transform[0][1] or transform[1][0]: flags = flags | WE_HAVE_A_TWO_BY_TWO data = data + struct.pack(">hhhh", transform[0][0], transform[0][1], transform[1][0], transform[1][1]) elif transform[0][0] != transform[1][1]: flags = flags | WE_HAVE_AN_X_AND_Y_SCALE data = data + struct.pack(">hh", transform[0][0], transform[1][1]) else: flags = flags | WE_HAVE_A_SCALE data = data + struct.pack(">h", transform[0][0]) glyphID = glyfTable.getGlyphID(self.glyphName) return struct.pack(">HH", flags, glyphID) + data
def compile(self, more, haveInstructions, glyfTable): data = b"" # reset all flags we will calculate ourselves flags = self.flags & (ROUND_XY_TO_GRID | USE_MY_METRICS | SCALED_COMPONENT_OFFSET | UNSCALED_COMPONENT_OFFSET | NON_OVERLAPPING | OVERLAP_COMPOUND) if more: flags = flags | MORE_COMPONENTS if haveInstructions: flags = flags | WE_HAVE_INSTRUCTIONS if hasattr(self, "firstPt"): if (0 <= self.firstPt <= 255) and (0 <= self.secondPt <= 255): data = data + struct.pack(">BB", self.firstPt, self.secondPt) else: data = data + struct.pack(">HH", self.firstPt, self.secondPt) flags = flags | ARG_1_AND_2_ARE_WORDS else: x = otRound(self.x) y = otRound(self.y) flags = flags | ARGS_ARE_XY_VALUES if (-128 <= x <= 127) and (-128 <= y <= 127): data = data + struct.pack(">bb", x, y) else: data = data + struct.pack(">hh", x, y) flags = flags | ARG_1_AND_2_ARE_WORDS if hasattr(self, "transform"): transform = [[fl2fi(x,14) for x in row] for row in self.transform] if transform[0][1] or transform[1][0]: flags = flags | WE_HAVE_A_TWO_BY_TWO data = data + struct.pack(">hhhh", transform[0][0], transform[0][1], transform[1][0], transform[1][1]) elif transform[0][0] != transform[1][1]: flags = flags | WE_HAVE_AN_X_AND_Y_SCALE data = data + struct.pack(">hh", transform[0][0], transform[1][1]) else: flags = flags | WE_HAVE_A_SCALE data = data + struct.pack(">h", transform[0][0]) glyphID = glyfTable.getGlyphID(self.glyphName) return struct.pack(">HH", flags, glyphID) + data
def test_double_precision_float(self): # https://github.com/fonttools/fonttools/issues/963 afloat = 242.50000000000003 g = GlyphCoordinates([(afloat, 0)]) g.toInt() # this would return 242 if the internal array.array typecode is 'f', # since the Python float is truncated to a C float. # when using typecode 'd' it should return the correct value 243 assert g[0][0] == otRound(afloat)
def test_double_precision_float(self): # https://github.com/fonttools/fonttools/issues/963 afloat = 242.50000000000003 g = GlyphCoordinates([(afloat, 0)]) g.toInt() # this would return 242 if the internal array.array typecode is 'f', # since the Python float is truncated to a C float. # when using typecode 'd' it should return the correct value 243 assert g[0][0] == otRound(afloat)
def degreesToInt(value): # Fit the range -360..360 into -32768..32768 # If angle is outside the range, force it into the range if value >= 360: # print("warning, angle out of range:", value) value %= 360 elif value <= -360: # print("warning, angle out of range:", value) value %= -360 return otRound(value * DEGREES_SCALE)
def instantiateCvar(varfont, location): log.info("Instantiating cvt/cvar tables") cvar = varfont["cvar"] cvt = varfont["cvt "] pinnedAxes = set(location.keys()) newVariations = [] deltas = {} for var in cvar.variations: tupleAxes = set(var.axes.keys()) pinnedTupleAxes = tupleAxes & pinnedAxes if not pinnedTupleAxes: # A tuple for only axes being kept is untouched newVariations.append(var) continue else: # compute influence at pinned location only for the pinned axes pinnedAxesSupport = {a: var.axes[a] for a in pinnedTupleAxes} scalar = supportScalar(location, pinnedAxesSupport) if not scalar: # no influence (default value or out of range); drop tuple continue if tupleAxes.issubset(pinnedAxes): for i, c in enumerate(var.coordinates): if c is not None: # Compute deltas which need to be applied to values in cvt deltas[i] = deltas.get(i, 0) + scalar * c else: # Apply influence to delta values for i, d in enumerate(var.coordinates): if d is not None: var.coordinates[i] = otRound(d * scalar) for axis in pinnedTupleAxes: del var.axes[axis] newVariations.append(var) if deltas: for i, delta in deltas.items(): cvt[i] += otRound(delta) if newVariations: cvar.variations = newVariations else: del varfont["cvar"]
def openTypeHheaCaretSlopeRunFallback(info): """ Fallback to *openTypeHheaCaretSlopeRun*. If the italicAngle is zero, return 0. If italicAngle is non-zero, compute the slope run from the complementary openTypeHheaCaretSlopeRise. """ italicAngle = getAttrWithFallback(info, "italicAngle") if italicAngle != 0: slopeRise = getAttrWithFallback(info, "openTypeHheaCaretSlopeRise") return otRound(math.tan(math.radians(-italicAngle)) * slopeRise) return 0
def t2c_round(number, tolerance=0.5): if tolerance == 0: return number # no-op rounded = otRound(number) # return rounded integer if the tolerance >= 0.5, or if the absolute # difference between the original float and the rounded integer is # within the tolerance if tolerance >= .5 or abs(rounded - number) <= tolerance: return rounded else: # else return the value un-rounded return number
def VarData_addItem(self, deltas): deltas = [otRound(d) for d in deltas] countUs = self.VarRegionCount countThem = len(deltas) if countUs + 1 == countThem: deltas = tuple(deltas[1:]) else: assert countUs == countThem, (countUs, countThem) deltas = tuple(deltas) self.Item.append(list(deltas)) self.ItemCount = len(self.Item)
def average_char_width(ctx, otf): hmtx = otf.get("hmtx") if hmtx is not None: widths = list( filter(lambda w: w > 0, map(lambda hrec: hrec[0], hmtx.metrics.values()))) if widths: return otRound(sum(widths) / len(widths)) else: ctx.log.append(semlog.warning_missing_hmtx(target="avgCharWidth")) return 0
def t2c_round(number, tolerance=0.5): if tolerance == 0: return number # no-op rounded = otRound(number) # return rounded integer if the tolerance >= 0.5, or if the absolute # difference between the original float and the rounded integer is # within the tolerance if tolerance >= .5 or abs(rounded - number) <= tolerance: return rounded else: # else return the value un-rounded return number
def VarData_addItem(self, deltas): deltas = [otRound(d) for d in deltas] countUs = self.VarRegionCount countThem = len(deltas) if countUs + 1 == countThem: deltas = tuple(deltas[1:]) else: assert countUs == countThem, (countUs, countThem) deltas = tuple(deltas) self.Item.append(list(deltas)) self.ItemCount = len(self.Item)
def interpolate_cff2_PrivateDict(topDict, interpolateFromDeltas): pd_blend_lists = ("BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues", "StemSnapH", "StemSnapV") pd_blend_values = ("BlueScale", "BlueShift", "BlueFuzz", "StdHW", "StdVW") for fontDict in topDict.FDArray: pd = fontDict.Private vsindex = pd.vsindex if (hasattr(pd, 'vsindex')) else 0 for key, value in pd.rawDict.items(): if (key in pd_blend_values) and isinstance(value, list): delta = interpolateFromDeltas(vsindex, value[1:]) pd.rawDict[key] = otRound(value[0] + delta) elif (key in pd_blend_lists) and isinstance(value[0], list): """If any argument in a BlueValues list is a blend list, then they all are. The first value of each list is an absolute value. The delta tuples are calculated from relative master values, hence we need to append all the deltas to date to each successive absolute value.""" delta = 0 for i, val_list in enumerate(value): delta += otRound( interpolateFromDeltas(vsindex, val_list[1:])) value[i] = val_list[0] + delta
def _SetCoordinates(font, glyphName, coord): glyf = font["glyf"] assert glyphName in glyf.glyphs glyph = glyf[glyphName] # Handle phantom points for (left, right, top, bottom) positions. assert len(coord) >= 4 if not hasattr(glyph, 'xMin'): glyph.recalcBounds(glyf) leftSideX = coord[-4][0] rightSideX = coord[-3][0] topSideY = coord[-2][1] bottomSideY = coord[-1][1] for _ in range(4): del coord[-1] if glyph.isComposite(): assert len(coord) == len(glyph.components) for p,comp in zip(coord, glyph.components): if hasattr(comp, 'x'): comp.x,comp.y = p elif glyph.numberOfContours is 0: assert len(coord) == 0 else: assert len(coord) == len(glyph.coordinates) glyph.coordinates = coord glyph.recalcBounds(glyf) horizontalAdvanceWidth = otRound(rightSideX - leftSideX) if horizontalAdvanceWidth < 0: # unlikely, but it can happen, see: # https://github.com/fonttools/fonttools/pull/1198 horizontalAdvanceWidth = 0 leftSideBearing = otRound(glyph.xMin - leftSideX) # XXX Handle vertical font["hmtx"].metrics[glyphName] = horizontalAdvanceWidth, leftSideBearing
def interpolate_cff2_PrivateDict(topDict, interpolateFromDeltas): pd_blend_lists = ("BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues", "StemSnapH", "StemSnapV") pd_blend_values = ("BlueScale", "BlueShift", "BlueFuzz", "StdHW", "StdVW") for fontDict in topDict.FDArray: pd = fontDict.Private vsindex = pd.vsindex if (hasattr(pd, 'vsindex')) else 0 for key, value in pd.rawDict.items(): if (key in pd_blend_values) and isinstance(value, list): delta = interpolateFromDeltas(vsindex, value[1:]) pd.rawDict[key] = otRound(value[0] + delta) elif (key in pd_blend_lists) and isinstance(value[0], list): """If any argument in a BlueValues list is a blend list, then they all are. The first value of each list is an absolute value. The delta tuples are calculated from relative master values, hence we need to append all the deltas to date to each successive absolute value.""" delta = 0 for i, val_list in enumerate(value): delta += otRound(interpolateFromDeltas(vsindex, val_list[1:])) value[i] = val_list[0] + delta
def merge(merger, self, lst): model = merger.model scalars = merger.scalars # TODO Handle differing valueformats for name, tableName in [('XAdvance','XAdvDevice'), ('YAdvance','YAdvDevice'), ('XPlacement','XPlaDevice'), ('YPlacement','YPlaDevice')]: assert not hasattr(self, tableName) if hasattr(self, name): values = [getattr(a, name, 0) for a in lst] value = otRound(model.interpolateFromMastersAndScalars(values, scalars)) setattr(self, name, value)
def getCharString(self, private=None, globalSubrs=None, optimize=True): commands = self._commands if optimize: maxstack = 48 if not self._CFF2 else 513 commands = specializeCommands(commands, generalizeFirst=False, maxstack=maxstack) program = commandsToProgram(commands) if self._width is not None: assert not self._CFF2, "CFF2 does not allow encoding glyph width in CharString." program.insert(0, otRound(self._width)) if not self._CFF2: program.append('endchar') charString = T2CharString( program=program, private=private, globalSubrs=globalSubrs) return charString
def interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc): """Unlike TrueType glyphs, neither advance width nor bounding box info is stored in a CFF2 charstring. The width data exists only in the hmtx and HVAR tables. Since LSB data cannot be interpolated reliably from the master LSB values in the hmtx table, we traverse the charstring to determine the actual bound box. """ charstrings = topDict.CharStrings boundsPen = BoundsPen(glyphOrder) hmtx = varfont['hmtx'] hvar_table = None if 'HVAR' in varfont: hvar_table = varfont['HVAR'].table fvar = varfont['fvar'] varStoreInstancer = VarStoreInstancer(hvar_table.VarStore, fvar.axes, loc) for gid, gname in enumerate(glyphOrder): entry = list(hmtx[gname]) # get width delta. if hvar_table: if hvar_table.AdvWidthMap: width_idx = hvar_table.AdvWidthMap.mapping[gname] else: width_idx = gid width_delta = otRound(varStoreInstancer[width_idx]) else: width_delta = 0 # get LSB. boundsPen.init() charstring = charstrings[gname] charstring.draw(boundsPen) if boundsPen.bounds is None: # Happens with non-marking glyphs lsb_delta = 0 else: lsb = boundsPen.bounds[0] lsb_delta = entry[1] - lsb if lsb_delta or width_delta: if width_delta: entry[0] += width_delta if lsb_delta: entry[1] = lsb hmtx[gname] = tuple(entry)
def merge(merger, self, lst): # Hack till we become selfless. self.__dict__ = lst[0].__dict__.copy() if self.Format != 3: return instancer = merger.instancer dev = self.DeviceTable del self.DeviceTable if dev: assert dev.DeltaFormat == 0x8000 varidx = (dev.StartSize << 16) + dev.EndSize delta = otRound(instancer[varidx]) self.Coordinate += delta self.Format = 1
def storeMasters(self, master_values): deltas = [otRound(d) for d in self._model.getDeltas(master_values)] base = deltas.pop(0) deltas = tuple(deltas) varIdx = self._cache.get(deltas) if varIdx is not None: return base, varIdx if not self._data: self._add_VarData() inner = len(self._data.Item) if inner == 0xFFFF: # Full array. Start new one. self._add_VarData() return self.storeMasters(master_values) self._data.Item.append(deltas) varIdx = (self._outer << 16) + inner self._cache[deltas] = varIdx return base, varIdx
def interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder): charstrings = topDict.CharStrings for gname in glyphOrder: # Interpolate charstring # e.g replace blend op args with regular args, # and use and discard vsindex op. charstring = charstrings[gname] new_program = [] vsindex = 0 last_i = 0 for i, token in enumerate(charstring.program): if token == 'vsindex': vsindex = charstring.program[i - 1] if last_i != 0: new_program.extend(charstring.program[last_i:i - 1]) last_i = i + 1 elif token == 'blend': num_regions = charstring.getNumRegions(vsindex) numMasters = 1 + num_regions num_args = charstring.program[i - 1] # The program list starting at program[i] is now: # ..args for following operations # num_args values from the default font # num_args tuples, each with numMasters-1 delta values # num_blend_args # 'blend' argi = i - (num_args * numMasters + 1) end_args = tuplei = argi + num_args while argi < end_args: next_ti = tuplei + num_regions deltas = charstring.program[tuplei:next_ti] delta = interpolateFromDeltas(vsindex, deltas) charstring.program[argi] += otRound(delta) tuplei = next_ti argi += 1 new_program.extend(charstring.program[last_i:end_args]) last_i = i + 1 if last_i != 0: new_program.extend(charstring.program[last_i:]) charstring.program = new_program
def merge(merger, self, lst): # Hack till we become selfless. self.__dict__ = lst[0].__dict__.copy() instancer = merger.instancer # TODO Handle differing valueformats for name, tableName in [('XAdvance','XAdvDevice'), ('YAdvance','YAdvDevice'), ('XPlacement','XPlaDevice'), ('YPlacement','YPlaDevice')]: if not hasattr(self, tableName): continue dev = getattr(self, tableName) delattr(self, tableName) if dev is None: continue assert dev.DeltaFormat == 0x8000 varidx = (dev.StartSize << 16) + dev.EndSize delta = otRound(instancer[varidx]) setattr(self, name, getattr(self, name) + delta)
def storeMasters(self, master_values): deltas = self._model.getDeltas(master_values) base = otRound(deltas.pop(0)) return base, self.storeDeltas(deltas)
def _merge_TTHinting(font, model, master_ttfs, tolerance=0.5): log.info("Merging TT hinting") assert "cvar" not in font # Check that the existing hinting is compatible # fpgm and prep table for tag in ("fpgm", "prep"): all_pgms = [m[tag].program for m in master_ttfs if tag in m] if len(all_pgms) == 0: continue if tag in font: font_pgm = font[tag].program else: font_pgm = Program() if any(pgm != font_pgm for pgm in all_pgms): log.warning("Masters have incompatible %s tables, hinting is discarded." % tag) _remove_TTHinting(font) return # glyf table for name, glyph in font["glyf"].glyphs.items(): all_pgms = [ m["glyf"][name].program for m in master_ttfs if hasattr(m["glyf"][name], "program") ] if not any(all_pgms): continue glyph.expand(font["glyf"]) if hasattr(glyph, "program"): font_pgm = glyph.program else: font_pgm = Program() if any(pgm != font_pgm for pgm in all_pgms if pgm): log.warning("Masters have incompatible glyph programs in glyph '%s', hinting is discarded." % name) _remove_TTHinting(font) return # cvt table all_cvs = [Vector(m["cvt "].values) for m in master_ttfs if "cvt " in m] if len(all_cvs) == 0: # There is no cvt table to make a cvar table from, we're done here. return if len(all_cvs) != len(master_ttfs): log.warning("Some masters have no cvt table, hinting is discarded.") _remove_TTHinting(font) return num_cvt0 = len(all_cvs[0]) if (any(len(c) != num_cvt0 for c in all_cvs)): log.warning("Masters have incompatible cvt tables, hinting is discarded.") _remove_TTHinting(font) return # We can build the cvar table now. cvar = font["cvar"] = newTable('cvar') cvar.version = 1 cvar.variations = [] deltas = model.getDeltas(all_cvs) supports = model.supports for i,(delta,support) in enumerate(zip(deltas[1:], supports[1:])): delta = [otRound(d) for d in delta] if all(abs(v) <= tolerance for v in delta): continue var = TupleVariation(support, delta) cvar.variations.append(var)
def _add_HVAR(font, model, master_ttfs, axisTags): log.info("Generating HVAR") hAdvanceDeltas = {} metricses = [m["hmtx"].metrics for m in master_ttfs] for glyph in font.getGlyphOrder(): hAdvances = [metrics[glyph][0] for metrics in metricses] # TODO move round somewhere else? hAdvanceDeltas[glyph] = tuple(otRound(d) for d in model.getDeltas(hAdvances)[1:]) # Direct mapping supports = model.supports[1:] varTupleList = builder.buildVarRegionList(supports, axisTags) varTupleIndexes = list(range(len(supports))) n = len(supports) items = [] for glyphName in font.getGlyphOrder(): items.append(hAdvanceDeltas[glyphName]) # Build indirect mapping to save on duplicates, compare both sizes uniq = list(set(items)) mapper = {v:i for i,v in enumerate(uniq)} mapping = [mapper[item] for item in items] advanceMapping = builder.buildVarIdxMap(mapping, font.getGlyphOrder()) # Direct varData = builder.buildVarData(varTupleIndexes, items) directStore = builder.buildVarStore(varTupleList, [varData]) # Indirect varData = builder.buildVarData(varTupleIndexes, uniq) indirectStore = builder.buildVarStore(varTupleList, [varData]) mapping = indirectStore.optimize() advanceMapping.mapping = {k:mapping[v] for k,v in advanceMapping.mapping.items()} # Compile both, see which is more compact writer = OTTableWriter() directStore.compile(writer, font) directSize = len(writer.getAllData()) writer = OTTableWriter() indirectStore.compile(writer, font) advanceMapping.compile(writer, font) indirectSize = len(writer.getAllData()) use_direct = directSize < indirectSize # Done; put it all together. assert "HVAR" not in font HVAR = font["HVAR"] = newTable('HVAR') hvar = HVAR.table = ot.HVAR() hvar.Version = 0x00010000 hvar.LsbMap = hvar.RsbMap = None if use_direct: hvar.VarStore = directStore hvar.AdvWidthMap = None else: hvar.VarStore = indirectStore hvar.AdvWidthMap = advanceMapping
def instantiateVariableFont(varfont, location, inplace=False): """ Generate a static instance from a variable TTFont and a dictionary defining the desired location along the variable font's axes. The location values must be specified as user-space coordinates, e.g.: {'wght': 400, 'wdth': 100} By default, a new TTFont object is returned. If ``inplace`` is True, the input varfont is modified and reduced to a static font. """ if not inplace: # make a copy to leave input varfont unmodified stream = BytesIO() varfont.save(stream) stream.seek(0) varfont = TTFont(stream) fvar = varfont['fvar'] axes = {a.axisTag:(a.minValue,a.defaultValue,a.maxValue) for a in fvar.axes} loc = normalizeLocation(location, axes) if 'avar' in varfont: maps = varfont['avar'].segments loc = {k: piecewiseLinearMap(v, maps[k]) for k,v in loc.items()} # Quantize to F2Dot14, to avoid surprise interpolations. loc = {k:floatToFixedToFloat(v, 14) for k,v in loc.items()} # Location is normalized now log.info("Normalized location: %s", loc) if 'gvar' in varfont: log.info("Mutating glyf/gvar tables") gvar = varfont['gvar'] glyf = varfont['glyf'] # get list of glyph names in gvar sorted by component depth glyphnames = sorted( gvar.variations.keys(), key=lambda name: ( glyf[name].getCompositeMaxpValues(glyf).maxComponentDepth if glyf[name].isComposite() else 0, name)) for glyphname in glyphnames: variations = gvar.variations[glyphname] coordinates,_ = _GetCoordinates(varfont, glyphname) origCoords, endPts = None, None for var in variations: scalar = supportScalar(loc, var.axes) if not scalar: continue delta = var.coordinates if None in delta: if origCoords is None: origCoords,control = _GetCoordinates(varfont, glyphname) endPts = control[1] if control[0] >= 1 else list(range(len(control[1]))) delta = iup_delta(delta, origCoords, endPts) coordinates += GlyphCoordinates(delta) * scalar _SetCoordinates(varfont, glyphname, coordinates) else: glyf = None if 'cvar' in varfont: log.info("Mutating cvt/cvar tables") cvar = varfont['cvar'] cvt = varfont['cvt '] deltas = {} for var in cvar.variations: scalar = supportScalar(loc, var.axes) if not scalar: continue for i, c in enumerate(var.coordinates): if c is not None: deltas[i] = deltas.get(i, 0) + scalar * c for i, delta in deltas.items(): cvt[i] += otRound(delta) if 'CFF2' in varfont: log.info("Mutating CFF2 table") glyphOrder = varfont.getGlyphOrder() CFF2 = varfont['CFF2'] topDict = CFF2.cff.topDictIndex[0] vsInstancer = VarStoreInstancer(topDict.VarStore.otVarStore, fvar.axes, loc) interpolateFromDeltas = vsInstancer.interpolateFromDeltas interpolate_cff2_PrivateDict(topDict, interpolateFromDeltas) CFF2.desubroutinize(varfont) interpolate_cff2_charstrings(topDict, interpolateFromDeltas, glyphOrder) interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc) del topDict.rawDict['VarStore'] del topDict.VarStore if 'MVAR' in varfont: log.info("Mutating MVAR table") mvar = varfont['MVAR'].table varStoreInstancer = VarStoreInstancer(mvar.VarStore, fvar.axes, loc) records = mvar.ValueRecord for rec in records: mvarTag = rec.ValueTag if mvarTag not in MVAR_ENTRIES: continue tableTag, itemName = MVAR_ENTRIES[mvarTag] delta = otRound(varStoreInstancer[rec.VarIdx]) if not delta: continue setattr(varfont[tableTag], itemName, getattr(varfont[tableTag], itemName) + delta) log.info("Mutating FeatureVariations") for tableTag in 'GSUB','GPOS': if not tableTag in varfont: continue table = varfont[tableTag].table if not hasattr(table, 'FeatureVariations'): continue variations = table.FeatureVariations for record in variations.FeatureVariationRecord: applies = True for condition in record.ConditionSet.ConditionTable: if condition.Format == 1: axisIdx = condition.AxisIndex axisTag = fvar.axes[axisIdx].axisTag Min = condition.FilterRangeMinValue Max = condition.FilterRangeMaxValue v = loc[axisTag] if not (Min <= v <= Max): applies = False else: applies = False if not applies: break if applies: assert record.FeatureTableSubstitution.Version == 0x00010000 for rec in record.FeatureTableSubstitution.SubstitutionRecord: table.FeatureList.FeatureRecord[rec.FeatureIndex].Feature = rec.Feature break del table.FeatureVariations if 'GDEF' in varfont and varfont['GDEF'].table.Version >= 0x00010003: log.info("Mutating GDEF/GPOS/GSUB tables") gdef = varfont['GDEF'].table instancer = VarStoreInstancer(gdef.VarStore, fvar.axes, loc) merger = MutatorMerger(varfont, loc) merger.mergeTables(varfont, [varfont], ['GDEF', 'GPOS']) # Downgrade GDEF. del gdef.VarStore gdef.Version = 0x00010002 if gdef.MarkGlyphSetsDef is None: del gdef.MarkGlyphSetsDef gdef.Version = 0x00010000 if not (gdef.LigCaretList or gdef.MarkAttachClassDef or gdef.GlyphClassDef or gdef.AttachList or (gdef.Version >= 0x00010002 and gdef.MarkGlyphSetsDef)): del varfont['GDEF'] addidef = False if glyf: for glyph in glyf.glyphs.values(): if hasattr(glyph, "program"): instructions = glyph.program.getAssembly() # If GETVARIATION opcode is used in bytecode of any glyph add IDEF addidef = any(op.startswith("GETVARIATION") for op in instructions) if addidef: break if addidef: log.info("Adding IDEF to fpgm table for GETVARIATION opcode") asm = [] if 'fpgm' in varfont: fpgm = varfont['fpgm'] asm = fpgm.program.getAssembly() else: fpgm = newTable('fpgm') fpgm.program = ttProgram.Program() varfont['fpgm'] = fpgm asm.append("PUSHB[000] 145") asm.append("IDEF[ ]") args = [str(len(loc))] for a in fvar.axes: args.append(str(floatToFixed(loc[a.axisTag], 14))) asm.append("NPUSHW[ ] " + ' '.join(args)) asm.append("ENDF[ ]") fpgm.program.fromAssembly(asm) # Change maxp attributes as IDEF is added if 'maxp' in varfont: maxp = varfont['maxp'] if hasattr(maxp, "maxInstructionDefs"): maxp.maxInstructionDefs += 1 else: setattr(maxp, "maxInstructionDefs", 1) if hasattr(maxp, "maxStackElements"): maxp.maxStackElements = max(len(loc), maxp.maxStackElements) else: setattr(maxp, "maxInstructionDefs", len(loc)) if 'name' in varfont: log.info("Pruning name table") exclude = {a.axisNameID for a in fvar.axes} for i in fvar.instances: exclude.add(i.subfamilyNameID) exclude.add(i.postscriptNameID) varfont['name'].names[:] = [ n for n in varfont['name'].names if n.nameID not in exclude ] if "wght" in location and "OS/2" in varfont: varfont["OS/2"].usWeightClass = otRound( max(1, min(location["wght"], 1000)) ) if "wdth" in location: wdth = location["wdth"] for percent, widthClass in sorted(OS2_WIDTH_CLASS_VALUES.items()): if wdth < percent: varfont["OS/2"].usWidthClass = widthClass break else: varfont["OS/2"].usWidthClass = 9 if "slnt" in location and "post" in varfont: varfont["post"].italicAngle = max(-90, min(location["slnt"], 90)) log.info("Removing variable tables") for tag in ('avar','cvar','fvar','gvar','HVAR','MVAR','VVAR','STAT'): if tag in varfont: del varfont[tag] return varfont
def _makeAnchorFormatA(x, y): return ast.Anchor(x=otRound(x), y=otRound(y))