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