Diff for /parser3/src/classes/image.C between versions 1.113.12.1 and 1.163

version 1.113.12.1, 2006/04/09 12:25:03 version 1.163, 2017/05/16 22:39:19
Line 1 Line 1
 /** @file  /** @file
         Parser: @b image parser class.          Parser: @b image parser class.
   
         Copyright(c) 2001-2005 ArtLebedev Group (http://www.artlebedev.com)          Copyright (c) 2001-2017 Art. Lebedev Studio (http://www.artlebedev.com)
         Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)          Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
 */  */
   
 static const char * const IDENT_IMAGE_C="$Date$";  
   
 /*  /*
         jpegsize: gets the width and height (in pixels) of a jpeg file          jpegsize: gets the width and height (in pixels) of a jpeg file
         Andrew Tong, werdna@ugcs.caltech.edu           February 14, 1995          Andrew Tong, werdna@ugcs.caltech.edu           February 14, 1995
Line 17  static const char * const IDENT_IMAGE_C= Line 15  static const char * const IDENT_IMAGE_C=
 */  */
   
 #include "pa_config_includes.h"  #include "pa_config_includes.h"
   
 #include "pa_vmethod_frame.h"  
   
 #include "gif.h"  #include "gif.h"
   
   #include "pa_vmethod_frame.h"
 #include "pa_common.h"  #include "pa_common.h"
 #include "pa_request.h"  #include "pa_request.h"
 #include "pa_vfile.h"  #include "pa_vfile.h"
 #include "pa_vimage.h"  #include "pa_vimage.h"
 #include "pa_vdate.h"  #include "pa_vdate.h"
 #include "pa_table.h"  #include "pa_table.h"
   #include "pa_charsets.h"
   
   volatile const char * IDENT_IMAGE_C="$Id$";
   
   // defines
   
   static const String spacebar_width_name("space");
   static const String monospace_width_name("width");
   static const String letter_spacing_name("spacing");
   
 // class  // class
   
 class MImage: public Methoded {  class MImage: public Methoded {
 public: // VStateless_class  public: // VStateless_class
         Value* create_new_value(Pool&, HashStringValue&) { return new VImage(); }          Value* create_new_value(Pool&) { return new VImage(); }
   
 public:  public:
         MImage();          MImage();
   
 public: // Methoded  
         bool used_directly() { return true; }  
   
 };  };
   
 // globals  // globals
   
 DECLARE_CLASS_VAR(image, new MImage, 0);  DECLARE_CLASS_VAR(image, new MImage);
   
 // helpers  // helpers
   
   #define EXIF_TAG(tag, name) put(tag, #name);
   
 /// value of exif tag -> it's value  /// value of exif tag -> it's value
 class EXIF_tag_value2name: public Hash<int, const char*> {  class EXIF_tag_value2name: public Hash<int, const char*> {
 public:  public:
         EXIF_tag_value2name() {          EXIF_tag_value2name() {
         // image JPEG Exif          // image JPEG Exif
                 #define EXIF_TAG(tag, name) \  
                         put(tag, #name);  
                 // Tags used by IFD0 (main image)                  // Tags used by IFD0 (main image)
                 EXIF_TAG(0x010e,        ImageDescription);                  EXIF_TAG(0x010e,        ImageDescription);
                 EXIF_TAG(0x010f,        Make);                  EXIF_TAG(0x010f,        Make);
Line 130  public: Line 131  public:
                 EXIF_TAG(0x83bb,        IPTC/NAA);                  EXIF_TAG(0x83bb,        IPTC/NAA);
                 EXIF_TAG(0x8773,        InterColorProfile);                  EXIF_TAG(0x8773,        InterColorProfile);
                 EXIF_TAG(0x8824,        SpectralSensitivity);                  EXIF_TAG(0x8824,        SpectralSensitivity);
                 EXIF_TAG(0x8825,        GPSInfo);                  //EXIF_TAG(0x8825,      GPSInfo);
                 EXIF_TAG(0x8828,        OECF);                  EXIF_TAG(0x8828,        OECF);
                 EXIF_TAG(0x8829,        Interlace);                  EXIF_TAG(0x8829,        Interlace);
                 EXIF_TAG(0x882a,        TimeZoneOffset);                  EXIF_TAG(0x882a,        TimeZoneOffset);
Line 147  public: Line 148  public:
                 EXIF_TAG(0xa20b,        FlashEnergy);                  EXIF_TAG(0xa20b,        FlashEnergy);
                 EXIF_TAG(0xa20c,        SpatialFrequencyResponse);                  EXIF_TAG(0xa20c,        SpatialFrequencyResponse);
                 EXIF_TAG(0xa214,        SubjectLocation);                  EXIF_TAG(0xa214,        SubjectLocation);
                 #undef EXIF_TAG  
                   // additional things added by misha@
                   EXIF_TAG(0x0100,        ImageWidth);
                   EXIF_TAG(0x0101,        ImageLength);
                   EXIF_TAG(0x0102,        BitsPerSample);
                   EXIF_TAG(0x0103,        Compression);
                   EXIF_TAG(0x0106,        PhotometricInterpretation);
                   EXIF_TAG(0x010a,        FillOrder);
                   EXIF_TAG(0x010d,        DocumentName);
                   EXIF_TAG(0x0111,        StripOffsets);
                   EXIF_TAG(0x0115,        SamplesPerPixel);
                   EXIF_TAG(0x0116,        RowsPerStrip);
                   EXIF_TAG(0x0117,        StripByteCounts);
                   EXIF_TAG(0x011c,        PlanarConfiguration);
                   EXIF_TAG(0x0156,        TransferRange);
                   EXIF_TAG(0x0200,        JPEGProc);
                   EXIF_TAG(0x0201,        JPEGInterchangeFormat);
                   EXIF_TAG(0x0202,        JPEGInterchangeFormatLength);
                   EXIF_TAG(0x0212,        YCbCrSubSampling);
                   EXIF_TAG(0xa401,        CustomRendered);
                   EXIF_TAG(0xa402,        ExposureMode);
                   EXIF_TAG(0xa403,        WhiteBalance);
                   EXIF_TAG(0xa404,        DigitalZoomRatio);
                   EXIF_TAG(0xa405,        FocalLengthIn35mmFilm);
                   EXIF_TAG(0xa406,        SceneCaptureType);
                   EXIF_TAG(0xa407,        GainControl);
                   EXIF_TAG(0xa408,        Contrast);
                   EXIF_TAG(0xa409,        Saturation);
                   EXIF_TAG(0xa40a,        Sharpness);
                   EXIF_TAG(0xa40b,        DeviceSettingDescription);
                   EXIF_TAG(0xa40c,        SubjectDistanceRange);
                   EXIF_TAG(0xa420,        ImageUniqueID);
   
                   // other tags
                   EXIF_TAG(0xa430,        CameraOwnerName);
                   EXIF_TAG(0xa431,        BodySerialNumber);
                   EXIF_TAG(0xa432,        LensSpecification);
                   EXIF_TAG(0xa433,        LensManufactor);
                   EXIF_TAG(0xa434,        LensModel);
                   EXIF_TAG(0xa435,        LensSerialNumber);
         }          }
 } exif_tag_value2name;  } exif_tag_value2name;
   
   class EXIF_gps_tag_value2name: public Hash<int, const char*> {
   public:
                   EXIF_gps_tag_value2name() {
                   EXIF_TAG(0x0,   GPSVersionID);
                   EXIF_TAG(0x1,   GPSLatitudeRef);
                   EXIF_TAG(0x2,   GPSLatitude);
                   EXIF_TAG(0x3,   GPSLongitudeRef);
                   EXIF_TAG(0x4,   GPSLongitude);
                   EXIF_TAG(0x5,   GPSAltitudeRef);
                   EXIF_TAG(0x6,   GPSAltitude);
                   EXIF_TAG(0x7,   GPSTimeStamp);
                   EXIF_TAG(0x8,   GPSSatellites);
                   EXIF_TAG(0x9,   GPSStatus);
                   EXIF_TAG(0xA,   GPSMeasureMode);
                   EXIF_TAG(0xB,   GPSDOP);
                   EXIF_TAG(0xC,   GPSSpeedRef);
                   EXIF_TAG(0xD,   GPSSpeed);
                   EXIF_TAG(0xE,   GPSTrackRef);
                   EXIF_TAG(0xF,   GPSTrack);
                   EXIF_TAG(0x10,  GPSImgDirectionRef);
                   EXIF_TAG(0x11,  GPSImgDirection);
                   EXIF_TAG(0x12,  GPSMapDatum);
                   EXIF_TAG(0x13,  GPSDestLatitudeRef);
                   EXIF_TAG(0x14,  GPSDestLatitude);
                   EXIF_TAG(0x15,  GPSDestLongitudeRef);
                   EXIF_TAG(0x16,  GPSDestLongitude);
                   EXIF_TAG(0x17,  GPSDestBearingRef);
                   EXIF_TAG(0x18,  GPSDestBearing);
                   EXIF_TAG(0x19,  GPSDestDistanceRef);
                   EXIF_TAG(0x1A,  GPSDestDistance);
                   EXIF_TAG(0x1B,  GPSProcessingMethod);
                   EXIF_TAG(0x1C,  GPSAreaInformation);
                   EXIF_TAG(0x1D,  GPSDateStamp);
                   EXIF_TAG(0x1E,  GPSDifferential);
           }
   } exif_gps_tag_value2name;
   
   #undef EXIF_TAG
   
 #ifndef DOXYGEN  #ifndef DOXYGEN
 class Measure_reader {  class Measure_reader {
Line 161  public: Line 239  public:
 };  };
   
 class Measure_file_reader: public Measure_reader {  class Measure_file_reader: public Measure_reader {
         const String& file_name; const char* fname;          const String& file_name;
         int f;          int f;
   
 public:  public:
         Measure_file_reader(int af, const String& afile_name, const char* afname):           Measure_file_reader(int af, const String& afile_name):
                 file_name(afile_name), fname(afname), f(af) {                  file_name(afile_name), f(af) {
         }          }
   
         override size_t read(const char* &abuf, size_t limit) {          override size_t read(const char* &abuf, size_t limit) {
Line 174  public: Line 252  public:
                         return 0;                          return 0;
   
                 char* lbuf=new(PointerFreeGC) char[limit];                  char* lbuf=new(PointerFreeGC) char[limit];
                 size_t read_size=(size_t)::read(f, lbuf, limit);  abuf=lbuf;                  ssize_t read_size=::read(f, lbuf, limit);  abuf=lbuf;
                 if(ssize_t(read_size)<0 || read_size>limit)                  if(read_size<0)
                         throw Exception(0,                          throw Exception(0, &file_name, "measure read failed: %s (%d)", strerror(errno), errno);
                                 &file_name,   
                                 "measure failed: actually read %u bytes count not in [0..%u] valid range",   
                         read_size, limit);  
   
                 return read_size;                  return read_size;
         }          }
   
         override void seek(long value, int whence) {          override void seek(long value, int whence) {
                 if(lseek(f, value, whence)<0)                  if(lseek(f, value, whence)<0)
                         throw Exception("image.format",                          throw Exception(IMAGE_FORMAT, &file_name, "seek(value=%ld, whence=%d) failed: %s (%d)", value, whence, strerror(errno), errno);
                                 &file_name,   
                                 "seek(value=%ld, whence=%d) failed: %s (%d), actual filename '%s'",   
                                         value, whence, strerror(errno), errno, fname);  
         }          }
   
         override long tell() { return lseek(f, 0, SEEK_CUR); }          override long tell() { return lseek(f, 0, SEEK_CUR); }
Line 221  public: Line 292  public:
                 case SEEK_CUR: new_offset=offset+value; break;                  case SEEK_CUR: new_offset=offset+value; break;
                 case SEEK_SET: new_offset=(size_t)value; break;                  case SEEK_SET: new_offset=(size_t)value; break;
                 default:                   default: 
                         throw Exception(0,                           throw Exception(0, 0, "whence #%d not supported", 0, whence); 
                                 0,   
                                 "whence #%d not supported", 0, whence);   
                         break; // never                          break; // never
                 }                  }
                                   
                 if((ssize_t)new_offset<0 || new_offset>size)                  if((ssize_t)new_offset<0 || new_offset>size)
                         throw Exception("image.format",                          throw Exception(IMAGE_FORMAT, &file_name, "seek(value=%l, whence=%d) failed: out of buffer, new_offset>size (%l>%l) or new_offset<0", 
                                 &file_name,   
                                 "seek(value=%l, whence=%d) failed: out of buffer, new_offset>size (%l>%l) or new_offset<0",   
                                         value, whence, new_offset, size);                                          value, whence, new_offset, size);
                 offset=new_offset;                  offset=new_offset;
         }          }
