Skip to content

Commit 02a62e6

Browse files
committed
moved some expensive loops to pathmatics
- generation of beziers from TextFrames and conversion of ns->cgpaths now happen in obj-c
1 parent 04ab02e commit 02a62e6

6 files changed

Lines changed: 128 additions & 43 deletions

File tree

app/deps/pathmatics/Vandercook.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// Vandercook.h
3+
// PlotDevice
4+
//
5+
// Created by Christian Swinehart on 10/24/14.
6+
//
7+
//
8+
9+
#import <AppKit/AppKit.h>
10+
11+
@interface Vandercook : NSObject
12+
+ (CGPathRef)cgPath:(NSBezierPath *)nsPath;
13+
+ (NSBezierPath *)traceGlyphs:(NSRange)glyph_range atOffset:(NSPoint)offset withLayout:(NSLayoutManager *)layout;
14+
@end

app/deps/pathmatics/Vandercook.m

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//
2+
// Vandercook.m
3+
// PlotDevice
4+
//
5+
// Created by Christian Swinehart on 10/24/14.
6+
//
7+
//
8+
9+
#import "Vandercook.h"
10+
11+
@implementation Vandercook
12+
13+
+ (NSBezierPath *)traceGlyphs:(NSRange)glyph_range atOffset:(NSPoint)offset withLayout:(NSLayoutManager *)layout{
14+
NSBezierPath *path = [NSBezierPath bezierPath];
15+
NSTextStorage *store = layout.textStorage;
16+
NSUInteger start = glyph_range.location;
17+
NSUInteger end = start + glyph_range.length;
18+
19+
for (NSUInteger glyph_idx=start; glyph_idx<end; glyph_idx++){
20+
// don't draw tabs, newlines, etc.
21+
if([layout notShownAttributeForGlyphAtIndex:glyph_idx]) continue;
22+
23+
// shift the glyph's location by the line- and frame-offsets
24+
NSRect line_rect = [layout lineFragmentRectForGlyphAtIndex:glyph_idx effectiveRange:nil];
25+
NSPoint glyph_pt = [layout locationForGlyphAtIndex:glyph_idx];
26+
glyph_pt.x += line_rect.origin.x + offset.x;
27+
glyph_pt.y += line_rect.origin.y + offset.y;
28+
glyph_pt.y *= -1;
29+
[path moveToPoint:glyph_pt];
30+
31+
// add the glyph to the path
32+
NSUInteger txt_idx = [layout characterIndexForGlyphAtIndex:glyph_idx];
33+
NSFont *font = [store attribute:@"NSFont" atIndex:txt_idx effectiveRange:nil];
34+
NSGlyph glyph = [layout glyphAtIndex:glyph_idx];
35+
[path appendBezierPathWithGlyph:glyph inFont:font];
36+
[path closePath];
37+
}
38+
39+
return path;
40+
41+
}
42+
43+
+ (CGPathRef)cgPath:(NSBezierPath *)nsPath{
44+
NSInteger i, numElements;
45+
46+
// Need to begin a path here.
47+
CGPathRef immutablePath = NULL;
48+
49+
// Then draw the path elements.
50+
numElements = [nsPath elementCount];
51+
if (numElements > 0){
52+
CGMutablePathRef path = CGPathCreateMutable();
53+
NSPoint points[3];
54+
BOOL didClosePath = YES;
55+
56+
for (i = 0; i < numElements; i++){
57+
switch ([nsPath elementAtIndex:i associatedPoints:points]){
58+
case NSMoveToBezierPathElement:
59+
CGPathMoveToPoint(path, NULL, points[0].x, points[0].y);
60+
break;
61+
62+
case NSLineToBezierPathElement:
63+
CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y);
64+
didClosePath = NO;
65+
break;
66+
67+
case NSCurveToBezierPathElement:
68+
CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y,
69+
points[1].x, points[1].y,
70+
points[2].x, points[2].y);
71+
didClosePath = NO;
72+
break;
73+
74+
case NSClosePathBezierPathElement:
75+
CGPathCloseSubpath(path);
76+
didClosePath = YES;
77+
break;
78+
}
79+
}
80+
81+
// Be sure the path is closed or Quartz may not do valid hit detection.
82+
if (!didClosePath)
83+
CGPathCloseSubpath(path);
84+
85+
immutablePath = CGPathCreateCopy(path);
86+
CGPathRelease(path);
87+
}
88+
89+
return immutablePath;
90+
}
91+
92+
@end

app/deps/pathmatics/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from setuptools.extension import Extension
33

44
cPathmatics = Extension("cPathmatics",
5-
sources = ["pathmatics.m", "gpc.c",],
5+
sources = ["pathmatics.m", "gpc.c", "Vandercook.m"],
66
extra_link_args=['-framework', 'AppKit', '-framework', 'Foundation'],
77
extra_compile_args=['-Qunused-arguments'])
88

