/* * Draw.c -- Implementation of common drawing routines. * * Authors : Patrick Lecoanet. * Creation date : Sat Dec 10 12:51:30 1994 * * $Id$ */ /* * Copyright (c) 1993 - 1999 CENA, Patrick Lecoanet -- * * This code is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this code; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* ********************************************************************************** * * The algorihms used to draw the arrows, to do the 3d effects and to * smooth the polygons are adapted from Tk. * ********************************************************************************** */ #include "config.h" #include "Types.h" #include "Draw.h" #include "Geo.h" #include "List.h" #include "WidgetInfo.h" #include "Image.h" #include #include #define POLYGON_RELIEF_DRAW 0 #define POLYGON_RELIEF_RENDER 1 #define POLYGON_RELIEF_DIST 2 #define POLYGON_RELIEF_BBOX 3 #define POLYGON_RELIEF_IN_BBOX 4 #define TOP_CONTRAST 13 #define BOTTOM_CONTRAST 6 #define MAX_INTENSITY 65535 #define ARROW_SHAPE_B 10.0 #define ARROW_SHAPE_C 5.0 #define OPEN_ARROW_SHAPE_A 4.0 #define CLOSED_ARROW_SHAPE_A ARROW_SHAPE_B #define LIGHTNING_SHAPE_A_RATIO 10.0 #define LIGHTNING_SHAPE_B_RATIO 8.0 /* ********************************************************************************** * * ZnSetLineStyle -- * ********************************************************************************** */ void ZnSetLineStyle(WidgetInfo *wi, LineStyle line_style) { if (wi->render) { #ifdef GLX switch (line_style) { case LINE_DASHED : glLineStipple(1, 0xF0F0); glEnable(GL_LINE_STIPPLE); break; case LINE_MIXED : glLineStipple(1, 0x27FF); glEnable(GL_LINE_STIPPLE); break; case LINE_DOTTED : glLineStipple(1, 0x18C3); glEnable(GL_LINE_STIPPLE); break; default: glDisable(GL_LINE_STIPPLE); } #endif } else { XGCValues values; static const char dashed[] = { 8 }; static const char dotted[] = { 2, 5 }; static const char mixed[] = { 8, 5, 2, 5 }; values.line_style = LineOnOffDash; switch (line_style) { case LINE_DASHED : XSetDashes(wi->dpy, wi->gc, 0, dashed, 1); break; case LINE_MIXED : XSetDashes(wi->dpy, wi->gc, 0, mixed, 4); break; case LINE_DOTTED : XSetDashes(wi->dpy, wi->gc, 0, dotted, 2); break; default: values.line_style = LineSolid; break; } XChangeGC(wi->dpy, wi->gc, GCLineStyle, &values); } } /* ********************************************************************************** * * ZnGetLineShape -- * Compute the points describing the given line shape between point p1 and p2. * If bbox is non null, it is filled with the bounding box of the shape. * * For the time being this procedure handles straight lines, right and left * lightnings, right and left corners, right and left double corners.. * * * Here are the parameters for lightnings: * * ******* * ******* * * ****** * * ****** ******+ * * ****** ****** * *| * ****** ****** * * | LIGHTNING_SHAPE_A * ****** ****** * * | * ****** * * | * ..******.........................+.+.*........................******.. * | * * ****** * | * * ****** ****** * | * * ****** ****** * | * * ****** ****** * | * ******* ****** * | * ****** * | * ****** * | ******** * | | | | * | |----| | LIGHTNING_SHAPE_B * | | * |--------------------------------| LENGTH / 2 * ********************************************************************************** */ void ZnGetLineShape(ZnPoint *p1, ZnPoint *p2, unsigned int line_width, LineShape shape, ZnBBox *bbox, ZnList to_points) { ZnPoint *points; int num_points, i; /* * Compute all line points according to shape. */ if ((shape == LINE_LEFT_LIGHTNING) || (shape == LINE_RIGHT_LIGHTNING)) { double alpha, theta; double length, length2; double shape_a, shape_b; double dx, dy; double temp; num_points = LIGHTNING_POINTS; ZnListAssertSize(to_points, num_points); points = (ZnPoint *) ZnListArray(to_points); points[0] = *p1; points[3] = *p2; dx = p2->x - p1->x; dy = p2->y - p1->y; length = hypot(dx, dy); shape_a = length/LIGHTNING_SHAPE_A_RATIO + ((double) line_width)/2; shape_b = length/LIGHTNING_SHAPE_B_RATIO + ((double) line_width)/2; if (shape == LINE_LEFT_LIGHTNING) alpha = atan2(shape_a, shape_b); else alpha = -atan2(shape_a, shape_b); length2 = hypot(shape_a, shape_b); theta = atan2(-dy, dx); dx = p1->x + dx/2; dy = p1->y + dy/2; temp = cos(theta + alpha) * length2; points[1].x = dx + temp; points[2].x = dx - temp; temp = sin(theta + alpha) * length2; points[1].y = dy - temp; points[2].y = dy + temp; } else if (shape == LINE_LEFT_CORNER || shape == LINE_RIGHT_CORNER) { num_points = CORNER_POINTS; ZnListAssertSize(to_points, num_points); points = (ZnPoint *) ZnListArray(to_points); points[0] = *p1; points[2] = *p2; if (shape == LINE_LEFT_CORNER) { points[1].x = p1->x; points[1].y = p2->y; } else { points[1].x = p2->x; points[1].y = p1->y; } } else if (shape == LINE_DOUBLE_LEFT_CORNER || shape == LINE_DOUBLE_RIGHT_CORNER) { int dx, dy; num_points = DOUBLE_CORNER_POINTS; ZnListAssertSize(to_points, num_points); points = (ZnPoint *) ZnListArray(to_points); points[0] = *p1; points[3] = *p2; if (shape == LINE_DOUBLE_LEFT_CORNER) { dy = p2->y - p1->y; points[1].x = p1->x; points[2].x = p2->x; points[1].y = points[2].y = p1->y + dy/2; } else { dx = p2->x - p1->x; points[1].x = points[2].x = p1->x + dx/2; points[1].y = p1->y; points[2].y = p2->y; } } else /* if (shape) == LINE_STRAIGHT) */ { num_points = STRAIGHT_POINTS; ZnListAssertSize(to_points, num_points); points = (ZnPoint *) ZnListArray(to_points); points[0] = *p1; points[1] = *p2; } /* * Fill in the bbox, if requested. */ if (bbox) { ResetBBox(bbox); for (i = 0; i < num_points; i++) { AddPointToBBox(bbox, points[i].x, points[i].y); } /* Enlarge to take line_width into account. */ if (line_width > 1) { int lw_2 = (line_width+1)/2; bbox->orig.x -= lw_2; bbox->orig.y -= lw_2; bbox->corner.x += lw_2; bbox->corner.y += lw_2; } } } /* ********************************************************************************** * * ZnDrawLineShape -- * Draw a line given the points describing its path. It is designed to work * with GetLineShape albeit it does fairly trivial things. In the future some * shapes might need cooperation between the two and the clients will be ready * for that. * * ********************************************************************************** */ void ZnDrawLineShape(WidgetInfo *wi, ZnPoint *p, int num_p, LineStyle line_style, ZnColor foreground, unsigned int line_width, LineShape shape) { XPoint *xpoints; int i; XGCValues values; /* * Setup GC. */ ZnSetLineStyle(wi, line_style); values.foreground = ZnPixel(foreground); values.line_width = (line_width == 1) ? 0 : line_width; values.fill_style = FillSolid; values.join_style = JoinRound; values.cap_style = CapRound; XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCLineWidth|GCJoinStyle|GCCapStyle|GCForeground, &values); ZnListAssertSize(wi->work_xpts, num_p); xpoints = (XPoint *) ZnListArray(wi->work_xpts); for (i = 0; i < num_p; i++) { xpoints[i].x = p[i].x; xpoints[i].y = p[i].y; } XDrawLines(wi->dpy, wi->draw_buffer, wi->gc, xpoints, num_p, CoordModeOrigin); } /* * ReliefColorOfSegment -- */ static XColor * ReliefColorOfSegment(ZnReal x1, ZnReal y1, ZnReal x2, ZnReal y2, ReliefStyle relief, ZnGradient *gradient, WidgetInfo *wi) { ZnReal angle, angle_step, origin, position; int num_colors, color_index; num_colors = RELIEF_STEPS*2+1; angle_step = M_PI / (num_colors-1); origin = -(DegreesToRadian(wi->light_angle))-(angle_step/2.0); if (relief == RELIEF_SUNKEN) { origin += M_PI; } angle = ProjectionToAngle(y1 - y2, x2 - x1) + M_PI - origin; while (angle < 0.0) { angle += 2*M_PI; } while (angle > 2*M_PI) { angle -= 2*M_PI; } color_index = (int) (angle/angle_step); if (color_index > num_colors-1) { color_index = 2*(num_colors-1)-color_index; } if ((color_index < 0) || (color_index >= num_colors)) { fprintf(stderr, "Color index out of gradient (should not happen).\n"); if (color_index < 0) { color_index = 0; } if (color_index >= num_colors) { color_index = num_colors-1; } } position = 100*color_index/num_colors; /*printf("position %g, angle %g(%g), origin %g\n", position, RadianToDegrees(angle), angle, RadianToDegrees(origin));*/ return ZnGetGradientColor(gradient, position, NULL); } /* ********************************************************************************** * * ZnDrawRectangleRelief -- * Draw the bevels inside bbox. * ********************************************************************************** */ void ZnDrawRectangleRelief(WidgetInfo *wi, ReliefStyle relief, ZnGradient *gradient, XRectangle *bbox, unsigned int line_width) { XPoint bevel[4]; /* * If we haven't enough space to draw, exit. */ if ((bbox->width < 2*line_width) || (bbox->height < 2*line_width)) { return; } /* * Grooves and ridges are drawn with two recursives calls with * half the width of the original one. */ if ((relief == RELIEF_RIDGE) || (relief == RELIEF_GROOVE)) { unsigned int new_line_width; int offset; XRectangle internal_bbox; new_line_width = line_width/2; offset = line_width - new_line_width; ZnDrawRectangleRelief(wi, (relief==RELIEF_GROOVE)?RELIEF_SUNKEN:RELIEF_RAISED, gradient, bbox, new_line_width); internal_bbox = *bbox; internal_bbox.x +=offset; internal_bbox.y += offset; internal_bbox.width -= offset*2; internal_bbox.height -= offset*2; ZnDrawRectangleRelief(wi, (relief==RELIEF_GROOVE)?RELIEF_RAISED:RELIEF_SUNKEN, gradient, &internal_bbox, new_line_width); return; } XSetFillStyle(wi->dpy, wi->gc, FillSolid); bevel[0].x = bbox->x; bevel[0].y = bevel[1].y = bbox->y; bevel[1].x = bbox->x + bbox->width; bevel[2].y = bevel[3].y = bbox->y + line_width; bevel[2].x = bevel[1].x - line_width; bevel[3].x = bevel[0].x + line_width; XSetForeground(wi->dpy, wi->gc, ZnPixel(ReliefColorOfSegment(bevel[1].x, bevel[1].y, bevel[0].x, bevel[0].y, relief, gradient, wi))); XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin); bevel[0] = bevel[1]; bevel[3] = bevel[2]; bevel[1].y += bbox->height; bevel[2].y = bevel[1].y - line_width; XSetForeground(wi->dpy, wi->gc, ZnPixel(ReliefColorOfSegment(bevel[1].x, bevel[1].y, bevel[0].x, bevel[0].y, relief, gradient, wi))); XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin); bevel[0] = bevel[1]; bevel[3] = bevel[2]; bevel[1].x -= bbox->width; bevel[2].x = bevel[1].x + line_width; XSetForeground(wi->dpy, wi->gc, ZnPixel(ReliefColorOfSegment(bevel[1].x, bevel[1].y, bevel[0].x, bevel[0].y, relief, gradient, wi))); XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin); bevel[0] = bevel[1]; bevel[3] = bevel[2]; bevel[1].x = bbox->x; bevel[1].y = bbox->y; bevel[2].x = bevel[3].x; bevel[2].y = bbox->y + line_width; XSetForeground(wi->dpy, wi->gc, ZnPixel(ReliefColorOfSegment(bevel[1].x, bevel[1].y, bevel[0].x, bevel[0].y, relief, gradient, wi))); XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel, 4, Convex, CoordModeOrigin); } typedef struct { WidgetInfo *wi; ZnPoint *pp; ZnPoint *p0; ZnPoint *p1; double dist; ZnBBox *bbox; ReliefStyle relief; ZnGradient *gradient; int alpha; ZnBool smooth; int result; int count; ZnBool toggle; } PolygonData; static void DoPolygon(ZnPoint *p, int num_points, int line_width, ZnBool (*cb)(ZnPoint *bevels, PolygonData *pd), PolygonData *pd) { int i, processed_points; ZnPoint *p1, *p11=NULL, *p2; ZnPoint pp1, pp2, new_pp1, new_pp2; ZnPoint perp, c, shift1, shift2; ZnPoint bevel_points[4]; ZnBool folded, closed, colinear; ZnReal dx, dy; /* * If the polygon is closed (last point is the same as first) open it by * dropping the last point. The algorithm closes the path automatically. * We remember this to decide if we draw the last bevel or not and if we * need to generate ends perpendicular to the path.. */ closed = False; if ((p->x == p[num_points-1].x) && (p->y == p[num_points-1].y)) { closed = True; num_points--; } /*printf("num_points=%d(%s)\n", num_points, closed?"closed":"");*/ /* * We loop on all vertices of the polygon. * At each step we try to compute the corresponding border * corner `corner'. Then we build a polygon for the bevel. * Things look like this: * * bevel[1] / * * / * | / * | / * pp1 * * p[i-1] * | | bevel[0] * | | * | | * | | bevel[3] * | | p[i] * | | p1 p2 * pp2 * *--------------------* * | * | * corner *----*--------------------* * bevel[2] new_pp1 new_pp2 * * pp1 and pp2 are the ends of a segment // to p1 p2 at line_width * from it. These points are *NOT* necessarily on the perpendicular * going through p1 or p2. * This loop needs a bootstrap phase of two iterations (i.e we need to * process two points). This is why we start at the point before the last * and then wrap to the first point. * The algorithm discards any duplicate contiguous points. * It makes a special case if two consecutives edges are folded: * * bevel[1] pp1 pp2 a bevel[2] * *-----------*--------------*----------* * \ * \ * p[i-1] \ bevel[3] * *--------*-------------------------*---* corner * bevel[0] p2 p1 / * / * / * ----------*-----------*-------------* * new_pp1 new_pp2 c * * In such a case we need to compute a, c, corner from pp1, pp2, new_pp1 * and new_pp2. We compute the perpendicular to p1,p2 through p1, intersect * it with pp1,pp2 to obtain a, intersect it with new_pp1, new_pp2 to * obtain c, shift a,c and intersect it with p1,p2 to obtain corner. * */ processed_points = 0; if (!closed) { i = 0; p1 = p; } else { i = -2; p1 = &p[num_points-2]; } for (p2 = p1+1; i < num_points; i++, p2++) { /* * When it is time to wrap, do it */ if ((i == -1) || (i == num_points-1)) { p2 = p; } /* * Skip over close vertices. */ dx = p2->x - p1->x; dy = p2->y - p1->y; if ((ABS(dx) < 1.0) && (ABS(dy) < 1.0)) { continue; } ShiftLine(p1, p2, line_width, &new_pp1, &new_pp2); bevel_points[3] = *p1; folded = False; colinear = False; /* * The first two cases are for `open' polygons. We compute * a bevel closure that is perpendicular to the path. */ if ((processed_points == 0) && !closed) { perp.x = p1->x + (p2->y - p1->y); perp.y = p1->y - (p2->x - p1->x); IntersectLines(p1, &perp, &new_pp1, &new_pp2, &bevel_points[2]); } else if ((processed_points == num_points-1) && !closed) { perp.x = p1->x + (p11->y - p1->y); perp.y = p1->y - (p11->x - p1->x); IntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]); } else if (processed_points >= 1) { ZnReal dotp, dist, odx, ody; /* * The dot product of the two faces tell if the are * folded or colinear. The */ odx = p11->x - p1->x; ody = p11->y - p1->y; dotp = odx*dx + ody*dy; dist = LineToPointDist(p11, p2, p1); if ((dist < 4.0) && (dotp <= 0)) { perp.x = p1->x + (p2->y - p1->y); perp.y = p1->y - (p2->x - p1->x); IntersectLines(p1, &perp, &new_pp1, &new_pp2, &bevel_points[2]); colinear = True; } else { folded = !IntersectLines(&new_pp1, &new_pp2, &pp1, &pp2, &bevel_points[2]); /*printf("new_pp1 %g@%g, new_pp2 %g@%g, pp1 %g@%g, pp2 %g@%g, inter %g@%g\n", new_pp1.x, new_pp1.y, new_pp2.x, new_pp2.y, pp1.x, pp1.y, pp2.x, pp2.y, bevel_points[2].x, bevel_points[2].y);*/ folded = folded && (dotp < 0); if (folded) { /*printf("DoPolygonRelief: folded edges detected, %g@%g, %g@%g, %g@%g, %g@%g\n", pp1.x, pp1.y, pp2.x, pp2.y, new_pp1.x, new_pp1.y, new_pp2.x, new_pp2.y);*/ perp.x = p1->x + (p2->y - p1->y); perp.y = p1->y - (p2->x - p1->x); IntersectLines(p1, &perp, &pp1, &pp2, &bevel_points[2]); IntersectLines(p1, &perp, &new_pp1, &new_pp2, &c); ShiftLine(p1, &perp, line_width, &shift1, &shift2); IntersectLines(p1, p2, &shift1, &shift2, &bevel_points[3]); } } } if ((processed_points >= 2) || (!closed && (processed_points == 1))) { if ((processed_points == num_points-1) && !closed) { pd->p0 = pd->p1 = NULL; } else { pd->p0 = p1; pd->p1 = p2; } if ((*cb)(bevel_points, pd)) { return; } } p11 = p1; p1 = p2; pp1 = new_pp1; pp2 = new_pp2; bevel_points[0] = bevel_points[3]; if (folded) { bevel_points[1] = c; } else if ((processed_points >= 1) || !closed) { bevel_points[1] = bevel_points[2]; } processed_points++; } } /* ********************************************************************************** * * ZnGetPolygonReliefBBox -- * Returns the bevelled polygon bounding box. * ********************************************************************************** */ static ZnBool PolygonBBoxCB(ZnPoint *bevels, PolygonData *pd) { int i; for (i = 0; i < 4; i++) { AddPointToBBox(pd->bbox, bevels[i].x, bevels[i].y); } return 0; } void ZnGetPolygonReliefBBox(ZnPoint *points, int num_points, int line_width, ZnBBox *bbox) { PolygonData pd; pd.bbox = bbox; ResetBBox(bbox); DoPolygon(points, num_points, line_width, PolygonBBoxCB, &pd); } /* ********************************************************************************** * * ZnPolygonReliefInBBox -- * Returns (-1) if the relief is entirely outside the bbox, (1) if it is * entirely inside or (0) if in between * ********************************************************************************** */ static ZnBool PolygonInBBoxCB(ZnPoint *bevels, PolygonData *pd) { if (pd->count == 0) { pd->count++; pd->result = PolygonInBBox(bevels, 4, pd->bbox, NULL); if (pd->result == 0) { return 1; } } else { if (PolygonInBBox(bevels, 4, pd->bbox, NULL) != pd->result) { pd->result = 0; return 1; } } return 0; } int ZnPolygonReliefInBBox(ZnPoint *points, int num_points, int line_width, ZnBBox *area) { PolygonData pd; pd.bbox = area; pd.count = 0; DoPolygon(points, num_points, line_width, PolygonInBBoxCB, &pd); return pd.result; } /* ********************************************************************************** * * ZnPolygonReliefToPointDist -- * Returns the distance between the given point and * the bevelled polygon. * ********************************************************************************** */ static ZnBool PolygonDistCB(ZnPoint *bevels, PolygonData *pd) { double new_dist; new_dist = PolygonToPointDist(bevels, 4, pd->pp); if (new_dist < 0.0) { new_dist = 0.0; } if (new_dist < pd->dist) { pd->dist = new_dist; } return 0; } double ZnPolygonReliefToPointDist(ZnPoint *points, int num_points, int line_width, ZnPoint *pp) { PolygonData pd; pd.dist = 1.0e40; pd.pp = pp; DoPolygon(points, num_points, line_width, PolygonDistCB, &pd); return pd.dist; } /* ********************************************************************************** * * ZnDrawPolygonRelief -- * Draw the bevels around path. * ********************************************************************************** */ static ZnBool PolygonDrawCB(ZnPoint *bevels, PolygonData *pd) { WidgetInfo *wi = pd->wi; XPoint bevel_xpoints[5]; XGCValues values; int j; values.foreground = ZnPixel(ReliefColorOfSegment(bevels[0].x, bevels[0].y, bevels[3].x, bevels[3].y, pd->relief, pd->gradient, pd->wi)); values.fill_style = FillSolid; XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCForeground, &values); for (j = 0; j < 4; j++) { bevel_xpoints[j].x = REAL_TO_INT(bevels[j].x); bevel_xpoints[j].y = REAL_TO_INT(bevels[j].y); } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, bevel_xpoints, 4, Convex, CoordModeOrigin); return 0; } void ZnDrawPolygonRelief(WidgetInfo *wi, ReliefStyle relief, ZnGradient *gradient, ZnPoint *points, int num_points, int line_width) { PolygonData pd; pd.wi = wi; pd.gradient = gradient; /* * Grooves and ridges are drawn with two calls. The first * with the original width, the second with half the width. */ if ((relief == RELIEF_RIDGE) || (relief == RELIEF_GROOVE)) { pd.relief = (relief==RELIEF_GROOVE)?RELIEF_RAISED:RELIEF_SUNKEN; DoPolygon(points, num_points, line_width, PolygonDrawCB, &pd); pd.relief = (relief==RELIEF_GROOVE)?RELIEF_SUNKEN:RELIEF_RAISED; DoPolygon(points, num_points, line_width/2, PolygonDrawCB, &pd); } else { pd.relief = relief; DoPolygon(points, num_points, line_width, PolygonDrawCB, &pd); } } /* ********************************************************************************** * * ZnRenderPolygonRelief -- * Draw the bevels around path using alpha enabled rendering. * ********************************************************************************** */ #ifdef GLX static ZnBool PolygonRenderCB(ZnPoint *bevels, PolygonData *pd) { int i; ZnPoint p[6]; XColor *c[8]; XColor *color = ZnGetGradientColor(pd->gradient, 51, NULL); ReliefStyle relief, int_relief; ZnBool two_faces, round, rule; rule = pd->relief & RELIEF_RULE; round = pd->relief & RELIEF_ROUND; two_faces = pd->relief & RELIEF_TWO_FACES; relief = pd->relief & RELIEF_MASK; for (i = 0; i < 4; i++) { p[i].x = REAL_TO_INT(bevels[i].x); p[i].y = REAL_TO_INT(bevels[i].y); } if (two_faces) { p[4].x = (p[0].x+p[1].x)/2; p[4].y = (p[0].y+p[1].y)/2; p[5].x = (p[2].x+p[3].x)/2; p[5].y = (p[2].y+p[3].y)/2; if (relief == RELIEF_SUNKEN) { int_relief = RELIEF_RAISED; } else { int_relief = RELIEF_SUNKEN; } c[0]=c[1]=c[2]=c[3] = ReliefColorOfSegment(bevels[0].x, bevels[0].y, bevels[3].x, bevels[3].y, relief, pd->gradient, pd->wi); c[4]=c[5]=c[6]=c[7] = ReliefColorOfSegment(bevels[0].x, bevels[0].y, bevels[3].x, bevels[3].y, int_relief, pd->gradient, pd->wi); if (pd->smooth && pd->p0) { c[2]=c[3] = ReliefColorOfSegment(pd->p0->x, pd->p0->y, pd->p1->x, pd->p1->y, relief, pd->gradient, pd->wi); c[6]=c[7] = ReliefColorOfSegment(pd->p0->x, pd->p0->y, pd->p1->x, pd->p1->y, int_relief, pd->gradient, pd->wi); } if (round) { if (!rule) { c[0]=c[3]=c[5]=c[6]=color; } else { c[1]=c[2]=c[4]=c[7]=color; } } glBegin(GL_QUADS); glColor4us(c[0]->red, c[0]->green, c[0]->blue, pd->alpha); glVertex2f(p[0].x, p[0].y); glColor4us(c[1]->red, c[1]->green, c[1]->blue, pd->alpha); glVertex2f(p[4].x, p[4].y); glColor4us(c[2]->red, c[2]->green, c[2]->blue, pd->alpha); glVertex2f(p[5].x, p[5].y); glColor4us(c[3]->red, c[3]->green, c[3]->blue, pd->alpha); glVertex2f(p[3].x, p[3].y); glColor4us(c[4]->red, c[4]->green, c[4]->blue, pd->alpha); glVertex2f(p[4].x, p[4].y); glColor4us(c[5]->red, c[5]->green, c[5]->blue, pd->alpha); glVertex2f(p[1].x, p[1].y); glColor4us(c[6]->red, c[6]->green, c[6]->blue, pd->alpha); glVertex2f(p[2].x, p[2].y); glColor4us(c[7]->red, c[7]->green, c[7]->blue, pd->alpha); glVertex2f(p[5].x, p[5].y); glEnd(); } else { /* Single face */ c[0]=c[1]=c[2]=c[3] = ReliefColorOfSegment(bevels[0].x, bevels[0].y, bevels[3].x, bevels[3].y, relief, pd->gradient, pd->wi); if (pd->smooth && pd->p0) { c[2]=c[3] = ReliefColorOfSegment(pd->p0->x, pd->p0->y, pd->p1->x, pd->p1->y, relief, pd->gradient, pd->wi); } if (round) { c[1]=c[2] = color; } glBegin(GL_QUADS); glColor4us(c[0]->red, c[0]->green, c[0]->blue, pd->alpha); glVertex2f(p[0].x, p[0].y); glColor4us(c[1]->red, c[1]->green, c[1]->blue, pd->alpha); glVertex2f(p[1].x, p[1].y); glColor4us(c[2]->red, c[2]->green, c[2]->blue, pd->alpha); glVertex2f(p[2].x, p[2].y); glColor4us(c[3]->red, c[3]->green, c[3]->blue, pd->alpha); glVertex2f(p[3].x, p[3].y); glEnd(); } return 0; } void ZnRenderPolygonRelief(WidgetInfo *wi, ReliefStyle relief, ZnGradient *gradient, ZnBool smooth, ZnPoint *points, int num_points, ZnReal line_width) { PolygonData pd; pd.wi = wi; pd.gradient = gradient; ZnGetGradientColor(gradient, 0, &pd.alpha); pd.alpha = ZnComposeAlpha(pd.alpha, wi->alpha); pd.smooth = smooth; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); pd.relief = relief; pd.count = 0; DoPolygon(points, num_points, line_width, PolygonRenderCB, &pd); } void ZnRenderPolyline(WidgetInfo *wi, ZnPoint *points, int num_points, ZnReal line_width, LineStyle line_style, int cap_style, int join_style, ZnLineEnd first_end, ZnLineEnd last_end, ZnGradient *gradient) { int num_clips = ZnListSize(wi->clip_stack); ZnPoint end_points[LINE_END_POINTS]; ZnBool need_rcaps, thin, closed; int pass, num_passes, i, k, m; ZnPoint c1, c2; XColor *color; int alpha; /* * The code below draws curves thiner than the min * of GL_SMOOTH_LINE_WIDTH_RANGE and GL_SMOOTH_POINT_SIZE_RANGE * with a mix of anti-aliased lines and points. The curves that * are thicker are drawn using regular polygons. * BUG: The joints are drawn only rounded. * The caps can be either round or butt (but not projecting). */ thin = ((line_width <= wi->max_line_width) && (line_width <= wi->max_point_width)); closed = (points->x == points[num_points-1].x) && (points->y == points[num_points-1].y); color = ZnGetGradientColor(gradient, 0, &alpha); alpha = ZnComposeAlpha(alpha, wi->alpha); glColor4us(color->red, color->green, color->blue, alpha); ZnSetLineStyle(wi, line_style); glLineWidth(line_width); if (thin && (alpha != 65535)) { /* * This makes a special case for transparent lines. * In this case we need to avoid drawing twice a * single pixel. To achieve this we use the stencil * buffer to protect already drawn pixels, unfortunately * using antialiasing write in the stencil even if * the pixel area is not fully covered resulting in * a crack that can't be covered by points later on. * To handle this case we need to disable the stencil * which in turn result in erroneous alpha coverage. * * We have chosen to drawn transparent lines with a * correct coverage but NOT antialiased. */ glPointSize(line_width>1?line_width-1:line_width); glDisable(GL_LINE_SMOOTH); } else { glPointSize(line_width); } num_passes = 1; if (alpha != 65535) { num_passes = 2; } for (pass = 0; pass < num_passes; pass++) { if (alpha != 65535) { if (pass == 0) { GLX_START_CLIP(num_clips, True); } else { GLX_RESTORE_STENCIL(num_clips, False); } } if (first_end) { GetLineEnd(&points[0], &points[1], line_width, cap_style, first_end, end_points); glBegin(GL_TRIANGLE_FAN); for (m = 0; m < LINE_END_POINTS; m++) { glVertex2f(end_points[m].x, end_points[m].y); } glEnd(); } if (last_end) { GetLineEnd(&points[num_points-1], &points[num_points-2], line_width, cap_style, last_end, end_points); glBegin(GL_TRIANGLE_FAN); for (m = 0; m < LINE_END_POINTS; m++) { glVertex2f(end_points[m].x, end_points[m].y); } glEnd(); } if (thin) { glBegin(GL_LINE_STRIP); for (i = 0; i < num_points; i++) { glVertex2f(points[i].x, points[i].y); } glEnd(); } else { glBegin(GL_QUADS); for (i = 0; i < num_points-1; i++) { GetButtPoints(&points[i+1], &points[i], line_width, False, &c1, &c2); glVertex2f(c1.x, c1.y); glVertex2f(c2.x, c2.y); GetButtPoints(&points[i], &points[i+1], line_width, False, &c1, &c2); glVertex2f(c1.x, c1.y); glVertex2f(c2.x, c2.y); } glEnd(); } /* if (pass == 0) { GLX_RENDER_CLIPPED(); } else { GLX_END_CLIP(num_clips); break; }*/ need_rcaps = ((line_width > 1) && (cap_style == CapRound)); i = 0; k = num_points; if (closed) { k--; } if (!need_rcaps || first_end) { i++; } if ((!need_rcaps && !closed) || last_end) { k--; } if (thin) { glBegin(GL_POINTS); for ( ; i < k; i++) { glVertex2f(points[i].x, points[i].y); } glEnd(); } else { int num_cpoints; ZnReal lw_2 = line_width / 2.0; ZnPoint *cpoints = GetCirclePoints(3, ZN_CIRCLE_COARSE, 0, 2*M_PI, &num_cpoints, NULL); for ( ; i < k; i++) { glBegin(GL_TRIANGLE_FAN); glVertex2f(points[i].x, points[i].y); for (m = 0; m < num_cpoints; m++) { glVertex2f(points[i].x + cpoints[m].x*lw_2, points[i].y + cpoints[m].y*lw_2); } glEnd(); } } } GLX_END_CLIP(num_clips); if (thin) { glEnable(GL_LINE_SMOOTH); } } void ZnRenderIcon(WidgetInfo *wi, ZnImage image, ZnGradient *gradient, ZnPoint *origin, ZnBool modulate) { ZnPoint p[4]; int width, height; ZnSizeOfImage(image, &width, &height); p[0] = *origin; p[1].x = origin->x; p[1].y = origin->y + height; p[2].x = origin->x + width; p[2].y = p[1].y; p[3].x = p[2].x; p[3].y = origin->y; ZnRenderImage(wi, image, gradient, p, modulate); } void ZnRenderImage(WidgetInfo *wi, ZnImage image, ZnGradient *gradient, ZnPoint *quad, ZnBool modulate) { XColor *color; int alpha; ZnReal t, s; GLuint texobj; color = ZnGetGradientColor(gradient, 0, &alpha); alpha = ZnComposeAlpha(alpha, wi->alpha); texobj = ZnImageTex(image, &t, &s); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texobj); if (modulate) { glColor4us(color->red, color->green, color->blue, alpha); } else { glColor4us(65535, 65535, 65535, alpha); } glBegin(GL_QUADS); glTexCoord2f(0.0, 0.0); glVertex2f(quad[0].x, quad[0].y); glTexCoord2f(0.0, t); glVertex2f(quad[1].x, quad[1].y); glTexCoord2f(s, t); glVertex2f(quad[2].x, quad[2].y); glTexCoord2f(s, 0.0); glVertex2f(quad[3].x, quad[3].y); glEnd(); glDisable(GL_TEXTURE_2D); } void ZnRenderTile(WidgetInfo *wi, ZnImage tile, ZnGradient *gradient, void cb(void *), void *closure, ZnPoint *quad) /* Right now it's a ZnBBox */ { ZnReal x, y, nx, ny, lx, ly, s, t, tiles, tilet; int width, height, alpha, num_clips = ZnListSize(wi->clip_stack); GLuint texobj; if (gradient) { ZnGetGradientColor(gradient, 0.0, &alpha); alpha = ZnComposeAlpha(alpha, wi->alpha); } else { alpha = ZnComposeAlpha(100, wi->alpha); } if (cb) { /* * Setup the stencil buffer with the shape to be drawn. */ GLX_START_CLIP(num_clips, False); (*cb)(closure); GLX_RESTORE_STENCIL(num_clips, True); } /* * Then texture map the quad through the shape. * The rectangle is drawn using quads, each * quad matching the size of the texture tile. */ ZnSizeOfImage(tile, &width, &height); texobj = ZnImageTex(tile, &tilet, &tiles); glEnable(GL_TEXTURE_2D); glColor4us(65535, 65535, 65535, alpha); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glBindTexture(GL_TEXTURE_2D, texobj); y = quad[0].y; lx = quad[1].x; ly = quad[1].y; glBegin(GL_QUADS); do { x = quad[0].x; t = 1.0; ny = y + height; if (ny > ly) { ny = ly; t = (ly - y) / (ZnReal) height; } t *= tilet; do { s = 1.0; nx = x + width; if (nx > lx) { nx = lx; s = (lx - x) / (ZnReal) width; } s *= tiles; glTexCoord2f(0.0, 0.0); glVertex2f(x, y); glTexCoord2f(0.0, t); glVertex2f(x, ny); glTexCoord2f(s, t); glVertex2f(nx, ny); glTexCoord2f(s, 0.0); glVertex2f(nx, y); x = nx; } while (x != lx); y = ny; } while (y != ly); glEnd(); if (cb) { GLX_END_CLIP(num_clips); } glDisable(GL_TEXTURE_2D); } void ZnComputeAxialGradient(WidgetInfo *wi, ZnPoly *shape, int angle, ZnPoint *grad_geo) { ZnTransfo *transfo1, *transfo2; ZnContour *c; ZnBBox bbox; ZnPoint *points, p[4]; int i; transfo1 = ZnTransfoNew(); transfo2 = ZnTransfoNew(); ZnRotateDeg(transfo1, angle); ZnRotateDeg(transfo2, -angle); c = shape->contours; ResetBBox(&bbox); for (i = 0; i < shape->num_contours; i++, c++) { if (c->cw) { continue; } ZnListAssertSize(wi->work_pts, c->num_points); points = ZnListArray(wi->work_pts); ZnTransformPoints(transfo1, c->points, points, c->num_points); AddPointsToBBox(&bbox, points, c->num_points); } bbox.orig.x--; bbox.orig.y--; bbox.corner.x++; bbox.corner.y++; p[0] = bbox.orig; p[2] = bbox.corner; p[1].x = p[2].x; p[1].y = p[0].y; p[3].x = p[0].x; p[3].y = p[2].y; ZnTransfoSetIdentity(transfo1); ZnTransfoCompose(transfo1, transfo2, wi->current_transfo); ZnTransformPoints(transfo1, p, grad_geo, 4); ZnTransfoFree(transfo1); ZnTransfoFree(transfo2); } void ZnComputeRadialGradient(WidgetInfo *wi, ZnPoly *shape, ZnBBox *bbox, ZnPoint *center, ZnPoint *grad_geo) { ZnReal dist, new, x, y, fact; ZnContour *c; ZnPoint *points; ZnTransfo *transfo1; int i, j; ZnTransformPoint(wi->current_transfo, center, &grad_geo[0]); transfo1 = ZnTransfoNew(); ZnTranslate(transfo1, -grad_geo[0].x, -grad_geo[0].y); fact = (bbox->corner.x-bbox->orig.x)/(bbox->corner.y-bbox->orig.y); ZnScale(transfo1, 1.0, fact); dist = 0.0; c = shape->contours; for (j = 0; j < shape->num_contours; j++, c++) { if (c->cw) { continue; } ZnListAssertSize(wi->work_pts, c->num_points); points = ZnListArray(wi->work_pts); ZnTransformPoints(transfo1, c->points, points, c->num_points); for (i = 0; i < c->num_points; i++, points++) { x = points->x; y = points->y; new = x*x+y*y; if (new > dist) { dist = new; } } } grad_geo[1].x = sqrt(dist) + 2; /* Max radius plus a fuzz factor */ grad_geo[1].y = grad_geo[1].x / fact; ZnTransfoFree(transfo1); } void ZnRenderGradient(WidgetInfo *wi, ZnGradient *gradient, /* The grdient to be drawn (static * parameters). */ void cb(void *), /* A callback called to clip the shape * containing the gradient. */ void *closure, /* The callback parameter. */ ZnPoint *quad, /* The gradient geometric parameters * (dynamic). */ ZnPoly *poly /* Used only by ZN_PATH_GRADIENT */ ) { int alpha, angle, i, j; int type = gradient->type; XColor *color; ZnPoint p; ZnPoint dposa, dposb, dposc, dposd; ZnPoint dcontrol; ZnReal npos, pos, control; int num_clips = ZnListSize(wi->clip_stack); ZnPoint iquad[4]; if (!cb && (type == ZN_AXIAL_GRADIENT)) { /* Render an aligned * axial gradient in the quad */ angle = gradient->g.angle; /* * Adjust the quad for 90 180 and 270 degrees axial * gradients. Other angles not supported. */ switch (angle) { case 90: iquad[0] = quad[3]; iquad[3] = quad[2]; iquad[2] = quad[1]; iquad[1] = quad[0]; quad = iquad; break; case 180: iquad[0] = quad[2]; iquad[3] = quad[1]; iquad[2] = quad[0]; iquad[1] = quad[3]; quad = iquad; break; case 270: iquad[0] = quad[1]; iquad[3] = quad[0]; iquad[2] = quad[3]; iquad[1] = quad[2]; quad = iquad; break; } } if (cb) { /* * Draw the gradient shape in the stencil using the provided * callback (clipping). */ GLX_START_CLIP(num_clips, False); (*cb)(closure); GLX_RESTORE_STENCIL(num_clips, True); } if (type == ZN_AXIAL_GRADIENT) { /* * Then fill the axial gradient using the provided * quad and colors. The stencil will be restored * to its previous state in the process. */ glBegin(GL_QUAD_STRIP); for (i = 0; i < gradient->num_colors; i++) { color = gradient->colors[i].rgb; alpha = ZnComposeAlpha(gradient->colors[i].alpha, wi->alpha); glColor4us(color->red, color->green, color->blue, alpha); pos = gradient->colors[i].position; control = gradient->colors[i].control; dposa.x = (quad[1].x - quad[0].x)*pos/100.0; dposa.y = (quad[1].y - quad[0].y)*pos/100.0; p.x = quad[0].x + dposa.x; p.y = quad[0].y + dposa.y; glVertex2f(p.x, p.y); dposb.x = (quad[2].x - quad[3].x)*pos/100.0; dposb.y = (quad[2].y - quad[3].y)*pos/100.0; p.x = quad[3].x + dposb.x; p.y = quad[3].y + dposb.y; glVertex2f(p.x, p.y); if ((control != 50.0) && (i != gradient->num_colors-1)) { color = gradient->colors[i].mid_rgb; alpha = ZnComposeAlpha(gradient->colors[i].mid_alpha, wi->alpha); glColor4us(color->red, color->green, color->blue, alpha); npos = gradient->colors[i+1].position; dposc.x = (quad[1].x - quad[0].x)*npos/100.0; dposc.y = (quad[1].y - quad[0].y)*npos/100.0; dcontrol.x = (dposc.x - dposa.x)*control/100.0; dcontrol.y = (dposc.y - dposa.y)*control/100.0; p.x = quad[0].x + dposa.x + dcontrol.x; p.y = quad[0].y + dposa.y + dcontrol.y; glVertex2f(p.x, p.y); dposd.x = (quad[2].x - quad[3].x)*npos/100.0; dposd.y = (quad[2].y - quad[3].y)*npos/100.0; dcontrol.x = (dposd.x - dposb.x)*control/100.0; dcontrol.y = (dposd.y - dposb.y)*control/100.0; p.x = quad[3].x + dposb.x + dcontrol.x; p.y = quad[3].y + dposb.y + dcontrol.y; glVertex2f(p.x, p.y); } } glEnd(); } else if (type == ZN_RADIAL_GRADIENT) { ZnReal x, radiusx, radius2x, radius3x; ZnReal y, radiusy, radius2y, radius3y; int num_p, alpha2; ZnPoint *genarc; XColor *color2; genarc = GetCirclePoints(3, ZN_CIRCLE_FINE, 0, 2*M_PI, &num_p, NULL); radiusx = 0; radiusy = 0; color = gradient->colors[0].rgb; alpha = ZnComposeAlpha(gradient->colors[0].alpha, wi->alpha); control = gradient->colors[0].control; for (j = 1; j < gradient->num_colors; j++) { radius2x = quad[1].x*gradient->colors[j].position/100.0; radius2y = quad[1].y*gradient->colors[j].position/100.0; if ((control != 50) && (j != gradient->num_colors-1)) { glBegin(GL_QUAD_STRIP); color2 = gradient->colors[j-1].mid_rgb; alpha2 = ZnComposeAlpha(gradient->colors[j-1].mid_alpha, wi->alpha); radius3x = radiusx + (radius2x-radiusx)*control/100.0; radius3y = radiusy + (radius2y-radiusy)*control/100.0; for (i = 0; i < num_p; i++) { x = quad[0].x+genarc[i].x*radiusx; y = quad[0].y+genarc[i].y*radiusy; glColor4us(color->red, color->green, color->blue, alpha); glVertex2f(x, y); x = quad[0].x+genarc[i].x*radius3x; y = quad[0].y+genarc[i].y*radius3y; glColor4us(color2->red, color2->green, color2->blue, alpha); glVertex2f(x, y); } radiusx = radius3x; radiusy = radius3y; color = color2; alpha = alpha2; glEnd(); } glBegin(GL_QUAD_STRIP); color2 = gradient->colors[j].rgb; alpha2 = ZnComposeAlpha(gradient->colors[j].alpha, wi->alpha); for (i = 0; i < num_p; i++) { x = quad[0].x+genarc[i].x*radiusx; y = quad[0].y+genarc[i].y*radiusy; glColor4us(color->red, color->green, color->blue, alpha); glVertex2f(x, y); x = quad[0].x+genarc[i].x*radius2x; y = quad[0].y+genarc[i].y*radius2y; glColor4us(color2->red, color2->green, color2->blue, alpha2); glVertex2f(x, y); } glEnd(); radiusx = radius2x; radiusy = radius2y; color = color2; alpha = alpha2; control = gradient->colors[j].control; } } else if (type == ZN_PATH_GRADIENT) { ZnPoint p, pp, p2, pp2, p3, pp3; int num_p, k, ii; ZnPoint *points; ZnReal position; for (k = 0; k < poly->num_contours; k++) { if (poly->contours[k].cw) { continue; } points = poly->contours[k].points; num_p = poly->contours[k].num_points; for (i = 0; i < num_p; i++) { if (i == num_p-1) { ii = 0; } else { ii = i+1; } glBegin(GL_QUAD_STRIP); p.x = p.y = pp.x = pp.y = 0; control = gradient->colors[0].control; position = gradient->colors[0].position; alpha = ZnComposeAlpha(gradient->colors[0].alpha, wi->alpha); color = gradient->colors[0].rgb; glColor4us(color->red, color->green, color->blue, alpha); glVertex2f(quad[0].x+p.x, quad[0].y+p.y); glVertex2f(quad[0].x+pp.x, quad[0].y+pp.y); for (j = 0; j < gradient->num_colors-1; j++) { position = gradient->colors[j+1].position; p2.x = (points[i].x-quad[0].x)*position/100.0; p2.y = (points[i].y-quad[0].y)*position/100.0; pp2.x = (points[ii].x-quad[0].x)*position/100.0; pp2.y = (points[ii].y-quad[0].y)*position/100.0; if (control != 50) { color = gradient->colors[j].mid_rgb; alpha = ZnComposeAlpha(gradient->colors[j].mid_alpha, wi->alpha); p3.x = p.x+(p2.x-p.x)*control/100.0; p3.y = p.y+(p2.y-p.y)*control/100.0; pp3.x = pp.x+(pp2.x-pp.x)*control/100.0; pp3.y = pp.y+(pp2.y-pp.y)*control/100.0; glColor4us(color->red, color->green, color->blue, alpha); glVertex2f(quad[0].x+p3.x, quad[0].y+p3.y); glVertex2f(quad[0].x+pp3.x, quad[0].y+pp3.y); } control = gradient->colors[j+1].control; alpha = ZnComposeAlpha(gradient->colors[j+1].alpha, wi->alpha); color = gradient->colors[j+1].rgb; p = p2; pp = pp2; glColor4us(color->red, color->green, color->blue, alpha); glVertex2f(quad[0].x+p.x, quad[0].y+p.y); glVertex2f(quad[0].x+pp.x, quad[0].y+pp.y); } glEnd(); } } } if (cb) { /* * Restore the previous GL state. */ GLX_END_CLIP(num_clips); } } void ZnRenderHollowDot(WidgetInfo *wi, ZnPoint *p, ZnReal size) { int num_clips = ZnListSize(wi->clip_stack); GLX_START_CLIP(num_clips, False); glPointSize(size-2); glBegin(GL_POINTS); glVertex2f(p->x, p->y); glEnd(); GLX_RENDER_CLIPPED(); glPointSize(size); glBegin(GL_POINTS); glVertex2f(p->x, p->y); glEnd(); GLX_RESTORE_STENCIL(num_clips, False); glBegin(GL_POINTS); glVertex2f(p->x, p->y); glEnd(); GLX_END_CLIP(num_clips); } #endif #ifdef GLX /* Copyright (c) Mark J. Kilgard, 1997. */ /* This program is freely distributable without licensing fees and is provided without guarantee or warrantee expressed or implied. This program is -not- in the public domain. */ #include void ZnRenderGlyph(ZnTexFontInfo *tfi, int c) { ZnTexGVI *tgvi; tgvi = ZnTexFontGVI(tfi, c); glBegin(GL_QUADS); glTexCoord2fv(tgvi->t0); glVertex2sv(tgvi->v0); glTexCoord2fv(tgvi->t1); glVertex2sv(tgvi->v1); glTexCoord2fv(tgvi->t2); glVertex2sv(tgvi->v2); glTexCoord2fv(tgvi->t3); glVertex2sv(tgvi->v3); glEnd(); glTranslatef(tgvi->advance, 0.0, 0.0); } void ZnRenderString(ZnTexFontInfo *tfi, unsigned char *string, int len) { int i; for (i = 0; i < len; i++) { ZnRenderGlyph(tfi, string[i]); } } enum { MONO, TOP_BOTTOM, LEFT_RIGHT, FOUR }; void ZnRenderFancyString(ZnTexFontInfo *tfi, unsigned char *string, int len) { ZnTexGVI *tgvi; GLubyte c[4][3]; int mode = MONO; int i; for (i = 0; i < len; i++) { if (string[i] == 27) { switch (string[i + 1]) { case 'M': mode = MONO; glColor3ubv((GLubyte *) & string[i + 2]); i += 4; break; case 'T': mode = TOP_BOTTOM; memcpy(c, &string[i + 2], 6); i += 7; break; case 'L': mode = LEFT_RIGHT; memcpy(c, &string[i + 2], 6); i += 7; break; case 'F': mode = FOUR; memcpy(c, &string[i + 2], 12); i += 13; break; } } else { switch (mode) { case MONO: ZnRenderGlyph(tfi, string[i]); break; case TOP_BOTTOM: tgvi = ZnTexFontGVI(tfi, string[i]); glBegin(GL_QUADS); glColor3ubv(c[0]); glTexCoord2fv(tgvi->t0); glVertex2sv(tgvi->v0); glTexCoord2fv(tgvi->t1); glVertex2sv(tgvi->v1); glColor3ubv(c[1]); glTexCoord2fv(tgvi->t2); glVertex2sv(tgvi->v2); glTexCoord2fv(tgvi->t3); glVertex2sv(tgvi->v3); glEnd(); glTranslatef(tgvi->advance, 0.0, 0.0); break; case LEFT_RIGHT: tgvi = ZnTexFontGVI(tfi, string[i]); glBegin(GL_QUADS); glColor3ubv(c[0]); glTexCoord2fv(tgvi->t0); glVertex2sv(tgvi->v0); glColor3ubv(c[1]); glTexCoord2fv(tgvi->t1); glVertex2sv(tgvi->v1); glColor3ubv(c[1]); glTexCoord2fv(tgvi->t2); glVertex2sv(tgvi->v2); glColor3ubv(c[0]); glTexCoord2fv(tgvi->t3); glVertex2sv(tgvi->v3); glEnd(); glTranslatef(tgvi->advance, 0.0, 0.0); break; case FOUR: tgvi = ZnTexFontGVI(tfi, string[i]); glBegin(GL_QUADS); glColor3ubv(c[0]); glTexCoord2fv(tgvi->t0); glVertex2sv(tgvi->v0); glColor3ubv(c[1]); glTexCoord2fv(tgvi->t1); glVertex2sv(tgvi->v1); glColor3ubv(c[2]); glTexCoord2fv(tgvi->t2); glVertex2sv(tgvi->v2); glColor3ubv(c[3]); glTexCoord2fv(tgvi->t3); glVertex2sv(tgvi->v3); glEnd(); glTranslatef(tgvi->advance, 0.0, 0.0); break; } } } } #endif