Plan 9 from Bell Labs’s /usr/web/sources/contrib/steve/root/sys/src/cmd/graphviz/dotneato/common/splines.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
    This software may only be used by you under license from AT&T Corp.
    ("AT&T").  A copy of AT&T's Source Code Agreement is available at
    AT&T's Internet website having the URL:
    <http://www.research.att.com/sw/tools/graphviz/license/source.html>
    If you received this software without first entering into a license
    with AT&T, you have an infringing copy of this software and cannot use
    it without violating AT&T's intellectual property rights.
*/

/* Functions related to creating a spline and attaching it to
 * an edge, starting from a list of control points.
 */

#include <render.h>

/* wantclip:
 * Return false if head/tail end of edge should not be clipped
 * to node.
 */
static boolean 
wantclip(edge_t *e, node_t *n)
{
	char		*str;
	attrsym_t *sym = 0;
	boolean		rv = TRUE;

	if (n == e->tail) sym = E_tailclip;
	if (n == e->head) sym = E_headclip;
	if (sym) {	/* mapbool isn't a good fit, because we want "" to mean TRUE */
		str = agxget(e,sym->index);
		if (str && str[0]) rv = mapbool(str);
		else rv = TRUE;
	}
	return rv;
}

/* arrow_clip:
 * Clip arrow to node boundary.
 * The real work is done elsewhere. Here we get the real edge,
 * check that the edge has arrowheads, and that an endpoint
 * isn't a merge point where several parts of an edge meet.
 * (e.g., with edge concentrators).
 */
static void 
arrow_clip (edge_t *fe, edge_t *le,
            point *ps, int *startp, int *endp, 
            bezier *spl, splineInfo* info)
{
	edge_t *e;
	int i, j, sflag, eflag;

	for (e = fe; ED_to_orig(e); e = ED_to_orig(e))
	;
	j = info->swapEnds(e);
	arrow_flags (e, &sflag, &eflag);
 	if (info->splineMerge (le->head)) eflag = ARR_NONE;
 	if (info->splineMerge (fe->tail)) sflag = ARR_NONE;
	if (j) {i=sflag; sflag=eflag; eflag=i;} /* swap the two ends */

	if (sflag) *startp = arrowStartClip (e, ps, *startp, *endp, spl, sflag);
	if (eflag) *endp = arrowEndClip (e, ps, *startp, *endp, spl, eflag);
}

/* shape_clip0:
 * Clip Bezier to node shape using binary search.
 * left_inside specifies that curve[0] is inside the node, else
 * curve[3] is taken as inside.
 * Assumes ND_shape(n) and ND_shape(n)->insidefn are non-NULL.
 * See note on shape_clip.
 */
void 
shape_clip0 (node_t* n, point curve[4], edge_t* e, boolean left_inside)
{
	int i, save_real_size;
	boolean found, inside;
	pointf pt, opt, c[4], seg[4], best[4], *left, *right;
	double low, high, t;
	
	save_real_size = ND_rw_i(n);
	for (i = 0; i < 4; i++) {
		c[i].x = curve[i].x - ND_coord_i(n).x;
		c[i].y = curve[i].y - ND_coord_i(n).y;
	}

	if (left_inside)
		left = NULL, right = seg;
	else
		left = seg, right = NULL;

	found = FALSE;
	low = 0.0; high = 1.0;
	if (left_inside)
		pt = c[0];
	else
		pt = c[3];
	do {
		opt = pt;
		t = (high + low) / 2.0;
		pt = Bezier (c, 3, t, left, right);
		inside = ND_shape(n)->insidefn (n, pt, e);
		if (inside == FALSE) {
			for (i = 0; i < 4; i++)
				best[i] = seg[i];
			found = TRUE;
		}
		if (inside == left_inside)
			low = t;
		else
			high = t;
	} while (ABS (opt.x - pt.x) > .5 || ABS (opt.y - pt.y) > .5);

	if (found == FALSE)
		for (i = 0; i < 4; i++)
			best[i] = seg[i];

	for (i = 0; i < 4; i++) {
		curve[i].x = ROUND(best[i].x + ND_coord_i(n).x);
		curve[i].y = ROUND(best[i].y + ND_coord_i(n).y);
	}
	ND_rw_i(n) = save_real_size;
}

/* shape_clip:
 * Clip Bezier to node shape
 * Uses curve[0] to determine which side is inside the node. 
 * NOTE: This test is bad. It is possible for previous call to
 * shape_clip to produce a Bezier with curve[0] moved to the boundary
 * for which insidefn(curve[0]) is true. Thus, if the new Bezier is
 * fed back to shape_clip, it will again assume left_inside is true.
 * To be safe, shape_clip0 should guarantee that the computed boundary
 * point fails insidefn.
 */
