/*
 * FIG : Facility for Interactive Generation of figures
 * Copyright (c) 1985-1988 by Supoj Sutanthavibul
 * Parts Copyright (c) 1989-2015 by Brian V. Smith
 * Parts Copyright (c) 1991 by Paul King
 * Parts Copyright (c) 2016-2024 by Thomas Loimer
 *
 * Any party obtaining a copy of these files is granted, free of charge, a
 * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
 * nonexclusive right and license to deal in this software and documentation
 * files (the "Software"), including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense and/or sell copies of
 * the Software, and to permit persons who receive copies from any such
 * party to do so, with the only requirement being that the above copyright
 * and this permission notice remain intact.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "e_scale.h"

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include <X11/Xlib.h>

#include "resources.h"
#include "mode.h"
#include "object.h"
#include "paintop.h"

#include "e_movept.h"
#include "e_rotate.h"
#include "f_util.h"
#include "u_bound.h"
#include "u_create.h"
#include "u_draw.h"
#include "u_elastic.h"
#include "u_fonts.h"
#include "u_geom.h"
#include "u_list.h"
#include "u_markers.h"
#include "u_redraw.h"
#include "u_search.h"
#include "u_undo.h"
#include "w_canvas.h"
#include "w_cursor.h"
#include "w_mousefun.h"
#include "w_msgpanel.h"
#include "xfig_math.h"

static Boolean	init_boxscale_ellipse(int x, int y);
static Boolean	init_boxscale_line(int x, int y);
static Boolean	init_boxscale_compound(int x, int y);
static Boolean	init_scale_arc(void);
static Boolean	init_scale_ellipse(void);
static Boolean	init_scale_line(void);
static Boolean	init_scale_spline(void);

static void	scale_line(F_line *l, float sx, float sy, int refx, int refy);
static void	scale_spline(F_spline *s, float sx, float sy, int refx,int refy);
static void	scale_arc(F_arc *a, float sx, float sy, int refx, int refy);
static void	scale_ellipse(F_ellipse *e, float sx,float sy,int refx,int refy);
static void	scale_text(F_text *t, float sx, float sy, int refx, int refy);
static void	scale_arrows(F_line *obj, float sx, float sy);
static void	scale_arrow(F_arrow *arrow, float sx, float sy);

static void	init_box_scale(F_line *obj, int type, int x,int y,int px,int py);
static void	boxrelocate_ellipsepoint(F_ellipse *ellipse, int x, int y);
static void	init_center_scale(F_line *obj, int type, int x, int y,
				int px, int py);
static void	init_scale_compound(void);
static void	rescale_points(F_line *obj, int x, int y);
static void	relocate_ellipsepoint(F_ellipse *ellipse, int x, int y);
static void	relocate_arcpoint(F_arc *arc, int x, int y);

static void	fix_scale_arc(int x, int y);
static void	fix_scale_spline(int x, int y);
static void	fix_scale_line(int x, int y);
static void	fix_scale_ellipse(int x, int y);
static void	fix_boxscale_ellipse(int x, int y);
static void	fix_boxscale_line(int x, int y);
static void	fix_scale_compound(int x, int y);
static void	fix_boxscale_compound(int x, int y);

static void	cancel_scale_arc(void);
static void	cancel_scale_spline(void);
static void	cancel_scale_line(void);
static void	cancel_scale_ellipse(void);
static void	cancel_boxscale_ellipse(void);
static void	cancel_boxscale_line(void);
static void	cancel_scale_compound(void);
static void	cancel_boxscale_compound(void);
static void	prescale_compound(F_compound *c, int x, int y);


void
scale_selected(void)
{
    set_mousefun("scale box", "scale about center", "",
			LOC_OBJ, LOC_OBJ, LOC_OBJ);
    canvas_kbd_proc = null_proc;
    canvas_locmove_proc = null_proc;
    canvas_ref_proc = null_proc;
    init_searchproc_left(init_box_scale);
    init_searchproc_middle(init_center_scale);
    canvas_leftbut_proc = object_search_left;
    canvas_middlebut_proc = object_search_middle;
    canvas_rightbut_proc = null_proc;
    set_cursor(pick15_cursor);
    reset_action_on();
}

char *BOX_SCL_MSG = "Can't use box scale on selected object";
char *BOX_SCL2_MSG = "Can't use box scale on selected object; try putting it into a compound";

static void
init_box_scale(F_line *obj, int type, int x, int y, int px, int py)
{
	(void)x;
	(void)y;

    switch (type) {
    case O_POLYLINE:
	cur_l = (F_line *) obj;
	if (!init_boxscale_line(px, py))	/* non-box line */
	    return;
	break;
    case O_ELLIPSE:
	cur_e = (F_ellipse *) obj;
	if (!init_boxscale_ellipse(px, py))	/* selected center, ignore */
	    return;
	break;
    case O_COMPOUND:
	cur_c = (F_compound *) obj;
	if (!init_boxscale_compound(px, py))	/* non-box compound */
	    return;
	break;
    default:
	put_msg(BOX_SCL2_MSG);
	beep();
	return;
    }
    set_mousefun("new posn", "", "cancel", LOC_OBJ, LOC_OBJ, LOC_OBJ);
    draw_mousefun_canvas();
    canvas_middlebut_proc = null_proc;
}

static void
init_center_scale(F_line *obj, int type, int x, int y, int px, int py)
{
	(void)x;
	(void)y;
    double	    dx, dy, l;

    cur_x = from_x = px;
    cur_y = from_y = py;
    constrained = BOX_SCALE;
    switch (type) {
    case O_POLYLINE:
	cur_l = (F_line *) obj;
	if (!init_scale_line())		/* selected center */
	    return;
	break;
    case O_SPLINE:
	cur_s = (F_spline *) obj;
	if (!init_scale_spline())	/* selected center */
	    return;
	break;
    case O_ELLIPSE:
	cur_e = (F_ellipse *) obj;
	if (!init_scale_ellipse())	/* selected center */
	    return;
	break;
    case O_ARC:
	cur_a = (F_arc *) obj;
	if (!init_scale_arc())		/* selected center */
	    return;
	break;
    case O_COMPOUND:
	cur_c = (F_compound *) obj;
	init_scale_compound();
	break;
    }

    dx = (double) (from_x - fix_x);
    dy = (double) (from_y - fix_y);
    l = sqrt(dx * dx + dy * dy);
    cosa = fabs(dx / l);
    sina = fabs(dy / l);

    set_mousefun("", "new posn", "cancel", LOC_OBJ, LOC_OBJ, LOC_OBJ);
    draw_mousefun_canvas();
    canvas_leftbut_proc = null_proc;
}

