]> git.draconx.ca Git - dxcommon.git/blob - src/copysym.c
ca23c44cf1734b8dbd12b409f82343ccd668810d
[dxcommon.git] / src / copysym.c
1 /*
2  * Copyright © 2023-2024 Nick Bowler
3  *
4  * Helper function to output the copyright symbol in a specified encoding.
5  *
6  * License WTFPL2: Do What The Fuck You Want To Public License, version 2.
7  * This is free software: you are free to do what the fuck you want to.
8  * There is NO WARRANTY, to the extent permitted by law.
9  */
10
11 #if HAVE_CONFIG_H
12 #       include <config.h>
13 #endif
14
15 #if ENABLE_NLS
16
17 #include <stdlib.h>
18 #include <string.h>
19 #include <stddef.h>
20
21 #if HAVE_INTTYPES_H
22 #include <inttypes.h>
23 typedef uint_least32_t dx_u32;
24 #else
25 #include <limits.h>
26 #if UINT_MAX >= 0xffffffff
27 typedef unsigned dx_u32;
28 #else
29 typedef unsigned long dx_u32;
30 #endif
31 #endif
32
33 #define BSEARCH_ARRAY(key, arr, cmp) \
34         bsearch(key, arr, sizeof (arr) / sizeof *(arr), sizeof *(arr), cmp)
35
36 enum { PREFIXLEN = 5 };
37
38 static int compar_prefix(const void *key, const void *elem_)
39 {
40         const char (*elem)[PREFIXLEN+1] = (void *)elem_;
41
42         return strncmp(key, *elem, PREFIXLEN);
43 }
44
45 /*
46  * Return, as a multibyte string, the copyright symbol for the
47  * given character encoding, which is one of the strings returned
48  * by Gnulib's locale_charset function.  In particular, we are
49  * looking for one of the strings:
50  *
51  *     CP1129
52  *     CP1250
53  *     CP1251
54  *     CP1252
55  *     CP1253
56  *     CP1254
57  *     CP1256
58  *     CP1257
59  *     CP1258
60  *     CP775
61  *     CP850
62  *     CP856
63  *     CP857
64  *     CP869
65  *     CP922
66  *     GEORGIAN-PS
67  *     ISO-8859-1
68  *     ISO-8859-13
69  *     ISO-8859-14
70  *     ISO-8859-15
71  *     ISO-8859-7
72  *     ISO-8859-8
73  *     ISO-8859-9
74  *     PT154
75  *     EUC-JP
76  *     GB18030
77  *     KOI8-R
78  *     KOI8-T
79  *     KOI8-U
80  *     UTF-8
81  *
82  * All of these are ASCII supersets.  EBCDIC code pages like CP1122 are
83  * presently handled by returning (C), even if the character set does
84  * include the copyright symbol.
85  *
86  * To simplify the implementation, we allow some slop in the matching,
87  * as long as the result is valid for any actual encoding names.
88  *
89  * If NLS support is disabled, or if the character set does not
90  * include the copyright symbol, then the string (C) is returned
91  * in the C execution character set.
92  */
93 const char *copyright_symbol(const char *charset)
94 {
95         /* All known encodings of the copyright symbol */
96         static const char codes[] =
97                 "(C)"          "\0"
98                 "\xc2\xa9"     "\0"
99                 "\x97"         "\0"
100                 "\xa8"         "\0"
101                 "\xb8"         "\0"
102                 "\xbf"         "\0"
103                 "\x8f\xa2\xed" "\0"
104                 "\x81\x30\x84\x38";
105
106         /*
107          * We need the list below to be in lexicographic order in
108          * the C execution character encoding.
109          */
110 #if 'B'>'E' || 'C'>'E' || 'E'>'G' || 'G'>'I' || 'K'>'P' || 'P'>'U'
111 #  error this character encoding is unsupported, please report a bug.
112 #endif
113
114         /*
115          * For character sets that include the copyright symbol,
116          * the first 5 characters suffices to distinguish amongst
117          * all the different possible encodings.
118          *
119          * The final byte of each entry indicates the corresponding
120          * offset into the codes array, except for CP112x and ISO-8859-x
121          * which use the special values 0 and 1, respectively (handled
122          * below).
123          */
124         static const char t1[][PREFIXLEN+1] = {
125                 "CP112\x00",
126                 "CP125\x05",
127                 "CP775\x09",
128                 "CP850\x0b",
129                 "CP856\x0b",
130                 "CP857\x0b",
131                 "CP869\x07",
132                 "CP922\x05",
133                 "EUC-J\x0f",
134                 "GB180\x13",
135                 "GEORG\x05",
136                 "ISO-8\x01",
137                 "KOI8-\x0d",
138                 "PT154\x05",
139                 "UTF-8\x04"
140         };
141
142         unsigned cindex = 0;
143         const char *m;
144
145         if (!charset || !(m = BSEARCH_ARRAY(charset, t1, compar_prefix)))
146                 goto no_conv;
147
148         cindex = m[PREFIXLEN];
149         charset += PREFIXLEN;
150
151         /*
152          * We now need to identify encodings that match one of the 5-character
153          * prefixes above but don't actually have the copyright symbol in their
154          * character set.  Specifically, these are:
155          *
156          *   CP1122 (does have it, but EBCDIC)
157          *   CP1124
158          *   CP1125
159          *   ISO-8859-10
160          *   ISO-8859-11
161          *   ISO-8859-2
162          *   ISO-8859-3
163          *   ISO-8859-4
164          *   ISO-8859-5
165          *   ISO-8859-6
166          */
167         if (cindex == 0) {
168                 /* CP112x, only CP1129 has copyright symbol. */
169                 cindex = 5 * (*charset == '9');
170         } else if (cindex == 1) {
171                 /*
172                  * ISO-8859 special case.  Simply find and look at the final
173                  * two digits.  The set bits in the 'accept' value indicate
174                  * which encodings have the copyright symbol.
175                  */
176                 dx_u32 accept  = 0x00380383;
177                 dx_u32 collect = 0;
178                 char c;
179
180                 while ((c = *charset++)) {
181                         collect <<= 4;
182
183                         if (c != '-')
184                                 collect |= c - '0';
185                 }
186
187                 cindex = 5 * ((accept >> (collect & 0x1f)) & 1);
188         }
189 no_conv:
190         return codes+cindex;
191 }
192
193 #endif