1 /*--------------------------------------------------------------------
2 *
3 * Copyright (c) 1991-2021 by the GMT Team (https://www.generic-mapping-tools.org/team.html)
4 * See LICENSE.TXT file for copying and redistribution conditions.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; version 3 or any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * Contact info: www.generic-mapping-tools.org
16 *--------------------------------------------------------------------*/
17 /*
18 * Brief synopsis: pstext will read (x, y[, z][, font, angle, justify], text) from input
19 * and plot the text strings at (x,y) on a map using the font attributes
20 * and justification selected by the user. Alternatively (with -M), read
21 * one or more text paragraphs to be typeset.
22 *
23 * Author: Paul Wessel
24 * Date: 1-JAN-2010
25 * Version: 6 API
26 */
27
28 #include "gmt_dev.h"
29
30 #define THIS_MODULE_CLASSIC_NAME "pstext"
31 #define THIS_MODULE_MODERN_NAME "text"
32 #define THIS_MODULE_LIB "core"
33 #define THIS_MODULE_PURPOSE "Plot or typeset text"
34 #define THIS_MODULE_KEYS "<D{,>X}"
35 #define THIS_MODULE_NEEDS "JR"
36 #define THIS_MODULE_OPTIONS "-:>BJKOPRUVXYaefhpqtxyw" GMT_OPT("Ec")
37
38 EXTERN_MSC void gmtlib_enforce_rgb_triplets (struct GMT_CTRL *GMT, char *text, unsigned int size);
39
40 #define PSTEXT_CLIPPLOT 1
41 #define PSTEXT_CLIPONLY 2
42
43 #define PSTEXT_SHOW_FONTS 128
44
45 #define GET_REC_TEXT 0 /* Free-form text as trailing text in the record */
46 #define GET_SEG_LABEL 1 /* Use the current segment label (-L<label>) as the text */
47 #define GET_SEG_HEADER 2 /* Use the current segment header as the text */
48 #define GET_CMD_TEXT 3 /* Use the given +t<text> as the text */
49 #define GET_CMD_FORMAT 4 /* Format z-column using given format (or FORMAT_FLOAT_OUT) */
50 #define GET_REC_NUMBER 5 /* Use record number (relative to given offset) as text */
51
52 struct PSTEXT_CTRL {
53 struct PSTEXT_A { /* -A */
54 bool active;
55 } A;
56 struct PSTEXT_C { /* -C[<dx>/<dy>][+to|O|c|C] */
57 bool active;
58 bool percent;
59 double dx, dy;
60 char mode;
61 } C;
62 struct PSTEXT_D { /* -D[j]<dx>[/<dy>][v[<pen>]] */
63 bool active;
64 bool line;
65 int justify;
66 double dx, dy;
67 struct GMT_PEN pen;
68 } D;
69 struct PSTEXT_F { /* -F[+c+f<fontinfo>+a<angle>+j<justification>+l|h|r|z|t] */
70 bool active;
71 bool read_font; /* True if we must read fonts from input file */
72 bool orientation; /* True if we should treat angles as orientations for text */
73 bool mixed; /* True if input record contains a text item */
74 bool get_xy_from_justify; /* True if +c was given and we just get it from input */
75 bool word; /* True if we are to select a single word from the trailing text as the label */
76 bool no_input; /* True if we give a single static text and place it via +c */
77 struct GMT_FONT font;
78 double angle;
79 int justify, R_justify, nread, first, w_col;
80 unsigned int get_text; /* 0 = from data record, 1 = segment label (+l), 2 = segment header (+h), 3 = specified text (+t), 4 = format z using text (+z) */
81 char read[4]; /* Contains a|A, c, f, and/or j in order required to be read from input */
82 char *text;
83 } F;
84 struct PSTEXT_G { /* -G<fill> | -G[+n] */
85 bool active;
86 unsigned int mode;
87 struct GMT_FILL fill;
88 } G;
89 struct PSTEXT_L { /* -L */
90 bool active;
91 } L;
92 struct PSTEXT_M { /* -M */
93 bool active;
94 } M;
95 struct PSTEXT_N { /* -N */
96 bool active;
97 } N;
98 struct PSTEXT_Q { /* -Q<case> */
99 bool active;
100 int mode; /* 0 = do nothing, -1 = force lower case, +1 = force upper case */
101 } Q;
102 struct PSTEXT_S { /* -S[dx>/<dy>/][<fill>] */
103 bool active;
104 double off[2];
105 struct GMT_FILL fill;
106 } S;
107 struct PSTEXT_S_OLD { /* -S<pen> GMT 4 syntax - deprecated */
108 bool active;
109 struct GMT_PEN pen;
110 } S_old;
111 struct PSTEXT_W { /* -W[<pen>] */
112 bool active;
113 struct GMT_PEN pen;
114 } W;
115 struct PSTEXT_Z { /* -Z<z_level> */
116 bool active;
117 } Z;
118 };
119
120 struct PSTEXT_INFO {
121 int text_justify;
122 int block_justify;
123 int boxflag;
124 int space_flag;
125 double x_offset, y_offset; /* Offset from reference point */
126 double line_spacing;
127 double paragraph_width;
128 double paragraph_angle;
129 double x_space, y_space; /* Extra spacing between box and text */
130 struct GMT_FONT font;
131 struct GMT_PEN boxpen;
132 struct GMT_PEN vecpen;
133 struct GMT_FILL boxfill;
134 };
135
New_Ctrl(struct GMT_CTRL * GMT)136 static void *New_Ctrl (struct GMT_CTRL *GMT) { /* Allocate and initialize a new control structure */
137 struct PSTEXT_CTRL *C;
138
139 C = gmt_M_memory (GMT, NULL, 1, struct PSTEXT_CTRL);
140
141 /* Initialize values whose defaults are not 0/false/NULL */
142
143 C->D.pen = C->W.pen = GMT->current.setting.map_default_pen;
144 C->C.dx = C->C.dy = GMT_TEXT_CLEARANCE; /* 15% of font size is default clearance */
145 C->C.percent = true;
146 C->C.mode = 'o'; /* Rectangular box shape */
147 C->F.justify = PSL_MC; /* MC is the default */
148 C->F.font = GMT->current.setting.font_annot[GMT_PRIMARY]; /* Default font */
149 C->F.font.set = 0;
150 gmt_init_fill (GMT, &C->G.fill, -1.0, -1.0, -1.0); /* No fill */
151 C->S_old.pen = GMT->current.setting.map_default_pen;
152 C->S.off[GMT_X] = GMT->session.u2u[GMT_PT][GMT_INCH] * GMT_FRAME_CLEARANCE; /* Default is 4p */
153 C->S.off[GMT_Y] = -C->S.off[GMT_X]; /* Set the shadow offsets [default is (4p, -4p)] */
154 gmt_init_fill (GMT, &C->S.fill, gmt_M_is255 (127), gmt_M_is255 (127), gmt_M_is255 (127)); /* Default if gray shade is used */
155
156 return (C);
157 }
158
Free_Ctrl(struct GMT_CTRL * GMT,struct PSTEXT_CTRL * C)159 static void Free_Ctrl (struct GMT_CTRL *GMT, struct PSTEXT_CTRL *C) { /* Deallocate control structure */
160 if (!C) return;
161 gmt_M_str_free (C->F.text);
162 gmt_M_free (GMT, C);
163 }
164
pstext_output_words(struct GMT_CTRL * GMT,struct PSL_CTRL * PSL,double x,double y,char * text,struct PSTEXT_INFO * T,struct PSTEXT_CTRL * Ctrl)165 GMT_LOCAL void pstext_output_words (struct GMT_CTRL *GMT, struct PSL_CTRL *PSL, double x, double y, char *text, struct PSTEXT_INFO *T, struct PSTEXT_CTRL *Ctrl) {
166 double offset[2];
167
168 gmt_M_memcpy (PSL->current.rgb[PSL_IS_FILL], GMT->session.no_rgb, 3, double); /* Reset to -1,-1,-1 since text setting must set the color desired */
169 gmt_M_memcpy (PSL->current.rgb[PSL_IS_STROKE], GMT->session.no_rgb, 3, double); /* Reset to -1,-1,-1 since text setting must set the color desired */
170 if (T->space_flag) { /* Meant % of fontsize */
171 offset[0] = 0.01 * T->x_space * T->font.size / PSL_POINTS_PER_INCH;
172 offset[1] = 0.01 * T->y_space * T->font.size / PSL_POINTS_PER_INCH;
173 }
174 else { /* Gave in distance units */
175 offset[0] = T->x_space;
176 offset[1] = T->y_space;
177 }
178
179 /* Set some paragraph parameters */
180 PSL_setparagraph (PSL, T->line_spacing, T->paragraph_width, T->text_justify);
181 PSL_setfont (PSL, T->font.id);
182
183 if (T->boxflag & 32) { /* Need to draw a vector from (x,y) to the offset text */
184 gmt_setpen (GMT, &(T->vecpen));
185 PSL_plotsegment (PSL, x, y, x + T->x_offset, y + T->y_offset);
186 }
187 if (Ctrl->D.justify) /* Smart offset according to justification (from Dave Huang) */
188 gmt_smart_justify (GMT, T->block_justify, T->paragraph_angle, T->x_offset, T->y_offset, &x, &y, Ctrl->D.justify);
189 else
190 x += T->x_offset, y += T->y_offset; /* Move to the actual reference point */
191 if (T->boxflag) { /* Need to lay down the box first, then place text */
192 int mode = 0;
193 struct GMT_FILL *fill = NULL;
194 if (T->boxflag & 1) mode = PSL_RECT_STRAIGHT; /* Set correct box shape */
195 if (T->boxflag & 4) mode = PSL_RECT_ROUNDED;
196 if (T->boxflag & 8) mode = PSL_RECT_CONCAVE;
197 if (T->boxflag & 16) mode = PSL_RECT_CONVEX;
198 if (Ctrl->S.active) { /* Lay down shaded box first */
199 PSL_setfill (PSL, Ctrl->S.fill.rgb, 0); /* shade color */
200 PSL_plotparagraphbox (PSL, x + Ctrl->S.off[GMT_X], y + Ctrl->S.off[GMT_Y], T->font.size, text, T->paragraph_angle, T->block_justify, offset, mode);
201 }
202 if (T->boxflag & 1) gmt_setpen (GMT, &(T->boxpen)); /* Change current pen */
203 if (T->boxflag & 2) fill = &(T->boxfill); /* Determine if fill or not */
204 if (T->boxflag & 3) gmt_setfill (GMT, fill, T->boxflag & 1); /* Change current fill and/or outline */
205 /* Compute text box, draw/fill it, and in the process store the text in the PS file for next command */
206 PSL_plotparagraphbox (PSL, x, y, T->font.size, text, T->paragraph_angle, T->block_justify, offset, mode);
207 /* Passing NULL means we typeset using the last stored paragraph info */
208 gmt_setfont (GMT, &T->font);
209 PSL_plotparagraph (PSL, x, y, T->font.size, NULL, T->paragraph_angle, T->block_justify);
210 }
211 else { /* No box beneath */
212 gmt_setfont (GMT, &T->font);
213 PSL_plotparagraph (PSL, x, y, T->font.size, text, T->paragraph_angle, T->block_justify);
214 }
215 }
216
pstext_load_parameters_pstext(struct GMT_CTRL * GMT,struct PSTEXT_INFO * T,struct PSTEXT_CTRL * C)217 GMT_LOCAL void pstext_load_parameters_pstext (struct GMT_CTRL *GMT, struct PSTEXT_INFO *T, struct PSTEXT_CTRL *C) {
218 gmt_M_memset (T, 1, struct PSTEXT_INFO);
219 if (C->C.mode != 'o' && C->C.dx == 0.0 && C->C.dy == 0.0) {
220 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Cannot have non-rectangular text box if clearance (-C) is zero.\n");
221 C->C.mode = 'o';
222 }
223 T->x_space = C->C.dx;
224 T->y_space = C->C.dy;
225 T->space_flag = (C->C.percent) ? 1 : 0;
226 if (C->D.active) {
227 T->x_offset = C->D.dx;
228 T->y_offset = C->D.dy;
229 if (C->D.line) T->boxflag |= 32;
230 T->vecpen = C->D.pen;
231 }
232 if (C->W.active || C->G.active) {
233 if (C->W.active) T->boxflag |= 1; /* Want box outline */
234 if (C->G.active) T->boxflag |= 2; /* Want filled box */
235 if (C->C.mode == 'O') T->boxflag |= 4; /* Want rounded box outline */
236 if (C->C.mode == 'c') T->boxflag |= 8; /* Want concave box outline */
237 if (C->C.mode == 'C') T->boxflag |= 16; /* Want convex box outline */
238 T->boxpen = C->W.pen;
239 T->boxfill = C->G.fill;
240 }
241 /* Initialize default attributes */
242 T->font = C->F.font;
243 T->paragraph_angle = C->F.angle;
244 T->block_justify = C->F.justify;
245 }
246
pstext_get_input_format_version(struct GMT_CTRL * GMT,char * buffer,int mode)247 GMT_LOCAL int pstext_get_input_format_version (struct GMT_CTRL *GMT, char *buffer, int mode) {
248 /* Try to determine if input is the old GMT4-style format.
249 * mode = 0 means normal text records, mode = 1 means paragraph mode.
250 * Return 4 if GMT 4, 5 if GMT 5, -1 if nothing can be done */
251
252 int n, k;
253 char size[GMT_LEN256] = {""}, angle[GMT_LEN256] = {""}, font[GMT_LEN256] = {""}, just[GMT_LEN256] = {""}, txt[GMT_BUFSIZ] = {""};
254 char spacing[GMT_LEN256] = {""}, width[GMT_LEN256] = {""}, pjust[GMT_LEN256] = {""};
255
256 if (!buffer || !buffer[0]) return (-1); /* Nothing to work with */
257
258 if (mode) { /* Paragraph control record */
259 n = sscanf (buffer, "%s %s %s %s %s %s %s\n", size, angle, font, just, spacing, width, pjust);
260 if (n < 7) return (5); /* Clearly not the old format since missing items */
261 }
262 else { /* Regular text record */
263 n = sscanf (buffer, "%s %s %s %s %[^\n]", size, angle, font, just, txt);
264 if (n < 5) return (5); /* Clearly not the old format since missing items */
265 }
266 if (gmt_not_numeric (GMT, angle)) return (5); /* Since angle is not a number */
267 k = (int)strlen (size) - 1;
268 if (size[k] == 'c' || size[k] == 'i' || size[k] == 'm' || size[k] == 'p') size[k] = '\0'; /* Chop of unit */
269 if (gmt_not_numeric (GMT, size)) return (5); /* Since size is not a number */
270 if (gmt_just_decode (GMT, just, PSL_NO_DEF) == -99) return (5); /* Since justify not in correct format */
271 if (mode) { /* A few more checks for paragraph mode */
272 k = (int)strlen (spacing) - 1;
273 if (spacing[k] == 'c' || spacing[k] == 'i' || spacing[k] == 'm' || spacing[k] == 'p') spacing[k] = '\0'; /* Chop of unit */
274 if (gmt_not_numeric (GMT, spacing)) return (5); /* Since spacing is not a number */
275 k = (int)strlen (width) - 1;
276 if (width[k] == 'c' || width[k] == 'i' || width[k] == 'm' || width[k] == 'p') width[k] = '\0'; /* Chop of unit */
277 if (gmt_not_numeric (GMT, width)) return (5); /* Since width is not a number */
278 if (!(pjust[0] == 'j' && pjust[1] == '\0') && gmt_just_decode (GMT, pjust, PSL_NONE) == -99) return (5);
279 }
280
281 /* Well, seems like the old format so far */
282 GMT_Report (GMT->parent, GMT_MSG_COMPAT, "Use of old style pstext input is deprecated.\n");
283 return (4);
284 }
285
usage(struct GMTAPI_CTRL * API,int level)286 static int usage (struct GMTAPI_CTRL *API, int level) {
287 /* This displays the pstext synopsis and optionally full usage information */
288 bool show_fonts = false;
289
290 const char *name = gmt_show_name_and_purpose (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_PURPOSE);
291 if (level & PSTEXT_SHOW_FONTS) show_fonts = true, level -= PSTEXT_SHOW_FONTS; /* Deal with the special bitflag for showing the fonts */
292 if (level == GMT_MODULE_PURPOSE) return (GMT_NOERROR);
293 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>]]] "
294 "[-F[+a[<angle>]][+c[<justify>]][+f[<font>]][+h|l|r[<first>]|+t<text>|+z[<fmt>]][+j[<justify>]]] %s "
295 "[-G[<color>][+n]] [-L] [-M] [-N] %s%s[-Ql|u] [-S[<dx>/<dy>/][<shade>]] [%s] [%s] [-W<pen>] [%s] [%s] [-Z] "
296 "[%s] %s[%s] [%s] [%s] [-it<word>] [%s] [%s] [%s] [%s] [%s] [%s]\n",
297 name, GMT_J_OPT, GMT_Rgeoz_OPT, GMT_B_OPT, API->K_OPT, API->O_OPT, API->P_OPT, GMT_U_OPT, GMT_X_OPT, GMT_Y_OPT,
298 GMT_V_OPT, GMT_a_OPT, API->c_OPT, GMT_e_OPT, GMT_f_OPT, GMT_h_OPT, GMT_p_OPT, GMT_qi_OPT, GMT_tv_OPT,
299 GMT_w_OPT, GMT_colon_OPT, GMT_PAR_OPT);
300 GMT_Usage (API, -2, "Note: Reads <x,y[,fontinfo,angle,justify],text> records from <table> [or standard input], "
301 "OR (with -M) one or more text paragraphs with formatting info in the segment headers. "
302 "Built-in escape sequences:");
303 GMT_Usage (API, 3, "%s @~ toggles between current font and Symbol font.", GMT_LINE_BULLET);
304 GMT_Usage (API, 3, "%s @%%<no>%% switches to font number <no>; @%%%% resets font.", GMT_LINE_BULLET);
305 GMT_Usage (API, 3, "%s @:<size>: switches font size; @:: resets font size.", GMT_LINE_BULLET);
306 GMT_Usage (API, 3, "%s @;<color>; switches font color; @;; resets font color.", GMT_LINE_BULLET);
307 GMT_Usage (API, 3, "%s @+ toggles between normal and superscript mode.", GMT_LINE_BULLET);
308 GMT_Usage (API, 3, "%s @- toggles between normal and subscript mode.", GMT_LINE_BULLET);
309 GMT_Usage (API, 3, "%s @# toggles between normal and Small Caps mode.", GMT_LINE_BULLET);
310 GMT_Usage (API, 3, "%s @_ toggles between normal and underlined text.", GMT_LINE_BULLET);
311 GMT_Usage (API, 3, "%s @!<char1><char2> makes one composite character.", GMT_LINE_BULLET);
312 GMT_Usage (API, 3, "%s @. prints the degree symbol.", GMT_LINE_BULLET);
313 GMT_Usage (API, 3, "%s @@ prints the @ sign itself.", GMT_LINE_BULLET);
314 GMT_Usage (API, 3, "%s @[<LaTeX expression>@[ may be used (except for -M).", GMT_LINE_BULLET);
315 GMT_Usage (API, -2, "Use @a|c|e|i|n|o|s|u|A|C|E|N|O|U for accented European characters. "
316 "See module documentation for more information.\n");
317
318 if (show_fonts) { /* List fonts */
319 unsigned int i;
320 char divider[38] = {"------------------------------------"};
321 int L = MAX (MIN (API->terminal_width-5, 37), 0); /* Number of dashes to print in divider line */
322 divider[L] = '\0'; /* Truncate the line */
323 GMT_Usage (API, -2, "Font Number and Name:");
324 gmt_message (API->GMT, " %s\n", divider);
325 for (i = 0; i < API->GMT->session.n_fonts; i++) {
326 gmt_message (API->GMT, "%7ld: ", i);
327 GMT_Usage (API, -9, "%s", API->GMT->session.font[i].name);
328 }
329 gmt_message (API->GMT, " %s\n", divider);
330 GMT_Usage (API, -2, "For additional fonts, see \"Using non-default fonts with GMT\" in the documentation.");
331 }
332
333 if (show_fonts) return (GMT_NOERROR);
334 if (level == GMT_SYNOPSIS) return (GMT_MODULE_SYNOPSIS);
335
336 GMT_Message (API, GMT_TIME_NONE, " REQUIRED ARGUMENTS:\n");
337 GMT_Usage (API, 1, "\n<table> is one or more ASCII files with text to be plotted. If no files are given, standard input is read.");
338 GMT_Option (API, "J-Z,R");
339 GMT_Message (API, GMT_TIME_NONE, "\n OPTIONAL ARGUMENTS:\n");
340 GMT_Usage (API, 1, "\n-A Angles given as azimuths; convert to directions using current projection.");
341 GMT_Option (API, "B-");
342 GMT_Usage (API, 1, "\n-C[<dx>/<dy>][+tc|C|o|O]");
343 GMT_Usage (API, -2, "Set the clearance between characters and surrounding box. Only used "
344 "if -W has been set. Append units {%s} or %% of fontsize [%d%%]. "
345 "Optionally append +t<shape> when -G and/or -W is used. Append a shape:", GMT_DIM_UNITS_DISPLAY, GMT_TEXT_CLEARANCE);
346 GMT_Usage (API, 3, "c: Concave rectangle (requires -M).");
347 GMT_Usage (API, 3, "C: Convex rectangle (requires -M).");
348 GMT_Usage (API, 3, "o: Rectangle [Default].");
349 GMT_Usage (API, 3, "O: Rectangle with rounded corners.");
350 GMT_Usage (API, 1, "\n-D[j|J]<dx>[/<dy>][+v[<pen>]]");
351 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]. "
352 "Use -Dj to move text origin away from point (direction determined by text's justification). "
353 "Upper case -DJ will shorten diagonal shifts at corners by sqrt(2). Cannot be used with -M. Optional modifier:");
354 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));
355 GMT_Usage (API, 1, "\n-F[+a[<angle>]][+c[<justify>]][+f[<font>]][+h|l|r[<first>]|+t<text>|+z[<fmt>]][+j[<justify>]]");
356 GMT_Usage (API, -2, "Specify values for text attributes that apply to all text records:");
357 GMT_Usage (API, 3, "+a Specify baseline <angle> for all text [0].");
358 GMT_Usage (API, 3, "+A As +a but force text-baselines in the -90/+90 range.");
359 GMT_Usage (API, 3, "+c Append <justify> to get the corresponding coordinate from the -R string instead of a given (x,y).");
360 GMT_Usage (API, 3, "+f Set size, font, and optionally the text color [%s].",
361 gmt_putfont (API->GMT, &API->GMT->current.setting.font_annot[GMT_PRIMARY]));
362 GMT_Usage (API, 3, "+j Set text justification relative to given (x,y) coordinate. "
363 "Give a 2-char combo from [T|M|B][L|C|R] (top/middle/bottom/left/center/right) [CM].");
364 GMT_Usage (API, -2, "Normally, the text is read from the data records. Alternative ways to provide text:");
365 GMT_Usage (API, 3, "+h Use as text the most recent segment header.");
366 GMT_Usage (API, 3, "+l Use as text the label specified via -L<label> in the most recent segment header.");
367 GMT_Usage (API, 3, "+r Use the current record number, starting at <first> [0].");
368 GMT_Usage (API, 3, "+t Use the appended <text> as is. Add modifier last if text contains + characters.");
369 GMT_Usage (API, 3, "+z Use formatted input z values (but see -Z) via format <fmt> [FORMAT_FLOAT_MAP].");
370 GMT_Usage (API, -2, "Note: If modifiers +f|a|j are not followed by a value then we read the information from the "
371 "data file in the order given by the -F option. Only one of +h or +l can be specified "
372 "and neither can be used in paragraph mode (-M).");
373 GMT_Usage (API, 1, "\n-G[<color>][+n]");
374 GMT_Usage (API, -2, "Paint the box underneath the text with specified color [Default is no paint]. "
375 "Alternatively, give no fill to plot text then activate clip paths based on text (and -C). "
376 "Use [ps]clip -C to deactivate the clipping. Cannot be used with paragraph mode (-M).");
377 GMT_Usage (API, 3, "+n Do NOT plot the text but only activate clipping.");
378 GMT_Option (API, "K");
379 GMT_Usage (API, 1, "\n-L List the font-numbers and font-names available, then exits.");
380 GMT_Usage (API, 1, "\n-M Set paragraph text mode [Default is single item mode]. "
381 "Expects <x y fontinfo angle justify linespace parwidth parjust> in segment header "
382 "followed by lines with one or more paragraphs of text. "
383 "<parjust> is one of (l)eft, (c)enter, (r)ight, or (j)ustified.");
384 GMT_Usage (API, 1, "\n-N Do Not clip text that exceeds the map boundaries [Default will clip].");
385 GMT_Option (API, "O,P");
386 GMT_Usage (API, 1, "\n-Ql|u");
387 GMT_Usage (API, -2, "Force all text to be (l)lower or (u)pper-case [Default leaves text as is].");
388 GMT_Usage (API, 1, "\n-S[<dx>/<dy>/][<shade>]");
389 GMT_Usage (API, -2, "Plot a shadow behind the text box. Requires -G<color> to be given as well. "
390 "Append <dx>/<dy> to change offset [%gp/%gp] and/or <shade> to change the shade [gray50].", GMT_FRAME_CLEARANCE, -GMT_FRAME_CLEARANCE);
391 GMT_Option (API, "U,V");
392 gmt_pen_syntax (API->GMT, 'W', NULL, "Draw a box around the text with the specified pen [Default pen is %s].", NULL, 0);
393 GMT_Option (API, "X");
394 GMT_Usage (API, 1, "\n-Z For 3-D plots: Expect records to have a z-value in the 3rd column (i.e., x y z ...). "
395 "Note 1: -Z also sets -N. Note 2: If -F+z is used the text is based on the 4th data column.");
396 GMT_Option (API, "a,c,e,f,h");
397 GMT_Usage (API, 1, "\n-it<word>");
398 GMT_Usage (API, -2, "Append -it<word> to use word number <word> (0 is first) in the text as the label [all the text].");
399 GMT_Option (API, "p,qi,t");
400 GMT_Usage (API, -2, "Note: For plotting text with variable transparency read from file, give no value.");
401 GMT_Option (API, "w,:,.");
402
403 return (GMT_MODULE_USAGE);
404 }
405
parse(struct GMT_CTRL * GMT,struct PSTEXT_CTRL * Ctrl,struct GMT_OPTION * options)406 static int parse (struct GMT_CTRL *GMT, struct PSTEXT_CTRL *Ctrl, struct GMT_OPTION *options) {
407 /* This parses the options provided to pstext and sets parameters in Ctrl.
408 * Note Ctrl has already been initialized and non-zero default values set.
409 * Any GMT common options will override values set previously by other commands.
410 * It also replaces any file names specified as input or output with the data ID
411 * returned when registering these sources/destinations with the API.
412 */
413
414 int j, k;
415 unsigned int pos, n_errors = 0;
416 bool explicit_justify = false, mess = false;
417 char txt_a[GMT_LEN256] = {""}, txt_b[GMT_LEN256] = {""}, txt_c[GMT_LEN256] = {""}, p[GMT_BUFSIZ] = {""}, *c = NULL, *q = NULL;
418 struct GMT_OPTION *opt = NULL;
419 struct GMTAPI_CTRL *API = GMT->parent;
420
421 for (opt = options; opt; opt = opt->next) { /* Process all the options given */
422
423 switch (opt->option) {
424
425 case '<': /* Input files */
426 if (GMT_Get_FilePath (API, GMT_IS_DATASET, GMT_IN, GMT_FILE_REMOTE, &(opt->arg))) n_errors++;;
427 break;
428
429 /* Processes program-specific parameters */
430
431 case 'A': /* Getting azimuths rather than directions, must convert via map projection */
432 n_errors += gmt_M_repeated_module_option (API, Ctrl->A.active);
433 Ctrl->A.active = true;
434 break;
435 case 'C':
436 n_errors += gmt_M_repeated_module_option (API, Ctrl->C.active);
437 Ctrl->C.active = true;
438 c = strstr (opt->arg, "+t");
439 if ((c = strstr (opt->arg, "+t"))) {
440 if (c[2]) Ctrl->C.mode = c[2];
441 n_errors += gmt_M_check_condition (GMT, !strchr("oOcC", Ctrl->C.mode), "Option -C: Modifier +t must add o, O, c, or C\n");
442 c[0] = '\0'; /* Hide modifier */
443 }
444 if (opt->arg[0]) { /* Replace default settings with user settings */
445 Ctrl->C.percent = (strchr (opt->arg, '%')) ? true : false;
446 k = sscanf (opt->arg, "%[^/]/%s", txt_a, txt_b);
447 for (j = 0; txt_a[j]; j++) if (txt_a[j] == '%') txt_a[j] = '\0'; /* Remove % signs before processing values */
448 for (j = 0; k == 2 && txt_b[j]; j++) if (txt_b[j] == '%') txt_b[j] = '\0';
449 Ctrl->C.dx = (Ctrl->C.percent) ? atof (txt_a) : gmt_M_to_inch (GMT, txt_a);
450 Ctrl->C.dy = (k == 2) ? ((Ctrl->C.percent) ? atof (txt_b) : gmt_M_to_inch (GMT, txt_b)) : Ctrl->C.dx;
451 }
452 if (c) c[0] = '+'; /* Restore */
453 break;
454 case 'D':
455 n_errors += gmt_M_repeated_module_option (API, Ctrl->D.active);
456 Ctrl->D.active = true;
457 k = 0;
458 if (opt->arg[k] == 'j') { Ctrl->D.justify = 1, k++; }
459 else if (opt->arg[k] == 'J') { Ctrl->D.justify = 2, k++; }
460 for (j = k; opt->arg[j] && opt->arg[j] != 'v'; j++);
461 if (opt->arg[j] == 'v') { /* Want to draw a line from point to offset point */
462 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");
464 if (opt->arg[j-1] == '+') /* New-style syntax */
465 opt->arg[j-1] = 0;
466 else
467 opt->arg[j] = 0;
468 }
469 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;
472 break;
473 case 'F':
474 n_errors += gmt_M_repeated_module_option (API, Ctrl->F.active);
475 Ctrl->F.active = true;
476 pos = 0;
477 Ctrl->F.no_input = gmt_no_pstext_input (API, opt->arg);
478 if ((c = strstr (opt->arg, "+t")) && (q = strchr (&c[1], '+'))) { /* Worry about plus symbols in the text. If not a valid modifier then we hide the plus symbol for now */
479 c++; /* Advance past the + in +t */
480 gmt_strrepc (c, '+', 1); /* Replace any other + characters with 1 */
481 mess = true;
482 }
483
484 while (gmt_getmodopt (GMT, 'F', opt->arg, "Aafjclhrtz", &pos, p, &n_errors) && n_errors == 0 && Ctrl->F.nread < 4) { /* Looking for +f, +a|A, +j, +c, +l|h */
485 switch (p[0]) {
486 /* A|a, f, j may be read from input */
487 case 'A': /* orientation */
488 Ctrl->F.orientation = true;
489 /* Intentionally fall through - to next case */
490 case 'a': /* Angle */
491 if (p[1] == '+' || p[1] == '\0') { /* Must read angle from input */
492 Ctrl->F.read[Ctrl->F.nread] = p[0];
493 Ctrl->F.nread++;
494 }
495 else /* Gave a fixed angle here */
496 Ctrl->F.angle = atof (&p[1]);
497 break;
498 case 'f': /* Font info */
499 if (p[1] == '+' || p[1] == '\0') { /* Must read font from input */
500 Ctrl->F.read[Ctrl->F.nread] = p[0];
501 Ctrl->F.nread++;
502 Ctrl->F.read_font = true;
503 Ctrl->F.mixed = true;
504 }
505 else /* Gave a fixed font here */
506 n_errors += gmt_getfont (GMT, &p[1], &(Ctrl->F.font));
507 break;
508 case 'j': /* Justification */
509 if (p[1] == '+' || p[1] == '\0') { /* Must read justification from input */
510 Ctrl->F.read[Ctrl->F.nread] = p[0];
511 Ctrl->F.nread++;
512 Ctrl->F.mixed = true;
513 }
514 else { /* Gave a fixed code here */
515 Ctrl->F.justify = gmt_just_decode (GMT, &p[1], PSL_NO_DEF);
516 explicit_justify = true;
517 }
518 break;
519 case 'c': /* -R corner justification */
520 if (p[1] == '+' || p[1] == '\0') { /* Must read corner justification from input */
521 Ctrl->F.read[Ctrl->F.nread] = p[0];
522 Ctrl->F.nread++;
523 Ctrl->F.mixed = Ctrl->F.get_xy_from_justify = true;
524 }
525 else { /* Gave a fixed code here */
526 Ctrl->F.R_justify = gmt_just_decode (GMT, &p[1], PSL_NO_DEF);
527 if (!explicit_justify) /* If not set explicitly, default to same justification as corner */
528 Ctrl->F.justify = Ctrl->F.R_justify;
529 }
530 break;
531 case 'l': /* Segment label request */
532 if (Ctrl->F.get_text) {
533 GMT_Report (API, GMT_MSG_ERROR, "Option -F: Only one of +l, +h, +r, +t, +z can be selected.\n");
534 n_errors++;
535 }
536 else
537 Ctrl->F.get_text = GET_SEG_LABEL;
538 break;
539 case 'h': /* Segment header request */
540 if (Ctrl->F.get_text) {
541 GMT_Report (API, GMT_MSG_ERROR, "Option -F: Only one of +l, +h, +r, +t, +z can be selected.\n");
542 n_errors++;
543 }
544 else
545 Ctrl->F.get_text = GET_SEG_HEADER;
546 break;
547 case 'r': /* Record number */
548 if (Ctrl->F.get_text) {
549 GMT_Report (API, GMT_MSG_ERROR, "Option -F: Only one of +l, +h, +r, +t, +z can be selected.\n");
550 n_errors++;
551 }
552 else if (p[1])
553 Ctrl->F.first = atoi (&p[1]);
554 Ctrl->F.get_text = GET_REC_NUMBER;
555 break;
556 case 't': /* Use specified text string */
557 if (Ctrl->F.get_text) {
558 GMT_Report (API, GMT_MSG_ERROR, "Option -F: Only one of +l, +h, +r, +t, +z can be selected.\n");
559 n_errors++;
560 }
561 else
562 Ctrl->F.text = strdup (&p[1]);
563 if (mess) /* Restore ASCII 1 to + */
564 gmt_strrepc (Ctrl->F.text, 1, '+'); /* Put back the + characters */
565 Ctrl->F.get_text = GET_CMD_TEXT;
566 break;
567 case 'z': /* z-column formatted */
568 if (Ctrl->F.get_text) {
569 GMT_Report (API, GMT_MSG_ERROR, "Option -F: Only one of +l, +h, +r, +t, +z can be selected.\n");
570 n_errors++;
571 }
572 else
573 Ctrl->F.text = (p[1]) ? strdup (&p[1]) : strdup (GMT->current.setting.format_float_map);
574 Ctrl->F.get_text = GET_CMD_FORMAT;
575 break;
576 default: break; /* These are caught in gmt_getmodopt so break is just for Coverity */
577 }
578 }
579 if (mess) /* Put back the + characters */
580 gmt_strrepc (opt->arg, 1, '+');
581 break;
582 case 'G':
583 n_errors += gmt_M_repeated_module_option (API, Ctrl->G.active);
584 Ctrl->G.active = true;
585 if (!strcmp (opt->arg, "+n") || (opt->arg[0] == 'C' && !opt->arg[1])) /* Accept -GC or -G+n */
586 Ctrl->G.mode = PSTEXT_CLIPONLY;
587 else if (!opt->arg[0] || (opt->arg[0] == 'c' && !opt->arg[1])) /* Accept -Gc or -G */
588 Ctrl->G.mode = PSTEXT_CLIPPLOT;
589 else if (gmt_getfill (GMT, opt->arg, &Ctrl->G.fill)) {
590 gmt_fill_syntax (GMT, 'G', NULL, " ");
591 n_errors++;
592 }
593 break;
594 case 'L':
595 n_errors += gmt_M_repeated_module_option (API, Ctrl->L.active);
596 Ctrl->L.active = true;
597 break;
598 case 'm':
599 if (gmt_M_compat_check (GMT, 4)) /* Warn and pass through */
600 GMT_Report (API, GMT_MSG_COMPAT, "-m option is deprecated and reverted back to -M to indicate paragraph mode.\n");
601 else
602 n_errors += gmt_default_error (GMT, opt->option);
603 /* Intentionally fall through */
604 case 'M': /* Paragraph mode */
605 n_errors += gmt_M_repeated_module_option (API, Ctrl->M.active);
606 Ctrl->M.active = true;
607 break;
608 case 'N': /* Do not clip at border */
609 n_errors += gmt_M_repeated_module_option (API, Ctrl->N.active);
610 Ctrl->N.active = true;
611 break;
612 case 'S':
613 n_errors += gmt_M_repeated_module_option (API, Ctrl->S.active);
614 if (opt->arg[0] == '\0' || (k = gmt_count_char (GMT, opt->arg, '/')) > 0 || gmt_is_fill (GMT, opt->arg)) {
615 /* -S[<dx>/<dy>/][>shade>]; requires -G */
616 Ctrl->S.active = true;
617 if (opt->arg[0]) {
618 k = sscanf (opt->arg, "%[^/]/%[^/]/%s", txt_a, txt_b, txt_c);
619 if (k == 1) { /* Just got a new fill */
620 if (gmt_getfill (GMT, txt_a, &Ctrl->S.fill)) n_errors++;
621 }
622 else if (k == 2) { /* Just got a new offset */
623 if (gmt_get_pair (GMT, opt->arg, GMT_PAIR_DIM_DUP, Ctrl->S.off) < 0) n_errors++;
624 }
625 else if (k == 3) { /* Got offset and fill */
626 Ctrl->S.off[GMT_X] = gmt_M_to_inch (GMT, txt_a);
627 Ctrl->S.off[GMT_Y] = gmt_M_to_inch (GMT, txt_b);
628 if (gmt_getfill (GMT, txt_c, &Ctrl->S.fill)) n_errors++;
629 }
630 else n_errors++;
631 } /* else we stick with the defaults */
632 }
633 else if (gmt_M_compat_check (GMT, 4)) { /* Warn and pass through */
634 GMT_Report (API, GMT_MSG_COMPAT, "-S<pen> option is deprecated; use font pen setting instead.\n");
635 Ctrl->S_old.active = true;
636 if (gmt_getpen (GMT, opt->arg, &Ctrl->S_old.pen)) {
637 gmt_pen_syntax (GMT, 'S', NULL, "draws outline of characters. Append pen attributes [Default pen is %s]", NULL, 0);
638 n_errors++;
639 }
640 }
641 else
642 n_errors += gmt_default_error (GMT, opt->option);
643 break;
644 case 'Q':
645 n_errors += gmt_M_repeated_module_option (API, Ctrl->Q.active);
646 Ctrl->Q.active = true;
647 if (opt->arg[0] == 'l') Ctrl->Q.mode = -1;
648 if (opt->arg[0] == 'u') Ctrl->Q.mode = +1;
649 break;
650 case 'T':
651 if (gmt_M_compat_check (GMT, 5)) { /* Warn and pass through */
652 GMT_Report (API, GMT_MSG_COMPAT, "-T option is deprecated; use modifier +t in -C instead.\n");
653 if (opt->arg[0]) Ctrl->C.mode = opt->arg[0];
654 n_errors += gmt_M_check_condition (GMT, !strchr("oOcC", Ctrl->C.mode), "Option -T: must add o, O, c, or C\n");
655 }
656 else
657 n_errors += gmt_default_error (GMT, opt->option);
658 break;
659 case 'W':
660 n_errors += gmt_M_repeated_module_option (API, Ctrl->W.active);
661 Ctrl->W.active = true;
662 if (gmt_getpen (GMT, opt->arg, &Ctrl->W.pen)) {
663 gmt_pen_syntax (GMT, 'W', NULL, "draws a box around the text with the specified pen [Default pen is %s]", NULL, 0);
664 n_errors++;
665 }
666 break;
667 case 'Z':
668 n_errors += gmt_M_repeated_module_option (API, Ctrl->Z.active);
669 /* For backward compatibility we will see -Z+ as the current -Z
670 * and -Z<level> as an alternative to -p<az>/<el>/<level> */
671 if (opt->arg[0] == '+' && !opt->arg[1]) /* Deprecated -Z+ option */
672 Ctrl->Z.active = true;
673 else if (opt->arg[0]) /* Deprecated -Z<level> option */
674 GMT->current.proj.z_level = atof(opt->arg);
675 else /* Normal -Z only */
676 Ctrl->Z.active = true;
677 break;
678
679 case 'i': /* Local -i option for pstext to select a specific word in the text */
680 if (opt->arg[0] != 't') {
681 GMT_Report (API, GMT_MSG_ERROR, "Option -i: Must give -it<word> from 0 (first) to nwords-1.\n");
682 n_errors++;
683 }
684 else {
685 Ctrl->F.word = true;
686 Ctrl->F.w_col = atoi (&opt->arg[1]);
687 if (Ctrl->F.w_col < 0) {
688 GMT_Report (API, GMT_MSG_ERROR, "Option -it<word>: Must select <word> from 0 (first) to nwords-1.\n");
689 n_errors++;
690 }
691 else
692 Ctrl->F.w_col++; /* So 0th word is 1 */
693 }
694 break;
695
696 default: /* Report bad options */
697 n_errors += gmt_default_error (GMT, opt->option);
698 break;
699 }
700 }
701
702 /* Check that the options selected are mutually consistent */
703
704 if (API->external && Ctrl->F.active && Ctrl->F.nread) { /* Impose order on external interfaces */
705 n_errors += gmt_M_check_condition (GMT, Ctrl->F.nread == 2 && tolower (Ctrl->F.read[1]) == 'a', "Option -F: Must list +a before +c, +f, +j for external API\n");
706 n_errors += gmt_M_check_condition (GMT, Ctrl->F.nread == 3 && (tolower (Ctrl->F.read[1]) == 'a' || tolower (Ctrl->F.read[2]) == 'a'), "Option -F: Must list +a before +c, +f, +j for external API\n");
707 n_errors += gmt_M_check_condition (GMT, Ctrl->F.nread == 4 && (tolower (Ctrl->F.read[2]) == 'a' || tolower (Ctrl->F.read[2]) == 'a' || tolower (Ctrl->F.read[3]) == 'a'), "Option -F: Must list +a before +c, +f, +j for external API\n");
708 }
709 n_errors += gmt_M_check_condition (GMT, !Ctrl->L.active && !GMT->common.R.active[RSET], "Must specify -R option\n");
710 n_errors += gmt_M_check_condition (GMT, !Ctrl->L.active && !GMT->common.J.active, "Must specify a map projection with the -J option\n");
711 n_errors += gmt_M_check_condition (GMT, Ctrl->C.dx < 0.0 || Ctrl->C.dy < 0.0, "Option -C: clearances cannot be negative!\n");
712 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");
713 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");
714 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");
715 n_errors += gmt_M_check_condition (GMT, Ctrl->G.mode && Ctrl->M.active, "Option -Gc: Cannot be used with -M.\n");
716 n_errors += gmt_M_check_condition (GMT, Ctrl->G.mode && Ctrl->W.active, "Option -Gc: Cannot be used with -W.\n");
717 n_errors += gmt_M_check_condition (GMT, Ctrl->G.mode && Ctrl->D.line, "Option -Gc: Cannot be used with -D...v<pen>.\n");
718 n_errors += gmt_M_check_condition (GMT, Ctrl->M.active && Ctrl->F.get_text, "Option -M: Cannot be used with -F...+l|h.\n");
719 n_errors += gmt_M_check_condition (GMT, Ctrl->S.active && !(Ctrl->G.active && Ctrl->G.mode == 0), "Option -S: Requires -G as well.\n");
720 n_errors += gmt_M_check_condition (GMT, strchr ("cC", Ctrl->C.mode) && !Ctrl->M.active, "Option -C: Box shape mode +tc|C is only available when -M is selected.\n");
721
722 return (n_errors ? GMT_PARSE_ERROR : GMT_NOERROR);
723 }
724
pstext_add_xy_via_justify(struct GMT_CTRL * GMT,int justify)725 GMT_LOCAL void pstext_add_xy_via_justify (struct GMT_CTRL *GMT, int justify) {
726 /* If -F+c, compute the missing x,y and place in current input record */
727 int ix, iy;
728
729 ix = (GMT->current.setting.io_lonlat_toggle[GMT_IN]); iy = 1 - ix;
730 gmt_just_to_xy (GMT, justify, &GMT->current.io.curr_rec[ix], &GMT->current.io.curr_rec[iy]);
731 GMT->current.io.curr_rec[GMT_Z] = GMT->current.proj.z_level;
732 }
733
pstext_validate_coord_and_text(struct GMT_CTRL * GMT,struct PSTEXT_CTRL * Ctrl,int rec_no,char * record,char buffer[])734 GMT_LOCAL int pstext_validate_coord_and_text (struct GMT_CTRL *GMT, struct PSTEXT_CTRL *Ctrl, int rec_no, char *record, char buffer[]) {
735 /* Paragraph mode: Parse x,y [and z], check for validity, and return the rest of the text in buffer */
736 int ix, iy, nscan = 0;
737 unsigned int pos = 0;
738 char txt_x[GMT_LEN256] = {""}, txt_y[GMT_LEN256] = {""}, txt_z[GMT_LEN256] = {""}, txt_t[GMT_LEN256] = {""};
739
740 ix = (GMT->current.setting.io_lonlat_toggle[GMT_IN]); iy = 1 - ix;
741 buffer[0] = '\0'; /* Initialize buffer to NULL */
742
743 if (Ctrl->Z.active) { /* Expect z in 3rd column */
744 if (gmt_strtok (record, GMT->current.io.scan_separators, &pos, txt_x)) nscan++; /* Returns xcol and update pos */
745 if (gmt_strtok (record, GMT->current.io.scan_separators, &pos, txt_y)) nscan++; /* Returns ycol and update pos */
746 if (gmt_strtok (record, GMT->current.io.scan_separators, &pos, txt_z)) nscan++; /* Returns zcol and update pos */
747 if (GMT->common.t.variable && gmt_strtok (record, GMT->current.io.scan_separators, &pos, txt_t)) nscan++; /* Returns first tcol and update pos */
748 if (GMT->common.t.n_transparencies == 2 && gmt_strtok (record, GMT->current.io.scan_separators, &pos, txt_t)) nscan++; /* Returns second tcol and update pos */
749 strcpy (buffer, &record[pos]);
750 sscanf (&record[pos], "%[^\n]\n", buffer); nscan++; /* Since sscanf could return -1 if nothing we increment nscan always */
751 if ((gmt_scanf (GMT, txt_z, gmt_M_type (GMT, GMT_IN, GMT_Z), &GMT->current.io.curr_rec[GMT_Z]) == GMT_IS_NAN)) {
752 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Record %d had bad z coordinate, skipped)\n", rec_no);
753 return (-1);
754 }
755 if ((gmt_scanf (GMT, txt_t, GMT_IS_FLOAT, &GMT->current.io.curr_rec[3]) == GMT_IS_NAN)) {
756 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Record %d had bad transparency, skipped)\n", rec_no);
757 return (-1);
758 }
759 }
760 else if (Ctrl->F.R_justify) {
761 gmt_just_to_xy (GMT, Ctrl->F.R_justify, &GMT->current.io.curr_rec[ix], &GMT->current.io.curr_rec[iy]);
762 nscan = 2; /* Since x,y are implicit */
763 nscan += sscanf (record, "%[^\n]\n", buffer);
764 GMT->current.io.curr_rec[GMT_Z] = GMT->current.proj.z_level;
765 }
766 else {
767 if (gmt_strtok (record, GMT->current.io.scan_separators, &pos, txt_x)) nscan++; /* Returns xcol and update pos */
768 if (gmt_strtok (record, GMT->current.io.scan_separators, &pos, txt_y)) nscan++; /* Returns ycol and update pos */
769 if (GMT->common.t.variable && gmt_strtok (record, GMT->current.io.scan_separators, &pos, txt_t)) nscan++; /* Returns first tcol and update pos */
770 if (GMT->common.t.n_transparencies == 2 && gmt_strtok (record, GMT->current.io.scan_separators, &pos, txt_t)) nscan++; /* Returns first tcol and update pos */
771 sscanf (&record[pos], "%[^\n]\n", buffer); nscan++; /* Since sscanf could return -1 if nothing we increment nscan always */
772 GMT->current.io.curr_rec[GMT_Z] = GMT->current.proj.z_level;
773 if ((gmt_scanf (GMT, txt_t, GMT_IS_FLOAT, &GMT->current.io.curr_rec[2]) == GMT_IS_NAN)) {
774 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Record %d had bad transparency, skipped)\n", rec_no);
775 return (-1);
776 }
777 }
778
779 if (!Ctrl->F.R_justify) {
780 if (gmt_scanf (GMT, txt_x, gmt_M_type (GMT, GMT_IN, GMT_X), &GMT->current.io.curr_rec[ix]) == GMT_IS_NAN) {
781 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Record %d had bad x coordinate, skipped)\n", rec_no);
782 return (-1);
783 }
784 if (gmt_scanf (GMT, txt_y, gmt_M_type (GMT, GMT_IN, GMT_Y), &GMT->current.io.curr_rec[iy]) == GMT_IS_NAN) {
785 GMT_Report (GMT->parent, GMT_MSG_ERROR, "Record %d had bad y coordinate, skipped)\n", rec_no);
786 return (-1);
787 }
788 }
789 return (nscan);
790 }
791
792 #define bailout(code) {gmt_M_free_options (mode); return (code);}
793 #define Return(code) {Free_Ctrl (GMT, Ctrl); gmt_end_module (GMT, GMT_cpy); bailout (code);}
794
pstext_get_label(struct GMT_CTRL * GMT,struct PSTEXT_CTRL * Ctrl,char * txt)795 GMT_LOCAL char *pstext_get_label (struct GMT_CTRL *GMT, struct PSTEXT_CTRL *Ctrl, char *txt) {
796 char *out = NULL;
797 if (Ctrl->F.word) { /* Must output a specific word from the trailing text only */
798 char *word = NULL, *orig = strdup (txt), *trail = orig;
799 int col = 0;
800 while (col != Ctrl->F.w_col && (word = strsep (&trail, GMT_TOKEN_SEPARATORS)) != NULL) {
801 if (*word != '\0') /* Skip empty strings */
802 col++;
803 }
804 if (word) /* Only write word if not NULL */
805 out = strdup (word);
806 else {
807 GMT_Report (GMT->parent, GMT_MSG_WARNING, "Trailing text did not have %d words (only %d found) - no label selected.\n", Ctrl->F.w_col, col);
808 out = strdup ("");
809 }
810 gmt_M_str_free (orig);
811 }
812 else /* Return copy of the whole enchilada */
813 out = strdup (txt);
814 return (out); /* The main program must free this at the end of each record processing */
815 }
816
GMT_pstext(void * V_API,int mode,void * args)817 EXTERN_MSC int GMT_pstext (void *V_API, int mode, void *args) {
818 /* High-level function that implements the pstext task */
819
820 int error = 0, k, fmode, nscan = 0, *c_just = NULL;
821 int input_format_version = GMT_NOTSET, rec_number = 0;
822
823 bool master_record = false, skip_text_records = false, old_is_world, clip_set = false, no_in_txt, check_if_outside;
824
825 unsigned int length = 0, n_paragraphs = 0, n_add, m = 0, pos, text_col, rec_mode, a_col = 0, tcol_f = 0, tcol_s = 0;
826 unsigned int n_read = 0, n_processed = 0, txt_alloc = 0, add, n_expected_cols, z_col = GMT_Z, n_skipped = 0;
827
828 size_t n_alloc = 0;
829
830 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;
831 double offset[2], tmp, *c_x = NULL, *c_y = NULL, *c_angle = NULL;
832
833 char text[GMT_BUFSIZ] = {""}, cp_line[GMT_BUFSIZ] = {""}, label[GMT_BUFSIZ] = {""}, buffer[GMT_BUFSIZ] = {""};
834 char pjust_key[5] = {""}, txt_a[GMT_LEN256] = {""}, txt_b[GMT_LEN256] = {""}, txt_f[GMT_LEN256] = {""};
835 char *paragraph = NULL, *line = NULL, *curr_txt = NULL, *in_txt = NULL, **c_txt = NULL, *use_text = NULL;
836 char this_size[GMT_LEN256] = {""}, this_font[GMT_LEN256] = {""}, just_key[5] = {""}, save_h_chars[GMT_LEN32] = {""};
837
838 enum GMT_enum_geometry geometry;
839
840 struct GMT_FONT *c_font = NULL;
841 struct PSTEXT_INFO T;
842 struct GMT_RECORD *In = NULL;
843 struct PSTEXT_CTRL *Ctrl = NULL;
844 struct GMT_CTRL *GMT = NULL, *GMT_cpy = NULL; /* General GMT internal parameters */
845 struct GMT_OPTION *options = NULL;
846 struct PSL_CTRL *PSL = NULL; /* General PSL internal parameters */
847 struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API); /* Cast from void to GMTAPI_CTRL pointer */
848
849 /*----------------------- Standard module initialization and parsing ----------------------*/
850
851 if (API == NULL) return (GMT_NOT_A_SESSION);
852 if (mode == GMT_MODULE_PURPOSE) return (usage (API, GMT_MODULE_PURPOSE)); /* Return the purpose of program */
853 options = GMT_Create_Options (API, mode, args); if (API->error) return (API->error); /* Set or get option list */
854
855 if ((error = gmt_report_usage (API, options, 0, usage)) != GMT_NOERROR) bailout (error); /* Give usage if requested */
856
857 /* Parse the command-line arguments; return if errors are encountered */
858
859 if ((GMT = gmt_init_module (API, THIS_MODULE_LIB, THIS_MODULE_CLASSIC_NAME, THIS_MODULE_KEYS, THIS_MODULE_NEEDS, NULL, &options, &GMT_cpy)) == NULL) bailout (API->error); /* Save current state */
860 if (GMT_Parse_Common (API, THIS_MODULE_OPTIONS, options)) Return (API->error);
861 Ctrl = New_Ctrl (GMT); /* Allocate and initialize a new control structure */
862 if ((error = parse (GMT, Ctrl, options)) != 0) Return (error);
863 if (Ctrl->L.active) Return (usage (API, GMT_SYNOPSIS | PSTEXT_SHOW_FONTS)); /* Return the synopsis with font listing */
864
865 /*---------------------------- This is the pstext main code ----------------------------*/
866
867 GMT_Report (API, GMT_MSG_INFORMATION, "Processing input text table data\n");
868 pstext_load_parameters_pstext (GMT, &T, Ctrl); /* Pass info from Ctrl to T */
869 tcol_f = 2 + Ctrl->Z.active; tcol_s = tcol_f + 1;
870
871 n_expected_cols = 2 + Ctrl->Z.active + Ctrl->F.nread + GMT->common.t.n_transparencies; /* Normal number of columns to read, plus any text. This includes x,y */
872 if (Ctrl->M.active) n_expected_cols += 3;
873 no_in_txt = (Ctrl->F.get_text > 1); /* No text in the input record */
874
875 if (gmt_map_setup (GMT, GMT->common.R.wesn)) Return (GMT_PROJECTION_ERROR);
876
877 if (Ctrl->G.mode) GMT->current.ps.nclip = (Ctrl->N.active) ? +1 : +2; /* Signal that this program initiates clipping that will outlive this process */
878
879 if ((PSL = gmt_plotinit (GMT, options)) == NULL) Return (GMT_RUNTIME_ERROR);
880
881 gmt_plane_perspective (GMT, GMT->current.proj.z_project.view_plane, GMT->current.proj.z_level);
882 if (Ctrl->G.mode) /* Delayed clipping so plot -B first */
883 gmt_set_basemap_orders (GMT, GMT_BASEMAP_FRAME_BEFORE, GMT_BASEMAP_GRID_BEFORE, GMT_BASEMAP_ANNOT_BEFORE);
884 else
885 gmt_set_basemap_orders (GMT, Ctrl->N.active ? GMT_BASEMAP_FRAME_BEFORE : GMT_BASEMAP_FRAME_AFTER, GMT_BASEMAP_GRID_BEFORE, GMT_BASEMAP_ANNOT_BEFORE);
886 gmt_plotcanvas (GMT); /* Fill canvas if requested */
887 gmt_map_basemap (GMT);
888
889 if (gmt_M_is_dnan (Ctrl->F.font.size))
890 Ctrl->F.font.size = GMT->current.setting.font_annot[GMT_PRIMARY].size;
891
892 pstext_load_parameters_pstext (GMT, &T, Ctrl); /* Pass info from Ctrl to T */
893 add = !(T.x_offset == 0.0 && T.y_offset == 0.0);
894 if (add && Ctrl->D.justify) T.boxflag |= 64;
895
896 if (!(Ctrl->N.active || Ctrl->Z.active)) {
897 gmt_BB_clip_on (GMT, GMT->session.no_rgb, 3);
898 clip_set = true;
899 }
900
901 if (Ctrl->F.nread && tolower (Ctrl->F.read[0]) == 'a') a_col = 1; /* Must include the a col among the numerics */
902 /* Find start column of plottable text in the trailing text string */
903 text_col = Ctrl->F.nread - a_col;
904
905 old_is_world = GMT->current.map.is_world;
906 GMT->current.map.is_world = true;
907 check_if_outside = !(Ctrl->N.active || Ctrl->F.get_xy_from_justify || Ctrl->F.R_justify);
908
909 if (Ctrl->F.no_input) { /* Plot the single label and bail. However, must set up everything else as normal */
910 int ix, iy;
911 double coord[2];
912 ix = (GMT->current.setting.io_lonlat_toggle[GMT_IN]); iy = 1 - ix;
913
914 /* Here, in_txt holds the text we wish to plot */
915
916 strcpy (text, Ctrl->F.text); /* Since we may need to do some replacements below */
917 in_txt = text;
918 gmtlib_enforce_rgb_triplets (GMT, in_txt, GMT_BUFSIZ); /* If @; is used, make sure the color information passed on to ps_text is in r/b/g format */
919 if (Ctrl->Q.active) gmt_str_setcase (GMT, in_txt, Ctrl->Q.mode);
920 use_text = pstext_get_label (GMT, Ctrl, in_txt); /* In case there are words */
921 pstext_add_xy_via_justify (GMT, Ctrl->F.R_justify);
922 plot_x = GMT->current.io.curr_rec[ix]; plot_y = GMT->current.io.curr_rec[iy];
923 xx[0] = plot_x; yy[0] = plot_y;
924
925 if (Ctrl->A.active) {
926 gmt_xy_to_geo (GMT, &coord[GMT_X], &coord[GMT_Y], plot_x, plot_y); /* Need original coordinates */
927 save_angle = T.paragraph_angle; /* Since we might overwrite the default */
928 tmp = gmt_azim_to_angle (GMT, coord[GMT_X], coord[GMT_Y], 0.1, save_angle);
929 T.paragraph_angle = fmod (tmp + 360.0 + 90.0, 180.0) - 90.0; /* Ensure usable angles for text plotting */
930 if (fabs (T.paragraph_angle - tmp) > 179.0) T.block_justify -= 2 * (T.block_justify%4 - 2); /* Flip any L/R code */
931 }
932 if (Ctrl->F.orientation) {
933 if (T.paragraph_angle > 180.0) T.paragraph_angle -= 360.0;
934 if (T.paragraph_angle > 90.0) T.paragraph_angle -= 180.0;
935 else if (T.paragraph_angle < -90.0) T.paragraph_angle += 180.0;
936 }
937 if (add) {
938 if (Ctrl->D.justify) /* Smart offset according to justification (from Dave Huang) */
939 gmt_smart_justify (GMT, T.block_justify, T.paragraph_angle, T.x_offset, T.y_offset, &plot_x, &plot_y, Ctrl->D.justify);
940 else { /* Default hard offset */
941 plot_x += T.x_offset;
942 plot_y += T.y_offset;
943 }
944 xx[1] = plot_x; yy[1] = plot_y;
945 }
946
947 PSL_setfont (PSL, T.font.id);
948 gmt_plane_perspective (GMT, GMT->current.proj.z_project.view_plane, 0.0);
949 if (T.boxflag & 32) { /* Draw line from original point to shifted location */
950 gmt_setpen (GMT, &T.vecpen);
951 PSL_plotsegment (PSL, xx[0], yy[0], xx[1], yy[1]);
952 }
953 if (!Ctrl->G.mode && T.boxflag & 3) { /* Plot the box beneath the text */
954 if (T.space_flag) { /* Meant % of fontsize */
955 offset[0] = 0.01 * T.x_space * T.font.size / PSL_POINTS_PER_INCH;
956 offset[1] = 0.01 * T.y_space * T.font.size / PSL_POINTS_PER_INCH;
957 }
958 else {
959 offset[0] = T.x_space;
960 offset[1] = T.y_space;
961 }
962 if (Ctrl->S.active) { /* Lay down shaded box first */
963 PSL_setfill (PSL, Ctrl->S.fill.rgb, 0); /* shade color */
964 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);
965 }
966 gmt_setpen (GMT, &T.boxpen); /* Box pen */
967 PSL_setfill (PSL, T.boxfill.rgb, T.boxflag & 1); /* Box color */
968 PSL_plottextbox (PSL, plot_x, plot_y, T.font.size, use_text, T.paragraph_angle, T.block_justify, offset, T.boxflag & 4);
969 curr_txt = NULL; /* Text has now been encoded in the PS file */
970 }
971 else
972 curr_txt = use_text;
973 fmode = gmt_setfont (GMT, &T.font);
974
975 PSL_plottext (PSL, plot_x, plot_y, T.font.size, curr_txt, T.paragraph_angle, T.block_justify, fmode);
976
977 if (clip_set)
978 gmt_map_clip_off (GMT);
979 gmt_map_basemap (GMT);
980 gmt_plane_perspective (GMT, -1, 0.0);
981 gmt_plotend (GMT);
982 gmt_M_str_free (use_text);
983
984 Return (GMT_NOERROR);
985 }
986
987 in = GMT->current.io.curr_rec; /* Since text gets parsed and stored in this record */
988 if (Ctrl->F.read_font)
989 GMT->current.io.scan_separators = GMT_TOKEN_SEPARATORS_PSTEXT; /* Characters that may separate columns in ASCII records */
990 if (Ctrl->M.active) { /* There are no coordinates, just text lines */
991 rec_mode = GMT_READ_TEXT;
992 geometry = GMT_IS_TEXT;
993 GMT_Set_Columns (API, GMT_IN, 0, GMT_COL_FIX);
994 strncpy (save_h_chars, GMT->current.setting.io_head_marker_in, GMT_LEN32); /* Must allow quotes and percentage signs in paragraph text */
995 strcpy (GMT->current.setting.io_head_marker_in, "#");
996 }
997 else {
998 unsigned int ncol = Ctrl->Z.active; /* Input will have z */
999 unsigned int cmode = GMT_COL_FIX; /* Normally there will be trailing text */
1000 unsigned int code = 0;
1001 char *cmode_type[2] = {"with", "with no"}, *rtype[4] = {"", "data", "text", "mixed"};
1002 if (!Ctrl->F.get_xy_from_justify && Ctrl->F.R_justify == 0) ncol += 2; /* Expect input to have x,y */
1003 ncol += a_col; /* Might also have the angle among the numerical columns */
1004 if (Ctrl->F.get_text == GET_CMD_FORMAT) { /* Format z column into text */
1005 z_col = ncol - a_col; /* Normally this would be GMT_Z */
1006 ncol++; /* One more numerical column to read */
1007 rec_mode = (Ctrl->F.mixed) ? GMT_READ_MIXED : GMT_READ_DATA;
1008 geometry = (Ctrl->F.mixed) ? GMT_IS_NONE : GMT_IS_POINT;
1009 if (!Ctrl->F.mixed) cmode = GMT_COL_FIX_NO_TEXT;
1010 code = 1;
1011 PSL_settextmode (PSL, PSL_TXTMODE_MINUS); /* Replace hyphens with minus signs */
1012 }
1013 else if (Ctrl->F.get_text == GET_REC_NUMBER) { /* Format record number into text */
1014 rec_mode = (ncol) ? GMT_READ_MIXED : GMT_READ_DATA;
1015 geometry = (ncol) ? GMT_IS_NONE : GMT_IS_POINT;
1016 if (ncol == 0) cmode = GMT_COL_FIX_NO_TEXT;
1017 code = 1;
1018 }
1019 else { /* Text is part of the record */
1020 rec_mode = (ncol) ? GMT_READ_MIXED : GMT_READ_TEXT;
1021 geometry = (ncol) ? GMT_IS_NONE : GMT_IS_TEXT;
1022 }
1023 if (a_col) a_col = ncol - 1; /* Now refers to numerical column with the angle */
1024 if (GMT->common.t.variable) {
1025 if (GMT->common.t.mode & GMT_SET_FILL_TRANSP) {
1026 ncol++; /* Read fill transparencies from data file */
1027 tcol_f = ncol - 1; /* If there is fill transparency then this is the column to use */
1028 gmt_set_column_type (GMT, GMT_IN, tcol_f, GMT_IS_FLOAT);
1029 }
1030 if (GMT->common.t.mode & GMT_SET_PEN_TRANSP) {
1031 ncol++; /* Read stroke transparencies from data file */
1032 tcol_s = ncol - 1;
1033 gmt_set_column_type (GMT, GMT_IN, tcol_s, GMT_IS_FLOAT);
1034 }
1035 }
1036 GMT_Report (API, GMT_MSG_DEBUG, "Expects a %s record with %d leading numerical columns, followed by %d text parameters and %s trailing text\n",
1037 rtype[rec_mode], ncol, Ctrl->F.nread - a_col, cmode_type[code]);
1038 GMT_Set_Columns (API, GMT_IN, ncol, cmode);
1039 GMT->current.io.curr_rec[GMT_Z] = GMT->current.proj.z_level; /* In case there are 3-D going on */
1040 }
1041 if (GMT_Init_IO (API, GMT_IS_DATASET, geometry, GMT_IN, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) { /* Register data input */
1042 Return (API->error);
1043 }
1044 if (GMT_Begin_IO (API, GMT_IS_DATASET, GMT_IN, GMT_HEADER_ON) != GMT_NOERROR) { /* Enables data input and sets access mode */
1045 Return (API->error);
1046 }
1047
1048 if (Ctrl->G.mode) { /* Need arrays to keep all the information until we lay it down in PSL */
1049 n_alloc = 0;
1050 gmt_M_malloc3 (GMT, c_angle, c_x, c_y, GMT_SMALL_CHUNK, &n_alloc, double);
1051 c_txt = gmt_M_memory (GMT, NULL, n_alloc, char *);
1052 c_just = gmt_M_memory (GMT, NULL, n_alloc, int);
1053 c_font = gmt_M_memory (GMT, NULL, n_alloc, struct GMT_FONT);
1054 }
1055 rec_number = Ctrl->F.first; /* Number of first output record label if -F+r<first> was selected */
1056
1057 do { /* Keep returning records until we have no more files */
1058 if ((In = GMT_Get_Record (API, rec_mode, NULL)) == NULL) { /* Keep returning records until we have no more files */
1059 if (gmt_M_rec_is_error (GMT)) {
1060 Return (GMT_RUNTIME_ERROR);
1061 }
1062 if (gmt_M_rec_is_table_header (GMT))
1063 continue; /* Skip table headers */
1064 if (gmt_M_rec_is_eof (GMT)) /* Reached end of file */
1065 break;
1066 /* Note: Blank lines may call through below - this is OK; hence no extra continue here */
1067 }
1068
1069 /* Data record or segment header (line == NULL) to process */
1070
1071 if (Ctrl->M.active) { /* Paragraph mode */
1072 if (gmt_M_rec_is_segment_header (GMT)) {
1073 line = GMT->current.io.segment_header;
1074 if (line[0] == '\0') continue; /* Can happen if reading from API memory */
1075 skip_text_records = false;
1076 if (n_processed) { /* Must output what we got */
1077 pstext_output_words (GMT, PSL, plot_x, plot_y, paragraph, &T, Ctrl);
1078 n_processed = length = 0;
1079 paragraph[0] = 0; /* Empty existing text */
1080 n_paragraphs++;
1081 }
1082
1083 if (line && (nscan = pstext_validate_coord_and_text (GMT, Ctrl, n_read, line, buffer)) == -1) continue; /* Failure */
1084
1085 if (Ctrl->F.R_justify) pstext_add_xy_via_justify (GMT, Ctrl->F.R_justify);
1086
1087 pos = 0;
1088
1089 if (gmt_M_compat_check (GMT, 4)) {
1090 if (input_format_version == GMT_NOTSET) input_format_version = pstext_get_input_format_version (GMT, buffer, 1);
1091 }
1092 if (input_format_version == 4) { /* Old-style GMT 4 records */
1093 nscan += sscanf (buffer, "%s %lf %s %s %s %s %s\n", this_size, &T.paragraph_angle, this_font, just_key, txt_a, txt_b, pjust_key);
1094 T.block_justify = gmt_just_decode (GMT, just_key, PSL_NO_DEF);
1095 T.line_spacing = gmt_M_to_inch (GMT, txt_a);
1096 T.paragraph_width = gmt_M_to_inch (GMT, txt_b);
1097 T.text_justify = (pjust_key[0] == 'j') ? PSL_JUST : gmt_just_decode (GMT, pjust_key, PSL_NONE);
1098 sprintf (txt_f, "%s,%s,", this_size, this_font); /* Merge size and font to be parsed by gmt_getfont */
1099 T.font = Ctrl->F.font;
1100 if (gmt_getfont (GMT, txt_f, &T.font)) GMT_Report (API, GMT_MSG_ERROR, "Record %d had bad font (set to %s)\n", n_read, gmt_putfont (GMT, &T.font));
1101 in_txt = NULL;
1102 n_expected_cols = 9 + Ctrl->Z.active;
1103 }
1104 else if (!Ctrl->F.nread) /* All attributes given via -F (or we accept defaults); skip to paragraph attributes */
1105 in_txt = buffer;
1106 else { /* Must pick up 1-3 attributes from data file */
1107 for (k = 0; k < Ctrl->F.nread; k++) {
1108 nscan += gmt_strtok (buffer, GMT->current.io.scan_separators, &pos, text);
1109 switch (Ctrl->F.read[k]) {
1110 case 'f':
1111 T.font = Ctrl->F.font;
1112 if (gmt_getfont (GMT, text, &T.font)) GMT_Report (API, GMT_MSG_ERROR, "Record %d had bad font (set to %s)\n", n_read, gmt_putfont (GMT, &T.font));
1113 break;
1114 case 'a': case 'A':
1115 T.paragraph_angle = atof (text);
1116 break;
1117 case 'j':
1118 T.block_justify = gmt_just_decode (GMT, text, PSL_NO_DEF);
1119 break;
1120 }
1121 }
1122 in_txt = &buffer[pos];
1123 }
1124
1125 if (in_txt) { /* Get the remaining parameters */
1126 nscan += sscanf (in_txt, "%s %s %s\n", txt_a, txt_b, pjust_key);
1127 T.text_justify = (pjust_key[0] == 'j') ? PSL_JUST : gmt_just_decode (GMT, pjust_key, PSL_NONE);
1128 T.line_spacing = gmt_M_to_inch (GMT, txt_a);
1129 T.paragraph_width = gmt_M_to_inch (GMT, txt_b);
1130 }
1131 if (T.block_justify == -99) {
1132 GMT_Report (API, GMT_MSG_ERROR, "Record %d had bad justification info (set to LB)\n", n_read);
1133 T.block_justify = 1;
1134 }
1135 if (nscan < (int)n_expected_cols) {
1136 GMT_Report (API, GMT_MSG_ERROR, "Record %d had incomplete paragraph information, skipped)\n", n_read);
1137 continue;
1138 }
1139 gmt_geo_to_xy (GMT, in[GMT_X], in[GMT_Y], &plot_x, &plot_y);
1140 if (check_if_outside) {
1141 skip_text_records = true; /* If this record should be skipped we must skip the whole paragraph */
1142 gmt_map_outside (GMT, in[GMT_X], in[GMT_Y]);
1143 if (abs (GMT->current.map.this_x_status) > 1 || abs (GMT->current.map.this_y_status) > 1) continue;
1144 skip_text_records = false; /* Since we got here we do not want to skip */
1145 }
1146 if (Ctrl->A.active) {
1147 save_angle = T.paragraph_angle; /* Since we might overwrite the default */
1148 tmp = gmt_azim_to_angle (GMT, in[GMT_X], in[GMT_Y], 0.1, save_angle);
1149 T.paragraph_angle = fmod (tmp + 360.0 + 90.0, 180.0) - 90.0; /* Ensure usable angles for text plotting */
1150 if (fabs (T.paragraph_angle - tmp) > 179.0) T.block_justify -= 2 * (T.block_justify%4 - 2); /* Flip any L/R code */
1151 }
1152 if (Ctrl->F.orientation) {
1153 if (T.paragraph_angle > 180.0) T.paragraph_angle -= 360.0;
1154 if (T.paragraph_angle > 90.0) T.paragraph_angle -= 180.0;
1155 else if (T.paragraph_angle < -90.0) T.paragraph_angle += 180.0;
1156 }
1157 master_record = true;
1158 }
1159 else { /* Text block record */
1160 line = In->text;
1161 if (line == NULL) {
1162 GMT_Report (API, GMT_MSG_ERROR, "Text record line %d is NULL! Skipped but this is trouble)\n", n_read);
1163 continue;
1164 }
1165 if (skip_text_records) continue; /* Skip all records for this paragraph */
1166 if (!master_record) {
1167 GMT_Report (API, GMT_MSG_ERROR, "Text record line %d not preceded by paragraph information, skipped)\n", n_read);
1168 continue;
1169 }
1170 strncpy (cp_line, line, GMT_BUFSIZ); /* Make a copy because in_line may be pointer to a strdup-ed line that we cannot enlarge */
1171 line = cp_line;
1172
1173 gmt_chop (line); /* Chop of line feed */
1174 gmtlib_enforce_rgb_triplets (GMT, line, GMT_BUFSIZ); /* If @; is used, make sure the color information passed on to ps_text is in r/b/g format */
1175
1176 if (line[0] == 0) { /* Blank line marked by single NULL character, replace by \r */
1177 n_add = 1;
1178 while ((length + n_add) > txt_alloc) {
1179 txt_alloc += GMT_BUFSIZ;
1180 paragraph = gmt_M_memory (GMT, paragraph, txt_alloc, char);
1181 }
1182 strcat (paragraph, "\r");
1183 }
1184 else {
1185 if (Ctrl->Q.active) gmt_str_setcase (GMT, line, Ctrl->Q.mode);
1186 n_add = (int)strlen (line) + 1;
1187 while ((length + n_add) > txt_alloc) {
1188 txt_alloc += GMT_BUFSIZ;
1189 paragraph = gmt_M_memory (GMT, paragraph, txt_alloc, char);
1190 }
1191 if (length) strcat (paragraph, " ");
1192 strcat (paragraph, line);
1193
1194 }
1195 length += n_add;
1196 n_processed++;
1197 }
1198 n_read++;
1199 }
1200 else { /* Plain style pstext input */
1201 double coord[2];
1202 int justify;
1203 if (gmt_M_rec_is_segment_header (GMT)) continue; /* Skip segment headers (line == NULL) */
1204 in = In->data;
1205 line = In->text;
1206 if (!no_in_txt) {
1207 if (line == NULL) {
1208 GMT_Report (API, GMT_MSG_ERROR, "Text record line %d is NULL! Skipped but this is trouble)\n", n_read);
1209 continue;
1210 }
1211 if (gmt_is_a_blank_line (line)) {
1212 n_skipped++;
1213 continue; /* Skip blank lines or # comments */
1214 }
1215 strncpy (cp_line, line, GMT_BUFSIZ-1); /* Make a copy because in_line may be pointer to a strdup-ed line that we cannot enlarge */
1216 line = cp_line;
1217 }
1218
1219 if (Ctrl->F.R_justify) pstext_add_xy_via_justify (GMT, Ctrl->F.R_justify);
1220 pos = 0; nscan = 3;
1221
1222 if (gmt_M_compat_check (GMT, 4)) {
1223 if (input_format_version == GMT_NOTSET) input_format_version = pstext_get_input_format_version (GMT, line, 0);
1224 }
1225 if (input_format_version == 4) { /* Old-style GMT 4 records */
1226 nscan--; /* Since we have already counted "text" */
1227 nscan += sscanf (line, "%s %lf %s %s %[^\n]\n", this_size, &T.paragraph_angle, this_font, just_key, text);
1228 T.block_justify = gmt_just_decode (GMT, just_key, PSL_NO_DEF);
1229 sprintf (txt_f, "%s,%s,", this_size, this_font); /* Merge size and font to be parsed by gmt_getfont */
1230 T.font = Ctrl->F.font;
1231 if (gmt_getfont (GMT, txt_f, &T.font)) GMT_Report (API, GMT_MSG_ERROR, "Record %d had bad font (set to %s)\n", n_read, gmt_putfont (GMT, &T.font));
1232 in_txt = text;
1233 n_expected_cols = 7 + Ctrl->Z.active;
1234 }
1235 else if (!Ctrl->F.nread) /* All attributes given via -F (or we accept defaults); just need text */
1236 in_txt = line;
1237 else { /* Must pick up 1-3 attributes from data file */
1238 for (k = 0; k < Ctrl->F.nread; k++) {
1239 switch (Ctrl->F.read[k]) {
1240 case 'a': case 'A':
1241 if (a_col)
1242 T.paragraph_angle = in[a_col];
1243 else {
1244 nscan += gmt_strtok (line, GMT->current.io.scan_separators, &pos, text);
1245 T.paragraph_angle = atof (text);
1246 }
1247 break;
1248 case 'c': /* Get x,y via code */
1249 nscan += gmt_strtok (line, GMT->current.io.scan_separators, &pos, text);
1250 justify = gmt_just_decode (GMT, text, PSL_NO_DEF);
1251 gmt_just_to_xy (GMT, justify, &coord[GMT_X], &coord[GMT_Y]);
1252 GMT->current.io.curr_rec[GMT_Z] = GMT->current.proj.z_level;
1253 break;
1254 case 'f':
1255 nscan += gmt_strtok (line, GMT->current.io.scan_separators, &pos, text);
1256 T.font = Ctrl->F.font;
1257 if (gmt_getfont (GMT, text, &T.font)) GMT_Report (API, GMT_MSG_ERROR, "Record %d had bad font (set to %s)\n", n_read, gmt_putfont (GMT, &T.font));
1258 if (gmt_M_compat_check (GMT, 4)) {
1259 if (Ctrl->S_old.active) {
1260 T.font.form |= 2;
1261 T.font.pen = Ctrl->S_old.pen;
1262 }
1263 }
1264 break;
1265 case 'j':
1266 nscan += gmt_strtok (line, GMT->current.io.scan_separators, &pos, text);
1267 T.block_justify = gmt_just_decode (GMT, text, PSL_NO_DEF);
1268 break;
1269 }
1270 }
1271 if (Ctrl->F.get_text == GET_REC_TEXT) in_txt = &line[pos];
1272 }
1273 if (Ctrl->F.get_text == GET_SEG_HEADER) {
1274 if (GMT->current.io.segment_header[0] == 0)
1275 GMT_Report (API, GMT_MSG_ERROR, "No active segment header to use; text is blank\n");
1276 strcpy (label, GMT->current.io.segment_header);
1277 in_txt = label;
1278 }
1279 else if (Ctrl->F.get_text == GET_SEG_LABEL) {
1280 if (!gmt_parse_segment_item (GMT, GMT->current.io.segment_header, "-L", label))
1281 GMT_Report (API, GMT_MSG_ERROR, "No active segment label to use; text is blank\n");
1282 in_txt = label;
1283 }
1284 else if (Ctrl->F.get_text == GET_CMD_TEXT) {
1285 strcpy (text, Ctrl->F.text); /* Since we may need to do some replacements below */
1286 in_txt = text;
1287 }
1288 else if (Ctrl->F.get_text == GET_REC_NUMBER) {
1289 sprintf (label, "%d", rec_number++);
1290 in_txt = label;
1291 }
1292 else if (Ctrl->F.get_text == GET_CMD_FORMAT) {
1293 sprintf (text, Ctrl->F.text, in[z_col]);
1294 in_txt = text;
1295 }
1296
1297 nscan += gmt_load_aspatial_string (GMT, GMT->current.io.OGR, text_col, in_txt); /* Substitute OGR attribute if used */
1298
1299 if (nscan < (int)n_expected_cols) {
1300 GMT_Report (API, GMT_MSG_ERROR, "Record %d is incomplete (skipped)\n", n_read);
1301 continue;
1302 }
1303 if (T.block_justify == -99) {
1304 GMT_Report (API, GMT_MSG_ERROR, "Record %d had bad justification info (set to LB)\n", n_read);
1305 T.block_justify = 1;
1306 }
1307
1308 /* Here, in_txt holds the text we wish to plot */
1309
1310 gmtlib_enforce_rgb_triplets (GMT, in_txt, GMT_BUFSIZ); /* If @; is used, make sure the color information passed on to ps_text is in r/b/g format */
1311 if (Ctrl->Q.active) gmt_str_setcase (GMT, in_txt, Ctrl->Q.mode);
1312 use_text = pstext_get_label (GMT, Ctrl, in_txt); /* In case there are words */
1313 if (gmt_text_is_latex (GMT, use_text)) {
1314 if (T.boxflag & 3) {
1315 GMT_Report (API, GMT_MSG_WARNING, "Record %d has LaTeX which cannot be used with box filling - skipping\n", n_read);
1316 gmt_M_str_free (use_text);
1317 continue;
1318 }
1319 else if (Ctrl->G.mode) {
1320 GMT_Report (API, GMT_MSG_WARNING, "Record %d has LaTeX which cannot be used with -G - skipping\n", n_read);
1321 gmt_M_str_free (use_text);
1322 continue;
1323 }
1324 }
1325 n_read++;
1326 if (Ctrl->F.get_xy_from_justify) {
1327 plot_x = coord[GMT_X], plot_y = coord[GMT_Y];
1328 }
1329 else if (Ctrl->F.R_justify)
1330 plot_x = in[GMT_X], plot_y = in[GMT_Y];
1331 else
1332 gmt_geo_to_xy (GMT, in[GMT_X], in[GMT_Y], &plot_x, &plot_y);
1333 xx[0] = plot_x; yy[0] = plot_y;
1334 if (check_if_outside) {
1335 gmt_map_outside (GMT, in[GMT_X], in[GMT_Y]);
1336 if (abs (GMT->current.map.this_x_status) > 1 || abs (GMT->current.map.this_y_status) > 1) continue;
1337 }
1338
1339 if (Ctrl->A.active) {
1340 save_angle = T.paragraph_angle; /* Since we might overwrite the default */
1341 tmp = gmt_azim_to_angle (GMT, in[GMT_X], in[GMT_Y], 0.1, save_angle);
1342 T.paragraph_angle = fmod (tmp + 360.0 + 90.0, 180.0) - 90.0; /* Ensure usable angles for text plotting */
1343 if (fabs (T.paragraph_angle - tmp) > 179.0) T.block_justify -= 2 * (T.block_justify%4 - 2); /* Flip any L/R code */
1344 }
1345 if (Ctrl->F.orientation) {
1346 if (T.paragraph_angle > 180.0) T.paragraph_angle -= 360.0;
1347 if (T.paragraph_angle > 90.0) T.paragraph_angle -= 180.0;
1348 else if (T.paragraph_angle < -90.0) T.paragraph_angle += 180.0;
1349 }
1350 if (add) {
1351 if (Ctrl->D.justify) /* Smart offset according to justification (from Dave Huang) */
1352 gmt_smart_justify (GMT, T.block_justify, T.paragraph_angle, T.x_offset, T.y_offset, &plot_x, &plot_y, Ctrl->D.justify);
1353 else { /* Default hard offset */
1354 plot_x += T.x_offset;
1355 plot_y += T.y_offset;
1356 }
1357 xx[1] = plot_x; yy[1] = plot_y;
1358 }
1359 n_paragraphs++;
1360
1361 if (GMT->common.t.variable) { /* Update the transparencies for current string (if -t was given) */
1362 double transp[2] = {0.0, 0.0}; /* None selected */
1363 if (GMT->common.t.n_transparencies == 2) { /* Requested two separate values to be read from file */
1364 transp[GMT_FILL_TRANSP] = 0.01 * in[tcol_f];
1365 transp[GMT_PEN_TRANSP] = 0.01 * in[tcol_s];
1366 }
1367 else if (GMT->common.t.mode & GMT_SET_FILL_TRANSP) { /* Gave fill transparency */
1368 transp[GMT_FILL_TRANSP] = 0.01 * in[tcol_f];
1369 if (GMT->common.t.n_transparencies == 0) transp[GMT_PEN_TRANSP] = transp[GMT_FILL_TRANSP]; /* Implied to be used for stroke also */
1370 }
1371 else { /* Gave stroke transparency */
1372 transp[GMT_PEN_TRANSP] = 0.01 * in[tcol_s];
1373 if (GMT->common.t.n_transparencies == 0) transp[GMT_FILL_TRANSP] = transp[GMT_PEN_TRANSP]; /* Implied to be used for fill also */
1374 }
1375 if (gmt_M_is_dnan (transp[GMT_FILL_TRANSP])) {
1376 GMT_Report (API, GMT_MSG_WARNING, "Record %d had bad fill transparency (NaN) - set to 0.0\n", n_read);
1377 transp[GMT_FILL_TRANSP] = 0.0;
1378 }
1379 else if (transp[GMT_FILL_TRANSP] < 0.0 || transp[GMT_FILL_TRANSP] > 100.0) {
1380 GMT_Report (API, GMT_MSG_WARNING, "Record %d had fill transparency out of range (%g) - set to 0.0\n", n_read, transp[GMT_FILL_TRANSP]);
1381 transp[GMT_FILL_TRANSP] = 0.0;
1382 }
1383 if (gmt_M_is_dnan (transp[GMT_PEN_TRANSP])) {
1384 GMT_Report (API, GMT_MSG_WARNING, "Record %d had bad stroke transparency (NaN) - set to 0.0\n", n_read);
1385 transp[GMT_PEN_TRANSP] = 0.0;
1386 }
1387 else if (transp[GMT_PEN_TRANSP] < 0.0 || transp[GMT_PEN_TRANSP] > 100.0) {
1388 GMT_Report (API, GMT_MSG_WARNING, "Record %d had stroke transparency out of range (%g) - set to 0.0\n", n_read, transp[GMT_PEN_TRANSP]);
1389 transp[GMT_PEN_TRANSP] = 0.0;
1390 }
1391 PSL_settransparencies (PSL, transp);
1392 }
1393 PSL_setfont (PSL, T.font.id);
1394 gmt_plane_perspective (GMT, GMT->current.proj.z_project.view_plane, in[GMT_Z]);
1395 if (T.boxflag & 32) { /* Draw line from original point to shifted location */
1396 gmt_setpen (GMT, &T.vecpen);
1397 PSL_plotsegment (PSL, xx[0], yy[0], xx[1], yy[1]);
1398 }
1399 if (!Ctrl->G.mode && T.boxflag & 3) { /* Plot the box beneath the text */
1400 if (T.space_flag) { /* Meant % of fontsize */
1401 offset[0] = 0.01 * T.x_space * T.font.size / PSL_POINTS_PER_INCH;
1402 offset[1] = 0.01 * T.y_space * T.font.size / PSL_POINTS_PER_INCH;
1403 }
1404 else {
1405 offset[0] = T.x_space;
1406 offset[1] = T.y_space;
1407 }
1408 if (Ctrl->S.active) { /* Lay down shaded box first */
1409 PSL_setfill (PSL, Ctrl->S.fill.rgb, 0); /* shade color */
1410 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);
1411 }
1412 gmt_setpen (GMT, &T.boxpen); /* Box pen */
1413 PSL_setfill (PSL, T.boxfill.rgb, T.boxflag & 1); /* Box color */
1414 PSL_plottextbox (PSL, plot_x, plot_y, T.font.size, use_text, T.paragraph_angle, T.block_justify, offset, T.boxflag & 4);
1415 curr_txt = NULL; /* Text has now been encoded in the PS file */
1416 }
1417 else
1418 curr_txt = use_text;
1419 fmode = gmt_setfont (GMT, &T.font);
1420 if (Ctrl->G.mode) {
1421 if (m <= n_alloc) {
1422 gmt_M_malloc3 (GMT, c_angle, c_x, c_y, m, &n_alloc, double);
1423 c_just = gmt_M_memory (GMT, c_just, n_alloc, int);
1424 c_txt = gmt_M_memory (GMT, c_txt, n_alloc, char *);
1425 c_font = gmt_M_memory (GMT, c_font, n_alloc, struct GMT_FONT);
1426 }
1427 c_angle[m] = T.paragraph_angle;
1428 c_txt[m] = strdup (curr_txt);
1429 c_x[m] = plot_x;
1430 c_y[m] = plot_y;
1431 c_just[m] = T.block_justify;
1432 c_font[m] = T.font;
1433 m++;
1434 }
1435 else
1436 gmt_map_text (GMT, plot_x, plot_y, &T.font, curr_txt, T.paragraph_angle, T.block_justify, fmode);
1437 if (Ctrl->A.active) T.paragraph_angle = save_angle; /* Restore original angle */
1438 gmt_M_str_free (use_text);
1439 }
1440
1441 } while (true);
1442
1443 if (GMT_End_IO (API, GMT_IN, 0) != GMT_NOERROR) { /* Disables further data input */
1444 Return (API->error);
1445 }
1446
1447 if (n_skipped && n_read == 0)
1448 GMT_Report (API, GMT_MSG_WARNING, "Skipped %u records as blank - please check input data.\n", n_skipped);
1449 PSL_settextmode (PSL, PSL_TXTMODE_HYPHEN); /* Back to leave as is */
1450
1451 if (GMT->common.t.variable) { /* Reset the transparencies */
1452 double transp[2] = {0.0, 0.0}; /* None selected */
1453 PSL_settransparencies (PSL, transp);
1454 }
1455
1456 if (Ctrl->M.active) {
1457 if (n_processed) { /* Must output the last paragraph */
1458 pstext_output_words (GMT, PSL, plot_x, plot_y, paragraph, &T, Ctrl);
1459 n_paragraphs++;
1460 }
1461 gmt_M_free (GMT, paragraph);
1462 strncpy (GMT->current.setting.io_head_marker_in, save_h_chars, GMT_LEN32); /* Restore original default */
1463 }
1464 if (Ctrl->G.mode && m) {
1465 int n_labels = m, form = (T.boxflag & 4) ? PSL_TXT_ROUND : 0; /* PSL_TXT_ROUND = Rounded rectangle */
1466 unsigned int kk;
1467 char *font = NULL, **fonts = NULL;
1468
1469 form |= PSL_TXT_INIT; /* To lay down all PSL attributes */
1470 if (Ctrl->G.mode == PSTEXT_CLIPPLOT) form |= PSL_TXT_SHOW; /* To place text */
1471 form |= PSL_TXT_CLIP_ON; /* To set clip path */
1472 gmt_textpath_init (GMT, &Ctrl->W.pen, Ctrl->G.fill.rgb);
1473 if (Ctrl->C.percent) { /* Meant % of fontsize */
1474 offset[0] = 0.01 * T.x_space * T.font.size / PSL_POINTS_PER_INCH;
1475 offset[1] = 0.01 * T.y_space * T.font.size / PSL_POINTS_PER_INCH;
1476 }
1477 else {
1478 offset[0] = T.x_space;
1479 offset[1] = T.y_space;
1480 }
1481 fonts = gmt_M_memory (GMT, NULL, m, char *);
1482 for (kk = 0; kk < m; kk++) {
1483 PSL_setfont (PSL, c_font[kk].id);
1484 #if 0
1485 psl_encodefont (PSL, PSL->current.font_no);
1486 #endif
1487 font = PSL_makefont (PSL, c_font[kk].size, c_font[kk].fill.rgb);
1488 fonts[kk] = strdup (font);
1489 }
1490 psl_set_int_array (PSL, "label_justify", c_just, m);
1491 psl_set_txt_array (PSL, "label_font", fonts, m);
1492 /* Turn clipping ON after [optionally] displaying the text */
1493 PSL_plottextline (PSL, NULL, NULL, NULL, 1, c_x, c_y, c_txt, c_angle, &n_labels, T.font.size, T.block_justify, offset, form);
1494 for (kk = 0; kk < m; kk++) {
1495 gmt_M_str_free (c_txt[kk]);
1496 gmt_M_str_free (fonts[kk]);
1497 }
1498 gmt_M_free (GMT, c_angle);
1499 gmt_M_free (GMT, c_x);
1500 gmt_M_free (GMT, c_y);
1501 gmt_M_free (GMT, c_txt);
1502 gmt_M_free (GMT, c_just);
1503 gmt_M_free (GMT, c_font);
1504 gmt_M_free (GMT, fonts);
1505 }
1506 else if (clip_set)
1507 gmt_map_clip_off (GMT);
1508
1509 GMT->current.map.is_world = old_is_world;
1510 GMT->current.io.scan_separators = GMT_TOKEN_SEPARATORS; /* Reset */
1511
1512 gmt_map_basemap (GMT);
1513 gmt_plane_perspective (GMT, -1, 0.0);
1514 gmt_plotend (GMT);
1515
1516 GMT_Report (API, GMT_MSG_INFORMATION, Ctrl->M.active ? "pstext: Plotted %d text blocks\n" : "pstext: Plotted %d text strings\n", n_paragraphs);
1517
1518 Return (GMT_NOERROR);
1519 }
1520
GMT_text(void * V_API,int mode,void * args)1521 EXTERN_MSC int GMT_text (void *V_API, int mode, void *args) {
1522 /* This is the GMT6 modern mode name */
1523 struct GMTAPI_CTRL *API = gmt_get_api_ptr (V_API); /* Cast from void to GMTAPI_CTRL pointer */
1524 if (API == NULL) return (GMT_NOT_A_SESSION);
1525 if (API->GMT->current.setting.run_mode == GMT_CLASSIC && !API->usage) {
1526 struct GMT_OPTION *options = GMT_Create_Options (API, mode, args);
1527 bool list_fonts = false;
1528 if (API->error) return (API->error); /* Set or get option list */
1529 list_fonts = (GMT_Find_Option (API, 'L', options) != NULL);
1530 gmt_M_free_options (mode);
1531 if (!list_fonts) {
1532 GMT_Report (API, GMT_MSG_ERROR, "Shared GMT module not found: text\n");
1533 return (GMT_NOT_A_VALID_MODULE);
1534 }
1535 }
1536 return GMT_pstext (V_API, mode, args);
1537 }
1538