static void
wrapup_scale(void)
{
    reset_action_on();
    scale_selected();
    draw_mousefun_canvas();
}

/*************************  ellipse  *******************************/

static Boolean
init_boxscale_ellipse(int x, int y)
{
    double	    dx, dy, l;

    if (cur_e->type == T_ELLIPSE_BY_RAD ||
	cur_e->type == T_CIRCLE_BY_RAD) {
	if (x == cur_e->start.x && y == cur_e->start.y) {
	    put_msg("Center point selected, ignored");
	    return False;
	} else {
	    fix_x = cur_e->center.x - (x - cur_e->center.x);
	    fix_y = cur_e->center.y - (y - cur_e->center.y);
	}
    } else {
	if (x == cur_e->start.x && y == cur_e->start.y) {
	    fix_x = cur_e->end.x;
	    fix_y = cur_e->end.y;
	} else {
	    fix_x = cur_e->start.x;
	    fix_y = cur_e->start.y;
	}
    }
    cur_x = from_x = x;
    cur_y = from_y = y;
    cur_angle = cur_e->angle;

    if (cur_x == fix_x || cur_y == fix_y) {
	put_msg(BOX_SCL2_MSG);
	beep();
	return False;
    }
    set_action_on();
    toggle_ellipsemarker(cur_e);
    constrained = BOX_SCALE;
    dx = (double) (cur_x - fix_x);
    dy = (double) (cur_y - fix_y);
    l = sqrt(dx * dx + dy * dy);
    cosa = fabs(dx / l);
    sina = fabs(dy / l);

    set_cursor(crosshair_cursor);
    if ((cur_e->type == T_CIRCLE_BY_DIA) ||
	(cur_e->type == T_CIRCLE_BY_RAD)) {
	canvas_locmove_proc = constrained_resizing_cbd;
	canvas_ref_proc = elastic_cbd;
	elastic_cbd();
    } else {
	canvas_locmove_proc = constrained_resizing_ebd;
	canvas_ref_proc = elastic_ebd;
	elastic_ebd();
    }
    canvas_leftbut_proc = fix_boxscale_ellipse;
    canvas_rightbut_proc = cancel_boxscale_ellipse;
    return True;
}

static void
cancel_boxscale_ellipse(void)
{
    canvas_ref_proc = canvas_locmove_proc = null_proc;
    if ((cur_e->type == T_CIRCLE_BY_DIA) ||
	(cur_e->type == T_CIRCLE_BY_RAD))
	elastic_cbd();
    else
	elastic_ebd();
    toggle_ellipsemarker(cur_e);
    wrapup_scale();
}

static void
fix_boxscale_ellipse(int x, int y)
{
    if ((cur_e->type == T_CIRCLE_BY_DIA) ||
	(cur_e->type == T_CIRCLE_BY_RAD))
	elastic_cbd();
    else
	elastic_ebd();
    adjust_box_pos(x, y, from_x, from_y, &cur_x, &cur_y);
    new_e = copy_ellipse(cur_e);
    boxrelocate_ellipsepoint(new_e, cur_x, cur_y);
    /* find how much the radii changed */
    /* dx = 1.0 * new_e->radiuses.x / cur_e->radiuses.x;
       dy = 1.0 * new_e->radiuses.y / cur_e->radiuses.y; */
    change_ellipse(cur_e, new_e);
    wrapup_scale();
    /* redraw anything under the old ellipse */
    redisplay_ellipse(cur_e);
    /* and the new one */
    redisplay_ellipse(new_e);
}

static void
boxrelocate_ellipsepoint(F_ellipse *ellipse, int x, int y)
{
    double	    dx, dy;

    set_temp_cursor(wait_cursor);
    draw_ellipse(ellipse, ERASE);
    if ((ellipse->type == T_CIRCLE_BY_RAD) ||
	(ellipse->type == T_ELLIPSE_BY_RAD)) {
	ellipse->end.x = x;
	ellipse->end.y = y;
    } else {
	if (ellipse->start.x == fix_x)
	    ellipse->end.x = x;
	if (ellipse->start.y == fix_y)
	    ellipse->end.y = y;
	if (ellipse->end.x == fix_x)
	    ellipse->start.x = x;
	if (ellipse->end.y == fix_y)
	    ellipse->start.y = y;
    }
    if ((ellipse->type == T_CIRCLE_BY_DIA) ||
	(ellipse->type == T_CIRCLE_BY_RAD)) {
	dx = ellipse->center.x = round((fix_x + x) / 2);
	dy = ellipse->center.y = round((fix_y + y) / 2);
	dx -= x;
	dy -= y;
	ellipse->radiuses.x = round(sqrt(dx * dx + dy * dy));
	ellipse->radiuses.y = ellipse->radiuses.x;
    } else {
	ellipse->center.x = (fix_x + x) / 2;
	ellipse->center.y = (fix_y + y) / 2;
	ellipse->radiuses.x = abs(ellipse->center.x - fix_x);
	ellipse->radiuses.y = abs(ellipse->center.y - fix_y);
    }
    if ((ellipse->type == T_CIRCLE_BY_RAD) ||
	(ellipse->type == T_ELLIPSE_BY_RAD)) {
	ellipse->start.x = ellipse->center.x;
	ellipse->start.y = ellipse->center.y;
    }
    reset_cursor();
}