Line 276  struct JPG_Size_segment_body { Line 343  struct JPG_Size_segment_body {
         char numComponents;           //< number of color components          char numComponents;           //< number of color components
 };  };
   
 /// JPEG frame header  
 struct JPG_Exif_segment_begin {  
         char signature[6]; // Exif\0\0  
 };  
   
 /// JPEG Exif TIFF Header  /// JPEG Exif TIFF Header
 struct JPG_Exif_TIFF_header {  struct JPG_Exif_TIFF_header {
         uchar byte_align_identifier[2];          uchar byte_align_identifier[2];
Line 303  struct JPG_Exif_IFD_entry { Line 365  struct JPG_Exif_IFD_entry {
   
 #define JPG_IFD_TAG_EXIF_OFFSET 0x8769  #define JPG_IFD_TAG_EXIF_OFFSET 0x8769
   
   #define JPG_IFD_TAG_EXIF_GPS_OFFSET 0x8825
   
 #define JPEG_EXIF_DATE_CHARS 20  #define JPEG_EXIF_DATE_CHARS 20
   
   
   #ifndef DOXYGEN
   struct Measure_info {
           ushort width;
           ushort height;
           Value** exif;
           Value** xmp;
           Charset* xmp_charset;
   };
   #endif
   
 //  //
   
 inline ushort x_endian_to_ushort(uchar b0, uchar b1) {  inline ushort x_endian_to_ushort(uchar b0, uchar b1) {
Line 325  inline uint endian_to_uint(bool is_big, Line 400  inline uint endian_to_uint(bool is_big,
                 x_endian_to_uint(b[0], b[1], b[2], b[3]);                  x_endian_to_uint(b[0], b[1], b[2], b[3]);
 }  }
   
 static void measure_gif(const String& origin_string,   static void measure_gif(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
                          Measure_reader& reader, ushort& width, ushort& height) {  
   
         const char* buf;          const char* buf;
         const size_t head_size=sizeof(GIF_Header);          const size_t head_size=sizeof(GIF_Header);
         if(reader.read(buf, head_size)<head_size)          if(reader.read(buf, head_size)<head_size)
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not GIF file - too small");
                         &origin_string,   
                         "not GIF file - too small");  
         GIF_Header *head=(GIF_Header *)buf;          GIF_Header *head=(GIF_Header *)buf;
   
         if(strncmp(head->signature, "GIF", 3)!=0)          if(strncmp(head->signature, "GIF", 3)!=0)
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not GIF file - wrong signature");
                         &origin_string,   
                         "not GIF file - wrong signature");        
   
         width=endian_to_ushort(false, head->width);          width=endian_to_ushort(false, head->width);
         height=endian_to_ushort(false, head->height);          height=endian_to_ushort(false, head->height);
 }  }
   
 static Value* parse_IFD_entry_formatted_one_value(  static Value* parse_IFD_entry_formatted_one_value(bool is_big, ushort format, size_t component_size, const uchar *value) {
                                                                                                   bool is_big,  
                                                                                                   ushort format,   
                                                                                                   size_t component_size,   
                                                                                                   const uchar *value) {  
         switch(format) {          switch(format) {
         case 1: // unsigned byte          case 1: // unsigned byte
                 return new VInt((uchar)value[0]);                  return new VInt((uchar)value[0]);
Line 382  static Value* parse_IFD_entry_formatted_ Line 448  static Value* parse_IFD_entry_formatted_
                 }                  }
                 /*                  /*
         case 11: // single float          case 11: // single float
                 todo                  @todo
         case 12: // double float          case 12: // double float
                 todo                  @todo
                 */                  */
         };                };      
                   
Line 392  static Value* parse_IFD_entry_formatted_ Line 458  static Value* parse_IFD_entry_formatted_
 }  }
   
 // date.C  // date.C
 tm cstr_to_time_t(char *cstr);  tm cstr_to_time_t(char *, const char **);
   
 static Value* parse_IFD_entry_formatted_value(bool is_big, ushort format,   static Value* parse_IFD_entry_formatted_value(bool is_big, ushort format, size_t component_size, uint components_count, const uchar *value) {
                                               size_t component_size, uint components_count,   
                                               const uchar *value) {  
         if(format==2) { // ascii string, exception: the only type with varying size          if(format==2) { // ascii string, exception: the only type with varying size
                 const char* cstr=(const char* )value;                  const char* cstr=(const char* )value;
                 size_t length=components_count;                  size_t length=components_count;
                 // Data format is "YYYY:MM:DD HH:MM:SS"+0x00, total 20bytes                  // Data format is "YYYY:MM:DD HH:MM:SS"+0x00, total 20bytes
                 if(length==JPEG_EXIF_DATE_CHARS                   if(length==JPEG_EXIF_DATE_CHARS && isdigit((unsigned char)cstr[0]) && cstr[length-1]==0) {
                         && isdigit((unsigned char)cstr[0])                          char cstr_writable[JPEG_EXIF_DATE_CHARS];
                         && cstr[length-1]==0) {  
                         char cstr_writable[JPEG_EXIF_DATE_CHARS];   
                         strcpy(cstr_writable, cstr);                          strcpy(cstr_writable, cstr);
   
                         try {                          try {
                                 return new VDate(cstr_to_time_t(cstr_writable));                                  tm tmIn=cstr_to_time_t(cstr_writable, 0);
                                   return new VDate(tmIn);
                         }                          }
                         catch(...) { /*ignore bad date times*/ }                          catch(...) { /*ignore bad date times*/ }
                 }                  }
   
                 if(const char* premature_zero_pos=(const char* )memchr(cstr, 0, length))                  return new VString(*new String(cstr, String::L_TAINTED));
                         length=premature_zero_pos-cstr;  
                 return new VString(*new String(cstr, length, true/*tainted*/));  
         }          }
   
         if(components_count==1)          if(components_count==1)
Line 432  static Value* parse_IFD_entry_formatted_ Line 493  static Value* parse_IFD_entry_formatted_
         return result;          return result;
 }  }
   
 static Value* parse_IFD_entry_value(  static Value* parse_IFD_entry_value(bool is_big, Measure_reader& reader, long tiff_base, JPG_Exif_IFD_entry& entry) {
                                                                         bool is_big, Measure_reader& reader, long tiff_base,  
                                                                         JPG_Exif_IFD_entry& entry) {  
         size_t format2component_size[]={          size_t format2component_size[]={
                 0, // undefined                  0, // undefined
                 1, // unsigned byte                  1, // unsigned byte
Line 464  static Value* parse_IFD_entry_value( Line 523  static Value* parse_IFD_entry_value(
         // You can get the total data byte length by multiplies           // You can get the total data byte length by multiplies 
         // a 'bytes/components' value (see above chart) by number of components stored 'NNNNNNNN' area          // a 'bytes/components' value (see above chart) by number of components stored 'NNNNNNNN' area
         uint components_count=endian_to_uint(is_big, entry.components_count);          uint components_count=endian_to_uint(is_big, entry.components_count);
         size_t value_size=component_size*components_count;          uint value_size=component_size*components_count;
         // If its size is over 4bytes, 'DDDDDDDD' contains the offset to data stored address          // If its size is over 4bytes, 'DDDDDDDD' contains the offset to data stored address
         Value* result;          Value* result;
   
         if(value_size<=4)          if(value_size<=4)
                 result=parse_IFD_entry_formatted_value(                  result=parse_IFD_entry_formatted_value(is_big, format, component_size, components_count, entry.value_or_offset_to_it);
                         is_big, format,   
                         component_size, components_count,   
                         entry.value_or_offset_to_it);  
         else {          else {
                 long remembered=reader.tell();                  long remembered=reader.tell();
                 {                  {
                         reader.seek(tiff_base+endian_to_uint(is_big, entry.value_or_offset_to_it), SEEK_SET);                          reader.seek(tiff_base+endian_to_uint(is_big, entry.value_or_offset_to_it), SEEK_SET);
                         const char* value;                          const char* value;
                         if(reader.read(value, value_size)<sizeof(value_size))                          if(reader.read(value, value_size)<value_size)
                                 return 0;                                  return 0;
                         result=parse_IFD_entry_formatted_value(                          result=parse_IFD_entry_formatted_value(is_big, format, component_size, components_count, (const uchar*)value);
                                 is_big, format,   
                                 component_size, components_count,   
                                 (const uchar*)value);  
                 }                  }
                 reader.seek(remembered, SEEK_SET);                  reader.seek(remembered, SEEK_SET);
         }          }
Line 491  static Value* parse_IFD_entry_value( Line 544  static Value* parse_IFD_entry_value(
         return result;          return result;
 }  }
   
 static void parse_IFD(HashStringValue& hash,  static void parse_IFD(HashStringValue& hash, bool is_big, Measure_reader& reader, long tiff_base, bool gps=false);
                       bool is_big, Measure_reader& reader, long tiff_base);  
   
 static void parse_IFD_entry(HashStringValue& hash,  static void parse_IFD_entry(HashStringValue& hash, bool is_big, Measure_reader& reader, long tiff_base, JPG_Exif_IFD_entry& entry, bool gps=false) {
                             bool is_big, Measure_reader& reader, long tiff_base,  
                             JPG_Exif_IFD_entry& entry) {  
         ushort tag=endian_to_ushort(is_big, entry.tag);          ushort tag=endian_to_ushort(is_big, entry.tag);
         if(tag==JPG_IFD_TAG_EXIF_OFFSET) {  
           if(tag==JPG_IFD_TAG_EXIF_OFFSET || tag==JPG_IFD_TAG_EXIF_GPS_OFFSET){
                 long remembered=reader.tell();                  long remembered=reader.tell();
                 {                  {
                         reader.seek(tiff_base+endian_to_uint(is_big, entry.value_or_offset_to_it), SEEK_SET);                          reader.seek(tiff_base+endian_to_uint(is_big, entry.value_or_offset_to_it), SEEK_SET);
                         parse_IFD(hash, is_big, reader, tiff_base);                          parse_IFD(hash, is_big, reader, tiff_base, (tag==JPG_IFD_TAG_EXIF_GPS_OFFSET)?true:gps);
                 }                  }
                 reader.seek(remembered, SEEK_SET);                  reader.seek(remembered, SEEK_SET);
                 return;                  return;
         }          }
           
         if(Value* value=parse_IFD_entry_value(is_big, reader, tiff_base, entry)) {          if(Value* value=parse_IFD_entry_value(is_big, reader, tiff_base, entry)) {
                 if(const char* name=exif_tag_value2name.get(tag))                  if(const char* name=(gps)?exif_gps_tag_value2name.get(tag):exif_tag_value2name.get(tag))
                         hash.put(String::Body(name), value);                          hash.put(name, value);
                 else                  else
                         hash.put(String::Body::Format(tag), value);                          hash.put(String::Body::Format(tag), value);
         }          }
 }  }
   
 static void parse_IFD(  static void parse_IFD(HashStringValue& hash, bool is_big, Measure_reader& reader, long tiff_base, bool gps) {
                       HashStringValue& hash,  
                       bool is_big, Measure_reader& reader, long tiff_base) {  
         const char* buf;          const char* buf;
         if(reader.read(buf, sizeof(JPG_Exif_IFD_begin))<sizeof(JPG_Exif_IFD_begin))          if(reader.read(buf, sizeof(JPG_Exif_IFD_begin))<sizeof(JPG_Exif_IFD_begin))
                 return;                  return;
Line 529  static void parse_IFD( Line 578  static void parse_IFD(
                 if(reader.read(buf, sizeof(JPG_Exif_IFD_entry))<sizeof(JPG_Exif_IFD_entry))                  if(reader.read(buf, sizeof(JPG_Exif_IFD_entry))<sizeof(JPG_Exif_IFD_entry))
                         return;                          return;
   
                 parse_IFD_entry(hash, is_big, reader, tiff_base, *(JPG_Exif_IFD_entry *)buf);                  parse_IFD_entry(hash, is_big, reader, tiff_base, *(JPG_Exif_IFD_entry *)buf, gps);
         }          }
         // then goes: LLLLLLLL Offset to next IFD [not going there]          // then goes: LLLLLLLL Offset to next IFD [not going there]
 }  }
   
 static Value* parse_exif(Measure_reader& reader, const String& origin_string) {  static Value* parse_exif(Measure_reader& reader) {
         const char* buf;          const char* buf;
         if(reader.read(buf, sizeof(JPG_Exif_segment_begin))<sizeof(JPG_Exif_segment_begin))  
                 throw Exception("image.format",   
                         &origin_string,   
                         "not JPEG file - can not fully read Exif segment start");  
   
         JPG_Exif_segment_begin *start=(JPG_Exif_segment_begin *)buf;  
         if(memcmp(start->signature, "Exif\0\0", 4+2)!=0) //signature invalid?  
                 return 0; // ignore invalid block  
   
         uint tiff_base=reader.tell();          uint tiff_base=reader.tell();
         if(reader.read(buf, sizeof(JPG_Exif_TIFF_header))<sizeof(JPG_Exif_TIFF_header))          if(reader.read(buf, sizeof(JPG_Exif_TIFF_header))<sizeof(JPG_Exif_TIFF_header))
Line 563  static Value* parse_exif(Measure_reader& Line 604  static Value* parse_exif(Measure_reader&
         return vhash;          return vhash;
 }  }
   
 static void measure_jpeg(const String& origin_string,   static Value* parse_xmp(Measure_reader& reader, ushort xmp_length, Measure_info &info) {
                          Measure_reader& reader, ushort& width, ushort& height, Value** exif) {          const char* buf;
           if(reader.read(buf, xmp_length)<xmp_length)
                   return 0;
   
           String::C xmp = Charset::transcode(String::C(pa_strdup(buf, xmp_length), xmp_length), *info.xmp_charset, pa_thread_request().charsets.source());
           return new VString(*new String(xmp, String::L_TAINTED));
   }
   
   static void measure_jpeg(const String& origin_string, Measure_reader& reader, Measure_info &info) {
         // JFIF format markers          // JFIF format markers
         const uchar MARKER=0xFF;          const uchar MARKER=0xFF;
         const uchar CODE_SIZE_A=0xC0;          const uchar CODE_SIZE_A=0xC0;
         const uchar CODE_SIZE_B=0xC1;          const uchar CODE_SIZE_B=0xC1;
         const uchar CODE_SIZE_C=0xC2;          const uchar CODE_SIZE_C=0xC2;
         const uchar CODE_SIZE_D=0xC3;          const uchar CODE_SIZE_D=0xC3;
         const uchar CODE_EXIF=0xE1;          const uchar CODE_APP1=0xE1;
   
         const char* buf;          const char* buf;
         const size_t prefix_size=2;          const size_t prefix_size=2;
         if(reader.read(buf, prefix_size)<prefix_size)          if(reader.read(buf, prefix_size)<prefix_size)
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not JPEG file - too small");
                         &origin_string,   
                         "not JPEG file - too small");  
         uchar *signature=(uchar *)buf;          uchar *signature=(uchar *)buf;
                   
         if(!(signature[0]==0xFF && signature[1]==0xD8))           if(!(signature[0]==0xFF && signature[1]==0xD8)) 
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not JPEG file - wrong signature");
                         &origin_string,   
                         "not JPEG file - wrong signature");  
   
         while(true) {          while(true) {
                 uint segment_base=reader.tell()+2/*marker,code*/;                  uint segment_base=reader.tell()+2/*marker,code*/;
Line 592  static void measure_jpeg(const String& o Line 637  static void measure_jpeg(const String& o
                         break;                          break;
                 JPG_Segment_head *head=(JPG_Segment_head *)buf;                  JPG_Segment_head *head=(JPG_Segment_head *)buf;
   
         // Verify that it's a valid segment.                  // Verify that it's a valid segment.
                 if(head->marker!=MARKER)                  if(head->marker!=MARKER)
                         throw Exception("image.format",                           throw Exception(IMAGE_FORMAT, &origin_string, "not JPEG file - marker not found");
                                 &origin_string,   
                                 "not JPEG file - marker not found");                  ushort segment_length=endian_to_ushort(true, head->length);
   
                 switch(head->code) {                  switch(head->code) {
                 // http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html                  // http://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_JPEG_files
                 case CODE_EXIF:                  case CODE_APP1:
                         if(exif && !*exif) // seen .jpg with some xml under EXIF tag, after real exif block :)                          {
                                 *exif=parse_exif(reader, origin_string);                                  const size_t EXIF_SIG_LEN=6; // Exif\0\0
                                   const size_t XMP_SIG_LEN=29; // http://ns.adobe.com/xap/1.0/\0
   
                                   if(segment_length<EXIF_SIG_LEN+2 || reader.read(buf, EXIF_SIG_LEN)<EXIF_SIG_LEN)
                                           break;
                                   if(memcmp(buf, "Exif\0\0", EXIF_SIG_LEN)==0){
                                           if(info.exif && !*info.exif) // backward compatibility: using first segment
                                                   *info.exif=parse_exif(reader);
                                           break;
                                   }
   
                                   if(memcmp(buf, "http:/", EXIF_SIG_LEN))
                                           break;
                                   if(segment_length<XMP_SIG_LEN+2 || reader.read(buf, XMP_SIG_LEN-EXIF_SIG_LEN)<XMP_SIG_LEN-EXIF_SIG_LEN)
                                           break;
                                   if(memcmp(buf, "/ns.adobe.com/xap/1.0/\0", XMP_SIG_LEN-EXIF_SIG_LEN)==0){
                                           if(info.xmp && !*info.xmp) // backward compatibility: using first segment
                                                   *info.xmp=parse_xmp(reader, segment_length - XMP_SIG_LEN - 2 /* segment_length */, info);
                                   }
   
                           }
                         break;                          break;
   
                 case CODE_SIZE_A:                  case CODE_SIZE_A:
Line 612  static void measure_jpeg(const String& o Line 677  static void measure_jpeg(const String& o
                         {                          {
                                 // Segments that contain size info                                  // Segments that contain size info
                                 if(reader.read(buf, sizeof(JPG_Size_segment_body))<sizeof(JPG_Size_segment_body))                                  if(reader.read(buf, sizeof(JPG_Size_segment_body))<sizeof(JPG_Size_segment_body))
                                         throw Exception("image.format",                                           throw Exception(IMAGE_FORMAT, &origin_string, "not JPEG file - can not fully read Size segment");
                                                 &origin_string,   
                                                 "not JPEG file - can not fully read Size segment");  
                                 JPG_Size_segment_body *body=(JPG_Size_segment_body *)buf;                                  JPG_Size_segment_body *body=(JPG_Size_segment_body *)buf;
                                                                   
                                 width=endian_to_ushort(true, body->width);                                  info.width=endian_to_ushort(true, body->width);
                                 height=endian_to_ushort(true, body->height);                                  info.height=endian_to_ushort(true, body->height);
                         }                                                 }
                         return;                          return;
                 };                  };
   
                 reader.seek(segment_base+endian_to_ushort(true, head->length), SEEK_SET);                  reader.seek(segment_base + segment_length, SEEK_SET);
         }          }
   
         throw Exception("image.format",           throw Exception(IMAGE_FORMAT, &origin_string, "broken JPEG file - size frame not found");
                 &origin_string,   
                 "broken JPEG file - size frame not found");  
 }  }
   
 static void measure_png(const String& origin_string,   static void measure_png(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
                          Measure_reader& reader, ushort& width, ushort& height) {  
   
         const char* buf;          const char* buf;
         const size_t head_size=sizeof(PNG_Header);          const size_t head_size=sizeof(PNG_Header);
         if(reader.read(buf, head_size)<head_size)          if(reader.read(buf, head_size)<head_size)
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not PNG file - too small");
                         &origin_string,   
                         "not PNG file - too small");  
         PNG_Header *head=(PNG_Header *)buf;          PNG_Header *head=(PNG_Header *)buf;
   
         if(strncmp(head->signature, "IHDR", 4)!=0)          if(strncmp(head->signature, "IHDR", 4)!=0)
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &origin_string, "not PNG file - wrong signature");
                         &origin_string,   
                         "not PNG file - wrong signature");        
   
         width=endian_to_ushort(true, head->width);          width=endian_to_ushort(true, head->width);
         height=endian_to_ushort(true, head->height);          height=endian_to_ushort(true, head->height);
Line 653  static void measure_png(const String& or Line 709  static void measure_png(const String& or
   
 // measure center  // measure center
   
 static void measure(const String& file_name,   static void measure(const String& file_name, Measure_reader& reader, Measure_info &info) {
                          Measure_reader& reader, ushort& width, ushort& height, Value** exif) {          const char* file_name_cstr=file_name.taint_cstr(String::L_FILE_SPEC);
         const char* file_name_cstr=file_name.cstr(String::L_FILE_SPEC);  
         if(const char* cext=strrchr(file_name_cstr, '.')) {          if(const char* cext=strrchr(file_name_cstr, '.')) {
                 cext++;                  cext++;
                 if(strcasecmp(cext, "GIF")==0)                  if(strcasecmp(cext, "GIF")==0)
                         measure_gif(file_name, reader, width, height);                          measure_gif(file_name, reader, info.width, info.height);
                 else if(strcasecmp(cext, "JPG")==0 || strcasecmp(cext, "JPEG")==0)                   else if(strcasecmp(cext, "JPG")==0 || strcasecmp(cext, "JPEG")==0) 
                         measure_jpeg(file_name, reader, width, height, exif);                          measure_jpeg(file_name, reader, info);
                 else if(strcasecmp(cext, "PNG")==0)                  else if(strcasecmp(cext, "PNG")==0)
                         measure_png(file_name, reader, width, height);                          measure_png(file_name, reader, info.width, info.height);
                 else                  else
                         throw Exception("image.format",                           throw Exception(IMAGE_FORMAT, &file_name, "unhandled image file name extension '%s'", cext);
                                 &file_name,   
                                 "unhandled image file name extension '%s'", cext);  
         } else          } else
                 throw Exception("image.format",                   throw Exception(IMAGE_FORMAT, &file_name, "can not determine image type - no file name extension");
                         &file_name,   
                         "can not determine image type - no file name extension");  
 }  }
   
 // methods  // methods
   
 #ifndef DOXYGEN  static void file_measure_action(struct stat& /*finfo*/, int f, const String& file_spec, void *context) {
 struct File_measure_action_info {          Measure_file_reader reader(f, file_spec);
         ushort* width;          measure(file_spec, reader, *static_cast<Measure_info *>(context));
         ushort* height;  
         Value** exif;  
         const String* file_name;  
 };  
 #endif  
 static void file_measure_action(  
                                                                 struct stat& /*finfo*/, int f,   
                                                                 const String& /*file_spec*/, const char* fname, bool /*as_text*/,  
                                                                 void *context) {  
         File_measure_action_info& info=*static_cast<File_measure_action_info *>(context);  
   
         Measure_file_reader reader(f, *info.file_name, fname);  
         measure(*info.file_name, reader, *info.width, *info.height, info.exif);  
 }  }
   
 static void _measure(Request& r, MethodParams& params) {  static void _measure(Request& r, MethodParams& params) {
         Value& data=params.as_no_junction(0, "data must not be code");          Value& data=params.as_no_junction(0, "data must not be code");
   
         ushort width=0;  
         ushort height=0;  
         Value* exif=0;          Value* exif=0;
           Value* xmp=0;
           Measure_info info={ 0, 0, 0, 0, &pa_UTF8_charset };
   
           if(params.count()>1)
                   if(HashStringValue* options=params.as_hash(1, "methods options")) {
                           int valid_options=0;
                           for(HashStringValue::Iterator i(*options); i; i.next() ){
                                   String::Body key=i.key();
                                   Value* value=i.value();
                                   if(key == "exif") {
                                           if(r.process(*value).as_bool())
                                                   info.exif=&exif;
                                           valid_options++;
                                   }
                                   if(key == "xmp") {
                                           if(r.process(*value).as_bool())
                                                   info.xmp=&xmp;
                                           valid_options++;
                                   }
                                   if(key == "xmp-charset") {
                                           info.xmp_charset=&pa_charsets.get(value->as_string());
                                           valid_options++;
                                   }
                           }
                           if(valid_options!=options->count())
                                   throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
                   }
   
         const String* file_name;          const String* file_name;
         if((file_name=data.get_string())) {  
                 File_measure_action_info info={          if(file_name=data.get_string()) {
                         &width, &height,                  file_read_action_under_lock(r.absolute(*file_name), "measure", file_measure_action, &info);
                         &exif,  
                         file_name  
                 };  
                 file_read_action_under_lock(r.absolute(*file_name),   
                         "measure", file_measure_action, &info);  
         } else {          } else {
                 VFile* vfile=data.as_vfile();                  VFile* vfile=data.as_vfile(String::L_AS_IS);
                 file_name=&vfile->fields().get(name_name)->as_string();                  file_name=&vfile->fields().get(name_name)->as_string();
                 Measure_buf_reader reader(                  Measure_buf_reader reader(vfile->value_ptr(), vfile->value_size(), *file_name);
                         vfile->value_ptr(),                  measure(*file_name, reader, info);
                         vfile->value_size(),  
                         *file_name  
                 );  
                 measure(*file_name, reader, width, height, &exif);  
         }          }
   
         GET_SELF(r, VImage).set(file_name, width, height, 0, exif);          GET_SELF(r, VImage).set(file_name, info.width, info.height, 0, exif, xmp);
 }  }
   
 #ifndef DOXYGEN  static void append_attrib_pair(String &tag, String::Body key, Value* value){
 struct Attrib_info {          // skip border attribute with empty value
         String* tag; ///< html tag being constructed          if(key=="border" && !value->is_defined())
         HashStringValue* skip; ///< tag attributes not to append to tag string [to skip]  
 };  
 #endif  
 static void append_attrib_pair(  
                                HashStringValue::key_type key,   
                                HashStringValue::value_type value,   
                                Attrib_info* info) {  
         // skip user-specified and internal(starting with "line-") attributes   
         if(info->skip && info->skip->get(key) || key.pos("line-")==0)  
                 return;                  return;
   
         // src="a.gif" width=123 ismap[=-1]          // src="a.gif" width="123" ismap[=-1]
         *info->tag << " " << key;          tag << " " << key;
         if(value->is_string() || value->as_int()>=0)          if(value->is_string() || value->as_int()>=0)
                 *info->tag << "=\"" << value->as_string() << "\"";                  tag << "=\"" << value->as_string() << "\"";
 }  }
 static void _html(Request& r, MethodParams& params) {  
   
   static void _html(Request& r, MethodParams& params) {
         String tag;          String tag;
         tag << "<img";          tag << "<img";
   
Line 752  static void _html(Request& r, MethodPara Line 798  static void _html(Request& r, MethodPara
   
         if(params.count()) {          if(params.count()) {
                 // for backward compatibility: someday was ^html{}                  // for backward compatibility: someday was ^html{}
                 Value& vattribs=r.process_to_value(params[0],                  Value& vattribs=r.process(params[0]);
                         false/*don't intercept string*/);                  if(!vattribs.is_string()) { // allow empty
                 if(!vattribs.is_string()) // allow empty  
                         if((attribs=vattribs.get_hash())) {                          if((attribs=vattribs.get_hash())) {
                                 Attrib_info info={&tag, 0};                                  for(HashStringValue::Iterator i(*attribs); i; i.next() )
                                 attribs->for_each<Attrib_info*>(append_attrib_pair, &info);                                          append_attrib_pair(tag, i.key(), i.value());
                         } else                          } else
                                 throw Exception("parser.runtime",                                   throw Exception(PARSER_RUNTIME, 0, "attributes must be hash");
                                         0,                   }
                                         "attributes must be hash");  
         }          }
   
         {          for(HashStringValue::Iterator i(fields); i; i.next() ){
                 Attrib_info info={&tag, attribs};                  String::Body key=i.key();
                 fields.for_each<Attrib_info*>(append_attrib_pair, &info);                  // skip user-specified attributes
                   if(attribs && attribs->get(key))
                           continue;
                   // allow only html attributes (to exclude exif, line-*)
                   if(key=="src" || key=="width" || key=="height" || key=="border")
                           append_attrib_pair(tag, key, i.value());
         }          }
   
         tag << " />";          tag << " />";
         r.write_pass_lang(tag);          r.write(tag);
 }  }
   
 /// @test wrap FILE to auto-object  /// @test wrap FILE to auto-object
 static gdImage* load(Request& r,   static gdImage* load(Request& r, const String& file_name){
                                          const String& file_name){          const char* file_name_cstr=r.absolute(file_name).taint_cstr(String::L_FILE_SPEC);
         const char* file_name_cstr=r.absolute(file_name).cstr(String::L_FILE_SPEC);          if(FILE *f=pa_fopen(file_name_cstr, "rb")) {
         if(FILE *f=fopen(file_name_cstr, "rb")) {  
                 gdImage* image=new gdImage;                  gdImage* image=new gdImage;
                 bool ok=image->CreateFromGif(f);                  bool ok=image->CreateFromGif(f);
                 fclose(f);                  fclose(f);
                 if(!ok)                  if(!ok)
                         throw Exception("image.format",                           throw Exception(IMAGE_FORMAT, &file_name, "is not in GIF format");
                                 &file_name,  
                                 "is not in GIF format");  
                 return image;                  return image;
         } else {          } else {
                 throw Exception("file.missing",                   throw Exception("file.missing", 0, "can not open '%s'", file_name_cstr);
                         0,   
                         "can not open '%s'", file_name_cstr);  
         }          }
 }  }
   
   
 static void _load(Request& r, MethodParams& params) {  static void _load(Request& r, MethodParams& params) {
         const String& file_name=params.as_string(0, "file name must not be code");          const String& file_name=params.as_string(0, FILE_NAME_MUST_NOT_BE_CODE);
   
         gdImage* image=load(r, file_name);          gdImage* image=load(r, file_name);
         GET_SELF(r, VImage).set(&file_name, image->SX(), image->SY(), image);          GET_SELF(r, VImage).set(&file_name, image->SX(), image->SY(), image);
Line 815  static void _create(Request& r, MethodPa Line 860  static void _create(Request& r, MethodPa
 static void _gif(Request& r, MethodParams& params) {  static void _gif(Request& r, MethodParams& params) {
         gdImage& image=GET_SELF(r, VImage).image();          gdImage& image=GET_SELF(r, VImage).image();
   
         const String *file_name=0;          const String *file_name=params.count()>0?&params.as_string(0, FILE_NAME_MUST_BE_STRING):0;
         if(params.count()>0)  
                 file_name=&params.as_string(0, "file name must be string");  
   
         gdBuf buf=image.Gif();          gdBuf buf=image.Gif();
                   
         VFile& vfile=*new VFile;          VFile& vfile=*new VFile;
         Value* content_type=new VString(*new String("image/gif"));  
         vfile.set(false/*not tainted*/,   
                 (const char*)buf.ptr, buf.size,   
                 file_name? file_name->cstr(String::L_FILE_SPEC): 0,   
                 content_type);  
   
         r.write_no_lang(vfile);          vfile.set_binary(false/*not tainted*/, (const char *)buf.ptr, buf.size, file_name, new VString(*new String("image/gif")));
   
           r.write(vfile);
 }  }
   
 static void _line(Request& r, MethodParams& params) {  static void _line(Request& r, MethodParams& params) {
Line 874  static void _bar(Request& r, MethodParam Line 914  static void _bar(Request& r, MethodParam
 }  }
   
 #ifndef DOXYGEN  #ifndef DOXYGEN
 static void add_point(Table::element_type row,   static void add_point(Table::element_type row, gdImage::Point **p) {
                                           gdImage::Point **p) {  
         if(row->count()!=2)          if(row->count()!=2)
                 throw Exception(0,                  throw Exception(0, 0, "coordinates table must contain two columns: x and y values");
                         0,  
                         "coordinates table must contain two columns: x and y values");  
         (**p).x=row->get(0)->as_int();          (**p).x=row->get(0)->as_int();
         (**p).y=row->get(1)->as_int();          (**p).y=row->get(1)->as_int();
         (*p)++;          (*p)++;
 }  }
 #endif  #endif
   #ifndef DOXYGEN
   static void add_point(int x, int y, gdImage::Point **p) {
           (**p).x=x;
           (**p).y=y;
           (*p)++;
   }
   #endif
 static void _replace(Request& r, MethodParams& params) {  static void _replace(Request& r, MethodParams& params) {
           int src_color=params.as_int(0, "src color must be int", r);
           int dest_color=params.as_int(1, "dest color must be int", r);
   
         gdImage& image=GET_SELF(r, VImage).image();          gdImage& image=GET_SELF(r, VImage).image();
   
         Table* table=params.as_no_junction(2, "coordinates must not be code").get_table();          gdImage::Point* all_p=0;
         if(!table)           size_t count=0;
                 throw Exception(0,          if(params.count() == 3){
                         0,                  Table* table=params.as_table(2, "coordinates");
                         "coordinates must be table");                  count=table->count();
                   all_p=new(PointerFreeGC) gdImage::Point[count];
                   gdImage::Point* add_p=all_p;
                   table->for_each(add_point, &add_p);
           } else {
                   int max_x=image.SX()-1;
                   int max_y=image.SY()-1;
                   if(max_x > 0 && max_y > 0){
                           count=4;
                           all_p=new(PointerFreeGC) gdImage::Point[count];
                           gdImage::Point* add_p=all_p;
                           add_point(0, 0, &add_p);
                           add_point(max_x, 0, &add_p);
                           add_point(max_x, max_y, &add_p);
                           add_point(0, max_y, &add_p);
                   }
           }
   
         gdImage::Point *all_p=new(PointerFreeGC) gdImage::Point[table->count()];          if(count)
         gdImage::Point *add_p=all_p;                      image.FilledPolygonReplaceColor(all_p, count, image.Color(src_color), image.Color(dest_color));
         table->for_each(add_point, &add_p);  
         image.FilledPolygonReplaceColor(all_p, table->count(),   
                 image.Color(params.as_int(0, "src color must be int", r)),  
                 image.Color(params.as_int(1, "dest color must be int", r)));  
 }  }
   
 static void _polyline(Request& r, MethodParams& params) {  static void _polyline(Request& r, MethodParams& params) {
         gdImage& image=GET_SELF(r, VImage).image();          gdImage& image=GET_SELF(r, VImage).image();
   
         Table* table=params.as_no_junction(1, "coordinates must not be code").get_table();          Table* table=params.as_table(1, "coordinates");
         if(!table)   
                 throw Exception(0,  
                         0,  
                         "coordinates must be table");  
   
         gdImage::Point* all_p=new(PointerFreeGC) gdImage::Point[table->count()];          gdImage::Point* all_p=new(PointerFreeGC) gdImage::Point[table->count()];
         gdImage::Point *add_p=all_p;              gdImage::Point *add_p=all_p;    
Line 922  static void _polyline(Request& r, Method Line 977  static void _polyline(Request& r, Method
 static void _polygon(Request& r, MethodParams& params) {  static void _polygon(Request& r, MethodParams& params) {
         gdImage& image=GET_SELF(r, VImage).image();          gdImage& image=GET_SELF(r, VImage).image();
   
         Table* table=params.as_no_junction(1, "coordinates must not be code").get_table();          Table* table=(Table*)params.as_table(1, "coordinates");
         if(!table)   
                 throw Exception(0,  
                         0,  
                         "coordinates must be table");  
   
         gdImage::Point* all_p=new(PointerFreeGC) gdImage::Point[table->count()];          gdImage::Point* all_p=new(PointerFreeGC) gdImage::Point[table->count()];
         gdImage::Point *add_p=all_p;              gdImage::Point *add_p=all_p;    
Line 938  static void _polygon(Request& r, MethodP Line 989  static void _polygon(Request& r, MethodP
 static void _polybar(Request& r, MethodParams& params) {  static void _polybar(Request& r, MethodParams& params) {
         gdImage& image=GET_SELF(r, VImage).image();          gdImage& image=GET_SELF(r, VImage).image();
   
         Table* table=params.as_no_junction(1, "coordinates must not be code").get_table();          Table* table=(Table*)params.as_table(1, "coordinates");
         if(!table)   
                 throw Exception("parser.runtime",  
                         0,  
                         "coordinates must be table");  
   
         gdImage::Point* all_p=new(PointerFreeGC) gdImage::Point[table->count()];          gdImage::Point* all_p=new(PointerFreeGC) gdImage::Point[table->count()];
         gdImage::Point *add_p=all_p;              gdImage::Point *add_p=all_p;    
Line 957  static void _polybar(Request& r, MethodP Line 1004  static void _polybar(Request& r, MethodP
   
 // Font class  // Font class
   
 const int Font::letter_spacing=1;  Font::Font(Charset& asource_charset, const String& aalphabet, gdImage* aifont, int aheight, int amonospace, int aspacebarspace, int aletterspacing):
           letterspacing(aletterspacing),
 Font::Font(//,           height(aheight),
         const String& aalphabet,           monospace(amonospace),
         gdImage* aifont, int aheight, int amonospace, int aspacebarspace):          spacebarspace(aspacebarspace),
         height(aheight), monospace(amonospace),  spacebarspace(aspacebarspace),  
         ifont(aifont),          ifont(aifont),
         alphabet(aalphabet)     {          alphabet(aalphabet),
           fsource_charset(asource_charset){
   
           if(fsource_charset.isUTF8()){
                   size_t index=0;
                   for(UTF8_string_iterator i(alphabet); i.has_next(); )
                           fletter2index.put_dont_replace(i.next(), index++);
           }
 }  }
   
 /* ******************************** char ********************************** */  /* ******************************** char ********************************** */
Line 974  size_t Font::index_of(char ch) { Line 1027  size_t Font::index_of(char ch) {
         return alphabet.pos(ch);          return alphabet.pos(ch);
 }  }
   
   size_t Font::index_of(XMLCh ch) {
           if(ch==' ') return STRING_NOT_FOUND;
           return fletter2index.get(ch);
   }
   
 int Font::index_width(size_t index) {  int Font::index_width(size_t index) {
         if(index==STRING_NOT_FOUND)          if(index==STRING_NOT_FOUND)
                 return spacebarspace;                  return spacebarspace;
Line 994  void Font::index_display(gdImage& image, Line 1052  void Font::index_display(gdImage& image,
 /* ******************************** string ********************************** */  /* ******************************** string ********************************** */
   
 int Font::step_width(int index) {  int Font::step_width(int index) {
         return letter_spacing + (monospace ? monospace : index_width(index));          return letterspacing + (monospace ? monospace : index_width(index));
 }  }
   
 // counts trailing letter_spacing, consider this OK. useful for contiuations  // counts trailing letter_spacing, consider this OK. useful for contiuations
 int Font::string_width(const String& s){  int Font::string_width(const String& s){
         const char* cstr=s.cstr();          const char* cstr=s.cstr();
         int result=0;          int result=0;
         for(const char* current=cstr; *current; current++)  
                 result+=step_width(index_of(*current));          if(fsource_charset.isUTF8()){
                   for(UTF8_string_iterator i(s); i.has_next(); )
                           result+=step_width(index_of(i.next()));
           } else {
                   for(const char* current=cstr; *current; current++)
                           result+=step_width(index_of(*current));
           }
   
         return result;          return result;
 }  }
   
 void Font::string_display(gdImage& image, int x, int y, const String& s){  void Font::string_display(gdImage& image, int x, int y, const String& s){
         const char* cstr=s.cstr();          const char* cstr=s.cstr();
         for(const char* current=cstr; *current; current++) {  
                 size_t index=index_of(*current);          if(fsource_charset.isUTF8()){
                 index_display(image, x, y, index);                  for(UTF8_string_iterator i(s); i.has_next(); ){
                 x+=step_width(index);                          size_t index=index_of(i.next());
                           index_display(image, x, y, index);
                           x+=step_width(index);
                   }
           } else {
                   for(const char* current=cstr; *current; current++) {
                           size_t index=index_of(*current);
                           index_display(image, x, y, index);
                           x+=step_width(index);
                   }
         }          }
 }  }
   
Line 1020  void Font::string_display(gdImage& image Line 1094  void Font::string_display(gdImage& image
   
 static void _font(Request& r, MethodParams& params) {  static void _font(Request& r, MethodParams& params) {
         const String& alphabet=params.as_string(0, "alphabet must not be code");          const String& alphabet=params.as_string(0, "alphabet must not be code");
         gdImage* image=load(r, params.as_string(1, "file_name must not be code"));          size_t alphabet_length=alphabet.length(r.charsets.source());
         int spacebar_width=params.as_int(2, "spacebar_width must be int", r);          if(!alphabet_length)
         int monospace_width;                  throw Exception(PARSER_RUNTIME, 0, "alphabet must not be empty");
         if(params.count()>3) {  
                 monospace_width=params.as_int(3, "monospace_width must be int", r);          gdImage* image=load(r, params.as_string(1, FILE_NAME_MUST_NOT_BE_CODE));
                 if(!monospace_width)  
                         monospace_width=image->SX();          int spacebar_width=image->SX();
         } else          int monospace_width=0; // proportional
                 monospace_width=0;          int letter_spacing=1;
           if(params.count()>2){
                   if(HashStringValue* options=params[2].get_hash()){
                           // third option is hash
                           if(params.count()>3)
                                   throw Exception(PARSER_RUNTIME, 0, "too many params were specified");
                           int valid_options=0;
                           if(Value* vspacebar_width=options->get(spacebar_width_name)){
                                   valid_options++;
                                   spacebar_width=r.process(*vspacebar_width).as_int();
                           }
                           if(Value* vmonospace_width=options->get(monospace_width_name)){
                                   valid_options++;
                                   monospace_width=r.process(*vmonospace_width).as_int();
                                   if(!monospace_width)
                                           monospace_width=image->SX();
                           }
                           if(Value* vletter_spacing=options->get(letter_spacing_name)){
                                   valid_options++;
                                   letter_spacing=r.process(*vletter_spacing).as_int();
                           }
                           if(valid_options!=options->count())
                                   throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
                   } else {
                           // backward
                           spacebar_width=params.as_int(2, "param must be int or hash", r);
                           if(params.count()>3) {
                                   monospace_width=params.as_int(3, "monospace_width must be int", r);
                                   if(!monospace_width)
                                           monospace_width=image->SX();
                           }
                   }
           }
   
         if(!alphabet.length())          if(int remainder=image->SY() % alphabet_length)
                 throw Exception("parser.runtime",                  throw Exception(PARSER_RUNTIME, 0, "font-file height(%d) not divisable by alphabet size(%d), remainder=%d", image->SY(), alphabet_length, remainder);
                         0,  
                         "alphabet must not be empty");  
   
         if(int remainder=image->SY() % alphabet.length())  
                 throw Exception("parser.runtime",  
                         0,  
                         "font-file height(%d) not divisable by alphabet size(%d), remainder=%d",  
                                 image->SY(), alphabet.length(), remainder);  
                   
         GET_SELF(r, VImage).set_font(new Font(          GET_SELF(r, VImage).set_font(new Font(r.charsets.source(), alphabet, image, image->SY() / alphabet_length, monospace_width, spacebar_width, letter_spacing));
                 alphabet,   
                 image,   
                 image->SY() / alphabet.length(), monospace_width, spacebar_width));  
 }  }
   
 static void _text(Request& r, MethodParams& params) {  static void _text(Request& r, MethodParams& params) {
Line 1060  static void _length(Request& r, MethodPa Line 1155  static void _length(Request& r, MethodPa
         const String& s=params.as_string(0, "text must not be code");          const String& s=params.as_string(0, "text must not be code");
   
         VImage& vimage=GET_SELF(r, VImage);          VImage& vimage=GET_SELF(r, VImage);
         r.write_no_lang(*new VInt(vimage.font().string_width(s)));          r.write(*new VInt(vimage.font().string_width(s)));
 }  }
   
 static void _arc(Request& r, MethodParams& params) {  static void _arc(Request& r, MethodParams& params) {
Line 1106  static void _circle(Request& r, MethodPa Line 1201  static void _circle(Request& r, MethodPa
 gdImage& as_image(MethodParams& params, int index, const char* msg) {  gdImage& as_image(MethodParams& params, int index, const char* msg) {
         Value& value=params.as_no_junction(index, msg);          Value& value=params.as_no_junction(index, msg);
   
         if(Value* vimage=value.as(VIMAGE_TYPE, false)) {          if(Value* vimage=value.as(VIMAGE_TYPE)) {
                 return static_cast<VImage *>(vimage)->image();                  return static_cast<VImage *>(vimage)->image();
         } else          } else
                 throw Exception("parser.runtime",                   throw Exception(PARSER_RUNTIME, 0, msg);
                         0,   
                         msg);  
 }  }
   
 static void _copy(Request& r, MethodParams& params) {  static void _copy(Request& r, MethodParams& params) {
Line 1147  static void _pixel(Request& r, MethodPar Line 1240  static void _pixel(Request& r, MethodPar
                 image.SetPixel(x, y,                   image.SetPixel(x, y, 
                         image.Color(params.as_int(2, "color must be int", r)));                          image.Color(params.as_int(2, "color must be int", r)));
         } else           } else 
                 r.write_no_lang(*new VInt(image.DecodeColor(image.GetPixel(x, y))));                  r.write(*new VInt(image.DecodeColor(image.GetPixel(x, y))));
 }  }
   
   
Line 1155  static void _pixel(Request& r, MethodPar Line 1248  static void _pixel(Request& r, MethodPar
   
 MImage::MImage(): Methoded("image") {  MImage::MImage(): Methoded("image") {
         // ^image:measure[DATA]          // ^image:measure[DATA]
         add_native_method("measure", Method::CT_DYNAMIC, _measure, 1, 1);          // ^image:measure[DATA; $.exif(false) $.xmp(false) $.xmp-charset[UTF-8] ]
           add_native_method("measure", Method::CT_DYNAMIC, _measure, 1, 2);
   
         // ^image.html[]          // ^image.html[]
         // ^image.html[hash]          // ^image.html[hash]
Line 1184  MImage::MImage(): Methoded("image") { Line 1278  MImage::MImage(): Methoded("image") {
         add_native_method("bar", Method::CT_DYNAMIC, _bar, 5, 5);          add_native_method("bar", Method::CT_DYNAMIC, _bar, 5, 5);
   
         // ^image.replace(color-source;color-dest)[table x:y]          // ^image.replace(color-source;color-dest)[table x:y]
         add_native_method("replace", Method::CT_DYNAMIC, _replace, 3, 3);          // ^image.replace(color-source;color-dest)
           add_native_method("replace", Method::CT_DYNAMIC, _replace, 2, 3);
   
         // ^image.polyline(color)[table x:y]          // ^image.polyline(color)[table x:y]
         add_native_method("polyline", Method::CT_DYNAMIC, _polyline, 2, 2);          add_native_method("polyline", Method::CT_DYNAMIC, _polyline, 2, 2);
Line 1195  MImage::MImage(): Methoded("image") { Line 1290  MImage::MImage(): Methoded("image") {
         // ^image.polybar(color)[table x:y]          // ^image.polybar(color)[table x:y]
         add_native_method("polybar", Method::CT_DYNAMIC, _polybar, 2, 2);          add_native_method("polybar", Method::CT_DYNAMIC, _polybar, 2, 2);
   
     // ^image.font[alPHAbet;font-file-name.gif](spacebar_width)          // ^image.font[alPHAbet;font-file-name.gif]
     // ^image.font[alPHAbet;font-file-name.gif](spacebar_width;width)          // ^image.font[alPHAbet;font-file-name.gif](spacebar_width)
         add_native_method("font", Method::CT_DYNAMIC, _font, 3, 4);          // ^image.font[alPHAbet;font-file-name.gif](spacebar_width;letter_width)
           // ^image.font[alPHAbet;font-file-name.gif][$.space-width(.) $.letter-width(.) $.letter-space(.)]
           add_native_method("font", Method::CT_DYNAMIC, _font, 2, 4);
   
     // ^image.text(x;y)[text]          // ^image.text(x;y)[text]
         add_native_method("text", Method::CT_DYNAMIC, _text, 3, 3);          add_native_method("text", Method::CT_DYNAMIC, _text, 3, 3);
                   
     // ^image.ngth[text]          // ^image.length[text]
         add_native_method("length", Method::CT_DYNAMIC, _length, 1, 1);          add_native_method("length", Method::CT_DYNAMIC, _length, 1, 1);
                   
         // ^image.arc(center x;center y;width;height;start in degrees;end in degrees;color)          // ^image.arc(center x;center y;width;height;start in degrees;end in degrees;color)

Removed from v.1.113.12.1  
changed lines
  Added in v.1.163


E-mail: