3 * Copyright (C) 2007-2010 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 ( (*chunk1 == '\0') && (*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) )
225 * lgl_str_format_fraction:
226 * @x: Floating point number to convert to fractional notation
228 * Create fractional representation of number, if possible. Uses UTF-8 superscripts and
229 * subscripts for numerator and denominator values respecively.
231 * Returns: UTF-8 string containing fractional representation of x.
234 lgl_str_format_fraction (gdouble x)
236 static gdouble denom[] = { 1., 2., 3., 4., 8., 16., 32., 0. };
237 static gchar *denom_string[] = { "1", "₂", "₃", "₄", "₈", "₁₆", "₃₂", NULL };
238 static gchar *num_string[] = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹",
239 "¹⁰", "¹¹", "¹²", "¹³", "¹⁴", "¹⁵", "¹⁶", "¹⁷", "¹⁸", "¹⁹",
240 "²⁰", "²¹", "²²", "²³", "²⁴", "²⁵", "²⁶", "²⁷", "²⁸", "²⁹",
243 gdouble product, remainder;
246 for ( i=0; denom[i] != 0.0; i++ )
248 product = x * denom[i];
249 remainder = fabs(product - ((gint)(product+0.5)));
250 if ( remainder < FRAC_EPSILON ) break;
253 if ( denom[i] == 0.0 )
255 /* None of our denominators work. */
256 return g_strdup_printf ("%.5g", x);
258 if ( denom[i] == 1.0 )
260 /* Simple integer. */
261 return g_strdup_printf ("%.0f", x);
263 n = (gint)( x * denom[i] + 0.5 );
267 return g_strdup_printf ("%d%s/%s", (n/d), num_string[n%d], denom_string[i]);
271 return g_strdup_printf ("%s/%s", num_string[n%d], denom_string[i]);
278 * Local Variables: -- emacs
280 * c-basic-offset: 8 -- emacs
281 * tab-width: 8 -- emacs
282 * indent-tabs-mode: nil -- emacs