static Boolean
init_scale_ellipse(void)
{
    fix_x = cur_e->center.x;
    fix_y = cur_e->center.y;
    cur_angle = cur_e->angle;
    if (from_x == fix_x && from_y == fix_y) {
	put_msg("Center point selected, ignored");
	return False;
    }
    set_action_on();
    toggle_ellipsemarker(cur_e);
    set_cursor(crosshair_cursor);
    canvas_locmove_proc = scaling_ellipse;
    canvas_ref_proc = elastic_scale_curellipse;
    elastic_scaleellipse(cur_e);
    canvas_middlebut_proc = fix_scale_ellipse;
    canvas_rightbut_proc = cancel_scale_ellipse;
    if (cur_e->type == T_ELLIPSE_BY_RAD)
	length_msg(MSG_RADIUS2);
    else if (cur_e->type == T_CIRCLE_BY_RAD)
	length_msg(MSG_RADIUS);
    else
	length_msg(MSG_DIAM);
    return True;		/* all is Ok */
}

static void
cancel_scale_ellipse(void)
{
    canvas_ref_proc = canvas_locmove_proc = null_proc;
    elastic_scaleellipse(cur_e);
    toggle_ellipsemarker(cur_e);
    wrapup_scale();
}

static void
fix_scale_ellipse(int x, int y)
{
    elastic_scaleellipse(cur_e);
    adjust_box_pos(x, y, from_x, from_y, &cur_x, &cur_y);
    new_e = copy_ellipse(cur_e);
    relocate_ellipsepoint(new_e, cur_x, cur_y);
    change_ellipse(cur_e, new_e);
    wrapup_scale();
    /* redraw anything under the old ellipse */
    redisplay_ellipse(cur_e);
    /* and the new one */
    redisplay_ellipse(new_e);
}

static void
relocate_ellipsepoint(F_ellipse *ellipse, int x, int y)
{
    double	    newx, newy, oldx, oldy;
    double	    newd, oldd, scalefact;

    set_temp_cursor(wait_cursor);
    draw_ellipse(ellipse, ERASE);

    newx = x - fix_x;
    newy = y - fix_y;
    newd = sqrt(newx * newx + newy * newy);
    oldx = from_x - fix_x;
    oldy = from_y - fix_y;
    oldd = sqrt(oldx * oldx + oldy * oldy);
    scalefact = newd / oldd;

    ellipse->radiuses.x = round(ellipse->radiuses.x * scalefact);
    ellipse->radiuses.y = round(ellipse->radiuses.y * scalefact);
    ellipse->end.x = fix_x + round((ellipse->end.x - fix_x) * scalefact);
    ellipse->end.y = fix_y + round((ellipse->end.y - fix_y) * scalefact);
    ellipse->start.x = fix_x + round((ellipse->start.x - fix_x) * scalefact);
    ellipse->start.y = fix_y + round((ellipse->start.y - fix_y) * scalefact);
    reset_cursor();
}

/***************************  arc  *********************************/

static Boolean
init_scale_arc(void)
{
    fix_x = cur_a->center.x;
    fix_y = cur_a->center.y;
    if (from_x == fix_x && from_y == fix_y) {
	put_msg("Center point selected, ignored");
	return False;
    }
    set_action_on();
    toggle_arcmarker(cur_a);
    elastic_scalearc(cur_a);
    set_cursor(crosshair_cursor);
    canvas_locmove_proc = scaling_arc;
    canvas_ref_proc = elastic_scale_curarc;
    canvas_middlebut_proc = fix_scale_arc;
    canvas_rightbut_proc = cancel_scale_arc;
    return True;
}

static void
cancel_scale_arc(void)
{
    canvas_ref_proc = canvas_locmove_proc = null_proc;
    elastic_scalearc(cur_a);
    /* erase last lengths if appres.showlengths is true */
    erase_lengths();
    toggle_arcmarker(cur_a);
    wrapup_scale();
}

static void
fix_scale_arc(int x, int y)
{
    double	    newx, newy, oldx, oldy;
    double	    newd, oldd, scalefact;

    elastic_scalearc(cur_a);

    adjust_box_pos(x, y, from_x, from_y, &x, &y);
    new_a = copy_arc(cur_a);

    newx = cur_x - fix_x;
    newy = cur_y - fix_y;
    newd = sqrt(newx * newx + newy * newy);
    oldx = from_x - fix_x;
    oldy = from_y - fix_y;
    oldd = sqrt(oldx * oldx + oldy * oldy);
    scalefact = newd / oldd;
    /* scale any arrowheads */
    scale_arrows((F_line *)new_a,scalefact,scalefact);

    relocate_arcpoint(new_a, x, y);
    change_arc(cur_a, new_a);
    wrapup_scale();
    /* redraw anything under the old arc */
    redisplay_arc(cur_a);
    /* and the new one */
    redisplay_arc(new_a);
}

static void
relocate_arcpoint(F_arc *arc, int x, int y)
{
    double	    newx, newy, oldx, oldy;
    double	    newd, oldd, scalefact;
    float	    xx, yy;
    F_pos	    p0, p1, p2;

    newx = x - fix_x;
    newy = y - fix_y;
    newd = sqrt(newx * newx + newy * newy);

    oldx = from_x - fix_x;
    oldy = from_y - fix_y;
    oldd = sqrt(oldx * oldx + oldy * oldy);

    scalefact = newd / oldd;

    p0 = arc->point[0];
    p1 = arc->point[1];
    p2 = arc->point[2];
    p0.x = fix_x + round((p0.x - fix_x) * scalefact);
    p0.y = fix_y + round((p0.y - fix_y) * scalefact);
    p1.x = fix_x + round((p1.x - fix_x) * scalefact);
    p1.y = fix_y + round((p1.y - fix_y) * scalefact);
    p2.x = fix_x + round((p2.x - fix_x) * scalefact);
    p2.y = fix_y + round((p2.y - fix_y) * scalefact);
    if (compute_arccenter(p0, p1, p2, &xx, &yy)) {
	arc->point[0].x = p0.x;
	arc->point[0].y = p0.y;
	arc->point[1].x = p1.x;
	arc->point[1].y = p1.y;
	arc->point[2].x = p2.x;
	arc->point[2].y = p2.y;
	arc->center.x = xx;
	arc->center.y = yy;
	arc->direction = compute_direction(p0, p1, p2);
    }
    set_modifiedflag();
}

/**************************  spline  *******************************/

