2 * Copyright (c) 2007 Ivan Leben
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library in the file COPYING;
16 * if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <vg/openvg.h>
22 #include "shContext.h"
23 #include "shGeometry.h"
26 static int shAddVertex(SHPath *p, SHVertex *v, SHint *contourStart)
28 /* Assert contour was open */
29 SH_ASSERT((*contourStart) >= 0);
31 /* Check vertex limit */
32 if (p->vertices.size >= SH_MAX_VERTICES) return 0;
34 /* Add vertex to subdivision */
35 shVertexArrayPushBackP(&p->vertices, v);
37 /* Increment contour size. Its stored in
38 the flags of first contour vertex */
39 p->vertices.items[*contourStart].flags++;
44 static void shSubrecurseQuad(SHPath *p, SHQuad *quad, SHint *contourStart)
47 SHVector2 mid, dif, c1, c2, c3;
48 SHQuad quads[SH_MAX_RECURSE_DEPTH];
49 SHQuad *q, *qleft, *qright;
57 /* Calculate distance of control point from its
58 counterpart on the line between end points */
59 SET2V(mid, q->p1); ADD2V(mid, q->p3); DIV2(mid, 2);
60 SET2V(dif, q->p2); SUB2V(dif, mid); ABS2(dif);
62 /* Cancel if the curve is flat enough */
63 if (dif.x + dif.y <= 1.0f || qindex == SH_MAX_RECURSE_DEPTH-1) {
65 /* Add subdivision point */
66 v.point = q->p3; v.flags = 0;
67 if (qindex == 0) return; /* Skip last point */
68 if (!shAddVertex(p, &v, contourStart)) return;
73 /* Left recursion goes on top of stack! */
74 qright = q; qleft = &quads[++qindex];
76 /* Subdivide into 2 sub-curves */
77 SET2V(c1, q->p1); ADD2V(c1, q->p2); DIV2(c1, 2);
78 SET2V(c3, q->p2); ADD2V(c3, q->p3); DIV2(c3, 2);
79 SET2V(c2, c1); ADD2V(c2, c3); DIV2(c2, 2);
81 /* Add left recursion onto stack */
86 /* Add right recursion onto stack */
94 static void shSubrecurseCubic(SHPath *p, SHCubic *cubic, SHint *contourStart)
97 SHfloat dx1, dy1, dx2, dy2;
98 SHVector2 mm, c1, c2, c3, c4, c5;
99 SHCubic cubics[SH_MAX_RECURSE_DEPTH];
100 SHCubic *c, *cleft, *cright;
104 while (cindex >= 0) {
108 /* Calculate distance of control points from their
109 counterparts on the line between end points */
110 dx1 = 3.0f*c->p2.x - 2.0f*c->p1.x - c->p4.x; dx1 *= dx1;
111 dy1 = 3.0f*c->p2.y - 2.0f*c->p1.y - c->p4.y; dy1 *= dy1;
112 dx2 = 3.0f*c->p3.x - 2.0f*c->p4.x - c->p1.x; dx2 *= dx2;
113 dy2 = 3.0f*c->p3.y - 2.0f*c->p4.y - c->p1.y; dy2 *= dy2;
114 if (dx1 < dx2) dx1 = dx2;
115 if (dy1 < dy2) dy1 = dy2;
117 /* Cancel if the curve is flat enough */
118 if (dx1+dy1 <= 1.0 || cindex == SH_MAX_RECURSE_DEPTH-1) {
120 /* Add subdivision point */
121 v.point = c->p4; v.flags = 0;
122 if (cindex == 0) return; /* Skip last point */
123 if (!shAddVertex(p, &v, contourStart)) return;
128 /* Left recursion goes on top of stack! */
129 cright = c; cleft = &cubics[++cindex];
131 /* Subdivide into 2 sub-curves */
132 SET2V(c1, c->p1); ADD2V(c1, c->p2); DIV2(c1, 2);
133 SET2V(mm, c->p2); ADD2V(mm, c->p3); DIV2(mm, 2);
134 SET2V(c5, c->p3); ADD2V(c5, c->p4); DIV2(c5, 2);
136 SET2V(c2, c1); ADD2V(c2, mm); DIV2(c2, 2);
137 SET2V(c4, mm); ADD2V(c4, c5); DIV2(c4, 2);
139 SET2V(c3, c2); ADD2V(c3, c4); DIV2(c3, 2);
141 /* Add left recursion to stack */
147 /* Add right recursion to stack */
156 static void shSubrecurseArc(SHPath *p, SHArc *arc,
157 SHVector2 *c,SHVector2 *ux, SHVector2 *uy,
161 SHfloat am, cosa, sina, dx, dy;
162 SHVector2 uux, uuy, c1, m;
163 SHArc arcs[SH_MAX_RECURSE_DEPTH];
164 SHArc *a, *aleft, *aright;
168 while (aindex >= 0) {
172 /* Middle angle and its cos/sin */
173 am = (a->a1 + a->a2)/2;
178 SET2V(uux, (*ux)); MUL2(uux, cosa);
179 SET2V(uuy, (*uy)); MUL2(uuy, sina);
180 SET2V(c1, (*c)); ADD2V(c1, uux); ADD2V(c1, uuy);
182 /* Check distance from linear midpoint */
183 SET2V(m, a->p1); ADD2V(m, a->p2); DIV2(m, 2);
184 dx = c1.x - m.x; dy = c1.y - m.y;
185 if (dx < 0.0f) dx = -dx;
186 if (dy < 0.0f) dy = -dy;
188 /* Stop if flat enough */
189 if (dx+dy <= 1.0f || aindex == SH_MAX_RECURSE_DEPTH-1) {
191 /* Add middle subdivision point */
192 v.point = c1; v.flags = 0;
193 if (!shAddVertex(p, &v, contourStart)) return;
194 if (aindex == 0) return; /* Skip very last point */
196 /* Add end subdivision point */
197 v.point = a->p2; v.flags = 0;
198 if (!shAddVertex(p, &v, contourStart)) return;
203 /* Left subdivision goes on top of stack! */
204 aright = a; aleft = &arcs[++aindex];
206 /* Add left recursion to stack */
212 /* Add right recursion to stack */
221 static void shSubdivideSegment(SHPath *p, VGPathSegment segment,
222 VGPathCommand originalCommand,
223 SHfloat *data, void *userData)
226 SHint *contourStart = ((SHint**)userData)[0];
227 SHint *surfaceSpace = ((SHint**)userData)[1];
228 SHQuad quad; SHCubic cubic; SHArc arc;
230 VG_GETCONTEXT(VG_NO_RETVAL);
236 /* Set contour start here */
237 (*contourStart) = p->vertices.size;
239 /* First contour vertex */
244 TRANSFORM2(v.point, context->pathTransform);
249 /* Last contour vertex */
252 v.flags = SH_VERTEX_FLAG_SEGEND | SH_VERTEX_FLAG_CLOSE;
254 TRANSFORM2(v.point, context->pathTransform);
259 /* Last segment vertex */
262 v.flags = SH_VERTEX_FLAG_SEGEND;
264 TRANSFORM2(v.point, context->pathTransform);
269 /* Recurse subdivision */
270 SET2(quad.p1, data[0], data[1]);
271 SET2(quad.p2, data[2], data[3]);
272 SET2(quad.p3, data[4], data[5]);
274 TRANSFORM2(quad.p1, context->pathTransform);
275 TRANSFORM2(quad.p2, context->pathTransform);
276 TRANSFORM2(quad.p3, context->pathTransform); }
277 shSubrecurseQuad(p, &quad, contourStart);
279 /* Last segment vertex */
282 v.flags = SH_VERTEX_FLAG_SEGEND;
284 TRANSFORM2(v.point, context->pathTransform);
289 /* Recurse subdivision */
290 SET2(cubic.p1, data[0], data[1]);
291 SET2(cubic.p2, data[2], data[3]);
292 SET2(cubic.p3, data[4], data[5]);
293 SET2(cubic.p4, data[6], data[7]);
295 TRANSFORM2(cubic.p1, context->pathTransform);
296 TRANSFORM2(cubic.p2, context->pathTransform);
297 TRANSFORM2(cubic.p3, context->pathTransform);
298 TRANSFORM2(cubic.p4, context->pathTransform); }
299 shSubrecurseCubic(p, &cubic, contourStart);
301 /* Last segment vertex */
304 v.flags = SH_VERTEX_FLAG_SEGEND;
306 TRANSFORM2(v.point, context->pathTransform);
311 SH_ASSERT(segment==VG_SCWARC_TO || segment==VG_SCCWARC_TO ||
312 segment==VG_LCWARC_TO || segment==VG_LCCWARC_TO);
314 /* Recurse subdivision */
315 SET2(arc.p1, data[0], data[1]);
316 SET2(arc.p2, data[10], data[11]);
317 arc.a1 = data[8]; arc.a2 = data[9];
318 SET2(c, data[2], data[3]);
319 SET2(ux, data[4], data[5]);
320 SET2(uy, data[6], data[7]);
322 TRANSFORM2(arc.p1, context->pathTransform);
323 TRANSFORM2(arc.p2, context->pathTransform);
324 TRANSFORM2(c, context->pathTransform);
325 TRANSFORM2DIR(ux, context->pathTransform);
326 TRANSFORM2DIR(uy, context->pathTransform); }
327 shSubrecurseArc(p, &arc, &c, &ux, &uy, contourStart);
329 /* Last segment vertex */
330 v.point.x = data[10];
331 v.point.y = data[11];
332 v.flags = SH_VERTEX_FLAG_SEGEND;
334 TRANSFORM2(v.point, context->pathTransform); }
338 /* Add subdivision vertex */
339 shAddVertex(p, &v, contourStart);
342 /*--------------------------------------------------
343 * Processes path data by simplfying it and sending
344 * each segment to subdivision callback function
345 *--------------------------------------------------*/
347 void shFlattenPath(SHPath *p, SHint surfaceSpace)
349 SHint contourStart = -1;
350 // SHint surfSpace = surfaceSpace;
353 SH_PROCESS_SIMPLIFY_LINES |
354 SH_PROCESS_SIMPLIFY_CURVES |
355 SH_PROCESS_CENTRALIZE_ARCS |
356 SH_PROCESS_REPAIR_ENDS;
358 userData[0] = &contourStart;
359 userData[1] = &surfaceSpace;
361 shVertexArrayClear(&p->vertices);
362 shProcessPathData(p, processFlags, shSubdivideSegment, userData);
365 /*-------------------------------------------
366 * Adds a rectangle to the path's stroke.
367 *-------------------------------------------*/
369 static void shPushStrokeQuad(SHPath *p, SHVector2 *p1, SHVector2 *p2,
370 SHVector2 *p3, SHVector2 *p4)
372 shVector2ArrayPushBackP(&p->stroke, p1);
373 shVector2ArrayPushBackP(&p->stroke, p2);
374 shVector2ArrayPushBackP(&p->stroke, p3);
375 shVector2ArrayPushBackP(&p->stroke, p3);
376 shVector2ArrayPushBackP(&p->stroke, p4);
377 shVector2ArrayPushBackP(&p->stroke, p1);
380 /*-------------------------------------------
381 * Adds a triangle to the path's stroke.
382 *-------------------------------------------*/
384 static void shPushStrokeTri(SHPath *p, SHVector2 *p1,
385 SHVector2 *p2, SHVector2 *p3)
387 shVector2ArrayPushBackP(&p->stroke, p1);
388 shVector2ArrayPushBackP(&p->stroke, p2);
389 shVector2ArrayPushBackP(&p->stroke, p3);
392 /*-----------------------------------------------------------
393 * Adds a miter join to the path's stroke at the given
394 * turn point [c], with the end of the previous segment
395 * outset [o1] and the beginning of the next segment
396 * outset [o2], transiting from tangent [d1] to [d2].
397 *-----------------------------------------------------------*/
399 static void shStrokeJoinMiter(SHPath *p, SHVector2 *c,
400 SHVector2 *o1, SHVector2 *d1,
401 SHVector2 *o2, SHVector2 *d2)
403 /* Init miter top to first point in case lines are colinear */
404 SHVector2 x; SET2V(x,(*o1));
406 /* Find intersection of two outer turn edges
407 (lines defined by origin and direction) */
408 shLineLineXsection(o1, d1, o2, d2, &x);
410 /* Add a "diamond" quad with top on intersected point
411 and bottom on center of turn (on the line) */
412 shPushStrokeQuad(p, &x, o1, c, o2);
415 /*-----------------------------------------------------------
416 * Adds a round join to the path's stroke at the given
417 * turn point [c], with the end of the previous segment
418 * outset [pstart] and the beginning of the next segment
419 * outset [pend], transiting from perpendicular vector
420 * [tstart] to [tend].
421 *-----------------------------------------------------------*/
423 static void shStrokeJoinRound(SHPath *p, SHVector2 *c,
424 SHVector2 *pstart, SHVector2 *tstart,
425 SHVector2 *pend, SHVector2 *tend)
428 SHfloat a, ang, cosa, sina;
430 /* Find angle between lines */
431 ang = ANGLE2((*tstart),(*tend));
433 /* Begin with start point */
435 for (a=0.0f; a<ang; a+=PI/12) {
437 /* Rotate perpendicular vector around and
438 find next offset point from center */
441 SET2(p2, tstart->x*cosa - tstart->y*sina,
442 tstart->x*sina + tstart->y*cosa);
445 /* Add triangle, save previous */
446 shPushStrokeTri(p, &p1, &p2, c);
450 /* Add last triangle */
451 shPushStrokeTri(p, &p1, pend, c);
454 static void shStrokeCapRound(SHPath *p, SHVector2 *c, SHVector2 *t, SHint start)
457 SHfloat ang, cosa, sina;
462 /* Revert perpendicular vector if start cap */
464 if (start) MUL2(tt, -1);
466 /* Find start point */
470 for (a = 1; a<=steps; ++a) {
472 /* Rotate perpendicular vector around and
473 find next offset point from center */
474 ang = (SHfloat)a * PI / steps;
477 SET2(p2, tt.x*cosa - tt.y*sina,
478 tt.x*sina + tt.y*cosa);
481 /* Add triangle, save previous */
482 shPushStrokeTri(p, &p1, &p2, c);
487 static void shStrokeCapSquare(SHPath *p, SHVector2 *c, SHVector2 *t, SHint start)
489 SHVector2 tt, p1, p2, p3, p4;
491 /* Revert perpendicular vector if start cap */
493 if (start) MUL2(tt, -1);
495 /* Find four corners of the quad */
500 ADD2(p2, tt.y, -tt.x);
503 ADD2(p3, -2*tt.x, -2*tt.y);
506 ADD2(p4, -tt.y, tt.x);
508 shPushStrokeQuad(p, &p1, &p2, &p3, &p4);
511 /*-----------------------------------------------------------
512 * Generates stroke of a path according to VGContext state.
513 * Produces quads for every linear subdivision segment or
514 * dash "on" segment, handles line caps and joins.
515 *-----------------------------------------------------------*/
517 void shStrokePath(VGContext* c, SHPath *p)
519 /* Line width and vertex count */
520 SHfloat w = c->strokeLineWidth / 2;
521 SHfloat mlimit = c->strokeMiterLimit;
522 SHint vertsize = p->vertices.size;
525 SHint contourStart = 0;
526 SHint contourLength = 0;
533 /* Current vertices */
537 SHVector2 d, t, dprev, tprev;
538 SHfloat norm, cross, mlength;
540 /* Stroke edge points */
541 SHVector2 l1, r1, l2, r2, lprev, rprev;
545 SHfloat dashLength = 0.0f, strokeLength = 0.0f;
546 SHint dashSize = c->strokeDashPattern.size;
547 SHfloat *dashPattern = c->strokeDashPattern.items;
550 /* Dash edge points */
551 SHVector2 dash1, dash2;
552 SHVector2 dashL1, dashR1;
553 SHVector2 dashL2, dashR2;
554 SHfloat nextDashLength, dashOffset;
556 /* Discard odd dash segment */
557 dashSize -= dashSize % 2;
559 /* Init previous so compiler doesn't warn
560 for uninitialized usage */
561 SET2(tprev, 0,0); SET2(dprev, 0,0);
562 SET2(lprev, 0,0); SET2(rprev, 0,0);
565 /* Walk over subdivision vertices */
566 for (i1=0; i1<vertsize; ++i1) {
569 /* Start new contour if exists */
570 if (contourStart < vertsize)
575 start = end = loop = close = segend = 0;
578 if (i1 == contourStart) {
579 /* Contour has started. Get length */
580 contourLength = p->vertices.items[i1].flags;
584 if (contourLength <= 1) {
585 /* Discard empty contours. */
586 contourStart = i1 + 1;
591 v1 = &p->vertices.items[i1];
592 v2 = &p->vertices.items[i2];
594 if (i2 == contourStart + contourLength-1) {
595 /* Contour has ended. Check close */
596 close = v2->flags & SH_VERTEX_FLAG_CLOSE;
600 if (i1 == contourStart + contourLength-1) {
601 /* Loop back to first edge. Check close */
602 close = v1->flags & SH_VERTEX_FLAG_CLOSE;
604 contourStart = i1 + 1;
609 if (!start && !loop) {
610 /* We are inside a contour. Check segment end. */
611 segend = (v1->flags & SH_VERTEX_FLAG_SEGEND);
614 if (dashSize > 0 && start &&
615 (contourStart == 0 || c->strokeDashPhaseReset)) {
617 /* Reset pattern phase at contour start */
618 dashLength = -c->strokeDashPhase;
623 if (dashLength < 0.0f) {
624 /* Consume dash segments forward to reach stroke start */
625 while (dashLength + dashPattern[dashIndex] <= 0.0f) {
626 dashLength += dashPattern[dashIndex];
627 dashIndex = (dashIndex + 1) % dashSize;
630 }else if (dashLength > 0.0f) {
631 /* Consume dash segments backward to return to stroke start */
632 dashIndex = dashSize;
633 while (dashLength > 0.0f) {
634 dashIndex = dashIndex ? (dashIndex-1) : (dashSize-1);
635 dashLength -= dashPattern[dashIndex];
640 /* Subdiv segment vertices and points */
641 v1 = &p->vertices.items[i1];
642 v2 = &p->vertices.items[i2];
646 /* Direction vector */
647 SET2(d, p2->x-p1->x, p2->y-p1->y);
649 if (norm == 0.0f) d = dprev;
652 /* Perpendicular vector */
655 cross = CROSS2(t,tprev);
657 /* Left and right edge points */
658 SET2V(l1, (*p1)); ADD2V(l1, t);
659 SET2V(r1, (*p1)); SUB2V(r1, t);
660 SET2V(l2, (*p2)); ADD2V(l2, t);
661 SET2V(r2, (*p2)); SUB2V(r2, t);
663 /* Check if join needed */
664 if ((segend || (loop && close)) && dashOn) {
666 switch (c->strokeJoinStyle) {
669 /* Add a round join to stroke */
671 shStrokeJoinRound(p, p1, &lprev, &tprev, &l1, &t);
673 SHVector2 _t, _tprev;
674 SET2(_t, -t.x, -t.y);
675 SET2(_tprev, -tprev.x, -tprev.y);
676 shStrokeJoinRound(p, p1, &r1, &_t, &rprev, &_tprev);
682 /* Add a miter join to stroke */
683 mlength = 1/SH_COS((ANGLE2(t, tprev))/2);
684 if (mlength <= mlimit) {
686 shStrokeJoinMiter(p, p1, &lprev, &dprev, &l1, &d);
687 else if (cross < 0.0f)
688 shStrokeJoinMiter(p, p1, &rprev, &dprev, &r1, &d);
690 }/* Else fall down to bevel */
694 /* Add a bevel join to stroke */
696 shPushStrokeTri(p, &l1, &lprev, p1);
697 else if (cross < 0.0f)
698 shPushStrokeTri(p, &r1, &rprev, p1);
702 }else if (!start && !loop && dashOn) {
704 /* Fill gap with previous (= bevel join) */
706 shPushStrokeTri(p, &l1, &lprev, p1);
707 else if (cross < 0.0f)
708 shPushStrokeTri(p, &r1, &rprev, p1);
712 /* Apply cap to start of a non-closed contour or
713 if we are dashing and dash segment is on */
714 if ((dashSize == 0 && loop && !close) ||
715 (dashSize > 0 && start && dashOn)) {
716 switch (c->strokeCapStyle) {
718 shStrokeCapRound(p, p1, &t, 1); break;
720 shStrokeCapSquare(p, p1, &t, 1); break;
731 /* Start with beginning of subdiv segment */
732 SET2V(dash1, (*p1)); SET2V(dashL1, l1); SET2V(dashR1, r1);
735 /* Interpolate point on the current subdiv segment */
736 nextDashLength = dashLength + dashPattern[dashIndex];
737 dashOffset = (nextDashLength - strokeLength) / norm;
738 if (dashOffset > 1.0f) dashOffset = 1;
739 SET2V(dash2, (*p2)); SUB2V(dash2, (*p1));
740 MUL2(dash2, dashOffset); ADD2V(dash2, (*p1));
742 /* Left and right edge points */
743 SET2V(dashL2, dash2); ADD2V(dashL2, t);
744 SET2V(dashR2, dash2); SUB2V(dashR2, t);
746 /* Add quad for this dash segment */
747 if (dashOn) shPushStrokeQuad(p, &dashL2, &dashL1, &dashR1, &dashR2);
749 /* Move to next dash segment if inside this subdiv segment */
750 if (nextDashLength <= strokeLength + norm) {
751 dashIndex = (dashIndex + 1) % dashSize;
752 dashLength = nextDashLength;
755 SET2V(dashL1, dashL2);
756 SET2V(dashR1, dashR2);
758 /* Apply cap to dash segment */
759 switch (c->strokeCapStyle) {
761 shStrokeCapRound(p, &dash1, &t, dashOn); break;
763 shStrokeCapSquare(p, &dash1, &t, dashOn); break;
768 /* Consume dash segments until subdiv end met */
769 } while (nextDashLength < strokeLength + norm);
773 /* Add quad for this line segment */
774 shPushStrokeQuad(p, &l2, &l1, &r1, &r2);
778 /* Apply cap to end of a non-closed contour or
779 if we are dashing and dash segment is on */
780 if ((dashSize == 0 && end && !close) ||
781 (dashSize > 0 && end && dashOn)) {
782 switch (c->strokeCapStyle) {
784 shStrokeCapRound(p, p2, &t, 0); break;
786 shStrokeCapSquare(p, p2, &t, 0); break;
791 /* Save previous edge */
792 strokeLength += norm;
801 /*-------------------------------------------------------------
802 * Transforms the tessellation vertices using the given matrix
803 *-------------------------------------------------------------*/
805 void shTransformVertices(SHMatrix3x3 *m, SHPath *p)
810 for (i = p->vertices.size-1; i>=0; --i) {
811 v = (&p->vertices.items[i].point);
812 TRANSFORM2((*v), (*m)); }
815 /*--------------------------------------------------------
816 * Finds the tight bounding box of path's tesselation
817 * vertices. Depends on whether the path had been
818 * tesselated in user or surface space.
819 *--------------------------------------------------------*/
821 void shFindBoundbox(SHPath *p)
825 if (p->vertices.size == 0) {
831 p->min.x = p->max.x = p->vertices.items[0].point.x;
832 p->min.y = p->max.y = p->vertices.items[0].point.y;
834 for (i=0; i<p->vertices.size; ++i) {
836 SHVector2 *v = &p->vertices.items[i].point;
837 if (v->x < p->min.x) p->min.x = v->x;
838 if (v->x > p->max.x) p->max.x = v->x;
839 if (v->y < p->min.y) p->min.y = v->y;
840 if (v->y > p->max.y) p->max.y = v->y;
844 /*--------------------------------------------------------
845 * Outputs a tight bounding box of a path in path's own
847 *--------------------------------------------------------*/
849 VG_API_CALL void vgPathBounds(VGPath path,
850 VGfloat * minX, VGfloat * minY,
851 VGfloat * width, VGfloat * height)
854 VG_GETCONTEXT(VG_NO_RETVAL);
856 VG_RETURN_ERR_IF(!shIsValidPath(context, path),
857 VG_BAD_HANDLE_ERROR, VG_NO_RETVAL);
859 VG_RETURN_ERR_IF(minX == NULL || minY == NULL ||
860 width == NULL || height == NULL,
861 VG_ILLEGAL_ARGUMENT_ERROR, VG_NO_RETVAL);
863 /* TODO: check output pointer alignment */
866 VG_RETURN_ERR_IF(!(p->caps & VG_PATH_CAPABILITY_PATH_BOUNDS),
867 VG_PATH_CAPABILITY_ERROR, VG_NO_RETVAL);
869 /* Update path geometry */
876 *width = p->max.x - p->min.x;
877 *height = p->max.y - p->min.y;
879 VG_RETURN(VG_NO_RETVAL);
882 /*------------------------------------------------------------
883 * Outputs a bounding box of a path defined by its control
884 * points that is guaranteed to enclose the path geometry
885 * after applying the current path-user-to-surface transform
886 *------------------------------------------------------------*/
888 VG_API_CALL void vgPathTransformedBounds(VGPath path,
889 VGfloat * minX, VGfloat * minY,
890 VGfloat * width, VGfloat * height)
893 VG_GETCONTEXT(VG_NO_RETVAL);
895 VG_RETURN_ERR_IF(!shIsValidPath(context, path),
896 VG_BAD_HANDLE_ERROR, VG_NO_RETVAL);
898 VG_RETURN_ERR_IF(minX == NULL || minY == NULL ||
899 width == NULL || height == NULL,
900 VG_ILLEGAL_ARGUMENT_ERROR, VG_NO_RETVAL);
902 /* TODO: check output pointer alignment */
905 VG_RETURN_ERR_IF(!(p->caps & VG_PATH_CAPABILITY_PATH_BOUNDS),
906 VG_PATH_CAPABILITY_ERROR, VG_NO_RETVAL);
908 /* Update path geometry */
915 *width = p->max.x - p->min.x;
916 *height = p->max.y - p->min.y;
918 /* Invalidate subdivision for rendering */
919 p->cacheDataValid = VG_FALSE;
921 VG_RETURN(VG_NO_RETVAL);
924 VG_API_CALL VGfloat vgPathLength(VGPath path,
925 VGint startSegment, VGint numSegments)
930 VG_API_CALL void vgPointAlongPath(VGPath path,
931 VGint startSegment, VGint numSegments,
933 VGfloat * x, VGfloat * y,
934 VGfloat * tangentX, VGfloat * tangentY)