Merge branch 'upstream' into tizen
[platform/upstream/lcms2.git] / src / cmscgats.c
1 //---------------------------------------------------------------------------------
2 //
3 //  Little Color Management System
4 //  Copyright (c) 1998-2023 Marti Maria Saguer
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the "Software"),
8 // to deal in the Software without restriction, including without limitation
9 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 // and/or sell copies of the Software, and to permit persons to whom the Software
11 // is furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18 // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24 //---------------------------------------------------------------------------------
25 //
26
27 #include "lcms2_internal.h"
28
29
30 // IT8.7 / CGATS.17-200x handling -----------------------------------------------------------------------------
31
32
33 #define MAXID        128     // Max length of identifier
34 #define MAXSTR      1024     // Max length of string
35 #define MAXTABLES    255     // Max Number of tables in a single stream
36 #define MAXINCLUDE    20     // Max number of nested includes
37
38 #define DEFAULT_DBL_FORMAT  "%.10g" // Double formatting
39
40 #ifdef CMS_IS_WINDOWS_
41 #    include <io.h>
42 #    define DIR_CHAR    '\\'
43 #else
44 #    define DIR_CHAR    '/'
45 #endif
46
47
48 // Symbols
49 typedef enum {
50
51         SUNDEFINED,
52         SINUM,      // Integer
53         SDNUM,      // Real
54         SIDENT,     // Identifier
55         SSTRING,    // string
56         SCOMMENT,   // comment
57         SEOLN,      // End of line
58         SEOF,       // End of stream
59         SSYNERROR,  // Syntax error found on stream
60
61         // IT8 symbols
62
63         SBEGIN_DATA,
64         SBEGIN_DATA_FORMAT,
65         SEND_DATA,
66         SEND_DATA_FORMAT,
67         SKEYWORD,
68         SDATA_FORMAT_ID,
69         SINCLUDE,
70
71         // Cube symbols
72
73         SDOMAIN_MAX,
74         SDOMAIN_MIN,
75         S_LUT1D_SIZE,
76         S_LUT1D_INPUT_RANGE,
77         S_LUT3D_SIZE,
78         S_LUT3D_INPUT_RANGE,
79         S_LUT_IN_VIDEO_RANGE,
80         S_LUT_OUT_VIDEO_RANGE,
81         STITLE
82
83     } SYMBOL;
84
85
86 // How to write the value
87 typedef enum {
88
89         WRITE_UNCOOKED,
90         WRITE_STRINGIFY,
91         WRITE_HEXADECIMAL,
92         WRITE_BINARY,
93         WRITE_PAIR
94
95     } WRITEMODE;
96
97 // Linked list of variable names
98 typedef struct _KeyVal {
99
100         struct _KeyVal*  Next;
101         char*            Keyword;       // Name of variable
102         struct _KeyVal*  NextSubkey;    // If key is a dictionary, points to the next item
103         char*            Subkey;        // If key is a dictionary, points to the subkey name
104         char*            Value;         // Points to value
105         WRITEMODE        WriteAs;       // How to write the value
106
107    } KEYVALUE;
108
109
110 // Linked list of memory chunks (Memory sink)
111 typedef struct _OwnedMem {
112
113         struct _OwnedMem* Next;
114         void *            Ptr;          // Point to value
115
116    } OWNEDMEM;
117
118 // Suballocator
119 typedef struct _SubAllocator {
120
121          cmsUInt8Number* Block;
122          cmsUInt32Number BlockSize;
123          cmsUInt32Number Used;
124
125     } SUBALLOCATOR;
126
127 // Table. Each individual table can hold properties and rows & cols
128 typedef struct _Table {
129
130         char SheetType[MAXSTR];               // The first row of the IT8 (the type)
131
132         int            nSamples, nPatches;    // Cols, Rows
133         int            SampleID;              // Pos of ID
134
135         KEYVALUE*      HeaderList;            // The properties
136
137         char**         DataFormat;            // The binary stream descriptor
138         char**         Data;                  // The binary stream
139
140     } TABLE;
141
142 // File stream being parsed
143 typedef struct _FileContext {
144         char           FileName[cmsMAX_PATH];    // File name if being read from file
145         FILE*          Stream;                   // File stream or NULL if holded in memory
146     } FILECTX;
147
148 //Very simple string 
149 typedef struct {
150
151         struct struct_it8* it8;
152         cmsInt32Number max;
153         cmsInt32Number len;
154         char* begin;
155     } string;
156
157
158 // This struct hold all information about an open IT8 handler.
159 typedef struct struct_it8 {
160
161         cmsUInt32Number  TablesCount;                     // How many tables in this stream
162         cmsUInt32Number  nTable;                          // The actual table
163
164         // Partser type
165         cmsBool        IsCUBE;
166
167         // Tables
168         TABLE Tab[MAXTABLES];
169
170         // Memory management
171         OWNEDMEM*      MemorySink;            // The storage backend
172         SUBALLOCATOR   Allocator;             // String suballocator -- just to keep it fast
173
174         // Parser state machine
175         SYMBOL             sy;                // Current symbol
176         int                ch;                // Current character
177
178         cmsInt32Number     inum;              // integer value
179         cmsFloat64Number   dnum;              // real value
180
181         string*        id;            // identifier
182         string*        str;           // string
183
184         // Allowed keywords & datasets. They have visibility on whole stream
185         KEYVALUE*      ValidKeywords;
186         KEYVALUE*      ValidSampleID;
187
188         char*          Source;                // Points to loc. being parsed
189         cmsInt32Number lineno;                // line counter for error reporting
190
191         FILECTX*       FileStack[MAXINCLUDE]; // Stack of files being parsed
192         cmsInt32Number IncludeSP;             // Include Stack Pointer
193
194         char*          MemoryBlock;           // The stream if holded in memory
195
196         char           DoubleFormatter[MAXID];// Printf-like 'cmsFloat64Number' formatter
197
198         cmsContext    ContextID;              // The threading context
199
200    } cmsIT8;
201
202
203 // The stream for save operations
204 typedef struct {
205
206         FILE* stream;   // For save-to-file behaviour
207
208         cmsUInt8Number* Base;
209         cmsUInt8Number* Ptr;        // For save-to-mem behaviour
210         cmsUInt32Number Used;
211         cmsUInt32Number Max;
212
213     } SAVESTREAM;
214
215
216 // ------------------------------------------------------ cmsIT8 parsing routines
217
218
219 // A keyword
220 typedef struct {
221
222         const char *id;
223         SYMBOL sy;
224
225    } KEYWORD;
226
227 // The keyword->symbol translation tables. Sorting is required.
228 static const KEYWORD TabKeysIT8[] = {
229
230         {"$INCLUDE",               SINCLUDE},   // This is an extension!
231         {".INCLUDE",               SINCLUDE},   // This is an extension!
232
233         {"BEGIN_DATA",             SBEGIN_DATA },
234         {"BEGIN_DATA_FORMAT",      SBEGIN_DATA_FORMAT },
235         {"DATA_FORMAT_IDENTIFIER", SDATA_FORMAT_ID},
236         {"END_DATA",               SEND_DATA},
237         {"END_DATA_FORMAT",        SEND_DATA_FORMAT},
238         {"KEYWORD",                SKEYWORD}
239         };
240
241 #define NUMKEYS_IT8 (sizeof(TabKeysIT8)/sizeof(KEYWORD))
242
243 static const KEYWORD TabKeysCUBE[] = {
244         
245         {"DOMAIN_MAX",             SDOMAIN_MAX },
246         {"DOMAIN_MIN",             SDOMAIN_MIN },        
247         {"LUT_1D_SIZE",            S_LUT1D_SIZE },
248         {"LUT_1D_INPUT_RANGE",     S_LUT1D_INPUT_RANGE },
249         {"LUT_3D_SIZE",            S_LUT3D_SIZE },
250         {"LUT_3D_INPUT_RANGE",     S_LUT3D_INPUT_RANGE },        
251         {"LUT_IN_VIDEO_RANGE",     S_LUT_IN_VIDEO_RANGE },
252         {"LUT_OUT_VIDEO_RANGE",    S_LUT_OUT_VIDEO_RANGE },
253         {"TITLE",                  STITLE }
254         
255 };
256
257 #define NUMKEYS_CUBE (sizeof(TabKeysCUBE)/sizeof(KEYWORD))
258
259
260
261 // Predefined properties
262
263 // A property
264 typedef struct {
265         const char *id;    // The identifier
266         WRITEMODE as;      // How is supposed to be written
267     } PROPERTY;
268
269 static PROPERTY PredefinedProperties[] = {
270
271         {"NUMBER_OF_FIELDS", WRITE_UNCOOKED},    // Required - NUMBER OF FIELDS
272         {"NUMBER_OF_SETS",   WRITE_UNCOOKED},    // Required - NUMBER OF SETS
273         {"ORIGINATOR",       WRITE_STRINGIFY},   // Required - Identifies the specific system, organization or individual that created the data file.
274         {"FILE_DESCRIPTOR",  WRITE_STRINGIFY},   // Required - Describes the purpose or contents of the data file.
275         {"CREATED",          WRITE_STRINGIFY},   // Required - Indicates date of creation of the data file.
276         {"DESCRIPTOR",       WRITE_STRINGIFY},   // Required  - Describes the purpose or contents of the data file.
277         {"DIFFUSE_GEOMETRY", WRITE_STRINGIFY},   // The diffuse geometry used. Allowed values are "sphere" or "opal".
278         {"MANUFACTURER",     WRITE_STRINGIFY},
279         {"MANUFACTURE",      WRITE_STRINGIFY},   // Some broken Fuji targets does store this value
280         {"PROD_DATE",        WRITE_STRINGIFY},   // Identifies year and month of production of the target in the form yyyy:mm.
281         {"SERIAL",           WRITE_STRINGIFY},   // Uniquely identifies individual physical target.
282
283         {"MATERIAL",         WRITE_STRINGIFY},    // Identifies the material on which the target was produced using a code
284                                                   // uniquely identifying th e material. This is intend ed to be used for IT8.7
285                                                   // physical targets only (i.e . IT8.7/1 and IT8.7/2).
286
287         {"INSTRUMENTATION",  WRITE_STRINGIFY},    // Used to report the specific instrumentation used (manufacturer and
288                                                   // model number) to generate the data reported. This data will often
289                                                   // provide more information about the particular data collected than an
290                                                   // extensive list of specific details. This is particularly important for
291                                                   // spectral data or data derived from spectrophotometry.
292
293         {"MEASUREMENT_SOURCE", WRITE_STRINGIFY},  // Illumination used for spectral measurements. This data helps provide
294                                                   // a guide to the potential for issues of paper fluorescence, etc.
295
296         {"PRINT_CONDITIONS", WRITE_STRINGIFY},     // Used to define the characteristics of the printed sheet being reported.
297                                                    // Where standard conditions have been defined (e.g., SWOP at nominal)
298                                                    // named conditions may suffice. Otherwise, detailed information is
299                                                    // needed.
300
301         {"SAMPLE_BACKING",   WRITE_STRINGIFY},     // Identifies the backing material used behind the sample during
302                                                    // measurement. Allowed values are "black", "white", or {"na".
303                                                   
304         {"CHISQ_DOF",        WRITE_STRINGIFY},     // Degrees of freedom associated with the Chi squared statistic
305                                                    // below properties are new in recent specs:
306
307         {"MEASUREMENT_GEOMETRY", WRITE_STRINGIFY}, // The type of measurement, either reflection or transmission, should be indicated
308                                                    // along with details of the geometry and the aperture size and shape. For example,
309                                                    // for transmission measurements it is important to identify 0/diffuse, diffuse/0,
310                                                    // opal or integrating sphere, etc. For reflection it is important to identify 0/45,
311                                                    // 45/0, sphere (specular included or excluded), etc.
312
313        {"FILTER",            WRITE_STRINGIFY},     // Identifies the use of physical filter(s) during measurement. Typically used to
314                                                    // denote the use of filters such as none, D65, Red, Green or Blue.
315                                                   
316        {"POLARIZATION",      WRITE_STRINGIFY},     // Identifies the use of a physical polarization filter during measurement. Allowed
317                                                    // values are {"yes", "white", "none" or "na".
318
319        {"WEIGHTING_FUNCTION", WRITE_PAIR},         // Indicates such functions as: the CIE standard observer functions used in the
320                                                    // calculation of various data parameters (2 degree and 10 degree), CIE standard
321                                                    // illuminant functions used in the calculation of various data parameters (e.g., D50,
322                                                    // D65, etc.), density status response, etc. If used there shall be at least one
323                                                    // name-value pair following the WEIGHTING_FUNCTION tag/keyword. The first attribute
324                                                    // in the set shall be {"name" and shall identify the particular parameter used.
325                                                    // The second shall be {"value" and shall provide the value associated with that name.
326                                                    // For ASCII data, a string containing the Name and Value attribute pairs shall follow
327                                                    // the weighting function keyword. A semi-colon separates attribute pairs from each
328                                                    // other and within the attribute the name and value are separated by a comma.
329
330        {"COMPUTATIONAL_PARAMETER", WRITE_PAIR},    // Parameter that is used in computing a value from measured data. Name is the name
331                                                    // of the calculation, parameter is the name of the parameter used in the calculation
332                                                    // and value is the value of the parameter.
333                                                    
334        {"TARGET_TYPE",        WRITE_STRINGIFY},    // The type of target being measured, e.g. IT8.7/1, IT8.7/3, user defined, etc.
335                                                   
336        {"COLORANT",           WRITE_STRINGIFY},    // Identifies the colorant(s) used in creating the target.
337                                                   
338        {"TABLE_DESCRIPTOR",   WRITE_STRINGIFY},    // Describes the purpose or contents of a data table.
339                                                   
340        {"TABLE_NAME",         WRITE_STRINGIFY}     // Provides a short name for a data table.
341 };
342
343 #define NUMPREDEFINEDPROPS (sizeof(PredefinedProperties)/sizeof(PROPERTY))
344
345
346 // Predefined sample types on dataset
347 static const char* PredefinedSampleID[] = {
348         "SAMPLE_ID",      // Identifies sample that data represents
349         "STRING",         // Identifies label, or other non-machine readable value.
350                           // Value must begin and end with a " symbol
351
352         "CMYK_C",         // Cyan component of CMYK data expressed as a percentage
353         "CMYK_M",         // Magenta component of CMYK data expressed as a percentage
354         "CMYK_Y",         // Yellow component of CMYK data expressed as a percentage
355         "CMYK_K",         // Black component of CMYK data expressed as a percentage
356         "D_RED",          // Red filter density
357         "D_GREEN",        // Green filter density
358         "D_BLUE",         // Blue filter density
359         "D_VIS",          // Visual filter density
360         "D_MAJOR_FILTER", // Major filter d ensity
361         "RGB_R",          // Red component of RGB data
362         "RGB_G",          // Green component of RGB data
363         "RGB_B",          // Blue com ponent of RGB data
364         "SPECTRAL_NM",    // Wavelength of measurement expressed in nanometers
365         "SPECTRAL_PCT",   // Percentage reflectance/transmittance
366         "SPECTRAL_DEC",   // Reflectance/transmittance
367         "XYZ_X",          // X component of tristimulus data
368         "XYZ_Y",          // Y component of tristimulus data
369         "XYZ_Z",          // Z component of tristimulus data
370         "XYY_X",          // x component of chromaticity data
371         "XYY_Y",          // y component of chromaticity data
372         "XYY_CAPY",       // Y component of tristimulus data
373         "LAB_L",          // L* component of Lab data
374         "LAB_A",          // a* component of Lab data
375         "LAB_B",          // b* component of Lab data
376         "LAB_C",          // C*ab component of Lab data
377         "LAB_H",          // hab component of Lab data
378         "LAB_DE",         // CIE dE
379         "LAB_DE_94",      // CIE dE using CIE 94
380         "LAB_DE_CMC",     // dE using CMC
381         "LAB_DE_2000",    // CIE dE using CIE DE 2000
382         "MEAN_DE",        // Mean Delta E (LAB_DE) of samples compared to batch average
383                           // (Used for data files for ANSI IT8.7/1 and IT8.7/2 targets)
384         "STDEV_X",        // Standard deviation of X (tristimulus data)
385         "STDEV_Y",        // Standard deviation of Y (tristimulus data)
386         "STDEV_Z",        // Standard deviation of Z (tristimulus data)
387         "STDEV_L",        // Standard deviation of L*
388         "STDEV_A",        // Standard deviation of a*
389         "STDEV_B",        // Standard deviation of b*
390         "STDEV_DE",       // Standard deviation of CIE dE
391         "CHI_SQD_PAR"};   // The average of the standard deviations of L*, a* and b*. It is
392                           // used to derive an estimate of the chi-squared parameter which is
393                           // recommended as the predictor of the variability of dE
394
395 #define NUMPREDEFINEDSAMPLEID (sizeof(PredefinedSampleID)/sizeof(char *))
396
397 //Forward declaration of some internal functions
398 static void* AllocChunk(cmsIT8* it8, cmsUInt32Number size);
399
400 static
401 string* StringAlloc(cmsIT8* it8, int max)
402 {
403     string* s = (string*) AllocChunk(it8, sizeof(string));
404     if (s == NULL) return NULL;
405
406     s->it8 = it8;
407     s->max = max;
408     s->len = 0;
409     s->begin = (char*) AllocChunk(it8, s->max);
410
411     return s;
412 }
413
414 static
415 void StringClear(string* s)
416 {
417     s->len = 0;
418 }
419
420 static
421 void StringAppend(string* s, char c)
422 {
423     if (s->len + 1 >= s->max)
424     {
425         char* new_ptr;
426
427         s->max *= 10;
428         new_ptr = (char*) AllocChunk(s->it8, s->max);
429         if (new_ptr != NULL && s->begin != NULL)
430             memcpy(new_ptr, s->begin, s->len);
431
432         s->begin = new_ptr;
433     }
434
435     if (s->begin != NULL)
436     {
437         s->begin[s->len++] = c;
438         s->begin[s->len] = 0;
439     }
440 }
441
442 static
443 char* StringPtr(string* s)
444 {
445     return s->begin;
446 }
447
448 static
449 void StringCat(string* s, const char* c)
450 {
451     while (*c)
452     {
453         StringAppend(s, *c);
454         c++;
455     }
456 }
457
458
459 // Checks whatever c is a separator
460 static
461 cmsBool isseparator(int c)
462 {
463     return (c == ' ') || (c == '\t');
464 }
465
466 // Checks whatever c is a valid identifier char
467 static
468 cmsBool ismiddle(int c)
469 {
470    return (!isseparator(c) && (c != '#') && (c !='\"') && (c != '\'') && (c > 32) && (c < 127));
471 }
472
473 // Checks whatsever c is a valid identifier middle char.
474 static
475 cmsBool isidchar(int c)
476 {
477    return isalnum(c) || ismiddle(c);
478 }
479
480 // Checks whatsever c is a valid identifier first char.
481 static
482 cmsBool isfirstidchar(int c)
483 {
484      return c != '-' && !isdigit(c) && ismiddle(c);
485 }
486
487 // Guess whether the supplied path looks like an absolute path
488 static
489 cmsBool isabsolutepath(const char *path)
490 {
491     char ThreeChars[4];
492
493     if(path == NULL)
494         return FALSE;
495     if (path[0] == 0)
496         return FALSE;
497
498     strncpy(ThreeChars, path, 3);
499     ThreeChars[3] = 0;
500
501     if(ThreeChars[0] == DIR_CHAR)
502         return TRUE;
503
504 #ifdef  CMS_IS_WINDOWS_
505     if (isalpha((int) ThreeChars[0]) && ThreeChars[1] == ':')
506         return TRUE;
507 #endif
508     return FALSE;
509 }
510
511
512 // Makes a file path based on a given reference path
513 // NOTE: this function doesn't check if the path exists or even if it's legal
514 static
515 cmsBool BuildAbsolutePath(const char *relPath, const char *basePath, char *buffer, cmsUInt32Number MaxLen)
516 {
517     char *tail;
518     cmsUInt32Number len;
519
520     // Already absolute?
521     if (isabsolutepath(relPath)) {
522
523         memcpy(buffer, relPath, MaxLen);
524         buffer[MaxLen-1] = 0;
525         return TRUE;
526     }
527
528     // No, search for last
529     memcpy(buffer, basePath, MaxLen);
530     buffer[MaxLen-1] = 0;
531
532     tail = strrchr(buffer, DIR_CHAR);
533     if (tail == NULL) return FALSE;    // Is not absolute and has no separators??
534
535     len = (cmsUInt32Number) (tail - buffer);
536     if (len >= MaxLen) return FALSE;
537
538     // No need to assure zero terminator over here
539     strncpy(tail + 1, relPath, MaxLen - len);
540
541     return TRUE;
542 }
543
544
545 // Make sure no exploit is being even tried
546 static
547 const char* NoMeta(const char* str)
548 {
549     if (strchr(str, '%') != NULL)
550         return "**** CORRUPTED FORMAT STRING ***";
551
552     return str;
553 }
554
555 // Syntax error
556 static
557 cmsBool SynError(cmsIT8* it8, const char *Txt, ...)
558 {
559     char Buffer[256], ErrMsg[1024];
560     va_list args;
561
562     va_start(args, Txt);
563     vsnprintf(Buffer, 255, Txt, args);
564     Buffer[255] = 0;
565     va_end(args);
566
567     snprintf(ErrMsg, 1023, "%s: Line %d, %s", it8->FileStack[it8 ->IncludeSP]->FileName, it8->lineno, Buffer);
568     ErrMsg[1023] = 0;
569     it8->sy = SSYNERROR;
570     cmsSignalError(it8 ->ContextID, cmsERROR_CORRUPTION_DETECTED, "%s", ErrMsg);
571     return FALSE;
572 }
573
574 // Check if current symbol is same as specified. issue an error else.
575 static
576 cmsBool Check(cmsIT8* it8, SYMBOL sy, const char* Err)
577 {
578         if (it8 -> sy != sy)
579                 return SynError(it8, NoMeta(Err));
580         return TRUE;
581 }
582
583 // Read Next character from stream
584 static
585 void NextCh(cmsIT8* it8)
586 {
587     if (it8 -> FileStack[it8 ->IncludeSP]->Stream) {
588
589         it8 ->ch = fgetc(it8 ->FileStack[it8 ->IncludeSP]->Stream);
590
591         if (feof(it8 -> FileStack[it8 ->IncludeSP]->Stream))  {
592
593             if (it8 ->IncludeSP > 0) {
594
595                 fclose(it8 ->FileStack[it8->IncludeSP--]->Stream);
596                 it8 -> ch = ' ';                            // Whitespace to be ignored
597
598             } else
599                 it8 ->ch = 0;   // EOF
600         }
601     }
602     else {
603         it8->ch = *it8->Source;
604         if (it8->ch) it8->Source++;
605     }
606 }
607
608
609 // Try to see if current identifier is a keyword, if so return the referred symbol
610 static
611 SYMBOL BinSrchKey(const char *id, int NumKeys, const KEYWORD* TabKeys)
612 {
613     int l = 1;
614     int r = NumKeys;
615     int x, res;
616
617     while (r >= l)
618     {
619         x = (l+r)/2;
620         res = cmsstrcasecmp(id, TabKeys[x-1].id);
621         if (res == 0) return TabKeys[x-1].sy;
622         if (res < 0) r = x - 1;
623         else l = x + 1;
624     }
625
626     return SUNDEFINED;
627 }
628
629
630 // 10 ^n
631 static
632 cmsFloat64Number xpow10(int n)
633 {
634     return pow(10, (cmsFloat64Number) n);
635 }
636
637
638 //  Reads a Real number, tries to follow from integer number
639 static
640 void ReadReal(cmsIT8* it8, cmsInt32Number inum)
641 {
642     it8->dnum = (cmsFloat64Number)inum;
643
644     while (isdigit(it8->ch)) {
645
646         it8->dnum = (cmsFloat64Number)it8->dnum * 10.0 + (cmsFloat64Number)(it8->ch - '0');
647         NextCh(it8);
648     }
649
650     if (it8->ch == '.') {        // Decimal point
651
652         cmsFloat64Number frac = 0.0;      // fraction
653         int prec = 0;                     // precision
654
655         NextCh(it8);               // Eats dec. point
656
657         while (isdigit(it8->ch)) {
658
659             frac = frac * 10.0 + (cmsFloat64Number)(it8->ch - '0');
660             prec++;
661             NextCh(it8);
662         }
663
664         it8->dnum = it8->dnum + (frac / xpow10(prec));
665     }
666
667     // Exponent, example 34.00E+20
668     if (toupper(it8->ch) == 'E') {
669
670         cmsInt32Number e;
671         cmsInt32Number sgn;
672
673         NextCh(it8); sgn = 1;
674
675         if (it8->ch == '-') {
676
677             sgn = -1; NextCh(it8);
678         }
679         else
680             if (it8->ch == '+') {
681
682                 sgn = +1;
683                 NextCh(it8);
684             }
685
686         e = 0;
687         while (isdigit(it8->ch)) {
688
689             cmsInt32Number digit = (it8->ch - '0');
690
691             if ((cmsFloat64Number)e * 10.0 + (cmsFloat64Number)digit < (cmsFloat64Number)+2147483647.0)
692                 e = e * 10 + digit;
693
694             NextCh(it8);
695         }
696
697         e = sgn*e;
698         it8->dnum = it8->dnum * xpow10(e);
699     }
700 }
701
702 // Parses a float number
703 // This can not call directly atof because it uses locale dependent
704 // parsing, while CCMX files always use . as decimal separator
705 static
706 cmsFloat64Number ParseFloatNumber(const char *Buffer)
707 {
708     cmsFloat64Number dnum = 0.0;
709     int sign = 1;
710
711     // keep safe
712     if (Buffer == NULL) return 0.0;
713
714     if (*Buffer == '-' || *Buffer == '+') {
715
716         sign = (*Buffer == '-') ? -1 : 1;
717         Buffer++;
718     }
719
720
721     while (*Buffer && isdigit((int)*Buffer)) {
722
723         dnum = dnum * 10.0 + (*Buffer - '0');
724         if (*Buffer) Buffer++;
725     }
726
727     if (*Buffer == '.') {
728
729         cmsFloat64Number frac = 0.0;      // fraction
730         int prec = 0;                     // precision
731
732         if (*Buffer) Buffer++;
733
734         while (*Buffer && isdigit((int)*Buffer)) {
735
736             frac = frac * 10.0 + (*Buffer - '0');
737             prec++;
738             if (*Buffer) Buffer++;
739         }
740
741         dnum = dnum + (frac / xpow10(prec));
742     }
743
744     // Exponent, example 34.00E+20
745     if (*Buffer && toupper(*Buffer) == 'E') {
746
747         int e;
748         int sgn;
749
750         if (*Buffer) Buffer++;
751         sgn = 1;
752
753         if (*Buffer == '-') {
754
755             sgn = -1;
756             if (*Buffer) Buffer++;
757         }
758         else
759             if (*Buffer == '+') {
760
761                 sgn = +1;
762                 if (*Buffer) Buffer++;
763             }
764
765         e = 0;
766         while (*Buffer && isdigit((int)*Buffer)) {
767
768             cmsInt32Number digit = (*Buffer - '0');
769
770             if ((cmsFloat64Number)e * 10.0 + digit < (cmsFloat64Number)+2147483647.0)
771                 e = e * 10 + digit;
772
773             if (*Buffer) Buffer++;
774         }
775
776         e = sgn*e;
777         dnum = dnum * xpow10(e);
778     }
779
780     return sign * dnum;
781 }
782
783
784 // Reads a string, special case to avoid infinite recursion on .include
785 static
786 void InStringSymbol(cmsIT8* it8)
787 {
788     while (isseparator(it8->ch))
789         NextCh(it8);
790
791     if (it8->ch == '\'' || it8->ch == '\"')
792     {
793         int sng;
794
795         sng = it8->ch;
796         StringClear(it8->str);
797
798         NextCh(it8);
799
800         while (it8->ch != sng) {
801
802             if (it8->ch == '\n' || it8->ch == '\r' || it8->ch == 0) break;
803             else {
804                 StringAppend(it8->str, (char)it8->ch);
805                 NextCh(it8);
806             }
807         }
808
809         it8->sy = SSTRING;
810         NextCh(it8);        
811     }
812     else
813         SynError(it8, "String expected");
814
815 }
816
817 // Reads next symbol
818 static
819 void InSymbol(cmsIT8* it8)
820 {
821     SYMBOL key;
822     
823     do {
824
825         while (isseparator(it8->ch))
826             NextCh(it8);
827
828         if (isfirstidchar(it8->ch)) {          // Identifier
829
830             StringClear(it8->id);
831
832             do {
833
834                 StringAppend(it8->id, (char) it8->ch);
835
836                 NextCh(it8);
837
838             } while (isidchar(it8->ch));
839
840
841             key = BinSrchKey(StringPtr(it8->id),
842                     it8->IsCUBE ? NUMKEYS_CUBE : NUMKEYS_IT8,
843                     it8->IsCUBE ? TabKeysCUBE : TabKeysIT8);
844             if (key == SUNDEFINED) it8->sy = SIDENT;
845             else it8->sy = key;
846
847         }
848         else                         // Is a number?
849             if (isdigit(it8->ch) || it8->ch == '.' || it8->ch == '-' || it8->ch == '+')
850             {
851                 int sign = 1;
852
853                 if (it8->ch == '-') {
854                     sign = -1;
855                     NextCh(it8);
856                 }
857
858                 it8->inum = 0;
859                 it8->sy   = SINUM;
860
861                 if (it8->ch == '0') {          // 0xnnnn (Hexa) or 0bnnnn (Binary)
862
863                     NextCh(it8);
864                     if (toupper(it8->ch) == 'X') {
865
866                         int j;
867
868                         NextCh(it8);
869                         while (isxdigit(it8->ch))
870                         {
871                             it8->ch = toupper(it8->ch);
872                             if (it8->ch >= 'A' && it8->ch <= 'F')  j = it8->ch -'A'+10;
873                             else j = it8->ch - '0';
874
875                             if ((cmsFloat64Number) it8->inum * 16.0 + (cmsFloat64Number) j > (cmsFloat64Number)+2147483647.0)
876                             {
877                                 SynError(it8, "Invalid hexadecimal number");
878                                 it8->sy = SEOF;
879                                 return;
880                             }
881
882                             it8->inum = it8->inum * 16 + j;
883                             NextCh(it8);
884                         }
885                         return;
886                     }
887
888                     if (toupper(it8->ch) == 'B') {  // Binary
889
890                         int j;
891
892                         NextCh(it8);
893                         while (it8->ch == '0' || it8->ch == '1')
894                         {
895                             j = it8->ch - '0';
896
897                             if ((cmsFloat64Number) it8->inum * 2.0 + j > (cmsFloat64Number)+2147483647.0)
898                             {
899                                 SynError(it8, "Invalid binary number");
900                                 it8->sy = SEOF;
901                                 return;
902                             }
903
904                             it8->inum = it8->inum * 2 + j;
905                             NextCh(it8);
906                         }
907                         return;
908                     }
909                 }
910
911
912                 while (isdigit(it8->ch)) {
913
914                     cmsInt32Number digit = (it8->ch - '0');
915
916                     if ((cmsFloat64Number) it8->inum * 10.0 + (cmsFloat64Number) digit > (cmsFloat64Number) +2147483647.0) {
917                         ReadReal(it8, it8->inum);
918                         it8->sy = SDNUM;
919                         it8->dnum *= sign;
920                         return;
921                     }
922
923                     it8->inum = it8->inum * 10 + digit;
924                     NextCh(it8);
925                 }
926
927                 if (it8->ch == '.') {
928
929                     ReadReal(it8, it8->inum);
930                     it8->sy = SDNUM;
931                     it8->dnum *= sign;
932                     return;
933                 }
934
935                 it8 -> inum *= sign;
936
937                 // Special case. Numbers followed by letters are taken as identifiers
938
939                 if (isidchar(it8 ->ch)) {
940
941                     char buffer[127];
942
943                     if (it8 ->sy == SINUM) {
944
945                         snprintf(buffer, sizeof(buffer), "%d", it8->inum);
946                     }
947                     else {
948
949                         snprintf(buffer, sizeof(buffer), it8 ->DoubleFormatter, it8->dnum);
950                     }
951
952                     StringClear(it8->id);
953                     StringCat(it8->id, buffer);
954
955                     do {
956
957                         StringAppend(it8->id, (char) it8->ch);
958
959                         NextCh(it8);
960
961                     } while (isidchar(it8->ch));
962
963                     it8->sy = SIDENT;
964                 }
965                 return;
966
967             }
968             else
969                 switch ((int) it8->ch) {
970         
971         // Eof stream markers
972         case '\x1a':
973         case 0:
974         case -1:
975             it8->sy = SEOF;
976             break;
977
978
979         // Next line
980         case '\r':
981             NextCh(it8);
982             if (it8->ch == '\n')
983                 NextCh(it8);
984             it8->sy = SEOLN;
985             it8->lineno++;
986             break;
987
988         case '\n':
989             NextCh(it8);
990             it8->sy = SEOLN;
991             it8->lineno++;
992             break;
993
994         // Comment
995         case '#':
996             NextCh(it8);
997             while (it8->ch && it8->ch != '\n' && it8->ch != '\r')
998                 NextCh(it8);
999
1000             it8->sy = SCOMMENT;
1001             break;
1002
1003         // String.
1004         case '\'':
1005         case '\"':
1006             InStringSymbol(it8);
1007             break;
1008
1009
1010         default:
1011             SynError(it8, "Unrecognized character: 0x%x", it8 ->ch);
1012             it8->sy = SEOF;
1013             return;
1014             }
1015
1016     } while (it8->sy == SCOMMENT);
1017
1018     // Handle the include special token
1019
1020     if (it8 -> sy == SINCLUDE) {
1021
1022                 FILECTX* FileNest;
1023
1024                 if(it8 -> IncludeSP >= (MAXINCLUDE-1)) {
1025
1026                     SynError(it8, "Too many recursion levels");
1027                     it8->sy = SEOF;
1028                     return;
1029                 }
1030
1031                 InStringSymbol(it8);
1032                 if (!Check(it8, SSTRING, "Filename expected"))
1033                 {
1034                     it8->sy = SEOF;
1035                     return;
1036                 }
1037
1038                 FileNest = it8 -> FileStack[it8 -> IncludeSP + 1];
1039                 if(FileNest == NULL) {
1040
1041                     FileNest = it8 ->FileStack[it8 -> IncludeSP + 1] = (FILECTX*)AllocChunk(it8, sizeof(FILECTX));
1042                     if (FileNest == NULL) {
1043                         SynError(it8, "Out of memory");
1044                         it8->sy = SEOF;
1045                         return;
1046                     }
1047                 }
1048
1049                 if (BuildAbsolutePath(StringPtr(it8->str),
1050                                       it8->FileStack[it8->IncludeSP]->FileName,
1051                                       FileNest->FileName, cmsMAX_PATH-1) == FALSE) {
1052                     SynError(it8, "File path too long");
1053                     it8->sy = SEOF;
1054                     return;
1055                 }
1056
1057                 FileNest->Stream = fopen(FileNest->FileName, "rte");
1058                 if (FileNest->Stream == NULL) {
1059
1060                         SynError(it8, "File %s not found", FileNest->FileName);
1061                         it8->sy = SEOF;
1062                         return;
1063                 }
1064                 it8->IncludeSP++;
1065
1066                 it8 ->ch = ' ';
1067                 InSymbol(it8);
1068     }
1069
1070 }
1071
1072 // Checks end of line separator
1073 static
1074 cmsBool CheckEOLN(cmsIT8* it8)
1075 {
1076         if (!Check(it8, SEOLN, "Expected separator")) return FALSE;
1077         while (it8 -> sy == SEOLN)
1078                         InSymbol(it8);
1079         return TRUE;
1080
1081 }
1082
1083 // Skip a symbol
1084
1085 static
1086 void Skip(cmsIT8* it8, SYMBOL sy)
1087 {
1088         if (it8->sy == sy && it8->sy != SEOF)
1089                         InSymbol(it8);
1090 }
1091
1092
1093 // Skip multiple EOLN
1094 static
1095 void SkipEOLN(cmsIT8* it8)
1096 {
1097     while (it8->sy == SEOLN) {
1098              InSymbol(it8);
1099     }
1100 }
1101
1102
1103 // Returns a string holding current value
1104 static
1105 cmsBool GetVal(cmsIT8* it8, char* Buffer, cmsUInt32Number max, const char* ErrorTitle)
1106 {
1107     switch (it8->sy) {
1108
1109     case SEOLN:   // Empty value
1110                   Buffer[0]=0;
1111                   break;
1112     case SIDENT:  strncpy(Buffer, StringPtr(it8->id), max);
1113                   Buffer[max-1]=0;
1114                   break;
1115     case SINUM:   snprintf(Buffer, max, "%d", it8 -> inum); break;
1116     case SDNUM:   snprintf(Buffer, max, it8->DoubleFormatter, it8 -> dnum); break;
1117     case SSTRING: strncpy(Buffer, StringPtr(it8->str), max);
1118                   Buffer[max-1] = 0;
1119                   break;
1120
1121
1122     default:
1123          return SynError(it8, "%s", ErrorTitle);
1124     }
1125
1126     Buffer[max] = 0;
1127     return TRUE;
1128 }
1129
1130 // ---------------------------------------------------------- Table
1131
1132 static
1133 TABLE* GetTable(cmsIT8* it8)
1134 {
1135    if ((it8 -> nTable >= it8 ->TablesCount)) {
1136
1137            SynError(it8, "Table %d out of sequence", it8 -> nTable);
1138            return it8 -> Tab;
1139    }
1140
1141    return it8 ->Tab + it8 ->nTable;
1142 }
1143
1144 // ---------------------------------------------------------- Memory management
1145
1146
1147 // Frees an allocator and owned memory
1148 void CMSEXPORT cmsIT8Free(cmsHANDLE hIT8)
1149 {
1150    cmsIT8* it8 = (cmsIT8*) hIT8;
1151
1152     if (it8 == NULL)
1153         return;
1154
1155     if (it8->MemorySink) {
1156
1157         OWNEDMEM* p;
1158         OWNEDMEM* n;
1159
1160         for (p = it8->MemorySink; p != NULL; p = n) {
1161
1162             n = p->Next;
1163             if (p->Ptr) _cmsFree(it8 ->ContextID, p->Ptr);
1164             _cmsFree(it8 ->ContextID, p);
1165         }
1166     }
1167
1168     if (it8->MemoryBlock)
1169         _cmsFree(it8 ->ContextID, it8->MemoryBlock);
1170
1171     _cmsFree(it8 ->ContextID, it8);
1172 }
1173
1174
1175 // Allocates a chunk of data, keep linked list
1176 static
1177 void* AllocBigBlock(cmsIT8* it8, cmsUInt32Number size)
1178 {
1179     OWNEDMEM* ptr1;
1180     void* ptr = _cmsMallocZero(it8->ContextID, size);
1181
1182     if (ptr != NULL) {
1183
1184         ptr1 = (OWNEDMEM*) _cmsMallocZero(it8 ->ContextID, sizeof(OWNEDMEM));
1185
1186         if (ptr1 == NULL) {
1187
1188             _cmsFree(it8 ->ContextID, ptr);
1189             return NULL;
1190         }
1191
1192         ptr1-> Ptr        = ptr;
1193         ptr1-> Next       = it8 -> MemorySink;
1194         it8 -> MemorySink = ptr1;
1195     }
1196
1197     return ptr;
1198 }
1199
1200
1201 // Suballocator.
1202 static
1203 void* AllocChunk(cmsIT8* it8, cmsUInt32Number size)
1204 {
1205     cmsUInt32Number Free = it8 ->Allocator.BlockSize - it8 ->Allocator.Used;
1206     cmsUInt8Number* ptr;
1207
1208     size = _cmsALIGNMEM(size);
1209
1210     if (size > Free) {
1211
1212         if (it8 -> Allocator.BlockSize == 0)
1213
1214                 it8 -> Allocator.BlockSize = 20*1024;
1215         else
1216                 it8 ->Allocator.BlockSize *= 2;
1217
1218         if (it8 ->Allocator.BlockSize < size)
1219                 it8 ->Allocator.BlockSize = size;
1220
1221         it8 ->Allocator.Used = 0;
1222         it8 ->Allocator.Block = (cmsUInt8Number*) AllocBigBlock(it8, it8 ->Allocator.BlockSize);       
1223     }
1224
1225     if (it8->Allocator.Block == NULL)
1226         return NULL;
1227
1228     ptr = it8 ->Allocator.Block + it8 ->Allocator.Used;
1229     it8 ->Allocator.Used += size;
1230
1231     return (void*) ptr;
1232
1233 }
1234
1235
1236 // Allocates a string
1237 static
1238 char *AllocString(cmsIT8* it8, const char* str)
1239 {
1240     cmsUInt32Number Size = (cmsUInt32Number) strlen(str)+1;
1241     char *ptr;
1242
1243
1244     ptr = (char *) AllocChunk(it8, Size);
1245     if (ptr) memcpy(ptr, str, Size-1);
1246
1247     return ptr;
1248 }
1249
1250 // Searches through linked list
1251
1252 static
1253 cmsBool IsAvailableOnList(KEYVALUE* p, const char* Key, const char* Subkey, KEYVALUE** LastPtr)
1254 {
1255     if (LastPtr) *LastPtr = p;
1256
1257     for (;  p != NULL; p = p->Next) {
1258
1259         if (LastPtr) *LastPtr = p;
1260
1261         if (*Key != '#') { // Comments are ignored
1262
1263             if (cmsstrcasecmp(Key, p->Keyword) == 0)
1264                 break;
1265         }
1266     }
1267
1268     if (p == NULL)
1269         return FALSE;
1270
1271     if (Subkey == 0)
1272         return TRUE;
1273
1274     for (; p != NULL; p = p->NextSubkey) {
1275
1276         if (p ->Subkey == NULL) continue;
1277
1278         if (LastPtr) *LastPtr = p;
1279
1280         if (cmsstrcasecmp(Subkey, p->Subkey) == 0)
1281             return TRUE;
1282     }
1283
1284     return FALSE;
1285 }
1286
1287
1288
1289 // Add a property into a linked list
1290 static
1291 KEYVALUE* AddToList(cmsIT8* it8, KEYVALUE** Head, const char *Key, const char *Subkey, const char* xValue, WRITEMODE WriteAs)
1292 {
1293     KEYVALUE* p;
1294     KEYVALUE* last;
1295
1296
1297     // Check if property is already in list
1298
1299     if (IsAvailableOnList(*Head, Key, Subkey, &p)) {
1300
1301         // This may work for editing properties
1302
1303         if (cmsstrcasecmp(Key, "NUMBER_OF_FIELDS") == 0 ||
1304             cmsstrcasecmp(Key, "NUMBER_OF_SETS") == 0) {
1305
1306             SynError(it8, "duplicate key <%s>", Key);
1307             return NULL;
1308         }
1309     }
1310     else {
1311
1312         last = p;
1313
1314         // Allocate the container
1315         p = (KEYVALUE*) AllocChunk(it8, sizeof(KEYVALUE));
1316         if (p == NULL)
1317         {
1318             SynError(it8, "AddToList: out of memory");
1319             return NULL;
1320         }
1321
1322         // Store name and value
1323         p->Keyword = AllocString(it8, Key);
1324         p->Subkey = (Subkey == NULL) ? NULL : AllocString(it8, Subkey);
1325
1326         // Keep the container in our list
1327         if (*Head == NULL) {
1328             *Head = p;
1329         }
1330         else
1331         {
1332             if (Subkey != NULL && last != NULL) {
1333
1334                 last->NextSubkey = p;
1335
1336                 // If Subkey is not null, then last is the last property with the same key,
1337                 // but not necessarily is the last property in the list, so we need to move
1338                 // to the actual list end
1339                 while (last->Next != NULL)
1340                          last = last->Next;
1341             }
1342
1343             if (last != NULL) last->Next = p;
1344         }
1345
1346         p->Next    = NULL;
1347         p->NextSubkey = NULL;
1348     }
1349
1350     p->WriteAs = WriteAs;
1351
1352     if (xValue != NULL) {
1353
1354         p->Value   = AllocString(it8, xValue);
1355     }
1356     else {
1357         p->Value   = NULL;
1358     }
1359
1360     return p;
1361 }
1362
1363 static
1364 KEYVALUE* AddAvailableProperty(cmsIT8* it8, const char* Key, WRITEMODE as)
1365 {
1366     return AddToList(it8, &it8->ValidKeywords, Key, NULL, NULL, as);
1367 }
1368
1369
1370 static
1371 KEYVALUE* AddAvailableSampleID(cmsIT8* it8, const char* Key)
1372 {
1373     return AddToList(it8, &it8->ValidSampleID, Key, NULL, NULL, WRITE_UNCOOKED);
1374 }
1375
1376
1377 static
1378 void AllocTable(cmsIT8* it8)
1379 {
1380     TABLE* t;
1381
1382     t = it8 ->Tab + it8 ->TablesCount;
1383
1384     t->HeaderList = NULL;
1385     t->DataFormat = NULL;
1386     t->Data       = NULL;
1387
1388     it8 ->TablesCount++;
1389 }
1390
1391
1392 cmsInt32Number CMSEXPORT cmsIT8SetTable(cmsHANDLE  IT8, cmsUInt32Number nTable)
1393 {
1394      cmsIT8* it8 = (cmsIT8*) IT8;
1395
1396      if (nTable >= it8 ->TablesCount) {
1397
1398          if (nTable == it8 ->TablesCount) {
1399
1400              AllocTable(it8);
1401          }
1402          else {
1403              SynError(it8, "Table %d is out of sequence", nTable);
1404              return -1;
1405          }
1406      }
1407
1408      it8 ->nTable = nTable;
1409
1410      return (cmsInt32Number) nTable;
1411 }
1412
1413
1414
1415 // Init an empty container
1416 cmsHANDLE  CMSEXPORT cmsIT8Alloc(cmsContext ContextID)
1417 {
1418     cmsIT8* it8;
1419     cmsUInt32Number i;
1420
1421     it8 = (cmsIT8*) _cmsMallocZero(ContextID, sizeof(cmsIT8));
1422     if (it8 == NULL) return NULL;
1423
1424     AllocTable(it8);
1425
1426     it8->MemoryBlock = NULL;
1427     it8->MemorySink  = NULL;
1428
1429     it8->IsCUBE = FALSE;
1430
1431     it8 ->nTable = 0;
1432
1433     it8->ContextID = ContextID;
1434     it8->Allocator.Used = 0;
1435     it8->Allocator.Block = NULL;
1436     it8->Allocator.BlockSize = 0;
1437
1438     it8->ValidKeywords = NULL;
1439     it8->ValidSampleID = NULL;
1440
1441     it8 -> sy = SUNDEFINED;
1442     it8 -> ch = ' ';
1443     it8 -> Source = NULL;
1444     it8 -> inum = 0;
1445     it8 -> dnum = 0.0;
1446
1447     it8->FileStack[0] = (FILECTX*)AllocChunk(it8, sizeof(FILECTX));
1448     it8->IncludeSP   = 0;
1449     it8 -> lineno = 1;
1450
1451     it8->id = StringAlloc(it8, MAXSTR);
1452     it8->str = StringAlloc(it8, MAXSTR);
1453
1454     strcpy(it8->DoubleFormatter, DEFAULT_DBL_FORMAT);
1455     cmsIT8SetSheetType((cmsHANDLE) it8, "CGATS.17");
1456
1457     // Initialize predefined properties & data
1458
1459     for (i=0; i < NUMPREDEFINEDPROPS; i++)
1460             AddAvailableProperty(it8, PredefinedProperties[i].id, PredefinedProperties[i].as);
1461
1462     for (i=0; i < NUMPREDEFINEDSAMPLEID; i++)
1463             AddAvailableSampleID(it8, PredefinedSampleID[i]);
1464
1465
1466    return (cmsHANDLE) it8;
1467 }
1468
1469
1470 const char* CMSEXPORT cmsIT8GetSheetType(cmsHANDLE hIT8)
1471 {
1472         return GetTable((cmsIT8*) hIT8)->SheetType;
1473 }
1474
1475 cmsBool CMSEXPORT cmsIT8SetSheetType(cmsHANDLE hIT8, const char* Type)
1476 {
1477         TABLE* t = GetTable((cmsIT8*) hIT8);
1478
1479         strncpy(t ->SheetType, Type, MAXSTR-1);
1480         t ->SheetType[MAXSTR-1] = 0;
1481         return TRUE;
1482 }
1483
1484 cmsBool CMSEXPORT cmsIT8SetComment(cmsHANDLE hIT8, const char* Val)
1485 {
1486     cmsIT8* it8 = (cmsIT8*) hIT8;
1487
1488     if (!Val) return FALSE;
1489     if (!*Val) return FALSE;
1490
1491     return AddToList(it8, &GetTable(it8)->HeaderList, "# ", NULL, Val, WRITE_UNCOOKED) != NULL;
1492 }
1493
1494 // Sets a property
1495 cmsBool CMSEXPORT cmsIT8SetPropertyStr(cmsHANDLE hIT8, const char* Key, const char *Val)
1496 {
1497     cmsIT8* it8 = (cmsIT8*) hIT8;
1498
1499     if (!Val) return FALSE;
1500     if (!*Val) return FALSE;
1501
1502     return AddToList(it8, &GetTable(it8)->HeaderList, Key, NULL, Val, WRITE_STRINGIFY) != NULL;
1503 }
1504
1505 cmsBool CMSEXPORT cmsIT8SetPropertyDbl(cmsHANDLE hIT8, const char* cProp, cmsFloat64Number Val)
1506 {
1507     cmsIT8* it8 = (cmsIT8*) hIT8;
1508     char Buffer[1024];
1509
1510     snprintf(Buffer, 1023, it8->DoubleFormatter, Val);
1511
1512     return AddToList(it8, &GetTable(it8)->HeaderList, cProp, NULL, Buffer, WRITE_UNCOOKED) != NULL;
1513 }
1514
1515 cmsBool CMSEXPORT cmsIT8SetPropertyHex(cmsHANDLE hIT8, const char* cProp, cmsUInt32Number Val)
1516 {
1517     cmsIT8* it8 = (cmsIT8*) hIT8;
1518     char Buffer[1024];
1519
1520     snprintf(Buffer, 1023, "%u", Val);
1521
1522     return AddToList(it8, &GetTable(it8)->HeaderList, cProp, NULL, Buffer, WRITE_HEXADECIMAL) != NULL;
1523 }
1524
1525 cmsBool CMSEXPORT cmsIT8SetPropertyUncooked(cmsHANDLE hIT8, const char* Key, const char* Buffer)
1526 {
1527     cmsIT8* it8 = (cmsIT8*) hIT8;
1528
1529     return AddToList(it8, &GetTable(it8)->HeaderList, Key, NULL, Buffer, WRITE_UNCOOKED) != NULL;
1530 }
1531
1532 cmsBool CMSEXPORT cmsIT8SetPropertyMulti(cmsHANDLE hIT8, const char* Key, const char* SubKey, const char *Buffer)
1533 {
1534     cmsIT8* it8 = (cmsIT8*) hIT8;
1535
1536     return AddToList(it8, &GetTable(it8)->HeaderList, Key, SubKey, Buffer, WRITE_PAIR) != NULL;
1537 }
1538
1539 // Gets a property
1540 const char* CMSEXPORT cmsIT8GetProperty(cmsHANDLE hIT8, const char* Key)
1541 {
1542     cmsIT8* it8 = (cmsIT8*) hIT8;
1543     KEYVALUE* p;
1544
1545     if (IsAvailableOnList(GetTable(it8) -> HeaderList, Key, NULL, &p))
1546     {
1547         return p -> Value;
1548     }
1549     return NULL;
1550 }
1551
1552
1553 cmsFloat64Number CMSEXPORT cmsIT8GetPropertyDbl(cmsHANDLE hIT8, const char* cProp)
1554 {
1555     const char *v = cmsIT8GetProperty(hIT8, cProp);
1556
1557     if (v == NULL) return 0.0;
1558
1559     return ParseFloatNumber(v);
1560 }
1561
1562 const char* CMSEXPORT cmsIT8GetPropertyMulti(cmsHANDLE hIT8, const char* Key, const char *SubKey)
1563 {
1564     cmsIT8* it8 = (cmsIT8*) hIT8;
1565     KEYVALUE* p;
1566
1567     if (IsAvailableOnList(GetTable(it8) -> HeaderList, Key, SubKey, &p)) {
1568         return p -> Value;
1569     }
1570     return NULL;
1571 }
1572
1573 // ----------------------------------------------------------------- Datasets
1574
1575 // A safe atoi that returns 0 when NULL input is given
1576 static
1577 cmsInt32Number satoi(const char* b)
1578 {
1579     int n;
1580
1581     if (b == NULL) return 0;
1582
1583     n = atoi(b);
1584     if (n > 0x7fffffffL) return 0x7fffffffL;
1585     if (n < -0x7ffffffeL) return -0x7ffffffeL;
1586
1587     return (cmsInt32Number)n;
1588 }
1589
1590
1591 static
1592 cmsBool AllocateDataFormat(cmsIT8* it8)
1593 {
1594     TABLE* t = GetTable(it8);
1595
1596     if (t -> DataFormat) return TRUE;    // Already allocated
1597
1598     t -> nSamples  = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
1599
1600     if (t -> nSamples <= 0) {
1601
1602         SynError(it8, "AllocateDataFormat: Unknown NUMBER_OF_FIELDS");
1603         return FALSE;        
1604         }
1605
1606     t -> DataFormat = (char**) AllocChunk (it8, ((cmsUInt32Number) t->nSamples + 1) * sizeof(char *));
1607     if (t->DataFormat == NULL) {
1608
1609         SynError(it8, "AllocateDataFormat: Unable to allocate dataFormat array");
1610         return FALSE;
1611     }
1612
1613     return TRUE;
1614 }
1615
1616 static
1617 const char *GetDataFormat(cmsIT8* it8, int n)
1618 {
1619     TABLE* t = GetTable(it8);
1620
1621     if (t->DataFormat)
1622         return t->DataFormat[n];
1623
1624     return NULL;
1625 }
1626
1627 static
1628 cmsBool SetDataFormat(cmsIT8* it8, int n, const char *label)
1629 {
1630     TABLE* t = GetTable(it8);
1631
1632     if (!t->DataFormat) {
1633
1634         if (!AllocateDataFormat(it8))
1635             return FALSE;
1636     }
1637
1638     if (n > t -> nSamples) {
1639         SynError(it8, "More than NUMBER_OF_FIELDS fields.");
1640         return FALSE;
1641     }
1642
1643     if (t->DataFormat) {
1644         t->DataFormat[n] = AllocString(it8, label);
1645         if (t->DataFormat[n] == NULL) return FALSE;
1646     }
1647
1648     return TRUE;
1649 }
1650
1651
1652 cmsBool CMSEXPORT cmsIT8SetDataFormat(cmsHANDLE  h, int n, const char *Sample)
1653 {
1654     cmsIT8* it8 = (cmsIT8*)h;
1655     return SetDataFormat(it8, n, Sample);
1656 }
1657
1658 // Convert to binary
1659 static
1660 const char* satob(const char* v)
1661 {
1662     cmsUInt32Number x;
1663     static char buf[33];
1664     char *s = buf + 33;
1665     
1666     if (v == NULL) return "0";
1667     
1668     x = atoi(v);
1669     *--s = 0;
1670     if (!x) *--s = '0';
1671     for (; x; x /= 2) *--s = '0' + x%2;
1672     
1673     return s;
1674 }
1675
1676
1677 static
1678 cmsBool AllocateDataSet(cmsIT8* it8)
1679 {
1680     TABLE* t = GetTable(it8);
1681
1682     if (t -> Data) return TRUE;    // Already allocated
1683
1684     t-> nSamples   = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
1685     t-> nPatches   = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));
1686
1687     if (t -> nSamples < 0 || t->nSamples > 0x7ffe || t->nPatches < 0 || t->nPatches > 0x7ffe)
1688     {
1689         SynError(it8, "AllocateDataSet: too much data");
1690         return FALSE;
1691     }
1692     else {
1693         // Some dumb analizers warns of possible overflow here, just take a look couple of lines above.
1694         t->Data = (char**)AllocChunk(it8, ((cmsUInt32Number)t->nSamples + 1) * ((cmsUInt32Number)t->nPatches + 1) * sizeof(char*));
1695         if (t->Data == NULL) {
1696
1697             SynError(it8, "AllocateDataSet: Unable to allocate data array");
1698             return FALSE;
1699         }
1700     }
1701
1702     return TRUE;
1703 }
1704
1705 static
1706 char* GetData(cmsIT8* it8, int nSet, int nField)
1707 {
1708     TABLE* t = GetTable(it8);
1709     int nSamples    = t -> nSamples;
1710     int nPatches    = t -> nPatches;
1711
1712     if (nSet < 0 || nSet >= nPatches || nField < 0 || nField >= nSamples)
1713         return NULL;
1714
1715     if (!t->Data) return NULL;
1716     return t->Data [nSet * nSamples + nField];
1717 }
1718
1719 static
1720 cmsBool SetData(cmsIT8* it8, int nSet, int nField, const char *Val)
1721 {
1722     TABLE* t = GetTable(it8);
1723
1724     if (!t->Data) {
1725         if (!AllocateDataSet(it8)) return FALSE;
1726     }
1727
1728     if (!t->Data) return FALSE;
1729
1730     if (nSet > t -> nPatches || nSet < 0) {
1731
1732             return SynError(it8, "Patch %d out of range, there are %d patches", nSet, t -> nPatches);
1733     }
1734
1735     if (nField > t ->nSamples || nField < 0) {
1736             return SynError(it8, "Sample %d out of range, there are %d samples", nField, t ->nSamples);
1737
1738     }
1739
1740     t->Data [nSet * t -> nSamples + nField] = AllocString(it8, Val);
1741     return TRUE;
1742 }
1743
1744
1745 // --------------------------------------------------------------- File I/O
1746
1747
1748 // Writes a string to file
1749 static
1750 void WriteStr(SAVESTREAM* f, const char *str)
1751 {
1752     cmsUInt32Number len;
1753
1754     if (str == NULL)
1755         str = " ";
1756
1757     // Length to write
1758     len = (cmsUInt32Number) strlen(str);
1759     f ->Used += len;
1760
1761
1762     if (f ->stream) {   // Should I write it to a file?
1763
1764         if (fwrite(str, 1, len, f->stream) != len) {
1765             cmsSignalError(0, cmsERROR_WRITE, "Write to file error in CGATS parser");
1766             return;
1767         }
1768
1769     }
1770     else {  // Or to a memory block?
1771
1772         if (f ->Base) {   // Am I just counting the bytes?
1773
1774             if (f ->Used > f ->Max) {
1775
1776                  cmsSignalError(0, cmsERROR_WRITE, "Write to memory overflows in CGATS parser");
1777                  return;
1778             }
1779
1780             memmove(f ->Ptr, str, len);
1781             f->Ptr += len;
1782         }
1783
1784     }
1785 }
1786
1787
1788 // Write formatted
1789
1790 static
1791 void Writef(SAVESTREAM* f, const char* frm, ...)
1792 {
1793     char Buffer[4096];
1794     va_list args;
1795
1796     va_start(args, frm);
1797     vsnprintf(Buffer, 4095, frm, args);
1798     Buffer[4095] = 0;
1799     WriteStr(f, Buffer);
1800     va_end(args);
1801
1802 }
1803
1804 // Writes full header
1805 static
1806 void WriteHeader(cmsIT8* it8, SAVESTREAM* fp)
1807 {
1808     KEYVALUE* p;
1809     TABLE* t = GetTable(it8);
1810
1811     // Writes the type
1812     WriteStr(fp, t->SheetType);
1813     WriteStr(fp, "\n");
1814
1815     for (p = t->HeaderList; (p != NULL); p = p->Next)
1816     {
1817         if (*p ->Keyword == '#') {
1818
1819             char* Pt;
1820
1821             WriteStr(fp, "#\n# ");
1822             for (Pt = p ->Value; *Pt; Pt++) {
1823
1824
1825                 Writef(fp, "%c", *Pt);
1826
1827                 if (*Pt == '\n') {
1828                     WriteStr(fp, "# ");
1829                 }
1830             }
1831
1832             WriteStr(fp, "\n#\n");
1833             continue;
1834         }
1835
1836
1837         if (!IsAvailableOnList(it8-> ValidKeywords, p->Keyword, NULL, NULL)) {
1838
1839 #ifdef CMS_STRICT_CGATS
1840             WriteStr(fp, "KEYWORD\t\"");
1841             WriteStr(fp, p->Keyword);
1842             WriteStr(fp, "\"\n");
1843 #endif
1844
1845             AddAvailableProperty(it8, p->Keyword, WRITE_UNCOOKED);
1846         }
1847
1848         WriteStr(fp, p->Keyword);
1849         if (p->Value) {
1850
1851             switch (p ->WriteAs) {
1852
1853             case WRITE_UNCOOKED:
1854                     Writef(fp, "\t%s", p ->Value);
1855                     break;
1856
1857             case WRITE_STRINGIFY:
1858                     Writef(fp, "\t\"%s\"", p->Value );
1859                     break;
1860
1861             case WRITE_HEXADECIMAL:
1862                     Writef(fp, "\t0x%X", satoi(p ->Value));
1863                     break;
1864
1865             case WRITE_BINARY:
1866                     Writef(fp, "\t0b%s", satob(p ->Value));
1867                     break;
1868
1869             case WRITE_PAIR:
1870                     Writef(fp, "\t\"%s,%s\"", p->Subkey, p->Value);
1871                     break;
1872
1873             default: SynError(it8, "Unknown write mode %d", p ->WriteAs);
1874                      return;
1875             }
1876         }
1877
1878         WriteStr (fp, "\n");
1879     }
1880
1881 }
1882
1883
1884 // Writes the data format
1885 static
1886 void WriteDataFormat(SAVESTREAM* fp, cmsIT8* it8)
1887 {
1888     int i, nSamples;
1889     TABLE* t = GetTable(it8);
1890
1891     if (!t -> DataFormat) return;
1892
1893        WriteStr(fp, "BEGIN_DATA_FORMAT\n");
1894        WriteStr(fp, " ");
1895        nSamples = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_FIELDS"));
1896
1897        if (nSamples <= t->nSamples) {
1898
1899            for (i = 0; i < nSamples; i++) {
1900
1901                WriteStr(fp, t->DataFormat[i]);
1902                WriteStr(fp, ((i == (nSamples - 1)) ? "\n" : "\t"));
1903            }
1904        }
1905
1906        WriteStr (fp, "END_DATA_FORMAT\n");
1907 }
1908
1909
1910 // Writes data array
1911 static
1912 void WriteData(SAVESTREAM* fp, cmsIT8* it8)
1913 {
1914        int  i, j, nPatches;
1915        TABLE* t = GetTable(it8);
1916
1917        if (!t->Data) return;
1918
1919        WriteStr (fp, "BEGIN_DATA\n");
1920
1921        nPatches = satoi(cmsIT8GetProperty(it8, "NUMBER_OF_SETS"));
1922
1923        if (nPatches <= t->nPatches) {
1924
1925            for (i = 0; i < nPatches; i++) {
1926
1927                WriteStr(fp, " ");
1928
1929                for (j = 0; j < t->nSamples; j++) {
1930
1931                    char* ptr = t->Data[i * t->nSamples + j];
1932
1933                    if (ptr == NULL) WriteStr(fp, "\"\"");
1934                    else {
1935                        // If value contains whitespace, enclose within quote
1936
1937                        if (strchr(ptr, ' ') != NULL) {
1938
1939                            WriteStr(fp, "\"");
1940                            WriteStr(fp, ptr);
1941                            WriteStr(fp, "\"");
1942                        }
1943                        else
1944                            WriteStr(fp, ptr);
1945                    }
1946
1947                    WriteStr(fp, ((j == (t->nSamples - 1)) ? "\n" : "\t"));
1948                }
1949            }
1950        }
1951        WriteStr (fp, "END_DATA\n");
1952 }
1953
1954
1955
1956 // Saves whole file
1957 cmsBool CMSEXPORT cmsIT8SaveToFile(cmsHANDLE hIT8, const char* cFileName)
1958 {
1959     SAVESTREAM sd;
1960     cmsUInt32Number i;
1961     cmsIT8* it8 = (cmsIT8*) hIT8;
1962
1963     memset(&sd, 0, sizeof(sd));
1964
1965     sd.stream = fopen(cFileName, "wte");
1966     if (!sd.stream) return FALSE;
1967
1968     for (i=0; i < it8 ->TablesCount; i++) {
1969
1970         TABLE* t;
1971
1972         if (cmsIT8SetTable(hIT8, i) < 0) goto Error;
1973         
1974         /**
1975         * Check for wrong data
1976         */
1977         t = GetTable(it8);
1978         if (t->Data == NULL) goto Error;
1979         if (t->DataFormat == NULL) goto Error;
1980
1981         WriteHeader(it8, &sd);
1982         WriteDataFormat(&sd, it8);
1983         WriteData(&sd, it8);
1984     }
1985
1986     if (fclose(sd.stream) != 0) return FALSE;
1987     return TRUE;
1988
1989 Error:
1990     fclose(sd.stream);
1991     return FALSE;
1992
1993 }
1994
1995
1996 // Saves to memory
1997 cmsBool CMSEXPORT cmsIT8SaveToMem(cmsHANDLE hIT8, void *MemPtr, cmsUInt32Number* BytesNeeded)
1998 {
1999     SAVESTREAM sd;
2000     cmsUInt32Number i;
2001     cmsIT8* it8 = (cmsIT8*) hIT8;
2002
2003     memset(&sd, 0, sizeof(sd));
2004
2005     sd.stream = NULL;
2006     sd.Base   = (cmsUInt8Number*) MemPtr;
2007     sd.Ptr    = sd.Base;
2008
2009     sd.Used = 0;
2010
2011     if (sd.Base && (*BytesNeeded > 0)) {
2012
2013         sd.Max = (*BytesNeeded) - 1;     // Write to memory?
2014     }
2015     else
2016         sd.Max  = 0;                // Just counting the needed bytes
2017
2018     for (i=0; i < it8 ->TablesCount; i++) {
2019
2020         cmsIT8SetTable(hIT8, i);
2021         WriteHeader(it8, &sd);
2022         WriteDataFormat(&sd, it8);
2023         WriteData(&sd, it8);
2024     }
2025
2026     sd.Used++;  // The \0 at the very end
2027
2028     if (sd.Base)
2029         *sd.Ptr = 0;
2030
2031     *BytesNeeded = sd.Used;
2032
2033     return TRUE;
2034 }
2035
2036
2037 // -------------------------------------------------------------- Higher level parsing
2038
2039 static
2040 cmsBool DataFormatSection(cmsIT8* it8)
2041 {
2042     int iField = 0;
2043     TABLE* t = GetTable(it8);
2044
2045     InSymbol(it8);   // Eats "BEGIN_DATA_FORMAT"
2046     CheckEOLN(it8);
2047
2048     while (it8->sy != SEND_DATA_FORMAT &&
2049         it8->sy != SEOLN &&
2050         it8->sy != SEOF &&
2051         it8->sy != SSYNERROR)  {
2052
2053             if (it8->sy != SIDENT) {
2054
2055                 return SynError(it8, "Sample type expected");
2056             }
2057
2058             if (!SetDataFormat(it8, iField, StringPtr(it8->id))) return FALSE;
2059             iField++;
2060
2061             InSymbol(it8);
2062             SkipEOLN(it8);
2063        }
2064
2065        SkipEOLN(it8);
2066        Skip(it8, SEND_DATA_FORMAT);
2067        SkipEOLN(it8);
2068
2069        if (iField != t ->nSamples) {
2070            SynError(it8, "Count mismatch. NUMBER_OF_FIELDS was %d, found %d\n", t ->nSamples, iField);
2071
2072
2073        }
2074
2075        return TRUE;
2076 }
2077
2078
2079
2080 static
2081 cmsBool DataSection (cmsIT8* it8)
2082 {
2083     int  iField = 0;
2084     int  iSet   = 0;
2085     char Buffer[256];
2086     TABLE* t = GetTable(it8);
2087
2088     InSymbol(it8);   // Eats "BEGIN_DATA"
2089     CheckEOLN(it8);
2090
2091     if (!t->Data) {
2092         if (!AllocateDataSet(it8)) return FALSE;
2093     }
2094
2095     while (it8->sy != SEND_DATA && it8->sy != SEOF)
2096     {
2097         if (iField >= t -> nSamples) {
2098             iField = 0;
2099             iSet++;
2100
2101         }
2102
2103         if (it8->sy != SEND_DATA && it8->sy != SEOF) {
2104
2105             switch (it8->sy)
2106             {
2107
2108             // To keep very long data
2109             case SIDENT:  
2110                 if (!SetData(it8, iSet, iField, StringPtr(it8->id)))
2111                     return FALSE;
2112                 break;
2113
2114             case SSTRING:
2115                 if (!SetData(it8, iSet, iField, StringPtr(it8->str)))
2116                     return FALSE;
2117                 break;
2118
2119             default:
2120
2121             if (!GetVal(it8, Buffer, 255, "Sample data expected"))
2122                 return FALSE;
2123
2124             if (!SetData(it8, iSet, iField, Buffer))
2125                 return FALSE;
2126             }
2127
2128             iField++;
2129
2130             InSymbol(it8);
2131             SkipEOLN(it8);
2132         }
2133     }
2134
2135     SkipEOLN(it8);
2136     Skip(it8, SEND_DATA);
2137     SkipEOLN(it8);
2138
2139     // Check for data completion.
2140
2141     if ((iSet+1) != t -> nPatches)
2142         return SynError(it8, "Count mismatch. NUMBER_OF_SETS was %d, found %d\n", t ->nPatches, iSet+1);
2143
2144     return TRUE;
2145 }
2146
2147
2148
2149
2150 static
2151 cmsBool HeaderSection(cmsIT8* it8)
2152 {
2153     char VarName[MAXID];
2154     char Buffer[MAXSTR];
2155     KEYVALUE* Key;
2156
2157         while (it8->sy != SEOF &&
2158                it8->sy != SSYNERROR &&
2159                it8->sy != SBEGIN_DATA_FORMAT &&
2160                it8->sy != SBEGIN_DATA) {
2161
2162
2163         switch (it8 -> sy) {
2164
2165         case SKEYWORD:
2166                 InSymbol(it8);
2167                 if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
2168                 if (!AddAvailableProperty(it8, Buffer, WRITE_UNCOOKED)) return FALSE;
2169                 InSymbol(it8);
2170                 break;
2171
2172
2173         case SDATA_FORMAT_ID:
2174                 InSymbol(it8);
2175                 if (!GetVal(it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
2176                 if (!AddAvailableSampleID(it8, Buffer)) return FALSE;
2177                 InSymbol(it8);
2178                 break;
2179
2180
2181         case SIDENT:
2182             strncpy(VarName, StringPtr(it8->id), MAXID - 1);
2183             VarName[MAXID - 1] = 0;
2184
2185             if (!IsAvailableOnList(it8->ValidKeywords, VarName, NULL, &Key)) {
2186
2187 #ifdef CMS_STRICT_CGATS
2188                 return SynError(it8, "Undefined keyword '%s'", VarName);
2189 #else
2190                 Key = AddAvailableProperty(it8, VarName, WRITE_UNCOOKED);
2191                 if (Key == NULL) return FALSE;
2192 #endif
2193             }
2194
2195             InSymbol(it8);
2196             if (!GetVal(it8, Buffer, MAXSTR - 1, "Property data expected")) return FALSE;
2197
2198             if (Key->WriteAs != WRITE_PAIR) {
2199                 AddToList(it8, &GetTable(it8)->HeaderList, VarName, NULL, Buffer,
2200                     (it8->sy == SSTRING) ? WRITE_STRINGIFY : WRITE_UNCOOKED);
2201             }
2202             else {
2203                 const char *Subkey;
2204                 char *Nextkey;
2205                 if (it8->sy != SSTRING)
2206                     return SynError(it8, "Invalid value '%s' for property '%s'.", Buffer, VarName);
2207
2208                 // chop the string as a list of "subkey, value" pairs, using ';' as a separator
2209                 for (Subkey = Buffer; Subkey != NULL; Subkey = Nextkey)
2210                 {
2211                     char *Value, *temp;
2212
2213                     //  identify token pair boundary
2214                     Nextkey = (char*)strchr(Subkey, ';');
2215                     if (Nextkey)
2216                         *Nextkey++ = '\0';
2217
2218                     // for each pair, split the subkey and the value
2219                     Value = (char*)strrchr(Subkey, ',');
2220                     if (Value == NULL)
2221                         return SynError(it8, "Invalid value for property '%s'.", VarName);
2222
2223                     // gobble the spaces before the coma, and the coma itself
2224                     temp = Value++;
2225                     do *temp-- = '\0'; while (temp >= Subkey && *temp == ' ');
2226
2227                     // gobble any space at the right
2228                     temp = Value + strlen(Value) - 1;
2229                     while (*temp == ' ') *temp-- = '\0';
2230
2231                     // trim the strings from the left
2232                     Subkey += strspn(Subkey, " ");
2233                     Value += strspn(Value, " ");
2234
2235                     if (Subkey[0] == 0 || Value[0] == 0)
2236                         return SynError(it8, "Invalid value for property '%s'.", VarName);
2237                     AddToList(it8, &GetTable(it8)->HeaderList, VarName, Subkey, Value, WRITE_PAIR);
2238                 }
2239             }
2240
2241             InSymbol(it8);
2242             break;
2243
2244
2245         case SEOLN: break;
2246
2247         default:
2248                 return SynError(it8, "expected keyword or identifier");
2249         }
2250
2251     SkipEOLN(it8);
2252     }
2253
2254     return TRUE;
2255
2256 }
2257
2258
2259 static
2260 void ReadType(cmsIT8* it8, char* SheetTypePtr)
2261 {
2262     cmsInt32Number cnt = 0;
2263
2264     // First line is a very special case.
2265
2266     while (isseparator(it8->ch))
2267             NextCh(it8);
2268
2269     while (it8->ch != '\r' && it8 ->ch != '\n' && it8->ch != '\t' && it8 -> ch != 0) {
2270
2271         if (cnt++ < MAXSTR) 
2272             *SheetTypePtr++= (char) it8 ->ch;
2273         NextCh(it8);
2274     }
2275
2276     *SheetTypePtr = 0;
2277 }
2278
2279
2280 static
2281 cmsBool ParseIT8(cmsIT8* it8, cmsBool nosheet)
2282 {
2283     char* SheetTypePtr = it8 ->Tab[0].SheetType;
2284
2285     if (nosheet == 0) {
2286         ReadType(it8, SheetTypePtr);
2287     }
2288
2289     InSymbol(it8);
2290
2291     SkipEOLN(it8);
2292
2293     while (it8-> sy != SEOF &&
2294            it8-> sy != SSYNERROR) {
2295
2296             switch (it8 -> sy) {
2297
2298             case SBEGIN_DATA_FORMAT:
2299                     if (!DataFormatSection(it8)) return FALSE;
2300                     break;
2301
2302             case SBEGIN_DATA:
2303
2304                     if (!DataSection(it8)) return FALSE;
2305
2306                     if (it8 -> sy != SEOF) {
2307
2308                             AllocTable(it8);
2309                             it8 ->nTable = it8 ->TablesCount - 1;
2310
2311                             // Read sheet type if present. We only support identifier and string.
2312                             // <ident> <eoln> is a type string
2313                             // anything else, is not a type string
2314                             if (nosheet == 0) {
2315
2316                                 if (it8 ->sy == SIDENT) {
2317
2318                                     // May be a type sheet or may be a prop value statement. We cannot use insymbol in
2319                                     // this special case...
2320                                      while (isseparator(it8->ch))
2321                                          NextCh(it8);
2322
2323                                      // If a newline is found, then this is a type string
2324                                     if (it8 ->ch == '\n' || it8->ch == '\r') {
2325
2326                                          cmsIT8SetSheetType(it8, StringPtr(it8 ->id));
2327                                          InSymbol(it8);
2328                                     }
2329                                     else
2330                                     {
2331                                         // It is not. Just continue
2332                                         cmsIT8SetSheetType(it8, "");
2333                                     }
2334                                 }
2335                                 else
2336                                     // Validate quoted strings
2337                                     if (it8 ->sy == SSTRING) {
2338                                         cmsIT8SetSheetType(it8, StringPtr(it8 ->str));
2339                                         InSymbol(it8);
2340                                     }
2341                            }
2342
2343                     }
2344                     break;
2345
2346             case SEOLN:
2347                     SkipEOLN(it8);
2348                     break;
2349
2350             default:
2351                     if (!HeaderSection(it8)) return FALSE;
2352            }
2353
2354     }
2355
2356     return (it8 -> sy != SSYNERROR);
2357 }
2358
2359
2360
2361 // Init useful pointers
2362
2363 static
2364 void CookPointers(cmsIT8* it8)
2365 {
2366     int idField, i;
2367     char* Fld;
2368     cmsUInt32Number j;
2369     cmsUInt32Number nOldTable = it8->nTable;
2370
2371     for (j = 0; j < it8->TablesCount; j++) {
2372
2373         TABLE* t = it8->Tab + j;
2374
2375         t->SampleID = 0;
2376         it8->nTable = j;
2377
2378         for (idField = 0; idField < t->nSamples; idField++)
2379         {
2380             if (t->DataFormat == NULL) {
2381                 SynError(it8, "Undefined DATA_FORMAT");
2382                 return;
2383             }
2384
2385             Fld = t->DataFormat[idField];
2386             if (!Fld) continue;
2387
2388
2389             if (cmsstrcasecmp(Fld, "SAMPLE_ID") == 0) {
2390
2391                 t->SampleID = idField;
2392             }
2393
2394             // "LABEL" is an extension. It keeps references to forward tables
2395
2396             if ((cmsstrcasecmp(Fld, "LABEL") == 0) || Fld[0] == '$') {
2397
2398                 // Search for table references...
2399                 for (i = 0; i < t->nPatches; i++) {
2400
2401                     char* Label = GetData(it8, i, idField);
2402
2403                     if (Label) {
2404
2405                         cmsUInt32Number k;
2406
2407                         // This is the label, search for a table containing
2408                         // this property
2409
2410                         for (k = 0; k < it8->TablesCount; k++) {
2411
2412                             TABLE* Table = it8->Tab + k;
2413                             KEYVALUE* p;
2414
2415                             if (IsAvailableOnList(Table->HeaderList, Label, NULL, &p)) {
2416
2417                                 // Available, keep type and table
2418                                 char Buffer[256];
2419
2420                                 char* Type = p->Value;
2421                                 int  nTable = (int)k;
2422
2423                                 snprintf(Buffer, 255, "%s %d %s", Label, nTable, Type);
2424
2425                                 SetData(it8, i, idField, Buffer);
2426                             }
2427                         }
2428                     }
2429                 }
2430             }
2431         }
2432     }
2433
2434     it8->nTable = nOldTable;
2435 }
2436
2437 // Try to infere if the file is a CGATS/IT8 file at all. Read first line
2438 // that should be something like some printable characters plus a \n
2439 // returns 0 if this is not like a CGATS, or an integer otherwise. This integer is the number of words in first line?
2440 static
2441 int IsMyBlock(const cmsUInt8Number* Buffer, cmsUInt32Number n)
2442 {
2443     int words = 1, space = 0, quot = 0;
2444     cmsUInt32Number i;
2445
2446     if (n < 10) return 0;   // Too small
2447
2448     if (n > 132)
2449         n = 132;
2450
2451     for (i = 1; i < n; i++) {
2452
2453         switch(Buffer[i])
2454         {
2455         case '\n':
2456         case '\r':
2457             return ((quot == 1) || (words > 2)) ? 0 : words;
2458         case '\t':
2459         case ' ':
2460             if(!quot && !space)
2461                 space = 1;
2462             break;
2463         case '\"':
2464             quot = !quot;
2465             break;
2466         default:
2467             if (Buffer[i] < 32) return 0;
2468             if (Buffer[i] > 127) return 0;
2469             words += space;
2470             space = 0;
2471             break;
2472         }
2473     }
2474
2475     return 0;
2476 }
2477
2478
2479 static
2480 cmsBool IsMyFile(const char* FileName)
2481 {
2482    FILE *fp;
2483    cmsUInt32Number Size;
2484    cmsUInt8Number Ptr[133];
2485
2486    fp = fopen(FileName, "rte");
2487    if (!fp) {
2488        cmsSignalError(0, cmsERROR_FILE, "File '%s' not found", FileName);
2489        return FALSE;
2490    }
2491
2492    Size = (cmsUInt32Number) fread(Ptr, 1, 132, fp);
2493
2494    if (fclose(fp) != 0)
2495        return FALSE;
2496
2497    Ptr[Size] = '\0';
2498
2499    return IsMyBlock(Ptr, Size);
2500 }
2501
2502 // ---------------------------------------------------------- Exported routines
2503
2504
2505 cmsHANDLE  CMSEXPORT cmsIT8LoadFromMem(cmsContext ContextID, const void *Ptr, cmsUInt32Number len)
2506 {
2507     cmsHANDLE hIT8;
2508     cmsIT8*  it8;
2509     int type;
2510
2511     _cmsAssert(Ptr != NULL);
2512     _cmsAssert(len != 0);
2513
2514     type = IsMyBlock((const cmsUInt8Number*)Ptr, len);
2515     if (type == 0) return NULL;
2516
2517     hIT8 = cmsIT8Alloc(ContextID);
2518     if (!hIT8) return NULL;
2519
2520     it8 = (cmsIT8*) hIT8;
2521     it8 ->MemoryBlock = (char*) _cmsMalloc(ContextID, len + 1);
2522     if (it8->MemoryBlock == NULL)
2523     {
2524         cmsIT8Free(hIT8);
2525         return NULL;
2526     }
2527
2528     strncpy(it8 ->MemoryBlock, (const char*) Ptr, len);
2529     it8 ->MemoryBlock[len] = 0;
2530
2531     strncpy(it8->FileStack[0]->FileName, "", cmsMAX_PATH-1);
2532     it8-> Source = it8 -> MemoryBlock;
2533
2534     if (!ParseIT8(it8, type-1)) {
2535
2536         cmsIT8Free(hIT8);
2537         return NULL;
2538     }
2539
2540     CookPointers(it8);
2541     it8 ->nTable = 0;
2542
2543     _cmsFree(ContextID, it8->MemoryBlock);
2544     it8 -> MemoryBlock = NULL;
2545
2546     return hIT8;
2547
2548
2549 }
2550
2551
2552 cmsHANDLE  CMSEXPORT cmsIT8LoadFromFile(cmsContext ContextID, const char* cFileName)
2553 {
2554
2555      cmsHANDLE hIT8;
2556      cmsIT8*  it8;
2557      int type;
2558
2559      _cmsAssert(cFileName != NULL);
2560
2561      type = IsMyFile(cFileName);
2562      if (type == 0) return NULL;
2563
2564      hIT8 = cmsIT8Alloc(ContextID);
2565      it8 = (cmsIT8*) hIT8;
2566      if (!hIT8) return NULL;
2567
2568
2569      it8 ->FileStack[0]->Stream = fopen(cFileName, "rte");
2570
2571      if (!it8 ->FileStack[0]->Stream) {
2572          cmsIT8Free(hIT8);
2573          return NULL;
2574      }
2575
2576
2577     strncpy(it8->FileStack[0]->FileName, cFileName, cmsMAX_PATH-1);
2578     it8->FileStack[0]->FileName[cmsMAX_PATH-1] = 0;
2579
2580     if (!ParseIT8(it8, type-1)) {
2581
2582             fclose(it8 ->FileStack[0]->Stream);
2583             cmsIT8Free(hIT8);
2584             return NULL;
2585     }
2586
2587     CookPointers(it8);
2588     it8 ->nTable = 0;
2589
2590     if (fclose(it8 ->FileStack[0]->Stream)!= 0) {
2591             cmsIT8Free(hIT8);
2592             return NULL;
2593     }
2594
2595     return hIT8;
2596
2597 }
2598
2599 int CMSEXPORT cmsIT8EnumDataFormat(cmsHANDLE hIT8, char ***SampleNames)
2600 {
2601     cmsIT8* it8 = (cmsIT8*) hIT8;
2602     TABLE* t;
2603
2604     _cmsAssert(hIT8 != NULL);
2605
2606     t = GetTable(it8);
2607
2608     if (SampleNames)
2609         *SampleNames = t -> DataFormat;
2610     return t -> nSamples;
2611 }
2612
2613
2614 cmsUInt32Number CMSEXPORT cmsIT8EnumProperties(cmsHANDLE hIT8, char ***PropertyNames)
2615 {
2616     cmsIT8* it8 = (cmsIT8*) hIT8;
2617     KEYVALUE* p;
2618     cmsUInt32Number n;
2619     char **Props;
2620     TABLE* t;
2621
2622     _cmsAssert(hIT8 != NULL);
2623
2624     t = GetTable(it8);
2625
2626     // Pass#1 - count properties
2627
2628     n = 0;
2629     for (p = t -> HeaderList;  p != NULL; p = p->Next) {
2630         n++;
2631     }
2632
2633
2634     Props = (char**)AllocChunk(it8, sizeof(char*) * n);
2635     if (Props != NULL) {
2636
2637         // Pass#2 - Fill pointers
2638         n = 0;
2639         for (p = t->HeaderList; p != NULL; p = p->Next) {
2640             Props[n++] = p->Keyword;
2641         }
2642
2643     }
2644     *PropertyNames = Props;
2645
2646     return n;
2647 }
2648
2649 cmsUInt32Number CMSEXPORT cmsIT8EnumPropertyMulti(cmsHANDLE hIT8, const char* cProp, const char ***SubpropertyNames)
2650 {
2651     cmsIT8* it8 = (cmsIT8*) hIT8;
2652     KEYVALUE *p, *tmp;
2653     cmsUInt32Number n;
2654     const char **Props;
2655     TABLE* t;
2656
2657     _cmsAssert(hIT8 != NULL);
2658
2659
2660     t = GetTable(it8);
2661
2662     if(!IsAvailableOnList(t->HeaderList, cProp, NULL, &p)) {
2663         *SubpropertyNames = 0;
2664         return 0;
2665     }
2666
2667     // Pass#1 - count properties
2668
2669     n = 0;
2670     for (tmp = p;  tmp != NULL; tmp = tmp->NextSubkey) {
2671         if(tmp->Subkey != NULL)
2672             n++;
2673     }
2674
2675
2676     Props = (const char **) AllocChunk(it8, sizeof(char *) * n);
2677     if (Props != NULL) {
2678
2679         // Pass#2 - Fill pointers
2680         n = 0;
2681         for (tmp = p; tmp != NULL; tmp = tmp->NextSubkey) {
2682             if (tmp->Subkey != NULL)
2683                 Props[n++] = p->Subkey;
2684         }
2685     }
2686
2687     *SubpropertyNames = Props;
2688     return n;
2689 }
2690
2691 static
2692 int LocatePatch(cmsIT8* it8, const char* cPatch)
2693 {
2694     int i;
2695     const char *data;
2696     TABLE* t = GetTable(it8);
2697
2698     for (i=0; i < t-> nPatches; i++) {
2699
2700         data = GetData(it8, i, t->SampleID);
2701
2702         if (data != NULL) {
2703
2704                 if (cmsstrcasecmp(data, cPatch) == 0)
2705                         return i;
2706                 }
2707         }
2708
2709         // SynError(it8, "Couldn't find patch '%s'\n", cPatch);
2710         return -1;
2711 }
2712
2713
2714 static
2715 int LocateEmptyPatch(cmsIT8* it8)
2716 {
2717     int i;
2718     const char *data;
2719     TABLE* t = GetTable(it8);
2720
2721     for (i=0; i < t-> nPatches; i++) {
2722
2723         data = GetData(it8, i, t->SampleID);
2724
2725         if (data == NULL)
2726             return i;
2727
2728     }
2729
2730     return -1;
2731 }
2732
2733 static
2734 int LocateSample(cmsIT8* it8, const char* cSample)
2735 {
2736     int i;
2737     const char *fld;
2738     TABLE* t = GetTable(it8);
2739
2740     for (i=0; i < t->nSamples; i++) {
2741
2742         fld = GetDataFormat(it8, i);
2743         if (fld != NULL) {
2744             if (cmsstrcasecmp(fld, cSample) == 0)
2745                 return i;
2746         }
2747     }
2748
2749     return -1;
2750
2751 }
2752
2753
2754 int CMSEXPORT cmsIT8FindDataFormat(cmsHANDLE hIT8, const char* cSample)
2755 {
2756     cmsIT8* it8 = (cmsIT8*) hIT8;
2757
2758     _cmsAssert(hIT8 != NULL);
2759
2760     return LocateSample(it8, cSample);
2761 }
2762
2763
2764
2765 const char* CMSEXPORT cmsIT8GetDataRowCol(cmsHANDLE hIT8, int row, int col)
2766 {
2767     cmsIT8* it8 = (cmsIT8*) hIT8;
2768
2769     _cmsAssert(hIT8 != NULL);
2770
2771     return GetData(it8, row, col);
2772 }
2773
2774
2775 cmsFloat64Number CMSEXPORT cmsIT8GetDataRowColDbl(cmsHANDLE hIT8, int row, int col)
2776 {
2777     const char* Buffer;
2778
2779     Buffer = cmsIT8GetDataRowCol(hIT8, row, col);
2780
2781     if (Buffer == NULL) return 0.0;
2782
2783     return ParseFloatNumber(Buffer);
2784 }
2785
2786
2787 cmsBool CMSEXPORT cmsIT8SetDataRowCol(cmsHANDLE hIT8, int row, int col, const char* Val)
2788 {
2789     cmsIT8* it8 = (cmsIT8*) hIT8;
2790
2791     _cmsAssert(hIT8 != NULL);
2792
2793     return SetData(it8, row, col, Val);
2794 }
2795
2796
2797 cmsBool CMSEXPORT cmsIT8SetDataRowColDbl(cmsHANDLE hIT8, int row, int col, cmsFloat64Number Val)
2798 {
2799     cmsIT8* it8 = (cmsIT8*) hIT8;
2800     char Buff[256];
2801
2802     _cmsAssert(hIT8 != NULL);
2803
2804     snprintf(Buff, 255, it8->DoubleFormatter, Val);
2805
2806     return SetData(it8, row, col, Buff);
2807 }
2808
2809
2810
2811 const char* CMSEXPORT cmsIT8GetData(cmsHANDLE hIT8, const char* cPatch, const char* cSample)
2812 {
2813     cmsIT8* it8 = (cmsIT8*) hIT8;
2814     int iField, iSet;
2815
2816     _cmsAssert(hIT8 != NULL);
2817
2818     iField = LocateSample(it8, cSample);
2819     if (iField < 0) {
2820         return NULL;
2821     }
2822
2823     iSet = LocatePatch(it8, cPatch);
2824     if (iSet < 0) {
2825             return NULL;
2826     }
2827
2828     return GetData(it8, iSet, iField);
2829 }
2830
2831
2832 cmsFloat64Number CMSEXPORT cmsIT8GetDataDbl(cmsHANDLE  it8, const char* cPatch, const char* cSample)
2833 {
2834     const char* Buffer;
2835
2836     Buffer = cmsIT8GetData(it8, cPatch, cSample);
2837
2838     return ParseFloatNumber(Buffer);
2839 }
2840
2841
2842
2843 cmsBool CMSEXPORT cmsIT8SetData(cmsHANDLE hIT8, const char* cPatch, const char* cSample, const char *Val)
2844 {
2845     cmsIT8* it8 = (cmsIT8*) hIT8;
2846     int iField, iSet;
2847     TABLE* t;
2848
2849     _cmsAssert(hIT8 != NULL);
2850
2851     t = GetTable(it8);
2852
2853     iField = LocateSample(it8, cSample);
2854
2855     if (iField < 0)
2856         return FALSE;
2857
2858     if (t-> nPatches == 0) {
2859
2860         if (!AllocateDataFormat(it8))
2861             return FALSE;
2862
2863         if (!AllocateDataSet(it8))
2864             return FALSE;
2865
2866         CookPointers(it8);
2867     }
2868
2869     if (cmsstrcasecmp(cSample, "SAMPLE_ID") == 0) {
2870
2871         iSet   = LocateEmptyPatch(it8);
2872         if (iSet < 0) {
2873             return SynError(it8, "Couldn't add more patches '%s'\n", cPatch);
2874         }
2875
2876         iField = t -> SampleID;
2877     }
2878     else {
2879         iSet = LocatePatch(it8, cPatch);
2880         if (iSet < 0) {
2881             return FALSE;
2882         }
2883     }
2884
2885     return SetData(it8, iSet, iField, Val);
2886 }
2887
2888
2889 cmsBool CMSEXPORT cmsIT8SetDataDbl(cmsHANDLE hIT8, const char* cPatch,
2890                                    const char* cSample,
2891                                    cmsFloat64Number Val)
2892 {
2893     cmsIT8* it8 = (cmsIT8*) hIT8;
2894     char Buff[256];
2895
2896     _cmsAssert(hIT8 != NULL);
2897
2898     snprintf(Buff, 255, it8->DoubleFormatter, Val);
2899     return cmsIT8SetData(hIT8, cPatch, cSample, Buff);
2900 }
2901
2902 // Buffer should get MAXSTR at least
2903
2904 const char* CMSEXPORT cmsIT8GetPatchName(cmsHANDLE hIT8, int nPatch, char* buffer)
2905 {
2906     cmsIT8* it8 = (cmsIT8*) hIT8;
2907     TABLE* t;
2908     char* Data;
2909
2910     _cmsAssert(hIT8 != NULL);
2911
2912     t = GetTable(it8);
2913     Data = GetData(it8, nPatch, t->SampleID);
2914
2915     if (!Data) return NULL;
2916     if (!buffer) return Data;
2917
2918     strncpy(buffer, Data, MAXSTR-1);
2919     buffer[MAXSTR-1] = 0;
2920     return buffer;
2921 }
2922
2923 int CMSEXPORT cmsIT8GetPatchByName(cmsHANDLE hIT8, const char *cPatch)
2924 {
2925     _cmsAssert(hIT8 != NULL);
2926
2927     return LocatePatch((cmsIT8*)hIT8, cPatch);
2928 }
2929
2930 cmsUInt32Number CMSEXPORT cmsIT8TableCount(cmsHANDLE hIT8)
2931 {
2932     cmsIT8* it8 = (cmsIT8*) hIT8;
2933
2934     _cmsAssert(hIT8 != NULL);
2935
2936     return it8 ->TablesCount;
2937 }
2938
2939 // This handles the "LABEL" extension.
2940 // Label, nTable, Type
2941
2942 int CMSEXPORT cmsIT8SetTableByLabel(cmsHANDLE hIT8, const char* cSet, const char* cField, const char* ExpectedType)
2943 {
2944     const char* cLabelFld;
2945     char Type[256], Label[256];
2946     cmsUInt32Number nTable;
2947
2948     _cmsAssert(hIT8 != NULL);
2949
2950     if (cField != NULL && *cField == 0)
2951             cField = "LABEL";
2952
2953     if (cField == NULL)
2954             cField = "LABEL";
2955
2956     cLabelFld = cmsIT8GetData(hIT8, cSet, cField);
2957     if (!cLabelFld) return -1;
2958
2959     if (sscanf(cLabelFld, "%255s %u %255s", Label, &nTable, Type) != 3)
2960             return -1;
2961
2962     if (ExpectedType != NULL && *ExpectedType == 0)
2963         ExpectedType = NULL;
2964
2965     if (ExpectedType) {
2966
2967         if (cmsstrcasecmp(Type, ExpectedType) != 0) return -1;
2968     }
2969
2970     return cmsIT8SetTable(hIT8, nTable);
2971 }
2972
2973
2974 cmsBool CMSEXPORT cmsIT8SetIndexColumn(cmsHANDLE hIT8, const char* cSample)
2975 {
2976     cmsIT8* it8 = (cmsIT8*) hIT8;
2977     int pos;
2978
2979     _cmsAssert(hIT8 != NULL);
2980
2981     pos = LocateSample(it8, cSample);
2982     if(pos == -1)
2983         return FALSE;
2984
2985     it8->Tab[it8->nTable].SampleID = pos;
2986     return TRUE;
2987 }
2988
2989
2990 void CMSEXPORT cmsIT8DefineDblFormat(cmsHANDLE hIT8, const char* Formatter)
2991 {
2992     cmsIT8* it8 = (cmsIT8*) hIT8;
2993
2994     _cmsAssert(hIT8 != NULL);
2995
2996     if (Formatter == NULL)
2997         strcpy(it8->DoubleFormatter, DEFAULT_DBL_FORMAT);
2998     else
2999         strncpy(it8->DoubleFormatter, Formatter, sizeof(it8->DoubleFormatter));
3000
3001     it8 ->DoubleFormatter[sizeof(it8 ->DoubleFormatter)-1] = 0;
3002 }
3003
3004
3005 static
3006 cmsBool ReadNumbers(cmsIT8* cube, int n, cmsFloat64Number* arr)
3007 {
3008     int i;
3009
3010     for (i = 0; i < n; i++) {
3011
3012         if (cube->sy == SINUM)
3013             arr[i] = cube->inum;
3014         else
3015             if (cube->sy == SDNUM)
3016                 arr[i] = cube->dnum;
3017             else
3018                 return SynError(cube, "Number expected");
3019
3020         InSymbol(cube);
3021     }
3022
3023     return CheckEOLN(cube);
3024 }
3025
3026 static
3027 cmsBool ParseCube(cmsIT8* cube, cmsStage** Shaper, cmsStage** CLUT, char title[])
3028 {
3029     cmsFloat64Number domain_min[3] = { 0, 0, 0 };
3030     cmsFloat64Number domain_max[3] = { 1.0, 1.0, 1.0 };
3031     cmsFloat64Number check_0_1[2] = { 0, 1.0 };
3032     int shaper_size = 0;
3033     int lut_size = 0;
3034     int i;
3035
3036     InSymbol(cube);
3037
3038     while (cube->sy != SEOF) {
3039         switch (cube->sy)
3040         {
3041         // Set profile description
3042         case STITLE:
3043             InSymbol(cube);
3044             if (!Check(cube, SSTRING, "Title string expected")) return FALSE;
3045             memcpy(title, StringPtr(cube->str), MAXSTR);
3046             title[MAXSTR - 1] = 0;
3047             InSymbol(cube);
3048             break;
3049
3050         // Define domain
3051         case SDOMAIN_MIN:
3052             InSymbol(cube);
3053             if (!ReadNumbers(cube, 3, domain_min)) return FALSE;
3054             break;
3055
3056         case SDOMAIN_MAX:
3057             InSymbol(cube);
3058             if (!ReadNumbers(cube, 3, domain_max)) return FALSE;
3059             break;
3060
3061         // Define shaper
3062         case S_LUT1D_SIZE:
3063             InSymbol(cube);
3064             if (!Check(cube, SINUM, "Shaper size expected")) return FALSE;
3065             shaper_size = cube->inum;
3066             InSymbol(cube);
3067             break;
3068         
3069         // Deefine CLUT
3070         case S_LUT3D_SIZE:
3071             InSymbol(cube);
3072             if (!Check(cube, SINUM, "LUT size expected")) return FALSE;
3073             lut_size = cube->inum;
3074             InSymbol(cube);
3075             break;
3076
3077         // Range. If present, has to be 0..1.0
3078         case S_LUT1D_INPUT_RANGE:
3079         case S_LUT3D_INPUT_RANGE:
3080             InSymbol(cube);
3081             if (!ReadNumbers(cube, 2, check_0_1)) return FALSE;
3082             if (check_0_1[0] != 0 || check_0_1[1] != 1.0) {
3083                 return SynError(cube, "Unsupported format");
3084             }
3085             break;
3086
3087         case SEOLN:
3088             InSymbol(cube);
3089             break;
3090
3091         default:
3092         case S_LUT_IN_VIDEO_RANGE:
3093         case S_LUT_OUT_VIDEO_RANGE:
3094             return SynError(cube, "Unsupported format");
3095
3096             // Read and create tables
3097         case SINUM:
3098         case SDNUM:
3099
3100             if (shaper_size > 0) {
3101
3102                 cmsToneCurve* curves[3];
3103                 cmsFloat32Number* shapers = (cmsFloat32Number*)_cmsMalloc(cube->ContextID, 3 * shaper_size * sizeof(cmsFloat32Number));
3104                 if (shapers == NULL) return FALSE;
3105
3106                 for (i = 0; i < shaper_size; i++) {
3107
3108                     cmsFloat64Number nums[3];
3109
3110                     if (!ReadNumbers(cube, 3, nums)) return FALSE;
3111
3112                     shapers[i + 0]               = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0]));
3113                     shapers[i + 1 * shaper_size] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1]));
3114                     shapers[i + 2 * shaper_size] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2]));
3115                 }
3116
3117                 for (i = 0; i < 3; i++) {
3118
3119                     curves[i] = cmsBuildTabulatedToneCurveFloat(cube->ContextID, shaper_size,
3120                         &shapers[i * shaper_size]);
3121                     if (curves[i] == NULL) return FALSE;
3122                 }
3123
3124                 *Shaper = cmsStageAllocToneCurves(cube->ContextID, 3, curves);
3125
3126                 cmsFreeToneCurveTriple(curves);
3127             }
3128
3129             if (lut_size > 0) {
3130
3131                 int nodes = lut_size * lut_size * lut_size;
3132
3133                 cmsFloat32Number* lut_table = _cmsMalloc(cube->ContextID, nodes * 3 * sizeof(cmsFloat32Number));
3134                 if (lut_table == NULL) return FALSE;
3135
3136                 for (i = 0; i < nodes; i++) {
3137
3138                     cmsFloat64Number nums[3];
3139
3140                     if (!ReadNumbers(cube, 3, nums)) return FALSE;
3141
3142                     lut_table[i * 3 + 2] = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0]));
3143                     lut_table[i * 3 + 1] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1]));
3144                     lut_table[i * 3 + 0] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2]));
3145                 }
3146
3147                 *CLUT = cmsStageAllocCLutFloat(cube->ContextID, lut_size, 3, 3, lut_table);
3148                 _cmsFree(cube->ContextID, lut_table);
3149             }   
3150
3151             if (!Check(cube, SEOF, "Extra symbols found in file")) return FALSE;
3152         }
3153     }
3154
3155     return TRUE;
3156 }
3157
3158 // Share the parser to read .cube format and create RGB devicelink profiles
3159 cmsHPROFILE CMSEXPORT cmsCreateDeviceLinkFromCubeFileTHR(cmsContext ContextID, const char* cFileName)
3160 {    
3161     cmsHPROFILE hProfile = NULL;
3162     cmsIT8* cube = NULL;
3163     cmsPipeline* Pipeline = NULL;   
3164     cmsStage* CLUT = NULL;
3165     cmsStage* Shaper = NULL;
3166     cmsMLU* DescriptionMLU = NULL;
3167     char title[MAXSTR];
3168
3169     _cmsAssert(cFileName != NULL);
3170     
3171     cube = (cmsIT8*) cmsIT8Alloc(ContextID);    
3172     if (!cube) return NULL;
3173
3174     cube->IsCUBE = TRUE;
3175     cube->FileStack[0]->Stream = fopen(cFileName, "rt");
3176
3177     if (!cube->FileStack[0]->Stream) goto Done;
3178
3179     strncpy(cube->FileStack[0]->FileName, cFileName, cmsMAX_PATH - 1);
3180     cube->FileStack[0]->FileName[cmsMAX_PATH - 1] = 0;
3181
3182     if (!ParseCube(cube, &Shaper, &CLUT, title)) goto Done;
3183         
3184     // Success on parsing, let's create the profile
3185     hProfile = cmsCreateProfilePlaceholder(ContextID);
3186     if (!hProfile) goto Done;
3187         
3188     cmsSetProfileVersion(hProfile, 4.4);
3189
3190     cmsSetDeviceClass(hProfile, cmsSigLinkClass);
3191     cmsSetColorSpace(hProfile,  cmsSigRgbData);
3192     cmsSetPCS(hProfile,         cmsSigRgbData);
3193
3194     cmsSetHeaderRenderingIntent(hProfile, INTENT_PERCEPTUAL);
3195
3196     // Creates a Pipeline to hold CLUT and shaper
3197     Pipeline = cmsPipelineAlloc(ContextID, 3, 3);
3198     if (Pipeline == NULL) goto Done;
3199
3200     // Populates the pipeline
3201     if (Shaper != NULL) {
3202         if (!cmsPipelineInsertStage(Pipeline, cmsAT_BEGIN, Shaper))
3203             goto Done;
3204     }
3205
3206     if (CLUT != NULL) {
3207         if (!cmsPipelineInsertStage(Pipeline, cmsAT_END, CLUT))
3208             goto Done;
3209     }
3210
3211     // Propagate the description. We put no copyright because we know
3212     // nothing on the copyrighted state of the .cube
3213     DescriptionMLU = cmsMLUalloc(ContextID, 1);
3214     if (!cmsMLUsetUTF8(DescriptionMLU, cmsNoLanguage, cmsNoCountry, title)) goto Done;
3215
3216     // Flush the tags
3217     if (!cmsWriteTag(hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Done;
3218     if (!cmsWriteTag(hProfile, cmsSigAToB0Tag, (void*)Pipeline)) goto Done;
3219
3220 Done:
3221
3222     if (DescriptionMLU != NULL)
3223         cmsMLUfree(DescriptionMLU);
3224
3225     if (Pipeline != NULL)
3226         cmsPipelineFree(Pipeline);
3227
3228     cmsIT8Free((cmsHANDLE) cube);
3229
3230     return hProfile;
3231 }
3232
3233 cmsHPROFILE CMSEXPORT cmsCreateDeviceLinkFromCubeFile(const char* cFileName)
3234 {
3235     return cmsCreateDeviceLinkFromCubeFileTHR(NULL, cFileName);
3236 }