blob: 83769c79a6a5156e76e62ca38d7cdf03f3da2307 [file] [log] [blame]
/*
* Twin - A Tiny Window System
* Copyright © 2004 Keith Packard <keithp@keithp.com>
* All rights reserved.
*
* This Library 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 Library 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 the Gnome Library; see the file COPYING.LIB. If not,
* write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "twinint.h"
/*
* Find the point in path which is furthest left of the line
*/
static int
_twin_path_leftpoint (twin_path_t *path,
twin_spoint_t *p1,
twin_spoint_t *p2)
{
twin_spoint_t *points = path->points;
int p;
int best = 0;
/*
* Normal form of the line is Ax + By + C = 0,
* these are the A and B factors. As we're just comparing
* across x and y, the value of C isn't relevant
*/
twin_dfixed_t Ap = p2->y - p1->y;
twin_dfixed_t Bp = p1->x - p2->x;
twin_dfixed_t max = -0x7fffffff;
for (p = 0; p < path->npoints; p++)
{
twin_dfixed_t vp = Ap * points[p].x + Bp * points[p].y;
if (vp > max)
{
max = vp;
best = p;
}
}
return best;
}
static int
_around_order (twin_spoint_t *a1,
twin_spoint_t *a2,
twin_spoint_t *b1,
twin_spoint_t *b2)
{
twin_dfixed_t adx = (a2->x - a1->x);
twin_dfixed_t ady = (a2->y - a1->y);
twin_dfixed_t bdx = (b2->x - b1->x);
twin_dfixed_t bdy = (b2->y - b1->y);
twin_dfixed_t diff = (ady * bdx - bdy * adx);
if (diff < 0) return -1;
if (diff > 0) return 1;
return 0;
}
#define F(x) twin_sfixed_to_double(x)
#if 0
#include <stdio.h>
#include <math.h>
#define DBGOUT(x...) printf(x)
static double
_angle (twin_spoint_t *a, twin_spoint_t *b)
{
twin_sfixed_t dx = b->x - a->x;
twin_sfixed_t dy = b->y - a->y;
double rad;
rad = atan2 ((double) dy, (double) dx);
return rad * 180 / M_PI;
}
#else
#define DBGOUT(x...)
#endif
#define A(a) ((a) < 0 ? -(a) : (a))
/*
* Convolve one subpath with a convex pen. The result is
* a closed path.
*/
static void
_twin_subpath_convolve (twin_path_t *path,
twin_path_t *stroke,
twin_path_t *pen)
{
twin_spoint_t *sp = stroke->points;
twin_spoint_t *pp = pen->points;
int ns = stroke->npoints;
int np = pen->npoints;
twin_spoint_t *sp0 = &sp[0];
twin_spoint_t *sp1 = &sp[1];
int start = _twin_path_leftpoint (pen, sp0, sp1);
twin_spoint_t *spn1 = &sp[ns-1];
twin_spoint_t *spn2 = &sp[ns-2];
int ret = _twin_path_leftpoint (pen, spn1, spn2);
int p;
int s;
int starget;
int ptarget;
int inc;
int first;
DBGOUT ("convolve stroke:\n");
for (s = 0; s < ns; s++)
DBGOUT ("\ts%02d: %9.4f, %9.4f\n", s, F(sp[s].x), F(sp[s].y));
DBGOUT ("convolve pen:\n");
for (p = 0; p < np; p++)
DBGOUT ("\tp%02d: %9.4f, %9.4f\n", p, F(pp[p].x), F(pp[p].y));
s = 0;
p = start;
DBGOUT ("start: ");
DBGOUT ("s%02d (%9.4f, %9.4f), p%02d (%9.4f, %9.4f): %9.4f, %9.4f\n",
s, F(sp[s].x), F(sp[s].y),
p, F(pp[p].x), F(pp[p].y),
F(sp[s].x + pp[p].x), F(sp[s].y + pp[p].y));
_twin_path_smove (path, sp[s].x + pp[p].x, sp[s].y + pp[p].y);
first = path->npoints - 1;
/* step along the path first */
inc = 1;
starget = ns-1;
ptarget = ret;
for (;;)
{
/*
* Convolve the edges
*/
do
{
int sn = s + inc;
int pn = (p == np - 1) ? 0 : p + 1;
int pm = (p == 0) ? np - 1 : p - 1;
/*
* step along pen (forwards or backwards) or stroke as appropriate
*/
DBGOUT ("\tangles: stroke %9.4f +pen %9.4f -pen %9.4f\n",
_angle (&sp[s], &sp[sn]),
_angle (&pp[p], &pp[pn]),
_angle (&pp[pm], &pp[p]));
if (_around_order (&sp[s],&sp[sn],&pp[p],&pp[pn]) > 0)
{
DBGOUT ("+pen: ");
p = pn;
}
else if (_around_order (&sp[s],&sp[sn],&pp[pm],&pp[p]) < 0)
{
DBGOUT ("-pen: ");
p = pm;
}
else
{
DBGOUT ("stroke: ");
s = sn;
}
DBGOUT ("s%02d (%9.4f, %9.4f), p%02d (%9.4f, %9.4f): %9.4f, %9.4f\n",
s, F(sp[s].x), F(sp[s].y),
p, F(pp[p].x), F(pp[p].y),
F(sp[s].x + pp[p].x), F(sp[s].y + pp[p].y));
_twin_path_sdraw (path, sp[s].x + pp[p].x, sp[s].y + pp[p].y);
} while (s != starget);
/*
* Finish this edge
*/
/* draw a cap */
switch (path->state.cap_style) {
int pm;
case TwinCapProjecting:
/*
* This draws a rough projecting cap using the
* pen.
*
* First, project the line forward one pen radius
* by finding the pen location halfway between the
* two normals.
*
* Then, just add that to the normals themselves to
* find the corners of the projecting cap.
*
* The result may have significant error, so overwrite
* the existing corners with the new coordinates to
* avoid a kink.
*/
if (p <= ptarget)
pm = (ptarget + p) >> 1;
else
{
pm = (ptarget + np + p) >> 1;
if (pm >= np) pm -= np;
}
/* replace last point with corner of cap */
path->npoints--;
_twin_path_sdraw (path,
sp[s].x + pp[pm].x + pp[p].x,
sp[s].y + pp[pm].y + pp[p].y);
p = ptarget;
if (inc == 1)
{
/* start next line at cap corner */
_twin_path_sdraw (path,
sp[s].x + pp[pm].x + pp[p].x,
sp[s].y + pp[pm].y + pp[p].y);
}
else
{
/* overwrite initial point */
path->points[first].x = sp[s].x + pp[pm].x + pp[p].x;
path->points[first].y = sp[s].y + pp[pm].y + pp[p].y;
}
break;
case TwinCapButt:
p = ptarget-1;
/* fall through … */
case TwinCapRound:
while (p != ptarget)
{
if (++p == np) p = 0;
DBGOUT("cap: ");
DBGOUT ("s%02d (%9.4f, %9.4f), p%02d (%9.4f, %9.4f): %9.4f, %9.4f\n",
s, F(sp[s].x), F(sp[s].y),
p, F(pp[p].x), F(pp[p].y),
F(sp[s].x + pp[p].x), F(sp[s].y + pp[p].y));
_twin_path_sdraw (path, sp[s].x + pp[p].x, sp[s].y + pp[p].y);
}
break;
}
if (inc == -1)
break;
/* reach the end of the path? Go back the other way now */
inc = -1;
ptarget = start;
starget = 0;
}
twin_path_close (path);
}
void
twin_path_convolve (twin_path_t *path,
twin_path_t *stroke,
twin_path_t *pen)
{
int p;
int s;
twin_path_t *hull = twin_path_convex_hull (pen);
p = 0;
for (s = 0; s <= stroke->nsublen; s++)
{
int sublen;
int npoints;
if (s == stroke->nsublen)
sublen = stroke->npoints;
else
sublen = stroke->sublen[s];
npoints = sublen - p;
if (npoints > 1)
{
twin_path_t subpath;
subpath.points = stroke->points + p;
subpath.npoints = npoints;
subpath.sublen = 0;
subpath.nsublen = 0;
_twin_subpath_convolve (path, &subpath, hull);
p = sublen;
}
}
twin_path_destroy (hull);
}