4 * PCB, interactive printed circuit board design
5 * Copyright (C) 2011 Andrew Poelstra
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 * Contact addresses for paper mail and Email:
22 * Andrew Poelstra, 16966 60A Ave, V3S 8X5 Surrey, BC, Canada
27 /*! \file <pcb-printf.c>
28 * \brief Implementation of printf wrapper to output pcb coords and angles
30 * For details of all supported specifiers, see the comment at the
40 #include "pcb-printf.h"
42 /* Helper macros for tables */
43 #define MM_TO_COORD3(a,b,c) MM_TO_COORD (a), MM_TO_COORD (b), MM_TO_COORD (c)
44 #define MIL_TO_COORD3(a,b,c) MIL_TO_COORD (a), MIL_TO_COORD (b), MIL_TO_COORD (c)
45 #define MM_TO_COORD5(a,b,c,d,e) MM_TO_COORD (a), MM_TO_COORD (b), MM_TO_COORD (c), \
46 MM_TO_COORD (d), MM_TO_COORD (e)
47 #define MIL_TO_COORD5(a,b,c,d,e) MIL_TO_COORD (a), MIL_TO_COORD (b), MIL_TO_COORD (c), \
48 MIL_TO_COORD (d), MIL_TO_COORD (e)
50 /* These should be kept in order of smallest scale_factor
51 * to largest -- the code uses this ordering when finding
52 * the best scale to use for a group of measures. */
54 static Unit Units
[] = {
55 { 0, "km", NULL
, 'k', 0.000001, METRIC
, ALLOW_KM
, 5,
56 0.00005, 0.0005, 0.0025, 0.05, 0.25,
58 { 0, "m", NULL
, 'f', 0.001, METRIC
, ALLOW_M
, 5,
59 0.0005, 0.005, 0.025, 0.5, 2.5,
61 { 0, "cm", NULL
, 'e', 0.1, METRIC
, ALLOW_CM
, 5,
62 0.005, 0.05, 0.25, 5, 25,
64 { 0, "mm", NULL
, 'm', 1, METRIC
, ALLOW_MM
, 4,
65 0.005, 0.05, 0.25, 5, 25,
67 { 0, "um", NULL
, 'u', 1000, METRIC
, ALLOW_UM
, 2,
68 0.005, 0.05, 0.25, 5, 25,
70 { 0, "nm", NULL
, 'n', 1000000, METRIC
, ALLOW_NM
, 0,
71 5, 50, 2500, 5000, 25000,
73 /* Hack: Pixels get parsed like nanometers. If the value of the
74 * resulting integer is sufficiently small, the code interprets
75 * it a screen pixels. This affects rat thickness. */
76 { 0, "px", NULL
, 'n', 1000000, METRIC
, ALLOW_NM
, 0,
77 5, 50, 2500, 5000, 25000,
81 { 0, "in", NULL
, 'i', 0.001, IMPERIAL
, ALLOW_IN
, 5,
82 0.1, 1.0, 5.0, 25, 100,
84 { 0, "mil", NULL
, 'l', 1, IMPERIAL
, ALLOW_MIL
, 2,
85 0.1, 1.0, 10, 100, 1000,
87 { 0, "dmil", NULL
, 't', 10, IMPERIAL
, ALLOW_DMIL
, 0,
88 1, 10, 100, 1000, 10000,
90 { 0, "cmil", NULL
, 'c', 100, IMPERIAL
, ALLOW_CMIL
, 0,
91 1, 10, 100, 1000, 10000,
94 #define N_UNITS ((int) (sizeof Units / sizeof Units[0]))
95 /* \brief Initialize non-static data for pcb-printf
96 * \par Function Description
97 * Assigns each unit its index for quick access through the
98 * main units array, and internationalize the units for GUI
101 void initialize_units()
104 for (i
= 0; i
< N_UNITS
; ++i
)
107 Units
[i
].in_suffix
= _(Units
[i
].suffix
);
111 /* \brief Get/set a mask of units to use when saving .pcb files
112 * \par Function Description
113 * If passed 0, returns the current mask of units to use in a .pcb
114 * file; if passed anything else, replaces the current mask. This
115 * mask should only contain units which are readable by recent versions
116 * of pcb; currently this means only ALLOW_MIL and ALLOW_MM. (Versions
117 * prior to 20110703 nominally support other units, but in fact the scaling
118 * calculations are incorrect so the wrong values will be read! See commit
119 * 750a1c5 for more details.)
121 * \return the current mask.
123 enum e_allow
set_allow_readable(enum e_allow new_mask
)
125 static enum e_allow readable_mask
= ALLOW_READABLE
;
127 readable_mask
= new_mask
;
128 return readable_mask
;
132 /* TABLE FORMAT | default | min | max
138 static Increments increments_metric
= {
140 MM_TO_COORD3 (0.1, 0.01, 1.0),
141 MM_TO_COORD3 (0.2, 0.01, 0.5),
142 MM_TO_COORD3 (0.1, 0.005, 0.5),
143 MM_TO_COORD3 (0.05, 0.005, 0.5)
145 static Increments increments_imperial
= {
147 MIL_TO_COORD3 (5, 1, 25),
148 MIL_TO_COORD3 (10, 1, 10),
149 MIL_TO_COORD3 (5, 0.5, 10),
150 MIL_TO_COORD3 (2, 0.5, 10)
153 /* \brief Obtain a unit object from its suffix
154 * \par Function Description
155 * Looks up a given suffix in the main units array. Internationalized
156 * unit suffixes are not supported, though pluralized units are, for
157 * backward-compatibility.
159 * \param [in] const_suffix The suffix to look up
161 * \return A const pointer to the Unit struct, or NULL if none was found
163 const Unit
*get_unit_struct (const char *const_suffix
)
167 /* Turn given suffix into something we can modify... */
168 char *m_suffix
= g_strdup (const_suffix
);
169 /* ...and store this in a pointer we can move. */
170 char *suffix
= m_suffix
;
172 /* Determine bounds */
173 while (isspace (*suffix
))
175 while (isalnum (suffix
[s_len
]))
178 /* Also understand plural suffixes: "inches", "mils" */
181 if (suffix
[s_len
- 2] == 'e' && suffix
[s_len
- 1] == 's')
182 suffix
[s_len
- 2] = 0;
183 else if (suffix
[s_len
- 1] == 's')
184 suffix
[s_len
- 1] = 0;
188 if (*suffix
&& s_len
> 0)
189 for (i
= 0; i
< N_UNITS
; ++i
)
190 if (strncmp (suffix
, Units
[i
].suffix
, s_len
) == 0 ||
191 strncmp (suffix
, Units
[i
].alias
[0], s_len
) == 0)
200 void copy_nonzero_increments (Increments
*dst
, const Increments
*src
)
202 if (src
->grid
>= dst
->grid_min
&& src
->grid
<= dst
->grid_max
)
203 dst
->grid
= src
->grid
;
204 if (src
->line
>= dst
->line_min
&& src
->line
<= dst
->line_max
)
205 dst
->line
= src
->line
;
206 if (src
->size
>= dst
->size_min
&& src
->size
<= dst
->size_max
)
207 dst
->size
= src
->size
;
208 if (src
->clear
>= dst
->clear_min
&& src
->clear
<= dst
->clear_max
)
209 dst
->clear
= src
->clear
;
213 /* \brief Returns the master unit list. This may not be modified. */
214 const Unit
*get_unit_list (void)
218 /* \brief Returns the length of the master unit list. */
219 int get_n_units (void)
224 /* \brief Obtain the increment values for a given family of units
225 * \par Function Description
227 * \param [in] family One of METRIC or IMPERIAL.
229 * \return A pointer to the appropriate increments structure.
231 Increments
*get_increments_struct (enum e_family family
)
236 return &increments_metric
;
238 return &increments_imperial
;
243 /* \brief Convert a pcb coord to the given unit
245 * \param [in] unit The unit to convert to
246 * \param [in] x The quantity to convert
248 * \return The converted measure
250 double coord_to_unit (const Unit
*unit
, Coord x
)
255 base
= unit
->family
== METRIC
258 return unit
->scale_factor
* base
;
261 /* \brief Convert a given unit to pcb coords
263 * \param [in] unit The unit to convert from
264 * \param [in] x The quantity to convert
266 * \return The converted measure
268 Coord
unit_to_coord (const Unit
*unit
, double x
)
273 base
= unit
->family
== METRIC
276 return DOUBLE_TO_COORD (base
/ unit
->scale_factor
);
279 static int min_sig_figs(double d
)
286 /* Normalize to x.xxxx... form */
288 while(d
>= 10) d
/= 10;
289 while(d
< 1) d
*= 10;
291 rv
= sprintf(buf
, "%g", d
);
295 /* \brief Internal coord-to-string converter for pcb-printf
296 * \par Function Description
297 * Converts a (group of) measurement(s) to a comma-deliminated
298 * string, with appropriate units. If more than one coord is
299 * given, the list is enclosed in parens to make the scope of
300 * the unit suffix clear.
302 * \param [in] coord Array of coords to convert
303 * \param [in] n_coords Number of coords in array
304 * \param [in] printf_spec printf sub-specifier to use with %f
305 * \param [in] e_allow Bitmap of units the function may use
306 * \param [in] suffix_type Whether to add a suffix
308 * \return A string containing the formatted coords. Must be freed with g_free.
310 static gchar
*CoordsToString(Coord coord
[], int n_coords
, const char *printf_spec
, enum e_allow allow
, enum e_suffix suffix_type
)
314 gchar filemode_buff
[G_ASCII_DTOSTR_BUF_SIZE
];
315 enum e_family family
;
320 value
= malloc (n_coords
* sizeof *value
);
321 buff
= g_string_new ("");
324 if (buff
== NULL
|| value
== NULL
)
328 if (printf_spec
== NULL
)
331 /* Check our freedom in choosing units */
332 if ((allow
& ALLOW_IMPERIAL
) == 0)
334 else if ((allow
& ALLOW_METRIC
) == 0)
341 for (i
= 0; i
< n_coords
; ++i
)
342 if(min_sig_figs(COORD_TO_MIL(coord
[i
])) < min_sig_figs(COORD_TO_MM(coord
[i
])))
347 if (imp_votes
> met_votes
)
354 for (i
= 0; i
< n_coords
; ++i
)
358 case METRIC
: value
[i
] = COORD_TO_MM (coord
[i
]); break;
359 case IMPERIAL
: value
[i
] = COORD_TO_MIL (coord
[i
]); break;
363 /* Determine scale factor -- find smallest unit that brings
364 * the whole group above unity */
365 for (n
= 0; n
< N_UNITS
; ++n
)
367 if ((Units
[n
].allow
& allow
) != 0 && (Units
[n
].family
== family
))
371 for (i
= 0; i
< n_coords
; ++i
)
372 if (fabs(value
[i
] * Units
[n
].scale_factor
) > 1)
374 if (n_above_one
== n_coords
)
378 /* If nothing worked, wind back to the smallest allowable unit */
383 } while ((Units
[n
].allow
& allow
) == 0 || Units
[n
].family
!= family
);
386 /* Apply scale factor */
387 suffix
= Units
[n
].suffix
;
388 for (i
= 0; i
< n_coords
; ++i
)
389 value
[i
] = value
[i
] * Units
[n
].scale_factor
;
391 /* Create sprintf specifier, using default_prec no precision is given */
393 while (printf_spec
[i
] == '%' || isdigit(printf_spec
[i
]) ||
394 printf_spec
[i
] == '-' || printf_spec
[i
] == '+' ||
395 printf_spec
[i
] == '#')
397 if (printf_spec
[i
] == '.')
398 printf_buff
= g_strdup_printf (", %sf", printf_spec
);
400 printf_buff
= g_strdup_printf (", %s.%df", printf_spec
, Units
[n
].default_prec
);
402 /* Actually sprintf the values in place
403 * (+ 2 skips the ", " for first value) */
405 g_string_append_c (buff
, '(');
406 if (suffix_type
== FILE_MODE
|| suffix_type
== FILE_MODE_NO_SUFFIX
)
408 g_ascii_formatd (filemode_buff
, sizeof filemode_buff
, printf_buff
+ 2, value
[0]);
409 g_string_append_printf (buff
, "%s", filemode_buff
);
412 g_string_append_printf (buff
, printf_buff
+ 2, value
[0]);
413 for (i
= 1; i
< n_coords
; ++i
)
415 if (suffix_type
== FILE_MODE
|| suffix_type
== FILE_MODE_NO_SUFFIX
)
417 g_ascii_formatd (filemode_buff
, sizeof filemode_buff
, printf_buff
, value
[i
]);
418 g_string_append_printf (buff
, "%s", filemode_buff
);
421 g_string_append_printf (buff
, printf_buff
, value
[i
]);
424 g_string_append_c (buff
, ')');
426 if (value
[0] != 0 || n_coords
> 1)
431 case FILE_MODE_NO_SUFFIX
:
434 g_string_append_printf (buff
, " %s", suffix
);
437 g_string_append_printf (buff
, "%s", suffix
);
442 g_free (printf_buff
);
444 /* Return just the gchar* part of our string */
445 return g_string_free (buff
, FALSE
);
448 /* \brief Main pcb-printf function
449 * \par Function Description
450 * This is a printf wrapper that accepts new format specifiers to
451 * output pcb coords as various units. See the comment at the top
452 * of pcb-printf.h for full details.
454 * \param [in] fmt Format specifier
455 * \param [in] args Arguments to specifier
457 * \return A formatted string. Must be freed with g_free.
459 gchar
*pcb_vprintf(const char *fmt
, va_list args
)
461 GString
*string
= g_string_new ("");
462 GString
*spec
= g_string_new ("");
464 enum e_allow mask
= ALLOW_ALL
;
466 if (string
== NULL
|| spec
== NULL
)
471 enum e_suffix suffix
= NO_SUFFIX
;
475 gchar
*unit_str
= NULL
;
476 const char *ext_unit
= "";
480 g_string_assign (spec
, "%");
483 while ( ! done
&& fmt
++ && *fmt
)
487 /* Our sub-specifiers */
489 mask
= ALLOW_CMIL
; /* This must be pcb's base unit */
492 suffix
= (suffix
== NO_SUFFIX
) ? SUFFIX
: FILE_MODE
;
495 suffix
= (suffix
== SUFFIX
) ? FILE_MODE
: FILE_MODE_NO_SUFFIX
;
497 /* Printf sub-specifiers */
499 g_string_append_printf (spec
, "%d", va_arg (args
, int));
503 //case '#': (duplicate)
519 g_string_append_c (spec
, *fmt
);
526 /* Tack full specifier onto specifier */
528 g_string_append_c (spec
, *fmt
);
532 case 'o': case 'i': case 'd':
533 case 'u': case 'x': case 'X':
534 if(strchr (spec
->str
, 'l'))
536 if(strchr (spec
->str
, 'l') != strrchr (spec
->str
, 'l'))
537 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, long long));
539 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, long));
543 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, int));
549 if(suffix
== FILE_MODE
|| suffix
== FILE_MODE_NO_SUFFIX
)
552 g_ascii_formatd (buffer
, 128, spec
->str
, va_arg(args
, double));
553 unit_str
= g_strdup_printf ("%s", buffer
);
556 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, double));
559 if(strchr (spec
->str
, 'l') && sizeof(int) <= sizeof(wchar_t))
560 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, wchar_t));
562 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, int));
565 if(strchr (spec
->str
, 'l'))
566 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, wchar_t *));
568 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, char *));
571 /* Depending on gcc settings, this will probably break with
572 * some silly "can't put %n in writeable data space" message */
573 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, int *));
576 unit_str
= g_strdup_printf (spec
->str
, va_arg(args
, void *));
579 g_string_append_c (string
, '%');
585 ext_unit
= va_arg(args
, const char *);
586 if (*fmt
!= '+' && *fmt
!= 'a')
587 value
[0] = va_arg(args
, Coord
);
591 case 's': unit_str
= CoordsToString(value
, 1, spec
->str
, ALLOW_MM
| ALLOW_MIL
, suffix
); break;
592 case 'S': unit_str
= CoordsToString(value
, 1, spec
->str
, mask
& ALLOW_ALL
, suffix
); break;
593 case 'M': unit_str
= CoordsToString(value
, 1, spec
->str
, mask
& ALLOW_METRIC
, suffix
); break;
594 case 'L': unit_str
= CoordsToString(value
, 1, spec
->str
, mask
& ALLOW_IMPERIAL
, suffix
); break;
595 case 'r': unit_str
= CoordsToString(value
, 1, spec
->str
, set_allow_readable(0), FILE_MODE
); break;
596 /* All these fallthroughs are deliberate */
597 case '9': value
[count
++] = va_arg(args
, Coord
);
598 case '8': value
[count
++] = va_arg(args
, Coord
);
599 case '7': value
[count
++] = va_arg(args
, Coord
);
600 case '6': value
[count
++] = va_arg(args
, Coord
);
601 case '5': value
[count
++] = va_arg(args
, Coord
);
602 case '4': value
[count
++] = va_arg(args
, Coord
);
603 case '3': value
[count
++] = va_arg(args
, Coord
);
606 value
[count
++] = va_arg(args
, Coord
);
607 unit_str
= CoordsToString(value
, count
, spec
->str
, mask
& ALLOW_ALL
, suffix
);
610 value
[1] = va_arg(args
, Coord
);
611 unit_str
= CoordsToString(value
, 2, spec
->str
, ALLOW_MM
| ALLOW_MIL
, suffix
);
614 for (i
= 0; i
< N_UNITS
; ++i
)
615 if (strcmp (ext_unit
, Units
[i
].suffix
) == 0)
616 unit_str
= CoordsToString(value
, 1, spec
->str
, Units
[i
].allow
, suffix
);
617 if (unit_str
== NULL
)
618 unit_str
= CoordsToString(value
, 1, spec
->str
, mask
& ALLOW_ALL
, suffix
);
621 g_string_append (spec
, ".0f");
622 if (suffix
== SUFFIX
)
623 g_string_append (spec
, " deg");
624 unit_str
= g_strdup_printf (spec
->str
, (double) va_arg(args
, Angle
));
627 mask
= va_arg(args
, enum e_allow
);
630 for (i
= 0; i
< N_UNITS
; ++i
)
631 if (*fmt
== Units
[i
].printf_code
)
632 unit_str
= CoordsToString(value
, 1, spec
->str
, Units
[i
].allow
, suffix
);
633 if (unit_str
== NULL
)
634 unit_str
= CoordsToString(value
, 1, spec
->str
, ALLOW_ALL
, suffix
);
639 if (unit_str
!= NULL
)
641 g_string_append (string
, unit_str
);
646 g_string_append_c (string
, *fmt
);
649 g_string_free (spec
, TRUE
);
650 /* Return just the gchar* part of our string */
651 return g_string_free (string
, FALSE
);
656 * \brief Wrapper for pcb_vprintf that outputs to a string.
658 * \param [in] string Pointer to string to output into.
660 * \param [in] size Maximum length of this string, including the
661 * terminating null byte.
663 * \param [in] fmt Format specifier.
665 * \return The length of the written string. In case the string was truncated
666 * due to the size limit, it's the length of the string which would
667 * have been written if enough space had been available.
669 * The returned string is guaranteed to be null terminated, even if truncated.
671 int pcb_snprintf(char *string
, size_t size
, const char *fmt
, ...)
679 tmp
= pcb_vprintf (fmt
, args
);
680 length
= strlen (tmp
);
681 strncpy (string
, tmp
, size
);
682 string
[size
- 1] = '\0';
690 /* \brief Wrapper for pcb_vprintf that outputs to a file
692 * \param [in] fh File to output to
693 * \param [in] fmt Format specifier
695 * \return The length of the written string
697 int pcb_fprintf(FILE *fh
, const char *fmt
, ...)
709 tmp
= pcb_vprintf (fmt
, args
);
710 rv
= fprintf (fh
, "%s", tmp
);
718 /* \brief Wrapper for pcb_vprintf that outputs to stdout
720 * \param [in] fmt Format specifier
722 * \return The length of the written string
724 int pcb_printf(const char *fmt
, ...)
732 tmp
= pcb_vprintf (fmt
, args
);
733 rv
= printf ("%s", tmp
);
740 /* \brief Wrapper for pcb_vprintf that outputs to a newly allocated string
742 * \param [in] fmt Format specifier
744 * \return The newly allocated string. Must be freed with g_free.
746 char *pcb_g_strdup_printf(const char *fmt
, ...)
752 tmp
= pcb_vprintf (fmt
, args
);
759 pcb_printf_register_tests ()
761 g_test_add_func ("/pcb-printf/test-unit", pcb_printf_test_unit
);
762 g_test_add_func ("/pcb-printf/test-printf", pcb_printf_test_printf
);
766 pcb_printf_test_unit ()
769 unit_to_coord (get_unit_struct ("m"), 1.0),
770 unit_to_coord (get_unit_struct ("mm"), 1.0),
771 unit_to_coord (get_unit_struct ("um"), 1.0),
772 unit_to_coord (get_unit_struct ("mil"), 1.0),
773 unit_to_coord (get_unit_struct ("mil"), 0.5),
774 unit_to_coord (get_unit_struct ("nm"), 67)
777 /* Loop unrolled for ease of pinpointing failure */
778 g_assert (get_unit_struct ("m") != NULL
);
780 g_assert_cmpuint (c
[0], ==, 1000000000);
781 g_assert_cmpuint (c
[1], ==, 1000000);
782 g_assert_cmpuint (c
[2], ==, 1000);
783 g_assert_cmpuint (c
[3], ==, 25400);
784 g_assert_cmpuint (c
[4], ==, 12700);
785 g_assert_cmpuint (c
[5], ==, 67);
789 pcb_printf_test_printf ()
791 Coord c
= unit_to_coord (get_unit_struct ("nm"), 314);
792 Coord d
= unit_to_coord (get_unit_struct ("nm"), 218);
793 Coord e
= unit_to_coord (get_unit_struct ("mm"), 101);
794 Coord f
= unit_to_coord (get_unit_struct ("mil"), 3);
796 g_assert_cmpstr (pcb_g_strdup_printf ("%mn", c
), ==, "314");
797 g_assert_cmpstr (pcb_g_strdup_printf ("%$mn", c
), ==, "314 nm");
798 g_assert_cmpstr (pcb_g_strdup_printf ("%mu", c
), ==, "0.31");
799 g_assert_cmpstr (pcb_g_strdup_printf ("%.3mu", c
), ==, "0.314");
800 g_assert_cmpstr (pcb_g_strdup_printf ("%$mu", c
), ==, "0.31 um");
801 g_assert_cmpstr (pcb_g_strdup_printf ("%$ml", c
), ==, "0.01 mil");
802 g_assert_cmpstr (pcb_g_strdup_printf ("%.5$ml", c
), ==, "0.01236 mil");
804 g_assert_cmpstr (pcb_g_strdup_printf ("%mn", e
), ==, "101000000");
805 g_assert_cmpstr (pcb_g_strdup_printf ("%$mn", e
), ==, "101000000 nm");
806 g_assert_cmpstr (pcb_g_strdup_printf ("%mu", e
), ==, "101000.00");
807 g_assert_cmpstr (pcb_g_strdup_printf ("%$mu", e
), ==, "101000.00 um");
808 g_assert_cmpstr (pcb_g_strdup_printf ("%ms", e
), ==, "101.0000");
809 g_assert_cmpstr (pcb_g_strdup_printf ("%$ms", e
), ==, "101.0000 mm");
810 g_assert_cmpstr (pcb_g_strdup_printf ("%mS", e
), ==, "10.10000");
811 g_assert_cmpstr (pcb_g_strdup_printf ("%$mS", e
), ==, "10.10000 cm");
812 g_assert_cmpstr (pcb_g_strdup_printf ("%$ml", e
), ==, "3976.38 mil");
813 g_assert_cmpstr (pcb_g_strdup_printf ("%.5$ml", e
), ==, "3976.37795 mil");
815 g_assert_cmpstr (pcb_g_strdup_printf ("%mn", f
), ==, "76200");
816 g_assert_cmpstr (pcb_g_strdup_printf ("%$mn", f
), ==, "76200 nm");
817 g_assert_cmpstr (pcb_g_strdup_printf ("%mm", f
), ==, "0.0762");
818 g_assert_cmpstr (pcb_g_strdup_printf ("%$mm", f
), ==, "0.0762 mm");
819 g_assert_cmpstr (pcb_g_strdup_printf ("%ms", f
), ==, "3.00");
820 g_assert_cmpstr (pcb_g_strdup_printf ("%$ms", f
), ==, "3.00 mil");
821 g_assert_cmpstr (pcb_g_strdup_printf ("%mS", f
), ==, "3.00");
822 g_assert_cmpstr (pcb_g_strdup_printf ("%$mS", f
), ==, "3.00 mil");
824 g_assert_cmpstr (pcb_g_strdup_printf ("%ms", c
), ==, "0.0003");
825 g_assert_cmpstr (pcb_g_strdup_printf ("%$ms", c
), ==, "0.0003 mm");
826 g_assert_cmpstr (pcb_g_strdup_printf ("%mS", c
), ==, "314");
827 g_assert_cmpstr (pcb_g_strdup_printf ("%$mS", c
), ==, "314 nm");
829 g_assert_cmpstr (pcb_g_strdup_printf ("%mD", c
, d
), ==, "(314, 218)");
830 g_assert_cmpstr (pcb_g_strdup_printf ("%$mD", c
, d
), ==, "(314, 218) nm");
832 g_assert_cmpstr (pcb_g_strdup_printf ("%`f", 7.2456), ==, "7.245600");
833 g_assert_cmpstr (pcb_g_strdup_printf ("%`.2f", 7.2456), ==, "7.25");
835 /* Some crashes noticed by Peter Clifton */
836 /* specifiers in "wrong" order (should work fine) */
837 g_assert_cmpstr (pcb_g_strdup_printf ("%$#mS", e
), ==, "397638 cmil");
838 /* invalid specifier (should passthrough to g_strdup_printf and output nothing) */
839 g_assert_cmpstr (pcb_g_strdup_printf ("%#S", e
), ==, "");