Fixed fatal linker error (global symbol conflict of "writephonebook")
[gnokii.git] / common / gsm-filetypes.c
1 /*
2
3   $Id$
4
5   G N O K I I
6
7   A Linux/Unix toolset and driver for Nokia mobile phones.
8
9   Copyright (C) 1999, 2000 Hugh Blemings & Pavel Janík ml. 
10
11   Released under the terms of the GNU GPL, see file COPYING for more details.
12         
13   Functions to read and write common file types.
14
15   $Log$
16   Revision 1.1.1.1  2001/11/25 21:59:00  short
17   :pserver:cvs@pserver.samba.org:/cvsroot - gnokii - Sun Nov 25 22:56 CET 2001
18
19   Revision 1.18  2001/09/20 21:46:20  pkot
20   Locale cleanups (Pawel Kot)
21  
22
23 */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <sys/stat.h>
30
31 #include "gsm-common.h"
32 #include "gsm-filetypes.h"
33 #include "gsm-bitmaps.h"
34 #include "gsm-ringtones.h"
35
36 #ifdef XPM
37 #include <X11/xpm.h>
38 #endif
39
40 #include "misc.h"
41
42 /* Ringtone File Functions */
43 /* ####################### */
44
45
46 /* Function to convert scale field in to correct number. */
47
48 int GetDuration (char *num)
49 {
50         int duration = 0;
51
52         switch (atoi(num)) {
53         case 1:
54                 duration = 128;
55                 break;
56         case 2:
57                 duration = 64;
58                 break;
59         case 4:
60                 duration = 32;
61                 break;
62         case 8:
63                 duration = 16;
64                 break;
65         case 16:
66                 duration = 8;
67                 break;
68         case 32:
69                 duration = 4;
70                 break;
71         }
72         return (duration);
73 }
74
75
76 int GetScale (char *num)
77 {
78         /* This may well need improving. */
79         int scale=0;
80
81         if ((atoi(num)) < 4) scale = (atoi(num));
82         if ((atoi(num)) > 4) scale = (atoi(num)) - 4;
83
84         return (scale);
85 }
86
87
88 /* Currently only reads rttl and ott files - can be later extended to midi etc. */
89
90 GSM_Error GSM_ReadRingtoneFile(char *FileName, GSM_Ringtone *ringtone)
91 {
92         FILE *file;
93         GSM_Error error;
94         GSM_Filetypes filetype;
95   
96         file = fopen(FileName, "rb");
97
98         if (!file)
99                 return(GE_CANTOPENFILE);
100
101         /* FIXME: for now identify the filetype based on the extension */
102         /* I don't like this but I haven't got any .ott files to work out a better way */
103
104         filetype = RTTL;
105         if (strstr(FileName, ".ott")) filetype = OTT; /* OTT files saved by NCDS3 */
106   
107         error=GE_NONE;
108
109         rewind(file);  /* Not necessary for now but safer */
110
111         switch (filetype) {
112         case RTTL:
113                 error = loadrttl(file, ringtone);
114                 fclose(file);
115                 break;
116         case OTT:
117                 error = loadott(file, ringtone);
118                 fclose(file);
119                 break;
120         default:
121                 error = GE_INVALIDFILEFORMAT;
122                 break;
123         }
124
125         return(error);
126 }
127
128
129 GSM_Error loadott(FILE *file, GSM_Ringtone *ringtone)
130 {
131         char Buffer[2000];
132         int i;
133   
134         i = fread(Buffer, 1, 2000, file);
135         if (!feof(file)) return GE_FILETOOLONG;
136         return GSM_UnPackRingtone(ringtone, Buffer, i);
137 }
138
139
140 GSM_Error loadrttl(FILE *file, GSM_Ringtone *ringtone)
141 {
142         int NrNote = 0;
143
144         int DefNoteScale = 2;
145         int DefNoteDuration = 4;
146         unsigned char buffer[2000];
147         unsigned char *def, *notes, *ptr;
148
149         fread(buffer, 2000, 1, file);
150
151         /* This is for buggy RTTTL ringtones without name. */
152         if (buffer[0] != RTTTL_SEP[0]) {
153                 strtok(buffer, RTTTL_SEP);
154                 sprintf(ringtone->name, "%s", buffer);
155                 def = strtok(NULL, RTTTL_SEP);
156                 notes = strtok(NULL, RTTTL_SEP);
157         } else {
158                 sprintf(ringtone->name, "GNOKII");
159                 def = strtok(buffer, RTTTL_SEP);
160                 notes = strtok(NULL, RTTTL_SEP);
161         }
162
163         ptr = strtok(def, ", ");
164         /* Parsing the <defaults> section. */
165         ringtone->tempo=63;
166
167         while (ptr) {
168
169                 switch(*ptr) {
170                 case 'd':
171                 case 'D':
172                         DefNoteDuration=GetDuration(ptr+2);
173                         break;
174                 case 'o':
175                 case 'O':
176                         DefNoteScale=GetScale(ptr+2);
177                         break;
178                 case 'b':
179                 case 'B':
180                         ringtone->tempo=atoi(ptr+2);
181                         break;
182                 }
183
184                 ptr=strtok(NULL,", ");
185         }
186
187         dprintf("DefNoteDuration = %d\n", DefNoteDuration);
188         dprintf("DefNoteScale = %d\n", DefNoteScale);
189
190         /* Parsing the <note-command>+ section. */
191         ptr = strtok(notes, ", ");
192         while (ptr && (NrNote < MAX_RINGTONE_NOTES)) {
193
194                 /* [<duration>] */
195                 ringtone->notes[NrNote].duration = GetDuration(ptr);
196                 if (ringtone->notes[NrNote].duration == 0)
197                         ringtone->notes[NrNote].duration = DefNoteDuration;
198
199                 /* Skip all numbers in duration specification. */
200                 while (isdigit(*ptr))
201                         ptr++;
202
203                 /* <note> */
204
205                 if ((*ptr >= 'a') && (*ptr <= 'g')) ringtone->notes[NrNote].note = ((*ptr - 'a') * 2) + 10;
206                 else if ((*ptr >= 'A') && (*ptr <= 'G')) ringtone->notes[NrNote].note = ((*ptr - 'A') * 2) + 10;
207                 else if ((*ptr == 'H') || (*ptr == 'h')) ringtone->notes[NrNote].note = 12;
208                 else ringtone->notes[NrNote].note = 255;
209
210                 if ((ringtone->notes[NrNote].note > 13) && (ringtone->notes[NrNote].note != 255))
211                         ringtone->notes[NrNote].note -= 14;
212
213                 ptr++;
214     
215                 if (*ptr == '#') {
216                         ringtone->notes[NrNote].note++;
217                         if ((ringtone->notes[NrNote].note == 5) || (ringtone->notes[NrNote].note == 13))
218                                 ringtone->notes[NrNote].note++;
219                         ptr++;
220                 }
221
222                 /* Check for dodgy rttl */
223                 /* [<special-duration>] */
224                 if (*ptr == '.') {
225                         ringtone->notes[NrNote].duration *= 1.5;
226                         ptr++;
227                 }
228
229                 /* [<scale>] */
230                 if (ringtone->notes[NrNote].note != 255) {
231                         if (isdigit(*ptr)) {
232                                 ringtone->notes[NrNote].note += GetScale(ptr) * 14;
233                                 ptr++;
234                         } else {
235                                 ringtone->notes[NrNote].note += DefNoteScale * 14;
236                         }
237                 }
238
239                 /* [<special-duration>] */
240                 if (*ptr == '.') {
241                         ringtone->notes[NrNote].duration *= 1.5;
242                         ptr++;
243                 }
244
245                 NrNote++;
246                 ptr = strtok(NULL, ", ");
247         }
248
249         ringtone->NrNotes = NrNote;
250
251         return GE_NONE;
252 }
253
254
255 /* Save the ringtone file - this will overwrite the file */
256 /* Confirming must be done before this is called */
257 GSM_Error GSM_SaveRingtoneFile(char *FileName, GSM_Ringtone *ringtone)
258 {
259         FILE *file; 
260         GSM_Error error;
261
262         file = fopen(FileName, "wb");
263   
264         if (!file)
265                 return(GE_CANTOPENFILE);
266   
267         /* FIXME... */ 
268         /* We need a way of passing these functions a filetype rather than rely on the extension */
269         if (strstr(FileName, ".ott")) {
270                 error = saveott(file, ringtone);
271         } else {
272                 error = saverttl(file, ringtone);
273         }
274         fclose(file);
275         return error;
276 }
277
278
279 GSM_Error saveott(FILE *file, GSM_Ringtone *ringtone)
280 {
281         char Buffer[2000];
282         int i=2000;
283   
284         /* PackRingtone writes up to i chars and returns in i the number written */
285         GSM_PackRingtone(ringtone, Buffer, &i);
286
287         if (i < 2000) {
288                 fwrite(Buffer, 1, i, file);
289                 return GE_NONE;
290         } else {
291                 return GE_FILETOOLONG;
292         }
293 }
294
295 GSM_Error saverttl(FILE *file, GSM_Ringtone *ringtone)
296 {
297         int DefDuration, DefScale = 2, CurrentNote;
298         int buffer[6];
299         int i, j, k = 0;
300   
301         /* Saves ringtone name */
302         fprintf(file, "%s:", ringtone->name);
303
304         /* Find the most frequently used duration and use this for the default */
305         for (i = 0; i < 6; i++) buffer[i] = 0;
306         for (i = 0; i < ringtone->NrNotes; i++) {
307                 switch (ringtone->notes[i].duration) {
308                 case 192:
309                         buffer[0]++; break;
310                 case 128: 
311                         buffer[0]++; break;
312                 case 96:        
313                         buffer[1]++; break;
314                 case 64: 
315                         buffer[1]++; break;
316                 case 48:
317                         buffer[2]++; break;
318                 case 32: 
319                         buffer[2]++; break;
320                 case 24:
321                         buffer[3]++; break;
322                 case 16: 
323                         buffer[3]++; break;
324                 case 12:
325                         buffer[4]++; break;
326                 case 8: 
327                         buffer[4]++; break;
328                 case 6: 
329                         buffer[5]++; break;
330                 case 4:
331                         buffer[5]++; break;
332                 }
333         }
334
335         /* Now find the most frequently used */
336         j = 0;
337         for (i = 0; i < 6; i++) {
338                 if (buffer[i] > j) {
339                         k = i; 
340                         j = buffer[i];
341                 }
342         }
343
344         /* Finally convert and save the default duration */
345         switch (k) {
346         case 0:
347                 DefDuration = 128;
348                 fprintf(file, "d=1,");
349                 break;  
350         case 1:
351                 DefDuration = 64;
352                 fprintf(file, "d=2,");
353                 break;  
354         case 2:
355                 DefDuration = 32;
356                 fprintf(file, "d=4,");
357                 break;  
358         case 3:
359                 DefDuration = 16;
360                 fprintf(file, "d=8,");
361                 break;  
362         case 4:
363                 DefDuration = 8;
364                 fprintf(file, "d=16,");
365                 break;  
366         case 5:
367                 DefDuration = 4;
368                 fprintf(file, "d=32,");
369                 break;  
370         default:
371                 DefDuration = 16;
372                 fprintf(file, "d=8,");
373                 break;  
374         }  
375
376         /* Find the most frequently used scale and use this for the default */
377         for (i = 0; i < 6; i++) buffer[i] = 0;
378         for (i = 0; i < ringtone->NrNotes; i++) {
379                 if (ringtone->notes[i].note != 255) {
380                         buffer[ringtone->notes[i].note/14]++;
381                 }
382         }
383         j = 0;
384         for (i = 0; i < 6; i++) {
385                 if (buffer[i] > j) {
386                         DefScale = i;
387                         j = buffer[i];
388                 }
389         }
390
391         /* Save the default scale and tempo */
392         fprintf(file, "o=%i,", DefScale+4);
393         fprintf(file, "b=%i:", ringtone->tempo);
394
395         dprintf("DefNoteDuration=%d\n", DefDuration);
396         dprintf("DefNoteScale=%d\n", DefScale);
397         dprintf("Number of notes=%d\n",ringtone->NrNotes);
398
399         /* Now loop round for each note */
400         for (i = 0; i < ringtone->NrNotes; i++) {
401                 CurrentNote = ringtone->notes[i].note;
402
403                 /* This note has a duration different than the default. We must save it */
404                 if (ringtone->notes[i].duration != DefDuration) {
405                         switch (ringtone->notes[i].duration) {
406                         case 192:                      //192=128*1.5
407                                 fprintf(file, "1"); break;
408                         case 128:
409                                 fprintf(file, "1"); break;
410                         case 96:                       //96=64*1.5
411                                 fprintf(file, "2"); break;
412                         case 64:
413                                 fprintf(file, "2"); break;
414                         case 48:                       //48=32*1.5
415                                 fprintf(file, "4"); break;
416                         case 32:
417                                 fprintf(file, "4"); break;
418                         case 24:                       //24=16*1.5;
419                                 fprintf(file, "8"); break;
420                         case 16:
421                                 fprintf(file, "8"); break;
422                         case 12:                       //12=8*1.5
423                                 fprintf(file, "16"); break;
424                         case 8:
425                                 fprintf(file, "16"); break;
426                         case 6:                        //6=4*1.5
427                                 fprintf(file, "32"); break;
428                         case 4:
429                                 fprintf(file, "32"); break;
430                         default: 
431                                 break;
432                         }
433                 }
434     
435                 /* Now save the actual note */
436                 switch (GSM_GetNote(CurrentNote)) {
437                 case Note_C  :fprintf(file, "c"); break;
438                 case Note_Cis:fprintf(file, "c#"); break;
439                 case Note_D  :fprintf(file, "d"); break;
440                 case Note_Dis:fprintf(file, "d#"); break;
441                 case Note_E  :fprintf(file, "e"); break;
442                 case Note_F  :fprintf(file, "f"); break;
443                 case Note_Fis:fprintf(file, "f#"); break;
444                 case Note_G  :fprintf(file, "g"); break;
445                 case Note_Gis:fprintf(file, "g#"); break;
446                 case Note_A  :fprintf(file, "a"); break;
447                 case Note_Ais:fprintf(file, "a#"); break;
448                 case Note_H  :fprintf(file, "h"); break;
449                 default      :fprintf(file, "p"); break; //Pause ?
450                 }
451
452                 /* Saving info about special duration */
453                 if (ringtone->notes[i].duration == 128 * 1.5 ||
454                     ringtone->notes[i].duration == 64 * 1.5 ||
455                     ringtone->notes[i].duration == 32 * 1.5 ||
456                     ringtone->notes[i].duration == 16 * 1.5 ||
457                     ringtone->notes[i].duration == 8 * 1.5 ||
458                     ringtone->notes[i].duration == 4 * 1.5)
459                         fprintf(file, ".");
460     
461                 /* This note has a scale different than the default, so save it */
462                 if ( (CurrentNote != 255) && (CurrentNote/14 != DefScale))
463                         fprintf(file, "%i",(CurrentNote/14) + 4);
464     
465                 /* And a separator before next note */
466                 if (i!=ringtone->NrNotes - 1)
467                         fprintf(file, ",");
468
469         }
470
471         return GE_NONE;
472 }
473
474 /* Bitmap file functions */
475 /* ##################### */
476
477 GSM_Error GSM_ReadBitmapFile(char *FileName, GSM_Bitmap *bitmap)
478 {
479
480         FILE *file;
481         unsigned char buffer[300];
482         int error;
483         GSM_Filetypes filetype = None;
484
485         file = fopen(FileName, "rb");
486
487         if (!file)
488                 return(GE_CANTOPENFILE);
489
490         fread(buffer, 1, 9, file); /* Read the header of the file. */
491
492         /* Attempt to identify filetype */
493
494         if (memcmp(buffer, "NOL", 3) == 0) {  /* NOL files have 'NOL' at the start */
495                 filetype = NOL;
496         } else if (memcmp(buffer, "NGG", 3) == 0) {  /* NGG files have 'NGG' at the start */
497                 filetype = NGG;
498         } else if (memcmp(buffer, "FORM", 4) == 0) {  /* NSL files have 'FORM' at the start */
499                 filetype = NSL;
500         } else if (memcmp(buffer, "NLM", 3) == 0) {  /* NLM files have 'NLM' at the start */
501                 filetype = NLM;
502         } else if (memcmp(buffer, "BM", 2) == 0) {  /* BMP, I61 and GGP files have 'BM' at the start */
503                 filetype = BMP;
504         } else if (memcmp(buffer, "/* XPM */", 9) == 0) {  /* XPM files have 'XPM' at the start */  
505                 filetype = XPMF;
506         } else filetype = None;
507
508         if (strstr(FileName, ".otb")) filetype = OTA; /* OTA files saved by NCDS3 */
509   
510         rewind(file);
511
512         switch (filetype) {
513
514         case NOL:
515                 error = loadnol(file, bitmap);
516                 fclose(file);
517                 break;
518         case NGG:
519                 error = loadngg(file, bitmap);
520                 fclose(file);
521                 break;
522         case NSL:
523                 error = loadnsl(file, bitmap);
524                 fclose(file);
525                 break;
526         case NLM:
527                 error = loadnlm(file, bitmap);
528                 fclose(file);
529                 break;
530         case OTA:
531                 error = loadota(file, bitmap);
532                 fclose(file);
533                 break;
534         case BMP:
535                 error = loadbmp(file, bitmap);
536                 fclose(file);
537                 break;
538 #ifdef XPM
539         case XPMF:
540                 fclose(file);
541                 error = loadxpm(FileName, bitmap);
542                 break;
543 #endif
544         default:
545                 error = GE_INVALIDFILEFORMAT;
546                 break;
547         }
548
549         return(error);
550 }
551
552
553 #ifdef XPM
554
555 GSM_Error loadxpm(char *filename, GSM_Bitmap *bitmap)
556 {
557         int error, x, y;
558         XpmImage image;
559         XpmInfo info;
560
561         error = XpmReadFileToXpmImage(filename, &image, &info);
562
563         switch (error) {
564         case XpmColorError:  return GE_WRONGCOLORS; break;
565         case XpmColorFailed: return GE_WRONGCOLORS; break;
566         case XpmOpenFailed:  return GE_CANTOPENFILE; break;
567         case XpmFileInvalid: return GE_INVALIDFILEFORMAT; break;
568         case XpmSuccess: break;
569         default: break;
570         }
571
572         if (image.ncolors != 2) return GE_WRONGNUMBEROFCOLORS; 
573
574         /* All xpms are loaded as startup logos - but can be resized later */
575
576         bitmap->type = GSM_StartupLogo;
577
578         bitmap->height = image.height;
579         bitmap->width = image.width;
580         bitmap->size = ((bitmap->height / 8) + (bitmap->height % 8 > 0)) * bitmap->width;
581
582         if (bitmap->size > GSM_MAX_BITMAP_SIZE) {
583                 fprintf(stdout, "Bitmap too large\n");
584                 return GE_INVALIDIMAGESIZE;
585         }
586
587         GSM_ClearBitmap(bitmap);
588   
589         for(y = 0; y < image.height; y++) {
590                 for(x = 0; x < image.width; x++) {
591                         if (image.data[y * image.width + x] == 0) GSM_SetPointBitmap(bitmap, x, y);
592                 }
593         }
594
595         return GE_NONE;
596 }
597
598 #endif
599
600
601 /* Based on the article from the Polish Magazine "Bajtek" 11/92 */
602 /* Marcin-Wiacek@Topnet.PL */
603
604 /* This loads the image as a startup logo - but is resized as necessary later */
605
606 GSM_Error loadbmp(FILE *file, GSM_Bitmap *bitmap)
607 {
608         unsigned char buffer[34];
609         bool first_white;
610         int w, h, pos, y, x, i, sizeimage;
611
612         bitmap->type = GSM_StartupLogo;
613         bitmap->width = 84;
614         bitmap->height = 48;
615         bitmap->size = bitmap->width * bitmap->height / 8;
616
617         GSM_ClearBitmap(bitmap);
618   
619         fread(buffer, 1, 34, file); //required part of header
620
621         h = buffer[22] + 256 * buffer[21]; //height of image in the file
622         w = buffer[18] + 256 * buffer[17]; //width of image in the file
623         dprintf("Image Size in BMP file: %dx%d\n", w, h);
624
625         dprintf("Number of colors in BMP file: ");
626         switch (buffer[28]) {
627         case 1:  dprintf("2 (supported by gnokii)\n"); break;
628         case 4:  dprintf("16 (not supported by gnokii)\n"); break;
629         case 8:  dprintf("256 (not supported by gnokii)\n"); break;
630         case 24: dprintf("True Color (not supported by gnokii)\n"); break;
631         default: dprintf("unknown\n"); break;
632         }
633
634         if (buffer[28] != 1) {
635                 fprintf(stdout, "Wrong number of colors\n"); //we support only 2 colors images !
636                 return GE_WRONGNUMBEROFCOLORS;
637         }
638
639         dprintf("Compression in BMP file: ");
640         switch (buffer[30]) {
641         case 0:  dprintf("no compression (supported by gnokii)\n"); break;
642         case 1:  dprintf("RLE8 (not supported by gnokii)\n"); break;
643         case 2:  dprintf("RLE4 (not supported by gnokii)\n"); break;
644         default: dprintf("unknown\n"); break;
645         }
646
647         if (buffer[30] != 0) {
648                 dprintf("Subformat not supported\n"); //we don't support RLE compression
649                 return GE_SUBFORMATNOTSUPPORTED;
650         }  
651   
652         pos = buffer[10] - 34;
653         fread(buffer, 1, pos, file); //rest of header (if exists) and color palette
654   
655         dprintf("First color in BMP file: %i %i %i ", buffer[pos-8], buffer[pos-7], buffer[pos-6]);
656         if (buffer[pos-8] == 0 && buffer[pos-7] == 0 && buffer[pos-6] == 0) dprintf("(white)");
657         if (buffer[pos-8] == 0xFF && buffer[pos-7] == 0xFF && buffer[pos-6] == 0xFF) dprintf("(black)");
658         if (buffer[pos-8] == 102 && buffer[pos-7] == 204 && buffer[pos-6] == 102) dprintf("(green)");
659         dprintf("\n");
660
661         dprintf("Second color in BMP file: %i %i %i ",buffer[pos-4], buffer[pos-3], buffer[pos-2]);
662         if (buffer[pos-4] == 0 && buffer[pos-3] == 0 && buffer[pos-2] == 0) dprintf("(white)");
663         if (buffer[pos-4] == 0xFF && buffer[pos-3] == 0xFF && buffer[pos-2] == 0xFF) dprintf("(black)");
664         dprintf("\n");  
665
666         first_white = true;
667         if (buffer[pos-8] != 0 || buffer[pos-7] != 0 || buffer[pos-6] != 0) first_white = false;
668  
669         sizeimage = 0;
670         pos = 7;
671         for (y = h-1; y >= 0; y--) { //lines are written from the last to the first
672                 i = 1;
673                 for (x = 0; x < w; x++) {
674                         if (pos == 7) { //new byte !
675                                 fread(buffer, 1, 1, file);
676                                 sizeimage++;
677                                 i++;
678                                 if (i == 5) i = 1; //each line is written in multiply of 4 bytes
679                         }
680                         if (x <= bitmap->width && y <= bitmap->height) { //we have top left corner !
681                                 if (!first_white) {
682                                         if ((buffer[0] & (1 << pos)) <= 0) GSM_SetPointBitmap(bitmap, x, y);
683                                 } else {
684                                         if ((buffer[0] & (1 << pos)) > 0) GSM_SetPointBitmap(bitmap, x, y);
685                                 }
686                         }
687                         pos--;
688                         if (pos < 0) pos = 7; //going to new byte
689                 }
690                 pos = 7; //going to new byte
691                 while (i != 5) { //each line is written in multiples of 4 bytes
692                         fread(buffer, 1, 1, file);
693                         sizeimage++;
694                         i++;
695                 }
696         }
697         dprintf("Data size in BMP file: %i\n", sizeimage);
698         return(GE_NONE);
699 }
700
701 GSM_Error loadnol(FILE *file, GSM_Bitmap *bitmap)
702 {
703         unsigned char buffer[2000];
704         int i, j;
705
706         bitmap->type = GSM_OperatorLogo;
707
708         fread(buffer, 1, 6, file);
709         fread(buffer, 1, 4, file);
710         sprintf(bitmap->netcode, "%d %02d", buffer[0] + 256 * buffer[1], buffer[2]);
711
712         fread(buffer, 1, 4, file); /* Width and height of the icon. */
713         bitmap->width = buffer[0];
714         bitmap->height = buffer[2];
715         bitmap->size = bitmap->height * bitmap->width / 8;
716
717         if ((bitmap->height != 14) || (bitmap->width != 72)) {
718                 dprintf("Invalid Image Size (%dx%d).\n",bitmap->width,bitmap->height);
719                 return GE_INVALIDIMAGESIZE;
720         }
721
722         fread(buffer, 1, 6, file); /* Unknown bytes. */
723
724         for (i = 0; i < bitmap->size; i++) {
725                 if (fread(buffer, 1, 8, file) == 8) {
726                         bitmap->bitmap[i] = 0;
727                         for (j = 7; j >= 0; j--)
728                                 if (buffer[7-j] == '1')
729                                         bitmap->bitmap[i] |= (1 << j);
730                 } else {
731                         return(GE_FILETOOSHORT);
732                 }
733         }
734
735         /* Some programs writes here fileinfo */
736         if (fread(buffer, 1, 1, file) == 1) {
737                 dprintf("Fileinfo: %c", buffer[0]);
738                 while (fread(buffer, 1, 1, file) == 1) {
739                         if (buffer[0] != 0x0A) dprintf("%c",buffer[0]);
740                 }  
741                 dprintf("\n");
742         }
743         return(GE_NONE);
744 }
745
746 GSM_Error loadngg(FILE *file, GSM_Bitmap *bitmap)
747 {
748         unsigned char buffer[2000];
749         int i, j;
750
751         bitmap->type = GSM_CallerLogo;
752
753         fread(buffer, 1, 6, file);
754         fread(buffer, 1, 4, file); /* Width and height of the icon. */
755         bitmap->width = buffer[0];
756         bitmap->height = buffer[2];
757         bitmap->size = bitmap->height * bitmap->width / 8;
758
759         if ((bitmap->height != 14) || (bitmap->width != 72)) {
760                 dprintf("Invalid Image Size (%dx%d).\n", bitmap->width, bitmap->height);
761                 return GE_INVALIDIMAGESIZE;
762         }
763
764         fread(buffer, 1, 6, file); /* Unknown bytes. */
765
766         for (i = 0; i < bitmap->size; i++) {
767                 if (fread(buffer, 1, 8, file) == 8){
768                         bitmap->bitmap[i] = 0;
769                         for (j = 7; j >= 0;j--)
770                                 if (buffer[7-j] == '1')
771                                         bitmap->bitmap[i] |= (1 << j);
772                 } else {
773                         return(GE_FILETOOSHORT);
774                 }
775         }
776
777         /* Some programs writes here fileinfo */
778         if (fread(buffer, 1, 1, file) == 1) {
779                 dprintf("Fileinfo: %c",buffer[0]);
780                 while (fread(buffer, 1, 1, file) == 1) {
781                         if (buffer[0] != 0x0A) dprintf("%c", buffer[0]);
782                 }  
783                 dprintf("\n");
784         }
785         return(GE_NONE);
786 }
787
788 GSM_Error loadnsl(FILE *file, GSM_Bitmap *bitmap)
789 {
790         unsigned char block[6], buffer[505];
791         int block_size;
792
793         bitmap->size = 0;
794   
795         while (fread(block, 1, 6, file) == 6) {
796                 block_size = block[4] * 256 + block[5];
797                 dprintf("Block %c%c%c%c, size %i\n", block[0], block[1], block[2], block[3], block_size);
798                 if (!strncmp(block, "FORM", 4)) {
799                         dprintf("  File ID\n");
800                 } else {
801                         if (block_size > 504) return(GE_INVALIDFILEFORMAT);
802
803                         if (block_size != 0) {
804
805                                 fread(buffer, 1, block_size, file);
806                                 buffer[block_size] = 0; //if it's string, we end it with \0
807
808                                 if (!strncmp(block, "VERS", 4)) dprintf("  File saved by: %s\n", buffer);
809                                 if (!strncmp(block, "MODL", 4)) dprintf("  Logo saved from: %s\n", buffer);
810                                 if (!strncmp(block, "COMM", 4)) dprintf("  Phone was connected to COM port: %s\n", buffer);
811         
812                                 if (!strncmp(block, "NSLD", 4)) {          
813                                         bitmap->type = GSM_StartupLogo;
814                                         bitmap->height = 48;
815                                         bitmap->width = 84;
816                                         bitmap->size = (bitmap->height * bitmap->width) / 8;
817
818                                         memcpy(bitmap->bitmap, buffer, bitmap->size);
819
820                                         dprintf("  Startup logo (size %i)\n", block_size);
821                                 }
822                         }
823                 }
824         }
825         if (bitmap->size == 0) return(GE_FILETOOSHORT);
826         return(GE_NONE);
827 }
828
829 GSM_Error loadnlm (FILE *file, GSM_Bitmap *bitmap)
830 {
831         unsigned char buffer[84*48];
832         int pos, pos2, x, y;
833         div_t division;
834
835         fread(buffer, 1, 5, file);
836         fread(buffer, 1, 1, file);
837
838         switch (buffer[0]) {
839         case 0x00:
840                 bitmap->type = GSM_OperatorLogo;
841                 break;
842         case 0x01:
843                 bitmap->type = GSM_CallerLogo;
844                 break;
845         case 0x02:
846                 bitmap->type = GSM_StartupLogo;
847                 break;
848         case 0x03:
849                 bitmap->type = GSM_PictureImage;
850                 break;
851         default:
852                 return(GE_SUBFORMATNOTSUPPORTED);
853         }
854   
855         fread(buffer, 1, 4, file);
856         bitmap->width = buffer[1];
857         bitmap->height = buffer[2];
858         bitmap->size = bitmap->width * bitmap->height / 8;
859
860         division = div(bitmap->width, 8);
861         if (division.rem != 0) division.quot++; /* For startup logos */
862   
863         if (fread(buffer, 1, (division.quot * bitmap->height), file) != (division.quot * bitmap->height))
864                 return(GE_FILETOOSHORT);
865     
866         GSM_ClearBitmap(bitmap);
867   
868         pos = 0; pos2 = 7;
869         for (y = 0; y < bitmap->height; y++) {
870                 for (x = 0; x < bitmap->width; x++) {
871                         if ((buffer[pos] & (1 << pos2)) > 0) GSM_SetPointBitmap(bitmap, x, y);
872                         pos2--;
873                         if (pos2 < 0) {pos2 = 7; pos++;} //going to new byte
874                 }
875                 if (pos2 != 7) {pos2 = 7; pos++;} //for startup logos-new line means new byte
876         }
877         return (GE_NONE);
878 }
879
880 GSM_Error loadota(FILE *file, GSM_Bitmap *bitmap)
881 {
882         char buffer[4];
883
884         /* We could check for extended info here - indicated by the 7th bit being set in the first byte */
885         fread(buffer,1,4,file);
886
887         bitmap->width = buffer[1];
888         bitmap->height = buffer[2];
889         bitmap->size = bitmap->width * bitmap->height / 8;
890
891         if ((bitmap->height == 48) && (bitmap->width == 84)) {
892                 bitmap->type = GSM_StartupLogo;
893         }
894         else if ((bitmap->height == 14) && (bitmap->width == 72)) {
895                 bitmap->type = GSM_CallerLogo;
896         } else {
897                 dprintf("Invalid Image Size (%dx%d).\n", bitmap->width, bitmap->height);
898                 return GE_INVALIDIMAGESIZE;
899         }
900         if (fread(bitmap->bitmap,1,bitmap->size,file) != bitmap->size)
901                 return(GE_FILETOOSHORT);
902         return(GE_NONE);
903 }
904
905 /* This overwrites an existing file - so this must be checked before calling */
906 GSM_Error GSM_SaveBitmapFile(char *FileName, GSM_Bitmap *bitmap)
907 {
908         FILE *file;
909         bool done = false;
910
911         /* XPMs are a bit messy because we have to pass it the filename */
912   
913 #ifdef XPM
914         if (strstr(FileName, ".xpm")) {
915                 savexpm(FileName, bitmap);
916         } else {
917 #endif    
918    
919                 file = fopen(FileName, "wb");
920       
921                 if (!file)
922                         return(GE_CANTOPENFILE);
923         
924                 if (strstr(FileName, ".nlm")) {
925                         savenlm(file, bitmap);
926                         done = true;
927                 }
928                 if (strstr(FileName, ".ngg")) {
929                         savengg(file, bitmap);
930                         done = true;    
931                 }
932                 if (strstr(FileName, ".nsl")) {
933                         savensl(file, bitmap);
934                         done = true;
935                 }
936                 if (strstr(FileName, ".otb")) {
937                         saveota(file, bitmap);
938                         done = true;
939                 }
940                 if (strstr(FileName, ".nol")) {
941                         savenol(file, bitmap);
942                         done = true;
943                 }
944                 if (strstr(FileName, ".bmp") ||
945                     strstr(FileName, ".ggp") ||
946                     strstr(FileName, ".i61")) {
947                         savebmp(file, bitmap);
948                         done = true;
949                 }
950    
951                 if (!done) {
952                         switch (bitmap->type) {
953                         case GSM_CallerLogo:
954                                 savengg(file, bitmap);
955                                 break;
956                         case GSM_OperatorLogo:
957                                 savenol(file, bitmap);
958                                 break;
959                         case GSM_StartupLogo:
960                                 savensl(file, bitmap);
961                                 break;
962                         case GSM_PictureImage:
963                                 savenlm(file, bitmap);
964                                 break;
965                         case GSM_WelcomeNoteText:
966                                 break;
967                         case GSM_DealerNoteText:
968                                 break;
969                         case GSM_None:
970                                 break;
971                         }      
972                 }
973                 fclose(file);
974 #ifdef XPM
975         }
976 #endif    
977         return GE_NONE;
978 }
979
980
981 /* FIXME - this should not ask for confirmation here - I'm not sure what calls it though */
982 /* mode == 0 -> overwrite
983  * mode == 1 -> ask
984  * mode == 2 -> append
985  */
986 int GSM_SaveTextFile(char *FileName, char *text, int mode)
987 {
988         FILE *file;
989         int confirm = -1;
990         char ans[5];
991         struct stat buf;
992
993         /* Ask before overwriting */
994         if ((mode == 1) && (stat(FileName, &buf) == 0)) {
995                 fprintf(stdout, _("File %s exists.\n"), FileName);
996                 while (confirm < 0) {
997                         fprintf(stderr, _("Overwrite? (yes/no) "));
998                         GetLine(stdin, ans, 4);
999                         if (!strcmp(ans, _("yes"))) confirm = 1;
1000                         else if (!strcmp(ans, _("no"))) confirm = 0;
1001                 }  
1002                 if (!confirm) return -1;
1003         }
1004
1005         if (mode == 2) file = fopen(FileName, "a");
1006         else file = fopen(FileName, "w");
1007
1008         if (!file) {
1009                 fprintf(stderr, _("Failed to write file %s\n"),  FileName);
1010                 return(-1);
1011         }
1012         fprintf(file, "%s\n", text);
1013         fclose(file);
1014         return 2;
1015 }
1016
1017
1018 #ifdef XPM
1019 void savexpm(char *filename, GSM_Bitmap *bitmap)
1020 {
1021         XpmColor colors[2] = {{".","c","#000000","#000000","#000000","#000000"},
1022                               {"#","c","#ffffff","#ffffff","#ffffff","#ffffff"}};
1023         XpmImage image;
1024         unsigned int data[6240];
1025         int x, y;
1026
1027         image.height = bitmap->height;
1028         image.width = bitmap->width;
1029         image.cpp = 1;
1030         image.ncolors = 2;
1031         image.colorTable = colors;
1032         image.data = data;
1033
1034         for (y = 0; y < image.height; y++) {
1035                 for (x = 0; x < image.width; x++)
1036                         if (GSM_IsPointBitmap(bitmap, x, y))
1037                                 data[y * image.width + x] = 0;
1038                         else
1039                                 data[y * image.width + x] = 1;
1040         }
1041   
1042         XpmWriteFileFromXpmImage(filename, &image, NULL);
1043 }
1044 #endif
1045
1046 /* Based on the article from the Polish Magazine "Bajtek" 11/92 */
1047 /* Marcin-Wiacek@Topnet.PL */
1048 void savebmp(FILE *file, GSM_Bitmap *bitmap)
1049 {
1050         int x, y, pos, i, sizeimage;
1051         unsigned char buffer[1];
1052         div_t division;
1053   
1054         char header[] = {
1055 /*1'st header*/   'B','M',             /* BMP file ID */
1056                   0x00,0x00,0x00,0x00, /* Size of file */
1057                   0x00,0x00,           /* Reserved for future use */
1058                   0x00,0x00,           /* Reserved for future use */
1059                   62,0x00,0x00,0x00, /* Offset for image data */
1060                  
1061 /*2'nd header*/     40,0x00,0x00,0x00, /* Length of this part of header */
1062                   0x00,0x00,0x00,0x00, /* Width of image */
1063                   0x00,0x00,0x00,0x00, /* Height of image */             
1064                   1,0x00,           /* How many planes in target device */
1065                   1,0x00,           /* How many colors in image. 1 means 2^1=2 colors */
1066                   0x00,0x00,0x00,0x00, /* Type of compression. 0 means no compression */
1067 /*Sometimes */    0x00,0x00,0x00,0x00, /* Size of part with image data */
1068 /*ttttttt...*/    0xE8,0x03,0x00,0x00, /* XPelsPerMeter */
1069 /*hhiiiiissss*/   0xE8,0x03,0x00,0x00, /* YPelsPerMeter */                
1070 /*part of header*/2,0x00,0x00,0x00, /* How many colors from palette is used */
1071 /*doesn't exist*/ 0x00,0x00,0x00,0x00, /* How many colors from palette is required to display image. 0 means all */
1072                  
1073 /*Color palette*/ 0x00,0x00,0x00,      /* First color in palette in Blue, Green, Red. Here white */
1074                   0x00,                /* Each color in palette is end by 4'th byte */
1075                   0xFF,0xFF,0xFF,      /* Second color in palette in Blue, Green, Red. Here black */
1076                   0x00};               /* Each color in palette is end by 4'th byte */
1077
1078         header[22] = bitmap->height;
1079         header[18] = bitmap->width;
1080   
1081         pos = 7;
1082         sizeimage = 0;
1083         for (y = bitmap->height - 1; y >= 0; y--) { //lines are written from the last to the first
1084                 i = 1;
1085                 for (x = 0; x < bitmap->width; x++) {
1086                         if (pos == 7) { //new byte !
1087                                 sizeimage++;
1088                                 i++;
1089                                 if (i == 5) i = 1; //each line is written in multiply of 4 bytes
1090                         }
1091                         pos--;
1092                         if (pos < 0) pos = 7; //going to new byte
1093                 }
1094                 pos = 7; //going to new byte
1095                 while (i != 5) { //each line is written in multiply of 4 bytes
1096                         sizeimage++;
1097                         i++;
1098                 }
1099         }
1100         dprintf("Data size in BMP file: %i\n", sizeimage);
1101         division = div(sizeimage, 256);
1102         header[35] = division.quot;
1103         header[34] = sizeimage - (division.quot * 256);
1104   
1105         sizeimage = sizeimage + sizeof(header);
1106         dprintf("Size of BMP file: %i\n", sizeimage);
1107         division = div(sizeimage, 256);
1108         header[3] = division.quot;
1109         header[2] = sizeimage - (division.quot * 256);
1110        
1111         fwrite(header, 1, sizeof(header), file);
1112
1113         pos = 7;
1114         for (y = bitmap->height - 1; y >= 0; y--) { //lines are written from the last to the first
1115                 i = 1;
1116                 for (x = 0; x < bitmap->width; x++) {
1117                         if (pos == 7) { //new byte !
1118                                 if (x != 0) fwrite(buffer, 1, sizeof(buffer), file);
1119                                 i++;
1120                                 if(i == 5) i = 1; //each line is written in multiply of 4 bytes
1121                                 buffer[0] = 0;
1122                         }
1123                         if (GSM_IsPointBitmap(bitmap, x, y)) buffer[0] |= (1 << pos);
1124                         pos--;
1125                         if (pos < 0) pos = 7; //going to new byte
1126                 }
1127                 pos = 7; //going to new byte
1128                 fwrite(buffer, 1, sizeof(buffer), file);
1129                 while (i != 5) { //each line is written in multiply of 4 bytes
1130                         buffer[0] = 0;
1131                         fwrite(buffer, 1, sizeof(buffer), file);
1132                         i++;
1133                 }
1134         }
1135 }
1136
1137 void savengg(FILE *file, GSM_Bitmap *bitmap)
1138 {
1139
1140         char header[] = {'N','G','G',0x00,0x01,0x00,
1141                          0x00,0x00,           /* Width */
1142                          0x00,0x00,           /* Height */
1143                          0x01,0x00,0x01,0x00,
1144                          0x00,                /* Unknown.Can't be checksum - for */
1145                          /* the same logo files can be different */
1146                          0x00}; 
1147
1148         char buffer[8];
1149         int i, j;
1150         /* This could be the phone info... */
1151         GSM_Information info={"",0,0,0,0,0,0,0,0,0,0,0,0,0,14,72};
1152
1153         GSM_ResizeBitmap(bitmap, GSM_CallerLogo, &info);
1154
1155         header[6] = bitmap->width;
1156         header[8] = bitmap->height;
1157
1158         fwrite(header, 1, sizeof(header), file);
1159
1160         for (i = 0; i < bitmap->size; i++) {
1161                 for (j = 7; j >= 0;j--)
1162                         if ((bitmap->bitmap[i] & (1 << j)) > 0) {
1163                                 buffer[7-j] = '1';
1164                         } else {
1165                                 buffer[7-j] = '0';
1166                         }
1167                 fwrite(buffer, 1, 8, file);
1168         }
1169 }
1170   
1171 void savenol(FILE *file, GSM_Bitmap *bitmap)
1172 {
1173
1174         char header[] = {'N','O','L',0x00,0x01,0x00,
1175                          0x00,0x00,           /* MCC */
1176                          0x00,0x00,           /* MNC */
1177                          0x00,0x00,           /* Width */
1178                          0x00,0x00,           /* Height */
1179                          0x01,0x00,0x01,0x00,
1180                          0x00,                /* Unknown.Can't be checksum - for */
1181                          /* the same logo files can be different */
1182                          0x00};
1183         char buffer[8];
1184         int i, j, country, net;
1185         /* This could be the phone info... */
1186         GSM_Information info={"",0,0,0,0,0,0,0,0,0,0,0,14,72,0,0};
1187
1188         GSM_ResizeBitmap(bitmap, GSM_OperatorLogo, &info);
1189
1190         sscanf(bitmap->netcode, "%d %d", &country, &net);
1191
1192         header[6] = country % 256;
1193         header[7] = country / 256;
1194         header[8] = net % 256;
1195         header[9] = net / 256;
1196         header[10] = bitmap->width;
1197         header[12] = bitmap->height;
1198
1199         fwrite(header, 1, sizeof(header), file);
1200   
1201         for (i = 0; i < bitmap->size; i++) {
1202                 for (j = 7; j >= 0; j--)
1203                         if ((bitmap->bitmap[i] & (1 << j)) > 0) {
1204                                 buffer[7-j] = '1';
1205                         } else {
1206                                 buffer[7-j] = '0';
1207                         }
1208                 fwrite(buffer, 1, 8, file);
1209         }
1210 }
1211
1212 void savensl(FILE *file, GSM_Bitmap *bitmap)
1213 {
1214
1215         u8 header[] = {'F','O','R','M', 0x01,0xFE,  /* File ID block,      size 1*256+0xFE=510*/
1216                        'N','S','L','D', 0x01,0xF8}; /* Startup Logo block, size 1*256+0xF8=504*/
1217         /* This could be the phone info... */
1218         GSM_Information info={"",0,0,0,0,0,0,0,0,0,48,84,0,0,0,0};
1219
1220         GSM_ResizeBitmap(bitmap, GSM_StartupLogo, &info);
1221
1222         fwrite(header, 1, sizeof(header), file);
1223
1224         fwrite(bitmap->bitmap, 1, bitmap->size, file);
1225 }
1226
1227 void saveota(FILE *file, GSM_Bitmap *bitmap)
1228 {
1229         char header[] = {0x01,
1230                          0x00, /* Width */
1231                          0x00, /* Height */
1232                          0x01};
1233
1234         header[1] = bitmap->width;
1235         header[2] = bitmap->height;
1236     
1237         fwrite(header, 1, sizeof(header), file);
1238
1239         fwrite(bitmap->bitmap, 1, bitmap->size, file);
1240 }
1241
1242 void savenlm(FILE *file, GSM_Bitmap *bitmap)
1243 {
1244         char header[] = {'N','L','M', /* Nokia Logo Manager file ID. */
1245                          0x20,
1246                          0x01,
1247                          0x00,        /* 0x00 (OP), 0x01 (CLI), 0x02 (Startup), 0x03 (Picture)*/
1248                          0x00,
1249                          0x00,        /* Width. */
1250                          0x00,        /* Height. */
1251                          0x01};
1252                  
1253         unsigned char buffer[11 * 48];
1254         int x, y, pos, pos2;
1255         div_t division;
1256
1257         switch (bitmap->type) {
1258         case GSM_OperatorLogo:
1259                 header[5] = 0x00;
1260                 break;
1261         case GSM_CallerLogo:
1262                 header[5] = 0x01;
1263                 break;
1264         case GSM_StartupLogo:
1265                 header[5] = 0x02;
1266                 break;
1267         case GSM_PictureImage:
1268                 header[5] = 0x03;
1269                 break;
1270         case GSM_WelcomeNoteText:
1271                 break;
1272         case GSM_DealerNoteText:
1273                 break;
1274         case GSM_None:
1275                 break;
1276         }
1277   
1278         header[7] = bitmap->width;
1279         header[8] = bitmap->height;
1280   
1281         pos = 0; pos2 = 7;
1282         for (y = 0; y < bitmap->height; y++) {
1283                 for (x = 0; x < bitmap->width; x++) {
1284                         if (pos2 == 7) buffer[pos] = 0;
1285                         if (GSM_IsPointBitmap(bitmap, x, y)) buffer[pos] |= (1 << pos2);
1286                         pos2--;
1287                         if (pos2 < 0) {pos2 = 7; pos++;} //going to new line
1288                 }
1289                 if (pos2 != 7) {pos2 = 7; pos++;} //for startup logos - new line with new byte
1290         }
1291   
1292         division = div(bitmap->width, 8);
1293         if (division.rem != 0) division.quot++; /* For startup logos */
1294   
1295         fwrite(header, 1, sizeof(header), file);
1296
1297         fwrite(buffer,1,(division.quot*bitmap->height),file);
1298 }
1299
1300 GSM_Error GSM_ShowBitmapFile(char *FileName)
1301 {
1302         FILE *file;
1303         unsigned char buffer[300];
1304         int error;
1305         GSM_Filetypes filetype = None;
1306         int i, k = 0;
1307         GSM_Bitmap bitmap;
1308
1309         file = fopen(FileName, "rb");
1310
1311         if (!file)
1312                 return (GE_CANTOPENFILE);
1313
1314         fread(buffer, 1, 9, file);
1315
1316         if (memcmp(buffer, "NOL", 3) == 0) {
1317                 filetype = NOL;
1318         } else if (memcmp(buffer, "NGG", 3) == 0) {
1319                 filetype = NGG;
1320         } else if (memcmp(buffer, "FORM", 4) == 0) {
1321                 filetype = NSL;
1322         } else if (memcmp(buffer, "NLM", 3) == 0) {
1323                 filetype = NLM;
1324         } else if (memcmp(buffer, "BM", 2) == 0) {
1325                 filetype = BMP;
1326         } else if (memcmp(buffer, "XPM", 3) == 0) {
1327                 filetype = XPMF;
1328         } else filetype = None;  
1329         
1330         if (strstr(FileName, ".otb")) filetype = OTA;
1331
1332         rewind(file);
1333
1334         switch (filetype) {
1335         case NOL:
1336                 error = loadnol(file, &bitmap);
1337                 fclose(file);
1338                 break;
1339         case NGG:
1340                 error = loadngg(file, &bitmap);
1341                 fclose(file);
1342                 break;
1343         case NSL:
1344                 error = loadnsl(file, &bitmap);
1345                 fclose(file);
1346                 break;
1347         case OTA:
1348                 error = loadota(file, &bitmap);
1349                 fclose(file);
1350                 break;
1351         case BMP:
1352                 error = loadbmp(file, &bitmap);
1353                 fclose(file);
1354                 break;
1355 #ifdef XPM
1356         case XPMF:
1357                 fclose(file);
1358                 error = loadxpm(FileName, &bitmap);
1359                 break;
1360 #endif
1361         default:
1362                 error = GE_INVALIDFILEFORMAT;
1363                 break;
1364         }  
1365
1366         if (error != GE_NONE)
1367                 return (error);
1368
1369         for (i = 0; i < bitmap.size; i++) {
1370                 int j;
1371
1372                 for (j = 7; j >= 0; j--) {
1373                         if ((bitmap.bitmap[i] & (1 << j))) {
1374                                 fprintf(stdout, "#");
1375                         } else {
1376                                 fprintf(stdout, " ");
1377                         }
1378                         if (!(++k % bitmap.width))
1379                                 fprintf(stdout, "\n");
1380                 }
1381         }
1382
1383         return (GE_NONE);
1384 }