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

Language Python Lines 273
MD5 Hash aecc6d87dcc22446dca418b151450f18 Estimated Cost $4,437 (why?)
Repository git://pkgs.fedoraproject.org/bkchem View Raw File
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#--------------------------------------------------------------------------
#     This file is part of BKChem - a chemical drawing program
#     Copyright (C) 2002-2009 Beda Kosata <beda@zirael.org>

#     This program 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.

#     This program 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.

#     Complete text of GNU GPL can be found in the file gpl.txt in the
#     main directory of the program

#--------------------------------------------------------------------------


"""this module provides extended methods for formating of text items (for canvas)
ftext is XML based. Tags used for formating are:
sub - (sub)script, sup - (sup)erscript, b - bold, i - italic"""

import tkFont
import dom_extensions
import xml.sax
import copy

import tuning



class ftext:


  def __init__( self, canvas, xy, text, font=None, pos="center-first", fill='#000', big_charges=True, justify='right'):
    self.big_charges = big_charges  # should +- in <sup> be drawn in bigger font (not scaled down)?
    self.canvas = canvas
    self.items = []
    self.tags = ('ftext'),
    if xy:
      self.x, self.y = xy
    if text:
      self.text = text
    if font:
      self.font = font
    else:
      self.font = tkFont.Font( family="Helvetica", size=12)
    self._font_family = self.font.actual('family')
    self._font_size = int( self.font.actual('size'))
    self.pos = pos
    self.fill = fill
    self.justify = justify

    
  def draw( self):
    # split text to chunks
    chs = self.get_chunks()
    if not chs:
      return None

    self.items = []
    self._current_x = self.x
    self._current_y = self.y

    self.items = chs
    last_attrs = set()
    last_x = self._current_x
    
    for ch in self.items:
      scale = 1
      if set(('sub','sup')) & ch.attrs:
        if not (ch.text in "-" and self.big_charges):
          scale = 0.7
        else:
          scale = 1
        # we ignore subscripts and superscript in bbox calculation
        ch.ignore_y = True
        ch.ignore_x = True
        if set(('sub','sup')) & last_attrs:
          self._current_x = last_x
      last_x = self._current_x
      last_attrs = ch.attrs
      bbox = self._draw_chunk( ch, scale=scale)
      if ch.newline_after:
        self._current_y = bbox[3] + self.font.metrics()['linespace'] / 2.0
        self._current_x = self.x


    #does not work when 1. character is not regular
    if self.pos == 'center-first':
      self.diff = self.font.measure( self.items[0].text[0])/2.0
    elif self.pos == 'center-last':
      x1, y1, x2, y2 = self.bbox()
      self.diff = x2 -x1 -self.font.measure( self.items[0].text[-1])/2.0 -2
    self.move( -self.diff, 0)
    return self.bbox()





  def _draw_chunk( self, chunk, scale=1):
    weight = ''
    canvas = self.canvas
    x = self._current_x
    y = self._current_y

    if 'b' in chunk.attrs:
      weight = "bold"
    if 'i' in chunk.attrs:
      weight += " italic"
    if not weight:
      weight = "normal"

    if 'sub' in chunk.attrs:
      item = canvas.create_text( x+tuning.Tuning.Screen.pick_best_value("supsubscript_x_shift",self._font_size),
                                 y+tuning.Tuning.Screen.pick_best_value("subscript_y_shift",self._font_size),
                                 tags=self.tags, text=chunk.text,
                                 font=(self._font_family, int( round( self._font_size*scale)), weight),
                                 anchor="nw", justify=self.justify, fill=self.fill)
    elif 'sup' in chunk.attrs:
      item = canvas.create_text( x+tuning.Tuning.Screen.pick_best_value("supsubscript_x_shift",self._font_size),
                                 y,
                                 tags=self.tags, text=chunk.text,
                                 font=(self._font_family, int( round( self._font_size*scale)), weight),
                                 anchor="sw", justify=self.justify, fill=self.fill)
    else:
      item = canvas.create_text( x, y, tags=self.tags, text=chunk.text,
                                 font=(self._font_family, int( round( self._font_size*scale)), weight),
                                 anchor="w",
                                 justify=self.justify,
                                 fill=self.fill)
    bbox = canvas.bbox( item)
    chunk.item = item
    chunk.dx = abs( bbox[0] - bbox[2])
    self._current_x = bbox[2]
    return bbox



  def get_chunks( self):
    text = self.sanitized_text()
    handler = FtextHandler()
    xml.sax.parseString( text, handler)
    chunks = []
    for ch in handler.chunks:
      parts = ch.text.split("\n")
      if len( parts) > 1:
        for i,part in enumerate( parts):
          if i < len( parts)-1:
            new_ch = text_chunk( text=part, attrs=ch.attrs, newline_after=True)
          else:
            new_ch = text_chunk( text=part, attrs=ch.attrs)
          chunks.append( new_ch)
      else:
        chunks.append( ch)
    return chunks


    
  def bbox( self, complete=False):
    """returns the bounding box of the object as a list of [x1,y1,x2,y2]"""
    xbbox = list( self.canvas.list_bbox( [i.item for i in self.items if complete or not i.ignore_x]))
    ybbox = list( self.canvas.list_bbox( [i.item for i in self.items if complete or not i.ignore_y]))
    bbox = [xbbox[0], ybbox[1], xbbox[2], ybbox[3]]
