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