static Boolean
init_scale_spline(void)
{
    int		    sumx, sumy, cnt;
    F_point	   *p;

    p = cur_s->points;
    if (closed_spline(cur_s))
	p = p->next;
    for (sumx = 0, sumy = 0, cnt = 0; p != NULL; p = p->next) {
	sumx = sumx + p->x;
	sumy = sumy + p->y;
	cnt++;
    }
    fix_x = sumx / cnt;
    fix_y = sumy / cnt;
    if (from_x == fix_x && from_y == fix_y) {
	put_msg("Center point selected, ignored");
	return False;
    }
    set_action_on();
    set_cursor(crosshair_cursor);
    toggle_splinemarker(cur_s);
    elastic_scalepts(cur_s->points);
    canvas_locmove_proc = scaling_spline;
    canvas_ref_proc = elastic_scale_curspline;
    canvas_middlebut_proc = fix_scale_spline;
    canvas_rightbut_proc = cancel_scale_spline;
    return True;
}

static void
cancel_scale_spline(void)
{
    canvas_ref_proc = canvas_locmove_proc = null_proc;
    elastic_scalepts(cur_s->points);
    toggle_splinemarker(cur_s);
    wrapup_scale();
}

static void
fix_scale_spline(int x, int y)
{
    elastic_scalepts(cur_s->points);
    canvas_ref_proc = null_proc;
    adjust_box_pos(x, y, from_x, from_y, &x, &y);
    /* make a copy of the original and save as unchanged object */
    old_s = copy_spline(cur_s);
    clean_up();
    set_latestspline(old_s);
    set_action_object(F_EDIT, O_SPLINE);
    old_s->next = cur_s;
    /* now change the original to become the new object */
    rescale_points((F_line *)cur_s, x, y);
    wrapup_scale();
    /* redraw anything under the old spline */
    redisplay_spline(old_s);
    /* and the new one */
    redisplay_spline(cur_s);
}

/***************************  compound	********************************/

static Boolean
init_boxscale_compound(int x, int y)
{
    int		    xmin, ymin, xmax, ymax;
    double	    dx, dy, l;

    xmin = min2(cur_c->secorner.x, cur_c->nwcorner.x);
    ymin = min2(cur_c->secorner.y, cur_c->nwcorner.y);
    xmax = max2(cur_c->secorner.x, cur_c->nwcorner.x);
    ymax = max2(cur_c->secorner.y, cur_c->nwcorner.y);

    if (xmin == xmax || ymin == ymax) {
	put_msg(BOX_SCL_MSG);
	beep();
	return False;
    }
    set_action_on();
    toggle_compoundmarker(cur_c);
    set_cursor(crosshair_cursor);

    if (x == xmin) {
	fix_x = xmax;
	from_x = x;
	if (y == ymin) {
	    fix_y = ymax;
	    from_y = y;
	    constrained = BOX_SCALE;
	} else if (y == ymax) {
	    fix_y = ymin;
	    from_y = y;
	    constrained = BOX_SCALE;
	} else {
	    fix_y = ymax;
	    from_y = ymin;
	    constrained = BOX_HSTRETCH;
	}
    } else if (x == xmax) {
	fix_x = xmin;
	from_x = x;
	if (y == ymin) {
	    fix_y = ymax;
	    from_y = y;
	    constrained = BOX_SCALE;
	} else if (y == ymax) {
	    fix_y = ymin;
	    from_y = y;
	    constrained = BOX_SCALE;
	} else {
	    fix_y = ymax;
	    from_y = ymin;
	    constrained = BOX_HSTRETCH;
	}
    } else {
	if (y == ymin) {
	    fix_y = ymax;
	    from_y = y;
	    fix_x = xmax;
	    from_x = xmin;
	    constrained = BOX_VSTRETCH;
	} else {		/* y == ymax */
	    fix_y = ymin;
	    from_y = y;
	    fix_x = xmax;
	    from_x = xmin;
	    constrained = BOX_VSTRETCH;
	}
    }

    cur_x = from_x;
    cur_y = from_y;

    if (constrained == BOX_SCALE) {
	dx = (double) (cur_x - fix_x);
	dy = (double) (cur_y - fix_y);
	l = sqrt(dx * dx + dy * dy);
	cosa = fabs(dx / l);
	sina = fabs(dy / l);
    }
    boxsize_scale_msg(1);
    elastic_box(fix_x, fix_y, cur_x, cur_y);
    canvas_locmove_proc = constrained_resizing_scale_box;
    canvas_ref_proc = elastic_fixedbox;
    canvas_leftbut_proc = fix_boxscale_compound;
    canvas_rightbut_proc = cancel_boxscale_compound;
    return True;
}

static void
cancel_boxscale_compound(void)
{
    canvas_ref_proc = canvas_locmove_proc = null_proc;
    elastic_box(fix_x, fix_y, cur_x, cur_y);
    /* erase last lengths if appres.showlengths is true */
    erase_lengths();
    toggle_compoundmarker(cur_c);
    wrapup_scale();
}

static void
fix_boxscale_compound(int x, int y)
{
    double	scalex, scaley;

    elastic_box(fix_x, fix_y, cur_x, cur_y);
    /* erase last lengths if appres.showlengths is true */
    erase_lengths();
    adjust_box_pos(x, y, from_x, from_y, &x, &y);
    new_c = copy_compound(cur_c);
    scalex = (double) (x - fix_x) / (from_x - fix_x);
    scaley = (double) (y - fix_y) / (from_y - fix_y);
    scale_compound(new_c, scalex, scaley, fix_x, fix_y);
    change_compound(cur_c, new_c);
    wrapup_scale();
    /* redraw anything under the old compound */
    redisplay_compound(cur_c);
    /* and the new one */
    redisplay_compound(new_c);
}

static void
init_scale_compound(void)
{
    fix_x = (cur_c->nwcorner.x + cur_c->secorner.x) / 2;
    fix_y = (cur_c->nwcorner.y + cur_c->secorner.y) / 2;
    set_action_on();
    toggle_compoundmarker(cur_c);
    set_cursor(crosshair_cursor);
    elastic_scalecompound(cur_c);
    canvas_locmove_proc = scaling_compound;
    canvas_ref_proc = elastic_scale_curcompound;
    canvas_middlebut_proc = fix_scale_compound;
    canvas_rightbut_proc = cancel_scale_compound;
}

static void
cancel_scale_compound(void)
{
    canvas_ref_proc = canvas_locmove_proc = null_proc;
    elastic_scalecompound(cur_c);
    /* erase last lengths if appres.showlengths is true */
    erase_lengths();
    toggle_compoundmarker(cur_c);
    wrapup_scale();
}

