PageRenderTime 110ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/bkchem-0.14.0-pre2/bkchem/ftext.py

#
Python | 272 lines | 227 code | 25 blank | 20 comment | 14 complexity | aecc6d87dcc22446dca418b151450f18 MD5 | raw file
Possible License(s): GPL-2.0
  1. #--------------------------------------------------------------------------
  2. # This file is part of BKChem - a chemical drawing program
  3. # Copyright (C) 2002-2009 Beda Kosata <beda@zirael.org>
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. # Complete text of GNU GPL can be found in the file gpl.txt in the
  13. # main directory of the program
  14. #--------------------------------------------------------------------------
  15. """this module provides extended methods for formating of text items (for canvas)
  16. ftext is XML based. Tags used for formating are:
  17. sub - (sub)script, sup - (sup)erscript, b - bold, i - italic"""
  18. import tkFont
  19. import dom_extensions
  20. import xml.sax
  21. import copy
  22. import tuning
  23. class ftext:
  24. def __init__( self, canvas, xy, text, font=None, pos="center-first", fill='#000', big_charges=True, justify='right'):
  25. self.big_charges = big_charges # should +- in <sup> be drawn in bigger font (not scaled down)?
  26. self.canvas = canvas
  27. self.items = []
  28. self.tags = ('ftext'),
  29. if xy:
  30. self.x, self.y = xy
  31. if text:
  32. self.text = text
  33. if font:
  34. self.font = font
  35. else:
  36. self.font = tkFont.Font( family="Helvetica", size=12)
  37. self._font_family = self.font.actual('family')
  38. self._font_size = int( self.font.actual('size'))
  39. self.pos = pos
  40. self.fill = fill
  41. self.justify = justify
  42. def draw( self):
  43. # split text to chunks
  44. chs = self.get_chunks()
  45. if not chs:
  46. return None
  47. self.items = []
  48. self._current_x = self.x
  49. self._current_y = self.y
  50. self.items = chs
  51. last_attrs = set()
  52. last_x = self._current_x
  53. for ch in self.items:
  54. scale = 1
  55. if set(('sub','sup')) & ch.attrs:
  56. if not (ch.text in "-" and self.big_charges):
  57. scale = 0.7
  58. else:
  59. scale = 1
  60. # we ignore subscripts and superscript in bbox calculation
  61. ch.ignore_y = True
  62. ch.ignore_x = True
  63. if set(('sub','sup')) & last_attrs:
  64. self._current_x = last_x
  65. last_x = self._current_x
  66. last_attrs = ch.attrs
  67. bbox = self._draw_chunk( ch, scale=scale)
  68. if ch.newline_after:
  69. self._current_y = bbox[3] + self.font.metrics()['linespace'] / 2.0
  70. self._current_x = self.x
  71. #does not work when 1. character is not regular
  72. if self.pos == 'center-first':
  73. self.diff = self.font.measure( self.items[0].text[0])/2.0
  74. elif self.pos == 'center-last':
  75. x1, y1, x2, y2 = self.bbox()
  76. self.diff = x2 -x1 -self.font.measure( self.items[0].text[-1])/2.0 -2
  77. self.move( -self.diff, 0)
  78. return self.bbox()
  79. def _draw_chunk( self, chunk, scale=1):
  80. weight = ''
  81. canvas = self.canvas
  82. x = self._current_x
  83. y = self._current_y
  84. if 'b' in chunk.attrs:
  85. weight = "bold"
  86. if 'i' in chunk.attrs:
  87. weight += " italic"
  88. if not weight:
  89. weight = "normal"
  90. if 'sub' in chunk.attrs:
  91. item = canvas.create_text( x+tuning.Tuning.Screen.pick_best_value("supsubscript_x_shift",self._font_size),
  92. y+tuning.Tuning.Screen.pick_best_value("subscript_y_shift",self._font_size),
  93. tags=self.tags, text=chunk.text,
  94. font=(self._font_family, int( round( self._font_size*scale)), weight),
  95. anchor="nw", justify=self.justify, fill=self.fill)
  96. elif 'sup' in chunk.attrs:
  97. item = canvas.create_text( x+tuning.Tuning.Screen.pick_best_value("supsubscript_x_shift",self._font_size),
  98. y,
  99. tags=self.tags, text=chunk.text,
  100. font=(self._font_family, int( round( self._font_size*scale)), weight),
  101. anchor="sw", justify=self.justify, fill=self.fill)
  102. else:
  103. item = canvas.create_text( x, y, tags=self.tags, text=chunk.text,
  104. font=(self._font_family, int( round( self._font_size*scale)), weight),
  105. anchor="w",
  106. justify=self.justify,
  107. fill=self.fill)
  108. bbox = canvas.bbox( item)
  109. chunk.item = item
  110. chunk.dx = abs( bbox[0] - bbox[2])
  111. self._current_x = bbox[2]
  112. return bbox
  113. def get_chunks( self):
  114. text = self.sanitized_text()
  115. handler = FtextHandler()
  116. xml.sax.parseString( text, handler)
  117. chunks = []
  118. for ch in handler.chunks:
  119. parts = ch.text.split("\n")
  120. if len( parts) > 1:
  121. for i,part in enumerate( parts):
  122. if i < len( parts)-1:
  123. new_ch = text_chunk( text=part, attrs=ch.attrs, newline_after=True)
  124. else:
  125. new_ch = text_chunk( text=part, attrs=ch.attrs)
  126. chunks.append( new_ch)
  127. else:
  128. chunks.append( ch)
  129. return chunks
  130. def bbox( self, complete=False):
  131. """returns the bounding box of the object as a list of [x1,y1,x2,y2]"""
  132. xbbox = list( self.canvas.list_bbox( [i.item for i in self.items if complete or not i.ignore_x]))
  133. ybbox = list( self.canvas.list_bbox( [i.item for i in self.items if complete or not i.ignore_y]))
  134. bbox = [xbbox[0], ybbox[1], xbbox[2], ybbox[3]]
  135. ## for i in self.items:
  136. ## if i.ignore_y:
  137. ## x1, y1, x2, y2 = self.canvas.bbox( i.item)
  138. ## if not i.ignore_x:
  139. ## bbox[0] = min( (bbox[0], x1))
  140. ## bbox[2] = max( (bbox[2], x2))
  141. ## if y1 < bbox[1]:
  142. ## bbox[1] -= 2 # hack
  143. return bbox
  144. def move( self, dx, dy):
  145. for i in self.items:
  146. self.canvas.move( i.item, dx, dy)
  147. def move_to( self, x, y):
  148. dx = self.x - x - self.diff
  149. dy = self.y - y
  150. for i in self.items:
  151. self.canvas.move( i.item, dx, dy)
  152. def lift( self):
  153. for i in self.items:
  154. self.canvas.lift( i.item)
  155. def delete( self):
  156. for i in self.items:
  157. self.canvas.delete( i.item)
  158. def sanitized_text( self):
  159. return self.__class__.sanitize_text( self.text)
  160. @classmethod
  161. def sanitize_text( cls, text):
  162. if type( text) != unicode:
  163. text = text.decode('utf-8')
  164. text = unescape_html_entity_references( text).encode('utf-8')
  165. x = "<ftext>%s</ftext>" % text
  166. try:
  167. xml.sax.parseString( x, xml.sax.ContentHandler())
  168. except xml.sax.SAXParseException:
  169. text = xml.sax.saxutils.escape( text)
  170. x = "<ftext>%s</ftext>" % text
  171. return x
  172. class text_chunk:
  173. def __init__( self, text, attrs=None, newline_after=False):
  174. self.text = text
  175. self.attrs = attrs or set()
  176. self.item = None
  177. self.dx = 0
  178. self.ignore_y = False
  179. self.ignore_x = False
  180. self.newline_after = newline_after
  181. class FtextHandler ( xml.sax.ContentHandler):
  182. def __init__( self):
  183. xml.sax.ContentHandler.__init__( self)
  184. self._above = []
  185. self.chunks = []
  186. self._text = ""
  187. def startElement( self, name, attrs):
  188. self._closeCurrentText()
  189. self._above.append( name)
  190. def endElement( self, name):
  191. self._closeCurrentText()
  192. self._above.pop( -1)
  193. def _closeCurrentText( self):
  194. if self._text:
  195. self.chunks.append( text_chunk( self._text, attrs = set( self._above)))
  196. self._text = ""
  197. def characters( self, data):
  198. self._text += data
  199. from htmlentitydefs import name2codepoint
  200. import re
  201. def unescape_html_entity_references( text):
  202. return re.sub( "&([a-zA-Z]+);", _unescape_one_html_entity_reference, text)
  203. def _unescape_one_html_entity_reference( m):
  204. """we will use this function inside a regexp to replace entities"""
  205. hit = m.group(1)
  206. if hit not in ["amp","gt","lt"] and hit in name2codepoint:
  207. return unichr( name2codepoint[hit])
  208. else:
  209. return "&"+hit+";"