root/gaphor/tags/gaphor-0.12.5/gaphor/diagram/style.py

Revision 2025, 7.3 kB (checked in by wrobe..@pld-linux.org, 1 year ago)

- fixed hint calculation in case of text align at orthogonal line
- implemented text bottom align at a line

Line 
1 """
2 Style classes and constants.
3 """
4
5 from math import pi
6
7 # padding
8 PADDING_TOP, PADDING_RIGHT, PADDING_BOTTOM, PADDING_LEFT = range(4)
9
10 # horizontal align
11 ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT = -1, 0, 1
12
13 # vertical align
14 ALIGN_TOP, ALIGN_MIDDLE, ALIGN_BOTTOM = -1, 0, 1
15
16 # hint tuples to move text depending on quadrant
17 WIDTH_HINT = (0, 0, -1)    # width hint tuple
18 R_WIDTH_HINT = (-1, -1, 0)    # width hint tuple
19 PADDING_HINT = (1, 1, -1)  # padding hint tuple
20
21 EPSILON = 1e-6
22
23 class Style(object):
24     """
25     Item style information. Style information is provided through object's
26     attributes, i.e.::
27
28         class InitialNodeItem
29             __style__ = {
30                 'name-align': ('center', 'top'),
31             }
32
33     is translated to::
34
35         >>> print style.name_align
36         ('center', 'top')
37     """
38
39     def __init__(self, *args, **kwargs):
40         super(Style, self).__init__()
41         for d in args:
42             self.update(d)
43         if kwargs:
44             self.update(kwargs)
45
46     def add(self, name, value):
47         """
48         Add style variable.
49
50         Variable name can contain hyphens, which is converted to
51         underscode, i.e. 'name-align' -> 'name_align'.
52
53         @param name:  style variable name
54         @param value: style variable value
55         """
56         name = name.replace('-', '_')
57         setattr(self, name, value)
58
59     def update(self, style):
60         for name, value in style.items():
61             self.add(name, value)
62
63     def items(self):
64         """
65         Return iterator of (name, value) style information items.
66         """
67         return self.__dict__.iteritems()
68
69
70 def get_min_size(width, height, padding):
71     """
72     Get minimal size of an object using padding information.
73
74     @param width:    object width
75     @param height:   object height
76     @param padding:  padding information as a tuple
77         (top, right, bottom, left)
78     """
79     width  += padding[PADDING_LEFT] + padding[PADDING_RIGHT]
80     height += padding[PADDING_TOP]  + padding[PADDING_BOTTOM]
81     return width, height
82
83
84 def get_text_point(extents, width, height, align, padding, outside):
85     """
86     Calculate position of the text relative to containing box defined by
87     tuple (0, 0, width, height).  Text is aligned using align and padding
88     information. It can be also placed outside the box if ``outside''
89     parameter is set to ``True''.
90
91     Parameters:
92      - extents: text extents like width, height, etc.
93      - width:   width of the containing box
94      - height:  height of the containing box
95      - align:   text align information (center, top, etc.)
96      - padding: text padding
97      - outside: should text be put outside containing box
98     """
99     #x_bear, y_bear, w, h, x_adv, y_adv = extents
100     w, h = extents
101
102     halign, valign = align
103
104     if outside:
105         if halign == ALIGN_LEFT:
106             x = -w - padding[PADDING_LEFT]
107         elif halign == ALIGN_CENTER:
108             x = (width - w) / 2
109         elif halign == ALIGN_RIGHT:
110             x = width + padding[PADDING_RIGHT]
111         else:
112             assert False
113
114         if valign == ALIGN_TOP:
115             y = -h -padding[PADDING_TOP]
116         elif valign == ALIGN_MIDDLE:
117             y = (height - h) / 2
118         elif valign == ALIGN_BOTTOM:
119             y = height + padding[PADDING_BOTTOM]
120         else:
121             assert False
122
123     else:
124         if halign == ALIGN_LEFT:
125             x = padding[PADDING_LEFT]
126         elif halign == ALIGN_CENTER:
127             x = (width - w) / 2 + padding[PADDING_LEFT] - padding[PADDING_RIGHT]
128         elif halign == ALIGN_RIGHT:
129             x = width - w - padding[PADDING_RIGHT]
130         else:
131             assert False
132
133         if valign == ALIGN_TOP:
134             y = padding[PADDING_TOP]
135         elif valign == ALIGN_MIDDLE:
136             y = (height - h) / 2
137         elif valign == ALIGN_BOTTOM:
138             y = height - h - padding[PADDING_BOTTOM]
139         else:
140             assert False
141     return x, y
142
143
144 def get_text_point_at_line(extents, p1, p2, align, padding):
145     """
146     Calculate position of the text relative to a line defined by points
147     (p1, p2). Text is aligned using align and padding information.
148
149     Parameters:
150      - extents: text extents like width, height, etc.
151      - p1:      beginning of line
152      - p2:      end of line
153      - align:   text align information (center, top, etc.)
154      - padding: text padding
155     """
156     name_dx = 0.0
157     name_dy = 0.0
158     ofs = 5
159
160     dx = float(p2[0]) - float(p1[0])
161     dy = float(p2[1]) - float(p1[1])
162    
163     name_w, name_h = extents
164
165     if dy == 0:
166         rc = 1000.0 # quite a lot...
167     else:
168         rc = dx / dy
169     abs_rc = abs(rc)
170     h = dx > 0 # right side of the box
171     v = dy > 0 # bottom side
172
173     if abs_rc > 6:
174         # horizontal line
175         if h:
176             name_dx = ofs
177             name_dy = -ofs - name_h
178         else:
179             name_dx = -ofs - name_w
180             name_dy = -ofs - name_h
181     elif 0 <= abs_rc <= 0.2:
182         # vertical line
183         if v:
184             name_dx = -ofs - name_w
185             name_dy = ofs
186         else:
187             name_dx = -ofs - name_w
188             name_dy = -ofs - name_h
189     else:
190         # Should both items be placed on the same side of the line?
191         r = abs_rc < 1.0
192
193         # Find out alignment of text (depends on the direction of the line)
194         align_left = (h and not r) or (r and not h)
195         align_bottom = (v and not r) or (r and not v)
196         if align_left:
197             name_dx = ofs
198         else:
199             name_dx = -ofs - name_w
200         if align_bottom:
201             name_dy = -ofs - name_h
202         else:
203             name_dy = ofs
204     return p1[0] + name_dx, p1[1] + name_dy
205
206
207
208 def get_text_point_at_line2(extents, p1, p2, align, padding):
209     """
210     Calculate position of the text relative to a line defined by points
211     (p1, p2). Text is aligned using align and padding information.
212
213     TODO: merge with get_text_point_at_line function
214
215     Parameters:
216      - extents: text extents like width, height, etc.
217      - p1:      beginning of line
218      - p2:      end of line
219      - align:   text align information (center, top, etc.)
220      - padding: text padding
221     """
222     x0 = (p1[0] + p2[0]) / 2.0
223     y0 = (p1[1] + p2[1]) / 2.0
224     dx = p2[0] - p1[0]
225     dy = p2[1] - p1[1]
226
227     if abs(dx) < EPSILON:
228         d1 = -1.0
229         d2 = 1.0
230     elif abs(dy) < EPSILON:
231         d1 = 0.0
232         d2 = 0.0
233     else:
234         d1 = dy / dx
235         d2 = abs(d1)
236
237     width, height = extents
238     halign, valign = align
239
240     # move to center and move by delta depending on line angle
241     if d2 < 0.5774: # <0, 30>, <150, 180>, <-180, -150>, <-30, 0>
242         # horizontal mode
243         w2 = width / 2.0
244         hint = w2 * d2
245
246         x = x0 - w2
247         if valign == ALIGN_TOP:
248             y = y0 - height - padding[PADDING_BOTTOM] - hint
249         else:
250             y = y0 + padding[PADDING_TOP] + hint
251     else:
252         # much better in case of vertical lines
253
254         # determine quadrant, we are interested in 1 or 3 and 2 or 4
255         # see hint tuples below
256         h2 = height / 2.0
257         q = cmp(d1, 0)
258         if abs(dx) < EPSILON:
259             hint = 0
260         else:
261             hint = h2 / d2
262
263         if valign == ALIGN_TOP:
264             x = x0 + PADDING_HINT[q] * (padding[PADDING_LEFT] + hint) + width * WIDTH_HINT[q]
265         else:
266             x = x0 - PADDING_HINT[q] * (padding[PADDING_RIGHT] + hint) + width * R_WIDTH_HINT[q]
267         y = y0 - h2
268
269     return x, y
270
271
272 # vim:sw=4:et
Note: See TracBrowser for help on using the browser.