static void
fix_scale_compound(int x, int y)
{
    elastic_scalecompound(cur_c);
    /* erase last lengths if appres.showlengths is true */
    erase_lengths();
    adjust_box_pos(x, y, from_x, from_y, &cur_x, &cur_y);
    /* make a copy of the original and save as unchanged object */
    old_c = copy_compound(cur_c);
    clean_up();
    set_latestcompound(old_c);
    set_action_object(F_EDIT, O_COMPOUND);
    old_c->next = cur_c;
    /* now change the original to become the new object */
    prescale_compound(cur_c, cur_x, cur_y);
    wrapup_scale();
    /* redraw anything under the old compound */
    redisplay_compound(old_c);
    /* and the new one */
    redisplay_compound(cur_c);
}

static void
prescale_compound(F_compound *c, int x, int y)
{
    double	    newx, newy, oldx, oldy;
    double	    newd, oldd, scalefact;

    newx = x - fix_x;
    newy = y - fix_y;
    newd = sqrt(newx * newx + newy * newy);
    oldx = from_x - fix_x;
    oldy = from_y - fix_y;
    oldd = sqrt(oldx * oldx + oldy * oldy);
    scalefact = newd / oldd;
    scale_compound(c, scalefact, scalefact, fix_x, fix_y);
}

void
scale_compound(F_compound *c, double sx, double sy, int refx, int refy)
{
    F_line	   *l;
    F_spline	   *s;
    F_ellipse	   *e;
    F_text	   *t;
    F_arc	   *a;
    F_compound	   *c1;
    int		    x1, y1, x2, y2;

    /* if sx and sy == 1.0, return now */
    if (sx == 0.0 && sy == 0.0)
	return;

    /* check if really a dimension line */
    if (rescale_dimension_line(c, sx, sy, refx, refy))
	return; /* yes, return now */

    x1 = round(refx + (c->nwcorner.x - refx) * sx);
    y1 = round(refy + (c->nwcorner.y - refy) * sy);
    x2 = round(refx + (c->secorner.x - refx) * sx);
    y2 = round(refy + (c->secorner.y - refy) * sy);
    c->nwcorner.x = min2(x1, x2);
    c->nwcorner.y = min2(y1, y2);
    c->secorner.x = max2(x1, x2);
    c->secorner.y = max2(y1, y2);

    for (l = c->lines; l != NULL; l = l->next) {
	scale_line(l, sx, sy, refx, refy);
    }
    for (s = c->splines; s != NULL; s = s->next) {
	scale_spline(s, sx, sy, refx, refy);
    }
    for (a = c->arcs; a != NULL; a = a->next) {
	scale_arc(a, sx, sy, refx, refy);
    }
    for (e = c->ellipses; e != NULL; e = e->next) {
	scale_ellipse(e, sx, sy, refx, refy);
    }
    for (t = c->texts; t != NULL; t = t->next) {
	scale_text(t, sx, sy, refx, refy);
    }
    for (c1 = c->compounds; c1 != NULL; c1 = c1->next) {
	scale_compound(c1, sx, sy, refx, refy);
	/* if there's a dimension line in this compound reset corners */
	c->nwcorner.x = min2(c->nwcorner.x, c1->nwcorner.x);
	c->nwcorner.y = min2(c->nwcorner.y, c1->nwcorner.y);
	c->secorner.x = max2(c->secorner.x, c1->secorner.x);
	c->secorner.y = max2(c->secorner.y, c1->secorner.y);
    }
}