void 
shape_clip (node_t* n, point curve[4], edge_t* e)
{
	int save_real_size;
	boolean left_inside;
	pointf c;
	
	if (ND_shape(n) == NULL) return;
	if (ND_shape(n)->insidefn == NULL) return;

	save_real_size = ND_rw_i(n);
	c.x = curve[0].x - ND_coord_i(n).x;
	c.y = curve[0].y - ND_coord_i(n).y;
	left_inside = ND_shape(n)->insidefn (n, c, e);
	ND_rw_i(n) = save_real_size;
	shape_clip0 (n, curve, e, left_inside);
}

/* new_spline:
 * Create and attach a new bezier of size sz to the edge d
 */
bezier*
new_spline (edge_t* e, int sz)
{
	bezier *rv;

	while (ED_edge_type(e) != NORMAL)
		e = ED_to_orig(e);
	if (ED_spl(e) == NULL)
		ED_spl(e) = NEW (splines);
	ED_spl(e)->list = ALLOC (ED_spl(e)->size + 1, ED_spl(e)->list, bezier);
	rv = &(ED_spl(e)->list[ED_spl(e)->size++]);
	rv->list = N_NEW (sz, point);
	rv->size = sz;
	rv->sflag = rv->eflag = FALSE;
	return rv;
}

/* update_bb:
 * Update the bounding box of g based on the addition of
 * point p.
 */
static void 
update_bb(graph_t* g, point pt)
{
	if (pt.x > GD_bb(g).UR.x)  GD_bb(g).UR.x = pt.x;
	if (pt.y > GD_bb(g).UR.y)  GD_bb(g).UR.y = pt.y;
	if (pt.x < GD_bb(g).LL.x)  GD_bb(g).LL.x = pt.x;
	if (pt.y < GD_bb(g).LL.y)  GD_bb(g).LL.y = pt.y;
}

/* clip_and_install:
 * Given a raw spline (pn control points in ps), representing
 * a path from edge fe ending in edge le, clip the ends to
 * the node boundaries and attach the resulting spline to the
 * edge.
 */
void 
clip_and_install (edge_t* fe, edge_t* le, point* ps, int pn, splineInfo* info)
{
	pointf   p2;
	bezier *newspl;
	node_t *tn, *hn;
	int start, end, i;
	graph_t	*g;
	edge_t	*orig;

	tn = fe->tail; hn = le->head; g = tn->graph;
	newspl = new_spline (fe, pn);

	for (orig = fe; ED_edge_type(orig) != NORMAL; orig = ED_to_orig(orig));

		/* may be a reversed flat edge */
	if ((tn->u.rank == hn->u.rank) && (tn->u.order > hn->u.order))
		{node_t *tmp; tmp = hn; hn = tn; tn = tmp;}

		/* spline may be interior to node */
	if (wantclip(orig,tn) && ND_shape(tn) && ND_shape(tn)->insidefn) {
		for (start=0; start < pn - 4; start+=3) {
			p2.x = ps[start+3].x - ND_coord_i(tn).x;
			p2.y = ps[start+3].y - ND_coord_i(tn).y;
			if (ND_shape(tn)->insidefn (tn, p2, fe) == FALSE)
				break;
		}
		shape_clip0 (tn, &ps[start], fe, TRUE);
	} else start = 0;
	if (wantclip(orig,hn) && ND_shape(hn) && ND_shape(hn)->insidefn) {
		for (end = pn - 4; end > 0; end -= 3) {
			p2.x = ps[end].x - ND_coord_i(hn).x;
			p2.y = ps[end].y - ND_coord_i(hn).y;
			if (ND_shape(hn)->insidefn (hn, p2, le) == FALSE)
				break;
		}
		shape_clip0 (hn, &ps[end], le, FALSE);
	} else end = pn - 4;
	for (; start < pn - 4; start+=3)
		if (ps[start].x != ps[start + 3].x || ps[start].y != ps[start + 3].y)
			break;
	for (; end > 0; end -= 3)
		if (ps[end].x != ps[end + 3].x || ps[end].y != ps[end + 3].y)
			break;
	arrow_clip (fe, le, ps, &start, &end, newspl, info);
	for (i = start; i < end + 4; i++) {
		point		pt;
		pt = newspl->list[i - start] = ps[i];
		update_bb(g,pt);
	}
	newspl->size = end - start + 4;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.