##     for i in self.items:
##       if i.ignore_y:
##         x1, y1, x2, y2 = self.canvas.bbox( i.item)
##         if not i.ignore_x:
##           bbox[0] = min( (bbox[0], x1))
##           bbox[2] = max( (bbox[2], x2))
##         if y1 < bbox[1]:
##           bbox[1] -= 2 # hack
    return bbox
          


  def move( self, dx, dy):
    for i in self.items:
      self.canvas.move( i.item, dx, dy)

  def move_to( self, x, y):
    dx = self.x - x - self.diff
    dy = self.y - y
    for i in self.items:
      self.canvas.move( i.item, dx, dy)

  def lift( self):
    for i in self.items:
      self.canvas.lift( i.item)

  def delete( self):
    for i in self.items:
      self.canvas.delete( i.item)

      
  def sanitized_text( self):
    return self.__class__.sanitize_text( self.text)


  @classmethod
  def sanitize_text( cls, text):
    if type( text) != unicode:
      text = text.decode('utf-8')
    text = unescape_html_entity_references( text).encode('utf-8')
    x = "<ftext>%s</ftext>" % text
    try:
      xml.sax.parseString( x, xml.sax.ContentHandler())
    except xml.sax.SAXParseException:
      text = xml.sax.saxutils.escape( text)
      x = "<ftext>%s</ftext>" % text
    return x
    
    


class text_chunk:

  def __init__( self, text, attrs=None, newline_after=False):
    self.text = text
    self.attrs = attrs or set()
    self.item = None
    self.dx = 0
    self.ignore_y = False
    self.ignore_x = False
    self.newline_after = newline_after


class FtextHandler ( xml.sax.ContentHandler):

  def __init__( self):
    xml.sax.ContentHandler.__init__( self)
    self._above = []
    self.chunks = []
    self._text = ""

  def startElement( self, name, attrs):
    self._closeCurrentText()
    self._above.append( name)


  def endElement( self, name):
    self._closeCurrentText()
    self._above.pop( -1)


  def _closeCurrentText( self):
    if self._text:
      self.chunks.append( text_chunk( self._text, attrs = set( self._above)))
      self._text = ""


  def characters( self, data):
    self._text += data



from htmlentitydefs import name2codepoint
import re

def unescape_html_entity_references( text):
  return re.sub( "&([a-zA-Z]+);", _unescape_one_html_entity_reference, text)

def _unescape_one_html_entity_reference( m):
  """we will use this function inside a regexp to replace entities"""
  hit = m.group(1)
  if hit not in ["amp","gt","lt"] and hit in name2codepoint:
    return unichr( name2codepoint[hit])
  else:
    return "&"+hit+";"
Back to Top