Blob Blame History Raw
From 41b9bb838a3d544539f6e68aa4f87d70ef7d45ce Mon Sep 17 00:00:00 2001
From: Thomas Loimer <thomas.loimer@tuwien.ac.at>
Date: Sun, 5 Jan 2020 19:22:12 +0100
Subject: [PATCH 8/8] Replace most calls to fgets() by getline() in read.c

Also, fig files version 1.4 must begin with `#FIG 1.4`. Previously, a `#` in the
first line was sufficient to detect at least a version 1.4 fig file.
Move some variables with file scope into functions.

This commit fixes tickets #58, #59, #61, #62, #67, #78 and #79.

In fig2dev/lib/, replacements are provided for some library functions used in
fig2dev, e.g., strncasecmp(), strrchr(), etc. The getline() function was
introduced more recently than any of the functions provided in fig2dev/lib.
Nevertheless, for getline() a replacement function is not provided. It seems,
that all the replacement functions do not work, but nobody noticed. Therefore,
only provide a replacement function for getline() if that turns out to
be useful.
The replacement functions do not work, because a header file providing the
necessary function declarations is missing.
---
 configure.ac          |   3 +-
 fig2dev/fig2dev.c     |   4 +-
 fig2dev/fig2dev.h     |   4 +-
 fig2dev/read.c        | 904 +++++++++++++++++++++++-------------------
 fig2dev/read1_3.c     |  12 +-
 fig2dev/tests/read.at |  29 +-
 6 files changed, 533 insertions(+), 423 deletions(-)

