2015-09-10 13:27:45 -05:00
|
|
|
// 6 september 2015
|
2015-10-09 11:33:45 -05:00
|
|
|
#import "uipriv_darwin.h"
|
2015-09-10 13:27:45 -05:00
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
struct uiDrawPath {
|
|
|
|
CGMutablePathRef path;
|
|
|
|
uiDrawFillMode fillMode;
|
|
|
|
BOOL ended;
|
2015-09-10 13:27:45 -05:00
|
|
|
};
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
uiDrawPath *p;
|
2015-09-10 13:27:45 -05:00
|
|
|
|
2015-10-09 11:33:45 -05:00
|
|
|
p = uiNew(uiDrawPath);
|
2015-10-08 11:19:48 -05:00
|
|
|
p->path = CGPathCreateMutable();
|
2015-10-08 10:49:24 -05:00
|
|
|
p->fillMode = mode;
|
|
|
|
return p;
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
void uiDrawFreePath(uiDrawPath *p)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
CGPathRelease((CGPathRef) (p->path));
|
2015-10-09 11:33:45 -05:00
|
|
|
uiFree(p);
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
if (p->ended)
|
|
|
|
complain("attempt to add figure to ended path in uiDrawPathNewFigure()");
|
|
|
|
CGPathMoveToPoint(p->path, NULL, x, y);
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
|
2015-10-11 22:48:40 -05:00
|
|
|
void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
|
2015-10-08 10:49:24 -05:00
|
|
|
{
|
2015-10-08 11:19:48 -05:00
|
|
|
double sinStart, cosStart;
|
|
|
|
double startx, starty;
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
if (p->ended)
|
|
|
|
complain("attempt to add figure to ended path in uiDrawPathNewFigureWithArc()");
|
2015-10-08 11:19:48 -05:00
|
|
|
sinStart = sin(startAngle);
|
|
|
|
cosStart = cos(startAngle);
|
|
|
|
startx = xCenter + radius * cosStart;
|
2015-10-11 19:24:06 -05:00
|
|
|
starty = yCenter + radius * sinStart;
|
2015-10-08 10:49:24 -05:00
|
|
|
CGPathMoveToPoint(p->path, NULL, startx, starty);
|
2015-10-11 22:48:40 -05:00
|
|
|
uiDrawPathArcTo(p, xCenter, yCenter, radius, startAngle, sweep, negative);
|
2015-10-08 10:49:24 -05:00
|
|
|
}
|
2015-09-10 13:27:45 -05:00
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
if (p->ended)
|
|
|
|
complain("attempt to add line to ended path in uiDrawPathLineTo()");
|
|
|
|
CGPathAddLineToPoint(p->path, NULL, x, y);
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
|
2015-10-11 22:48:40 -05:00
|
|
|
void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-11 22:48:40 -05:00
|
|
|
bool cw;
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
if (p->ended)
|
|
|
|
complain("attempt to add arc to ended path in uiDrawPathArcTo()");
|
2015-10-09 21:00:41 -05:00
|
|
|
if (sweep > 2 * M_PI)
|
|
|
|
sweep = 2 * M_PI;
|
2015-10-11 22:48:40 -05:00
|
|
|
cw = false;
|
|
|
|
if (negative)
|
|
|
|
cw = true;
|
|
|
|
CGPathAddArc(p->path, NULL,
|
2015-10-08 10:49:24 -05:00
|
|
|
xCenter, yCenter,
|
|
|
|
radius,
|
2015-10-11 22:48:40 -05:00
|
|
|
startAngle, startAngle + sweep,
|
|
|
|
cw);
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
if (p->ended)
|
|
|
|
complain("attempt to add bezier to ended path in uiDrawPathBezierTo()");
|
|
|
|
CGPathAddCurveToPoint(p->path, NULL,
|
|
|
|
c1x, c1y,
|
|
|
|
c2x, c2y,
|
|
|
|
endX, endY);
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
void uiDrawPathCloseFigure(uiDrawPath *p)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
if (p->ended)
|
|
|
|
complain("attempt to close figure of ended path in uiDrawPathCloseFigure()");
|
|
|
|
CGPathCloseSubpath(p->path);
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
if (p->ended)
|
|
|
|
complain("attempt to add rectangle to ended path in uiDrawPathAddRectangle()");
|
|
|
|
CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
void uiDrawPathEnd(uiDrawPath *p)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
p->ended = TRUE;
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
struct uiDrawContext {
|
|
|
|
CGContextRef c;
|
|
|
|
};
|
|
|
|
|
|
|
|
uiDrawContext *newContext(CGContextRef ctxt)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
uiDrawContext *c;
|
|
|
|
|
2015-10-09 11:33:45 -05:00
|
|
|
c = uiNew(uiDrawContext);
|
2015-10-08 10:49:24 -05:00
|
|
|
c->c = ctxt;
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
2015-10-09 11:33:45 -05:00
|
|
|
void freeContext(uiDrawContext *c)
|
|
|
|
{
|
|
|
|
uiFree(c);
|
|
|
|
}
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
// a stroke is identical to a fill of a stroked path
|
|
|
|
// we need to do this in order to stroke with a gradient; see http://stackoverflow.com/a/25034854/3408572
|
|
|
|
// doing this for other brushes works too
|
|
|
|
void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
|
|
|
|
{
|
|
|
|
CGLineCap cap;
|
|
|
|
CGLineJoin join;
|
2015-10-16 10:05:08 -05:00
|
|
|
CGPathRef dashPath;
|
|
|
|
CGFloat *dashes;
|
|
|
|
size_t i;
|
2015-10-08 10:49:24 -05:00
|
|
|
uiDrawPath p2;
|
|
|
|
|
|
|
|
if (!path->ended)
|
|
|
|
complain("path not ended in uiDrawStroke()");
|
|
|
|
|
2015-09-10 13:27:45 -05:00
|
|
|
switch (p->Cap) {
|
|
|
|
case uiDrawLineCapFlat:
|
2015-10-08 10:49:24 -05:00
|
|
|
cap = kCGLineCapButt;
|
2015-09-10 13:27:45 -05:00
|
|
|
break;
|
|
|
|
case uiDrawLineCapRound:
|
2015-10-08 10:49:24 -05:00
|
|
|
cap = kCGLineCapRound;
|
2015-09-10 13:27:45 -05:00
|
|
|
break;
|
|
|
|
case uiDrawLineCapSquare:
|
2015-10-08 10:49:24 -05:00
|
|
|
cap = kCGLineCapSquare;
|
2015-09-10 13:27:45 -05:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
switch (p->Join) {
|
|
|
|
case uiDrawLineJoinMiter:
|
2015-10-08 10:49:24 -05:00
|
|
|
join = kCGLineJoinMiter;
|
2015-09-10 13:27:45 -05:00
|
|
|
break;
|
|
|
|
case uiDrawLineJoinRound:
|
2015-10-08 10:49:24 -05:00
|
|
|
join = kCGLineJoinRound;
|
2015-09-10 13:27:45 -05:00
|
|
|
break;
|
|
|
|
case uiDrawLineJoinBevel:
|
2015-10-08 10:49:24 -05:00
|
|
|
join = kCGLineJoinBevel;
|
2015-09-10 13:27:45 -05:00
|
|
|
break;
|
|
|
|
}
|
2015-10-08 10:49:24 -05:00
|
|
|
|
|
|
|
// create a temporary path identical to the previous one
|
2015-10-16 10:05:08 -05:00
|
|
|
dashPath = (CGPathRef) path->path;
|
|
|
|
if (p->NumDashes != 0) {
|
|
|
|
dashes = (CGFloat *) uiAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]");
|
|
|
|
for (i = 0; i < p->NumDashes; i++)
|
|
|
|
dashes[i] = p->Dashes[i];
|
|
|
|
dashPath = CGPathCreateCopyByDashingPath(path->path,
|
|
|
|
NULL,
|
|
|
|
p->DashPhase,
|
|
|
|
dashes,
|
|
|
|
p->NumDashes);
|
|
|
|
uiFree(dashes);
|
|
|
|
}
|
|
|
|
// the documentation is wrong: this produces a path suitable for calling CGPathCreateCopyByStrokingPath(), not for filling directly
|
2015-10-08 10:49:24 -05:00
|
|
|
// the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway
|
2015-10-16 10:05:08 -05:00
|
|
|
p2.path = (CGMutablePathRef) CGPathCreateCopyByStrokingPath(dashPath,
|
2015-10-08 10:49:24 -05:00
|
|
|
NULL,
|
|
|
|
p->Thickness,
|
|
|
|
cap,
|
|
|
|
join,
|
|
|
|
p->MiterLimit);
|
2015-10-16 10:05:08 -05:00
|
|
|
if (p->NumDashes != 0)
|
|
|
|
CGPathRelease(dashPath);
|
|
|
|
|
2015-10-11 23:27:34 -05:00
|
|
|
// always draw stroke fills using the winding rule
|
|
|
|
// otherwise intersecting figures won't draw correctly
|
|
|
|
p2.fillMode = uiDrawFillModeWinding;
|
2015-10-08 10:49:24 -05:00
|
|
|
p2.ended = path->ended;
|
|
|
|
uiDrawFill(c, &p2, b);
|
|
|
|
// and clean up
|
|
|
|
CGPathRelease((CGPathRef) (p2.path));
|
|
|
|
}
|
|
|
|
|
|
|
|
// for a solid fill, we can merely have Core Graphics fill directly
|
|
|
|
static void fillSolid(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
|
|
|
|
{
|
|
|
|
CGContextSetRGBFillColor(ctxt, b->R, b->G, b->B, b->A);
|
|
|
|
switch (p->fillMode) {
|
|
|
|
case uiDrawFillModeWinding:
|
|
|
|
CGContextFillPath(ctxt);
|
|
|
|
break;
|
|
|
|
case uiDrawFillModeAlternate:
|
|
|
|
CGContextEOFillPath(ctxt);
|
|
|
|
break;
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
// for a gradient fill, we need to clip to the path and then draw the gradient
|
|
|
|
// see http://stackoverflow.com/a/25034854/3408572
|
|
|
|
static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
|
2015-09-10 13:27:45 -05:00
|
|
|
{
|
2015-10-08 10:49:24 -05:00
|
|
|
CGGradientRef gradient;
|
|
|
|
CGColorSpaceRef colorspace;
|
|
|
|
CGFloat *colors;
|
|
|
|
CGFloat *locations;
|
2015-10-08 11:19:48 -05:00
|
|
|
size_t i;
|
2015-10-08 10:49:24 -05:00
|
|
|
|
|
|
|
// gradients need a color space
|
|
|
|
// for consistency with windows, use sRGB
|
|
|
|
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
|
|
|
|
|
|
|
// make the gradient
|
2015-10-09 11:33:45 -05:00
|
|
|
colors = uiAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]");
|
|
|
|
locations = uiAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]");
|
2015-10-08 10:49:24 -05:00
|
|
|
for (i = 0; i < b->NumStops; i++) {
|
|
|
|
colors[i * 4 + 0] = b->Stops[i].R;
|
|
|
|
colors[i * 4 + 1] = b->Stops[i].G;
|
|
|
|
colors[i * 4 + 2] = b->Stops[i].B;
|
|
|
|
colors[i * 4 + 3] = b->Stops[i].A;
|
|
|
|
locations[i] = b->Stops[i].Pos;
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
2015-10-08 10:49:24 -05:00
|
|
|
gradient = CGGradientCreateWithColorComponents(colorspace, colors, locations, b->NumStops);
|
2015-10-09 11:33:45 -05:00
|
|
|
uiFree(locations);
|
|
|
|
uiFree(colors);
|
2015-10-08 10:49:24 -05:00
|
|
|
|
2015-10-08 11:19:48 -05:00
|
|
|
// because we're mucking with clipping, we need to save the graphics state and restore it later
|
|
|
|
CGContextSaveGState(ctxt);
|
|
|
|
|
2015-10-08 10:49:24 -05:00
|
|
|
// clip
|
|
|
|
switch (p->fillMode) {
|
2015-09-10 13:27:45 -05:00
|
|
|
case uiDrawFillModeWinding:
|
2015-10-08 10:49:24 -05:00
|
|
|
CGContextClip(ctxt);
|
2015-09-10 13:27:45 -05:00
|
|
|
break;
|
|
|
|
case uiDrawFillModeAlternate:
|
2015-10-08 10:49:24 -05:00
|
|
|
CGContextEOClip(ctxt);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw the gradient
|
|
|
|
switch (b->Type) {
|
|
|
|
case uiDrawBrushTypeLinearGradient:
|
|
|
|
CGContextDrawLinearGradient(ctxt,
|
|
|
|
gradient,
|
|
|
|
CGPointMake(b->X0, b->Y0),
|
|
|
|
CGPointMake(b->X1, b->Y1),
|
|
|
|
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
2015-09-10 13:27:45 -05:00
|
|
|
break;
|
2015-10-08 10:49:24 -05:00
|
|
|
case uiDrawBrushTypeRadialGradient:
|
|
|
|
CGContextDrawRadialGradient(ctxt,
|
|
|
|
gradient,
|
|
|
|
CGPointMake(b->X0, b->Y0),
|
|
|
|
// make the start circle radius 0 to make it a point
|
|
|
|
0,
|
|
|
|
CGPointMake(b->X1, b->Y1),
|
|
|
|
b->OuterRadius,
|
|
|
|
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// and clean up
|
2015-10-08 11:19:48 -05:00
|
|
|
CGContextRestoreGState(ctxt);
|
2015-10-08 10:49:24 -05:00
|
|
|
CGGradientRelease(gradient);
|
|
|
|
CGColorSpaceRelease(colorspace);
|
|
|
|
}
|
|
|
|
|
|
|
|
void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
|
|
|
|
{
|
|
|
|
if (!path->ended)
|
|
|
|
complain("path not ended in uiDrawFill()");
|
2015-10-08 11:19:48 -05:00
|
|
|
CGContextAddPath(c->c, (CGPathRef) (path->path));
|
2015-10-08 10:49:24 -05:00
|
|
|
switch (b->Type) {
|
|
|
|
case uiDrawBrushTypeSolid:
|
|
|
|
fillSolid(c->c, path, b);
|
|
|
|
return;
|
|
|
|
case uiDrawBrushTypeLinearGradient:
|
|
|
|
case uiDrawBrushTypeRadialGradient:
|
|
|
|
fillGradient(c->c, path, b);
|
|
|
|
return;
|
|
|
|
// case uiDrawBrushTypeImage:
|
|
|
|
// TODO
|
|
|
|
return;
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
2015-10-08 10:49:24 -05:00
|
|
|
complain("unknown brush type %d in uiDrawFill()", b->Type);
|
2015-09-10 13:27:45 -05:00
|
|
|
}
|
2015-10-11 12:53:47 -05:00
|
|
|
|
|
|
|
static void m2c(uiDrawMatrix *m, CGAffineTransform *c)
|
|
|
|
{
|
|
|
|
c->a = m->M11;
|
|
|
|
c->b = m->M12;
|
|
|
|
c->c = m->M21;
|
|
|
|
c->d = m->M22;
|
|
|
|
c->tx = m->M31;
|
|
|
|
c->ty = m->M32;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void c2m(CGAffineTransform *c, uiDrawMatrix *m)
|
|
|
|
{
|
|
|
|
m->M11 = c->a;
|
|
|
|
m->M12 = c->b;
|
|
|
|
m->M21 = c->c;
|
|
|
|
m->M22 = c->d;
|
|
|
|
m->M31 = c->tx;
|
|
|
|
m->M32 = c->ty;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO get rid of the separate setIdentity()
|
|
|
|
void uiDrawMatrixSetIdentity(uiDrawMatrix *m)
|
|
|
|
{
|
|
|
|
setIdentity(m);
|
|
|
|
}
|
|
|
|
|
|
|
|
void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
|
|
|
|
{
|
|
|
|
CGAffineTransform c;
|
|
|
|
|
|
|
|
m2c(m, &c);
|
|
|
|
c = CGAffineTransformTranslate(c, x, y);
|
|
|
|
c2m(&c, m);
|
|
|
|
}
|
|
|
|
|
2015-10-12 00:43:12 -05:00
|
|
|
void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
|
2015-10-11 12:53:47 -05:00
|
|
|
{
|
|
|
|
CGAffineTransform c;
|
2015-10-12 00:43:12 -05:00
|
|
|
double xt, yt;
|
2015-10-11 12:53:47 -05:00
|
|
|
|
|
|
|
m2c(m, &c);
|
2015-10-12 00:43:12 -05:00
|
|
|
// TODO explain why the translation must come first
|
|
|
|
xt = x;
|
|
|
|
yt = y;
|
|
|
|
scaleCenter(xCenter, yCenter, &xt, &yt);
|
|
|
|
c = CGAffineTransformTranslate(c, xt, yt);
|
2015-10-11 12:53:47 -05:00
|
|
|
c = CGAffineTransformScale(c, x, y);
|
2015-10-12 00:43:12 -05:00
|
|
|
// TODO undo the translation?
|
2015-10-11 12:53:47 -05:00
|
|
|
c2m(&c, m);
|
|
|
|
}
|
|
|
|
|
|
|
|
void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
|
|
|
|
{
|
|
|
|
CGAffineTransform c;
|
|
|
|
|
|
|
|
m2c(m, &c);
|
|
|
|
c = CGAffineTransformTranslate(c, x, y);
|
2015-10-12 00:18:08 -05:00
|
|
|
c = CGAffineTransformRotate(c, amount);
|
2015-10-11 12:53:47 -05:00
|
|
|
c = CGAffineTransformTranslate(c, -x, -y);
|
|
|
|
c2m(&c, m);
|
|
|
|
}
|
|
|
|
|
2015-10-12 06:58:07 -05:00
|
|
|
void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
|
2015-10-11 12:53:47 -05:00
|
|
|
{
|
2015-10-12 06:58:07 -05:00
|
|
|
fallbackSkew(m, x, y, xamount, yamount);
|
2015-10-11 12:53:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
|
|
|
|
{
|
|
|
|
CGAffineTransform c;
|
|
|
|
CGAffineTransform d;
|
|
|
|
|
|
|
|
m2c(dest, &c);
|
|
|
|
m2c(src, &d);
|
|
|
|
c = CGAffineTransformConcat(c, d);
|
|
|
|
c2m(&c, dest);
|
|
|
|
}
|
|
|
|
|
|
|
|
// there is no test for invertibility; CGAffineTransformInvert() is merely documented as returning the matrix unchanged if it isn't invertible
|
|
|
|
// therefore, special care must be taken to catch matrices who are their own inverses
|
|
|
|
// TODO figure out which matrices these are and do so
|
|
|
|
int uiDrawMatrixInvertible(uiDrawMatrix *m)
|
|
|
|
{
|
|
|
|
CGAffineTransform c, d;
|
|
|
|
|
|
|
|
m2c(m, &c);
|
|
|
|
d = CGAffineTransformInvert(c);
|
|
|
|
return CGAffineTransformEqualToTransform(c, d) == false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int uiDrawMatrixInvert(uiDrawMatrix *m)
|
|
|
|
{
|
|
|
|
CGAffineTransform c, d;
|
|
|
|
|
|
|
|
m2c(m, &c);
|
|
|
|
d = CGAffineTransformInvert(c);
|
|
|
|
if (CGAffineTransformEqualToTransform(c, d))
|
|
|
|
return 0;
|
|
|
|
c2m(&d, m);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
|
|
|
|
{
|
|
|
|
CGAffineTransform c;
|
|
|
|
CGPoint p;
|
|
|
|
|
|
|
|
m2c(m, &c);
|
|
|
|
p = CGPointApplyAffineTransform(CGPointMake(*x, *y), c);
|
|
|
|
*x = p.x;
|
|
|
|
*y = p.y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
|
|
|
|
{
|
|
|
|
CGAffineTransform c;
|
|
|
|
CGSize s;
|
|
|
|
|
|
|
|
m2c(m, &c);
|
|
|
|
s = CGSizeApplyAffineTransform(CGSizeMake(*x, *y), c);
|
|
|
|
*x = s.width;
|
|
|
|
*y = s.height;
|
|
|
|
}
|
|
|
|
|
2015-10-12 20:11:42 -05:00
|
|
|
void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
|
|
|
|
{
|
|
|
|
CGAffineTransform cm;
|
|
|
|
|
|
|
|
m2c(m, &cm);
|
|
|
|
CGContextConcatCTM(c->c, cm);
|
|
|
|
}
|
|
|
|
|
2015-10-13 12:53:24 -05:00
|
|
|
void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
|
|
|
|
{
|
|
|
|
if (!path->ended)
|
|
|
|
complain("path not ended in uiDrawClip()");
|
|
|
|
CGContextAddPath(c->c, (CGPathRef) (path->path));
|
|
|
|
switch (path->fillMode) {
|
|
|
|
case uiDrawFillModeWinding:
|
|
|
|
CGContextClip(c->c);
|
|
|
|
break;
|
|
|
|
case uiDrawFillModeAlternate:
|
|
|
|
CGContextEOClip(c->c);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-11 12:53:47 -05:00
|
|
|
// TODO figure out what besides transforms these save/restore on all platforms
|
|
|
|
void uiDrawSave(uiDrawContext *c)
|
|
|
|
{
|
|
|
|
CGContextSaveGState(c->c);
|
|
|
|
}
|
|
|
|
|
|
|
|
void uiDrawRestore(uiDrawContext *c)
|
|
|
|
{
|
|
|
|
CGContextRestoreGState(c->c);
|
|
|
|
}
|
2015-12-22 20:48:26 -06:00
|
|
|
|
|
|
|
// TODO for all relevant routines, make sure we are freeing memory correctly
|
|
|
|
// TODO make sure allocation failures throw exceptions?
|
|
|
|
struct uiDrawFontFamilies {
|
|
|
|
CFArrayRef fonts;
|
|
|
|
};
|
|
|
|
|
|
|
|
uiDrawFontFamilies *uiDrawListFontFamilies(void)
|
|
|
|
{
|
|
|
|
uiDrawFontFamilies *ff;
|
|
|
|
|
|
|
|
ff = uiNew(uiDrawFontFamilies);
|
2015-12-22 23:58:06 -06:00
|
|
|
// TODO is there a way to get an error reason?
|
|
|
|
ff->fonts = CTFontManagerCopyAvailableFontFamilyNames();
|
|
|
|
if (ff->fonts == NULL)
|
|
|
|
complain("error getting available font names (no reason specified)");
|
2015-12-22 20:48:26 -06:00
|
|
|
return ff;
|
|
|
|
}
|
|
|
|
|
|
|
|
uintmax_t uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
|
|
|
|
{
|
2015-12-22 23:58:06 -06:00
|
|
|
return CFArrayGetCount(ff->fonts);
|
2015-12-22 20:48:26 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, uintmax_t n)
|
|
|
|
{
|
|
|
|
CFStringRef familystr;
|
|
|
|
char *family;
|
|
|
|
|
2015-12-22 23:58:06 -06:00
|
|
|
familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n);
|
2015-12-22 20:48:26 -06:00
|
|
|
// TODO create a uiDarwinCFStringToText()?
|
|
|
|
family = uiDarwinNSStringToText((NSString *) familystr);
|
2015-12-22 23:58:06 -06:00
|
|
|
// Get Rule means we do not free familystr
|
2015-12-22 20:48:26 -06:00
|
|
|
return family;
|
|
|
|
}
|
|
|
|
|
|
|
|
void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
|
|
|
|
{
|
|
|
|
CFRelease(ff->fonts);
|
|
|
|
uiFree(ff);
|
|
|
|
}
|
2015-12-27 10:32:47 -06:00
|
|
|
|
|
|
|
double uiDrawTextSizeToPoints(double textSize)
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
double uiDrawPointsToTextSize(double points)
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-01-07 20:51:37 -06:00
|
|
|
struct uiDrawTextLayout {
|
|
|
|
CFMutableAttributedStringRef mas;
|
|
|
|
intmax_t *bytesToCharacters;
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO this is *really* iffy, but we need to know character offsets...
|
|
|
|
static intmax_t *strToCFStrOffsetList(const char *str, CFMutableStringRef *cfstr)
|
|
|
|
{
|
|
|
|
intmax_t *bytesToCharacters;
|
|
|
|
intmax_t i, len;
|
|
|
|
|
|
|
|
len = strlen(str);
|
|
|
|
bytesToCharacters = (intmax_t *) uiAlloc(len * sizeof (intmax_t), "intmax_t[]");
|
|
|
|
|
|
|
|
*cfstr = CFStringCreateMutable(NULL, 0);
|
|
|
|
if (*cfstr == NULL)
|
|
|
|
complain("error creating CFMutableStringRef for storing string in uiDrawNewTextLayout()");
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (i < len) {
|
|
|
|
CFStringRef substr;
|
|
|
|
intmax_t n;
|
|
|
|
intmax_t j;
|
|
|
|
intmax_t pos;
|
|
|
|
|
|
|
|
// figure out how many characters to convert and convert them
|
|
|
|
for (n = 1; (i + n - 1) < len; n++) {
|
|
|
|
substr = CFStringCreateWithBytes(NULL, str + i, n, kCFStringEncodingUTF8, false);
|
|
|
|
if (substr != NULL) // found a full character
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// if this test passes we either:
|
|
|
|
// - reached the end of the string without a successful conversion (invalid string)
|
|
|
|
// - ran into allocation issues
|
|
|
|
if (substr == NULL)
|
|
|
|
complain("something bad happened when trying to prepare string in uiDrawNewTextLayout()");
|
|
|
|
|
|
|
|
// now save the character offsets for those bytes
|
|
|
|
pos = CFStringGetLength(*cfstr);
|
|
|
|
for (j = 0; j < n; j++)
|
|
|
|
bytesToCharacters[j] = pos;
|
|
|
|
|
|
|
|
// and add the characters that we converted
|
|
|
|
CFStringAppend(*cfstr, substr);
|
|
|
|
CFRelease(substr); // TODO correct?
|
|
|
|
|
|
|
|
// and go to the next
|
|
|
|
i += n;
|
|
|
|
}
|
|
|
|
|
|
|
|
return bytesToCharacters;
|
|
|
|
}
|
|
|
|
|
|
|
|
static CFMutableDictionaryRef newAttrList(void)
|
|
|
|
{
|
|
|
|
CFMutableDictionaryRef attr;
|
|
|
|
|
|
|
|
attr = CFDictionaryCreateMutable(NULL, 0, kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks);
|
|
|
|
if (attr == NULL)
|
|
|
|
complain("error creating attribute dictionary in newAttrList()()");
|
|
|
|
return attr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family)
|
|
|
|
{
|
|
|
|
CFStringRef cfstr;
|
|
|
|
|
|
|
|
cfstr = CFStringCreateWithCString(NULL, initialStyle->Family, kCFStringEncodingUTF8);
|
|
|
|
if (cfstr == NULL)
|
|
|
|
complain("error creating font family name CFStringRef in addFontFamilyAttr()");
|
|
|
|
CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr);
|
|
|
|
CFRelease(cfstr); // dictionary holds its own reference
|
|
|
|
}
|
|
|
|
|
|
|
|
struct traits {
|
|
|
|
uiDrawTextWeight weight;
|
|
|
|
uiDrawTextItalic italic;
|
|
|
|
uiDrawTextStretch stretch;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void addFontTraitsAttr(CFMutableDictionaryRef attr, struct traits *traits)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
uiDrawTextLayout *uiDrawNewTextLayout(const char *str, const uiDrawInitialTextStyle *initialStyle)
|
|
|
|
{
|
|
|
|
uiDrawTextLayout *layout;
|
|
|
|
CFMutableStringRef cfstr;
|
|
|
|
CFMutableDictionaryRef attr;
|
|
|
|
struct traits t;
|
|
|
|
|
|
|
|
layout = uiNew(uiDrawTextLayout);
|
|
|
|
|
|
|
|
layout->bytesToCharacters = strToCFStrOffsetList(str, &cfstr);
|
|
|
|
|
|
|
|
attr = newAttrList();
|
|
|
|
addFontFamilyAttr(attr, initialStyle->Family);
|
|
|
|
|
|
|
|
void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
|
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
uiFree(layout->bytesToCharacters);
|
|
|
|
uiFree(layout);
|
|
|
|
}
|
|
|
|
|
|
|
|
void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
|
2015-12-27 10:32:47 -06:00
|
|
|
{
|
|
|
|
// TODO
|
|
|
|
}
|