1 /*
2   filters and hooks that various languages can use
3 
4   Copyright (C) 2000-2003 David Necas (Yeti) <yeti@physics.muni.cz>
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of version 2 of the GNU General Public License as published
8   by the Free Software Foundation.
9 
10   This program is distributed in the hope that it will be useful, but WITHOUT
11   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13   more details.
14 
15   You should have received a copy of the GNU General Public License along
16   with this program; if not, write to the Free Software Foundation, Inc.,
17   59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
18 */
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #endif /* HAVE_CONFIG_H */
22 
23 #include <math.h>
24 
25 #include "enca.h"
26 #include "internal.h"
27 
28 /**
29  * EncaBoxDraw:
30  * @csname: Charset name.
31  * @isvbox: All other box drawing characters.
32  * @h1: Horizontal line character (light).
33  * @h2: Horizontal line character (heavy).
34  *
35  * Information about box-drawing characters for a charset.
36  **/
37 struct _EncaBoxDraw {
38   const char *csname;
39   const unsigned char *isvbox;
40   unsigned char h1;
41   unsigned char h2;
42 };
43 
44 typedef struct _EncaBoxDraw EncaBoxDraw;
45 
46 /* THIS IS A GENERATED TABLE, see tools/expand_table.pl */
47 static const unsigned char BOXVERT_IBM852[] = {
48   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
49   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
50   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
51   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
52   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
53   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
54   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
55   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
56   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
57   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
58   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
59   0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1,
60   1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0,
61   0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
62   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
63   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
64 };
65 
66 /* These are identical */
67 #define BOXVERT_IBM775 BOXVERT_IBM852
68 
69 /* THIS IS A GENERATED TABLE, see tools/expand_table.pl */
70 static const unsigned char BOXVERT_KEYBCS2[] = {
71   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
72   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
73   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
74   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
75   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
76   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
77   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
78   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
79   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
80   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
81   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
82   0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
83   1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
84   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
85   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
86   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
87 };
88 
89 /* These are identical */
90 #define BOXVERT_IBM866 BOXVERT_KEYBCS2
91 #define BOXVERT_CP1125 BOXVERT_KEYBCS2
92 
93 /* THIS IS A GENERATED TABLE, see tools/expand_table.pl */
94 static const unsigned char BOXVERT_KOI8R[] = {
95   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
96   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
97   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
98   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
99   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
100   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
101   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
102   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
103   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
104   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
105   0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
106   1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
107   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
108   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
109   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
110   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
111 };
112 
113 #if 0
114 /* UNUSED */
115 /* THIS IS A GENERATED TABLE, see tools/expand_table.pl */
116 static const unsigned char BOXVERT_KOI8RU[] = {
117   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
118   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
119   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
120   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
121   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
122   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
123   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
124   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
125   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
126   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
127   0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1,
128   1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
129   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
130   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
131   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
132   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
133 };
134 #endif
135 
136 /* THIS IS A GENERATED TABLE, see tools/expand_table.pl */
137 static const unsigned char BOXVERT_KOI8U[] = {
138   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
139   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
140   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
141   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
142   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
143   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
144   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
145   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
146   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
147   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
148   0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1,
149   1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0,
150   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
151   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
152   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
153   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
154 };
155 
156 /* THIS IS A GENERATED TABLE, see tools/expand_table.pl */
157 static const unsigned char BOXVERT_KOI8UNI[] = {
158   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
159   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
160   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
161   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
162   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
163   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
164   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
165   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
166   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
167   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
168   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
169   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
170   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
171   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
172   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
173   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
174 };
175 
176 static const EncaBoxDraw BOXDRAW[] = {
177   { "cp1125", BOXVERT_CP1125, 196, 205 },
178   { "ibm775", BOXVERT_IBM775, 196, 205 },
179   { "ibm852", BOXVERT_IBM852, 196, 205 },
180   { "ibm866", BOXVERT_IBM866, 196, 205 },
181   { "keybcs2", BOXVERT_KEYBCS2, 196, 205 },
182   { "koi8r", BOXVERT_KOI8R, 128, 160 },
183   { "koi8u", BOXVERT_KOI8U, 128, 160 },
184   { "koi8uni", BOXVERT_KOI8UNI, 128, 128 },  /* there's only one */
185 #if 0
186   { "koi8ru", BOXVERT_KOI8RU, 128, 160 },
187 #endif
188 };
189 
190 /* Local prototypes. */
191 static size_t filter_boxdraw_out(int charset,
192                                  unsigned char *buffer,
193                                  size_t size,
194                                  unsigned char fill_char);
195 
196 /**
197  * enca_filter_boxdraw:
198  * @analyser: Analyser whose charsets should be considered for filtration.
199  * @fill_char: Replacement character for filtered bytes.
200  *
201  * Runs boxdrawing characters filter on @buffer for each charset in @language.
202  *
203  * Returns: Number of characters filtered out.
204  **/
205 size_t
enca_filter_boxdraw(EncaAnalyserState * analyser,unsigned char fill_char)206 enca_filter_boxdraw(EncaAnalyserState *analyser,
207                     unsigned char fill_char)
208 {
209   size_t i;
210   size_t filtered = 0;
211 
212   for (i = 0; i < analyser->ncharsets; i++) {
213     filtered += filter_boxdraw_out(analyser->charsets[i],
214                                    analyser->buffer, analyser->size,
215                                    fill_char);
216   }
217 
218   return filtered;
219 }
220 
221 /**
222  * filter_boxdraw_out:
223  * @charset: Charset whose associated filter should be applied.
224  * @buffer: Buffer to be filtered.
225  * @size: Size of @buffer.
226  * @fill_char: Replacement character for filtered bytes.
227  *
228  * Replaces box-drawing characters in @buffer with @fill_char.
229  *
230  * Not all possibly box-drawing characters are replaced, only those meeting
231  * certain conditions to reduce false filtering.  It's assumed
232  * isspace(@fill_char) is true (it aborts when it isn't).
233  *
234  * It's OK to call with @charset which has no filter associated, it just
235  * returns zero then.
236  *
237  * Returns: The number of characters filtered.
238  **/
239 static size_t
filter_boxdraw_out(int charset,unsigned char * buffer,size_t size,unsigned char fill_char)240 filter_boxdraw_out(int charset,
241                    unsigned char *buffer,
242                    size_t size,
243                    unsigned char fill_char)
244 {
245   static int charset_id[ELEMENTS(BOXDRAW)];
246   static int charset_id_initialized = 0;
247   const EncaBoxDraw *bd;
248   size_t i, n, xout;
249 
250   assert(enca_isspace(fill_char));
251 
252   if (!charset_id_initialized) {
253     for (i = 0; i < ELEMENTS(BOXDRAW); i++) {
254       charset_id[i] = enca_name_to_charset(BOXDRAW[i].csname);
255       assert(charset_id[i] != ENCA_CS_UNKNOWN);
256     }
257     charset_id_initialized = 1;
258   }
259 
260   /* Find whether we have any filter associated with this charset. */
261   bd = NULL;
262   for (i = 0; i < ELEMENTS(BOXDRAW); i++) {
263     if (charset_id[i] == charset) {
264       bd = BOXDRAW + i;
265       break;
266     }
267   }
268   if (bd == NULL)
269     return 0;
270 
271   xout = 0;
272   /* First stage:
273    * Horizontal lines, they must occur at least two in a row. */
274   i = 0;
275   while (i < size-1) {
276     if (buffer[i] == bd->h1 || buffer[i] == bd->h2) {
277       for (n = i+1; buffer[n] == buffer[i] && n < size; n++)
278         ;
279 
280       if (n > i+1) {
281         memset(buffer + i, fill_char, n - i);
282         xout += n - i;
283       }
284       i = n;
285     }
286     else i++;
287   }
288 
289   /* Second stage:
290    * Vertical/mixed, they must occur separated by whitespace.
291    * We assume isspace(fill_char) is true. */
292   if (size > 1
293       && bd->isvbox[buffer[0]]
294       && enca_isspace(buffer[1])) {
295     buffer[0] = fill_char;
296     xout++;
297   }
298 
299   for (i = 1; i < size-1; i++) {
300     if (bd->isvbox[buffer[i]]
301         && enca_isspace(buffer[i-1])
302         && enca_isspace(buffer[i+1])) {
303       buffer[i] = fill_char;
304       xout++;
305     }
306   }
307 
308   if (size > 1
309       && bd->isvbox[buffer[size-1]]
310       && enca_isspace(buffer[size-2])) {
311     buffer[size-1] = fill_char;
312     xout++;
313   }
314 
315   return xout;
316 }
317 
318 /**
319  * enca_language_hook_ncs:
320  * @analyser: Analyser whose charset ratings are to be modified.
321  * @ncs: The number of charsets.
322  * @hookdata: What characters of which charsets should be given the extra
323  *            weight.
324  *
325  * Decide between two charsets differing only in a few characters.
326  *
327  * If the two most probable charsets correspond to @hookdata charsets,
328  * give the characters they differ half the weight of all other characters
329  * together, thus allowing to decide between the two very similar charsets.
330  *
331  * It also recomputes @order when something changes.
332  *
333  * Returns: Nonzero when @ratings were actually modified, nonzero otherwise.
334  **/
335 int
enca_language_hook_ncs(EncaAnalyserState * analyser,size_t ncs,EncaLanguageHookData1CS * hookdata)336 enca_language_hook_ncs(EncaAnalyserState *analyser,
337                        size_t ncs,
338                        EncaLanguageHookData1CS *hookdata)
339 {
340   const int *const ids = analyser->charsets;
341   const size_t ncharsets = analyser->ncharsets;
342   const size_t *counts = analyser->counts;
343   const size_t *const order = analyser->order;
344   double *const ratings = analyser->ratings;
345   size_t maxcnt, j, k, m;
346   double q;
347 
348   assert(ncharsets > 0);
349   assert(ncs <= ncharsets);
350   if (ncs < 2)
351     return 0;
352 
353   /*
354   for (j = 0; j < ncharsets; j++) {
355     fprintf(stderr, "%s:\t%g\n", enca_csname(ids[order[j]]), ratings[order[j]]);
356   }
357   */
358 
359   /* Find id's and check whether they are the first */
360   for (j = 0; j < ncs; j++) {
361     EncaLanguageHookData1CS *h = hookdata + j;
362 
363     /* Find charset if unknown */
364     if (h->cs == (size_t)-1) {
365       int id;
366 
367       id = enca_name_to_charset(h->name);
368       assert(id != ENCA_CS_UNKNOWN);
369       k = 0;
370       while (k < ncharsets && id != ids[k])
371         k++;
372       assert(k < ncharsets);
373       h->cs = k;
374     }
375 
376     /* If any charset is not between the first ncs ones, do nothing. */
377     k = 0;
378     while (k < ncs && order[k] != h->cs)
379       k++;
380     if (k == ncs)
381       return 0;
382   }
383 
384   /* Sum the extra-important characters and find maximum. */
385   maxcnt = 0;
386   for (j = 0; j < ncs; j++) {
387     EncaLanguageHookData1CS const *h = hookdata + j;
388 
389     for (m = k = 0; k < h->size; k++)
390       m += counts[h->list[k]];
391     if (m > maxcnt)
392       maxcnt = m;
393   }
394   if (maxcnt == 0)
395     return 0;
396 
397   /* Substract something from charsets that have less than maximum. */
398   q = 0.5 * ratings[order[0]]/(maxcnt + EPSILON);
399   for (j = 0; j < ncs; j++) {
400     EncaLanguageHookData1CS const *h = hookdata + j;
401 
402     m = maxcnt;
403     for (k = 0; k < h->size; k++)
404       m -= counts[h->list[k]];
405     ratings[h->cs] -= q*m;
406   }
407 
408   enca_find_max_sec(analyser);
409 
410   return 1;
411 }
412 
413 /**
414  * enca_language_hook_eol:
415  * @analyser: Analyser whose charset ratings are to be modified.
416  * @ncs: The number of charsets.
417  * @hookdata: What characters of which charsets should be decided with based
418  *            on the EOL type.
419  *
420  * Decide between two charsets differing only in EOL type or other surface.
421  *
422  * The (surface mask, charset) pairs are scanned in order. If a matching
423  * surface is found, ratings of all other charsets in the list are zeroed.
424  * So you can place a surface mask of all 1s at the end to match when nothing
425  * else matches.
426  *
427  * All the charsets have to have the same rating, or nothing happens.
428  *
429  * It also recomputes @order when something changes.
430  *
431  * Returns: Nonzero when @ratings were actually modified, nonzero otherwise.
432  **/
433 int
enca_language_hook_eol(EncaAnalyserState * analyser,size_t ncs,EncaLanguageHookDataEOL * hookdata)434 enca_language_hook_eol(EncaAnalyserState *analyser,
435                        size_t ncs,
436                        EncaLanguageHookDataEOL *hookdata)
437 {
438   const int *const ids = analyser->charsets;
439   const size_t ncharsets = analyser->ncharsets;
440   const size_t *const order = analyser->order;
441   double *const ratings = analyser->ratings;
442   size_t j, k;
443 
444   assert(ncharsets > 0);
445   assert(ncs <= ncharsets);
446   if (ncs < 2)
447     return 0;
448 
449   /* Rating equality check. */
450   for (j = 1; j < ncs; j++) {
451     if (fabs(ratings[order[j-1]] - ratings[order[j]]) > EPSILON)
452       return 0;
453   }
454 
455   /* Find id's and check whether they are the first */
456   for (j = 0; j < ncs; j++) {
457     EncaLanguageHookDataEOL *h = hookdata + j;
458 
459     /* Find charset if unknown */
460     if (h->cs == (size_t)-1) {
461       int id;
462 
463       id = enca_name_to_charset(h->name);
464       assert(id != ENCA_CS_UNKNOWN);
465       k = 0;
466       while (k < ncharsets && id != ids[k])
467         k++;
468       assert(k < ncharsets);
469       h->cs = k;
470     }
471 
472     /* If any charset is not between the first ncs ones, do nothing. */
473     k = 0;
474     while (k < ncs && order[k] != h->cs)
475       k++;
476     if (k == ncs)
477       return 0;
478   }
479 
480   /* Find first matching EOL type. */
481   for (j = 0; j < ncs; j++) {
482     EncaLanguageHookDataEOL const *h = hookdata + j;
483 
484     if (h->eol & analyser->result.surface) {
485       int chg = 0;
486 
487       for (k = 0; k < ncs; k++) {
488         h = hookdata + k;
489 
490         if (k != j && ratings[h->cs] > 0.0) {
491           ratings[h->cs] = 0.0;
492           chg = 1;
493         }
494       }
495       if (chg)
496         enca_find_max_sec(analyser);
497 
498       return chg;
499     }
500   }
501 
502   return 0;
503 }
504 
505 /* vim: ts=2
506  */
507 
508