Skip to content

Commit 7e40e67

Browse files
authored
Add a new modifier to -D (-D+f<file>) (#8965)
* Add new -D+f modifier to accept variable offsets via file. * Update the pstext docs * Add a variable offsets test. * Add var_offsets PS
1 parent 0141327 commit 7e40e67

5 files changed

Lines changed: 116 additions & 26 deletions

File tree

doc/rst/source/text.rst

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -159,22 +159,23 @@ Optional Arguments
159159

160160
- **o** - Straight rectangle [Default].
161161
- **O** - Set a rounded rectangle.
162-
- **c** - (Paragraph mode (|-M| only) selects a concave rectangle.
163-
- **C** - (Paragraph mode (|-M| only) selects a convex rectangle.
162+
- **c** - Paragraph mode (|-M| only) selects a concave rectangle.
163+
- **C** - Paragraph mode (|-M| only) selects a convex rectangle.
164164

165165
.. _-D:
166166

167-
**-D**\ [**j**\|\ **J**]\ *dx*\ [/*dy*][**+v**\ [*pen*]]
167+
**-D**\ [**j**\|\ **J**]\ *dx*\ [/*dy*][**+f**\ *offsets_file*][**+v**\ [*pen*]]
168168
Offsets the text from the projected (*x*,\ *y*) point by *dx*,\ *dy*
169169
[0/0]. If *dy* is not specified then it is set equal to *dx*. Prepend
170170
an optional directive:
171-
171+
172172
- **j** - Offset the text away from the point instead (i.e., the
173173
text justification will determine the direction of the shift).
174174
- **J** - Shorten diagonal offsets at corners by :math:`\sqrt{2}`.
175-
176-
Optionally, append the modifier:
177-
175+
- **+f** - Read per-record (*dx*, *dy*) offsets from *offsets_file* instead
176+
of using the single fixed *dx*\ [/*dy*] value. The file must have two
177+
columns, one offset pair per text record, in the same order as the input
178+
data. Values are in the current length unit [:term:`PROJ_LENGTH_UNIT`].
178179
- **+v** - Draw a line from the original point to the shifted point;
179180
append a *pen* to change the attributes for this line. **Note**:
180181
The **-Dj**\|\ **J** selection cannot be used with paragraph mode (|-M|).

src/longopt/pstext_inc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ static struct GMT_KEYWORD_DICTIONARY module_kw[] = {
3232
GMT_TP_STANDARD },
3333
{ 0, 'D', "offset",
3434
"j,J", "away,corners",
35-
"v", "line",
35+
"f,v", "offsets_file,line",
3636
GMT_TP_STANDARD },
3737
{ 0, 'F', "attributes|attrib",
3838
"", "",

src/pstext.c

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
#define THIS_MODULE_MODERN_NAME "text"
3333
#define THIS_MODULE_LIB "core"
3434
#define THIS_MODULE_PURPOSE "Plot or typeset text"
35-
#define THIS_MODULE_KEYS "<D{,>X},>DL"
35+
#define THIS_MODULE_KEYS "<D{,DD(=f,>X},>DL"
3636
#define THIS_MODULE_NEEDS "JR"
3737
#define THIS_MODULE_OPTIONS "-:>BJKOPRUVXYaefhpqtxyw" GMT_OPT("Ec")
3838

@@ -60,12 +60,14 @@ struct PSTEXT_CTRL {
6060
double dx, dy;
6161
char mode;
6262
} C;
63-
struct PSTEXT_D { /* -D[j]<dx>[/<dy>][v[<pen>]] */
63+
struct PSTEXT_D { /* -D[j|J]<dx>[/<dy>][+f<file>][+v[<pen>]] */
6464
bool active;
6565
bool line;
66+
bool variable; /* True if per-record offsets are read from +f<file> */
6667
int justify;
6768
double dx, dy;
6869
struct GMT_PEN pen;
70+
char *file; /* Name of file with per-record dx,dy offsets */
6971
} D;
7072
struct PSTEXT_F { /* -F[+c+f<fontinfo>+a<angle>+j<justification>+l|h|r|z|t] */
7173
bool active;
@@ -160,6 +162,7 @@ static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new
160162

161163
static void Free_Ctrl (struct GMT_CTRL *GMT, struct PSTEXT_CTRL *C) { /* Deallocate control structure */
162164
if (!C) return;
165+
gmt_M_str_free (C->D.file);
163166
gmt_M_str_free (C->F.text);
164167
gmt_M_free (GMT, C);
165168
}
@@ -292,7 +295,7 @@ static int usage (struct GMTAPI_CTRL *API, int level) {
292295
const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE);
293296
if (level & PSTEXT_SHOW_FONTS) show_fonts = true, level -= PSTEXT_SHOW_FONTS; /* Deal with the special bitflag for showing the fonts */
294297
if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR);
295-
GMT_Usage (API, 0, "usage: %s [<table>] %s %s [-A] [%s] [-C[<dx>[/<dy>]][+tc|C|o|O]] [-D[j|J]<dx>[/<dy>][+v[<pen>]]] "
298+
GMT_Usage (API, 0, "usage: %s [<table>] %s %s [-A] [%s] [-C[<dx>[/<dy>]][+tc|C|o|O]] [-D[j|J]<dx>[/<dy>][+f<file>][+v[<pen>]]] "
296299
"[-F[+a[<angle>]][+c[<justify>]][+f[<font>]][+h|l|r[<first>]|+t<text>|+z[<fmt>]][+j[<justify>]]] %s "
297300
"[-G[<color>][+n]] [-L] [-M] [-N] %s%s[-Ql|u] [-S[<dx>/<dy>/][<shade>]] [%s] [%s] [-W<pen>] [%s] [%s] [-Z] "
298301
"[%s] %s[%s] [%s] [%s] [-it<word>] [%s] [%s] [%s] [%s] [%s] [%s]\n",
@@ -349,10 +352,11 @@ static int usage (struct GMTAPI_CTRL *API, int level) {
349352
GMT_Usage (API, 3, "C: Convex rectangle (requires -M).");
350353
GMT_Usage (API, 3, "o: Rectangle [Default].");
351354
GMT_Usage (API, 3, "O: Rectangle with rounded corners.");
352-
GMT_Usage (API, 1, "\n-D[j|J]<dx>[/<dy>][+v[<pen>]]");
355+
GMT_Usage (API, 1, "\n-D[j|J]<dx>[/<dy>][+f<file>][+v[<pen>]]");
353356
GMT_Usage (API, -2, "Add <dx>,<dy> to the text origin AFTER projecting with -J. If <dy> is not given it equals <dx> [0/0]. "
354357
"Use -Dj to move text origin away from point (direction determined by text's justification). "
355-
"Upper case -DJ will shorten diagonal shifts at corners by sqrt(2). Cannot be used with -M. Optional modifier:");
358+
"Upper case -DJ will shorten diagonal shifts at corners by sqrt(2). Cannot be used with -M. Optional modifiers:");
359+
GMT_Usage (API, 3, "+f: Read per-record offsets (dx dy) from <file> (two columns in current distance units); overrides fixed <dx>/<dy>.");
356360
GMT_Usage (API, 3, "+v: Draw line from text to original point; optionally append a <pen> [%s].", gmt_putpen (API->GMT, &API->GMT->current.setting.map_default_pen));
357361
GMT_Usage (API, 1, "\n-F[+a[<angle>]][+c[<justify>]][+f[<font>]][+h|l|r[<first>]|+t<text>|+z[<fmt>]][+j[<justify>]]");
358362
GMT_Usage (API, -2, "Specify values for text attributes that apply to all text records:");
@@ -453,22 +457,41 @@ static int parse (struct GMT_CTRL *GMT, struct PSTEXT_CTRL *Ctrl, struct GMT_OPT
453457
if (c) c[0] = '+'; /* Restore */
454458
break;
455459
case 'D':
456-
n_errors += gmt_M_repeated_module_option (API, Ctrl->D.active);
460+
n_errors += gmt_M_repeated_module_option(API, Ctrl->D.active);
457461
k = 0;
458462
if (opt->arg[k] == 'j') { Ctrl->D.justify = 1, k++; }
459463
else if (opt->arg[k] == 'J') { Ctrl->D.justify = 2, k++; }
464+
/* Check for +f<file> modifier (per-record variable offsets); extract before other parsing */
465+
if ((c = strstr(&opt->arg[k], "+f"))) {
466+
char *end = strchr (&c[2], '+'); /* Look for next modifier (+v) after filename */
467+
if (end) { /* Another modifier follows after +f<file> */
468+
char saved = *end;
469+
*end = '\0';
470+
Ctrl->D.file = strdup(&c[2]);
471+
*end = saved;
472+
memmove(c, end, strlen(end) + 1); /* Remove +f<file> from option string */
473+
}
474+
else {
475+
Ctrl->D.file = strdup(&c[2]);
476+
c[0] = '\0'; /* Truncate option string at +f */
477+
}
478+
Ctrl->D.variable = true;
479+
}
480+
/* Handle +v modifier (new-style or old-style) */
460481
for (j = k; opt->arg[j] && opt->arg[j] != 'v'; j++);
461482
if (opt->arg[j] == 'v') { /* Want to draw a line from point to offset point */
462483
Ctrl->D.line = true;
463-
n_errors += gmt_M_check_condition (GMT, opt->arg[j+1] && gmt_getpen (GMT, &opt->arg[j+1], &Ctrl->D.pen), "Option -D: Give pen after +v\n");
484+
n_errors += gmt_M_check_condition(GMT, opt->arg[j+1] && gmt_getpen (GMT, &opt->arg[j+1], &Ctrl->D.pen), "Option -D: Give pen after +v\n");
464485
if (opt->arg[j-1] == '+') /* New-style syntax */
465486
opt->arg[j-1] = 0;
466487
else
467488
opt->arg[j] = 0;
468489
}
469490
j = sscanf (&opt->arg[k], "%[^/]/%s", txt_a, txt_b);
470-
Ctrl->D.dx = gmt_M_to_inch (GMT, txt_a);
471-
Ctrl->D.dy = (j == 2) ? gmt_M_to_inch (GMT, txt_b) : Ctrl->D.dx;
491+
if (j >= 1 && txt_a[0]) { /* Have explicit dx[/dy] */
492+
Ctrl->D.dx = gmt_M_to_inch(GMT, txt_a);
493+
Ctrl->D.dy = (j == 2) ? gmt_M_to_inch(GMT, txt_b) : Ctrl->D.dx;
494+
}
472495
break;
473496
case 'F':
474497
n_errors += gmt_M_repeated_module_option (API, Ctrl->F.active);
@@ -708,7 +731,7 @@ static int parse (struct GMT_CTRL *GMT, struct PSTEXT_CTRL *Ctrl, struct GMT_OPT
708731
n_errors += gmt_M_check_condition (GMT, !Ctrl->L.active && !GMT->common.J.active, "Must specify a map projection with the -J option\n");
709732
n_errors += gmt_M_check_condition (GMT, Ctrl->C.dx < 0.0 || Ctrl->C.dy < 0.0, "Option -C: clearances cannot be negative!\n");
710733
n_errors += gmt_M_check_condition (GMT, Ctrl->C.dx == 0.0 && Ctrl->C.dy == 0.0 && Ctrl->C.mode != 'o', "Option -C: Non-rectangular text boxes require a non-zero clearance\n");
711-
n_errors += gmt_M_check_condition (GMT, Ctrl->D.dx == 0.0 && Ctrl->D.dy == 0.0 && Ctrl->D.line, "-D<x/y>v requires one nonzero <x/y>\n");
734+
n_errors += gmt_M_check_condition (GMT, Ctrl->D.dx == 0.0 && Ctrl->D.dy == 0.0 && Ctrl->D.line && !Ctrl->D.variable, "-D<x/y>v requires one nonzero <x/y>\n");
712735
n_errors += gmt_M_check_condition (GMT, Ctrl->Q.active && abs (Ctrl->Q.mode) > 1, "Option -Q: Use l or u for lower/upper-case.\n");
713736
n_errors += gmt_M_check_condition (GMT, Ctrl->G.mode && Ctrl->M.active, "Option -Gc: Cannot be used with -M.\n");
714737
n_errors += gmt_M_check_condition (GMT, Ctrl->G.mode && Ctrl->W.active, "Option -Gc: Cannot be used with -W.\n");
@@ -824,6 +847,8 @@ EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) {
824847
unsigned int n_read = 0, n_processed = 0, txt_alloc = 0, add, n_expected_cols, z_col = GMT_Z, n_skipped = 0;
825848

826849
size_t n_alloc = 0;
850+
uint64_t n_D_offsets = 0, d_rec_no = 0;
851+
double *D_off_x = NULL, *D_off_y = NULL;
827852

828853
double plot_x = 0.0, plot_y = 0.0, save_angle = 0.0, xx[2] = {0.0, 0.0}, yy[2] = {0.0, 0.0}, *in = NULL;
829854
double offset[2], tmp, *c_x = NULL, *c_y = NULL, *c_angle = NULL;
@@ -892,7 +917,30 @@ EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) {
892917
Ctrl->F.font.size = GMT->current.setting.font_annot[GMT_PRIMARY].size;
893918

894919
pstext_load_parameters_pstext (GMT, &T, Ctrl); /* Pass info from Ctrl to T */
920+
if (Ctrl->D.variable) { /* Read per-record offsets from file */
921+
uint64_t tbl, seg, row;
922+
double unit_scale = GMT->session.u2u[GMT->current.setting.proj_length_unit][GMT_INCH];
923+
struct GMT_DATASET *D_file = NULL;
924+
if ((D_file = GMT_Read_Data (API, GMT_IS_DATASET, GMT_IS_FILE, GMT_IS_POINT,
925+
GMT_READ_NORMAL, NULL, Ctrl->D.file, NULL)) == NULL)
926+
Return (API->error);
927+
for (tbl = 0; tbl < D_file->n_tables; tbl++)
928+
for (seg = 0; seg < D_file->table[tbl]->n_segments; seg++)
929+
n_D_offsets += D_file->table[tbl]->segment[seg]->n_rows;
930+
D_off_x = gmt_M_memory (GMT, NULL, n_D_offsets, double);
931+
D_off_y = gmt_M_memory (GMT, NULL, n_D_offsets, double);
932+
n_D_offsets = 0;
933+
for (tbl = 0; tbl < D_file->n_tables; tbl++)
934+
for (seg = 0; seg < D_file->table[tbl]->n_segments; seg++)
935+
for (row = 0; row < D_file->table[tbl]->segment[seg]->n_rows; row++, n_D_offsets++) {
936+
D_off_x[n_D_offsets] = D_file->table[tbl]->segment[seg]->data[GMT_X][row] * unit_scale;
937+
D_off_y[n_D_offsets] = D_file->table[tbl]->segment[seg]->data[GMT_Y][row] * unit_scale;
938+
}
939+
if (GMT_Destroy_Data (API, &D_file) != GMT_NOERROR)
940+
Return (API->error);
941+
}
895942
add = !(T.x_offset == 0.0 && T.y_offset == 0.0);
943+
if (Ctrl->D.variable) add = 1; /* Variable offsets always enable displacement */
896944
if (add && Ctrl->D.justify) T.boxflag |= 64;
897945

898946
if (!(Ctrl->N.active || Ctrl->Z.active)) {
@@ -937,11 +985,13 @@ EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) {
937985
else if (T.paragraph_angle < -90.0) T.paragraph_angle += 180.0;
938986
}
939987
if (add) {
988+
double xoff = (Ctrl->D.variable && n_D_offsets > 0) ? D_off_x[0] : T.x_offset;
989+
double yoff = (Ctrl->D.variable && n_D_offsets > 0) ? D_off_y[0] : T.y_offset;
940990
if (Ctrl->D.justify) /* Smart offset according to justification (from Dave Huang) */
941-
gmt_smart_justify (GMT, T.block_justify, T.paragraph_angle, T.x_offset, T.y_offset, &plot_x, &plot_y, Ctrl->D.justify);
991+
gmt_smart_justify(GMT, T.block_justify, T.paragraph_angle, xoff, yoff, &plot_x, &plot_y, Ctrl->D.justify);
942992
else { /* Default hard offset */
943-
plot_x += T.x_offset;
944-
plot_y += T.y_offset;
993+
plot_x += xoff;
994+
plot_y += yoff;
945995
}
946996
xx[1] = plot_x; yy[1] = plot_y;
947997
}
@@ -1350,14 +1400,17 @@ EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) {
13501400
else if (T.paragraph_angle < -90.0) T.paragraph_angle += 180.0;
13511401
}
13521402
if (add) {
1403+
double xoff = (Ctrl->D.variable && d_rec_no < n_D_offsets) ? D_off_x[d_rec_no] : T.x_offset;
1404+
double yoff = (Ctrl->D.variable && d_rec_no < n_D_offsets) ? D_off_y[d_rec_no] : T.y_offset;
13531405
if (Ctrl->D.justify) /* Smart offset according to justification (from Dave Huang) */
1354-
gmt_smart_justify (GMT, T.block_justify, T.paragraph_angle, T.x_offset, T.y_offset, &plot_x, &plot_y, Ctrl->D.justify);
1406+
gmt_smart_justify(GMT, T.block_justify, T.paragraph_angle, xoff, yoff, &plot_x, &plot_y, Ctrl->D.justify);
13551407
else { /* Default hard offset */
1356-
plot_x += T.x_offset;
1357-
plot_y += T.y_offset;
1408+
plot_x += xoff;
1409+
plot_y += yoff;
13581410
}
13591411
xx[1] = plot_x; yy[1] = plot_y;
13601412
}
1413+
if (Ctrl->D.variable) d_rec_no++;
13611414
n_paragraphs++;
13621415

13631416
if (GMT->common.t.variable) { /* Update the transparencies for current string (if -t was given) */
@@ -1518,6 +1571,10 @@ EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) {
15181571

15191572
GMT_Report (API, GMT_MSG_INFORMATION, Ctrl->M.active ? "pstext: Plotted %d text blocks\n" : "pstext: Plotted %d text strings\n", n_paragraphs);
15201573

1574+
if (Ctrl->D.variable) {
1575+
gmt_M_free(GMT, D_off_x);
1576+
gmt_M_free(GMT, D_off_y);
1577+
}
15211578
Return (GMT_NOERROR);
15221579
}
15231580

test/baseline/pstext.dvc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
outs:
2-
- md5: cc3f79d0b3d11ebdcfe61ee6096d060c.dir
3-
nfiles: 16
2+
- md5: c31402871330634d214e02171504694b.dir
3+
nfiles: 17
44
path: pstext
55
hash: md5

test/pstext/var_offset.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env bash
2+
# Test pstext -D+f<file> for per-record variable offsets
3+
# Each text label gets its own (dx,dy) displacement from an offset file
4+
gmt begin var_offset
5+
# Create the offset file: 4 records with different (dx,dy) values (in cm)
6+
gmt set PROJ_LENGTH_UNIT=cm
7+
cat > offsets.txt <<- EOF
8+
0.3 0.3
9+
-0.3 0.3
10+
-0.3 -0.3
11+
0.3 -0.3
12+
EOF
13+
# Plot base map and mark the anchor points
14+
gmt basemap -R-5/5/-5/5 -JX10c -Bafg5 -BWSne+t"Variable offsets (-D+f)"
15+
# Draw small circles at the 4 anchor positions
16+
gmt plot -Sc0.15c -Gred -Wfaint <<- EOF
17+
-3 -3
18+
-3 3
19+
3 3
20+
3 -3
21+
EOF
22+
# Draw connecting lines from anchor to offset position to visualise displacement
23+
# (not strictly needed but shows the offset)
24+
# Place text labels using per-record variable offsets from offsets.txt
25+
gmt text -F+f12p,Helvetica-Bold,blue+jCM -D+v+foffsets.txt <<- EOF
26+
-3 -3 SW corner
27+
-3 3 NW corner
28+
3 3 NE corner
29+
3 -3 SE corner
30+
EOF
31+
rm -f offsets.txt
32+
gmt end show

0 commit comments

Comments
 (0)