# -*- coding: utf-8 -*- """ $Id$ Copyright 2010 Lars Kruse This file is part of PyCAM. PyCAM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PyCAM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with PyCAM. If not, see . """ from pycam.Geometry.Letters import Charset from pycam.Geometry.Line import Line from pycam.Geometry.Point import Point from pycam.Geometry import get_points_of_arc import pycam.Utils.log import pycam.Utils log = pycam.Utils.log.get_logger() class _CXFParseError(BaseException): pass class _LineFeeder(object): def __init__(self, items): self.items = items self._len = len(items) self.index = 0 def consume(self): if not self.is_empty(): result = self.get() self.index += 1 else: result = None return result def get(self): if not self.is_empty(): return self.items[self.index].strip() else: return None def is_empty(self): return self.index >= self._len def get_index(self): return self.index + 1 class CXFParser(object): META_KEYWORDS = ("letterspacing", "wordspacing", "linespacingfactor", "encoding") META_KEYWORDS_MULTI = ("author", "name") def __init__(self, stream, callback=None): self.letters = {} self.meta = {} self.callback = callback feeder = _LineFeeder(stream.readlines()) while not feeder.is_empty(): line = feeder.consume() if not line: # ignore pass elif line.startswith("#"): # comment or meta data content = line[1:].split(":", 1) if len(content) == 2: key = content[0].lower().strip() value = content[1].strip() if key in self.META_KEYWORDS: try: if key != "encoding": self.meta[key] = float(value) else: self.meta[key] = value except ValueError: raise _CXFParseError("Invalid meta information " \ + "in line %d" % feeder.get_index()) elif key in self.META_KEYWORDS_MULTI: if key in self.meta: self.meta[key].append(value) else: self.meta[key] = [value] else: # unknown -> ignore pass elif line.startswith("["): # Update the GUI from time to time. # This is useful for the big unicode font. if self.callback and (len(self.letters) % 50 == 0): self.callback() if (len(line) >= 3) and (line[2] == "]"): # single character for encoding in ("utf-8", "iso8859-1", "iso8859-15"): try: character = unicode(line[1], encoding) break except UnicodeDecodeError: pass else: raise _CXFParseError("Failed to decode character at " \ + "line %d" % feeder.get_index()) elif (len(line) >= 6) and (line[5] == "]"): # unicode character (e.g. "[1ae4]") try: character = unichr(int(line[1:5], 16)) except ValueError: raise _CXFParseError("Failed to parse unicode " \ + "character at line %d" % feeder.get_index()) elif (len(line) > 3) and (line.find("]") > 2): # read UTF8 (qcad 1 compatibility) end_bracket = line.find("] ") text = line[1:end_bracket] character = unicode(text, "utf-8")[0] else: # unknown format raise _CXFParseError("Failed to parse character at line " \ + "%d" % feeder.get_index()) # parse the following lines up to the next empty line char_definition = [] while not feeder.is_empty() and (len(feeder.get()) > 0): line = feeder.consume() # split the line after the first whitespace type_def, coord_string = line.split(None, 1) coords = [float(value) for value in coord_string.split(",")] type_char = line[0].upper() if (type_def == "L") and (len(coords) == 4): # line p1 = Point(coords[0], coords[1], 0) p2 = Point(coords[2], coords[3], 0) char_definition.append(Line(p1, p2)) elif (type_def in ("A", "AR")) and (len(coords) == 5): # arc previous = None center = Point(coords[0], coords[1], 0) radius = coords[2] start_angle, end_angle = coords[3], coords[4] if type_def == "AR": # reverse the arc start_angle, end_angle = end_angle, start_angle for p in get_points_of_arc(center, radius, start_angle, end_angle): current = Point(p[0], p[1], 0) if not previous is None: char_definition.append(Line(previous, current)) previous = current else: raise _CXFParseError("Failed to read item coordinates" \ + " in line %d" % feeder.get_index()) self.letters[character] = char_definition else: # unknown line format raise _CXFParseError("Failed to parse unknown content in " \ + "line %d" % feeder.get_index()) def import_font(filename, callback=None): try: infile = pycam.Utils.URIHandler(filename).open() except IOError, err_msg: log.error("CXFImporter: Failed to read file (%s): %s" \ % (filename, err_msg)) return None try: parsed_font = CXFParser(infile, callback=callback) except _CXFParseError, err_msg: log.warn("CFXImporter: Skipped font defintion file '%s'. Reason: %s." \ % (filename, err_msg)) return None charset = Charset(**parsed_font.meta) for key, value in parsed_font.letters.iteritems(): charset.add_character(key, value) log.info("CXFImporter: Imported CXF font from '%s': %d letters" \ % (filename, len(parsed_font.letters))) infile.close() return charset