# # Copyright (c) 2002, 2003, 2004, 2005, 2006 Art Haas # # This file is part of PythonCAD. # # PythonCAD 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 2 of the License, or # (at your option) any later version. # # PythonCAD 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 PythonCAD; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # basic text functionality # import math import types from PythonCAD.Generic import color from PythonCAD.Generic import entity from PythonCAD.Generic import util def font_style_string(style): """Return a text string for the font style. font_style_string(style) """ if style == TextStyle.FONT_NORMAL: _str = 'normal' elif style == TextStyle.FONT_OBLIQUE: _str = 'oblique' elif style == TextStyle.FONT_ITALIC: _str = 'italic' else: raise ValueError, "Unknown font style: " + str(style) return _str def font_weight_string(weight): """Return a text string for the font weight. font_weight_string(weight) """ if weight == TextStyle.WEIGHT_NORMAL: _str = 'normal' elif weight == TextStyle.WEIGHT_LIGHT: _str = 'light' elif weight == TextStyle.WEIGHT_BOLD: _str = 'bold' elif weight == TextStyle.WEIGHT_HEAVY: _str = 'heavy' else: raise ValueError, "Unknown font weight: " + str(weight) return _str # # the font_prop_map and parse_font() should be moved as # they are GTK specific ... # font_prop_map = { 'Oblique' : 'style', 'Italic' : 'style', 'Ultra-Light' : 'weight', 'Light' : 'weight', 'Medium' : 'weight', 'Semi-Bold' : 'weight', 'Bold' : 'weight', 'Ultra-Bold' : 'weight', 'Heavy' : 'weight', 'Ultra-Condensed' : 'stretch', 'Extra-Condensed' : 'stretch', 'Condensed' : 'stretch', 'Semi-Condensed' : 'stretch', 'Semi-Expanded' : 'stretch', 'Expanded' : 'stretch', 'Extra-Expanded' : 'stretch', 'Ultra-Expanded' : 'stretch', } def parse_font(fontstr): _size = 12 _weight = 0 # NORMAL _style = 0 # NORMAL _stretch = 0# NORMAL _family = 'Sans' if fontstr != '': _fontlist = fontstr.split() _fontlist.reverse() if _fontlist[0].isdigit(): _sz = _fontlist.pop(0) _size = int(_sz) while (_fontlist[0] in font_prop_map): _prop = _fontlist.pop(0) _item = font_prop_map[_prop] # print "prop: " + _prop # print "item: " + _item if _item == 'style': if _prop == 'Oblique': _style = 1 elif _prop == 'Italic': _style = 2 else: _style = 0 # NORMAL # default elif _item == 'weight': if (_prop == 'Ultra-Light' or _prop == 'Light' or _prop == 'Medium'): _weight = 1 elif (_prop == 'Semi-Bold' or _prop == 'Bold'): _weight = 2 elif (_prop == 'Ultra-Bold' or _prop == 'Heavy'): _weight = 3 else: _weight = 0 # NORMAL elif _item == 'stretch': _stretch = _prop # fixme - add stretching bits else: raise ValueError, "Unknown font property: " + _item _fontlist.reverse() if len(_fontlist): _family = ' '.join(_fontlist) return (_family, _style, _weight, _stretch, _size) # # Style class for a text block # class TextStyle(object): """A class for describing text properties. A TextStyle object has the following attributes: family: The font family style: The font style weight: The font weight color: The font color alignment: Text positioning relative to the location angle: Angular position of the text A TextStyle object has the following methods: getFamily(): Get the font family. getStyle(): Get the font style. getWeight(): Get the font weight. getColor(): Get the font color. getSize(): Get the text size. getAngle(): Get the text angle getAlignment(): Get the text positioning. The TextStyle class has the following classmethods: getStyleAsString(): Get the font style in string form. getStyleFromString(): Get the font style value given a string argument getWeightAsString(): Get the font weight in string form. getWeightFromString(): Get the font weight value given a string argument getAlignmentAsString(): Get the text positioning in string form. getAlignmentFromString(): Get the text positioning value given a string. getStyleStrings(): Get the available font style values as strings. getStyleValues(): Get the available font style values. getWeightStrings(): Get the available font weight values as strings. getWeightValues(): Get the available font weight values. getAlignmentStrings(): Get the available text alignment values as strings. getAlignmentValues(): Get the available text alignment values. """ FONT_NORMAL = 0 FONT_OBLIQUE = 1 FONT_ITALIC = 2 WEIGHT_NORMAL = 0 WEIGHT_LIGHT = 1 WEIGHT_BOLD = 2 WEIGHT_HEAVY = 3 ALIGN_LEFT = 0 ALIGN_CENTER = 1 ALIGN_RIGHT = 2 __defcolor = color.Color(0xffffff) def __init__(self, name, **kw): """Initialize a TextStyle object. ts = TextStyle(name) The following are the defaults: family: Sans style: NORMAL weight: NORMAL color: White (#ffffff) size: 1.0 angle: 0.0 alignment: LEFT """ _name = name if not isinstance(_name, unicode): _name = unicode(name) _family = 'Sans' if 'family' in kw: _family = kw['family'] if not isinstance(_family, str): raise TypeError, "Invalid font family: " + str(_family) _style = TextStyle.FONT_NORMAL if 'style' in kw: _style = kw['style'] if not isinstance(_style, int): raise TypeError, "Invalid font style: " + str(_style) if (_style != TextStyle.FONT_NORMAL and _style != TextStyle.FONT_OBLIQUE and _style != TextStyle.FONT_ITALIC): raise ValueError, "Invalid font style value: %d" % _style _weight = TextStyle.WEIGHT_NORMAL if 'weight' in kw: _weight = kw['weight'] if not isinstance(_weight, int): raise TypeError, "Invalid font weight: " + str(_weight) if (_weight != TextStyle.WEIGHT_NORMAL and _weight != TextStyle.WEIGHT_LIGHT and _weight != TextStyle.WEIGHT_BOLD and _weight != TextStyle.WEIGHT_HEAVY): raise ValueError, "Invalid font weight value: %d" % _weight _color = TextStyle.__defcolor if 'color' in kw: _color = kw['color'] if not isinstance(_color, color.Color): raise TypeError, "Invalid color: " + str(_color) _size = 1.0 if 'size' in kw: _size = util.get_float(kw['size']) if _size < 0.0: raise ValueError, "Invalid text size: %g" % _size _angle = 0.0 if 'angle' in kw: _angle = util.get_float(kw['angle']) if _angle > 360.0 or _angle < -360.0: _angle = math.fmod(_angle, 360.0) _align = TextStyle.ALIGN_LEFT if 'align' in kw: _align = kw['align'] if not isinstance(_align, int): raise TypeError, "Invalid text alignment: " + str(_align) if (_align != TextStyle.ALIGN_LEFT and _align != TextStyle.ALIGN_CENTER and _align != TextStyle.ALIGN_RIGHT): raise ValueError, "Invalid text alignment value: %d" % _align super(TextStyle, self).__init__() self.__name = _name self.__family = _family self.__style = _style self.__weight = _weight self.__color = _color self.__size = _size self.__angle = _angle self.__alignment = _align def __eq__(self, obj): if not isinstance(obj, TextStyle): return False if obj is self: return True return (self.__name == obj.getName() and self.__family == obj.getFamily() and self.__style == obj.getStyle() and self.__weight == obj.getWeight() and self.__color == obj.getColor() and abs(self.__size - obj.getSize()) < 1e-10 and abs(self.__angle - obj.getAngle()) < 1e-10 and self.__alignment == obj.getAlignment()) def __ne__(self, obj): if not isinstance(obj, TextStyle): return True if obj is self: return False return (self.__name != obj.getName() or self.__family != obj.getFamily() or self.__style != obj.getStyle() or self.__weight != obj.getWeight() or self.__color != obj.getColor() or abs(self.__size - obj.getSize()) > 1e-10 or abs(self.__angle - obj.getAngle()) > 1e-10 or self.__alignment != obj.getAlignment()) def finish(self): """Finalization for TextStyle instances. """ self.__color = None def getValues(self): _vals = {} _vals['name'] = self.__name _vals['family'] = self.__family _vals['style'] = self.__style _vals['weight'] = self.__weight _vals['color'] = self.__color.getColors() _vals['size'] = self.__size _vals['angle'] = self.__angle _vals['align'] = self.__alignment return _vals def getName(self): """Retrieve the name of the TextStyle. getName() """ return self.__name name = property(getName, None, None, "TextStyle name.") def getFamily(self): """Return the font family. getFamily() """ return self.__family family = property(getFamily, None, None, "Text font family") def getStyle(self): """Return the font style. getStyle() """ return self.__style style = property(getStyle, None, None, "Text font style") def getStyleAsString(cls, s): """Return a text string for the font style. getStyleAsString(s) This classmethod returns 'normal', 'oblique', or 'italic' """ if not isinstance(s, int): raise TypeError, "Invalid argument type: " + `type(s)` if s == TextStyle.FONT_NORMAL: _str = 'normal' elif s == TextStyle.FONT_OBLIQUE: _str = 'oblique' elif s == TextStyle.FONT_ITALIC: _str = 'italic' else: raise ValueError, "Unexpected style value: %d" % s return _str getStyleAsString = classmethod(getStyleAsString) def getStyleFromString(cls, s): """Return a font style value based on a text string. getStyleFromString(s) This classmethod returns a value based on the string argument: 'normal' -> TextStyle.FONT_NORMAL 'oblique' -> TextStyle.FONT_OBLIQUE 'italic' -> TextStyle.FONT_ITALIC If the string is not listed above a ValueError execption is raised. """ if not isinstance(s, str): raise TypeError, "Invalid argument type: " + `type(s)` _ls = s.lower() if _ls == 'normal': _v = TextStyle.FONT_NORMAL elif _ls == 'oblique': _v = TextStyle.FONT_OBLIQUE elif _ls == 'italic': _v = TextStyle.FONT_ITALIC else: raise ValueError, "Unexpected style string: " + s return _v getStyleFromString = classmethod(getStyleFromString) def getStyleStrings(cls): """Return the font style values as strings. getStyleStrings() This classmethod returns a list of strings. """ return [_('Normal'), _('Oblique'), _('Italic') ] getStyleStrings = classmethod(getStyleStrings) def getStyleValues(cls): """Return the font style values. getStyleValues() This classmethod returns a list of values. """ return [TextStyle.FONT_NORMAL, TextStyle.FONT_OBLIQUE, TextStyle.FONT_ITALIC ] getStyleValues = classmethod(getStyleValues) def getWeight(self): """Return the font weight. getWeight() """ return self.__weight weight = property(getWeight, None, None, "Text font weight") def getWeightAsString(cls, w): """Return a text string for the font weight. getWeightAsString(w) This classmethod returns 'normal', 'light', 'bold', or 'heavy'. """ if not isinstance(w, int): raise TypeError, "Invalid argument type: " + `type(w)` if w == TextStyle.WEIGHT_NORMAL: _str = 'normal' elif w == TextStyle.WEIGHT_LIGHT: _str = 'light' elif w == TextStyle.WEIGHT_BOLD: _str = 'bold' elif w == TextStyle.WEIGHT_HEAVY: _str = 'heavy' else: raise ValueError, "Unexpected weight value: %d" % w return _str getWeightAsString = classmethod(getWeightAsString) def getWeightFromString(cls, s): """Return a font weight value for a given string argument. getWeightFromString(s) This classmethod returns a value based on the string argument: 'normal' -> TextStyle.WEIGHT_NORMAL 'light' -> TextStyle.WEIGHT_LIGHT 'bold' -> TextStyle.WEIGHT_BOLD 'heavy' -> TextStyle.WEIGHT_HEAVY If the string is not listed above a ValueError execption is raised. """ if not isinstance(s, str): raise TypeError, "Invalid argument type: " + `type(s)` _ls = s.lower() if _ls == 'normal': _v = TextStyle.WEIGHT_NORMAL elif _ls == 'light': _v = TextStyle.WEIGHT_LIGHT elif _ls == 'bold': _v = TextStyle.WEIGHT_BOLD elif _ls == 'heavy': _v = TextStyle.WEIGHT_HEAVY else: raise ValueError, "Unexpected weight string: " + s return _v getWeightFromString = classmethod(getWeightFromString) def getWeightStrings(cls): """Return the font weight values as strings. getWeightStrings() This classmethod returns a list of strings. """ return [_('Normal'), _('Light'), _('Bold'), _('Heavy') ] getWeightStrings = classmethod(getWeightStrings) def getWeightValues(cls): """Return the font weight values. getWeightValues() This classmethod returns a list of values. """ return [TextStyle.WEIGHT_NORMAL, TextStyle.WEIGHT_LIGHT, TextStyle.WEIGHT_BOLD, TextStyle.WEIGHT_HEAVY ] getWeightValues = classmethod(getWeightValues) def getColor(self): """Return the font color. getColor() """ return self.__color color = property(getColor, None, None, "Text color") def getSize(self): """Return the specified size. getSize() """ return self.__size size = property(getSize, None, None, "Text size.") def getAngle(self): """Return the angle at which the text is drawn. getAngle() This method returns an angle -360.0 < angle < 360.0. """ return self.__angle angle = property(getAngle, None, None, "Text angle.") def getAlignment(self): """Return the line justification setting. getAlignment() """ return self.__alignment alignment = property(getAlignment, None, "Text alignment.") def getAlignmentAsString(cls, a): """Return a text string for the text alignment. getAlignmentAsString(w) This classmethod returns 'left', 'center', or 'right' """ if not isinstance(a, int): raise TypeError, "Invalid argument type: " + `type(a)` if a == TextStyle.ALIGN_LEFT: _str = 'left' elif a == TextStyle.ALIGN_CENTER: _str = 'center' elif a == TextStyle.ALIGN_RIGHT: _str = 'right' else: raise ValueError, "Unexpected alignment value: %d" % a return _str getAlignmentAsString = classmethod(getAlignmentAsString) def getAlignmentFromString(cls, s): """Return a text alignment based on a string argument. getAlignmentFromString(s) This classmethod returns a value based on the string argument: 'left' -> TextStyle.ALIGN_LEFT 'center' -> TextStyle.ALIGN_CENTER 'right' -> TextStyle.ALIGN_RIGHT If the string is not listed above a ValueError execption is raised. """ if not isinstance(s, str): raise TypeError, "Invalid argument type: " + `type(s)` _ls = s.lower() if _ls == 'left': _v = TextStyle.ALIGN_LEFT elif _ls == 'center': _v = TextStyle.ALIGN_CENTER elif _ls == 'right': _v = TextStyle.ALIGN_RIGHT else: raise ValueError, "Unexpected alignment string: " + s return _v getAlignmentFromString = classmethod(getAlignmentFromString) def getAlignmentStrings(cls): """Return the text alignment values as strings. getAlignmentStrings() This classmethod returns a list of strings. """ return [_('Left'), _('Center'), _('Right') ] getAlignmentStrings = classmethod(getAlignmentStrings) def getAlignmentValues(cls): """Return the text alignment values. getAlignmentValues() This classmethod returns a list of values. """ return [TextStyle.ALIGN_LEFT, TextStyle.ALIGN_CENTER, TextStyle.ALIGN_RIGHT ] getAlignmentValues = classmethod(getAlignmentValues) # # TextBlock # class TextBlock(entity.Entity): """A class representing text in a drawing. A TextBlock instance has the following attributes: text: Text within the TextBlock location: Spatial location of the TextBlock family: The font family style: The font style weight: The font weight color: The font color size: Text size alignment: Text positioning at the location angle: Angular position of the text A TextBlock instance has the following methods: {get/set}TextStyle(): Get/Set the TextStyle for the TextBlock {get/set}Text(): Get/Set the text {get/set}Location(): Get/Set the TextBlock location {get/set}Family(): Get/Set the font family. {get/set}Style(): Get/Set the font style. {get/set}Weight(): Get/Set the font weight. {get/set}Color(): Get/Set the font color. {get/set}Size(): Get/Set the text size. {get/set}Angle(): Get/Set the text angle {get/set}Alignment(): Get/Set the text positioning {get/set}Bounds(): Get/Set the height and width of the TextBlock. {get/set}FontScale(): Get/Set a scale factor used for font display. getLineCount(): Get the number of lines stored in the TextBlock The TextBlock class has the following classmethods: {get/set}DefaultTextStyle(): Get/Set the default TextStyle for the class. """ __messages = { 'text_changed' : True, 'textstyle_changed' : True, 'font_family_changed' : True, 'font_style_changed' : True, 'font_weight_changed' : True, 'font_color_changed' : True, 'text_size_changed' : True, 'text_angle_changed' : True, 'text_alignment_changed' : True, 'moved' : True, } __defstyle = None def __init__(self, x, y, text, textstyle=None, **kw): """Initialize a TextBlock instance. TextBlock(x, y, text[, textstyle=None]) """ _x = util.get_float(x) _y = util.get_float(y) if not isinstance(text, types.StringTypes): raise TypeError, "Invalid text data: " + str(text) _tstyle = textstyle if _tstyle is None: _tstyle = self.getDefaultTextStyle() else: if not isinstance(_tstyle, TextStyle): raise TypeError, "Invalid TextStyle object: " + `_tstyle` _family = None if 'family' in kw: _family = kw['family'] if not isinstance(_family, types.StringTypes): raise TypeError, "Invalid font family: " + str(_family) if _family == _tstyle.getFamily(): _family = None _style = None if 'style' in kw: _style = kw['style'] if not isinstance(_style, int): raise TypeError, "Invalid font style: " + str(_style) if (_style != TextStyle.FONT_NORMAL and _style != TextStyle.FONT_OBLIQUE and _style != TextStyle.FONT_ITALIC): raise ValueError, "Invalid font style value: %d" % _style if _style == _tstyle.getStyle(): _style = None _weight = None if 'weight' in kw: _weight = kw['weight'] if not isinstance(_weight, int): raise TypeError, "Invalid font weight: " + str(_weight) if (_weight != TextStyle.WEIGHT_NORMAL and _weight != TextStyle.WEIGHT_LIGHT and _weight != TextStyle.WEIGHT_BOLD and _weight != TextStyle.WEIGHT_HEAVY): raise ValueError, "Invalid font weight value: %d" % _weight if _weight == _tstyle.getWeight(): _weight = None _color = None if 'color' in kw: _color = kw['color'] if not isinstance(_color, color.Color): raise TypeError, "Invalid font color: " + str(_color) if _color == _tstyle.getColor(): _color = None _size = None if 'size' in kw: _size = util.get_float(kw['size']) if _size < 0.0: raise ValueError, "Invalid text size: %g" % _size if abs(_size - _tstyle.getSize()) < 1e-10: _size = None _angle = None if 'angle' in kw: _angle = util.get_float(kw['angle']) if _angle > 360.0 or _angle < -360.0: _angle = math.fmod(_angle, 360.0) if abs(_angle - _tstyle.getAngle()) < 1e-10: _angle = None _align = None if 'align' in kw: _align = kw['align'] if not isinstance(_align, int): raise TypeError, "Invalid text alignment: " + str(_align) if (_align != TextStyle.ALIGN_LEFT and _align != TextStyle.ALIGN_CENTER and _align != TextStyle.ALIGN_RIGHT): raise ValueError, "Invalid text alignment value: %d" % _align if _align == _tstyle.getAlignment(): _align = None super(TextBlock, self).__init__(**kw) self.__location = (_x, _y) self.__text = text self.__tstyle = _tstyle self.__family = _family self.__style = _style self.__weight = _weight self.__color = _color self.__size = _size self.__angle = _angle self.__alignment = _align self.__bounds = None self.__scale = None def getDefaultTextStyle(cls): if cls.__defstyle is None: cls.__defstyle = TextStyle(u'Default Text Style', color=color.Color(0xffffff)) return cls.__defstyle getDefaultTextStyle = classmethod(getDefaultTextStyle) def setDefaultTextStyle(cls, s): if not isinstance(s, TextStyle): raise TypeError, "Invalid TextStyle: " + `type(s)` cls.__defstyle = s setDefaultTextStyle = classmethod(setDefaultTextStyle) def finish(self): """Finalization for TextBlock instances. finish() """ if self.__color is not None: self.__color = None super(TextBlock, self).finish() def getValues(self): """Return values comprising the TextBlock. getValues() This method extends the Entity::getValues() method. """ _data = super(TextBlock, self).getValues() _data.setValue('type', 'textblock') _data.setValue('location', self.__location) _data.setValue('text', self.__text) _data.setValue('textstyle', self.__tstyle.getValues()) if self.__family is not None: _data.setValue('family', self.__family) if self.__style is not None: _data.setValue('style', self.__style) if self.__weight is not None: _data.setValue('weight', self.__weight) if self.__color is not None: _data.setValue('color', self.__color.getColors()) if self.__size is not None: _data.setValue('size', self.__size) if self.__angle is not None: _data.setValue('angle', self.__angle) if self.__alignment is not None: _data.setValue('align', self.__alignment) return _data def getText(self): """Get the current text within the TextBlock. getText() """ return self.__text def setText(self, text): """Set the text within the TextBlock. setText(text) """ if not isinstance(text, types.StringTypes): raise TypeError, "Invalid text data: " + str(text) _ot = self.__text if _ot != text: self.startChange('text_changed') self.__text = text self.endChange('text_changed') if self.__bounds is not None: self.__bounds = None self.sendMessage('text_changed', _ot) self.modified() text = property(getText, setText, None, "TextBlock text.") def getLocation(self): """Return the TextBlock spatial position. getLocation() """ return self.__location def setLocation(self, x, y): """Store the spatial position of the TextBlock. setLocation(x, y) Arguments 'x' and 'y' should be float values. """ _x = util.get_float(x) _y = util.get_float(y) _ox, _oy = self.__location if abs(_ox - _x) > 1e-10 or abs(_oy - _y) > 1e-10: self.startChange('moved') self.__location = (_x, _y) self.endChange('moved') self.sendMessage('moved', _ox, _oy) self.modified() location = property(getLocation, None, None, "TextBlock location") def getTextStyle(self): """Return the TextStyle associated with this TextBlock. getTextStyle() """ return self.__tstyle def setTextStyle(self, textstyle=None): """Store the TextStyle associated with this TextBlock. setTextStyle([textstyle]) Optional argument 'textstyle' should be an TextStyle instance. If no argument is given the TextBlock will use a default TextStyle. The TextBlock will have all the text appearance and positioning attributes set the the values in the TextStyle. """ _ts = textstyle if _ts is None: _ts = self.getDefaultTextStyle() if not isinstance(_ts, TextStyle): raise TypeError, "Invalid text style: " + `_ts` _os = self.__tstyle if _os != _ts: _opts = {} if self.__family is not None: _opts['family'] = self.__family if self.__style is not None: _opts['style'] = self.__style if self.__weight is not None: _opts['weight'] = self.__weight if self.__color is not None: _opts['color'] = self.__color if self.__size is not None: _opts['size'] = self.__size if self.__angle is not None: _opts['angle'] = self.__angle if self.__alignment is not None: _opts['align'] = self.__alignment self.startChange('textstyle_changed') self.__tstyle = _ts self.endChange('textstyle_changed') # # call the methods with no arguments to set the values # given in the new TextStyle # self.setFamily() self.setStyle() self.setWeight() self.setColor() self.setSize() self.setAngle() self.setAlignment() self.sendMessage('textstyle_changed', _os, _opts) self.modified() def getFamily(self): """Return the font family. getFamily() """ _family = self.__family if _family is None: _family = self.__tstyle.getFamily() return _family def setFamily(self, family=None): """Set the font family. setFamily([family]) Optional argument 'family' should be a string giving the font family. Calling this method without an argument sets the font family to that defined in the TextStyle. """ if self.isLocked(): raise RuntimeError, "Setting family not allowed - object locked." _family = family if _family is not None: if not isinstance(family, types.StringTypes): raise TypeError, "Invalid family type: " + `type(family)` _f = self.getFamily() if ((_family is None and self.__family is not None) or (_family is not None and _family != _f)): self.startChange('font_family_changed') self.__family = _family self.endChange('font_family_changed') if self.__bounds is not None: self.__bounds = None self.sendMessage('font_family_changed', _f) self.modified() family = property(getFamily, setFamily, None, "Text object font family") def getStyle(self): """Return the font style. getStyle() """ _style = self.__style if _style is None: _style = self.__tstyle.getStyle() return _style def setStyle(self, style=None): """Set the font style. setStyle([style]) Optional argument 'style' should be one of the following: TextStyle.FONT_NORMAL TextStyle.FONT_OBLIQUE TextStyle.FONT_ITALIC Calling this method without an argument restores the font style to that defined in the TextStyle. """ if self.isLocked(): raise RuntimeError, "Setting style not allowed - object locked." _style = style if _style is not None: if not isinstance(_style, int): raise TypeError, "Invalid TextStyle font style type: " + `type(_style)` if (_style != TextStyle.FONT_NORMAL and _style != TextStyle.FONT_OBLIQUE and _style != TextStyle.FONT_ITALIC): raise ValueError, "Invalid font style: " + str(_style) _s = self.getStyle() if ((_style is None and self.__style is not None) or (_style is not None and _style != _s)): self.startChange('font_style_changed') self.__style = _style self.endChange('font_style_changed') if self.__bounds is not None: self.__bounds = None self.sendMessage('font_style_changed', _s) self.modified() style = property(getStyle, setStyle, None, "Text font style") def getWeight(self): """Return the font weight. getWeight() """ _weight = self.__weight if _weight is None: _weight = self.__tstyle.getWeight() return _weight def setWeight(self, weight=None): """Set the font weight. setWeight([weight]) Optional argument 'weight' should be one of the following values: TextStyle.WEIGHT_NORMAL TextStyle.WEIGHT_LIGHT TextStyle.WEIGHT_BOLD TextStyle.WEIGHT_HEAVY Calling this method without an argument restores the font weight to that defined in the TextStyle. """ if self.isLocked(): raise RuntimeError, "Setting weight not allowed - object locked." _weight = weight if _weight is not None: if not isinstance(_weight, int): raise TypeError, "Invalid TextStyle font weight type: " + `type(_weight)` if (_weight != TextStyle.WEIGHT_NORMAL and _weight != TextStyle.WEIGHT_LIGHT and _weight != TextStyle.WEIGHT_BOLD and _weight != TextStyle.WEIGHT_HEAVY): raise ValueError, "Invalid text weight: %d" % _weight _w = self.getWeight() if ((_weight is None and self.__weight is not None) or (_weight is not None and _weight != _w)): self.startChange('font_weight_changed') self.__weight = _weight self.endChange('font_weight_changed') if self.__bounds is not None: self.__bounds = None self.sendMessage('font_weight_changed', _w) self.modified() weight = property(getWeight, setWeight, None, "Text font weight") def getColor(self): """Return the font color. getColor() """ _color = self.__color if _color is None: _color = self.__tstyle.getColor() return _color def setColor(self, col=None): """Set the font color. setColor([col]) Optional argument 'col' should be a Color object. Calling this method without an argument restores the font color to that defined in the TextStyle. """ if self.isLocked(): raise RuntimeError, "Setting color not allowed - object locked." _col = col if _col is not None: if not isinstance(_col, color.Color): raise TypeError, "Invalid color type: " + `type(_col)` _c = self.getColor() if ((_col is None and self.__color is not None) or (_col is not None and _col != _c)): self.startChange('font_color_changed') self.__color = _col self.endChange('font_color_changed') self.sendMessage('font_color_changed', _c) self.modified() color = property(getColor, setColor, None, "Text color") def getSize(self): """Return the text size. getSize() """ _size = self.__size if _size is None: _size = self.__tstyle.getSize() return _size def setSize(self, size=None): """Set the size of the text. setSize([size]) Optionala rgument 'size' should be a float value greater than 0. Calling this method without an argument restores the text size to the value given in the TextStyle. """ if self.isLocked(): raise RuntimeError, "Setting size not allowed - object locked." _size = size if _size is not None: _size = util.get_float(_size) if _size < 0.0: raise ValueError, "Invalid size: %g" % _size _os = self.getSize() if ((_size is None and self.__size is not None) or (_size is not None and abs(_size - _os) > 1e-10)): self.startChange('text_size_changed') self.__size = _size self.endChange('text_size_changed') if self.__bounds is not None: self.__bounds = None self.sendMessage('text_size_changed', _os) self.modified() size = property(getSize, setSize, None, "Text size.") def getAngle(self): """Return the angle at which the text is drawn. getAngle() This method returns an angle -360.0 < angle < 360.0. """ _angle = self.__angle if _angle is None: _angle = self.__tstyle.getAngle() return _angle def setAngle(self, angle=None): """Set the angle at which the text block should be drawn. setAngle([angle]) Optional argument 'angle' should be a float value. Calling this method without arguments sets the angle to be the value defined in the TextStyle. """ if self.isLocked(): raise RuntimeError, "Setting angle not allowed - object locked." _angle = angle if _angle is not None: _angle = util.get_float(_angle) if _angle > 360.0 or _angle < -360.0: _angle = math.fmod(_angle, 360.0) _a = self.getAngle() if ((_angle is None and self.__angle is not None) or (_angle is not None and abs(_angle - _a) > 1e-10)): self.startChange('text_angle_changed') self.__angle = _angle self.endChange('text_angle_changed') self.sendMessage('text_angle_changed', _a) self.modified() angle = property(getAngle, setAngle, None, "Text angle.") def getAlignment(self): """Return the line justification setting. getAlignment() """ _align = self.__alignment if _align is None: _align = self.__tstyle.getAlignment() return _align def setAlignment(self, align=None): """Set left, center, or right line justification. setAlignment([align]) Optional argument 'align' should be one of TextStyle.ALIGN_LEFT TextStyle.ALIGN_CENTER TextStyle.ALIGN_RIGHT Calling this method without arguments sets the text alignment to be that given in the TextStyle. """ if self.isLocked(): raise RuntimeError, "Setting alignment not allowed - object locked." _align = align if _align is not None: if not isinstance(_align, int): raise TypeError, "Invalid TextStyle alignment type: " + `type(_align)` if (_align != TextStyle.ALIGN_LEFT and _align != TextStyle.ALIGN_CENTER and _align != TextStyle.ALIGN_RIGHT): raise ValueError, "Invalid text alignment value: %d" % _align _a = self.getAlignment() if ((_align is None and self.__alignment is not None) or (_align is not None and _align != _a)): self.startChange('text_alignment_changed') self.__alignment = _align self.endChange('text_alignment_changed') if self.__bounds is not None: self.__bounds = None self.sendMessage('text_alignment_changed', _a) self.modified() alignment = property(getAlignment, setAlignment, None, "Text alignment.") def move(self, dx, dy): """Move a TextBlock. move(dx, dy) The first argument gives the x-coordinate displacement, and the second gives the y-coordinate displacement. Both values should be floats. """ if self.isLocked(): raise RuntimeError, "Moving not allowed - object locked." _dx = util.get_float(dx) _dy = util.get_float(dy) if abs(_dx) > 1e-10 or abs(_dy) > 1e-10: _x, _y = self.__location self.startChange('moved') self.__location = ((_x + _dx), (_y + _dy)) self.endChange('moved') self.sendMessage('moved', _x, _y) self.modified() def getLineCount(self): """Return the number of lines of text in the TextBlock getLineCount() """ # # ideally Python itself would provide a linecount() method # so the temporary list would not need to be created ... # return len(self.__text.splitlines()) def clone(self): """Return an identical copy of a TextBlock. clone() """ _x, _y = self.getLocation() _text = self.getText() _textstyle = self.getTextStyle() _tb = TextBlock(_x, _y, _text, _textstyle) _family = self.getFamily() if _family != _textstyle.getFamily(): _tb.setFamily(_family) _style = self.getStyle() if _style != _textstyle.getStyle(): _tb.setStyle(_style) _weight = self.getWeight() if _weight != _textstyle.getWeight(): _tb.setWeight(_weight) _color = self.getColor() if _color != _textstyle.getColor(): _tb.setColor(_color) _size = self.getSize() if abs(_size - _textstyle.getSize()) > 1e-10: _tb.setSize(_size) _angle = self.getAngle() if abs(_angle - _textstyle.getAngle()) > 1e-10: _tb.setAngle(_angle) _align = self.getAlignment() if _align != _textstyle.getAlignment(): _tb.setAlignment(_align) return _tb def getBounds(self): """Get the width and height of the TextBlock. getBounds() This method can return None if the boundary has not been calculated or the TextBlock has been changed. """ return self.__bounds def setBounds(self, width=None, height=None): """Set the width and height of the TextBlock. setBounds([width, height]) Arguments 'width' and 'height' should be positive float values if used. If both arguments are None, the boundary of the TextBlock is unset. """ _w = width _h = height if _w is None and _h is None: self.__bounds = None else: if _w is None: raise ValueError, "Width cannot be None" _w = util.get_float(_w) if _w < 0.0: raise ValueError, "Invalid width: %g" % _w if _h is None: raise ValueError, "Height cannot be None" _h = util.get_float(_h) if _h < 0.0: raise ValueError, "Invalid height: %g" % _w self.__bounds = (_w, _h) def inRegion (self, xmin, ymin, xmax, ymax, fully=True): """Returns True if the TextBlock is within the bounding values. inRegion(xmin, ymin, xmax, ymax) The four arguments define the boundary of an area, and the function returns True if the TextBlock lies within that area. Otherwise, the function returns False. """ _xmin = util.get_float(xmin) _ymin = util.get_float(ymin) _xmax = util.get_float(xmax) if _xmax < _xmin: raise ValueError, "Illegal values: xmax < xmin" _ymax = util.get_float(ymax) if _ymax < _ymin: raise ValueError, "Illegal values: ymax < ymin" util.test_boolean(fully) _x, _y = self.__location if self.__bounds is None: return not ((_x < _xmin) or (_x > _xmax) or (_y < _ymin) or (_y > _ymax)) else: _w, _h = self.__bounds _flag = True _align = self.getAlignment() if _align == TextStyle.ALIGN_LEFT: if ((_x > _xmax) or ((_x + _w) < _xmin)): _flag = False elif _align == TextStyle.ALIGN_CENTER: if (((_x - (_w/2.0)) > _xmax) or ((_x + (_w/2.0)) < _xmin)): _flag = False elif _align == TextStyle.ALIGN_RIGHT: if (((_x - _w) > _xmax) or (_x < _xmin)): _flag = False else: raise ValueError, "Unexpected alignment: %d" % _align if _flag: _flag = not ((_y < _ymin) or ((_y - _h) > _ymax)) return _flag def getFontScale(self): """Return the stored font scaling factor getFontScale() This method will raise a ValueError exception if there has not be a value stored with the setFontScale() call. """ if self.__scale is None: raise ValueError, "Scale not set." return self.__scale def setFontScale(self, scale): """Store a value used to scale the TextBlock font. setFontScale(scale) Argument 'scale' should be a positive float value. """ _s = util.get_float(scale) if not _s > 0.0: raise ValueError, "Invalid scale value: %g" % _s self.__scale = _s def sendsMessage(self, m): if m in TextBlock.__messages: return True return super(TextBlock, self).sendsMessage(m) # # TextBlock history class # class TextBlockLog(entity.EntityLog): __setops = { 'text_changed' : TextBlock.setText, 'font_family_changed' : TextBlock.setFamily, 'font_style_changed' : TextBlock.setStyle, 'font_color_changed' : TextBlock.setColor, 'font_weight_changed' : TextBlock.setWeight, 'text_size_changed' : TextBlock.setSize, 'text_angle_changed' : TextBlock.setAngle, 'text_alignment_changed' : TextBlock.setAlignment, } __getops = { 'text_changed' : TextBlock.getText, 'font_family_changed' : TextBlock.getFamily, 'font_style_changed' : TextBlock.getStyle, 'font_color_changed' : TextBlock.getColor, 'font_weight_changed' : TextBlock.getWeight, 'text_size_changed' : TextBlock.getSize, 'text_angle_changed' : TextBlock.getAngle, 'text_alignment_changed' : TextBlock.getAlignment, } def __init__(self, tblock): if not isinstance(tblock, TextBlock): raise TypeError, "Invalid TextBlock: " + `tblock` super(TextBlockLog, self).__init__(tblock) tblock.connect('textstyle_changed', self._textstyleChanged) tblock.connect('text_changed', self._textChanged) tblock.connect('font_family_changed', self._familyChanged) tblock.connect('font_style_changed', self._styleChanged) tblock.connect('font_color_changed', self._colorChanged) tblock.connect('font_weight_changed', self._weightChanged) tblock.connect('text_size_changed', self._sizeChanged) tblock.connect('text_angle_changed', self._angleChanged) tblock.connect('text_alignment_changed', self._alignmentChanged) tblock.connect('moved', self._moveText) def _textstyleChanged(self, tb, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _ts = args[0] if not isinstance(_ts, TextStyle): raise TypeError, "Invalid TextStyle type: " + `type(_ts)` _opts = args[1] if not isinstance(_opts, dict): raise TypeError, "Invalid option type: " + `type(_opts)` _data = {} _data['textstyle'] = _ts.getValues() for _k, _v in _opts: if _k == 'color': _data['color'] = _v.getColors() else: _data[_k] = _v self.saveUndoData('textstyle_changed', _data) def _textChanged(self, tb, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _text = args[0] if not isinstance(_text, types.StringTypes): raise TypeError, "Invalid text type: " + `type(_text)` self.saveUndoData('text_changed', _text) def _familyChanged(self, tb, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _family = args[0] if not isinstance(_family, types.StringTypes): raise TypeError, "Invalid family type: " + `type(_family)` self.saveUndoData('font_family_changed', _family) def _styleChanged(self, tb, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _style = args[0] if (_style != TextStyle.FONT_NORMAL and _style != TextStyle.FONT_OBLIQUE and _style != TextStyle.FONT_ITALIC): raise ValueError, "Invalid font style: " + str(_style) self.saveUndoData('font_style_changed', _style) def _colorChanged(self, tb, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _color = args[0] if not isinstance(_color, color.Color): raise TypeError, "Invalid color type: " + `type(_color)` self.saveUndoData('font_color_changed', _color.getColors()) def _weightChanged(self, tb, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _weight = args[0] if (_weight != TextStyle.WEIGHT_NORMAL and _weight != TextStyle.WEIGHT_LIGHT and _weight != TextStyle.WEIGHT_BOLD and _weight != TextStyle.WEIGHT_HEAVY): raise ValueError, "Invalid text weight: %d" % _weight self.saveUndoData('font_weight_changed', _weight) def _sizeChanged(self, tb, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _size = args[0] if not isinstance(_size, float): raise TypeError, "Unexpected size type: " + `type(_size)` if _size < 0.0: raise ValueError, "Invalid text size: %g" % _size self.saveUndoData('text_size_changed', _size) def _angleChanged(self, tb, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _angle = args[0] if not isinstance(_angle, float): raise TypeError, "Unexpected angle type: " + `type(_angle)` if _angle > 360.0 or _angle < -360.0: _angle = math.fmod(_angle, 360.0) self.saveUndoData('text_angle_changed', _angle) def _alignmentChanged(self, tb, *args): _alen = len(args) if _alen < 1: raise ValueError, "Invalid argument count: %d" % _alen _align = args[0] if (_align != TextStyle.ALIGN_LEFT and _align != TextStyle.ALIGN_CENTER and _align != TextStyle.ALIGN_RIGHT): raise ValueError, "Invalid text alignment value: %d" % _align self.saveUndoData('text_alignment_changed', _align) def _moveText(self, tb, *args): _alen = len(args) if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _x = args[0] if not isinstance(_x, float): raise TypeError, "Unexpected type for x: " + `type(_x)` _y = args[1] if not isinstance(_y, float): raise TypeError, "Unexpected type for y: " + `type(_y)` self.saveUndoData('moved', _x, _y) def execute(self, undo, *args): util.test_boolean(undo) _alen = len(args) if len(args) == 0: raise ValueError, "No arguments to execute()" _tblock = self.getObject() _image = None _layer = _tblock.getParent() if _layer is not None: _image = _layer.getParent() _op = args[0] if (_op == 'text_changed' or _op == 'font_family_changed' or _op == 'font_style_changed' or _op == 'font_weight_changed' or _op == 'text_size_changed' or _op == 'text_angle_changed' or _op == 'text_alignment_changed'): if len(args) < 2: raise ValueError, "Invalid argument count: %d" % _alen _val = args[1] _get = TextBlockLog.__getops[_op] _sdata = _get(_tblock) self.ignore(_op) try: _set = TextBlockLog.__setops[_op] if undo: _tblock.startUndo() try: _set(_tblock, _val) finally: _tblock.endUndo() else: _tblock.startRedo() try: _set(_tblock, _val) finally: _tblock.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _sdata) elif _op == 'moved': if _alen < 3: raise ValueError, "Invalid argument count: %d" % _alen _x = args[1] if not isinstance(_x, float): raise TypeError, "Unexpected type for x: " + `type(_x)` _y = args[2] if not isinstance(_y, float): raise TypeError, "Unexpected type for y: " + `type(_y)` _tx, _ty = _tblock.getLocation() self.ignore(_op) try: if undo: _tblock.startUndo() try: _tblock.setLocation(_x, _y) finally: _tblock.endUndo() else: _tblock.startRedo() try: _tblock.setLocation(_x, _y) finally: _tblock.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _tx, _ty) elif _op == 'font_color_changed': if _image is None: raise RuntimeError, "TextBlock not stored in an Image" if _alen < 2: raise ValueError, "Invalid argument count: %d" % _alen _sdata = _tblock.getColor().getColors() self.ignore(_op) try: _color = None for _c in _image.getImageEntities('color'): if _c.getColors() == args[1]: _color = _c break if undo: _tblock.startUndo() try: _tblock.setColor(_color) finally: _tblock.endUndo() else: _tblock.startRedo() try: _tblock.setColor(_color) finally: _tblock.endRedo() finally: self.receive(_op) self.saveData(undo, _op, _sdata) elif _op == 'textstyle_changed': if _image is None: raise RuntimeError, "TextBlock not stored in an Image" if _alen < 2: raise ValueError, "Invalid argument cound: %d" % _alen _data = args[1] _tsdata = _data['textstyle'] _sdata = {} _ts = _tblock.getTextStyle() _sdata['textstyle'] = _ts.getValues() _family = _tblock.getFamily() if _family != _ts.getFamily(): _sdata['family'] = _family _style = _tblock.getStyle() if _style != _ts.getStyle(): _sdata['style'] = _style _weight = _tblock.getWeight() if _weight != _ts.getWeight(): _sdata['weight'] = _weight _color = _tblock.getColor() if _color != _ts.getColor(): _sdata['color'] = _color.getColors() _size = _tblock.getSize() if abs(_size - _ts.getSize()) > 1e-10: _sdata['size'] = _size _angle = _tblock.getAngle() if abs(_angle - _ts.getAngle()) > 1e-10: _sdata['angle'] = _angle _align = _tblock.getAlignment() if _align != _ts.getAlignment(): _sdata['align'] = _align self.ignore(_op) try: _tstyle = None for _ts in _image.getImageEntities('textstyle'): if _ts.getName() != _tsdata['name']: continue if _ts.getFamily() != _tsdata['family']: continue if _ts.getStyle() != _tsdata['style']: continue if _ts.getWeight() != _tsdata['weight']: continue if _ts.getColor().getColors() != _tsdata['color']: continue if abs(_ts.getSize() - _tsdata['size']) > 1e-10: continue if abs(_ts.getAngle() - _tsdata['angle']) > 1e-10: continue if _ts.getAlignment() != _tsdata['align']: continue _tstyle = _ts break if undo: _tblock.startUndo() try: _tblock.setTextStyle(_tstyle) finally: _tblock.endUndo() else: _tblock.startRedo() try: _tblock.setTextStyle(_tstyle) finally: _tblock.endRedo() finally: self.receive(_op) # # restore values differing from the TextStyle # _tblock.mute() try: _family = _data.get('family') if _family is not None: _tblock.setFamily(_family) _style = _data.get('style') if _style is not None: _tblock.setStyle(_style) _weight = _data.get('weight') if _weight is not None: _tblock.setWeight(_weight) _color = _data.get('color') if _color is not None: _col = None for _c in _image.getImageEntities('color'): if _c.getColors() == _color: _col = _c break _tblock.setColor(_col) _size = _data.get('size') if _size is not None: _tblock.setSize(_size) _angle = _data.get('angle') if _angle is not None: _tblock.setAngle(_angle) _align = _data.get('align') if _align is not None: _tblock.setAlignment(_align) finally: _tblock.unmute() self.saveData(undo, _op, _sdata) else: super(TextBlockLog, self).execute(undo, *args)