Boolean
rescale_dimension_line(F_compound *dimline, float scalex, float scaley, int refx, int refy)
{
    F_line	   *line, *box, *tick1, *tick2;
    F_text	   *text;
    double	    length, dx, dy, angle;
    float	    save_rotnangle, save_rotn_dirn;
    int		    p1x, p1y, p2x, p2y, centerx, centery;
    int		    x1, x2, y1, y2, save;
    int		    tlen2, tht2;
    char	    str[80], comment[100];
    F_point	   *pnt;
    Boolean	    fixed_text;
    int		    save_lthick, save_t1thick, save_t2thick;

    /* locate the line components of the dimension line */
    if (!dimline_components(dimline, &line, &tick1, &tick2, &box))
	/* not a dimension line, return */
	return False;

    /* see if user has deleted main line */
    if (!line)
	return False;

    /* get the two endpoints and scale them */
    p1x = round(refx + (line->points->x - refx) * scalex);
    p1y = round(refy + (line->points->y - refy) * scaley);
    p2x = round(refx + (line->points->next->x - refx) * scalex);
    p2y = round(refy + (line->points->next->y - refy) * scaley);

    /* put them back */
    line->points->x = p1x;
    line->points->y = p1y;
    line->points->next->x = p2x;
    line->points->next->y = p2y;

    /* if drawn right to left or top to bottom at 90 degrees swap the two points */
    if (p1x > p2x || (p1y < p2y && p1x == p2x)) {
	save = p1x; p1x = p2x; p2x = save;
	save = p1y; p1y = p2y; p2y = save;
    }
    dx = p2x-p1x;
    dy = p2y-p1y;

    /* get the text component */
    text = dimline->texts;
    if (!text) {
	/* hmm, user must have deleted it */
	text = create_text();
	if (box)
	    text->depth  = box->depth-1;
	else
	    text->depth  = line->depth-2;
	add_depth(O_TXT, text->depth);
	text->cstring = (char *) NULL;	/* the string will be put in later */
	text->color = cur_dimline_textcolor;
	text->font = cur_dimline_font;
	text->size = cur_dimline_fontsize;
	text->flags = cur_dimline_psflag? PSFONT_TEXT: 0;
	text->pen_style = -1;
	text->type = T_CENTER_JUSTIFIED;
	dimline->texts = text;
    }
    fixed_text = text && text->comments && (strncasecmp(text->comments,"fixed text", 10)==0);
    /* length of the line */
    length = sqrt(dx*dx + dy*dy);

    if (!fixed_text) {
	/* make text with the length */
	make_dimension_string(length, str, False);

	/* put the new length in the comment */
	sprintf(comment, "Dimension line: %s",str);
	/* free any old comment */
	if (dimline->comments)
	    free(dimline->comments);
	/* put in the new one */
	dimline->comments = strdup(comment);
    }

    /* center the box for the length string */
    centerx = (p1x+p2x)/2;
    centery = (p1y+p2y)/2;
    /* angle of the line */
    angle = -atan2(dy, dx);
    while (angle < 0.0)
	    angle += M_2PI;

    /* recompute the text, text angle and text box */
    /* new string, but only if comment doesn't say "fixed text" */
    if (!fixed_text) {
	 /* free any old string */
	 if (text->cstring)
	    free(text->cstring);
	 text->cstring = strdup(str);
    }
    /* recalculate text sizes */
    text->angle = angle;
    if (text->xf_font)
	    font_close(text->xf_font);
    text->xf_font = font_open(psfont_text(text), text->font,
				    text->size * display_zoomscale, angle);
    /* Really, only text->length would be needed */
    font_setbbox_offset(text);
    font_ascentdescent(psfont_text(text), text->font, text->size, &y1, &y2);
    /* y1 = ascent, y2 = descent */
    text->base_x = centerx + round(-dy / length * y2);
    text->base_y = centery + round(dx / length * y2);

    /* half the text length + a margin */
    tlen2 = text->length / 2 + 60;
    /* half the height + a margin */
    tht2 = (y1 + y2) / 2 + 15;

    /* save global rotation angle and direction to use our own */
    save_rotnangle = act_rotnangle;
    save_rotn_dirn = rotn_dirn;
    act_rotnangle = -angle * 180.0 / M_PI;
    rotn_dirn = 1;

    /* now rescale the text box (the box) */
    if (box) {
	pnt = box->points;
	pnt->x = centerx-tlen2;
	pnt->y = centery-tht2;
	pnt = pnt->next;
	pnt->x = centerx-tlen2;
	pnt->y = centery+tht2;
	pnt = pnt->next;
	pnt->x = centerx+tlen2;
	pnt->y = centery+tht2;
	pnt = pnt->next;
	pnt->x = centerx+tlen2;
	pnt->y = centery-tht2;
	pnt = pnt->next;
	pnt->x = centerx-tlen2;
	pnt->y = centery-tht2;

	/* rotate the box around the center */
	rotate_line(box, centerx, centery);
    } /* if (box) */

    /* now recalculate the end ticks */
    if (tick1) {
	pnt = tick1->points;
	pnt->x = p1x;
	pnt->y = p1y-tht2;
	pnt = pnt->next;
	pnt->x = p1x;
	pnt->y = p1y+tht2;
	/* rotate the line around the endpoint */
	rotate_line(tick1, p1x, p1y);
    }

    /* the other tick */
    if (tick2) {
	pnt = tick2->points;
	pnt->x = p2x;
	pnt->y = p2y-tht2;
	pnt = pnt->next;
	pnt->x = p2x;
	pnt->y = p2y+tht2;
	/* rotate the line around the endpoint */
	rotate_line(tick2, p2x, p2y);
    }

    /* restore original rotation angle and direction */
    act_rotnangle = save_rotnangle;
    rotn_dirn = save_rotn_dirn;

    /* get the bounds of the compound */
    /* but set the thicknesses of the line and ticks to 0 so they aren't taken into account */
    save_lthick = line->thickness;
    line->thickness = 0;
    if (tick1) {
	save_t1thick = tick1->thickness;
	tick1->thickness = 0;
    }
    if (tick2) {
	save_t2thick = tick2->thickness;
	tick2->thickness = 0;
    }

    compound_bound(dimline, &x1, &y1, &x2, &y2);
    /* restore the thicknesses */
    line->thickness = save_lthick;
    if (tick1)
	tick1->thickness = save_t1thick;
    if (tick2)
	tick2->thickness = save_t2thick;

    dimline->nwcorner.x = x1;
    dimline->nwcorner.y = y1;
    dimline->secorner.x = x2;
    dimline->secorner.y = y2;

    return True;
}

static void
scale_line(F_line *l, float sx, float sy, int refx, int refy)
{
    F_point	   *p;

    for (p = l->points; p != NULL; p = p->next) {
	p->x = round(refx + (p->x - refx) * sx);
	p->y = round(refy + (p->y - refy) * sy);
    }
    /* now scale the radius for an arc-box */
    if (l->type == T_ARCBOX) {
	int h,w;
	/* scale by the average of height/width factor */
	l->radius = round(l->radius * (sx+sy)/2);
	/* if the radius is larger than half the width or height, set it to the
	   minimum of the width or heigth divided by 2 */
	w = abs(l->points->x-l->points->next->next->x);
	h = abs(l->points->y-l->points->next->next->y);
	if ((l->radius > w/2) || (l->radius > h/2))
		l->radius = min2(w,h)/2;
	/* finally, if it is 0, make it 1 */
	if (l->radius == 0)
		l->radius = 1;
    }
    /* finally, scale any arrowheads */
    scale_arrows(l,sx,sy);
}

static void
scale_spline(F_spline *s, float sx, float sy, int refx, int refy)
{
    F_point	   *p;

    for (p = s->points; p != NULL; p = p->next) {
	p->x = round(refx + (p->x - refx) * sx);
	p->y = round(refy + (p->y - refy) * sy);
    }
    /* scale any arrowheads */
    scale_arrows((F_line *)s,sx,sy);
}

