1 /* gEDA - GPL Electronic Design Automation
2  * libgeda - gEDA's library
3  * Copyright (C) 1998-2010 Ales Hvezda
4  * Copyright (C) 1998-2010 gEDA Contributors (see ChangeLog for details)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 /*! \file o_text_basic.c
22  *  \brief functions for the text and fonts
23  *
24  *  \par The font definitions
25  *
26  *  Each letter of the font is defined in a single font symbol file. In
27  *  the font symbol file, the character width is defined in the second
28  *  line. The first line contains the file format version.
29  *
30  *  All remaining lines are basic graphical lines. They build the
31  *  appearance of the character.
32  *
33  *  \image html o_text_font_overview.png
34  *  \image latex o_text_font_overview.pdf "font overview" width=14cm
35  *
36  *  The height of capital characters in the font files is 26. The size
37  *  of small letters is 16. The space below the zero line is used by
38  *  characters like <b>g</b>, <b>p</b> or <b>q</b>. The space above 26
39  *  is used by diacritic marks like accents, breve, circumflex mostly in
40  *  european characters.
41  *
42  *  \par The text definitions
43  *
44  *  The text is stored and printed in several different representations.
45  *
46  *  In the gEDA files the text is just a string. It is stored unmodified
47  *  in <b>OBJECT->text->string</b>.
48  *
49  *  If the string is an attribute with an equal sign as delimiter between
50  *  an attribute name and an attribute value, then it is possible to
51  *  hide some parts of the text. The still visible part of an attribute
52  *  is stored in <b>OBJECT->text->disp_string</b>.
53  *
54  *  \image html o_text_text_overview.png
55  *  \image latex o_text_text_overview.pdf "text overview" width=14cm
56  *
57  *  To draw the text in gschem, the string is interpreted and converted
58  *  to a list of basic graphical objects. The basic line objects are
59  *  collected from the font character objects.
60  */
61 
62 #include <config.h>
63 #include <missing.h>
64 
65 #include <stdio.h>
66 #include <math.h>
67 #include <sys/stat.h>
68 #ifdef HAVE_STRING_H
69 #include <string.h>
70 #endif
71 #ifdef HAVE_UNISTD_H
72 #include <unistd.h>
73 #endif
74 
75 #include "libgeda_priv.h"
76 
77 #ifdef HAVE_LIBDMALLOC
78 #include <dmalloc.h>
79 #endif
80 
81 /*! \brief Scale factor between legacy gschem font units and postscript points.
82  *
83  *  \par Description
84  *  gschem fonts are nominally specified in points, however there is a
85  *  difference in how the specified font size corresponds to the metrics of
86  *  the font when compared to typical typographic usage.
87  *
88  *  The following factor was impirically determined to approximately match the
89  *  cap-height between the legacy gschem font, and fonts rendered using pango.
90  */
91 #define GEDA_FONT_FACTOR 1.3
92 
93 /*! \brief Scale factor font height and line-spacing (for print only)
94  *
95  *  \par Description
96  *  Specifies the scale factor between the nominal font size and the inter-
97  *  line spacing used to render it when printing.
98  */
99 #define PRINT_LINE_SPACING 1.12
100 
101 /*! Size of a tab in characters */
102 int tab_in_chars = 8;
103 
104 /*! \brief update the visible part of a string
105  *  \par Function Description
106  *  If a string is an attribute, then it is possible to hide
107  *  the name or the value part of the attribute string.
108  *  This functions updates the text->disp_string according
109  *  to the object->show_name_value settings
110  *
111  *  \param [in] object  The OBJECT to update
112  */
update_disp_string(OBJECT * object)113 static void update_disp_string (OBJECT *object)
114 {
115   char *name = NULL;
116   char *value = NULL;
117   TEXT *text = object->text;
118 
119   g_free (text->disp_string);
120 
121   if (o_attrib_get_name_value (object, &name, &value)) {
122     switch (object->show_name_value) {
123       case (SHOW_NAME_VALUE):
124         text->disp_string = g_strdup (text->string);
125         break;
126 
127       case (SHOW_NAME):
128         if (name[0] != '\0') {
129           text->disp_string = g_strdup (name);
130         } else {
131           g_critical ("Got an improper attribute: %s\n",
132                       text->string);
133           text->disp_string = g_strdup ("invalid");
134         }
135         break;
136 
137       case (SHOW_VALUE):
138         if (value[0] != '\0') {
139           text->disp_string = g_strdup(value);
140         } else {
141           g_critical ("Got an improper attribute: %s\n",
142                       text->string);
143           text->disp_string = g_strdup ("invalid");
144         }
145         break;
146     }
147     /* free the strings allocated by o_attrib_get_name_value */
148     g_free(name);
149     g_free(value);
150   } else {
151     text->disp_string = g_strdup (text->string);
152   }
153 }
154 
155 /*! \brief calculate and return the boundaries of a text object
156  *  \par Function Description
157  *  This function calculates the object boudaries of a text \a object.
158  *
159  *  \param [in]  toplevel  The TOPLEVEL object.
160  *  \param [in]  o_current a text object
161  *  \param [out] left      the left world coord
162  *  \param [out] top       the top world coord
163  *  \param [out] right     the right world coord
164  *  \param [out] bottom    the bottom world coord
165  */
world_get_text_bounds(TOPLEVEL * toplevel,OBJECT * o_current,int * left,int * top,int * right,int * bottom)166 int world_get_text_bounds(TOPLEVEL *toplevel, OBJECT *o_current, int *left,
167                           int *top, int *right, int *bottom)
168 {
169   if (toplevel->rendered_text_bounds_func != NULL) {
170     return
171       toplevel->rendered_text_bounds_func (toplevel->rendered_text_bounds_data,
172                                            o_current,
173                                            left, top, right, bottom);
174   }
175 
176   return FALSE;
177 }
178 
179 /*! \brief get the position of a text object
180  *  \par Function Description
181  *  This function gets the position of the base point of a text object.
182  *
183  *  \param [in] toplevel The toplevel environment.
184  *  \param [out] x       pointer to the x-position
185  *  \param [out] y       pointer to the y-position
186  *  \param [in] object   The object to get the position.
187  *  \return TRUE if successfully determined the position, FALSE otherwise
188  */
o_text_get_position(TOPLEVEL * toplevel,gint * x,gint * y,OBJECT * object)189 gboolean o_text_get_position (TOPLEVEL *toplevel, gint *x, gint *y,
190                               OBJECT *object)
191 {
192   *x = object->text->x;
193   *y = object->text->y;
194   return TRUE;
195 }
196 
197 
198 /*! \brief count the lines of a text string
199  *  \par Function Description
200  *  This function just counts the number of lines that are
201  *  in the \a string.
202 
203  *  \param [in] string  text string to count the lines
204  *  \return the number of lines
205  */
o_text_num_lines(const char * string)206 int o_text_num_lines(const char *string)
207 {
208   int line_count = 0;
209   const gchar *aux;
210   gunichar current_char;
211 
212   if (string == NULL) {
213     return 0;
214   }
215 
216   /* if it's not null, then we have at least one line */
217   line_count++;
218   /* Count how many \n are in the string */
219   aux = string;
220   while (aux && ((gunichar) (*aux) != 0) ) {
221     current_char = g_utf8_get_char_validated(aux, -1);
222     if (current_char == '\n')
223       line_count++;
224     aux = g_utf8_find_next_char(aux, NULL);
225   }
226 
227   return (line_count);
228 }
229 
230 
231 /*! \brief Creates a text OBJECT and the graphical objects representing it
232  *  \par Function Description
233  *  Create an OBJECT of type OBJ_TEXT.
234  *
235  *  \param [in]  toplevel              The TOPLEVEL object.
236  *  \param [in]  type                   OBJ_TEXT (TODO: why bother)
237  *  \param [in]  color                  The color of the text.
238  *  \param [in]  x                      World x coord of text.
239  *  \param [in]  y                      World y coord of text.
240  *  \param [in]  alignment              How text bounding box aligns on (x, y).
241  *  \param [in]  angle                  Angle at which text will appear.
242  *  \param [in]  string                 The text (TODO: can be char const *)!
243  *  \param [in]  size                   Text size.
244  *  \param [in]  visibility             VISIBLE or INVISIBLE.
245  *  \param [in]  show_name_value        SHOW_NAME_VALUE or friends.
246  *  \return Pointer to text OBJECT.
247  *
248  *  \note
249  *  Caller is responsible for string; this function allocates its own copy.
250  */
o_text_new(TOPLEVEL * toplevel,char type,int color,int x,int y,int alignment,int angle,const char * string,int size,int visibility,int show_name_value)251 OBJECT *o_text_new(TOPLEVEL *toplevel,
252 		   char type, int color, int x, int y, int alignment,
253 		   int angle, const char *string, int size,
254 		   int visibility, int show_name_value)
255 {
256   OBJECT *new_node=NULL;
257   TEXT *text;
258 
259   if (string == NULL) {
260     return(NULL);
261   }
262 
263   new_node = s_basic_new_object(type, "text");
264 
265   text = (TEXT *) g_malloc(sizeof(TEXT));
266 
267   text->string = g_strdup (string);
268   text->disp_string = NULL; /* We'll fix this up later */
269   text->length = strlen(string);
270   text->size = size;
271   text->alignment = alignment;
272   text->x = x;
273   text->y = y;
274   text->angle = angle;
275 
276   new_node->text = text;
277 
278   new_node->color = color;
279   o_set_visibility (toplevel, new_node, visibility);
280   new_node->show_name_value = show_name_value;
281 
282   update_disp_string (new_node);
283 
284   /* Update bounding box */
285   new_node->w_bounds_valid = FALSE;
286 
287   return new_node;
288 }
289 
290 /*! \brief update the visual boundaries of the text object
291  *  \par Function Description
292  *  This function updates the boundaries of the object \a o_current.
293  *
294  *  \param [in]  toplevel  The TOPLEVEL object
295  *  \param [in]  o_current The OBJECT to update
296  */
o_text_recalc(TOPLEVEL * toplevel,OBJECT * o_current)297 void o_text_recalc(TOPLEVEL *toplevel, OBJECT *o_current)
298 {
299   int left, right, top, bottom;
300 
301   if ((!o_is_visible (toplevel, o_current)) &&
302       (!toplevel->show_hidden_text)) {
303     return;
304   }
305 
306   if ( !world_get_text_bounds(toplevel, o_current, &left, &top, &right, &bottom) )
307     return;
308 
309   o_current->w_left = left;
310   o_current->w_top = top;
311   o_current->w_right = right;
312   o_current->w_bottom = bottom;
313   o_current->w_bounds_valid = TRUE;
314 }
315 
316 /*! \brief read a text object from a char buffer
317  *  \par Function Description
318  *  This function reads a text object from the textbuffer \a tb and
319  *  the text starting with the line \a firstline.
320  *  If the line object was read successfully, a new object is
321  *  create and appended to the \a object_list.
322  *
323  *  \param [in] toplevel     The TOPLEVEL object
324  *  \param [in] first_line   the first line of the text
325  *  \param [in] tb           a text buffer (usually a line of a schematic file)
326  *  \param [in] release_ver  The release number gEDA
327  *  \param [in] fileformat_ver a integer value of the file format
328  *  \return The object list, or NULL on error.
329  */
o_text_read(TOPLEVEL * toplevel,const char * first_line,TextBuffer * tb,unsigned int release_ver,unsigned int fileformat_ver,GError ** err)330 OBJECT *o_text_read (TOPLEVEL *toplevel,
331 		    const char *first_line,
332 		    TextBuffer *tb,
333 		    unsigned int release_ver,
334             unsigned int fileformat_ver,
335             GError **err)
336 {
337   OBJECT *new_obj;
338   char type;
339   int x, y;
340   int color;
341   int size;
342   int visibility;
343   int show_name_value;
344   int angle;
345   int alignment;
346   int num_lines = 0;
347   int i;
348   char* string = NULL;
349   GString *textstr;
350 
351   if (fileformat_ver >= 1) {
352     if (sscanf(first_line, "%c %d %d %d %d %d %d %d %d %d\n", &type, &x, &y,
353 	       &color, &size,
354 	       &visibility, &show_name_value,
355 	       &angle, &alignment, &num_lines) != 10) {
356       g_set_error(err, EDA_ERROR, EDA_ERROR_PARSE, _("Failed to parse text object"));
357       return NULL;
358     }
359   } else if (release_ver < VERSION_20000220) {
360     /* yes, above less than (not less than and equal) is correct. The format */
361     /* change occurred in 20000220 */
362     if (sscanf(first_line, "%c %d %d %d %d %d %d %d\n", &type, &x, &y,
363 	       &color, &size,
364 	       &visibility, &show_name_value,
365 	       &angle) != 8) {
366       g_set_error(err, EDA_ERROR, EDA_ERROR_PARSE, _("Failed to parse text object"));
367       return NULL;
368     }
369     alignment = LOWER_LEFT; /* older versions didn't have this */
370     num_lines = 1; /* only support a single line */
371   } else {
372     if (sscanf(first_line, "%c %d %d %d %d %d %d %d %d\n", &type, &x, &y,
373 	       &color, &size,
374 	       &visibility, &show_name_value,
375            &angle, &alignment) != 9) {
376       g_set_error (err, EDA_ERROR, EDA_ERROR_PARSE, _("Failed to parse text object"));
377       return NULL;
378     }
379     num_lines = 1; /* only support a single line */
380   }
381 
382   if (size == 0) {
383     s_log_message(_("Found a zero size text string [ %c %d %d %d %d %d %d %d %d ]\n"), type, x, y, color, size, visibility, show_name_value, angle, alignment);
384   }
385 
386   switch(angle) {
387 
388     case(0):
389     case(90):
390     case(180):
391     case(270):
392     break;
393 
394     default:
395       s_log_message(_("Found an unsupported text angle [ %c %d %d %d %d %d %d %d %d ]\n"),
396                     type, x, y, color, size, visibility, show_name_value, angle, alignment);
397       s_log_message(_("Setting angle to 0\n"));
398       angle=0;
399       break;
400 
401   }
402 
403   switch(alignment) {
404     case(LOWER_LEFT):
405     case(MIDDLE_LEFT):
406     case(UPPER_LEFT):
407     case(LOWER_MIDDLE):
408     case(MIDDLE_MIDDLE):
409     case(UPPER_MIDDLE):
410     case(LOWER_RIGHT):
411     case(MIDDLE_RIGHT):
412     case(UPPER_RIGHT):
413 
414     break;
415 
416     default:
417       s_log_message(_("Found an unsupported text alignment [ %c %d %d %d %d %d %d %d %d ]\n"),
418                     type, x, y, color, size, visibility, show_name_value, angle, alignment);
419       s_log_message(_("Setting alignment to LOWER_LEFT\n"));
420       alignment = LOWER_LEFT;
421       break;
422   }
423 
424   if (color < 0 || color > MAX_COLORS) {
425     s_log_message(_("Found an invalid color [ %s ]\n"), first_line);
426     s_log_message(_("Setting color to default color\n"));
427     color = DEFAULT_COLOR;
428   }
429 
430   g_assert(num_lines && num_lines > 0);
431 
432   textstr = g_string_new ("");
433   for (i = 0; i < num_lines; i++) {
434     const gchar *line;
435 
436     line = s_textbuffer_next_line (tb);
437 
438     if (line == NULL) {
439       g_string_free (textstr, TRUE);
440       g_set_error(err, EDA_ERROR, EDA_ERROR_PARSE, _("Unexpected end-of-file after %d lines"), i);
441       return NULL;
442     }
443 
444     textstr = g_string_append (textstr, line);
445   }
446   /* retrieve the character string from the GString */
447   string = g_string_free (textstr, FALSE);
448 
449   string = remove_last_nl(string);
450 
451   /* convert the character string to UTF-8 if necessary */
452   if (!g_utf8_validate (string, -1, NULL)) {
453     /* if it is not utf-8, it is ISO_8859-15 */
454     gchar *tmp = g_convert (string, strlen (string),
455                             "UTF-8", "ISO_8859-15",
456                             NULL, NULL, NULL);
457     if (tmp == NULL) {
458       fprintf (stderr, "Failed to convert text string to UTF-8: %s.\n",
459                string);
460     } else {
461       /* successfully converted string, now use tmp as string */
462       g_free (string);
463       string = tmp;
464     }
465   }
466 
467   new_obj = o_text_new(toplevel, type, color, x, y,
468                        alignment, angle, string,
469                        size, visibility, show_name_value);
470   g_free(string);
471 
472   return new_obj;
473 }
474 
475 
476 /*! \brief Create a string representation of the text object
477  *  \par Function Description
478  *  This function takes a text \a object and return a string
479  *  according to the file format definition.
480  *
481  *  \param [in] toplevel  a TOPLEVEL structure
482  *  \param [in] object  a text OBJECT
483  *  \return the string representation of the text OBJECT
484  */
o_text_save(TOPLEVEL * toplevel,OBJECT * object)485 char *o_text_save(TOPLEVEL *toplevel, OBJECT *object)
486 {
487   int x, y;
488   int size;
489   char *string;
490   char *buf;
491   int num_lines;
492 
493   x = object->text->x;
494   y = object->text->y;
495 
496   string = object->text->string;
497   size = object->text->size;
498 
499   /* string can have multiple lines (seperated by \n's) */
500   num_lines = o_text_num_lines(string);
501 
502   buf = g_strdup_printf ("%c %d %d %d %d %d %d %d %d %d\n%s", object->type,
503                          x, y, object->color, size,
504                          o_is_visible (toplevel, object) ? VISIBLE : INVISIBLE,
505                          object->show_name_value, object->text->angle,
506                          object->text->alignment, num_lines, string);
507 
508   return(buf);
509 }
510 
511 /*! \brief recreate the graphics of a text object
512  *  \par Function Description
513  *  This function updates the underlying primary of the text object
514  *  \a o_current.
515  *
516  *  \param toplevel  The TOPLEVEL object
517  *  \param o_current The text object to update
518  */
o_text_recreate(TOPLEVEL * toplevel,OBJECT * o_current)519 void o_text_recreate(TOPLEVEL *toplevel, OBJECT *o_current)
520 {
521   o_emit_pre_change_notify (toplevel, o_current);
522   update_disp_string (o_current);
523   o_current->w_bounds_valid = FALSE;
524   o_emit_change_notify (toplevel, o_current);
525 }
526 
527 /*! \brief move a text object
528  *  \par Function Description
529  *  This function changes the position of a text object \a o_current.
530  *
531  *  \param [in] toplevel     The TOPLEVEL object
532  *  \param [in] dx           The x-distance to move the object
533  *  \param [in] dy           The y-distance to move the object
534  *  \param [in] o_current    The text OBJECT to be moved
535  */
o_text_translate_world(TOPLEVEL * toplevel,int dx,int dy,OBJECT * o_current)536 void o_text_translate_world(TOPLEVEL *toplevel,
537                             int dx, int dy, OBJECT *o_current)
538 {
539   o_current->text->x = o_current->text->x + dx;
540   o_current->text->y = o_current->text->y + dy;
541 
542   /* Update bounding box */
543   o_current->w_bounds_valid = FALSE;
544 }
545 
546 /*! \brief create a copy of a text object
547  *  \par Function Description
548  *  This function creates a copy of the text object \a o_current.
549  *
550  *  \param [in] toplevel     The TOPLEVEL object
551  *  \param [in] o_current    The object that is copied
552  *  \return a new text object
553  */
o_text_copy(TOPLEVEL * toplevel,OBJECT * o_current)554 OBJECT *o_text_copy(TOPLEVEL *toplevel, OBJECT *o_current)
555 {
556   OBJECT *new_obj;
557 
558   new_obj = o_text_new (toplevel, OBJ_TEXT, o_current->color,
559                         o_current->text->x, o_current->text->y,
560                         o_current->text->alignment,
561                         o_current->text->angle,
562                         o_current->text->string,
563                         o_current->text->size,
564                         o_is_visible (toplevel, o_current) ? VISIBLE : INVISIBLE,
565                         o_current->show_name_value);
566 
567   return new_obj;
568 }
569 
570 
571 /*! \brief write a text string to a postscript file
572  *  \par Function Description
573  *  This function writes the single \a string into the postscript file \a fp.
574  *
575  *  \param [in] fp           pointer to a FILE structure
576  *  \param [in] string       The string to print
577  *  \param [in] unicode_count Number of items in the unicode table
578  *  \param [in] unicode_table Table of unicode items
579  *
580  *  \todo investigate whether the TAB character is handled correctly
581  */
o_text_print_text_string(FILE * fp,char * string,int unicode_count,gunichar * unicode_table)582 void o_text_print_text_string(FILE *fp, char *string, int unicode_count,
583 			      gunichar *unicode_table)
584 {
585   int j;
586   gchar *aux;
587   gunichar current_char, c;
588 
589   if (!string)
590   {
591     return;
592   }
593 
594   aux = string;
595 
596   fprintf(fp, "(");
597 
598   while (aux && ((gunichar) (*aux) != 0)) {
599     current_char = g_utf8_get_char_validated(aux, -1);
600     if (current_char == '(' || current_char == ')' || current_char == '\\') {
601       fprintf(fp, "\\");
602     }
603 
604     c = current_char;
605     if (c >= 128) {
606       current_char = '?';
607       if (unicode_count)  {
608         for (j = 0; j < unicode_count; j++)
609           if (c == unicode_table[j]) {
610 	    current_char = j + 128;
611             break;
612           }
613       }
614     }
615 
616 
617     if (current_char == '\t') {
618       /* Output eight spaces instead of the tab character */
619       fprintf(fp, "       ");
620     } else {
621       fprintf(fp, "%c", current_char);
622     }
623 
624     aux = g_utf8_find_next_char(aux, NULL);
625   }
626 
627   fprintf(fp,") ");
628 }
629 
630 
631 /*! \brief print a text object into a postscript file
632  *  \par Function Description
633  *  This function writes the postscript representation of the text object
634  *  \a o_current into the the file \a fp.
635  *  \param [in] toplevel     The TOPLEVEL object
636  *  \param [in] fp           pointer to a FILE structure
637  *  \param [in] o_current    The OBJECT to print
638  *  \param [in] origin_x     x-coord of the postscript origin
639  *  \param [in] origin_y     y-coord of the postscript origin
640  *  \param [in] unicode_count Number of items in the unicode table
641  *  \param [in] unicode_table Table of unicode items
642  */
o_text_print(TOPLEVEL * toplevel,FILE * fp,OBJECT * o_current,int origin_x,int origin_y,int unicode_count,gunichar * unicode_table)643 void o_text_print(TOPLEVEL *toplevel, FILE *fp, OBJECT *o_current,
644 		  int origin_x, int origin_y,
645 		  int unicode_count, gunichar *unicode_table)
646 {
647   int alignment;
648   char *centering_control = NULL;
649   char *p,*s;
650   char *output_string = NULL;
651   char *name = NULL;
652   char *value = NULL;
653   int x, y, angle, len;
654   float font_size;
655 
656 
657   if (!o_current->text->string) {
658     return;
659   }
660 
661   f_print_set_color(toplevel, fp, o_current->color);
662 
663 
664   if (o_attrib_get_name_value (o_current, &name, &value)) {
665     switch(o_current->show_name_value) {
666       case(SHOW_NAME_VALUE):
667         output_string = g_strdup(o_current->text->string);
668         break;
669 
670       case(SHOW_NAME):
671         if (name[0] != '\0') {
672           output_string = g_strdup(name);
673         } else {
674           fprintf(stderr,
675                   "Got an improper attribute: %s\n",
676                   o_current->text->string);
677           output_string = g_strdup("invalid");
678         }
679         break;
680 
681       case(SHOW_VALUE):
682         if (value[0] != '\0') {
683           output_string = g_strdup(value);
684         } else {
685           /* you probably can remove this now... */
686           /* since improper attributes will never get here */
687           fprintf(stderr,
688                   "Got an improper attribute: %s\n",
689                   o_current->text->string);
690           output_string = g_strdup("invalid");
691         }
692         break;
693 
694     default:
695       g_return_if_reached ();
696 
697     }
698   } else {
699     output_string = g_strdup(o_current->text->string);
700   }
701 
702   /* Apply alignment map to apply when text is 180 degrees rotated.
703    * We want the text on the printer to appear upside right, even
704    * though mathematically it aught to be upside down.  To make this
705    * work, we will reset the angle to 0, when it's equal to 180
706    * degrees, then apply a transformation to the origin location as if
707    * the text was rotated about that point.  E.g. if the text origin
708    * was at the lower left, and the text was rotated by 180 degrees,
709    * it would be as if the origin was at the upper right. The same
710    * reasoning has been applied to all 8 other text origins.
711    * MIDDLE_MIDDLE maps to itself.
712    */
713   alignment = o_current->text->alignment;
714   angle = o_current->text->angle;
715   if(angle == 180) {
716     angle = 0;        /* reset angle to 0 to make text upright */
717     switch(alignment) {
718     case(LOWER_LEFT):    alignment = UPPER_RIGHT;
719       break;
720     case(MIDDLE_LEFT):   alignment = MIDDLE_RIGHT;
721       break;
722     case(UPPER_LEFT):    alignment = LOWER_RIGHT;
723       break;
724     case(LOWER_MIDDLE):  alignment = UPPER_MIDDLE;
725       break;
726     case(MIDDLE_MIDDLE): alignment = MIDDLE_MIDDLE;
727       break;
728     case(UPPER_MIDDLE):  alignment = LOWER_MIDDLE;
729       break;
730     case(LOWER_RIGHT):   alignment = UPPER_LEFT;
731       break;
732     case(MIDDLE_RIGHT):  alignment = MIDDLE_LEFT;
733       break;
734     case(UPPER_RIGHT):   alignment = LOWER_LEFT;
735       break;
736     }
737   }
738 
739   /* Create an appropriate control string for the centering. */
740   switch(alignment) {
741                                        /* hcenter rjustify vcenter vjustify */
742   case(LOWER_LEFT):    centering_control = "false false false false";
743     break;
744   case(MIDDLE_LEFT):   centering_control = "false false true false";
745     break;
746   case(UPPER_LEFT):    centering_control = "false false false true";
747     break;
748   case(LOWER_MIDDLE):  centering_control = "true false false false";
749     break;
750   case(MIDDLE_MIDDLE): centering_control = "true false true false";
751     break;
752   case(UPPER_MIDDLE):  centering_control = "true false false true";
753     break;
754   case(LOWER_RIGHT):   centering_control = "false true false false";
755     break;
756   case(MIDDLE_RIGHT):  centering_control = "false true true false";
757     break;
758   case(UPPER_RIGHT):   centering_control = "false true false true";
759     break;
760   }
761 
762   font_size = o_text_get_font_size_in_points (toplevel, o_current)
763                 / 72.0 * 1000.0;
764   fprintf(fp,"%s %f [",centering_control, font_size * PRINT_LINE_SPACING);
765 
766   /* split the line at each newline and print them */
767   p = output_string;   /* Current point */
768   s = output_string;   /* Start of the current string */
769   len = strlen(output_string)+1;
770   while(len != 0) {
771     /* Have we reached the end of a line? */
772     if((*p == '\n') || (*p == '\0')) {
773       /* Yes, replace the newline with a NULL and output the string */
774       *p = '\0';
775       o_text_print_text_string(fp,s,unicode_count,unicode_table);
776       /* Update output string start for next string */
777       s = p+1; /* One past the current character. */
778     }
779     p++;   /* Advance to next character */
780     len--; /* Keep track of how many characters left to process */
781   }
782 
783   /* Finish up with the rest of the text print command */
784   /* Collect pertinent info about the text location */
785   x = o_current->text->x;
786   y = o_current->text->y;
787   fprintf(fp,"] %d %d %d %f text\n",angle,x,y,font_size);
788 
789 
790   g_free(output_string);
791   g_free(name);
792   g_free(value);
793 }
794 
795 
796 /*! \brief rotate a text object around a centerpoint
797  *  \par Function Description
798  *  This function rotates a text \a object around the point
799  *  (\a world_centerx, \a world_centery).
800  *
801  *  \param [in] toplevel      The TOPLEVEL object
802  *  \param [in] world_centerx x-coord of the rotation center
803  *  \param [in] world_centery y-coord of the rotation center
804  *  \param [in] angle         The angle to rotate the text object
805  *  \param [in] object        The text object
806  *  \note only steps of 90 degrees are allowed for the \a angle
807  */
o_text_rotate_world(TOPLEVEL * toplevel,int world_centerx,int world_centery,int angle,OBJECT * object)808 void o_text_rotate_world(TOPLEVEL *toplevel,
809                          int world_centerx, int world_centery,
810                          int angle, OBJECT *object)
811 {
812   int x, y;
813   int newx, newy;
814 
815   g_return_if_fail(object != NULL);
816   g_return_if_fail(object->type == OBJ_TEXT);
817 
818   object->text->angle = ( object->text->angle + angle ) % 360;
819 
820   x = object->text->x + (-world_centerx);
821   y = object->text->y + (-world_centery);
822 
823   rotate_point_90(x, y, angle, &newx, &newy);
824 
825   x = newx + (world_centerx);
826   y = newy + (world_centery);
827 
828   o_text_translate_world(toplevel, x-object->text->x, y-object->text->y, object);
829 
830   o_text_recreate(toplevel, object);
831 }
832 
833 
834 /*! \brief mirror a text object horizontaly at a centerpoint
835  *  \par Function Description
836  *  This function mirrors a text \a object horizontaly at the point
837  *  (\a world_centerx, \a world_centery).
838  *
839  *  \param [in] toplevel      The TOPLEVEL object
840  *  \param [in] world_centerx x-coord of the mirror position
841  *  \param [in] world_centery y-coord of the mirror position
842  *  \param [in] object        The text object
843  */
o_text_mirror_world(TOPLEVEL * toplevel,int world_centerx,int world_centery,OBJECT * object)844 void o_text_mirror_world(TOPLEVEL *toplevel,
845 			 int world_centerx, int world_centery,
846 			 OBJECT *object)
847 {
848   int origx, origy;
849   int x, y;
850 
851   origx = object->text->x;
852   origy = object->text->y;
853 
854   x = origx + (-world_centerx);
855   y = origy + (-world_centery);
856 
857   if ((object->text->angle%180)==0) {
858     switch(object->text->alignment) {
859       case(LOWER_LEFT):
860         object->text->alignment=LOWER_RIGHT;
861         break;
862 
863       case(MIDDLE_LEFT):
864         object->text->alignment=MIDDLE_RIGHT;
865         break;
866 
867       case(UPPER_LEFT):
868         object->text->alignment=UPPER_RIGHT;
869         break;
870 
871       case(LOWER_RIGHT):
872         object->text->alignment=LOWER_LEFT;
873         break;
874 
875       case(MIDDLE_RIGHT):
876         object->text->alignment=MIDDLE_LEFT;
877         break;
878 
879       case(UPPER_RIGHT):
880         object->text->alignment=UPPER_LEFT;
881         break;
882 
883       default:
884         break;
885     }
886   } else {
887     switch(object->text->alignment) {
888       case(LOWER_LEFT):
889       object->text->alignment=UPPER_LEFT;
890       break;
891 
892       case(UPPER_LEFT):
893       object->text->alignment=LOWER_LEFT;
894       break;
895 
896       case(LOWER_RIGHT):
897       object->text->alignment=UPPER_RIGHT;
898       break;
899 
900       case(UPPER_RIGHT):
901       object->text->alignment=LOWER_RIGHT;
902       break;
903 
904       case(LOWER_MIDDLE):
905       object->text->alignment=UPPER_MIDDLE;
906       break;
907 
908       case(UPPER_MIDDLE):
909       object->text->alignment=LOWER_MIDDLE;
910       break;
911 
912       default:
913       break;
914     }
915   }
916 
917   object->text->x = -x + (world_centerx);
918   object->text->y =  y + (world_centery);
919 
920   o_text_recreate(toplevel, object);
921 }
922 
923 /*! \brief Calculates the distance between the given point and the closest
924  *  point on the text.
925  *
926  *  This function will calculate the distance to the text regardless
927  *  if the text is visible or not.
928  *
929  *  \param [in] object       The text OBJECT.
930  *  \param [in] x            The x coordinate of the given point.
931  *  \param [in] y            The y coordinate of the given point.
932  *  \param [in] force_solid  If true, force treating the object as solid.
933  *  \return The shortest distance from the object to the point. If the
934  *  distance cannot be calculated, this function returns a really large
935  *  number (G_MAXDOUBLE).  With an invalid parameter, this funciton
936  *  returns G_MAXDOUBLE.
937  */
o_text_shortest_distance(OBJECT * object,int x,int y,int force_solid)938 double o_text_shortest_distance (OBJECT *object, int x, int y, int force_solid)
939 {
940   double dx, dy;
941 
942   g_return_val_if_fail (object->text != NULL, G_MAXDOUBLE);
943 
944   dx = min (x - object->w_left, object->w_right - x);
945   dy = min (y - object->w_top, object->w_bottom - y);
946 
947   dx = min (dx, 0);
948   dy = min (dy, 0);
949 
950   return sqrt ((dx * dx) + (dy * dy));
951 }
952 
953 /*! \brief Set the string displayed by a text object.
954  *  \par Function Description
955  *  Updates the text object with a new text string.
956  *
957  *  \param [in]  toplevel              The TOPLEVEL object.
958  *  \param [in]  obj                   The text object.
959  *  \param [in]  new_string            The new value.
960  */
o_text_set_string(TOPLEVEL * toplevel,OBJECT * obj,const gchar * new_string)961 void o_text_set_string (TOPLEVEL *toplevel, OBJECT *obj,
962                         const gchar *new_string)
963 {
964   g_return_if_fail (toplevel != NULL);
965   g_return_if_fail (obj != NULL);
966   g_return_if_fail (obj->type == OBJ_TEXT);
967   g_return_if_fail (obj->text != NULL);
968   g_return_if_fail (new_string != NULL);
969 
970   g_free (obj->text->string);
971   obj->text->string = g_strdup (new_string);
972 
973   o_text_recreate (toplevel, obj);
974 
975   if (obj->attached_to != NULL)
976     o_attrib_emit_attribs_changed (toplevel, obj->attached_to);
977 }
978 
979 
980 
981 /*! \brief Get the string displayed by a text object.
982  *  \par Function Description
983  *  Retrieve the text string from a text object. The returned string
984  *  should be treated as constant.
985  *
986  *  \param [in]  toplevel              The TOPLEVEL object.
987  *  \param [in]  obj                   The text object.
988  *  \return The text object's string, or NULL on failure.
989  */
o_text_get_string(TOPLEVEL * toplevel,OBJECT * obj)990 const gchar *o_text_get_string (TOPLEVEL *toplevel, OBJECT *obj)
991 {
992   g_return_val_if_fail (toplevel != NULL, NULL);
993   g_return_val_if_fail (obj != NULL, NULL);
994   g_return_val_if_fail (obj->type == OBJ_TEXT, NULL);
995   g_return_val_if_fail (obj->text != NULL, NULL);
996 
997   return obj->text->string;
998 }
999 
1000 /*! \brief Set the font-renderer-specific bounds function.
1001  *  \par Function Description
1002  *  Set the function to be used to calculate text bounds for a given
1003  *  #TOPLEVEL.
1004  *
1005  *  \param [in] toplevel     The TOPLEVEL object
1006  *  \param [in] func      Function to use.
1007  *  \param [in] user_data User data to be passed to the function.
1008  */
o_text_set_rendered_bounds_func(TOPLEVEL * toplevel,RenderedBoundsFunc func,void * user_data)1009 void o_text_set_rendered_bounds_func (TOPLEVEL *toplevel,
1010                                       RenderedBoundsFunc func,
1011                                       void *user_data) {
1012   toplevel->rendered_text_bounds_func = func;
1013   toplevel->rendered_text_bounds_data = user_data;
1014 }
1015 
1016 
1017 /*! \brief Return font size of a text object in postscript points.
1018  *
1019  *  \par Description
1020  *  gEDA fonts are specified in a non-standard unit. This
1021  *  function applies an appopriate scaling to return the
1022  *  font size in postscript points.
1023  *
1024  *  \param [in] toplevel  The TOPLEVEL object
1025  *  \param [in] object    The text OBJECT whos font size to return
1026  *  \return The font size converted to postscript points.
1027  */
o_text_get_font_size_in_points(TOPLEVEL * toplevel,OBJECT * object)1028 double o_text_get_font_size_in_points (TOPLEVEL *toplevel, OBJECT *object)
1029 {
1030   g_return_val_if_fail (object->type == OBJ_TEXT, 0.);
1031 
1032   return object->text->size * GEDA_FONT_FACTOR;
1033 }
1034