3 * Copyright (C) 2007-2009 Jim Evins <evins@snaught.com>.
5 * This file is part of libglabels.
7 * libglabels is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * libglabels 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 Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with libglabels. If not, see <http://www.gnu.org/licenses/>.
28 #define FRAC_EPSILON 0.00005
31 /*===========================================*/
33 /*===========================================*/
36 /*===========================================*/
38 /*===========================================*/
41 /*===========================================*/
42 /* Local function prototypes */
43 /*===========================================*/
45 static gchar *span_digits (gchar **p);
46 static gchar *span_non_digits (gchar **p);
48 /*===========================================*/
50 /*===========================================*/
53 * lgl_str_utf8_casecmp:
54 * @s1: string to compare with s2.
55 * @s2: string to compare with s1.
57 * Compare two UTF-8 strings, ignoring the case of characters.
59 * This function should be used only on strings that are known to be encoded
60 * in UTF-8 or a compatible UTF-8 subset.
62 * Returns: 0 if the strings match, a negative value if s1 < s2,
63 * or a positive value if s1 > s2.
67 lgl_str_utf8_casecmp (const gchar *s1,
74 folded_s1 = g_utf8_casefold (s1, -1);
75 folded_s2 = g_utf8_casefold (s2, -1);
77 result = g_utf8_collate (folded_s1, folded_s2);
87 * lgl_str_part_name_cmp:
88 * @s1: string to compare with s2.
89 * @s2: string to compare with s1.
91 * Compare two UTF-8 strings representing part names or numbers. This function
92 * uses a natural sort order:
96 * - Strings are divided into chunks (numeric and non-numeric)
98 * - Non-numeric chunks are compared character by character
100 * - Numerical chunks are compared numerically, so that "20" precedes "100".
102 * - Comparison of chunks is performed left to right until the first difference
103 * is encountered or all chunks evaluate as equal.
105 * This function should be used only on strings that are known to be encoded
106 * in UTF-8 or a compatible UTF-8 subset.
108 * Numeric chunks are converted to 64 bit unsigned integers for comparison,
109 * so the behaviour may be unpredictable for numeric chunks that exceed
110 * 18446744073709551615.
112 * Returns: 0 if the strings match, a negative value if s1 < s2,
113 * or a positive value if s1 > s2.
117 lgl_str_part_name_cmp (const gchar *s1,
120 gchar *folded_s1, *p1, *chunk1;
121 gchar *folded_s2, *p2, *chunk2;
122 gboolean isnum1, isnum2;
127 if ( s1 == s2 ) return 0;
128 if (s1 == NULL) return -1;
129 if (s2 == NULL) return 1;
131 folded_s1 = g_utf8_casefold (s1, -1);
132 folded_s2 = g_utf8_casefold (s2, -1);
138 while ( (result == 0) && !done )
141 if ( g_ascii_isdigit (*p1) )
143 chunk1 = span_digits (&p1);
148 chunk1 = span_non_digits (&p1);
152 if ( g_ascii_isdigit (*p2) )
154 chunk2 = span_digits (&p2);
159 chunk2 = span_non_digits (&p2);
163 if ( (strlen(chunk1) == 0) && (strlen(chunk2) == 0) )
165 /* Case 1: Both are empty. */
168 else if ( isnum1 && isnum2 )
170 /* Case 2: They both contain numbers */
171 n1 = g_ascii_strtoull (chunk1, NULL, 10);
172 n2 = g_ascii_strtoull (chunk2, NULL, 10);
174 if ( n1 < n2 ) result = -1;
175 if ( n1 > n2 ) result = 1;
179 /* Case 3: One or both do not contain numbers */
180 result = g_utf8_collate (chunk1, chunk2);
195 span_digits (gchar **p)
197 gchar *chunk = g_new0 (gchar, strlen (*p) + 1);
200 for ( i = 0; **p && g_ascii_isdigit (**p); i++, *p = g_utf8_next_char(*p) )
210 span_non_digits (gchar **p)
212 gchar *chunk = g_new0 (gchar, strlen (*p) + 1);
215 for ( i = 0; **p && !g_ascii_isdigit (**p); i++, *p = g_utf8_next_char(*p) )
224 /****************************************************************************/
225 /* Create fractional representation of number, if possible. */
226 /****************************************************************************/
228 lgl_str_format_fraction (gdouble x)
230 static gdouble denom[] = { 1., 2., 3., 4., 8., 16., 32., 0. };
231 static gchar *denom_string[] = { "1", "₂", "₃", "₄", "₈", "₁₆", "₃₂", NULL };
232 static gchar *num_string[] = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹",
233 "¹⁰", "¹¹", "¹²", "¹³", "¹⁴", "¹⁵", "¹⁶", "¹⁷", "¹⁸", "¹⁹",
234 "²⁰", "²¹", "²²", "²³", "²⁴", "²⁵", "²⁶", "²⁷", "²⁸", "²⁹",
237 gdouble product, remainder;
240 for ( i=0; denom[i] != 0.0; i++ ) {
241 product = x * denom[i];
242 remainder = fabs(product - ((gint)(product+0.5)));
243 if ( remainder < FRAC_EPSILON ) break;
246 if ( denom[i] == 0.0 ) {
247 /* None of our denominators work. */
248 return g_strdup_printf ("%.5g", x);
250 if ( denom[i] == 1.0 ) {
251 /* Simple integer. */
252 return g_strdup_printf ("%d", (gint)x);
254 n = (gint)( x * denom[i] + 0.5 );
257 return g_strdup_printf ("%d%s/%s", (n/d), num_string[n%d], denom_string[i]);
259 return g_strdup_printf ("%s/%s", num_string[n%d], denom_string[i]);
266 * Local Variables: -- emacs
268 * c-basic-offset: 8 -- emacs
269 * tab-width: 8 -- emacs
270 * indent-tabs-mode: nil -- emacs