From 88e8dc84ca660945771f03bcd20a51628b9fefe1 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Mon, 25 May 2026 03:12:18 +0100 Subject: [PATCH] pstext: clip lines by text box Fix #3927 Assisted-by: Claude Opus 4.7 --- src/postscriptlight.c | 58 +++++++++++++++++++++++++++++++++++++++++++ src/postscriptlight.h | 1 + src/pstext.c | 42 +++++++++++++++---------------- 3 files changed, 80 insertions(+), 21 deletions(-) diff --git a/src/postscriptlight.c b/src/postscriptlight.c index cc85703752b..f40dfe373b6 100644 --- a/src/postscriptlight.c +++ b/src/postscriptlight.c @@ -4045,6 +4045,64 @@ int PSL_plotsegment (struct PSL_CTRL *PSL, double x0, double y0, double x1, doub return (PSL_NO_ERROR); } +int PSL_plotline_clipped_by_textbox(struct PSL_CTRL *PSL, double x0, double y0, double x1, double y1, + double fontsize, char *text, double angle, int justify, double offset[2]) { + /* Draw a line from (x0,y0) to the text-reference point (x1,y1), clipping out the part + * that falls inside the text bounding box (plus optional clearance offset) around (x1,y1). + * The box is sized for the given text/fontsize/justify and rotated by angle. Used for + * leader lines from a data point to an offset text label (pstext -D...+v). + */ + const char *jx[3] = {"0", "PSL_dim_w 2 div neg", "PSL_dim_w neg"}; + const char *jy[3] = {"0", "PSL_dim_h 2 div neg", "PSL_dim_h neg"}; + int x_just = 0, y_just = 0, ixl, iyl; + double a_rad, ca, sa, dx_u, dy_u, xl_u, yl_u, saved_fontsize; + + if (text == NULL || text[0] == '\0') /* No text -- just draw the line */ + return (PSL_plotsegment(PSL, x0, y0, x1, y1)); + + if (justify > 1) { + x_just = (justify + 3) % 4; /* 0=left, 1=center, 2=right */ + y_just = justify / 4; /* 0=bottom, 1=middle, 2=top */ + } + + /* Compute line-start position in the local (rotated) coord system whose origin is at (x1,y1) */ + a_rad = angle * D2R; + ca = cos(a_rad); sa = sin(a_rad); + dx_u = x0 - x1; dy_u = y0 - y1; + xl_u = dx_u * ca + dy_u * sa; + yl_u = -dx_u * sa + dy_u * ca; + ixl = (int)lrint(xl_u * PSL->internal.x2ix); + iyl = (int)lrint(yl_u * PSL->internal.y2iy); + + saved_fontsize = PSL->current.fontsize; /* PSL_deftextdim will update the cache; restore after grestore */ + + PSL_comment(PSL, "PSL_plotline_clipped_by_textbox begin\n"); + PSL_command(PSL, "V "); /* gsave */ + PSL_command(PSL, "%d %d T ", psl_ix(PSL, x1), psl_iy(PSL, y1)); + if (angle != 0.0) PSL_command(PSL, "%.12g R ", angle); + PSL_deftextdim(PSL, "PSL_dim", fontsize, text); /* Defines PSL_dim_w, _h, _d, _x0, _x1 in PS */ + PSL_defunits(PSL, "PSL_dx", offset[0]); + PSL_defunits(PSL, "PSL_dy", offset[1]); + + /* Build even-odd clip path: huge outer rectangle + textbox rectangle (the hole) */ + PSL_command(PSL, "N -10000000 -10000000 M 20000000 0 D 0 20000000 D -20000000 0 D P\n"); + PSL_command(PSL, "%s PSL_dim_x0 add PSL_dx sub %s PSL_dim_d add PSL_dy sub M ", + jx[x_just], jy[y_just]); + PSL_command(PSL, "PSL_dim_x1 PSL_dim_x0 sub PSL_dx 2 mul add 0 D "); + PSL_command(PSL, "0 PSL_dim_h PSL_dim_d sub PSL_dy 2 mul add D "); + PSL_command(PSL, "PSL_dim_x1 PSL_dim_x0 sub PSL_dx 2 mul add neg 0 D P\n"); + PSL_command(PSL, "eoclip N\n"); + + /* Draw the (now clipped) line from (xl, yl) to (0, 0) in local coords */ + PSL_command(PSL, "N %d %d M %d %d D S\n", ixl, iyl, -ixl, -iyl); + + PSL_command(PSL, "U\n"); /* grestore -- pops clip and CTM */ + PSL_comment(PSL, "PSL_plotline_clipped_by_textbox end\n"); + + PSL->current.fontsize = saved_fontsize; + return (PSL_NO_ERROR); +} + int PSL_setcurrentpoint (struct PSL_CTRL *PSL, double x, double y) { /* Set the current point only */ PSL->internal.ix = psl_ix (PSL, x); diff --git a/src/postscriptlight.h b/src/postscriptlight.h index a07fc5227c6..4aae05ddf6d 100644 --- a/src/postscriptlight.h +++ b/src/postscriptlight.h @@ -480,6 +480,7 @@ EXTERN_MSC int PSL_plotpolygon (struct PSL_CTRL *PSL, double *x, double *y, int EXTERN_MSC int PSL_plotgradienttriangle (struct PSL_CTRL *PSL, double *x, double *y, double *rgb, int steps); EXTERN_MSC int PSL_plotgradienttriangle_gouraud (struct PSL_CTRL *PSL, double *x, double *y, double *rgb); EXTERN_MSC int PSL_plotsegment (struct PSL_CTRL *PSL, double x0, double y0, double x1, double y1); +EXTERN_MSC int PSL_plotline_clipped_by_textbox (struct PSL_CTRL *PSL, double x0, double y0, double x1, double y1, double fontsize, char *text, double angle, int justify, double offset[2]); EXTERN_MSC int PSL_plotsymbol (struct PSL_CTRL *PSL, double x, double y, double param[], int symbol); EXTERN_MSC int PSL_plottext (struct PSL_CTRL *PSL, double x, double y, double fontsize, char *text, double angle, int justify, int mode); EXTERN_MSC int PSL_plottextbox (struct PSL_CTRL *PSL, double x, double y, double fontsize, char *text, double angle, int justify, double offset[], int mode); diff --git a/src/pstext.c b/src/pstext.c index 9bad9d3c34b..df595215956 100644 --- a/src/pstext.c +++ b/src/pstext.c @@ -998,19 +998,19 @@ EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) { PSL_setfont (PSL, T.font.id); gmt_plane_perspective (GMT, GMT->current.proj.z_project.view_plane, 0.0); - if (T.boxflag & 32) { /* Draw line from original point to shifted location */ - gmt_setpen (GMT, &T.vecpen); - PSL_plotsegment (PSL, xx[0], yy[0], xx[1], yy[1]); + if (T.space_flag) { /* Meant % of fontsize */ + offset[0] = 0.01 * T.x_space * T.font.size / PSL_POINTS_PER_INCH; + offset[1] = 0.01 * T.y_space * T.font.size / PSL_POINTS_PER_INCH; + } + else { + offset[0] = T.x_space; + offset[1] = T.y_space; + } + if (T.boxflag & 32) { /* Draw line from original point to shifted location, clipped at textbox edge */ + gmt_setpen(GMT, &T.vecpen); + PSL_plotline_clipped_by_textbox(PSL, xx[0], yy[0], xx[1], yy[1], T.font.size, use_text, T.paragraph_angle, T.block_justify, offset); } if (!Ctrl->G.mode && T.boxflag & 3) { /* Plot the box beneath the text */ - if (T.space_flag) { /* Meant % of fontsize */ - offset[0] = 0.01 * T.x_space * T.font.size / PSL_POINTS_PER_INCH; - offset[1] = 0.01 * T.y_space * T.font.size / PSL_POINTS_PER_INCH; - } - else { - offset[0] = T.x_space; - offset[1] = T.y_space; - } if (Ctrl->S.active) { /* Lay down shaded box first */ PSL_setfill (PSL, Ctrl->S.fill.rgb, 0); /* shade color */ PSL_plottextbox (PSL, plot_x + Ctrl->S.off[GMT_X], plot_y + Ctrl->S.off[GMT_Y], T.font.size, use_text, T.paragraph_angle, T.block_justify, offset, T.boxflag & 4); @@ -1477,19 +1477,19 @@ EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) { } PSL_setfont (PSL, T.font.id); gmt_plane_perspective (GMT, GMT->current.proj.z_project.view_plane, in[GMT_Z]); - if (T.boxflag & 32) { /* Draw line from original point to shifted location */ + if (T.space_flag) { /* Meant % of fontsize */ + offset[0] = 0.01 * T.x_space * T.font.size / PSL_POINTS_PER_INCH; + offset[1] = 0.01 * T.y_space * T.font.size / PSL_POINTS_PER_INCH; + } + else { + offset[0] = T.x_space; + offset[1] = T.y_space; + } + if (T.boxflag & 32) { /* Draw line from original point to shifted location, clipped at textbox edge */ gmt_setpen (GMT, &T.vecpen); - PSL_plotsegment (PSL, xx[0], yy[0], xx[1], yy[1]); + PSL_plotline_clipped_by_textbox(PSL, xx[0], yy[0], xx[1], yy[1], T.font.size, use_text, T.paragraph_angle, T.block_justify, offset); } if (!Ctrl->G.mode && T.boxflag & 3) { /* Plot the box beneath the text */ - if (T.space_flag) { /* Meant % of fontsize */ - offset[0] = 0.01 * T.x_space * T.font.size / PSL_POINTS_PER_INCH; - offset[1] = 0.01 * T.y_space * T.font.size / PSL_POINTS_PER_INCH; - } - else { - offset[0] = T.x_space; - offset[1] = T.y_space; - } if (Ctrl->S.active) { /* Lay down shaded box first */ PSL_setfill (PSL, Ctrl->S.fill.rgb, 0); /* shade color */ PSL_plottextbox (PSL, plot_x + Ctrl->S.off[GMT_X], plot_y + Ctrl->S.off[GMT_Y], T.font.size, use_text, T.paragraph_angle, T.block_justify, offset, T.boxflag & 4);