plotdevice/gfx/bezier.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -382,21 +382,7 @@ def _screen_transform(self):
382382

383383
@property
384384
def cgPath(self):
385-
# this really ought to live in pathmatics...
386-
ns = self._nsBezierPath
387-
cg = CGPathCreateMutable()
388-
for cmd, points in (ns.elementAtIndex_associatedPoints_(i) for i in xrange(ns.elementCount())):
389-
if cmd==NSMoveToBezierPathElement:
390-
CGPathMoveToPoint(cg, None, points[0].x, points[0].y)
391-
elif cmd==NSLineToBezierPathElement:
392-
CGPathAddLineToPoint(cg, None, points[0].x, points[0].y)
393-
elif cmd==NSCurveToBezierPathElement:
394-
CGPathAddCurveToPoint(cg, None, points[0].x, points[0].y,
395-
points[1].x, points[1].y,
396-
points[2].x, points[2].y)
397-
elif cmd==NSClosePathBezierPathElement:
398-
CGPathCloseSubpath(cg)
399-
return CGPathCreateCopy(cg)
385+
return pathmatics.convert_path(self._nsBezierPath)
400386

401387
def _draw(self):
402388
with _cg_context() as port:

plotdevice/gfx/typography.py

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .bezier import Bezier
1414
from ..util.foundry import *
1515
from ..util import _copy_attrs, numlike, XMLParser, read
16+
from ..lib import pathmatics
1617

1718
_ctx = None
1819
__all__ = ("Text", "Family", "Font", "Stylesheet",
@@ -439,31 +440,7 @@ def _draw(self):
439440

440441
@property
441442
def _nsBezierPath(self):
442-
dx, dy = self.offset
443-
start, length = self._glyphs
444-
path = NSBezierPath.bezierPath()
445-
txt = self.store.string()
446-
for glyph_idx in range(start, start+length):
447-
if self.layout.notShownAttributeForGlyphAtIndex_(glyph_idx):
448-
continue # don't draw tabs, newlines, etc.
449-
450-
txt_idx = self.layout.characterIndexForGlyphAtIndex_(glyph_idx)
451-
ns_font, _ = self.store.attribute_atIndex_effectiveRange_("NSFont", txt_idx, None)
452-
line_rect, _ = self.layout.lineFragmentRectForGlyphAtIndex_effectiveRange_(glyph_idx, None)
453-
454-
# convert glyph location from container coords to canvas coords
455-
layout_pt = self.layout.locationForGlyphAtIndex_(glyph_idx)
456-
final_pt = list(line_rect[0])
457-
final_pt[0] += layout_pt[0] + dx
458-
final_pt[1] += layout_pt[1] + dy
459-
g = self.layout.glyphAtIndex_(glyph_idx)
460-
if g==0: continue # when does glyphAtIndex return nil in practice?
461-
462-
if ns_font:
463-
path.moveToPoint_((final_pt[0], -final_pt[1]))
464-
path.appendBezierPathWithGlyph_inFont_(g, ns_font)
465-
path.closePath()
466-
return path
443+
return pathmatics.trace_text(frame=self)
467444

468445
class Stylesheet(object):
469446
kwargs = StyleMixin.opts

plotdevice/lib/pathmatics.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
1-
__all__ = ('linepoint', 'linelength', 'curvepoint', 'curvelength', 'segment_lengths',
2-
'length', 'point', 'points', 'contours', 'findpath', 'insert_point')
1+
import objc
2+
from .cocoa import CGPathRelease
33
from cPathmatics import intersects, union, intersect, difference, xor
44

5+
__all__ = ('linepoint', 'linelength', 'curvepoint', 'curvelength', 'segment_lengths',
6+
'length', 'point', 'points', 'contours', 'findpath', 'insert_point',
7+
'convert_path', 'trace_text')
8+
9+
Vandercook = objc.lookUpClass('Vandercook')
10+
def convert_path(ns_path):
11+
"""Creates a CGPath from the points in an NSBezierPath"""
12+
pth = Vandercook.cgPath_(ns_path)
13+
CGPathRelease(pth)
14+
return pth
15+
16+
def trace_text(frame):
17+
"""Returns an NSBezierPath with the glyphs contained by a TextFrame object"""
18+
return Vandercook.traceGlyphs_atOffset_withLayout_(frame._glyphs, frame.offset, frame.layout)
19+
520
try:
621
from cPathmatics import linepoint, linelength, curvepoint, curvelength
722
except ImportError:
@@ -574,3 +589,4 @@ def insert_point(path, t):
574589
if path[j].cmd == CLOSE:
575590
new_path.closepath()
576591
return new_path
592+

0 commit comments

Comments
 (0)