Skip to content
This repository was archived by the owner on Jan 19, 2023. It is now read-only.

Commit dc13894

Browse files
author
Frederik De Bleser
committed
Added line cap style and line join style attributes.
Includes support as state variables, arguments to the constructor, getters and setters in the BezierPath class and shortcut properties (for join style: MITER, ROUND and BEVEL; for cap style: BUTT, ROUND and SQUARE). Closes issue #1.
1 parent 25c96fe commit dc13894

File tree

3 files changed

+100
-15
lines changed

3 files changed

+100
-15
lines changed

nodebox/graphics/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ def _resetContext(self):
4141
self._fillcolor = self.Color()
4242
self._strokecolor = None
4343
self._strokewidth = 1.0
44+
self._capstyle = BUTT
45+
self._joinstyle = MITER
4446
self.canvas.background = self.Color(1.0)
4547
self._path = None
4648
self._autoclosepath = True
@@ -409,6 +411,20 @@ def strokewidth(self, width=None):
409411
if width is not None:
410412
self._strokewidth = max(width, 0.0001)
411413
return self._strokewidth
414+
415+
def capstyle(self, style=None):
416+
if style is not None:
417+
if style not in (BUTT, ROUND, SQUARE):
418+
raise NodeBoxError, 'Line cap style should be BUTT, ROUND or SQUARE.'
419+
self._capstyle = style
420+
return self._capstyle
421+
422+
def joinstyle(self, style=None):
423+
if style is not None:
424+
if style not in (MITER, ROUND, BEVEL):
425+
raise NodeBoxError, 'Line join style should be MITER, ROUND or BEVEL.'
426+
self._joinstyle = style
427+
return self._joinstyle
412428

413429
### Font Commands ###
414430

nodebox/graphics/cocoa.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"RGB", "HSB", "CMYK",
1414
"CENTER", "CORNER",
1515
"MOVETO", "LINETO", "CURVETO", "CLOSE",
16+
"MITER", "ROUND", "BEVEL", "BUTT", "SQUARE",
1617
"LEFT", "RIGHT", "CENTER", "JUSTIFY",
1718
"NORMAL","FORTYFIVE",
1819
"NUMBER", "TEXT", "BOOLEAN","BUTTON",
@@ -39,6 +40,12 @@
3940
CURVETO = NSCurveToBezierPathElement
4041
CLOSE = NSClosePathBezierPathElement
4142

43+
MITER = NSMiterLineJoinStyle
44+
ROUND = NSRoundLineJoinStyle # Also used for NSRoundLineCapStyle, same value.
45+
BEVEL = NSBevelLineJoinStyle
46+
BUTT = NSButtLineCapStyle
47+
SQUARE = NSSquareLineCapStyle
48+
4249
LEFT = NSLeftTextAlignment
4350
RIGHT = NSRightTextAlignment
4451
CENTER = NSCenterTextAlignment
@@ -66,6 +73,8 @@
6673
'_fillcolor': 'fill',
6774
'_strokecolor': 'stroke',
6875
'_strokewidth': 'strokewidth',
76+
'_capstyle': 'capstyle',
77+
'_joinstyle': 'joinstyle',
6978
'_transform': 'transform',
7079
'_transformmode': 'transformmode',
7180
'_fontname': 'font',
@@ -210,13 +219,15 @@ def _set_strokewidth(self, strokewidth):
210219
class BezierPath(Grob, TransformMixin, ColorMixin):
211220
"""A BezierPath provides a wrapper around NSBezierPath."""
212221

213-
stateAttributes = ('_fillcolor', '_strokecolor', '_strokewidth', '_transform', '_transformmode')
214-
kwargs = ('fill', 'stroke', 'strokewidth')
222+
stateAttributes = ('_fillcolor', '_strokecolor', '_strokewidth', '_capstyle', '_joinstyle', '_transform', '_transformmode')
223+
kwargs = ('fill', 'stroke', 'strokewidth', 'capstyle', 'joinstyle')
215224

216225
def __init__(self, ctx, path=None, **kwargs):
217226
super(BezierPath, self).__init__(ctx)
218227
TransformMixin.__init__(self)
219228
ColorMixin.__init__(self, **kwargs)
229+
self.capstyle = kwargs.get('capstyle', BUTT)
230+
self.joinstyle = kwargs.get('joinstyle', MITER)
220231
self._segment_cache = None
221232
if path is None:
222233
self._nsBezierPath = NSBezierPath.bezierPath()
@@ -238,6 +249,24 @@ def _get_path(self):
238249

239250
def copy(self):
240251
return self.__class__(self._ctx, self)
252+
253+
### Cap and Join style ###
254+
255+
def _get_capstyle(self):
256+
return self._capstyle
257+
def _set_capstyle(self, style):
258+
if style not in (BUTT, ROUND, SQUARE):
259+
raise NodeBoxError, 'Line cap style should be BUTT, ROUND or SQUARE.'
260+
self._capstyle = style
261+
capstyle = property(_get_capstyle, _set_capstyle)
262+
263+
def _get_joinstyle(self):
264+
return self._joinstyle
265+
def _set_joinstyle(self, style):
266+
if style not in (MITER, ROUND, BEVEL):
267+
raise NodeBoxError, 'Line join style should be MITER, ROUND or BEVEL.'
268+
self._joinstyle = style
269+
joinstyle = property(_get_joinstyle, _set_joinstyle)
241270

