def parse(name, value): words = fnutil.split_words(name, value, 4) return BBX( fnutil.parse_dec('width', words[0], 1, DPARSE_LIMIT), fnutil.parse_dec('height', words[1], 1, DPARSE_LIMIT), fnutil.parse_dec('bbxoff', words[2], -DPARSE_LIMIT, DPARSE_LIMIT), fnutil.parse_dec('bbyoff', words[3], -DPARSE_LIMIT, DPARSE_LIMIT))
def expand(self): # PREXPAND / VERTICAL ascent = self.props.get('FONT_ASCENT') descent = self.props.get('FONT_DESCENT') px_ascent = 0 if ascent is None else fnutil.parse_dec('FONT_ASCENT', ascent, 0, bdf.DPARSE_LIMIT) px_descent = 0 if descent is None else fnutil.parse_dec('FONT_DESCENT', descent, 0, bdf.DPARSE_LIMIT) for char in self.chars: px_ascent = max(px_ascent, char.bbx.height + char.bbx.yoff) px_descent = max(px_descent, -char.bbx.yoff) self.bbx.height = px_ascent + px_descent self.bbx.yoff = -px_descent # EXPAND / HORIZONTAL total_width = 0 self.min_width = self.chars[0].bbx.width for char in self.chars: self._expand(char) self.min_width = min(self.min_width, char.bbx.width) self.bbx.width = max(self.bbx.width, char.bbx.width) self.bbx.xoff = min(self.bbx.xoff, char.bbx.xoff) total_width += char.bbx.width self.avg_width = round(total_width / len(self.chars)) self.props.set('FONTBOUNDINGBOX', self.bbx)
def parse(self, name, value, params): if name == '-c': params.char_set = fnutil.parse_dec('charset', value, 0, 255) elif name == '-m': params.min_char = fnutil.parse_dec('minchar', value, 0, 255) elif name == '-f': if value in FNT_FAMILIES: params.fnt_family = FNT_FAMILIES.index(value) else: raise Exception('invalid fnt family') elif name == '-o': params.output = value else: self.fallback(name, params)
def parse(self, name, optarg): if name == '-c': self.char_set = fnutil.parse_dec('charset', optarg, 0, 255) elif name == '-m': self.min_char = fnutil.parse_dec('minchar', optarg, 0, 255) elif name == '-f': if optarg in FNT_FAMILIES: self.fnt_family = FNT_FAMILIES.index(optarg) else: raise Exception('invalid fnt family') elif name == '-o': self.output = optarg else: fncli.Options.parse(self, name, optarg)
def get_ascent(self): ascent = self.props.get('FONT_ASCENT') if ascent is not None: return fnutil.parse_dec('FONT_ASCENT', ascent, -HEIGHT_MAX, HEIGHT_MAX) return self.bbx.height + self.bbx.yoff
def check_prop(self, line): match = re.fullmatch(br'(\w+)\s+([-\d"].*)', line) if not match: raise Exception('invalid property format') name = match.group(1) value = match.group(2) if value.startswith(b'"'): if len(value) < 2 or not value.endswith(b'"'): raise Exception('no closing double quote') if re.search(b'[^"]"[^"]', value[1 : len(value) - 1]): raise Exception('unescaped double quote') else: fnutil.parse_dec('value', value, None, None) self.append(self.parsed.dupl_props, self.proplocs, name) return b'P%d 1' % self.line_no
def parse(self, name, value, params): if name == '-d': params.dir_hint = fnutil.parse_dec('DIR-HINT', value, -2, 2) elif name == '-e': params.em_size = fnutil.parse_dec('EM-SIZE', value, EM_SIZE_MIN, EM_SIZE_MAX) elif name == '-g': params.line_gap = fnutil.parse_dec('LINE-GAP', value, 0, EM_SIZE_MAX << 1) elif name == '-l': params.low_ppem = fnutil.parse_dec('LOW-PPEM', value, 1, bdf.DPARSE_LIMIT) elif name == '-E': params.encoding = value elif name == '-W': params.w_lang_id = fnutil.parse_hex('WLANG-ID', value, 0, 0x7FFF) elif name == '-X': params.x_max_extent = False elif name == '-L': params.single_loca = True elif name == '-P': params.post_names = True else: self.fallback(name, params)
def _parse(name, value, limit_x, limit_y): words = fnutil.split_words(name, value, 2) return Width(fnutil.parse_dec('width x', words[0], -limit_x, limit_x), fnutil.parse_dec('width y', words[1], -limit_y, limit_y))
def parse(name, value, limit): words = fnutil.split_words(name, value, 2) return Width(fnutil.parse_dec(name + '.x', words[0], -limit, limit), fnutil.parse_dec(name + '.y', words[1], -limit, limit))
def _read(self, input): # HEADER line = input.read_line() if self.props.parse(line, 'STARTFONT') != b'2.1': raise Exception('STARTFONT 2.1 expected') self.xlfd = self.props.read(input, 'FONT', lambda name, value: value.split(b'-', 15)) if len(self.xlfd) != 15 or self.xlfd[0] != b'': raise Exception('non-XLFD font names are not supported') self.props.read(input, 'SIZE') self.bbx = self.props.read(input, 'FONTBOUNDINGBOX', BBX.parse) line = input.read_lines(skip_comments) if line and line.startswith(b'STARTPROPERTIES'): num_props = self.props.parse(line, 'STARTPROPERTIES', fnutil.parse_dec) for _ in range(0, num_props): line = input.read_lines(skip_comments) if line is None: raise Exception('property expected') match = re.fullmatch(br'(\w+)\s+([-\d"].*)', line) if not match: raise Exception('invalid property format') name = str(match.group(1), 'ascii') value = match.group(2) if self.props.get(name) is not None: raise Exception('duplicate property') if name == 'DEFAULT_CHAR': self.default_code = fnutil.parse_dec(name, value) self.props[name] = value if self.props.read(input, 'ENDPROPERTIES') != b'': raise Exception('ENDPROPERTIES expected') line = input.read_lines(skip_comments) # GLYPHS num_chars = fnutil.parse_dec('CHARS', self.props.parse(line, 'CHARS'), 1, CHARS_MAX) for _ in range(0, num_chars): self.chars.append(Char.read(input)) #if next((char.code for char in self.chars if char.code == self.default_code), -1) != self.default_code: #raise Exception('invalid DEFAULT_CHAR') # FINAL if input.read_lines(skip_comments) != b'ENDFONT': raise Exception('ENDFONT expected') if input.read_line() is not None: raise Exception('garbage after ENDFONT') return self
def check_size(value): words = fnutil.split_words('SIZE', value, 3) fnutil.parse_dec('point size', words[0], 1, None) fnutil.parse_dec('x resolution', words[1], 1, None) fnutil.parse_dec('y resolution', words[2], 1, None)
def append_code(self, value): fnutil.parse_dec('encoding', value) self.append(self.parsed.dupl_codes, self.codelocs, value)
def main_program(nonopt, parsed): bstr = lambda number: bytes(str(number), 'ascii') # NON-OPTIONS if len(nonopt) < 4: raise Exception('invalid number of arguments, try --help') input = nonopt[0] registry = nonopt[1] encoding = nonopt[2] new_codes = [] if not re.fullmatch(r'[A-Za-z][\w.:()]*', registry) or not re.fullmatch( r'[\w.:()]+', encoding): raise Exception('invalid registry or encoding') # READ INPUT ifs = fnio.InputStream(input) try: old_font = bdf.Font.read(ifs) ifs.close() except Exception as ex: raise Exception(ifs.location() + str(ex)) # READ TABLES def load_code(line): new_codes.append(fnutil.parse_hex('unicode', line)) for name in nonopt[3:]: ifs = fnio.InputStream(name) try: ifs.read_lines(load_code) ifs.close() except Exception as ex: raise Exception(ifs.location() + str(ex)) if not new_codes: raise Exception('no characters in the output font') # CREATE GLYPHS new_font = bdf.Font() charmap = {char.code: char for char in old_font.chars} index = 0 unstart = 0 family = parsed.family if parsed.family is not None else old_font.xlfd[ bdf.XLFD.FAMILY_NAME] if parsed.filter: unstart = 32 if registry == 'ISO10646' else bdf.CHARS_MAX for code in new_codes: if code == 0xFFFF and parsed.filter: index += 1 continue if code in charmap: old_char = charmap[code] uni_ffff = False else: uni_ffff = True if code != 0xFFFF: raise Exception('%s does not contain U+%04X' % (input, code)) if old_font.default_code != -1: old_char = charmap[old_font.default_code] elif 0xFFFD in charmap: old_char = charmap[0xFFFD] else: raise Exception( '%s does not contain U+FFFF, and no replacement found' % input) new_char = copy.copy(old_char) new_char.code = code if index >= unstart else index index += 1 new_char.props = new_char.props.clone() new_char.props.set('ENCODING', bstr(new_char.code)) new_font.chars.append(new_char) if uni_ffff: new_char.props.set('STARTCHAR', b'uniFFFF') elif old_char.code == old_font.default_code or ( old_char.code == 0xFFFD and new_font.default_code == -1): new_font.default_code = new_char.code # CREATE HEADER registry = bytes(registry, 'ascii') encoding = bytes(encoding, 'ascii') for [name, value] in old_font.props: if name == 'FONT': new_font.xlfd = old_font.xlfd[:] new_font.xlfd[bdf.XLFD.FAMILY_NAME] = family new_font.xlfd[bdf.XLFD.CHARSET_REGISTRY] = registry new_font.xlfd[bdf.XLFD.CHARSET_ENCODING] = encoding value = b'-'.join(new_font.xlfd) elif name == 'STARTPROPERTIES': num_props = fnutil.parse_dec(name, value, 1) elif name == 'FAMILY_NAME': value = fnutil.quote(family) elif name == 'CHARSET_REGISTRY': value = fnutil.quote(registry) elif name == 'CHARSET_ENCODING': value = fnutil.quote(encoding) elif name == 'DEFAULT_CHAR': if new_font.default_code != -1: value = bstr(new_font.default_code) else: num_props -= 1 continue elif name == 'ENDPROPERTIES': if new_font.default_code != -1 and new_font.props.get( 'DEFAULT_CHAR') is None: new_font.props.add('DEFAULT_CHAR', bstr(new_font.default_code)) num_props += 1 new_font.props.set('STARTPROPERTIES', bstr(num_props)) elif name == 'CHARS': value = bstr(len(new_font.chars)) new_font.props.add(name, value) # COPY FIELDS new_font.bbx = old_font.bbx new_font.finis = old_font.finis # WRITE OUTPUT ofs = fnio.OutputStream(parsed.output) try: new_font.write(ofs) ofs.close() except Exception as ex: raise Exception(ofs.location() + str(ex) + ofs.destroy())
def parse(name, value): words = fnutil.split_words(name, value, 4) return BBX(fnutil.parse_dec('width', words[0], 1, WIDTH_MAX), fnutil.parse_dec('height', words[1], 1, HEIGHT_MAX), fnutil.parse_dec('bbxoff', words[2], -WIDTH_MAX, WIDTH_MAX), fnutil.parse_dec('bbyoff', words[3], -WIDTH_MAX, WIDTH_MAX))
def _read(self, input): # HEADER line = input.read_line() read_next = lambda: input.read_lines(_Base.skip_comments) if self.set_prop(line, 'STARTFONT') != b'2.1': raise Exception('STARTFONT 2.1 expected') self.xlfd = self.set_prop(read_next(), 'FONT', lambda name, value: value.split(b'-', 15)) if len(self.xlfd) != 15 or self.xlfd[0] != b'': raise Exception('non-XLFD font names are not supported') self.set_prop(read_next(), 'SIZE') self.bbx = self.set_prop(read_next(), 'FONTBOUNDINGBOX', lambda name, value: BBX.parse(name, value)) line = read_next() if line and line.startswith(b'STARTPROPERTIES'): num_props = self.set_prop( line, 'STARTPROPERTIES', lambda name, value: fnutil.parse_dec(name, value)) start_index = len(self.props) for _ in range(0, num_props): line = read_next() if not line: raise Exception('property expected') match = re.fullmatch(br'(\w+)\s+([\d"].*)', line) if not match: raise Exception('invalid property format') name = str(match.group(1), 'ascii') value = match.group(2) if name == 'DEFAULT_CHAR': self.default_code = fnutil.parse_dec(name, value) self.props[name] = value if self.set_prop(read_next(), 'ENDPROPERTIES') != b'': raise Exception('ENDPROPERTIES expected') if len(self.props) != start_index + num_props + 1: fnutil.warning(input.location(), 'duplicate properties') self.props['STARTPROPERTIES'] = bytes(str(len(self.props)), 'ascii') line = read_next() # GLYPHS num_chars = self.set_prop( line, 'CHARS', lambda name, value: fnutil.parse_dec(name, value, 1, CHARS_MAX)) for _ in range(0, num_chars): self.chars.append(Char.read(input)) if next((char.code for char in self.chars if char.code == self.default_code), -1) != self.default_code: raise Exception('invalid DEFAULT_CHAR') # ENDING if read_next() != b'ENDFONT': raise Exception('ENDFONT expected') if read_next(): raise Exception('garbage after ENDFONT') return self
def get_ascent(self): try: return fnutil.parse_dec('FONT_ASCENT', self.props['FONT_ASCENT'], -HEIGHT_MAX, HEIGHT_MAX) except KeyError: return self.bbx.height + self.bbx.yoff
def italic_angle(self): value = self.props.get('ITALIC_ANGLE') # must be integer return fnutil.parse_dec('ITALIC_ANGLE', value, -45, 45) if value else -11.5 if self.italic else 0
def _read(self, input): # HEADER read_next = lambda: input.read_lines(lambda line: self.keep_comments(line)) read_prop = lambda name, callback=None: self.props.parse(read_next(), name, callback) line = input.read_lines(Font.skip_empty) if self.props.parse(line, 'STARTFONT') != b'2.1': raise Exception('STARTFONT 2.1 expected') self.xlfd = read_prop('FONT', lambda name, value: value.split(b'-', 15)) if len(self.xlfd) != 15 or self.xlfd[0] != b'': raise Exception('non-XLFD font names are not supported') read_prop('SIZE') self.bbx = read_prop('FONTBOUNDINGBOX', BBX.parse) line = read_next() if line and line.startswith(b'STARTPROPERTIES'): num_props = self.props.parse(line, 'STARTPROPERTIES', fnutil.parse_dec) for _ in range(0, num_props): line = read_next() if not line: raise Exception('property expected') match = re.fullmatch(br'(\w+)\s+([-\d"].*)', line) if not match: raise Exception('invalid property format') name = str(match.group(1), 'ascii') value = match.group(2) if name == 'DEFAULT_CHAR': self.default_code = fnutil.parse_dec(name, value) self.props.add(name, value) if read_prop('ENDPROPERTIES') != b'': raise Exception('ENDPROPERTIES expected') line = read_next() # GLYPHS num_chars = self.props.parse(line, 'CHARS', lambda name, value: fnutil.parse_dec(name, value, 1, CHARS_MAX)) for _ in range(0, num_chars): self.chars.append(Char.read(input)) if next((char.code for char in self.chars if char.code == self.default_code), -1) != self.default_code: raise Exception('invalid DEFAULT_CHAR') # FINAL if input.read_lines(lambda line: self.keep_finishes(line)) != b'ENDFONT': raise Exception('ENDFONT expected') if input.read_lines(Font.skip_empty): raise Exception('garbage after ENDFONT') return self