static void
scale_arc(F_arc *a, float sx, float sy, int refx, int refy)
{
    int		    i;
    F_point	    p[3];

    for (i = 0; i < 3; i++) {
	/* save original points for co-linear check later */
	p[i].x = a->point[i].x;
	p[i].y = a->point[i].y;
	a->point[i].x = round(refx + (a->point[i].x - refx) * sx);
	a->point[i].y = round(refy + (a->point[i].y - refy) * sy);
    }
    if (compute_arccenter(a->point[0], a->point[1], a->point[2],
		          &a->center.x, &a->center.y) == 0) {
	/* the new arc is co-linear, move the middle point one pixel */
	if (a->point[0].x == a->point[1].x) { /* vertical, move middle left or right */
	    if (p[1].x > p[0].x)
		a->point[1].x++;	/* convex to the right -> ) */
	    else
		a->point[1].x--;	/* convex to the left -> ( */
	}
	/* check ALSO for horizontally co-linear in case all three points are equal */
	if (a->point[0].y == a->point[1].y) { /* horizontal, move middle point up or down */
	    if (p[1].y > p[0].y)
		a->point[1].y++;	/* curves up */
	    else
		a->point[1].y--;	/* curves down */
	}
	/* now check if the endpoints are equal, move one of them */
	if (a->point[0].x == a->point[2].x &&
	    a->point[0].y == a->point[2].y)
		a->point[2].x++;
	/* make up a center */
	a->center.x = a->point[1].x;
	a->center.y = a->point[1].y;
    }
    a->direction = compute_direction(a->point[0], a->point[1], a->point[2]);
    /* scale any arrowheads */
    scale_arrows((F_line *)a,sx,sy);
}

static void
scale_ellipse(F_ellipse *e, float sx, float sy, int refx, int refy)
{
    e->center.x = round(refx + (e->center.x - refx) * sx);
    e->center.y = round(refy + (e->center.y - refy) * sy);
    e->start.x = round(refx + (e->start.x - refx) * sx);
    e->start.y = round(refy + (e->start.y - refy) * sy);
    e->end.x = round(refx + (e->end.x - refx) * sx);
    e->end.y = round(refy + (e->end.y - refy) * sy);
    e->radiuses.x = abs(round(e->radiuses.x * sx));
    e->radiuses.y = abs(round(e->radiuses.y * sy));
    /* if this WAS a circle and is NOW an ellipse, change type to reflect */
    if (e->type == T_CIRCLE_BY_RAD || e->type == T_CIRCLE_BY_DIA) {
	if (e->radiuses.x != e->radiuses.y)
	    e->type -= 2;
    }
    /* conversely, if this WAS an ellipse and is NOW a circle, change type */
    else if (e->type == T_ELLIPSE_BY_RAD || e->type == T_ELLIPSE_BY_DIA) {
	if (e->radiuses.x == e->radiuses.y)
	    e->type += 2;
    }
}

static void
scale_text(F_text *t, float sx, float sy, int refx, int refy)
{
    int newsize;
    t->base_x = round(refx + (t->base_x - refx) * sx);
    t->base_y = round(refy + (t->base_y - refy) * sy);
    if (!rigid_text(t)) {
        newsize = t->size * sx;
	if (newsize < MIN_FONT_SIZE)
	    sx = (float) MIN_FONT_SIZE / t->size;
	else if (MAX_FONT_SIZE < newsize)
	    sx = (float) MAX_FONT_SIZE / t->size;
	t->size = t->size * sx;
	/* rescale font */
	font_reload(t);
	font_setbbox_offset(t);
    }
}


/***************************  line  ********************************/

static Boolean
init_boxscale_line(int x, int y)
{
    int		    xmin, ymin, xmax, ymax;
    F_point	   *p0, *p1, *p2;
    double	    dx, dy, l;

    if (cur_l->type != T_BOX &&
	cur_l->type != T_ARCBOX &&
	cur_l->type != T_PICTURE) {
	put_msg(BOX_SCL2_MSG);
	beep();
	return False;
    }
    p0 = cur_l->points;
    p1 = p0->next;
    p2 = p1->next;
    xmin = min3(p0->x, p1->x, p2->x);
    ymin = min3(p0->y, p1->y, p2->y);
    xmax = max3(p0->x, p1->x, p2->x);
    ymax = max3(p0->y, p1->y, p2->y);

    if (xmin == xmax || ymin == ymax) {
	put_msg(BOX_SCL_MSG);
	beep();
	return False;
    }
    set_action_on();
    toggle_linemarker(cur_l);

    if (x == xmin) {
	fix_x = xmax;
	from_x = x;
	if (y == ymin) {
	    fix_y = ymax;
	    from_y = y;
	    constrained = BOX_SCALE;
	} else if (y == ymax) {
	    fix_y = ymin;
	    from_y = y;
	    constrained = BOX_SCALE;
	} else {
	    fix_y = ymax;
	    from_y = ymin;
	    constrained = BOX_HSTRETCH;
	}
    } else if (x == xmax) {
	fix_x = xmin;
	from_x = x;
	if (y == ymin) {
	    fix_y = ymax;
	    from_y = y;
	    constrained = BOX_SCALE;
	} else if (y == ymax) {
	    fix_y = ymin;
	    from_y = y;
	    constrained = BOX_SCALE;
	} else {
	    fix_y = ymax;
	    from_y = ymin;
	    constrained = BOX_HSTRETCH;
	}
    } else {
	if (y == ymin) {
	    fix_y = ymax;
	    from_y = y;
	    fix_x = xmax;
	    from_x = xmin;
	    constrained = BOX_VSTRETCH;
	} else {		/* y == ymax */
	    fix_y = ymin;
	    from_y = y;
	    fix_x = xmax;
	    from_x = xmin;
	    constrained = BOX_VSTRETCH;
	}
    }

    cur_x = from_x;
    cur_y = from_y;
    set_cursor(crosshair_cursor);

    if (constrained == BOX_SCALE) {
	dx = (double) (cur_x - fix_x);
	dy = (double) (cur_y - fix_y);
	l = sqrt(dx * dx + dy * dy);
	cosa = fabs(dx / l);
	sina = fabs(dy / l);
    }
    boxsize_msg(1);
    elastic_box(fix_x, fix_y, cur_x, cur_y);
    canvas_locmove_proc = constrained_resizing_box;
    canvas_ref_proc = elastic_fixedbox;
    canvas_leftbut_proc = fix_boxscale_line;
    canvas_rightbut_proc = cancel_boxscale_line;
    return True;
}

static void
cancel_boxscale_line(void)
{
    canvas_ref_proc = canvas_locmove_proc = null_proc;
    elastic_box(fix_x, fix_y, cur_x, cur_y);
    /* erase last lengths if appres.showlengths is true */
    erase_lengths();
    toggle_linemarker(cur_l);
    wrapup_scale();
}