242271
### Path methods ###
243272

@@ -256,7 +285,7 @@ def curveto(self, x1, y1, x2, y2, x3, y3):
256285
def closepath(self):
257286
self._segment_cache = None
258287
self._nsBezierPath.closePath()
259-
288+
260289
def setlinewidth(self, width):
261290
self.linewidth = width
262291

@@ -359,6 +388,8 @@ def _draw(self):
359388
if (self._strokecolor):
360389
self._strokecolor.set()
361390
self._nsBezierPath.setLineWidth_(self._strokewidth)
391+
self._nsBezierPath.setLineCapStyle_(self._capstyle)
392+
self._nsBezierPath.setLineJoinStyle_(self._joinstyle)
362393
self._nsBezierPath.stroke()
363394
_restore()
364395

tests/graphics.py

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,69 @@
1111

1212
class GraphicsTestCase(unittest.TestCase):
1313

14+
def setUp(self):
15+
self.ctx = Context()
16+
1417
def test_arrow_type_error(self):
1518
"""Test if passing a wrong arrow type raises an error."""
16-
ctx = Context()
17-
self.assertRaises(NodeBoxError, ctx.arrow, 0, 0, 100, type=42)
19+
self.assertRaises(NodeBoxError, self.ctx.arrow, 0, 0, 100, type=42)
1820

1921
def test_too_many_pops(self):
2022
"""Test if popping too many times raises an error."""
21-
ctx = Context()
22-
self.assertRaises(NodeBoxError, ctx.pop)
23+
self.assertRaises(NodeBoxError, self.ctx.pop)
2324

2425
def test_font_not_found(self):
2526
"""Test if setting an unexisting font raises an error."""
26-
ctx = Context()
27-
old_font = ctx.font()
28-
self.assertRaises(NodeBoxError, ctx.font, "THIS_FONT_DOES_NOT_EXIST")
29-
self.assertEquals(ctx.font(), old_font, "Current font has not changed.")
27+
old_font = self.ctx.font()
28+
self.assertRaises(NodeBoxError, self.ctx.font, "THIS_FONT_DOES_NOT_EXIST")
29+
self.assertEquals(self.ctx.font(), old_font, "Current font has not changed.")
3030

3131
def test_ellipse(self):
3232
"""Test if ellipse is an alias for oval."""
33-
ctx = Context()
34-
self.assertTrue(hasattr(ctx, "ellipse"))
35-
self.assertTrue(ctx.ellipse == ctx.oval)
36-
p = BezierPath(ctx)
33+
self.assertTrue(hasattr(self.ctx, "ellipse"))
34+
self.assertTrue(self.ctx.ellipse == self.ctx.oval)
35+
p = BezierPath(self.ctx)
3736
self.assertTrue(hasattr(p, "ellipse"))
3837
self.assertTrue(p.ellipse == p.oval)
38+
39+
class BezierPathTestCase(unittest.TestCase):
40+
41+
def setUp(self):
42+
self.ctx = Context()
43+
44+
def test_capstyle_context(self):
45+
self.ctx.capstyle(SQUARE)
46+
p = BezierPath(self.ctx)
47+
self.assertEquals(p.capstyle, BUTT, "Default line cap style is butt.")
48+
p.inheritFromContext()
49+
self.assertEquals(p.capstyle, SQUARE)
50+
51+
def test_capstyle_constructor(self):
52+
p = BezierPath(self.ctx, capstyle=ROUND)
53+
self.assertEquals(p.capstyle, ROUND)
54+
55+
def test_capstyle_validation(self):
56+
self.assertRaises(NodeBoxError, self.ctx.capstyle, 42)
57+
self.assertRaises(NodeBoxError, BezierPath, self.ctx, capstyle=42)
58+
p = BezierPath(self.ctx)
59+
self.assertRaises(NodeBoxError, p._set_capstyle, 42)
60+
61+
def test_joinstyle_context(self):
62+
self.ctx.joinstyle(ROUND)
63+
p = BezierPath(self.ctx)
64+
self.assertEquals(p.joinstyle, MITER, "Default line join style is miter.")
65+
p.inheritFromContext()
66+
self.assertEquals(p.joinstyle, ROUND)
67+
68+
def test_joinstyle_constructor(self):
69+
p = BezierPath(self.ctx, joinstyle=ROUND)
70+
self.assertEquals(p.joinstyle, ROUND)
71+
72+
def test_joinstyle_validation(self):
73+
self.assertRaises(NodeBoxError, self.ctx.joinstyle, 42)
74+
self.assertRaises(NodeBoxError, BezierPath, self.ctx, joinstyle=42)
75+
p = BezierPath(self.ctx)
76+
self.assertRaises(NodeBoxError, p._set_joinstyle, 42)
3977

4078
if __name__=='__main__':
4179
unittest.main()

0 commit comments

Comments
 (0)