comparison mupdf-source/thirdparty/lcms2/src/cmscgats.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
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 } cmsIT8;
199
200
201 // The stream for save operations
202 typedef struct {
203
204 FILE* stream; // For save-to-file behaviour
205
206 cmsUInt8Number* Base;
207 cmsUInt8Number* Ptr; // For save-to-mem behaviour
208 cmsUInt32Number Used;
209 cmsUInt32Number Max;
210
211 } SAVESTREAM;
212
213
214 // ------------------------------------------------------ cmsIT8 parsing routines
215
216
217 // A keyword
218 typedef struct {
219
220 const char *id;
221 SYMBOL sy;
222
223 } KEYWORD;
224
225 // The keyword->symbol translation tables. Sorting is required.
226 static const KEYWORD TabKeysIT8[] = {
227
228 {"$INCLUDE", SINCLUDE}, // This is an extension!
229 {".INCLUDE", SINCLUDE}, // This is an extension!
230
231 {"BEGIN_DATA", SBEGIN_DATA },
232 {"BEGIN_DATA_FORMAT", SBEGIN_DATA_FORMAT },
233 {"DATA_FORMAT_IDENTIFIER", SDATA_FORMAT_ID},
234 {"END_DATA", SEND_DATA},
235 {"END_DATA_FORMAT", SEND_DATA_FORMAT},
236 {"KEYWORD", SKEYWORD}
237 };
238
239 #define NUMKEYS_IT8 (sizeof(TabKeysIT8)/sizeof(KEYWORD))
240
241 static const KEYWORD TabKeysCUBE[] = {
242
243 {"DOMAIN_MAX", SDOMAIN_MAX },
244 {"DOMAIN_MIN", SDOMAIN_MIN },
245 {"LUT_1D_SIZE", S_LUT1D_SIZE },
246 {"LUT_1D_INPUT_RANGE", S_LUT1D_INPUT_RANGE },
247 {"LUT_3D_SIZE", S_LUT3D_SIZE },
248 {"LUT_3D_INPUT_RANGE", S_LUT3D_INPUT_RANGE },
249 {"LUT_IN_VIDEO_RANGE", S_LUT_IN_VIDEO_RANGE },
250 {"LUT_OUT_VIDEO_RANGE", S_LUT_OUT_VIDEO_RANGE },
251 {"TITLE", STITLE }
252
253 };
254
255 #define NUMKEYS_CUBE (sizeof(TabKeysCUBE)/sizeof(KEYWORD))
256
257
258
259 // Predefined properties
260
261 // A property
262 typedef struct {
263 const char *id; // The identifier
264 WRITEMODE as; // How is supposed to be written
265 } PROPERTY;
266
267 static PROPERTY PredefinedProperties[] = {
268
269 {"NUMBER_OF_FIELDS", WRITE_UNCOOKED}, // Required - NUMBER OF FIELDS
270 {"NUMBER_OF_SETS", WRITE_UNCOOKED}, // Required - NUMBER OF SETS
271 {"ORIGINATOR", WRITE_STRINGIFY}, // Required - Identifies the specific system, organization or individual that created the data file.
272 {"FILE_DESCRIPTOR", WRITE_STRINGIFY}, // Required - Describes the purpose or contents of the data file.
273 {"CREATED", WRITE_STRINGIFY}, // Required - Indicates date of creation of the data file.
274 {"DESCRIPTOR", WRITE_STRINGIFY}, // Required - Describes the purpose or contents of the data file.
275 {"DIFFUSE_GEOMETRY", WRITE_STRINGIFY}, // The diffuse geometry used. Allowed values are "sphere" or "opal".
276 {"MANUFACTURER", WRITE_STRINGIFY},
277 {"MANUFACTURE", WRITE_STRINGIFY}, // Some broken Fuji targets does store this value
278 {"PROD_DATE", WRITE_STRINGIFY}, // Identifies year and month of production of the target in the form yyyy:mm.
279 {"SERIAL", WRITE_STRINGIFY}, // Uniquely identifies individual physical target.
280
281 {"MATERIAL", WRITE_STRINGIFY}, // Identifies the material on which the target was produced using a code
282 // uniquely identifying th e material. This is intend ed to be used for IT8.7
283 // physical targets only (i.e . IT8.7/1 and IT8.7/2).
284
285 {"INSTRUMENTATION", WRITE_STRINGIFY}, // Used to report the specific instrumentation used (manufacturer and
286 // model number) to generate the data reported. This data will often
287 // provide more information about the particular data collected than an
288 // extensive list of specific details. This is particularly important for
289 // spectral data or data derived from spectrophotometry.
290
291 {"MEASUREMENT_SOURCE", WRITE_STRINGIFY}, // Illumination used for spectral measurements. This data helps provide
292 // a guide to the potential for issues of paper fluorescence, etc.
293
294 {"PRINT_CONDITIONS", WRITE_STRINGIFY}, // Used to define the characteristics of the printed sheet being reported.
295 // Where standard conditions have been defined (e.g., SWOP at nominal)
296 // named conditions may suffice. Otherwise, detailed information is
297 // needed.
298
299 {"SAMPLE_BACKING", WRITE_STRINGIFY}, // Identifies the backing material used behind the sample during
300 // measurement. Allowed values are "black", "white", or {"na".
301
302 {"CHISQ_DOF", WRITE_STRINGIFY}, // Degrees of freedom associated with the Chi squared statistic
303 // below properties are new in recent specs:
304
305 {"MEASUREMENT_GEOMETRY", WRITE_STRINGIFY}, // The type of measurement, either reflection or transmission, should be indicated
306 // along with details of the geometry and the aperture size and shape. For example,
307 // for transmission measurements it is important to identify 0/diffuse, diffuse/0,
308 // opal or integrating sphere, etc. For reflection it is important to identify 0/45,
309 // 45/0, sphere (specular included or excluded), etc.
310
311 {"FILTER", WRITE_STRINGIFY}, // Identifies the use of physical filter(s) during measurement. Typically used to
312 // denote the use of filters such as none, D65, Red, Green or Blue.
313
314 {"POLARIZATION", WRITE_STRINGIFY}, // Identifies the use of a physical polarization filter during measurement. Allowed
315 // values are {"yes", "white", "none" or "na".
316
317 {"WEIGHTING_FUNCTION", WRITE_PAIR}, // Indicates such functions as: the CIE standard observer functions used in the
318 // calculation of various data parameters (2 degree and 10 degree), CIE standard
319 // illuminant functions used in the calculation of various data parameters (e.g., D50,
320 // D65, etc.), density status response, etc. If used there shall be at least one
321 // name-value pair following the WEIGHTING_FUNCTION tag/keyword. The first attribute
322 // in the set shall be {"name" and shall identify the particular parameter used.
323 // The second shall be {"value" and shall provide the value associated with that name.
324 // For ASCII data, a string containing the Name and Value attribute pairs shall follow
325 // the weighting function keyword. A semi-colon separates attribute pairs from each
326 // other and within the attribute the name and value are separated by a comma.
327
328 {"COMPUTATIONAL_PARAMETER", WRITE_PAIR}, // Parameter that is used in computing a value from measured data. Name is the name
329 // of the calculation, parameter is the name of the parameter used in the calculation
330 // and value is the value of the parameter.
331
332 {"TARGET_TYPE", WRITE_STRINGIFY}, // The type of target being measured, e.g. IT8.7/1, IT8.7/3, user defined, etc.
333
334 {"COLORANT", WRITE_STRINGIFY}, // Identifies the colorant(s) used in creating the target.
335
336 {"TABLE_DESCRIPTOR", WRITE_STRINGIFY}, // Describes the purpose or contents of a data table.
337
338 {"TABLE_NAME", WRITE_STRINGIFY} // Provides a short name for a data table.
339 };
340
341 #define NUMPREDEFINEDPROPS (sizeof(PredefinedProperties)/sizeof(PROPERTY))
342
343
344 // Predefined sample types on dataset
345 static const char* PredefinedSampleID[] = {
346 "SAMPLE_ID", // Identifies sample that data represents
347 "STRING", // Identifies label, or other non-machine readable value.
348 // Value must begin and end with a " symbol
349
350 "CMYK_C", // Cyan component of CMYK data expressed as a percentage
351 "CMYK_M", // Magenta component of CMYK data expressed as a percentage
352 "CMYK_Y", // Yellow component of CMYK data expressed as a percentage
353 "CMYK_K", // Black component of CMYK data expressed as a percentage
354 "D_RED", // Red filter density
355 "D_GREEN", // Green filter density
356 "D_BLUE", // Blue filter density
357 "D_VIS", // Visual filter density
358 "D_MAJOR_FILTER", // Major filter d ensity
359 "RGB_R", // Red component of RGB data
360 "RGB_G", // Green component of RGB data
361 "RGB_B", // Blue com ponent of RGB data
362 "SPECTRAL_NM", // Wavelength of measurement expressed in nanometers
363 "SPECTRAL_PCT", // Percentage reflectance/transmittance
364 "SPECTRAL_DEC", // Reflectance/transmittance
365 "XYZ_X", // X component of tristimulus data
366 "XYZ_Y", // Y component of tristimulus data
367 "XYZ_Z", // Z component of tristimulus data
368 "XYY_X", // x component of chromaticity data
369 "XYY_Y", // y component of chromaticity data
370 "XYY_CAPY", // Y component of tristimulus data
371 "LAB_L", // L* component of Lab data
372 "LAB_A", // a* component of Lab data
373 "LAB_B", // b* component of Lab data
374 "LAB_C", // C*ab component of Lab data
375 "LAB_H", // hab component of Lab data
376 "LAB_DE", // CIE dE
377 "LAB_DE_94", // CIE dE using CIE 94
378 "LAB_DE_CMC", // dE using CMC
379 "LAB_DE_2000", // CIE dE using CIE DE 2000
380 "MEAN_DE", // Mean Delta E (LAB_DE) of samples compared to batch average
381 // (Used for data files for ANSI IT8.7/1 and IT8.7/2 targets)
382 "STDEV_X", // Standard deviation of X (tristimulus data)
383 "STDEV_Y", // Standard deviation of Y (tristimulus data)
384 "STDEV_Z", // Standard deviation of Z (tristimulus data)
385 "STDEV_L", // Standard deviation of L*
386 "STDEV_A", // Standard deviation of a*
387 "STDEV_B", // Standard deviation of b*
388 "STDEV_DE", // Standard deviation of CIE dE
389 "CHI_SQD_PAR"}; // The average of the standard deviations of L*, a* and b*. It is
390 // used to derive an estimate of the chi-squared parameter which is
391 // recommended as the predictor of the variability of dE
392
393 #define NUMPREDEFINEDSAMPLEID (sizeof(PredefinedSampleID)/sizeof(char *))
394
395 //Forward declaration of some internal functions
396 static void* AllocChunk(cmsContext ContextID, cmsIT8* it8, cmsUInt32Number size);
397
398 static
399 string* StringAlloc(cmsContext ContextID, cmsIT8* it8, int max)
400 {
401 string* s = (string*) AllocChunk(ContextID, it8, sizeof(string));
402 if (s == NULL) return NULL;
403
404 s->it8 = it8;
405 s->max = max;
406 s->len = 0;
407 s->begin = (char*) AllocChunk(ContextID, it8, s->max);
408
409 return s;
410 }
411
412 static
413 void StringClear(string* s)
414 {
415 s->len = 0;
416 }
417
418 static
419 void StringAppend(cmsContext ContextID, string* s, char c)
420 {
421 if (s->len + 1 >= s->max)
422 {
423 char* new_ptr;
424
425 s->max *= 10;
426 new_ptr = (char*) AllocChunk(ContextID, s->it8, s->max);
427 if (new_ptr != NULL && s->begin != NULL)
428 memcpy(new_ptr, s->begin, s->len);
429
430 s->begin = new_ptr;
431 }
432
433 if (s->begin != NULL)
434 {
435 s->begin[s->len++] = c;
436 s->begin[s->len] = 0;
437 }
438 }
439
440 static
441 char* StringPtr(string* s)
442 {
443 return s->begin;
444 }
445
446 static
447 void StringCat(cmsContext ContextID, string* s, const char* c)
448 {
449 while (*c)
450 {
451 StringAppend(ContextID, s, *c);
452 c++;
453 }
454 }
455
456
457 // Checks whatever c is a separator
458 static
459 cmsBool isseparator(int c)
460 {
461 return (c == ' ') || (c == '\t');
462 }
463
464 // Checks whatever c is a valid identifier char
465 static
466 cmsBool ismiddle(int c)
467 {
468 return (!isseparator(c) && (c != '#') && (c !='\"') && (c != '\'') && (c > 32) && (c < 127));
469 }
470
471 // Checks whatsever c is a valid identifier middle char.
472 static
473 cmsBool isidchar(int c)
474 {
475 return isalnum(c) || ismiddle(c);
476 }
477
478 // Checks whatsever c is a valid identifier first char.
479 static
480 cmsBool isfirstidchar(int c)
481 {
482 return c != '-' && !isdigit(c) && ismiddle(c);
483 }
484
485 // Guess whether the supplied path looks like an absolute path
486 static
487 cmsBool isabsolutepath(const char *path)
488 {
489 char ThreeChars[4];
490
491 if(path == NULL)
492 return FALSE;
493 if (path[0] == 0)
494 return FALSE;
495
496 strncpy(ThreeChars, path, 3);
497 ThreeChars[3] = 0;
498
499 if(ThreeChars[0] == DIR_CHAR)
500 return TRUE;
501
502 #ifdef CMS_IS_WINDOWS_
503 if (isalpha((int) ThreeChars[0]) && ThreeChars[1] == ':')
504 return TRUE;
505 #endif
506 return FALSE;
507 }
508
509
510 // Makes a file path based on a given reference path
511 // NOTE: this function doesn't check if the path exists or even if it's legal
512 static
513 cmsBool BuildAbsolutePath(const char *relPath, const char *basePath, char *buffer, cmsUInt32Number MaxLen)
514 {
515 char *tail;
516 cmsUInt32Number len;
517
518 // Already absolute?
519 if (isabsolutepath(relPath)) {
520
521 memcpy(buffer, relPath, MaxLen);
522 buffer[MaxLen-1] = 0;
523 return TRUE;
524 }
525
526 // No, search for last
527 memcpy(buffer, basePath, MaxLen);
528 buffer[MaxLen-1] = 0;
529
530 tail = strrchr(buffer, DIR_CHAR);
531 if (tail == NULL) return FALSE; // Is not absolute and has no separators??
532
533 len = (cmsUInt32Number) (tail - buffer);
534 if (len >= MaxLen) return FALSE;
535
536 // No need to assure zero terminator over here
537 strncpy(tail + 1, relPath, MaxLen - len);
538
539 return TRUE;
540 }
541
542
543 // Make sure no exploit is being even tried
544 static
545 const char* NoMeta(const char* str)
546 {
547 if (strchr(str, '%') != NULL)
548 return "**** CORRUPTED FORMAT STRING ***";
549
550 return str;
551 }
552
553 // Syntax error
554 static
555 cmsBool SynError(cmsContext ContextID, cmsIT8* it8, const char *Txt, ...)
556 {
557 char Buffer[256], ErrMsg[1024];
558 va_list args;
559
560 va_start(args, Txt);
561 vsnprintf(Buffer, 255, Txt, args);
562 Buffer[255] = 0;
563 va_end(args);
564
565 snprintf(ErrMsg, 1023, "%s: Line %d, %s", it8->FileStack[it8 ->IncludeSP]->FileName, it8->lineno, Buffer);
566 ErrMsg[1023] = 0;
567 it8->sy = SSYNERROR;
568 cmsSignalError(ContextID, cmsERROR_CORRUPTION_DETECTED, "%s", ErrMsg);
569 return FALSE;
570 }
571
572 // Check if current symbol is same as specified. issue an error else.
573 static
574 cmsBool Check(cmsContext ContextID, cmsIT8* it8, SYMBOL sy, const char* Err)
575 {
576 if (it8 -> sy != sy)
577 return SynError(ContextID, it8, NoMeta(Err));
578 return TRUE;
579 }
580
581 // Read Next character from stream
582 static
583 void NextCh(cmsIT8* it8)
584 {
585 if (it8 -> FileStack[it8 ->IncludeSP]->Stream) {
586
587 it8 ->ch = fgetc(it8 ->FileStack[it8 ->IncludeSP]->Stream);
588
589 if (feof(it8 -> FileStack[it8 ->IncludeSP]->Stream)) {
590
591 if (it8 ->IncludeSP > 0) {
592
593 fclose(it8 ->FileStack[it8->IncludeSP--]->Stream);
594 it8 -> ch = ' '; // Whitespace to be ignored
595
596 } else
597 it8 ->ch = 0; // EOF
598 }
599 }
600 else {
601 it8->ch = *it8->Source;
602 if (it8->ch) it8->Source++;
603 }
604 }
605
606
607 // Try to see if current identifier is a keyword, if so return the referred symbol
608 static
609 SYMBOL BinSrchKey(const char *id, int NumKeys, const KEYWORD* TabKeys)
610 {
611 int l = 1;
612 int r = NumKeys;
613 int x, res;
614
615 while (r >= l)
616 {
617 x = (l+r)/2;
618 res = cmsstrcasecmp(id, TabKeys[x-1].id);
619 if (res == 0) return TabKeys[x-1].sy;
620 if (res < 0) r = x - 1;
621 else l = x + 1;
622 }
623
624 return SUNDEFINED;
625 }
626
627
628 // 10 ^n
629 static
630 cmsFloat64Number xpow10(int n)
631 {
632 return pow(10, (cmsFloat64Number) n);
633 }
634
635
636 // Reads a Real number, tries to follow from integer number
637 static
638 void ReadReal(cmsIT8* it8, cmsInt32Number inum)
639 {
640 it8->dnum = (cmsFloat64Number)inum;
641
642 while (isdigit(it8->ch)) {
643
644 it8->dnum = (cmsFloat64Number)it8->dnum * 10.0 + (cmsFloat64Number)(it8->ch - '0');
645 NextCh(it8);
646 }
647
648 if (it8->ch == '.') { // Decimal point
649
650 cmsFloat64Number frac = 0.0; // fraction
651 int prec = 0; // precision
652
653 NextCh(it8); // Eats dec. point
654
655 while (isdigit(it8->ch)) {
656
657 frac = frac * 10.0 + (cmsFloat64Number)(it8->ch - '0');
658 prec++;
659 NextCh(it8);
660 }
661
662 it8->dnum = it8->dnum + (frac / xpow10(prec));
663 }
664
665 // Exponent, example 34.00E+20
666 if (toupper(it8->ch) == 'E') {
667
668 cmsInt32Number e;
669 cmsInt32Number sgn;
670
671 NextCh(it8); sgn = 1;
672
673 if (it8->ch == '-') {
674
675 sgn = -1; NextCh(it8);
676 }
677 else
678 if (it8->ch == '+') {
679
680 sgn = +1;
681 NextCh(it8);
682 }
683
684 e = 0;
685 while (isdigit(it8->ch)) {
686
687 cmsInt32Number digit = (it8->ch - '0');
688
689 if ((cmsFloat64Number)e * 10.0 + (cmsFloat64Number)digit < (cmsFloat64Number)+2147483647.0)
690 e = e * 10 + digit;
691
692 NextCh(it8);
693 }
694
695 e = sgn*e;
696 it8->dnum = it8->dnum * xpow10(e);
697 }
698 }
699
700 // Parses a float number
701 // This can not call directly atof because it uses locale dependent
702 // parsing, while CCMX files always use . as decimal separator
703 static
704 cmsFloat64Number ParseFloatNumber(const char *Buffer)
705 {
706 cmsFloat64Number dnum = 0.0;
707 int sign = 1;
708
709 // keep safe
710 if (Buffer == NULL) return 0.0;
711
712 if (*Buffer == '-' || *Buffer == '+') {
713
714 sign = (*Buffer == '-') ? -1 : 1;
715 Buffer++;
716 }
717
718
719 while (*Buffer && isdigit((int)*Buffer)) {
720
721 dnum = dnum * 10.0 + (*Buffer - '0');
722 if (*Buffer) Buffer++;
723 }
724
725 if (*Buffer == '.') {
726
727 cmsFloat64Number frac = 0.0; // fraction
728 int prec = 0; // precision
729
730 if (*Buffer) Buffer++;
731
732 while (*Buffer && isdigit((int)*Buffer)) {
733
734 frac = frac * 10.0 + (*Buffer - '0');
735 prec++;
736 if (*Buffer) Buffer++;
737 }
738
739 dnum = dnum + (frac / xpow10(prec));
740 }
741
742 // Exponent, example 34.00E+20
743 if (*Buffer && toupper(*Buffer) == 'E') {
744
745 int e;
746 int sgn;
747
748 if (*Buffer) Buffer++;
749 sgn = 1;
750
751 if (*Buffer == '-') {
752
753 sgn = -1;
754 if (*Buffer) Buffer++;
755 }
756 else
757 if (*Buffer == '+') {
758
759 sgn = +1;
760 if (*Buffer) Buffer++;
761 }
762
763 e = 0;
764 while (*Buffer && isdigit((int)*Buffer)) {
765
766 cmsInt32Number digit = (*Buffer - '0');
767
768 if ((cmsFloat64Number)e * 10.0 + digit < (cmsFloat64Number)+2147483647.0)
769 e = e * 10 + digit;
770
771 if (*Buffer) Buffer++;
772 }
773
774 e = sgn*e;
775 dnum = dnum * xpow10(e);
776 }
777
778 return sign * dnum;
779 }
780
781
782 // Reads a string, special case to avoid infinite recursion on .include
783 static
784 void InStringSymbol(cmsContext ContextID, cmsIT8* it8)
785 {
786 while (isseparator(it8->ch))
787 NextCh(it8);
788
789 if (it8->ch == '\'' || it8->ch == '\"')
790 {
791 int sng;
792
793 sng = it8->ch;
794 StringClear(it8->str);
795
796 NextCh(it8);
797
798 while (it8->ch != sng) {
799
800 if (it8->ch == '\n' || it8->ch == '\r' || it8->ch == 0) break;
801 else {
802 StringAppend(ContextID, it8->str, (char)it8->ch);
803 NextCh(it8);
804 }
805 }
806
807 it8->sy = SSTRING;
808 NextCh(it8);
809 }
810 else
811 SynError(ContextID, it8, "String expected");
812
813 }
814
815 // Reads next symbol
816 static
817 void InSymbol(cmsContext ContextID, cmsIT8* it8)
818 {
819 SYMBOL key;
820
821 do {
822
823 while (isseparator(it8->ch))
824 NextCh(it8);
825
826 if (isfirstidchar(it8->ch)) { // Identifier
827
828 StringClear(it8->id);
829
830 do {
831
832 StringAppend(ContextID, it8->id, (char) it8->ch);
833
834 NextCh(it8);
835
836 } while (isidchar(it8->ch));
837
838
839 key = BinSrchKey(StringPtr(it8->id),
840 it8->IsCUBE ? NUMKEYS_CUBE : NUMKEYS_IT8,
841 it8->IsCUBE ? TabKeysCUBE : TabKeysIT8);
842 if (key == SUNDEFINED) it8->sy = SIDENT;
843 else it8->sy = key;
844
845 }
846 else // Is a number?
847 if (isdigit(it8->ch) || it8->ch == '.' || it8->ch == '-' || it8->ch == '+')
848 {
849 int sign = 1;
850
851 if (it8->ch == '-') {
852 sign = -1;
853 NextCh(it8);
854 }
855
856 it8->inum = 0;
857 it8->sy = SINUM;
858
859 if (it8->ch == '0') { // 0xnnnn (Hexa) or 0bnnnn (Binary)
860
861 NextCh(it8);
862 if (toupper(it8->ch) == 'X') {
863
864 int j;
865
866 NextCh(it8);
867 while (isxdigit(it8->ch))
868 {
869 it8->ch = toupper(it8->ch);
870 if (it8->ch >= 'A' && it8->ch <= 'F') j = it8->ch -'A'+10;
871 else j = it8->ch - '0';
872
873 if ((cmsFloat64Number) it8->inum * 16.0 + (cmsFloat64Number) j > (cmsFloat64Number)+2147483647.0)
874 {
875 SynError(ContextID, it8, "Invalid hexadecimal number");
876 it8->sy = SEOF;
877 return;
878 }
879
880 it8->inum = it8->inum * 16 + j;
881 NextCh(it8);
882 }
883 return;
884 }
885
886 if (toupper(it8->ch) == 'B') { // Binary
887
888 int j;
889
890 NextCh(it8);
891 while (it8->ch == '0' || it8->ch == '1')
892 {
893 j = it8->ch - '0';
894
895 if ((cmsFloat64Number) it8->inum * 2.0 + j > (cmsFloat64Number)+2147483647.0)
896 {
897 SynError(ContextID, it8, "Invalid binary number");
898 it8->sy = SEOF;
899 return;
900 }
901
902 it8->inum = it8->inum * 2 + j;
903 NextCh(it8);
904 }
905 return;
906 }
907 }
908
909
910 while (isdigit(it8->ch)) {
911
912 cmsInt32Number digit = (it8->ch - '0');
913
914 if ((cmsFloat64Number) it8->inum * 10.0 + (cmsFloat64Number) digit > (cmsFloat64Number) +2147483647.0) {
915 ReadReal(it8, it8->inum);
916 it8->sy = SDNUM;
917 it8->dnum *= sign;
918 return;
919 }
920
921 it8->inum = it8->inum * 10 + digit;
922 NextCh(it8);
923 }
924
925 if (it8->ch == '.') {
926
927 ReadReal(it8, it8->inum);
928 it8->sy = SDNUM;
929 it8->dnum *= sign;
930 return;
931 }
932
933 it8 -> inum *= sign;
934
935 // Special case. Numbers followed by letters are taken as identifiers
936
937 if (isidchar(it8 ->ch)) {
938
939 char buffer[127];
940
941 if (it8 ->sy == SINUM) {
942
943 snprintf(buffer, sizeof(buffer), "%d", it8->inum);
944 }
945 else {
946
947 snprintf(buffer, sizeof(buffer), it8 ->DoubleFormatter, it8->dnum);
948 }
949
950 StringClear(it8->id);
951 StringCat(ContextID, it8->id, buffer);
952
953 do {
954
955 StringAppend(ContextID, it8->id, (char) it8->ch);
956
957 NextCh(it8);
958
959 } while (isidchar(it8->ch));
960
961 it8->sy = SIDENT;
962 }
963 return;
964
965 }
966 else
967 switch ((int) it8->ch) {
968
969 // Eof stream markers
970 case '\x1a':
971 case 0:
972 case -1:
973 it8->sy = SEOF;
974 break;
975
976
977 // Next line
978 case '\r':
979 NextCh(it8);
980 if (it8->ch == '\n')
981 NextCh(it8);
982 it8->sy = SEOLN;
983 it8->lineno++;
984 break;
985
986 case '\n':
987 NextCh(it8);
988 it8->sy = SEOLN;
989 it8->lineno++;
990 break;
991
992 // Comment
993 case '#':
994 NextCh(it8);
995 while (it8->ch && it8->ch != '\n' && it8->ch != '\r')
996 NextCh(it8);
997
998 it8->sy = SCOMMENT;
999 break;
1000
1001 // String.
1002 case '\'':
1003 case '\"':
1004 InStringSymbol(ContextID, it8);
1005 break;
1006
1007
1008 default:
1009 SynError(ContextID, it8, "Unrecognized character: 0x%x", it8 ->ch);
1010 it8->sy = SEOF;
1011 return;
1012 }
1013
1014 } while (it8->sy == SCOMMENT);
1015
1016 // Handle the include special token
1017
1018 if (it8 -> sy == SINCLUDE) {
1019
1020 FILECTX* FileNest;
1021
1022 if(it8 -> IncludeSP >= (MAXINCLUDE-1)) {
1023
1024 SynError(ContextID, it8, "Too many recursion levels");
1025 it8->sy = SEOF;
1026 return;
1027 }
1028
1029 InStringSymbol(ContextID, it8);
1030 if (!Check(ContextID, it8, SSTRING, "Filename expected"))
1031 {
1032 it8->sy = SEOF;
1033 return;
1034 }
1035
1036 FileNest = it8 -> FileStack[it8 -> IncludeSP + 1];
1037 if(FileNest == NULL) {
1038
1039 FileNest = it8 ->FileStack[it8 -> IncludeSP + 1] = (FILECTX*)AllocChunk(ContextID, it8, sizeof(FILECTX));
1040 if (FileNest == NULL) {
1041 SynError(ContextID, it8, "Out of memory");
1042 it8->sy = SEOF;
1043 return;
1044 }
1045 }
1046
1047 if (BuildAbsolutePath(StringPtr(it8->str),
1048 it8->FileStack[it8->IncludeSP]->FileName,
1049 FileNest->FileName, cmsMAX_PATH-1) == FALSE) {
1050 SynError(ContextID, it8, "File path too long");
1051 it8->sy = SEOF;
1052 return;
1053 }
1054
1055 FileNest->Stream = fopen(FileNest->FileName, "rt");
1056 if (FileNest->Stream == NULL) {
1057
1058 SynError(ContextID, it8, "File %s not found", FileNest->FileName);
1059 it8->sy = SEOF;
1060 return;
1061 }
1062 it8->IncludeSP++;
1063
1064 it8 ->ch = ' ';
1065 InSymbol(ContextID, it8);
1066 }
1067
1068 }
1069
1070 // Checks end of line separator
1071 static
1072 cmsBool CheckEOLN(cmsContext ContextID, cmsIT8* it8)
1073 {
1074 if (!Check(ContextID, it8, SEOLN, "Expected separator")) return FALSE;
1075 while (it8 -> sy == SEOLN)
1076 InSymbol(ContextID, it8);
1077 return TRUE;
1078
1079 }
1080
1081 // Skip a symbol
1082
1083 static
1084 void Skip(cmsContext ContextID, cmsIT8* it8, SYMBOL sy)
1085 {
1086 if (it8->sy == sy && it8->sy != SEOF)
1087 InSymbol(ContextID, it8);
1088 }
1089
1090
1091 // Skip multiple EOLN
1092 static
1093 void SkipEOLN(cmsContext ContextID, cmsIT8* it8)
1094 {
1095 while (it8->sy == SEOLN) {
1096 InSymbol(ContextID, it8);
1097 }
1098 }
1099
1100
1101 // Returns a string holding current value
1102 static
1103 cmsBool GetVal(cmsContext ContextID, cmsIT8* it8, char* Buffer, cmsUInt32Number max, const char* ErrorTitle)
1104 {
1105 switch (it8->sy) {
1106
1107 case SEOLN: // Empty value
1108 Buffer[0]=0;
1109 break;
1110 case SIDENT: strncpy(Buffer, StringPtr(it8->id), max);
1111 Buffer[max-1]=0;
1112 break;
1113 case SINUM: snprintf(Buffer, max, "%d", it8 -> inum); break;
1114 case SDNUM: snprintf(Buffer, max, it8->DoubleFormatter, it8 -> dnum); break;
1115 case SSTRING: strncpy(Buffer, StringPtr(it8->str), max);
1116 Buffer[max-1] = 0;
1117 break;
1118
1119
1120 default:
1121 return SynError(ContextID, it8, "%s", ErrorTitle);
1122 }
1123
1124 Buffer[max] = 0;
1125 return TRUE;
1126 }
1127
1128 // ---------------------------------------------------------- Table
1129
1130 static
1131 TABLE* GetTable(cmsContext ContextID, cmsIT8* it8)
1132 {
1133 if ((it8 -> nTable >= it8 ->TablesCount)) {
1134
1135 SynError(ContextID, it8, "Table %d out of sequence", it8 -> nTable);
1136 return it8 -> Tab;
1137 }
1138
1139 return it8 ->Tab + it8 ->nTable;
1140 }
1141
1142 // ---------------------------------------------------------- Memory management
1143
1144
1145 // Frees an allocator and owned memory
1146 void CMSEXPORT cmsIT8Free(cmsContext ContextID, cmsHANDLE hIT8)
1147 {
1148 cmsIT8* it8 = (cmsIT8*) hIT8;
1149
1150 if (it8 == NULL)
1151 return;
1152
1153 if (it8->MemorySink) {
1154
1155 OWNEDMEM* p;
1156 OWNEDMEM* n;
1157
1158 for (p = it8->MemorySink; p != NULL; p = n) {
1159
1160 n = p->Next;
1161 if (p->Ptr) _cmsFree(ContextID, p->Ptr);
1162 _cmsFree(ContextID, p);
1163 }
1164 }
1165
1166 if (it8->MemoryBlock)
1167 _cmsFree(ContextID, it8->MemoryBlock);
1168
1169 _cmsFree(ContextID, it8);
1170 }
1171
1172
1173 // Allocates a chunk of data, keep linked list
1174 static
1175 void* AllocBigBlock(cmsContext ContextID, cmsIT8* it8, cmsUInt32Number size)
1176 {
1177 OWNEDMEM* ptr1;
1178 void* ptr = _cmsMallocZero(ContextID, size);
1179
1180 if (ptr != NULL) {
1181
1182 ptr1 = (OWNEDMEM*) _cmsMallocZero(ContextID, sizeof(OWNEDMEM));
1183
1184 if (ptr1 == NULL) {
1185
1186 _cmsFree(ContextID, ptr);
1187 return NULL;
1188 }
1189
1190 ptr1-> Ptr = ptr;
1191 ptr1-> Next = it8 -> MemorySink;
1192 it8 -> MemorySink = ptr1;
1193 }
1194
1195 return ptr;
1196 }
1197
1198
1199 // Suballocator.
1200 static
1201 void* AllocChunk(cmsContext ContextID, cmsIT8* it8, cmsUInt32Number size)
1202 {
1203 cmsUInt32Number Free = it8 ->Allocator.BlockSize - it8 ->Allocator.Used;
1204 cmsUInt8Number* ptr;
1205
1206 size = _cmsALIGNMEM(size);
1207
1208 if (size > Free) {
1209
1210 if (it8 -> Allocator.BlockSize == 0)
1211
1212 it8 -> Allocator.BlockSize = 20*1024;
1213 else
1214 it8 ->Allocator.BlockSize *= 2;
1215
1216 if (it8 ->Allocator.BlockSize < size)
1217 it8 ->Allocator.BlockSize = size;
1218
1219 it8 ->Allocator.Used = 0;
1220 it8 ->Allocator.Block = (cmsUInt8Number*) AllocBigBlock(ContextID, it8, it8 ->Allocator.BlockSize);
1221 }
1222
1223 if (it8->Allocator.Block == NULL)
1224 return NULL;
1225
1226 ptr = it8 ->Allocator.Block + it8 ->Allocator.Used;
1227 it8 ->Allocator.Used += size;
1228
1229 return (void*) ptr;
1230
1231 }
1232
1233
1234 // Allocates a string
1235 static
1236 char *AllocString(cmsContext ContextID, cmsIT8* it8, const char* str)
1237 {
1238 cmsUInt32Number Size = (cmsUInt32Number) strlen(str)+1;
1239 char *ptr;
1240
1241
1242 ptr = (char *) AllocChunk(ContextID, it8, Size);
1243 if (ptr) memcpy(ptr, str, Size-1);
1244
1245 return ptr;
1246 }
1247
1248 // Searches through linked list
1249
1250 static
1251 cmsBool IsAvailableOnList(cmsContext ContextID, KEYVALUE* p, const char* Key, const char* Subkey, KEYVALUE** LastPtr)
1252 {
1253 cmsUNUSED_PARAMETER(ContextID);
1254 if (LastPtr) *LastPtr = p;
1255
1256 for (; p != NULL; p = p->Next) {
1257
1258 if (LastPtr) *LastPtr = p;
1259
1260 if (*Key != '#') { // Comments are ignored
1261
1262 if (cmsstrcasecmp(Key, p->Keyword) == 0)
1263 break;
1264 }
1265 }
1266
1267 if (p == NULL)
1268 return FALSE;
1269
1270 if (Subkey == 0)
1271 return TRUE;
1272
1273 for (; p != NULL; p = p->NextSubkey) {
1274
1275 if (p ->Subkey == NULL) continue;
1276
1277 if (LastPtr) *LastPtr = p;
1278
1279 if (cmsstrcasecmp(Subkey, p->Subkey) == 0)
1280 return TRUE;
1281 }
1282
1283 return FALSE;
1284 }
1285
1286
1287
1288 // Add a property into a linked list
1289 static
1290 KEYVALUE* AddToList(cmsContext ContextID, cmsIT8* it8, KEYVALUE** Head, const char *Key, const char *Subkey, const char* xValue, WRITEMODE WriteAs)
1291 {
1292 KEYVALUE* p;
1293 KEYVALUE* last;
1294
1295
1296 // Check if property is already in list
1297
1298 if (IsAvailableOnList(ContextID, *Head, Key, Subkey, &p)) {
1299
1300 // This may work for editing properties
1301
1302 if (cmsstrcasecmp(Key, "NUMBER_OF_FIELDS") == 0 ||
1303 cmsstrcasecmp(Key, "NUMBER_OF_SETS") == 0) {
1304
1305 SynError(ContextID, it8, "duplicate key <%s>", Key);
1306 return NULL;
1307 }
1308 }
1309 else {
1310
1311 last = p;
1312
1313 // Allocate the container
1314 p = (KEYVALUE*) AllocChunk(ContextID, it8, sizeof(KEYVALUE));
1315 if (p == NULL)
1316 {
1317 SynError(ContextID, it8, "AddToList: out of memory");
1318 return NULL;
1319 }
1320
1321 // Store name and value
1322 p->Keyword = AllocString(ContextID, it8, Key);
1323 p->Subkey = (Subkey == NULL) ? NULL : AllocString(ContextID, it8, Subkey);
1324
1325 // Keep the container in our list
1326 if (*Head == NULL) {
1327 *Head = p;
1328 }
1329 else
1330 {
1331 if (Subkey != NULL && last != NULL) {
1332
1333 last->NextSubkey = p;
1334
1335 // If Subkey is not null, then last is the last property with the same key,
1336 // but not necessarily is the last property in the list, so we need to move
1337 // to the actual list end
1338 while (last->Next != NULL)
1339 last = last->Next;
1340 }
1341
1342 if (last != NULL) last->Next = p;
1343 }
1344
1345 p->Next = NULL;
1346 p->NextSubkey = NULL;
1347 }
1348
1349 p->WriteAs = WriteAs;
1350
1351 if (xValue != NULL) {
1352
1353 p->Value = AllocString(ContextID, it8, xValue);
1354 }
1355 else {
1356 p->Value = NULL;
1357 }
1358
1359 return p;
1360 }
1361
1362 static
1363 KEYVALUE* AddAvailableProperty(cmsContext ContextID, cmsIT8* it8, const char* Key, WRITEMODE as)
1364 {
1365 return AddToList(ContextID, it8, &it8->ValidKeywords, Key, NULL, NULL, as);
1366 }
1367
1368
1369 static
1370 KEYVALUE* AddAvailableSampleID(cmsContext ContextID, cmsIT8* it8, const char* Key)
1371 {
1372 return AddToList(ContextID, it8, &it8->ValidSampleID, Key, NULL, NULL, WRITE_UNCOOKED);
1373 }
1374
1375
1376 static
1377 void AllocTable(cmsContext ContextID, cmsIT8* it8)
1378 {
1379 TABLE* t;
1380 cmsUNUSED_PARAMETER(ContextID);
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(cmsContext ContextID, 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(ContextID, it8);
1401 }
1402 else {
1403 SynError(ContextID, 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(ContextID, it8);
1425
1426 it8->MemoryBlock = NULL;
1427 it8->MemorySink = NULL;
1428
1429 it8->IsCUBE = FALSE;
1430
1431 it8 ->nTable = 0;
1432
1433 it8->Allocator.Used = 0;
1434 it8->Allocator.Block = NULL;
1435 it8->Allocator.BlockSize = 0;
1436
1437 it8->ValidKeywords = NULL;
1438 it8->ValidSampleID = NULL;
1439
1440 it8 -> sy = SUNDEFINED;
1441 it8 -> ch = ' ';
1442 it8 -> Source = NULL;
1443 it8 -> inum = 0;
1444 it8 -> dnum = 0.0;
1445
1446 it8->FileStack[0] = (FILECTX*)AllocChunk(ContextID, it8, sizeof(FILECTX));
1447 it8->IncludeSP = 0;
1448 it8 -> lineno = 1;
1449
1450 it8->id = StringAlloc(ContextID, it8, MAXSTR);
1451 it8->str = StringAlloc(ContextID, it8, MAXSTR);
1452
1453 strcpy(it8->DoubleFormatter, DEFAULT_DBL_FORMAT);
1454 cmsIT8SetSheetType(ContextID, (cmsHANDLE) it8, "CGATS.17");
1455
1456 // Initialize predefined properties & data
1457
1458 for (i=0; i < NUMPREDEFINEDPROPS; i++)
1459 AddAvailableProperty(ContextID, it8, PredefinedProperties[i].id, PredefinedProperties[i].as);
1460
1461 for (i=0; i < NUMPREDEFINEDSAMPLEID; i++)
1462 AddAvailableSampleID(ContextID, it8, PredefinedSampleID[i]);
1463
1464
1465 return (cmsHANDLE) it8;
1466 }
1467
1468
1469 const char* CMSEXPORT cmsIT8GetSheetType(cmsContext ContextID, cmsHANDLE hIT8)
1470 {
1471 return GetTable(ContextID, (cmsIT8*) hIT8)->SheetType;
1472 }
1473
1474 cmsBool CMSEXPORT cmsIT8SetSheetType(cmsContext ContextID, cmsHANDLE hIT8, const char* Type)
1475 {
1476 TABLE* t = GetTable(ContextID, (cmsIT8*) hIT8);
1477
1478 strncpy(t ->SheetType, Type, MAXSTR-1);
1479 t ->SheetType[MAXSTR-1] = 0;
1480 return TRUE;
1481 }
1482
1483 cmsBool CMSEXPORT cmsIT8SetComment(cmsContext ContextID, cmsHANDLE hIT8, const char* Val)
1484 {
1485 cmsIT8* it8 = (cmsIT8*) hIT8;
1486
1487 if (!Val) return FALSE;
1488 if (!*Val) return FALSE;
1489
1490 return AddToList(ContextID, it8, &GetTable(ContextID, it8)->HeaderList, "# ", NULL, Val, WRITE_UNCOOKED) != NULL;
1491 }
1492
1493 // Sets a property
1494 cmsBool CMSEXPORT cmsIT8SetPropertyStr(cmsContext ContextID, cmsHANDLE hIT8, const char* Key, const char *Val)
1495 {
1496 cmsIT8* it8 = (cmsIT8*) hIT8;
1497
1498 if (!Val) return FALSE;
1499 if (!*Val) return FALSE;
1500
1501 return AddToList(ContextID, it8, &GetTable(ContextID, it8)->HeaderList, Key, NULL, Val, WRITE_STRINGIFY) != NULL;
1502 }
1503
1504 cmsBool CMSEXPORT cmsIT8SetPropertyDbl(cmsContext ContextID, cmsHANDLE hIT8, const char* cProp, cmsFloat64Number Val)
1505 {
1506 cmsIT8* it8 = (cmsIT8*) hIT8;
1507 char Buffer[1024];
1508
1509 snprintf(Buffer, 1023, it8->DoubleFormatter, Val);
1510
1511 return AddToList(ContextID, it8, &GetTable(ContextID, it8)->HeaderList, cProp, NULL, Buffer, WRITE_UNCOOKED) != NULL;
1512 }
1513
1514 cmsBool CMSEXPORT cmsIT8SetPropertyHex(cmsContext ContextID, cmsHANDLE hIT8, const char* cProp, cmsUInt32Number Val)
1515 {
1516 cmsIT8* it8 = (cmsIT8*) hIT8;
1517 char Buffer[1024];
1518
1519 snprintf(Buffer, 1023, "%u", Val);
1520
1521 return AddToList(ContextID, it8, &GetTable(ContextID, it8)->HeaderList, cProp, NULL, Buffer, WRITE_HEXADECIMAL) != NULL;
1522 }
1523
1524 cmsBool CMSEXPORT cmsIT8SetPropertyUncooked(cmsContext ContextID, cmsHANDLE hIT8, const char* Key, const char* Buffer)
1525 {
1526 cmsIT8* it8 = (cmsIT8*) hIT8;
1527
1528 return AddToList(ContextID, it8, &GetTable(ContextID, it8)->HeaderList, Key, NULL, Buffer, WRITE_UNCOOKED) != NULL;
1529 }
1530
1531 cmsBool CMSEXPORT cmsIT8SetPropertyMulti(cmsContext ContextID, cmsHANDLE hIT8, const char* Key, const char* SubKey, const char *Buffer)
1532 {
1533 cmsIT8* it8 = (cmsIT8*) hIT8;
1534
1535 return AddToList(ContextID, it8, &GetTable(ContextID, it8)->HeaderList, Key, SubKey, Buffer, WRITE_PAIR) != NULL;
1536 }
1537
1538 // Gets a property
1539 const char* CMSEXPORT cmsIT8GetProperty(cmsContext ContextID, cmsHANDLE hIT8, const char* Key)
1540 {
1541 cmsIT8* it8 = (cmsIT8*) hIT8;
1542 KEYVALUE* p;
1543
1544 if (IsAvailableOnList(ContextID, GetTable(ContextID, it8) -> HeaderList, Key, NULL, &p))
1545 {
1546 return p -> Value;
1547 }
1548 return NULL;
1549 }
1550
1551
1552 cmsFloat64Number CMSEXPORT cmsIT8GetPropertyDbl(cmsContext ContextID, cmsHANDLE hIT8, const char* cProp)
1553 {
1554 const char *v = cmsIT8GetProperty(ContextID, hIT8, cProp);
1555
1556 if (v == NULL) return 0.0;
1557
1558 return ParseFloatNumber(v);
1559 }
1560
1561 const char* CMSEXPORT cmsIT8GetPropertyMulti(cmsContext ContextID, cmsHANDLE hIT8, const char* Key, const char *SubKey)
1562 {
1563 cmsIT8* it8 = (cmsIT8*) hIT8;
1564 KEYVALUE* p;
1565
1566 if (IsAvailableOnList(ContextID, GetTable(ContextID, it8) -> HeaderList, Key, SubKey, &p)) {
1567 return p -> Value;
1568 }
1569 return NULL;
1570 }
1571
1572 // ----------------------------------------------------------------- Datasets
1573
1574 // A safe atoi that returns 0 when NULL input is given
1575 static
1576 cmsInt32Number satoi(const char* b)
1577 {
1578 int n;
1579
1580 if (b == NULL) return 0;
1581
1582 n = atoi(b);
1583 if (n > 0x7fffffffL) return 0x7fffffffL;
1584 if (n < -0x7ffffffeL) return -0x7ffffffeL;
1585
1586 return (cmsInt32Number)n;
1587 }
1588
1589
1590 static
1591 cmsBool AllocateDataFormat(cmsContext ContextID, cmsIT8* it8)
1592 {
1593 TABLE* t = GetTable(ContextID, it8);
1594
1595 if (t -> DataFormat) return TRUE; // Already allocated
1596
1597 t -> nSamples = satoi(cmsIT8GetProperty(ContextID, it8, "NUMBER_OF_FIELDS"));
1598
1599 if (t -> nSamples <= 0) {
1600
1601 SynError(ContextID, it8, "AllocateDataFormat: Unknown NUMBER_OF_FIELDS");
1602 return FALSE;
1603 }
1604
1605 t -> DataFormat = (char**) AllocChunk (ContextID, it8, ((cmsUInt32Number) t->nSamples + 1) * sizeof(char *));
1606 if (t->DataFormat == NULL) {
1607
1608 SynError(ContextID, it8, "AllocateDataFormat: Unable to allocate dataFormat array");
1609 return FALSE;
1610 }
1611
1612 return TRUE;
1613 }
1614
1615 static
1616 const char *GetDataFormat(cmsContext ContextID, cmsIT8* it8, int n)
1617 {
1618 TABLE* t = GetTable(ContextID, it8);
1619
1620 if (t->DataFormat)
1621 return t->DataFormat[n];
1622
1623 return NULL;
1624 }
1625
1626 static
1627 cmsBool SetDataFormat(cmsContext ContextID, cmsIT8* it8, int n, const char *label)
1628 {
1629 TABLE* t = GetTable(ContextID, it8);
1630
1631 if (!t->DataFormat) {
1632
1633 if (!AllocateDataFormat(ContextID, it8))
1634 return FALSE;
1635 }
1636
1637 if (n > t -> nSamples) {
1638 SynError(ContextID, it8, "More than NUMBER_OF_FIELDS fields.");
1639 return FALSE;
1640 }
1641
1642 if (t->DataFormat) {
1643 t->DataFormat[n] = AllocString(ContextID, it8, label);
1644 if (t->DataFormat[n] == NULL) return FALSE;
1645 }
1646
1647 return TRUE;
1648 }
1649
1650
1651 cmsBool CMSEXPORT cmsIT8SetDataFormat(cmsContext ContextID, cmsHANDLE h, int n, const char *Sample)
1652 {
1653 cmsIT8* it8 = (cmsIT8*)h;
1654 return SetDataFormat(ContextID, it8, n, Sample);
1655 }
1656
1657 // Convert to binary
1658 static
1659 const char* satob(const char* v)
1660 {
1661 cmsUInt32Number x;
1662 static char buf[33];
1663 char *s = buf + 33;
1664
1665 if (v == NULL) return "0";
1666
1667 x = atoi(v);
1668 *--s = 0;
1669 if (!x) *--s = '0';
1670 for (; x; x /= 2) *--s = '0' + x%2;
1671
1672 return s;
1673 }
1674
1675
1676 static
1677 cmsBool AllocateDataSet(cmsContext ContextID, cmsIT8* it8)
1678 {
1679 TABLE* t = GetTable(ContextID, it8);
1680
1681 if (t -> Data) return TRUE; // Already allocated
1682
1683 t-> nSamples = satoi(cmsIT8GetProperty(ContextID, it8, "NUMBER_OF_FIELDS"));
1684 t-> nPatches = satoi(cmsIT8GetProperty(ContextID, it8, "NUMBER_OF_SETS"));
1685
1686 if (t -> nSamples < 0 || t->nSamples > 0x7ffe || t->nPatches < 0 || t->nPatches > 0x7ffe)
1687 {
1688 SynError(ContextID, it8, "AllocateDataSet: too much data");
1689 return FALSE;
1690 }
1691 else {
1692 // Some dumb analizers warns of possible overflow here, just take a look couple of lines above.
1693 t->Data = (char**)AllocChunk(ContextID, it8, ((cmsUInt32Number)t->nSamples + 1) * ((cmsUInt32Number)t->nPatches + 1) * sizeof(char*));
1694 if (t->Data == NULL) {
1695
1696 SynError(ContextID, it8, "AllocateDataSet: Unable to allocate data array");
1697 return FALSE;
1698 }
1699 }
1700
1701 return TRUE;
1702 }
1703
1704 static
1705 char* GetData(cmsContext ContextID, cmsIT8* it8, int nSet, int nField)
1706 {
1707 TABLE* t = GetTable(ContextID, it8);
1708 int nSamples = t -> nSamples;
1709 int nPatches = t -> nPatches;
1710
1711 if (nSet < 0 || nSet >= nPatches || nField < 0 || nField >= nSamples)
1712 return NULL;
1713
1714 if (!t->Data) return NULL;
1715 return t->Data [nSet * nSamples + nField];
1716 }
1717
1718 static
1719 cmsBool SetData(cmsContext ContextID, cmsIT8* it8, int nSet, int nField, const char *Val)
1720 {
1721 TABLE* t = GetTable(ContextID, it8);
1722
1723 if (!t->Data) {
1724 if (!AllocateDataSet(ContextID, it8)) return FALSE;
1725 }
1726
1727 if (!t->Data) return FALSE;
1728
1729 if (nSet > t -> nPatches || nSet < 0) {
1730
1731 return SynError(ContextID, it8, "Patch %d out of range, there are %d patches", nSet, t -> nPatches);
1732 }
1733
1734 if (nField > t ->nSamples || nField < 0) {
1735 return SynError(ContextID, it8, "Sample %d out of range, there are %d samples", nField, t ->nSamples);
1736
1737 }
1738
1739 t->Data [nSet * t -> nSamples + nField] = AllocString(ContextID, it8, Val);
1740 return TRUE;
1741 }
1742
1743
1744 // --------------------------------------------------------------- File I/O
1745
1746
1747 // Writes a string to file
1748 static
1749 void WriteStr(cmsContext ContextID, SAVESTREAM* f, const char *str)
1750 {
1751 cmsUInt32Number len;
1752 cmsUNUSED_PARAMETER(ContextID);
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(cmsContext ContextID, 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(ContextID, f, Buffer);
1800 va_end(args);
1801
1802 }
1803
1804 // Writes full header
1805 static
1806 void WriteHeader(cmsContext ContextID, cmsIT8* it8, SAVESTREAM* fp)
1807 {
1808 KEYVALUE* p;
1809 TABLE* t = GetTable(ContextID, it8);
1810
1811 // Writes the type
1812 WriteStr(ContextID, fp, t->SheetType);
1813 WriteStr(ContextID, 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(ContextID, fp, "#\n# ");
1822 for (Pt = p ->Value; *Pt; Pt++) {
1823
1824
1825 Writef(ContextID, fp, "%c", *Pt);
1826
1827 if (*Pt == '\n') {
1828 WriteStr(ContextID, fp, "# ");
1829 }
1830 }
1831
1832 WriteStr(ContextID, fp, "\n#\n");
1833 continue;
1834 }
1835
1836
1837 if (!IsAvailableOnList(ContextID, it8-> ValidKeywords, p->Keyword, NULL, NULL)) {
1838
1839 #ifdef CMS_STRICT_CGATS
1840 WriteStr(ContextID, fp, "KEYWORD\t\"");
1841 WriteStr(ContextID, fp, p->Keyword);
1842 WriteStr(ContextID, fp, "\"\n");
1843 #endif
1844
1845 AddAvailableProperty(ContextID, it8, p->Keyword, WRITE_UNCOOKED);
1846 }
1847
1848 WriteStr(ContextID, fp, p->Keyword);
1849 if (p->Value) {
1850
1851 switch (p ->WriteAs) {
1852
1853 case WRITE_UNCOOKED:
1854 Writef(ContextID, fp, "\t%s", p ->Value);
1855 break;
1856
1857 case WRITE_STRINGIFY:
1858 Writef(ContextID, fp, "\t\"%s\"", p->Value );
1859 break;
1860
1861 case WRITE_HEXADECIMAL:
1862 Writef(ContextID, fp, "\t0x%X", satoi(p ->Value));
1863 break;
1864
1865 case WRITE_BINARY:
1866 Writef(ContextID, fp, "\t0b%s", satob(p ->Value));
1867 break;
1868
1869 case WRITE_PAIR:
1870 Writef(ContextID, fp, "\t\"%s,%s\"", p->Subkey, p->Value);
1871 break;
1872
1873 default: SynError(ContextID, it8, "Unknown write mode %d", p ->WriteAs);
1874 return;
1875 }
1876 }
1877
1878 WriteStr(ContextID, fp, "\n");
1879 }
1880
1881 }
1882
1883
1884 // Writes the data format
1885 static
1886 void WriteDataFormat(cmsContext ContextID, SAVESTREAM* fp, cmsIT8* it8)
1887 {
1888 int i, nSamples;
1889 TABLE* t = GetTable(ContextID, it8);
1890
1891 if (!t -> DataFormat) return;
1892
1893 WriteStr(ContextID, fp, "BEGIN_DATA_FORMAT\n");
1894 WriteStr(ContextID, fp, " ");
1895 nSamples = satoi(cmsIT8GetProperty(ContextID, it8, "NUMBER_OF_FIELDS"));
1896
1897 if (nSamples <= t->nSamples) {
1898
1899 for (i = 0; i < nSamples; i++) {
1900
1901 WriteStr(ContextID, fp, t->DataFormat[i]);
1902 WriteStr(ContextID, fp, ((i == (nSamples - 1)) ? "\n" : "\t"));
1903 }
1904 }
1905
1906 WriteStr (ContextID, fp, "END_DATA_FORMAT\n");
1907 }
1908
1909
1910 // Writes data array
1911 static
1912 void WriteData(cmsContext ContextID, SAVESTREAM* fp, cmsIT8* it8)
1913 {
1914 int i, j, nPatches;
1915 TABLE* t = GetTable(ContextID, it8);
1916
1917 if (!t->Data) return;
1918
1919 WriteStr (ContextID, fp, "BEGIN_DATA\n");
1920
1921 nPatches = satoi(cmsIT8GetProperty(ContextID, it8, "NUMBER_OF_SETS"));
1922
1923 if (nPatches <= t->nPatches) {
1924
1925 for (i = 0; i < nPatches; i++) {
1926
1927 WriteStr(ContextID, 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(ContextID, fp, "\"\"");
1934 else {
1935 // If value contains whitespace, enclose within quote
1936
1937 if (strchr(ptr, ' ') != NULL) {
1938
1939 WriteStr(ContextID, fp, "\"");
1940 WriteStr(ContextID, fp, ptr);
1941 WriteStr(ContextID, fp, "\"");
1942 }
1943 else
1944 WriteStr(ContextID, fp, ptr);
1945 }
1946
1947 WriteStr(ContextID, fp, ((j == (t->nSamples - 1)) ? "\n" : "\t"));
1948 }
1949 }
1950 }
1951 WriteStr(ContextID, fp, "END_DATA\n");
1952 }
1953
1954
1955
1956 // Saves whole file
1957 cmsBool CMSEXPORT cmsIT8SaveToFile(cmsContext ContextID, 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, "wt");
1966 if (!sd.stream) return FALSE;
1967
1968 for (i=0; i < it8 ->TablesCount; i++) {
1969
1970 TABLE* t;
1971
1972 if (cmsIT8SetTable(ContextID, hIT8, i) < 0) goto Error;
1973
1974 /**
1975 * Check for wrong data
1976 */
1977 t = GetTable(ContextID, it8);
1978 if (t->Data == NULL) goto Error;
1979 if (t->DataFormat == NULL) goto Error;
1980
1981 WriteHeader(ContextID, it8, &sd);
1982 WriteDataFormat(ContextID, &sd, it8);
1983 WriteData(ContextID, &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(cmsContext ContextID, 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(ContextID, hIT8, i);
2021 WriteHeader(ContextID, it8, &sd);
2022 WriteDataFormat(ContextID, &sd, it8);
2023 WriteData(ContextID, &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(cmsContext ContextID, cmsIT8* it8)
2041 {
2042 int iField = 0;
2043 TABLE* t = GetTable(ContextID, it8);
2044
2045 InSymbol(ContextID, it8); // Eats "BEGIN_DATA_FORMAT"
2046 CheckEOLN(ContextID, 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(ContextID, it8, "Sample type expected");
2056 }
2057
2058 if (!SetDataFormat(ContextID, it8, iField, StringPtr(it8->id))) return FALSE;
2059 iField++;
2060
2061 InSymbol(ContextID, it8);
2062 SkipEOLN(ContextID, it8);
2063 }
2064
2065 SkipEOLN(ContextID, it8);
2066 Skip(ContextID, it8, SEND_DATA_FORMAT);
2067 SkipEOLN(ContextID, it8);
2068
2069 if (iField != t ->nSamples) {
2070 SynError(ContextID, 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 (cmsContext ContextID, cmsIT8* it8)
2082 {
2083 int iField = 0;
2084 int iSet = 0;
2085 char Buffer[256];
2086 TABLE* t = GetTable(ContextID, it8);
2087
2088 InSymbol(ContextID, it8); // Eats "BEGIN_DATA"
2089 CheckEOLN(ContextID, it8);
2090
2091 if (!t->Data) {
2092 if (!AllocateDataSet(ContextID, 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(ContextID, it8, iSet, iField, StringPtr(it8->id)))
2111 return FALSE;
2112 break;
2113
2114 case SSTRING:
2115 if (!SetData(ContextID, it8, iSet, iField, StringPtr(it8->str)))
2116 return FALSE;
2117 break;
2118
2119 default:
2120
2121 if (!GetVal(ContextID, it8, Buffer, 255, "Sample data expected"))
2122 return FALSE;
2123
2124 if (!SetData(ContextID, it8, iSet, iField, Buffer))
2125 return FALSE;
2126 }
2127
2128 iField++;
2129
2130 InSymbol(ContextID, it8);
2131 SkipEOLN(ContextID, it8);
2132 }
2133 }
2134
2135 SkipEOLN(ContextID, it8);
2136 Skip(ContextID, it8, SEND_DATA);
2137 SkipEOLN(ContextID, it8);
2138
2139 // Check for data completion.
2140
2141 if ((iSet+1) != t -> nPatches)
2142 return SynError(ContextID, 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(cmsContext ContextID, 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(ContextID, it8);
2167 if (!GetVal(ContextID, it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
2168 if (!AddAvailableProperty(ContextID, it8, Buffer, WRITE_UNCOOKED)) return FALSE;
2169 InSymbol(ContextID, it8);
2170 break;
2171
2172
2173 case SDATA_FORMAT_ID:
2174 InSymbol(ContextID, it8);
2175 if (!GetVal(ContextID, it8, Buffer, MAXSTR-1, "Keyword expected")) return FALSE;
2176 if (!AddAvailableSampleID(ContextID, it8, Buffer)) return FALSE;
2177 InSymbol(ContextID, 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(ContextID, it8->ValidKeywords, VarName, NULL, &Key)) {
2186
2187 #ifdef CMS_STRICT_CGATS
2188 return SynError(ContextID, it8, "Undefined keyword '%s'", VarName);
2189 #else
2190 Key = AddAvailableProperty(ContextID, it8, VarName, WRITE_UNCOOKED);
2191 if (Key == NULL) return FALSE;
2192 #endif
2193 }
2194
2195 InSymbol(ContextID, it8);
2196 if (!GetVal(ContextID, it8, Buffer, MAXSTR - 1, "Property data expected")) return FALSE;
2197
2198 if (Key->WriteAs != WRITE_PAIR) {
2199 AddToList(ContextID, it8, &GetTable(ContextID, 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(ContextID, 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(ContextID, 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(ContextID, it8, "Invalid value for property '%s'.", VarName);
2237 AddToList(ContextID, it8, &GetTable(ContextID, it8)->HeaderList, VarName, Subkey, Value, WRITE_PAIR);
2238 }
2239 }
2240
2241 InSymbol(ContextID, it8);
2242 break;
2243
2244
2245 case SEOLN: break;
2246
2247 default:
2248 return SynError(ContextID, it8, "expected keyword or identifier");
2249 }
2250
2251 SkipEOLN(ContextID, 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(cmsContext ContextID, 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(ContextID, it8);
2290
2291 SkipEOLN(ContextID, 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(ContextID, it8)) return FALSE;
2300 break;
2301
2302 case SBEGIN_DATA:
2303
2304 if (!DataSection(ContextID, it8)) return FALSE;
2305
2306 if (it8 -> sy != SEOF) {
2307
2308 AllocTable(ContextID, 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(ContextID, it8, StringPtr(it8 ->id));
2327 InSymbol(ContextID, it8);
2328 }
2329 else
2330 {
2331 // It is not. Just continue
2332 cmsIT8SetSheetType(ContextID, it8, "");
2333 }
2334 }
2335 else
2336 // Validate quoted strings
2337 if (it8 ->sy == SSTRING) {
2338 cmsIT8SetSheetType(ContextID, it8, StringPtr(it8 ->str));
2339 InSymbol(ContextID, it8);
2340 }
2341 }
2342
2343 }
2344 break;
2345
2346 case SEOLN:
2347 SkipEOLN(ContextID, it8);
2348 break;
2349
2350 default:
2351 if (!HeaderSection(ContextID, 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(cmsContext ContextID, 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(ContextID, 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(ContextID, 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(ContextID, 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(ContextID, 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, "rt");
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(ContextID, 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(ContextID, it8, type-1)) {
2535
2536 cmsIT8Free(ContextID, hIT8);
2537 return NULL;
2538 }
2539
2540 CookPointers(ContextID, 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, "rt");
2570
2571 if (!it8 ->FileStack[0]->Stream) {
2572 cmsIT8Free(ContextID, 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(ContextID, it8, type-1)) {
2581
2582 fclose(it8 ->FileStack[0]->Stream);
2583 cmsIT8Free(ContextID, hIT8);
2584 return NULL;
2585 }
2586
2587 CookPointers(ContextID, it8);
2588 it8 ->nTable = 0;
2589
2590 if (fclose(it8 ->FileStack[0]->Stream)!= 0) {
2591 cmsIT8Free(ContextID, hIT8);
2592 return NULL;
2593 }
2594
2595 return hIT8;
2596
2597 }
2598
2599 int CMSEXPORT cmsIT8EnumDataFormat(cmsContext ContextID, cmsHANDLE hIT8, char ***SampleNames)
2600 {
2601 cmsIT8* it8 = (cmsIT8*) hIT8;
2602 TABLE* t;
2603
2604 _cmsAssert(hIT8 != NULL);
2605
2606 t = GetTable(ContextID, it8);
2607
2608 if (SampleNames)
2609 *SampleNames = t -> DataFormat;
2610 return t -> nSamples;
2611 }
2612
2613
2614 cmsUInt32Number CMSEXPORT cmsIT8EnumProperties(cmsContext ContextID, 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(ContextID, 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(ContextID, 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(cmsContext ContextID, 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(ContextID, it8);
2661
2662 if(!IsAvailableOnList(ContextID, 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(ContextID, 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(cmsContext ContextID, cmsIT8* it8, const char* cPatch)
2693 {
2694 int i;
2695 const char *data;
2696 TABLE* t = GetTable(ContextID, it8);
2697
2698 for (i=0; i < t-> nPatches; i++) {
2699
2700 data = GetData(ContextID, it8, i, t->SampleID);
2701
2702 if (data != NULL) {
2703
2704 if (cmsstrcasecmp(data, cPatch) == 0)
2705 return i;
2706 }
2707 }
2708
2709 // SynError(ContextID, it8, "Couldn't find patch '%s'\n", cPatch);
2710 return -1;
2711 }
2712
2713
2714 static
2715 int LocateEmptyPatch(cmsContext ContextID, cmsIT8* it8)
2716 {
2717 int i;
2718 const char *data;
2719 TABLE* t = GetTable(ContextID, it8);
2720
2721 for (i=0; i < t-> nPatches; i++) {
2722
2723 data = GetData(ContextID, 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(cmsContext ContextID, cmsIT8* it8, const char* cSample)
2735 {
2736 int i;
2737 const char *fld;
2738 TABLE* t = GetTable(ContextID, it8);
2739
2740 for (i=0; i < t->nSamples; i++) {
2741
2742 fld = GetDataFormat(ContextID, 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(cmsContext ContextID, cmsHANDLE hIT8, const char* cSample)
2755 {
2756 cmsIT8* it8 = (cmsIT8*) hIT8;
2757
2758 _cmsAssert(hIT8 != NULL);
2759
2760 return LocateSample(ContextID, it8, cSample);
2761 }
2762
2763
2764
2765 const char* CMSEXPORT cmsIT8GetDataRowCol(cmsContext ContextID, cmsHANDLE hIT8, int row, int col)
2766 {
2767 cmsIT8* it8 = (cmsIT8*) hIT8;
2768
2769 _cmsAssert(hIT8 != NULL);
2770
2771 return GetData(ContextID, it8, row, col);
2772 }
2773
2774
2775 cmsFloat64Number CMSEXPORT cmsIT8GetDataRowColDbl(cmsContext ContextID, cmsHANDLE hIT8, int row, int col)
2776 {
2777 const char* Buffer;
2778
2779 Buffer = cmsIT8GetDataRowCol(ContextID, hIT8, row, col);
2780
2781 if (Buffer == NULL) return 0.0;
2782
2783 return ParseFloatNumber(Buffer);
2784 }
2785
2786
2787 cmsBool CMSEXPORT cmsIT8SetDataRowCol(cmsContext ContextID, cmsHANDLE hIT8, int row, int col, const char* Val)
2788 {
2789 cmsIT8* it8 = (cmsIT8*) hIT8;
2790
2791 _cmsAssert(hIT8 != NULL);
2792
2793 return SetData(ContextID, it8, row, col, Val);
2794 }
2795
2796
2797 cmsBool CMSEXPORT cmsIT8SetDataRowColDbl(cmsContext ContextID, 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(ContextID, it8, row, col, Buff);
2807 }
2808
2809
2810
2811 const char* CMSEXPORT cmsIT8GetData(cmsContext ContextID, 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(ContextID, it8, cSample);
2819 if (iField < 0) {
2820 return NULL;
2821 }
2822
2823 iSet = LocatePatch(ContextID, it8, cPatch);
2824 if (iSet < 0) {
2825 return NULL;
2826 }
2827
2828 return GetData(ContextID, it8, iSet, iField);
2829 }
2830
2831
2832 cmsFloat64Number CMSEXPORT cmsIT8GetDataDbl(cmsContext ContextID, cmsHANDLE it8, const char* cPatch, const char* cSample)
2833 {
2834 const char* Buffer;
2835
2836 Buffer = cmsIT8GetData(ContextID, it8, cPatch, cSample);
2837
2838 return ParseFloatNumber(Buffer);
2839 }
2840
2841
2842
2843 cmsBool CMSEXPORT cmsIT8SetData(cmsContext ContextID, 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(ContextID, it8);
2852
2853 iField = LocateSample(ContextID, it8, cSample);
2854
2855 if (iField < 0)
2856 return FALSE;
2857
2858 if (t-> nPatches == 0) {
2859
2860 if (!AllocateDataFormat(ContextID, it8))
2861 return FALSE;
2862
2863 if (!AllocateDataSet(ContextID, it8))
2864 return FALSE;
2865
2866 CookPointers(ContextID, it8);
2867 }
2868
2869 if (cmsstrcasecmp(cSample, "SAMPLE_ID") == 0) {
2870
2871 iSet = LocateEmptyPatch(ContextID, it8);
2872 if (iSet < 0) {
2873 return SynError(ContextID, it8, "Couldn't add more patches '%s'\n", cPatch);
2874 }
2875
2876 iField = t -> SampleID;
2877 }
2878 else {
2879 iSet = LocatePatch(ContextID, it8, cPatch);
2880 if (iSet < 0) {
2881 return FALSE;
2882 }
2883 }
2884
2885 return SetData(ContextID, it8, iSet, iField, Val);
2886 }
2887
2888
2889 cmsBool CMSEXPORT cmsIT8SetDataDbl(cmsContext ContextID, 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(ContextID, hIT8, cPatch, cSample, Buff);
2900 }
2901
2902 // Buffer should get MAXSTR at least
2903
2904 const char* CMSEXPORT cmsIT8GetPatchName(cmsContext ContextID, 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(ContextID, it8);
2913 Data = GetData(ContextID, 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(cmsContext ContextID, cmsHANDLE hIT8, const char *cPatch)
2924 {
2925 _cmsAssert(hIT8 != NULL);
2926 cmsUNUSED_PARAMETER(ContextID);
2927
2928 return LocatePatch(ContextID, (cmsIT8*)hIT8, cPatch);
2929 }
2930
2931 cmsUInt32Number CMSEXPORT cmsIT8TableCount(cmsContext ContextID, cmsHANDLE hIT8)
2932 {
2933 cmsIT8* it8 = (cmsIT8*) hIT8;
2934 cmsUNUSED_PARAMETER(ContextID);
2935
2936 _cmsAssert(hIT8 != NULL);
2937
2938 return it8 ->TablesCount;
2939 }
2940
2941 // This handles the "LABEL" extension.
2942 // Label, nTable, Type
2943
2944 int CMSEXPORT cmsIT8SetTableByLabel(cmsContext ContextID, cmsHANDLE hIT8, const char* cSet, const char* cField, const char* ExpectedType)
2945 {
2946 const char* cLabelFld;
2947 char Type[256], Label[256];
2948 cmsUInt32Number nTable;
2949
2950 _cmsAssert(hIT8 != NULL);
2951
2952 if (cField != NULL && *cField == 0)
2953 cField = "LABEL";
2954
2955 if (cField == NULL)
2956 cField = "LABEL";
2957
2958 cLabelFld = cmsIT8GetData(ContextID, hIT8, cSet, cField);
2959 if (!cLabelFld) return -1;
2960
2961 if (sscanf(cLabelFld, "%255s %u %255s", Label, &nTable, Type) != 3)
2962 return -1;
2963
2964 if (ExpectedType != NULL && *ExpectedType == 0)
2965 ExpectedType = NULL;
2966
2967 if (ExpectedType) {
2968
2969 if (cmsstrcasecmp(Type, ExpectedType) != 0) return -1;
2970 }
2971
2972 return cmsIT8SetTable(ContextID, hIT8, nTable);
2973 }
2974
2975
2976 cmsBool CMSEXPORT cmsIT8SetIndexColumn(cmsContext ContextID, cmsHANDLE hIT8, const char* cSample)
2977 {
2978 cmsIT8* it8 = (cmsIT8*) hIT8;
2979 int pos;
2980
2981 _cmsAssert(hIT8 != NULL);
2982
2983 pos = LocateSample(ContextID, it8, cSample);
2984 if(pos == -1)
2985 return FALSE;
2986
2987 it8->Tab[it8->nTable].SampleID = pos;
2988 return TRUE;
2989 }
2990
2991
2992 void CMSEXPORT cmsIT8DefineDblFormat(cmsContext ContextID, cmsHANDLE hIT8, const char* Formatter)
2993 {
2994 cmsIT8* it8 = (cmsIT8*) hIT8;
2995 cmsUNUSED_PARAMETER(ContextID);
2996
2997 _cmsAssert(hIT8 != NULL);
2998
2999 if (Formatter == NULL)
3000 strcpy(it8->DoubleFormatter, DEFAULT_DBL_FORMAT);
3001 else
3002 strncpy(it8->DoubleFormatter, Formatter, sizeof(it8->DoubleFormatter));
3003
3004 it8 ->DoubleFormatter[sizeof(it8 ->DoubleFormatter)-1] = 0;
3005 }
3006
3007
3008 static
3009 cmsBool ReadNumbers(cmsContext ContextID, cmsIT8* cube, int n, cmsFloat64Number* arr)
3010 {
3011 int i;
3012
3013 for (i = 0; i < n; i++) {
3014
3015 if (cube->sy == SINUM)
3016 arr[i] = cube->inum;
3017 else
3018 if (cube->sy == SDNUM)
3019 arr[i] = cube->dnum;
3020 else
3021 return SynError(ContextID, cube, "Number expected");
3022
3023 InSymbol(ContextID, cube);
3024 }
3025
3026 return CheckEOLN(ContextID, cube);
3027 }
3028
3029 static
3030 cmsBool ParseCube(cmsContext ContextID, cmsIT8* cube, cmsStage** Shaper, cmsStage** CLUT, char title[])
3031 {
3032 cmsFloat64Number domain_min[3] = { 0, 0, 0 };
3033 cmsFloat64Number domain_max[3] = { 1.0, 1.0, 1.0 };
3034 cmsFloat64Number check_0_1[2] = { 0, 1.0 };
3035 int shaper_size = 0;
3036 int lut_size = 0;
3037 int i;
3038
3039 InSymbol(ContextID, cube);
3040
3041 while (cube->sy != SEOF) {
3042 switch (cube->sy)
3043 {
3044 // Set profile description
3045 case STITLE:
3046 InSymbol(ContextID, cube);
3047 if (!Check(ContextID, cube, SSTRING, "Title string expected")) return FALSE;
3048 memcpy(title, StringPtr(cube->str), MAXSTR);
3049 title[MAXSTR - 1] = 0;
3050 InSymbol(ContextID, cube);
3051 break;
3052
3053 // Define domain
3054 case SDOMAIN_MIN:
3055 InSymbol(ContextID, cube);
3056 if (!ReadNumbers(ContextID, cube, 3, domain_min)) return FALSE;
3057 break;
3058
3059 case SDOMAIN_MAX:
3060 InSymbol(ContextID, cube);
3061 if (!ReadNumbers(ContextID, cube, 3, domain_max)) return FALSE;
3062 break;
3063
3064 // Define shaper
3065 case S_LUT1D_SIZE:
3066 InSymbol(ContextID, cube);
3067 if (!Check(ContextID, cube, SINUM, "Shaper size expected")) return FALSE;
3068 shaper_size = cube->inum;
3069 InSymbol(ContextID, cube);
3070 break;
3071
3072 // Deefine CLUT
3073 case S_LUT3D_SIZE:
3074 InSymbol(ContextID, cube);
3075 if (!Check(ContextID, cube, SINUM, "LUT size expected")) return FALSE;
3076 lut_size = cube->inum;
3077 InSymbol(ContextID, cube);
3078 break;
3079
3080 // Range. If present, has to be 0..1.0
3081 case S_LUT1D_INPUT_RANGE:
3082 case S_LUT3D_INPUT_RANGE:
3083 InSymbol(ContextID, cube);
3084 if (!ReadNumbers(ContextID, cube, 2, check_0_1)) return FALSE;
3085 if (check_0_1[0] != 0 || check_0_1[1] != 1.0) {
3086 return SynError(ContextID, cube, "Unsupported format");
3087 }
3088 break;
3089
3090 case SEOLN:
3091 InSymbol(ContextID, cube);
3092 break;
3093
3094 default:
3095 case S_LUT_IN_VIDEO_RANGE:
3096 case S_LUT_OUT_VIDEO_RANGE:
3097 return SynError(ContextID, cube, "Unsupported format");
3098
3099 // Read and create tables
3100 case SINUM:
3101 case SDNUM:
3102
3103 if (shaper_size > 0) {
3104
3105 cmsToneCurve* curves[3];
3106 cmsFloat32Number* shapers = (cmsFloat32Number*)_cmsMalloc(ContextID, 3 * shaper_size * sizeof(cmsFloat32Number));
3107 if (shapers == NULL) return FALSE;
3108
3109 for (i = 0; i < shaper_size; i++) {
3110
3111 cmsFloat64Number nums[3];
3112
3113 if (!ReadNumbers(ContextID, cube, 3, nums)) return FALSE;
3114
3115 shapers[i + 0] = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0]));
3116 shapers[i + 1 * shaper_size] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1]));
3117 shapers[i + 2 * shaper_size] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2]));
3118 }
3119
3120 for (i = 0; i < 3; i++) {
3121
3122 curves[i] = cmsBuildTabulatedToneCurveFloat(ContextID, shaper_size,
3123 &shapers[i * shaper_size]);
3124 if (curves[i] == NULL) return FALSE;
3125 }
3126
3127 *Shaper = cmsStageAllocToneCurves(ContextID, 3, curves);
3128
3129 cmsFreeToneCurveTriple(ContextID, curves);
3130 }
3131
3132 if (lut_size > 0) {
3133
3134 int nodes = lut_size * lut_size * lut_size;
3135
3136 cmsFloat32Number* lut_table = _cmsMalloc(ContextID, nodes * 3 * sizeof(cmsFloat32Number));
3137 if (lut_table == NULL) return FALSE;
3138
3139 for (i = 0; i < nodes; i++) {
3140
3141 cmsFloat64Number nums[3];
3142
3143 if (!ReadNumbers(ContextID, cube, 3, nums)) return FALSE;
3144
3145 lut_table[i * 3 + 2] = (cmsFloat32Number) ((nums[0] - domain_min[0]) / (domain_max[0] - domain_min[0]));
3146 lut_table[i * 3 + 1] = (cmsFloat32Number) ((nums[1] - domain_min[1]) / (domain_max[1] - domain_min[1]));
3147 lut_table[i * 3 + 0] = (cmsFloat32Number) ((nums[2] - domain_min[2]) / (domain_max[2] - domain_min[2]));
3148 }
3149
3150 *CLUT = cmsStageAllocCLutFloat(ContextID, lut_size, 3, 3, lut_table);
3151 _cmsFree(ContextID, lut_table);
3152 }
3153
3154 if (!Check(ContextID, cube, SEOF, "Extra symbols found in file")) return FALSE;
3155 }
3156 }
3157
3158 return TRUE;
3159 }
3160
3161 // Share the parser to read .cube format and create RGB devicelink profiles
3162 cmsHPROFILE CMSEXPORT cmsCreateDeviceLinkFromCubeFile(cmsContext ContextID, const char* cFileName)
3163 {
3164 cmsHPROFILE hProfile = NULL;
3165 cmsIT8* cube = NULL;
3166 cmsPipeline* Pipeline = NULL;
3167 cmsStage* CLUT = NULL;
3168 cmsStage* Shaper = NULL;
3169 cmsMLU* DescriptionMLU = NULL;
3170 char title[MAXSTR];
3171
3172 _cmsAssert(cFileName != NULL);
3173
3174 cube = (cmsIT8*) cmsIT8Alloc(ContextID);
3175 if (!cube) return NULL;
3176
3177 cube->IsCUBE = TRUE;
3178 cube->FileStack[0]->Stream = fopen(cFileName, "rt");
3179
3180 if (!cube->FileStack[0]->Stream) goto Done;
3181
3182 strncpy(cube->FileStack[0]->FileName, cFileName, cmsMAX_PATH - 1);
3183 cube->FileStack[0]->FileName[cmsMAX_PATH - 1] = 0;
3184
3185 if (!ParseCube(ContextID, cube, &Shaper, &CLUT, title)) goto Done;
3186
3187 // Success on parsing, let's create the profile
3188 hProfile = cmsCreateProfilePlaceholder(ContextID);
3189 if (!hProfile) goto Done;
3190
3191 cmsSetProfileVersion(ContextID, hProfile, 4.4);
3192
3193 cmsSetDeviceClass(ContextID, hProfile, cmsSigLinkClass);
3194 cmsSetColorSpace(ContextID, hProfile, cmsSigRgbData);
3195 cmsSetPCS(ContextID, hProfile, cmsSigRgbData);
3196
3197 cmsSetHeaderRenderingIntent(ContextID, hProfile, INTENT_PERCEPTUAL);
3198
3199 // Creates a Pipeline to hold CLUT and shaper
3200 Pipeline = cmsPipelineAlloc(ContextID, 3, 3);
3201 if (Pipeline == NULL) goto Done;
3202
3203 // Populates the pipeline
3204 if (Shaper != NULL) {
3205 if (!cmsPipelineInsertStage(ContextID, Pipeline, cmsAT_BEGIN, Shaper))
3206 goto Done;
3207 }
3208
3209 if (CLUT != NULL) {
3210 if (!cmsPipelineInsertStage(ContextID, Pipeline, cmsAT_END, CLUT))
3211 goto Done;
3212 }
3213
3214 // Propagate the description. We put no copyright because we know
3215 // nothing on the copyrighted state of the .cube
3216 DescriptionMLU = cmsMLUalloc(ContextID, 1);
3217 if (!cmsMLUsetUTF8(ContextID, DescriptionMLU, cmsNoLanguage, cmsNoCountry, title)) goto Done;
3218
3219 // Flush the tags
3220 if (!cmsWriteTag(ContextID, hProfile, cmsSigProfileDescriptionTag, DescriptionMLU)) goto Done;
3221 if (!cmsWriteTag(ContextID, hProfile, cmsSigAToB0Tag, (void*)Pipeline)) goto Done;
3222
3223 Done:
3224
3225 if (DescriptionMLU != NULL)
3226 cmsMLUfree(ContextID, DescriptionMLU);
3227
3228 if (Pipeline != NULL)
3229 cmsPipelineFree(ContextID, Pipeline);
3230
3231 cmsIT8Free(ContextID, (cmsHANDLE) cube);
3232
3233 return hProfile;
3234 }