static void
fix_boxscale_line(int x, int y)
{
    int		owd,oht, nwd, nht;

    elastic_box(fix_x, fix_y, cur_x, cur_y);
    /* erase last lengths if appres.showlengths is true */
    erase_lengths();
    adjust_box_pos(x, y, from_x, from_y, &x, &y);
    new_l = copy_line(cur_l);
    draw_line(cur_l, ERASE);
    assign_newboxpoint(new_l, fix_x, fix_y, x, y);
    owd = abs(cur_l->points->x - cur_l->points->next->next->x);
    oht = abs(cur_l->points->y - cur_l->points->next->next->y);
    nwd = abs(new_l->points->x - new_l->points->next->next->x);
    nht = abs(new_l->points->y - new_l->points->next->next->y);
    if (new_l->type == T_PICTURE) {
	if (signof(fix_x - from_x) != signof(fix_x - x))
	    new_l->pic->flipped = 1 - new_l->pic->flipped;
	if (signof(fix_y - from_y) != signof(fix_y - y))
	    new_l->pic->flipped = 1 - new_l->pic->flipped;
    } else if (new_l->type == T_ARCBOX) {	/* scale the radius also */
	scale_radius(cur_l, new_l, owd, oht, nwd, nht);
    }
    change_line(cur_l, new_l);
    /* scalex = 1.0 * nwd/owd;
       scaley = 1.0 * nht/oht; */
    wrapup_scale();
    /* redraw anything under the old line */
    redisplay_line(cur_l);
    /* and the new one */
    redisplay_line(new_l);
}

void
scale_radius(F_line *old, F_line *new, int owd, int oht, int nwd, int nht)
{
	(void)old;
	float wdscale, htscale;

	wdscale = 1.0 * nwd/owd;
	htscale = 1.0 * nht/oht;
	/* scale by the average of height/width factor */
	new->radius = round(new->radius * (wdscale+htscale)/2);
	/* next, if the radius is larger than half the width, set it to the
	   minimum of the width or heigth divided by 2 */
	if ((new->radius > nwd/2) || (new->radius > nht/2))
		new->radius = min2(nwd,nht)/2;
	/* finally, if it is 0, make it 1 */
	if (new->radius == 0)
		new->radius = 1;
}

static Boolean
init_scale_line(void)
{
    int		    sumx, sumy, cnt;
    F_point	   *p;

    p = cur_l->points;
    if (cur_l->type != T_POLYLINE)
	p = p->next;
    for (sumx = 0, sumy = 0, cnt = 0; p != NULL; p = p->next) {
	sumx = sumx + p->x;
	sumy = sumy + p->y;
	cnt++;
    }
    fix_x = sumx / cnt;
    fix_y = sumy / cnt;
    if (from_x == fix_x && from_y == fix_y) {
	put_msg("Center point selected, ignored");
	return False;
    }
    set_action_on();
    toggle_linemarker(cur_l);
    set_cursor(crosshair_cursor);
    if (cur_l->type == T_BOX || cur_l->type == T_ARCBOX || cur_l->type == T_PICTURE) {
	boxsize_msg(2);	/* factor of 2 because measurement is from midpoint */
    }
    elastic_scalepts(cur_l->points);
    canvas_locmove_proc = scaling_line;
    canvas_ref_proc = elastic_scale_curline;
    canvas_middlebut_proc = fix_scale_line;
    canvas_rightbut_proc = cancel_scale_line;
    return True;
}

static void
cancel_scale_line(void)
{
    canvas_ref_proc = canvas_locmove_proc = null_proc;
    elastic_scalepts(cur_l->points);
    /* erase last lengths if appres.showlengths is true */
    erase_lengths();
    toggle_linemarker(cur_l);
    wrapup_scale();
}

static void
fix_scale_line(int x, int y)
{
    int		owd,oht, nwd, nht;

    elastic_scalepts(cur_l->points);
    /* erase last lengths if appres.showlengths is true */
    erase_lengths();
    adjust_box_pos(x, y, from_x, from_y, &x, &y);
    /* make a copy of the original and save as unchanged object */
    old_l = copy_line(cur_l);
    clean_up();
    set_latestline(old_l);
    set_action_object(F_EDIT, O_POLYLINE);
    old_l->next = cur_l;
    /* now change the original to become the new object */
    rescale_points(cur_l, x, y);
    /* and scale the radius if ARCBOX */
    if (cur_l->type == T_ARCBOX) {
	owd = abs(old_l->points->x - old_l->points->next->next->x);
	oht = abs(old_l->points->y - old_l->points->next->next->y);
	nwd = abs(cur_l->points->x - cur_l->points->next->next->x);
	nht = abs(cur_l->points->y - cur_l->points->next->next->y);
	scale_radius(cur_l, cur_l, owd, oht, nwd, nht);
    }
    wrapup_scale();
    /* redraw anything under the old line */
    redisplay_line(old_l);
    /* and the new one */
    redisplay_line(cur_l);
}

static void
rescale_points(F_line *obj, int x, int y)
{
    F_point	   *p;
    double	    newx, newy, oldx, oldy;
    double	    newd, oldd, scalefact;

    newx = x - fix_x;
    newy = y - fix_y;
    newd = sqrt(newx * newx + newy * newy);

    oldx = from_x - fix_x;
    oldy = from_y - fix_y;
    oldd = sqrt(oldx * oldx + oldy * oldy);

    scalefact = newd / oldd;
    for (p = obj->points; p != NULL; p = p->next) {
	p->x = fix_x + (p->x - fix_x) * scalefact;
	p->y = fix_y + (p->y - fix_y) * scalefact;
    }
    /* scale any arrows */
    scale_arrows(obj,scalefact,scalefact);
    set_modifiedflag();
}

/* scale arrowheads on passed object by sx,sy */

static void
scale_arrows(F_line *obj, float sx, float sy)
{
    scale_arrow(obj->for_arrow,sx,sy);
    scale_arrow(obj->back_arrow,sx,sy);
}

/* scale arrowhead by sx,sy - for now just use average
   of sx,sy for scale factor */

static void
scale_arrow(F_arrow *arrow, float sx, float sy)
{
    if (arrow == 0)
	return;

    /* scale the thickness by the average of the horizontal and vertical scale factors */
    sx = ((float)fabs(sx)+(float)fabs(sy))/2.0;
    arrow->ht *= sx;
    arrow->wd *= sx;
    return;
}