diff --git a/configure.ac b/configure.ac
index 8e955ee..881a39d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -5,7 +5,7 @@ AC_COPYRIGHT([Fig2dev: Translate Fig code to various Devices
 Copyright (c) 1991 by Micah Beck
 Parts Copyright (c) 1985-1988 by Supoj Sutanthavibul
 Parts Copyright (c) 1989-2015 by Brian V. Smith
-Parts Copyright (c) 2015-2019 by Thomas Loimer
+Parts Copyright (c) 2015-2020 by Thomas Loimer
 
 Any party obtaining a copy of these files is granted, free of charge, a
 full and unrestricted irrevocable, world-wide, paid up, royalty-free,
@@ -327,6 +327,7 @@ dnl Just provide our own pi
 # example.
 AC_HEADER_STDBOOL
 AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
 
 #
 # Checks for library functions.
diff --git a/fig2dev/fig2dev.c b/fig2dev/fig2dev.c
index d8c5e2a..62ec099 100644
--- a/fig2dev/fig2dev.c
+++ b/fig2dev/fig2dev.c
@@ -3,7 +3,7 @@
  * Copyright (c) 1991 by Micah Beck
  * Parts Copyright (c) 1985-1988 by Supoj Sutanthavibul
  * Parts Copyright (c) 1989-2015 by Brian V. Smith
- * Parts Copyright (c) 2015-2019 by Thomas Loimer
+ * Parts Copyright (c) 2015-2020 by Thomas Loimer
  *
  * Any party obtaining a copy of these files is granted, free of charge, a
  * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
@@ -83,7 +83,7 @@ bool	bgspec = false;		/* flag to say -g was specified */
 bool support_i18n = false;
 #endif
 char	gif_transparent[20]="\0"; /* GIF transp color hex name (e.g. #ff00dd) */
-char	papersize[20];		/* paper size */
+char	papersize[];		/* paper size */
 char	boundingbox[64];	/* boundingbox */
 char	lang[40];		/* selected output language */
 RGB	background;		/* background (if specified by -g) */
diff --git a/fig2dev/fig2dev.h b/fig2dev/fig2dev.h
index 9b6515f..25cc86b 100644
--- a/fig2dev/fig2dev.h
+++ b/fig2dev/fig2dev.h
@@ -3,7 +3,7 @@
  * Copyright (c) 1991 by Micah Beck
  * Parts Copyright (c) 1985-1988 by Supoj Sutanthavibul
  * Parts Copyright (c) 1989-2015 by Brian V. Smith
- * Parts Copyright (c) 2015-2019 by Thomas Loimer
+ * Parts Copyright (c) 2015-2020 by Thomas Loimer
  *
  * Any party obtaining a copy of these files is granted, free of charge, a
  * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
@@ -101,7 +101,7 @@ extern bool	bgspec;		/* flag to say -g was specified */
 extern bool support_i18n;
 #endif
 extern char	gif_transparent[];/* GIF transp color hex name (e.g. #ff00dd) */
-extern char	papersize[];	/* paper size */
+extern char	papersize[16];	/* paper size */
 extern char	boundingbox[];	/* boundingbox */
 extern char	lang[];		/* selected output language */
 extern const char	*Fig_color_names[]; /* hex names for Fig colors */
diff --git a/fig2dev/read.c b/fig2dev/read.c
index 9500091..aea9537 100644
--- a/fig2dev/read.c
+++ b/fig2dev/read.c
@@ -3,7 +3,7 @@
  * Copyright (c) 1991 by Micah Beck
  * Parts Copyright (c) 1985-1988 by Supoj Sutanthavibul
  * Parts Copyright (c) 1989-2015 by Brian V. Smith
- * Parts Copyright (c) 2015-2019 by Thomas Loimer
+ * Parts Copyright (c) 2015-2020 by Thomas Loimer
  *
  * Any party obtaining a copy of these files is granted, free of charge, a
  * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
@@ -45,28 +45,34 @@ extern F_arrow	*make_arrow(int type, int style,	/* arrow.c */
 User_color	 user_colors[MAX_USR_COLS];		/* fig2dev.h */
 int		 user_col_indx[MAX_USR_COLS];		/* fig2dev.h */
 int		 num_usr_cols;				/* fig2dev.h */
-int		 num_object;		/* read1_3.c */
 					/* flags, psfonts.h, genps.c */
 int		 v2_flag;		/* Protocol V2.0 or higher */
 int		 v21_flag;		/* Protocol V2.1 or higher */
 int		 v30_flag;		/* Protocol V3.0 or higher */
 int		 v32_flag;		/* Protocol V3.2 or higher */
 
-static void		 read_colordef(void);
-static F_ellipse	*read_ellipseobject(void);
-static F_line		*read_lineobject(FILE *fp);
-static F_text		*read_textobject(FILE *fp);
-static F_spline		*read_splineobject(FILE *fp);
-static F_arc		*read_arcobject(FILE *fp);
-static F_compound	*read_compoundobject(FILE *fp);
+static void		 read_colordef(char *line, int line_no);
+static F_ellipse	*read_ellipseobject(char *line, int line_no);
+static F_line		*read_lineobject(FILE *fp, char **restrict line,
+					size_t *line_len, int *line_no);
+static F_text		*read_textobject(FILE *fp, char **restrict line,
+					size_t *line_len, int *line_no);
+static F_spline		*read_splineobject(FILE *fp, char **restrict line,
+					size_t *line_len, int *line_no);
+static F_arc		*read_arcobject(FILE *fp, char **restrict line,
+					size_t *line_len, int *line_no);
+static F_compound	*read_compoundobject(FILE *fp, char **restrict line,
+					size_t *line_len, int *line_no);
 static F_comment	*attach_comments(void);
-static void		 count_lines_correctly(FILE *fp);
-static void		 init_pats_used(void);
-static int		 read_objects(FILE *fp, F_compound *obj);
-static int		 get_line(FILE *fp);
-static void		 skip_line(FILE *fp);
-static int		 backslash_count(char cp[], int start);
-static int		 save_comment(void);
+static void		count_lines_correctly(FILE *fp, int *line_no);
+static void		init_pats_used(void);
+static int		read_objects(FILE *fp, F_compound *obj);
+static ssize_t		get_line(FILE *fp, char **restrict line,
+					size_t *line_len, int *line_no);
+static void		skip_line(FILE *fp);
+static ptrdiff_t	backslash_count(const char *restrict cp,
+					ptrdiff_t start);
+
 static char	Err_incomp[] = "Incomplete %s object at line %d.";
 static char	Err_invalid[] = "Invalid %s object at line %d.";
 static char	Err_arrow[] = "Invalid %s arrow at line %d.";
@@ -77,9 +83,6 @@ static char	Err_arrow[] = "Invalid %s arrow at line %d.";
 /* max number of comments that can be stored with each object */
 #define		MAXCOMMENTS	100
 
-static int	 gif_colnum = 0;
-static char	 buf[BUFSIZ];
-static int	 line_no = 0;
 static char	*comments[MAXCOMMENTS];	/* comments saved for current object */
 static int	 numcom;		/* current comment index */
 static bool	 com_alloc = false;	/* whether or not the comment array
@@ -148,7 +151,6 @@ readfp_fig(FILE *fp, F_compound *obj)
 	char	c;
 	int	i, status;
 
-	num_object = 0;
 	num_usr_cols = 0;
 	init_pats_used();
 
@@ -157,15 +159,14 @@ readfp_fig(FILE *fp, F_compound *obj)
 	/* initialize the comment array */
 	if (!com_alloc)
 		for (i = 0; i < MAXCOMMENTS; ++i)
-			comments[i] = (char *) NULL;
+			comments[i] = (char *)NULL;
 	com_alloc = true;
-	memset((char*)obj, '\0', COMOBJ_SIZE);
+	memset((void *)obj, '\0', COMOBJ_SIZE);
 
 	/* read first character to see if it is "#" (#FIG 1.4 and newer) */
 	c = fgetc(fp);
 	if (feof(fp))
 		return -2;
-	memset((char*)obj, '\0', COMOBJ_SIZE);
 	/* put the character back */
 	ungetc(c, fp);
 	if (c == '#')
@@ -185,25 +186,30 @@ read_objects(FILE *fp, F_compound *obj)
 	F_spline	*s, *ls = NULL;
 	F_arc		*a, *la = NULL;
 	F_compound	*c, *lc = NULL;
-	int		object, coord_sys, len;
-
-	memset((char*)obj, '\0', COMOBJ_SIZE);
-
-	(void) fgets(buf, BUFSIZ, fp);	/* get the version line */
-	if (strncmp(buf, "#FIG ", 5)) {
-		put_msg("Incorrect format string in first line of input file.");
+	bool		objects = false;
+	int		object, coord_sys;
+	int		line_no;
+	int		gif_colnum = 0;
+	char		*line;
+	char		buf[16];
+	size_t		line_len = 256;
+
+	/* Get the 15 chars of the first line.
+	   Use fgets(), because get_line() would store the line as a comment */
+	if (fgets(buf, sizeof buf, fp) == NULL) {
+		put_msg("Could not read input file.");
 		return -1;
 	}
+	/* seek to the end of the first line */
+	if (strchr(buf, '\n') == NULL) {
+		int	c;
+		do
+			c = fgetc(fp);
+		while (c != '\n' && c != EOF);
+	}
 
-	/* remove newline and any carriage return (from a PC, perhaps) */
-	len = strlen(buf);
-	if (buf[len-1] == '\n') {
-		if (buf[len-2] == '\r')
-			buf[len-2] = '\0';
-		else
-			buf[len-1] = '\0';
-	} else {	/* fgets() only stops at newline and end-of-file */
-		put_msg("File is truncated at first line.");
+	if (strncmp(buf, "#FIG ", 5)) {
+		put_msg("Incorrect format string in first line of input file.");
 		return -1;
 	}
 
@@ -211,49 +217,65 @@ read_objects(FILE *fp, F_compound *obj)
 	v2_flag = (!strncmp(buf, "#FIG 2", 6) || !strncmp(buf, "#FIG 3", 6));
 	/* v21_flag is for version 2.1 or higher */
 	v21_flag = (!strncmp(buf, "#FIG 2.1", 8) || !strncmp(buf, "#FIG 3", 6));
-	/* version 2.2 was only beta - 3.0 is the official release (they are identical) */
+	/* version 2.2 was only beta - 3.0 is the official release
+	   (they are identical) */
 	v30_flag = (!strncmp(buf, "#FIG 3", 6) || !strncmp(buf, "#FIG 2.2", 8));
-	/* version 3.2 contains paper size, magnif, multiple page and transparent color
-	   in Fig file */
+	/* version 3.2 contains paper size, magnif, multiple page
+	   and transparent color in Fig file */
 	v32_flag = (!strncmp(buf, "#FIG 3.2", 8));
 	if (strncmp(&buf[5], PACKAGE_VERSION, 3) > 0) {
-	    put_msg("Fig file format (%s) newer than this version of fig2dev (%s), exiting",
-			&buf[5], PACKAGE_VERSION);
-	    exit(1);
+		put_msg("Fig file format (%s) newer than this version of fig2dev (%s), exiting",
+				&buf[5], PACKAGE_VERSION);
+		exit(EXIT_FAILURE);
+	}
+
+	if ((v2_flag | v21_flag | v30_flag | v32_flag) == 0 &&
+					strncmp(buf, "#FIG 1.4", 8)) {
+		put_msg("Cannot determine fig file format from string '%s'.",
+				&buf[5]);
+		exit(EXIT_FAILURE);
+	}
+
+	if ((line = malloc(line_len)) == NULL) {
+		put_msg(Err_mem);
+		return -1;
 	}
 
+	line_no = 1;
 	if (v30_flag) {
 	    /* read the orientation spec (landscape/portrait) */
-	    line_no=1;
-	    if (get_line(fp) < 0) {
+	    if (get_line(fp, &line, &line_len, &line_no) < 0) {
 		put_msg("File is truncated at landscape/portrait specification.");
+		free(line);
 		return -1;
 	    }
 	    /* but set only if the user didn't specify the orientation
 	       on the command line */
 	    if (!orientspec)
-		landscape = !strncasecmp(buf,"land",4);
+		landscape = !strncasecmp(line, "land", 4);
 
 	    /* now read the metric/inches spec OR centering spec */
-	    if (get_line(fp) < 0) {
+	    if (get_line(fp, &line, &line_len, &line_no) < 0) {
 		put_msg("File is truncated at metric/inches or centering specification.");
+		free(line);
 		return -1;
 	    }
 	    /* read justification spec */
-	    if ((strncasecmp(buf,"center",6) == 0) ||
-		(strncasecmp(buf,"flush",5) == 0)) {
+	    if ((strncasecmp(line, "center", 6) == 0) ||
+		(strncasecmp(line, "flush", 5) == 0)) {
 		/* but set only if user didn't specify it */
 		if (!centerspec)
-		    center = strncasecmp(buf,"flush",5);
+		    center = strncasecmp(line, "flush", 5);
 		/* now read metric/inches spec */
-		if (get_line(fp) < 0) {
+		if (get_line(fp, &line, &line_len, &line_no) < 0) {
 		    put_msg("File is truncated at metric/inches specification.");
+		    free(line);
 		    return -1;
 		}
 	    }
 	    /* read metric/inches spec */
 	    /* if metric, scale magnification to correct for xfig display error */
-	    if (strncasecmp(buf,"metric", 6) == 0) {
+	    if (strncasecmp(line, "metric", 6) == 0) {
 		metric = 1;
 	    } else {
 		metric = 0;
@@ -261,56 +283,67 @@ read_objects(FILE *fp, F_compound *obj)
 
 	    /* new stuff in 3.2 */
 	    if (v32_flag) {
-		char *p;
 		/* read the paper size */
-		if (get_line(fp) < 0) {
+		if (get_line(fp, &line, &line_len, &line_no) < 0) {
 		    put_msg("File is truncated at paper size specification.");
+		    free(line);
 		    return -1;
 		}
 		if (!paperspec) {
-		    strcpy(papersize,buf);
-		    /* and truncate at first blank, if any */
-		    if ((p=strchr(papersize,' ')))
+		    char *p;
+		    /* truncate at first blank, if any */
+		    if ((p = strchr(line, ' ')))
 			*p = '\0';
+		    if (strlen(line) + 1 > sizeof papersize) {
+			put_msg("Invalid paper size specification at line %d: %s",
+					line_no, line);
+			free(line);
+			return -1;
+		    }
+		    strcpy(papersize, line);
 		}
 
 		/* read the magnification */
-		if (get_line(fp) < 0) {
+		if (get_line(fp, &line, &line_len, &line_no) < 0) {
 		    put_msg("File is truncated at magnification specification.");
+		    free(line);
 		    return -1;
 		}
-		/* if the users hasn't specified a magnification on the command line,
-		   use the one in the file */
+		/* if the users hasn't specified a magnification on
+		   the command line, use the one in the file */
 		if (!magspec) {
-		    mag = atof(buf)/100.0;
+		    mag = atof(line)/100.0;
 		    if (mag <= 0.)
 			mag = 1.;
 		    fontmag = mag;
 		}
 
 		/* read the multiple page flag */
-		if (get_line(fp) < 0) {
+		if (get_line(fp, &line, &line_len, &line_no) < 0) {
 		    put_msg("File is truncated at multiple page specification.");
+		    free(line);
 		    return -1;
 		}
 		if (!multispec)
-		    multi_page = (strncasecmp(buf,"multiple",8) == 0);
+		    multi_page = (strncasecmp(line, "multiple", 8) == 0);
 
 		/* Read the GIF transparent color. */
-		if (get_line(fp) < 0) {
+		if (get_line(fp, &line, &line_len, &line_no) < 0) {
 		    put_msg("File is truncated at transparent color specification.");
+		    free(line);
 		    return -1;
 		}
 		if (!transspec) {
-		    gif_colnum = atoi(buf);
+		    gif_colnum = atoi(line);
 		    if (gif_colnum < -3) {
 			put_msg("Invalid color number for transparent color.");
+			free(line);
 			return -1;
 		    }
 		    /* if standard color, get the name from the array */
 		    /* for user colors, wait till we've read in the file to get the value */
 		    if (gif_colnum < NUM_STD_COLS && gif_colnum >= 0)
-			strcpy(gif_transparent,Fig_color_names[gif_colnum]);
+			strcpy(gif_transparent, Fig_color_names[gif_colnum]);
 		}
 	    }
 	} else {
@@ -329,17 +362,20 @@ read_objects(FILE *fp, F_compound *obj)
 	}
 
 	/* now read for resolution and coord_sys (coord_sys is not used) */
-	if (get_line(fp) < 0) {
+	if (get_line(fp, &line, &line_len, &line_no) < 0) {
 	    put_msg("File is truncated at resolution specification.");
+	    free(line);
 	    return -1;
 	}
-	if (sscanf(buf,"%lf%d\n", &ppi, &coord_sys) != 2) {
+	if (sscanf(line, "%lf%d", &ppi, &coord_sys) != 2) {
 	    put_msg("Incomplete resolution information at line %d.", line_no);
+	    free(line);
 	    return -1;
 	}
 	if (ppi <= 0.) {
 	    put_msg("Invalid resolution information (%g) at line %d.",
 		    ppi, line_no);
+	    free(line);
 	    return -1;
 	}
 
@@ -349,24 +385,28 @@ read_objects(FILE *fp, F_compound *obj)
 	/* attach any comments found thus far to the whole figure */
 	obj->comments = attach_comments();
 
-	while (get_line(fp) > 0) {
-	    if (sscanf(buf, "%d", &object) != 1) {
+	while (get_line(fp, &line, &line_len, &line_no) > 0) {
+	    if (sscanf(line, "%d", &object) != 1) {
 		put_msg("Incorrect format at line %d.", line_no);
+		free(line);
 		return -1;
 	    }
 	    switch (object) {
 		case OBJ_COLOR_DEF:
-		    read_colordef();
-		    if (num_object) {
+		    if (objects) {
 			put_msg("Color definitions must come before other objects (line %d).",
 				line_no);
-			return (-1);
+			free(line);
+			return -1;
 		    }
-		    ++num_usr_cols;
+		    read_colordef(line, line_no);
 		    break;
 		case OBJ_POLYLINE :
-		    if ((l = read_lineobject(fp)) == NULL)
+		    if ((l = read_lineobject(fp, &line, &line_len, &line_no)) ==
+				NULL) {
+			free(line);
 			return -1;
+		    }
 #ifdef V4_0
 		    if ((l->pic != NULL) && (l->pic->figure != NULL)) {
 			if (lc)
@@ -388,79 +428,97 @@ read_objects(FILE *fp, F_compound *obj)
 			ll = (ll->next = l);
 		    else
 			ll = obj->lines = l;
-		    num_object++;
+		    objects = true;
 		    break;
 #endif /* V4_0 */
 		case OBJ_SPLINE :
-		    if ((s = read_splineobject(fp)) == NULL) {
+		    if ((s = read_splineobject(fp, &line, &line_len, &line_no))
+				== NULL) {
+			free(line);
 			return -1;
-			}
+		    }
 		    if (v32_flag){ /* s is a line */
 		      if (ll)
 			ll = (ll->next = (F_line *) s);
 		      else
 			ll = obj->lines = (F_line *) s;
-		      num_object++;
+		      objects = true;
 		      break;
 		    }
 		    if (ls)
 			ls = (ls->next = s);
 		    else
 			ls = obj->splines = s;
-		    num_object++;
+		    objects = true;
 		    break;
 		case OBJ_ELLIPSE :
-		    if ((e = read_ellipseobject()) == NULL)
+		    if ((e = read_ellipseobject(line, line_no)) == NULL) {
+			free(line);
 			return -1;
+		    }
 		    if (le)
 			le = (le->next = e);
 		    else
 			le = obj->ellipses = e;
-		    num_object++;
+		    objects = true;
 		    break;
 		case OBJ_ARC :
-		    if ((a = read_arcobject(fp)) == NULL)
+		    if ((a = read_arcobject(fp, &line, &line_len, &line_no)) ==
+				NULL) {
+			free(line);
 			return -1;
+		    }
 		    if (la)
 			la = (la->next = a);
 		    else
 			la = obj->arcs = a;
-		    num_object++;
+		    objects = true;
 		    break;
 		case OBJ_TEXT :
-		    if ((t = read_textobject(fp)) == NULL)
+		    if ((t = read_textobject(fp, &line, &line_len, &line_no)) ==
+				NULL) {
+			free(line);
 			return -1;
+		    }
 		    if (lt)
 			lt = (lt->next = t);
 		    else
 			lt = obj->texts = t;
-		    num_object++;
+		    objects = true;
 		    break;
 		case OBJ_COMPOUND :
-		    if ((c = read_compoundobject(fp)) == NULL)
+		    if ((c = read_compoundobject(fp, &line, &line_len,&line_no))
+				== NULL) {
+			free(line);
 			return -1;
+		    }
 		    if (lc)
 			lc = (lc->next = c);
 		    else
 			lc = obj->compounds = c;
-		    num_object++;
+		    objects = true;
 		    break;
 		default :
 		    put_msg("Incorrect object code at line %d.", line_no);
+		    free(line);
 		    return -1;
 		} /*  switch */
-	} /*  while (get_line(fp)) */
+	} /*  while (get_line(...)) */
+	free(line);
 
 	/* if user color was requested for GIF transparent color, get the
 	   rgb values from the user color array now that we've read them in */
 	if (gif_colnum >= NUM_STD_COLS) {
 	    int i;
-	    for (i=0; i<num_usr_cols; ++i)
+	    /* read_colordef() counted, but ignored too many user colors */
+	    if (num_usr_cols > MAX_USR_COLS)
+		    num_usr_cols = MAX_USR_COLS;
+	    for (i=0; i < num_usr_cols; ++i)
 		if (user_col_indx[i] == gif_colnum)
 		    break;
 	    if (i < num_usr_cols)
-		sprintf(gif_transparent,"#%2x%2x%2x",
-				user_colors[i].r,user_colors[i].g,user_colors[i].b);
+		sprintf(gif_transparent, "#%2x%2x%2x",
+			user_colors[i].r, user_colors[i].g, user_colors[i].b);
 	}
 
 	if (feof(fp))
@@ -474,55 +532,72 @@ read_objects(FILE *fp, F_compound *obj)
 } /*  read_objects */
 
 static void
-read_colordef(void)
+read_colordef(char *line, int line_no)
 {
-    int			c;
-    unsigned int	r,g,b;
-
-    if ((sscanf(buf, "%*d %d #%2x%2x%2x", &c, &r, &g, &b) != 4) ||
-		(c < NUM_STD_COLS)) {
-	buf[strlen(buf)-1]='\0';	/* remove the newline */
-	put_msg("Invalid color definition: %s, setting to black (#00000).",buf);
-	r=g=b=0;
-    }
-    user_col_indx[num_usr_cols] = c;
-    user_colors[num_usr_cols].r = r;
-    user_colors[num_usr_cols].g = g;
-    user_colors[num_usr_cols].b = b;
+	int		c;
+	unsigned int	r,g,b;
+
+	if (num_usr_cols >= MAX_USR_COLS) {
+		if (num_usr_cols == MAX_USR_COLS) {
+			put_msg("Maximum number of color definitions (%d) exceeded at line %d.",
+					MAX_USR_COLS, line_no);
+			++num_usr_cols;
+		}
+		/* ignore additional colors */
+		return;
+	}
+	if (sscanf(line, "%*d %d #%2x%2x%2x", &c, &r, &g, &b) != 4) {
+		if (c >= NUM_STD_COLS && c < NUM_STD_COLS + MAX_USR_COLS) {
+			put_msg("Invalid color definition at line %d: %s, setting to black (#00000).",
+					line_no, line);
+			r = g = b = 0;
+		} else {
+			put_msg("User color number at line %d out of range (%d), should be between %d and %d.",
+					line_no, c, NUM_STD_COLS,
+					NUM_STD_COLS + MAX_USR_COLS - 1);
+			return;
+		}
+	}
+	user_col_indx[num_usr_cols] = c;
+	user_colors[num_usr_cols].r = r;
+	user_colors[num_usr_cols].g = g;
+	user_colors[num_usr_cols].b = b;
+	++num_usr_cols;
 }
 
 static void
-fix_and_note_color(int *color)
+fix_and_note_color(int *color, int line_no)
 {
-    int		    i;
-    if (*color < DEFAULT) {
-	put_msg("Invalid color number %d at line %d, using default color.",
-			*color, line_no);
-	*color = DEFAULT;
-	return;
-    }
-    if (*color < NUM_STD_COLS) {
-	if (*color >= BLACK_COLOR) {
-	    std_color_used[*color] = true;
+	int	i;
+
+	if (*color < DEFAULT) {
+		put_msg("Invalid color number %d at line %d, using default color.",
+				*color, line_no);
+		*color = DEFAULT;
+		return;
 	}
-	return;
-    }
-    for (i=0; i<num_usr_cols; ++i)
-	if (*color == user_col_indx[i]) {
-		*color = i + NUM_STD_COLS;
+	if (*color < NUM_STD_COLS) {
+		if (*color >= BLACK_COLOR) {
+			std_color_used[*color] = true;
+		}
 		return;
 	}
-    put_msg("Cannot locate user color %d, using default color at line %d.",
-		*color, line_no);
-    *color = DEFAULT;
-    return;
+	for (i = 0; i < MIN(num_usr_cols, MAX_USR_COLS); ++i)
+		if (*color == user_col_indx[i]) {
+			*color = i + NUM_STD_COLS;
+			return;
+		}
+	put_msg("Cannot locate user color %d, using default color at line %d.",
+			*color, line_no);
+	*color = DEFAULT;
+	return;
 }
 
 static void
-note_fill(int fill, int *color)
+note_fill(int fill, int *color, int line_no)
 {
 	if (fill != UNFILLED) {
-		fix_and_note_color(color);
+		fix_and_note_color(color, line_no);
 		if (fill >= NUMSHADES + NUMTINTS) {
 			pattern_used[fill - NUMSHADES - NUMTINTS] = true;
 			pats_used = true;
@@ -531,7 +606,7 @@ note_fill(int fill, int *color)
 }
 
 static F_arc *
-read_arcobject(FILE *fp)
+read_arcobject(FILE *fp, char **restrict line, size_t *line_len, int *line_no)
 {
 	F_arc	*a;
 	int	n, fa, ba;
@@ -548,7 +623,7 @@ read_arcobject(FILE *fp)
 	a->back_arrow = NULL;
 	a->next = NULL;
 	if (v30_flag) {
-	    n = sscanf(buf, "%*d%d%d%d%d%d%d%d%d%lf%d%d%d%d%lf%lf%d%d%d%d%d%d\n",
+	    n = sscanf(*line,"%*d%d%d%d%d%d%d%d%d%lf%d%d%d%d%lf%lf%d%d%d%d%d%d",
 		&a->type, &a->style, &a->thickness,
 		&a->pen_color, &a->fill_color, &a->depth, &a->pen, &a->fill_style,
 		&a->style_val, &a->cap_style,
@@ -558,7 +633,7 @@ read_arcobject(FILE *fp)
 		&a->point[1].x, &a->point[1].y,
 		&a->point[2].x, &a->point[2].y);
 	} else {
-	    n = sscanf(buf, "%*d%d%d%d%d%d%d%d%lf%d%d%d%lf%lf%d%d%d%d%d%d\n",
+	    n = sscanf(*line, "%*d%d%d%d%d%d%d%d%lf%d%d%d%lf%lf%d%d%d%d%d%d",
 		&a->type, &a->style, &a->thickness,
 		&a->pen_color, &a->depth, &a->pen, &a->fill_style,
 		&a->style_val, &a->direction, &fa, &ba,
@@ -570,45 +645,45 @@ read_arcobject(FILE *fp)
 	    a->cap_style = 0;        /* butt line cap */
 	}
 	if ((v30_flag && n != 21) || (!v30_flag && n != 19)) {
-	    put_msg(Err_incomp, "arc", line_no);
+	    put_msg(Err_incomp, "arc", *line_no);
 	    free(a);
 	    return NULL;
 	}
 	a->thickness *= round(THICK_SCALE);
 	a->fill_style = FILL_CONVERT(a->fill_style);
 	if (INVALID_ARC(a)) {
-		put_msg(Err_invalid, "arc", line_no);
+		put_msg(Err_invalid, "arc", *line_no);
 		free(a);
 		return NULL;
 	}
-	fix_and_note_color(&a->pen_color);
-	note_fill(a->fill_style, &a->fill_color);
+	fix_and_note_color(&a->pen_color, *line_no);
+	note_fill(a->fill_style, &a->fill_color, *line_no);
 	if (fa) {
-	    if (get_line(fp) < 0 ||
-	        sscanf(buf, "%d%d%lf%lf%lf",
+	    if (get_line(fp, line, line_len, line_no) < 0 ||
+	        sscanf(*line, "%d%d%lf%lf%lf",
 				&type, &style, &thickness, &wid, &ht) != 5) {
-		    put_msg(Err_incomp, "arc", line_no);
+		    put_msg(Err_incomp, "arc", *line_no);
 		    free(a);
 		    return NULL;
 	    }
 	    if ((a->for_arrow = make_arrow(type, style, thickness, wid, ht))
 			    == NULL) {
-		    put_msg(Err_arrow, "forward", line_no);
+		    put_msg(Err_arrow, "forward", *line_no);
 		    free(a);
 		    return NULL;
 	    }
 	}
 	if (ba) {
-	    if (get_line(fp) < 0 ||
-	        sscanf(buf, "%d%d%lf%lf%lf",
+	    if (get_line(fp, line, line_len, line_no) < 0 ||
+	        sscanf(*line, "%d%d%lf%lf%lf",
 				&type, &style, &thickness, &wid, &ht) != 5) {
-		    put_msg(Err_incomp, "arc", line_no);
+		    put_msg(Err_incomp, "arc", *line_no);
 		    free(a);
 		    return NULL;
 	    }
 	    if ((a->back_arrow = make_arrow(type, style, thickness, wid, ht))
 			    == NULL) {
-		    put_msg(Err_arrow, "backward", line_no);
+		    put_msg(Err_arrow, "backward", *line_no);
 		    free(a);
 		    return NULL;
 	    }
@@ -618,7 +693,8 @@ read_arcobject(FILE *fp)
 }
 
 static F_compound *
-read_compoundobject(FILE *fp)
+read_compoundobject(FILE *fp, char **restrict line, size_t *line_len,
+		int *line_no)
 {
 	F_arc		*a, *la = NULL;
 	F_ellipse	*e, *le = NULL;
@@ -638,22 +714,23 @@ read_compoundobject(FILE *fp)
 	com->next = NULL;
 	com->comments = attach_comments();	/* attach any comments */
 
-	n = sscanf(buf, "%*d%d%d%d%d\n", &com->nwcorner.x, &com->nwcorner.y,
+	n = sscanf(*line, "%*d%d%d%d%d", &com->nwcorner.x, &com->nwcorner.y,
 		&com->secorner.x, &com->secorner.y);
 	if (n != 4) {
-	    put_msg(Err_incomp, "compound", line_no);
+	    put_msg(Err_incomp, "compound", *line_no);
 	    free(com);
 	    return NULL;
 	    }
-	while (get_line(fp) > 0) {
-	    if (sscanf(buf, "%d", &object) != 1) {
-		put_msg(Err_incomp, "compound", line_no);
+	while (get_line(fp, line, line_len, line_no) > 0) {
+	    if (sscanf(*line, "%d", &object) != 1) {
+		put_msg(Err_incomp, "compound", *line_no);
 		free_compound(&com);
 		return NULL;
-		}
+	    }
 	    switch (object) {
 		case OBJ_POLYLINE :
-		    if ((l = read_lineobject(fp)) == NULL) {
+		    if ((l = read_lineobject(fp, line, line_len, line_no)) ==
+				    NULL) {
 			return NULL;
 			}
 #ifdef V4_0
@@ -674,7 +751,8 @@ read_compoundobject(FILE *fp)
 #endif /* V4_0 */
 		    break;
 		case OBJ_SPLINE :
-		    if ((s = read_splineobject(fp)) == NULL) {
+		    if ((s = read_splineobject(fp, line, line_len, line_no)) ==
+				    NULL) {
 			return NULL;
 			}
 		    if (v32_flag){ /* s is a line */
@@ -690,7 +768,7 @@ read_compoundobject(FILE *fp)
 			ls = com->splines = s;
 		    break;
 		case OBJ_ELLIPSE :
-		    if ((e = read_ellipseobject()) == NULL) {
+		    if ((e = read_ellipseobject(*line, *line_no)) == NULL) {
 			return NULL;
 			}
 		    if (le)
@@ -699,7 +777,8 @@ read_compoundobject(FILE *fp)
 			le = com->ellipses = e;
 		    break;
 		case OBJ_ARC :
-		    if ((a = read_arcobject(fp)) == NULL) {
+		    if ((a = read_arcobject(fp, line, line_len, line_no)) ==
+				    NULL) {
 			return NULL;
 			}
 		    if (la)
@@ -708,7 +787,8 @@ read_compoundobject(FILE *fp)
 			la = com->arcs = a;
 		    break;
 		case OBJ_TEXT :
-		    if ((t = read_textobject(fp)) == NULL) {
+		    if ((t = read_textobject(fp, line, line_len, line_no)) ==
+				    NULL) {
 			return NULL;
 			}
 		    if (lt)
@@ -717,7 +797,8 @@ read_compoundobject(FILE *fp)
 			lt = com->texts = t;
 		    break;
 		case OBJ_COMPOUND :
-		    if ((c = read_compoundobject(fp)) == NULL) {
+		    if ((c = read_compoundobject(fp, line, line_len, line_no))
+				    == NULL) {
 			return NULL;
 			}
 		    if (lc)
@@ -728,7 +809,7 @@ read_compoundobject(FILE *fp)
 		case OBJ_END_COMPOUND :
 		    return com;
 		default :
-		    put_msg("Wrong object code at line %d", line_no);
+		    put_msg("Wrong object code at line %d", *line_no);
 		    return NULL;
 		} /*  switch */
 	    }
@@ -739,7 +820,7 @@ read_compoundobject(FILE *fp)
 	}
 
 static F_ellipse *
-read_ellipseobject(void)
+read_ellipseobject(char *line, int line_no)
 {
 	F_ellipse	*e;
 	int		n;
@@ -749,7 +830,7 @@ read_ellipseobject(void)
 	e->pen = 0;
 	e->next = NULL;
 	if (v30_flag) {
-	    n = sscanf(buf, "%*d%d%d%d%d%d%d%d%d%lf%d%lf%d%d%d%d%d%d%d%d\n",
+	    n = sscanf(line, "%*d%d%d%d%d%d%d%d%d%lf%d%lf%d%d%d%d%d%d%d%d",
 		&e->type, &e->style, &e->thickness,
 		&e->pen_color, &e->fill_color, &e->depth, &e->pen, &e->fill_style,
 		&e->style_val, &e->direction, &e->angle,
@@ -758,7 +839,7 @@ read_ellipseobject(void)
 		&e->start.x, &e->start.y,
 		&e->end.x, &e->end.y);
 	} else {
-	    n = sscanf(buf, "%*d%d%d%d%d%d%d%d%lf%d%lf%d%d%d%d%d%d%d%d\n",
+	    n = sscanf(line, "%*d%d%d%d%d%d%d%d%lf%d%lf%d%d%d%d%d%d%d%d",
 		&e->type, &e->style, &e->thickness,
 		&e->pen_color, &e->depth, &e->pen, &e->fill_style,
 		&e->style_val, &e->direction, &e->angle,
@@ -773,7 +854,7 @@ read_ellipseobject(void)
 	    free(e);
 	    return NULL;
 	    }
-	fix_and_note_color(&e->pen_color);
+	fix_and_note_color(&e->pen_color, line_no);
 	e->thickness *= round(THICK_SCALE);
 	e->fill_style = FILL_CONVERT(e->fill_style);
 	if (e->radiuses.x < 0)
@@ -785,7 +866,7 @@ read_ellipseobject(void)
 		free(e);
 		return NULL;
 	}
-	note_fill(e->fill_style, &e->fill_color);
+	note_fill(e->fill_style, &e->fill_color, line_no);
 	e->comments = attach_comments();	/* attach any comments */
 	return e;
 }
@@ -804,8 +885,9 @@ read_ellipseobject(void)
  */
 static int
 sanitize_lineobject(
-	F_line *l,	/* the line */
-	F_point *p	/* the last point of the line */
+	F_line	*l,	/* the line */
+	F_point	*p,	/* the last point of the line */
+	int	line_no
 	)
 {
 	F_point	*q;
@@ -912,7 +994,7 @@ sanitize_lineobject(
 }
 
 static F_line *
-read_lineobject(FILE *fp)
+read_lineobject(FILE *fp, char **restrict line, size_t *line_len, int *line_no)
 {
 	F_line	*l;
 	F_point	*o = NULL, *p, *q;
@@ -933,40 +1015,38 @@ read_lineobject(FILE *fp)
 	l->pic = NULL;
 	l->comments = NULL;
 
-	sscanf(buf,"%*d%d",&l->type);	/* get the line type */
+	sscanf(*line, "%*d%d", &l->type);	/* get the line type */
 
 	radius_flag = v30_flag || v21_flag || (v2_flag && l->type == T_ARC_BOX);
 	if (radius_flag) {
 	    if (v30_flag) {
-		n = sscanf(buf, "%*d%d%d%d%d%d%d%d%d%lf%d%d%d%d%d%d",
+		n = sscanf(*line, "%*d%d%d%d%d%d%d%d%d%lf%d%d%d%d%d%d",
 		&l->type,&l->style,&l->thickness,&l->pen_color,&l->fill_color,
 		&l->depth,&l->pen,&l->fill_style,&l->style_val,
 		&l->join_style,&l->cap_style,
 		&l->radius,&fa,&ba,&npts);
 	    } else {
-		n = sscanf(buf, "%*d%d%d%d%d%d%d%d%lf%d%d%d",
-		&l->type,&l->style,&l->thickness,&l->pen_color,
-		&l->depth,&l->pen,&l->fill_style,&l->style_val,&l->radius,&fa, &ba);
+		n = sscanf(*line, "%*d%d%d%d%d%d%d%d%lf%d%d%d",
+		&l->type,&l->style,&l->thickness,&l->pen_color,&l->depth,
+		&l->pen,&l->fill_style,&l->style_val,&l->radius,&fa, &ba);
 		l->fill_color = l->pen_color;
 	    }
 	}
 	/* old format uses pen for radius of arc-box corners */
 	else {
-	    n = sscanf(buf, "%*d%d%d%d%d%d%d%d%lf%d%d",
+	    n = sscanf(*line, "%*d%d%d%d%d%d%d%d%lf%d%d",
 			&l->type,&l->style,&l->thickness,&l->pen_color,
 			&l->depth,&l->pen,&l->fill_style,&l->style_val,&fa,&ba);
 	    l->fill_color = l->pen_color;
-	    if (l->type == T_ARC_BOX)
-		{
-		l->radius = (int) l->pen;
+	    if (l->type == T_ARC_BOX) {
+		l->radius = l->pen;
 		l->pen = 0;
-		}
-	    else
+	    } else
 		l->radius = 0;
 	}
 	if ((!radius_flag && n!=10) ||
 	     (radius_flag && ((!v30_flag && n!=11)||(v30_flag && n!=15)))) {
-	    put_msg(Err_incomp, "line", line_no);
+	    put_msg(Err_incomp, "line", *line_no);
 	    free(l);
 	    return NULL;
 	}
@@ -974,45 +1054,47 @@ read_lineobject(FILE *fp)
 	l->thickness *= round(THICK_SCALE);
 	l->fill_style = FILL_CONVERT(l->fill_style);
 	if (INVALID_LINE(l)) {
-		put_msg(Err_invalid, "line", line_no);
+		put_msg(Err_invalid, "line", *line_no);
 		free(l);
 		return NULL;
 	}
-	note_fill(l->fill_style, &l->fill_color);
-	fix_and_note_color(&l->pen_color);
+	note_fill(l->fill_style, &l->fill_color, *line_no);
+	fix_and_note_color(&l->pen_color, *line_no);
 	if (fa) {
-	    if (get_line(fp) < 0 ||
-			sscanf(buf, "%d%d%lf%lf%lf",
+	    if (get_line(fp, line, line_len, line_no) < 0 ||
+			sscanf(*line, "%d%d%lf%lf%lf",
 				&type, &style, &thickness, &wid, &ht) != 5) {
-		put_msg(Err_incomp, "line", line_no);
+		put_msg(Err_incomp, "line", *line_no);
 		free(l);
 		return NULL;
 	    }
 	    if ((l->for_arrow = make_arrow(type, style, thickness, wid, ht))
 			    == NULL) {
-		    put_msg(Err_arrow, "forward", line_no);
+		    put_msg(Err_arrow, "forward", *line_no);
 		    free(l);
 		    return NULL;
 	    }
 	}
 	if (ba) {
-	    if (get_line(fp) < 0 ||
-			sscanf(buf, "%d%d%lf%lf%lf",
+	    if (get_line(fp, line, line_len, line_no) < 0 ||
+			sscanf(*line, "%d%d%lf%lf%lf",
 				&type, &style, &thickness, &wid, &ht) != 5) {
-		put_msg(Err_incomp, "line", line_no);
+		put_msg(Err_incomp, "line", *line_no);
 		free_linestorage(l);
 		return NULL;
 	    }
 	    if ((l->back_arrow = make_arrow(type, style, thickness, wid, ht))
 			    == NULL) {
-		    put_msg(Err_arrow, "backward", line_no);
+		    put_msg(Err_arrow, "backward", *line_no);
 		    free_linestorage(l);
 		    return NULL;
 	    }
 	}
 	if (l->type == T_PIC_BOX) {
-	    char	file[BUFSIZ], *c;
+	    char	*file, *c;
+	    int		pos;
 	    size_t	len;
+	    ssize_t	chars;
 
 	    if ((Pic_malloc(l->pic)) == NULL) {
 		free(l);
@@ -1026,21 +1108,22 @@ read_lineobject(FILE *fp)
 	    XpmCreateXpmImageFromBuffer("", &l->pic->xpmimage, NULL);
 #endif
 
-			/* %[^\n]: really, read until first '\0' in buf */
-	    if (get_line(fp) < 0 || sscanf(buf, "%d %[^\n]",
-					&l->pic->flipped, file) != 2) {
-	        put_msg(Err_incomp, "picture", line_no);
-		free(l);
-	        return NULL;
+	    if ((chars = get_line(fp, line, line_len, line_no)) < 0 ||
+			  sscanf(*line, "%d %n", &l->pic->flipped, &pos) != 1) {
+		    put_msg(Err_incomp, "picture", *line_no);
+		    free(l);
+		    return NULL;
 	    }
+	    file = *line + pos;
+	    len = chars - pos;	/* strlen(file) */
+
 	    /* if there is a path in the .fig filename, and the path of the
 	     * imported picture filename is NOT absolute, prepend the
 	     * .fig file path to it
 	     */
 	    if (from && (c = strrchr(from, '/')) && file[0] != '/') {
-		if ((l->pic->file = malloc((size_t)(c - from + 2) +
-				    (len = strlen(file)))) ==
-			NULL) {
+		if ((l->pic->file = malloc((size_t)(c - from + 2) + len)) ==
+				NULL) {
 		    put_msg(Err_mem);
 		    free(l);	/* Points not read yet. */
 		    return NULL;
@@ -1049,8 +1132,8 @@ read_lineobject(FILE *fp)
 		memcpy(l->pic->file + (c - from + 1), file, len + 1);
 	    } else {
 		/* either absolute picture path or no path in .fig filename */
-		l->pic->file = malloc(len = strlen(file) + 1);
-		memcpy(l->pic->file, file, len);
+		l->pic->file = malloc(len + 1);
+		memcpy(l->pic->file, file, len + 1);
 	    }
 	}
 
@@ -1062,9 +1145,9 @@ read_lineobject(FILE *fp)
 	p->next = NULL;
 
 	/* read first point of line */
-	++line_no;
+	++(*line_no);
 	if (fscanf(fp, "%d%d", &p->x, &p->y) != 2) {
-	    put_msg(Err_incomp, "line", line_no);
+	    put_msg(Err_incomp, "line", *line_no);
 	    free_linestorage(l);
 	    return NULL;
 	}
@@ -1072,9 +1155,9 @@ read_lineobject(FILE *fp)
 	if (!v30_flag)
 	    npts = 1000000;
 	for (--npts; npts > 0; --npts) {
-	    count_lines_correctly(fp);
+	    count_lines_correctly(fp, line_no);
 	    if (fscanf(fp, "%d%d", &x, &y) != 2) {
-		put_msg(Err_incomp, "line", line_no);
+		put_msg(Err_incomp, "line", *line_no);
 		free_linestorage(l);
 		return NULL;
 	    }
@@ -1103,7 +1186,7 @@ read_lineobject(FILE *fp)
 	    l->last[1].y = o->y;
 	}
 
-	if (sanitize_lineobject(l, p)) {
+	if (sanitize_lineobject(l, p, *line_no)) {
 	    free_linestorage(l);
 	    return NULL;
 	}
@@ -1115,7 +1198,8 @@ read_lineobject(FILE *fp)
 }
 
 static F_spline *
-read_splineobject(FILE *fp)
+read_splineobject(FILE *fp, char **restrict line, size_t *line_len,
+		int *line_no)
 {
 	F_spline	*s;
 	F_line          *l;
@@ -1137,58 +1221,58 @@ read_splineobject(FILE *fp)
 	s->next = NULL;
 
 	if (v30_flag) {
-	    n = sscanf(buf, "%*d%d%d%d%d%d%d%d%d%lf%d%d%d%d",
+	    n = sscanf(*line, "%*d%d%d%d%d%d%d%d%d%lf%d%d%d%d",
 		&s->type, &s->style, &s->thickness,
 		&s->pen_color, &s->fill_color,
 		&s->depth, &s->pen, &s->fill_style, &s->style_val,
 		&s->cap_style, &fa, &ba, &npts);
 	} else {
-	    n = sscanf(buf, "%*d%d%d%d%d%d%d%d%lf%d%d",
+	    n = sscanf(*line, "%*d%d%d%d%d%d%d%d%lf%d%d",
 		&s->type, &s->style, &s->thickness, &s->pen_color,
 		&s->depth, &s->pen, &s->fill_style, &s->style_val, &fa, &ba);
 	    s->fill_color = s->pen_color;
 	    s->cap_style = 0;        /* butt line cap */
 	}
 	if ((v30_flag && n != 13) || (!v30_flag && n != 10)) {
-	    put_msg(Err_incomp, "spline", line_no);
+	    put_msg(Err_incomp, "spline", *line_no);
 	    free(s);
 	    return NULL;
 	    }
 	s->thickness *= round(THICK_SCALE);
 	s->fill_style = FILL_CONVERT(s->fill_style);
 	if (INVALID_SPLINE(s)) {
-		put_msg(Err_invalid, "spline", line_no);
+		put_msg(Err_invalid, "spline", *line_no);
 		free(s);
 		return NULL;
 	}
-	note_fill(s->fill_style, &s->fill_color);
-	fix_and_note_color(&s->pen_color);
+	note_fill(s->fill_style, &s->fill_color, *line_no);
+	fix_and_note_color(&s->pen_color, *line_no);
 	if (fa) {
-	    if (get_line(fp) < 0 ||
-			    sscanf(buf, "%d%d%lf%lf%lf",
+	    if (get_line(fp, line, line_len, line_no) < 0 ||
+			    sscanf(*line, "%d%d%lf%lf%lf",
 				    &type, &style, &thickness, &wid, &ht) != 5) {
-		    put_msg(Err_incomp, "spline", line_no);
+		    put_msg(Err_incomp, "spline", *line_no);
 		    free(s);
 		    return NULL;
 	    }
 	    if ((s->for_arrow = make_arrow(type, style, thickness, wid, ht))
 			    == NULL) {
-		    put_msg(Err_arrow, "forward", line_no);
+		    put_msg(Err_arrow, "forward", *line_no);
 		    free(s);
 		    return NULL;
 	    }
 	}
 	if (ba) {
-	    if (get_line(fp) < 0 ||
-			    sscanf(buf, "%d%d%lf%lf%lf",
+	    if (get_line(fp, line, line_len, line_no) < 0 ||
+			    sscanf(*line, "%d%d%lf%lf%lf",
 				    &type, &style, &thickness, &wid, &ht) != 5) {
-		    put_msg(Err_incomp, "spline", line_no);
+		    put_msg(Err_incomp, "spline", *line_no);
 		    free_splinestorage(s);
 		    return NULL;
 	    }
 	    if ((s->back_arrow = make_arrow(type, style, thickness, wid, ht))
 			    == NULL) {
-		    put_msg(Err_arrow, "backward", line_no);
+		    put_msg(Err_arrow, "backward", *line_no);
 		    free_splinestorage(s);
 		    return NULL;
 	    }
@@ -1196,9 +1280,9 @@ read_splineobject(FILE *fp)
 
 	/* Read points */
 	/* read first point of line */
-	++line_no;
+	++(*line_no);
 	if ((n = fscanf(fp, "%d%d", &x, &y)) != 2) {
-	    put_msg(Err_incomp, "spline", line_no);
+	    put_msg(Err_incomp, "spline", *line_no);
 	    free_splinestorage(s);
 	    return NULL;
 	    };
@@ -1212,15 +1296,15 @@ read_splineobject(FILE *fp)
 	if (!v30_flag)
 		npts = 1000000;
 	if (npts < 2) {
-		put_msg(Err_incomp, "spline", line_no);
+		put_msg(Err_incomp, "spline", *line_no);
 		free_splinestorage(s);
 		return NULL;
 	}
 	for (--npts; npts > 0; --npts) {
 	    /* keep track of newlines for line counter */
-	    count_lines_correctly(fp);
+	    count_lines_correctly(fp, line_no);
 	    if (fscanf(fp, "%d%d", &x, &y) != 2) {
-		put_msg(Err_incomp, "spline", line_no);
+		put_msg(Err_incomp, "spline", *line_no);
 		free_splinestorage(s);
 		return NULL;
 		};
@@ -1250,9 +1334,9 @@ read_splineobject(FILE *fp)
 	    ptr = s->controls;
 	    while (ptr) {    /* read controls */
 		/* keep track of newlines for line counter */
-		count_lines_correctly(fp);
+		count_lines_correctly(fp, line_no);
 		if ((n = fscanf(fp, "%lf", &control_s)) != 1) {
-		  put_msg(Err_incomp, "spline", line_no);
+		  put_msg(Err_incomp, "spline", *line_no);
 		  free_splinestorage(s);
 		  return NULL;
 		}
@@ -1275,9 +1359,9 @@ read_splineobject(FILE *fp)
 	}
 	/* Read controls from older versions */
 	/* keep track of newlines for line counter */
-	count_lines_correctly(fp);
+	count_lines_correctly(fp, line_no);
 	if ((n = fscanf(fp, "%lf%lf%lf%lf", &lx, &ly, &rx, &ry)) != 4) {
-	    put_msg(Err_incomp, "spline", line_no);
+	    put_msg(Err_incomp, "spline", *line_no);
 	    free_splinestorage(s);
 	    return NULL;
 	}
@@ -1290,9 +1374,9 @@ read_splineobject(FILE *fp)
 	cp->rx = rx; cp->ry = ry;
 	while (--c) {
 	    /* keep track of newlines for line counter */
-	    count_lines_correctly(fp);
+	    count_lines_correctly(fp, line_no);
 	    if (fscanf(fp, "%lf%lf%lf%lf", &lx, &ly, &rx, &ry) != 4) {
-		put_msg(Err_incomp, "spline", line_no);
+		put_msg(Err_incomp, "spline", *line_no);
 		cp->next = NULL;
 		free_splinestorage(s);
 		return NULL;
@@ -1315,13 +1399,37 @@ read_splineobject(FILE *fp)
 	return s;
 }
 
+static char *
+find_end(const char *str, int v30flag)
+{
+	const char	endmark[] = "\\001";
+	char		*end;
+
+	if (v30flag) {
+		/* A string is terminated with the literal '\001',
+		   and 8-bit characters may be represented as \xxx */
+		end = strstr(str, endmark);
+		/* is this not '\\001', or '\\\\001', etc? */
+		while (end && backslash_count(str, end - str) % 2 == 0)
+			end = strstr(end + 3, endmark);
+	} else {
+		/* The text object is terminated by a CONTROL-A.
+		   If there is no CONTROL-A on this line, then this
+		   must be a multi-line text object. */
+		end = strchr(str, '\1');
+	}
+	return end;
+}
+
+
 static F_text *
-read_textobject(FILE *fp)
+read_textobject(FILE *fp, char **restrict line, size_t *line_len, int *line_no)
 {
 	F_text	*t;
-	int	n, ignore = 0;
-	char	s[BUFSIZ], s_temp[BUFSIZ], junk[2];
-	int	more, len, l;
+	bool	freestart = false;
+	int	i, n;
+	char	*end, *start;
+	size_t	len;
 
 	Text_malloc(t);
 	t->font = 0;
@@ -1329,32 +1437,101 @@ read_textobject(FILE *fp)
 	t->comments = NULL;
 	t->next = NULL;
 
-	if (v30_flag) {	/* order of parms is more like other objects now,
-			   string is now terminated with the literal '\001',
-			   and 8-bit characters are represented as \xxx */
+	n = sscanf(*line, "%*d%d%d%d%d%d%lf%lf%d%lf%lf%d%d %n",
+			&t->type, &t->color, &t->depth, &t->pen, &t->font,
+			&t->size, &t->angle, &t->flags, &t->height, &t->length,
+			&t->base_x, &t->base_y, &i);
+	if (n != 12) {
+		put_msg(Err_incomp, "text", *line_no);
+		free(t);
+		return NULL;
+	}
+	start = *line + i;
+	end = find_end(start, v30_flag);
 
-	    n = sscanf(buf, "%*d%d%d%d%d%d%lf%lf%d%lf%lf%d%d%[^\n]",
-		&t->type, &t->color, &t->depth, &t->pen,
-		&t->font, &t->size, &t->angle,
-		&t->flags, &t->height, &t->length,
-		&t->base_x, &t->base_y, s);
+	if (end) {
+		*end = '\0';
+		len = end - start;
 	} else {
-	    /* The text object is terminated by a CONTROL-A, so we read
-		everything up to the CONTROL-A and then read that character.
-		If we do not find the CONTROL-A on this line then this must
-		be a multi-line text object and we will have to read more. */
-
-	    n = sscanf(buf,"%*d%d%d%lf%d%d%d%lf%d%lf%lf%d%d%[^\1]%1[\1]",
-		&t->type, &t->font, &t->size, &t->pen,
-		&t->color, &t->depth, &t->angle,
-		&t->flags, &t->height, &t->length,
-		&t->base_x, &t->base_y, s, junk);
+		ssize_t	chars;
+		char	*next;
+
+		len = strlen(start);
+		start[len++] = '\n';	/* put back the newline */
+
+		/* allocate plenty of space */
+		next = malloc(len + BUFSIZ);
+		if (next == NULL) {
+			put_msg(Err_mem);
+			free(t);
+			return NULL;
+		}
+		memcpy(next, start, len);
+
+		while ((chars = getline(line, line_len, fp)) != -1) {
+			++(*line_no);
+			end = find_end(*line, v30_flag);
+			if (end) {
+				*end = '\0';
+				next = realloc(next, len + end - *line + 1);
+				memcpy(next + len, *line, end - *line + 1);
+				len += end - *line;
+				break;
+			} else {
+				if (**line + chars - 1 == '\n' && chars > 1 &&
+						**line + chars - 2 == '\r')
+					(*line)[chars-- - 2] = '\n';
+				next = realloc(next, len + chars + 1);
+				memcpy(next + len, *line, chars + 1);
+				len += chars;
+			}
+		}
+		start = next;
+		freestart = true;
+	}
+
+	/* convert any \xxx to characters */
+	if (v30_flag && (end = strchr(start, '\\'))) {
+		unsigned char	num;
+		char		*c = start;
+		size_t		l;
+
+		len = end - start;
+		l = len;
+		while (c[l] != '\0') {
+			if (c[l] == '\\') {
+				/* convert 3 digit octal value */
+				if (isdigit(c[l+1]) && c[l+2] != '\0' &&
+							c[l+3] != '\0') {
+					if (sscanf(c+l+1, "%3hho", &num) != 1) {
+						put_msg("Error in parsing text string on line %d",
+								*line_no);
+						return NULL;
+					}
+					/* no check of unsigned char overflow */
+					c[len++] = num;
+					l += 3;
+				} else {
+					/* an escaped char is un-escaped */
+					c[len++] = c[++l];
+				}
+			} else {
+				c[len++] = c[l];
+			}
+			++l;
+		}
+		c[len] = '\0';		/* terminate */
 	}
-	if ((n != 14) && (n != 13)) {
-	  put_msg(Err_incomp, "text", line_no);
-	  free(t);
-	  return NULL;
+
+	t->cstring = malloc(len + 1);
+	if (t->cstring == NULL) {
+		put_msg(Err_mem);
+		free(t);
+		return NULL;
 	}
+	memcpy(t->cstring, start, len + 1);
+	if (freestart)
+		free(start);
 
 	if (font_size != 0.0) {
 	    /* scale length/height of text by ratio of requested font size to actual size */
@@ -1364,89 +1541,6 @@ read_textobject(FILE *fp)
 	}
 	if (t->size <= 0.0)
 	    t->size = (float) DEFAULT_FONT_SIZE;
-	more = 0;
-	if (!v30_flag && n == 13)
-	    more = 1;  /* in older xfig there is more if ^A wasn't found yet */
-	else if (v30_flag) {	/* in 3.0 there is more if \001 wasn't found */
-	    len = strlen(s);
-	    if ((strcmp(&s[len-4],"\\001") == 0) &&	/* if we find '\000' */
-	        !(backslash_count(s, len-5) % 2)) {	/* and not '\\000' */
-		    more = 0;				/* then there are no more lines */
-		    s[len-4]='\0';			/* and get rid of the '\001' */
-	    } else {
-		more = 1;
-		s[len++]='\n';				/* put back the end of line char */
-		s[len] = '\0';				/* and terminate it */
-	    }
-	}
-	if (more) {
-	  /* Read in the subsequent lines of the text if there are more */
-	  do {
-	    ++line_no;				/* As is done in get_line */
-	    if (fgets(s_temp, BUFSIZ, fp) == NULL)
-		break;
-	    len = strlen(s_temp)-1;		/* ignore newline */
-	    if (len > 0 && s_temp[len-1] == '\r') { /* strip any trailing CR */
-		s_temp[len-1] = '\0';
-		len--;
-	    }
-	    if (v30_flag) {
-		if ((strncmp(&s_temp[len-4],"\\001",4) == 0) &&
-		    !(backslash_count(s_temp, len-5) % 2)) {
-			n=0;			/* found the '\001', set n to stop */
-			s_temp[len-4]='\0';	/* and get rid of the '\001' */
-		} else {
-			n=1;			/* keep going (more lines) */
-		}
-	    } else {
-		n = sscanf(buf, "%[^\1]%[\1]", s_temp, junk);
-	    }
-	    /* Safety check */
-	    if (strlen(s)+1 + strlen(s_temp)+1 > BUFSIZ) {
-	      /* Too many characters.  Ignore the rest. */
-	      ignore = 1;
-	    }
-	    if (!ignore)
-	      strcat(s, s_temp);
-	  } while (n == 1);
-	}
-
-	if (v30_flag) {		/* now convert any \xxx to ascii characters */
-	    if (strchr(s,'\\')) {
-		unsigned int num;
-		len = strlen(s);
-		for (l=0,n=0; l < len; ++l) {
-		    if (s[l]=='\\') {
-			/* a backslash, see if a digit follows */
-			if (l < len && isdigit(s[l+1])) {
-			    /* yes, scan for 3 digit octal value */
-			    if (sscanf(&s[l+1],"%3o",&num)!=1) {
-				put_msg("Error in parsing text string on line %d",
-						line_no);
-				return NULL;
-			    }
-			    buf[n++]= (unsigned char) num;	/* put char in */
-			    l += 3;			/* skip over digits */
-			} else {
-			    buf[n++] = s[++l];		/* some other escaped character */
-			}
-		    } else {
-			buf[n++] = s[l];		/* ordinary character */
-		    }
-		}
-		buf[n]='\0';		/* terminate */
-		strcpy(s,buf);		/* copy back to s */
-	    }
-	}
-	if (strlen(s) == 0)
-		(void)strcpy(s, " ");
-	t->cstring = calloc((unsigned)(strlen(s)), sizeof(char));
-	if (NULL == t->cstring) {
-	    put_msg(Err_mem);
-	    free(t);
-	    return NULL;
-	}
-	(void)strcpy(t->cstring, s+1);
 
 	if (!v21_flag && (t->font == 0 || t->font == DEFAULT))
 		t->flags = ((t->flags != DEFAULT) ? t->flags : 0)
@@ -1457,11 +1551,11 @@ read_textobject(FILE *fp)
 				| PSFONT_TEXT;
 
 	if (INVALID_TEXT(t)) {
-		put_msg(Err_invalid, "text", line_no);
+		put_msg(Err_invalid, "text", *line_no);
 		free_text(&t);
 		return NULL;
 	}
-	fix_and_note_color(&t->color);
+	fix_and_note_color(&t->color, *line_no);
 	t->comments = attach_comments();	/* attach any comments */
 	return t;
 }
@@ -1469,18 +1563,19 @@ read_textobject(FILE *fp)
 
 /* count consecutive backslashes backwards */
 
-static int
-backslash_count(char cp[], int start)
+static ptrdiff_t
+backslash_count(const char *restrict cp, ptrdiff_t start)
 {
-  int i, count = 0;
+	ptrdiff_t	i;
+	ptrdiff_t	count = 0;
 
-  for(i=start; i>=0; i--) {
-    if (cp[i] == '\\')
-	count++;
-    else
-	break;
-  }
-  return count;
+	for(i = start; i >= 0; --i) {
+		if (cp[i] == '\\')
+			++count;
+		else
+			break;
+	}
+	return count;
 }
 
 /* attach comments in linked list */
@@ -1509,54 +1604,63 @@ attach_comments(void)
     return icomp;
 }
 
+/* save a comment line to be stored with the *subsequent* object */
+
 static int
-get_line(FILE *fp)
+save_comment(char *restrict line, size_t len)
 {
-	int	len;
-	while (1) {
-		if (NULL == fgets(buf, BUFSIZ, fp)) {
-			return -1;
+	int	i;
+
+	/* skip too many comment lines */
+	if (numcom == MAXCOMMENTS)
+		return 2;
+
+	/* remove one leading blank from the comment, if there is one */
+	i = 1;
+	if (line[i] == ' ')
+		i = 2;
+
+	/* see if we've allocated space for this comment */
+	if (comments[numcom])
+		free(comments[numcom]);
+	if ((comments[numcom] = malloc(len + (1 - i))) == NULL)
+		return -1;
+
+	strcpy(comments[numcom++], &line[i]);
+	return 1;
+}
+
+static ssize_t
+get_line(FILE *fp, char **restrict line, size_t *line_len, int *line_no)
+{
+	ssize_t	chars;
+
+	while ((chars = getline(line, line_len, fp)) != -1) {
+		++(*line_no);
+		/* skip empty lines */
+		if (**line == '\n' || (**line == '\r' &&
+					chars == 2 && (*line)[1] == '\n'))
+			continue;
+		/* remove newline and possibly a carriage return */
+		if ((*line)[chars-1] == '\n') {
+			chars -= (*line)[chars - 2] == '\r' ? 2 : 1;
+			(*line)[chars] = '\0';
 		}
-		++line_no;
-		if (*buf == '#') {			/* save any comments */
-			if (save_comment() < 0)
+		/* save any comments */
+		if (**line == '#') {
+			if (save_comment(*line, (size_t)chars) < 0)
 				return -1;
-			/* skip empty lines */
-		} else if (*buf != '\n' || !(*buf == '\r' && buf[1] == '\n')) {
-			len = strlen(buf);
-			/* remove newline and possibly a carriage return */
-			if (buf[len-1] == '\n')
-				buf[len - (buf[len-2] == '\r' ? 2 : 1)] = '\0';
-			return 1;
+			continue;
 		}
+		/* return the line */
+		return chars;
 	}
+	/* chars == -1 */
+	return chars;
+	/* getline() only fails with EINVAL, and probably ENOMEM from malloc().
+	   No use to check for errno. */
 }
 
-/* save a comment line to be stored with the *subsequent* object */
-
-static int
-save_comment(void)
-{
-    int		    i;
-
-    /* skip too many comment lines */
-    if (numcom == MAXCOMMENTS)
-	return 2;
-    i=strlen(buf);
-    /* see if we've allocated space for this comment */
-    if (comments[numcom])
-	free(comments[numcom]);
-    if ((comments[numcom] = malloc(i+1)) == NULL)
-	    return -1;
-    /* remove any newline */
-    if (buf[i-1] == '\n')
-	buf[i-1] = '\0';
-    i=1;
-    if (buf[1] == ' ')	/* remove one leading blank from the comment, if there is one */
-	i=2;
-    strcpy(comments[numcom++], &buf[i]);
-    return 1;
-}
 
 /* skip to the end of the current line and any subsequent blank lines */
 
@@ -1714,15 +1818,15 @@ static int pop() {
  */
 
 static void
-count_lines_correctly(FILE *fp)
+count_lines_correctly(FILE *fp, int *line_no)
 {
 	int cc;
 	do {
-	    cc = getc(fp);
-	    if (cc == '\n') {
-		++line_no;
-		cc=getc(fp);
-	    }
+		cc = getc(fp);
+		if (cc == '\n') {
+			++(*line_no);
+			cc=getc(fp);
+		}
 	} while (cc == ' ' || cc == '\t');
 	ungetc(cc,fp);
 }
diff --git a/fig2dev/read1_3.c b/fig2dev/read1_3.c
index 64c7819..3b7a263 100644
--- a/fig2dev/read1_3.c
+++ b/fig2dev/read1_3.c
@@ -2,8 +2,8 @@
  * Fig2dev: Translate Fig code to various Devices
  * Copyright (c) 1991 by Micah Beck
  * Parts Copyright (c) 1985-1988 by Supoj Sutanthavibul
- * Parts Copyright (c) 1989-2012 by Brian V. Smith
- * Parts Copyright (c) 2015-2019 by Thomas Loimer
+ * Parts Copyright (c) 1989-2015 by Brian V. Smith
+ * Parts Copyright (c) 2015-2020 by Thomas Loimer
  *
  * Any party obtaining a copy of these files is granted, free of charge, a
  * full and unrestricted irrevocable, world-wide, paid up, royalty-free,
@@ -51,8 +51,6 @@
 
 extern F_arrow		*forward_arrow(void), *backward_arrow(void);
 extern int		figure_modified;
-//extern int		line_no;
-extern int		num_object;
 
 static F_ellipse	*read_ellipseobject(FILE *fp);
 static F_line		*read_lineobject(FILE *fp);
@@ -103,7 +101,6 @@ read_1_3_objects(FILE *fp, F_compound *obj)
 			ll = (ll->next = l);
 		    else
 			ll = obj->lines = l;
-		    num_object++;
 		    break;
 		case OBJ_SPLINE :
 		    if ((s = read_splineobject(fp)) == NULL) return(-1);
@@ -111,7 +108,6 @@ read_1_3_objects(FILE *fp, F_compound *obj)
 			ls = (ls->next = s);
 		    else
 			ls = obj->splines = s;
-		    num_object++;
 		    break;
 		case OBJ_ELLIPSE :
 		    if ((e = read_ellipseobject(fp)) == NULL) return(-1);
@@ -119,7 +115,6 @@ read_1_3_objects(FILE *fp, F_compound *obj)
 			le = (le->next = e);
 		    else
 			le = obj->ellipses = e;
-		    num_object++;
 		    break;
 		case OBJ_ARC :
 		    if ((a = read_arcobject(fp)) == NULL) return(-1);
@@ -127,7 +122,6 @@ read_1_3_objects(FILE *fp, F_compound *obj)
 			la = (la->next = a);
 		    else
 			la = obj->arcs = a;
-		    num_object++;
 		    break;
 		case OBJ_TEXT :
 		    if ((t = read_textobject(fp)) == NULL) return(-1);
@@ -135,7 +129,6 @@ read_1_3_objects(FILE *fp, F_compound *obj)
 			lt = (lt->next = t);
 		    else
 			lt = obj->texts = t;
-		    num_object++;
 		    break;
 		case OBJ_COMPOUND :
 		    if ((c = read_compoundobject(fp)) == NULL) return(-1);
@@ -143,7 +136,6 @@ read_1_3_objects(FILE *fp, F_compound *obj)
 			lc = (lc->next = c);
 		    else
 			lc = obj->compounds = c;
-		    num_object++;
 		    break;
 		default:
 		    put_msg("Incorrect object code %d", object);
diff --git a/fig2dev/tests/read.at b/fig2dev/tests/read.at
index 4ef8747..9b34bfb 100644
--- a/fig2dev/tests/read.at
+++ b/fig2dev/tests/read.at
@@ -2,7 +2,7 @@ dnl Fig2dev: Translate Fig code to various Devices
 dnl Copyright (c) 1991 by Micah Beck
 dnl Parts Copyright (c) 1985-1988 by Supoj Sutanthavibul
 dnl Parts Copyright (c) 1989-2015 by Brian V. Smith
-dnl Parts Copyright (c) 2015-2019 by Thomas Loimer
+dnl Parts Copyright (c) 2015-2020 by Thomas Loimer
 dnl
 dnl Any party obtaining a copy of these files is granted, free of charge, a
 dnl full and unrestricted irrevocable, world-wide, paid up, royalty-free,
@@ -14,7 +14,7 @@ dnl party to do so, with the only requirement being that the above copyright
 dnl and this permission notice remain intact.
 
 dnl read.at
-dnl Author: Thomas Loimer, 2017-2019
+dnl Author: Thomas Loimer, 2017-2020
 
 
 AT_BANNER([Sanitize and harden input.])
@@ -279,7 +279,7 @@ AT_CHECK([fig2dev -L box <<EOF
 3 0 0 0 0 0 0 0 0. 0 1
 0
 EOF
-],1,ignore,[Incomplete spline object at line 3.
+],1,ignore,[Incomplete spline object at line 4.
 ])
 AT_CLEANUP
 
@@ -287,7 +287,7 @@ AT_SETUP([allow last line without newline, ticket #28])
 AT_KEYWORDS([read.c])
 AT_CHECK([AS_ECHO_N(['#FIG 2
 0']) | fig2dev -L box],
-1, ignore, [Incomplete resolution information at line 1.
+1, ignore, [Incomplete resolution information at line 2.
 ])
 AT_CLEANUP
 
@@ -299,7 +299,7 @@ AT_CHECK([fig2dev -L box <<EOF
 2 1 1 1 -1 50 0 0 0. 0 0
 0
 EOF
-], 1, ignore, [Incomplete line object at line 3.
+], 1, ignore, [Incomplete line object at line 4.
 ])
 AT_CLEANUP
 
@@ -345,7 +345,7 @@ AT_CHECK([fig2dev -L pict2e <<EOF
 2 1 1 1 -502350 50 0 0 0. 0 0
 0 0 100 100 9999 0
 EOF
-],0,ignore-nolog,[Invalid color number -502350 at line 2, using default color.
+],0,ignore-nolog,[Invalid color number -502350 at line 3, using default color.
 ])
 AT_CLEANUP
 
@@ -387,9 +387,22 @@ AT_KEYWORDS([read.c])
 AT_CHECK([fig2dev -L box <<EOF
 #FIG 2
 1200 2
-4 2 0 0 1 0 0 390 306 110 376 639 5 Text
+4 2 0 0 1 0 0 0 6 110 376 639 5 Text
 EOF
-], 1, ignore, [Invalid text object at line 2.
+], 0, ignore)
+AT_CLEANUP
+
+AT_SETUP([reject too long papersize specification])
+AT_KEYWORDS([read.c])
+AT_CHECK([fig2dev -L box <<EOF
+#FIG 3.2
+Landscape
+Center
+Inches
+Papersize_name_too_long
+EOF
+], 1, ignore,
+    [Invalid paper size specification at line 5: Papersize_name_too_long
 ])
 AT_CLEANUP
 
-- 
2.24.1