Diff for /parser3/src/classes/image.C between versions 1.147 and 1.192

version 1.147, 2015/09/18 00:08:12 version 1.192, 2024/11/10 20:46:08
Line 1 Line 1
 /** @file  /** @file
         Parser: @b image parser class.          Parser: @b image parser class.
   
         Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com)          Copyright (c) 2001-2024 Art. Lebedev Studio (http://www.artlebedev.com)
         Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)          Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
 */  */
   
 /*  /*
Line 24 Line 24
 #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$";  volatile const char * IDENT_IMAGE_C="$Id$";
   
Line 45  public: Line 46  public:
   
 // globals  // globals
   
 DECLARE_CLASS_VAR(image, new MImage, 0);  DECLARE_CLASS_VAR(image, new MImage);
   
 // helpers  // helpers
   
Line 179  public: Line 180  public:
                 EXIF_TAG(0xa40b,        DeviceSettingDescription);                  EXIF_TAG(0xa40b,        DeviceSettingDescription);
                 EXIF_TAG(0xa40c,        SubjectDistanceRange);                  EXIF_TAG(0xa40c,        SubjectDistanceRange);
                 EXIF_TAG(0xa420,        ImageUniqueID);                  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);
           }
   
           static EXIF_tag_value2name &instance(){
                   static EXIF_tag_value2name *singleton=NULL;
                   if(!singleton)
                           singleton=new EXIF_tag_value2name;
                   return *singleton;
         }          }
 } exif_tag_value2name;  };
   
 class EXIF_gps_tag_value2name: public Hash<int, const char*> {  class EXIF_gps_tag_value2name: public Hash<int, const char*> {
 public:  public:
Line 217  public: Line 233  public:
                 EXIF_TAG(0x1D,  GPSDateStamp);                  EXIF_TAG(0x1D,  GPSDateStamp);
                 EXIF_TAG(0x1E,  GPSDifferential);                  EXIF_TAG(0x1E,  GPSDifferential);
         }          }
 } exif_gps_tag_value2name;  
   
 #undef EXIF_TAG          static EXIF_gps_tag_value2name &instance(){
                   static EXIF_gps_tag_value2name *singleton=NULL;
                   if(!singleton)
                           singleton=new EXIF_gps_tag_value2name;
                   return *singleton;
           }
   
   };
   
   
   ///*********************************************** support functions
   
 #ifndef DOXYGEN  
 class Measure_reader {  class Measure_reader {
 public:  public:
         virtual size_t read(const char* &buf, size_t limit)=0;          virtual size_t read(const char* &buf, size_t limit)=0;
         virtual void seek(long value, int whence)=0;          virtual void seek(uint64_t value)=0;
         virtual long tell()=0;          virtual uint64_t tell()=0;
           virtual uint64_t length()=0;
 };  };
   
 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 249  public: Line 274  public:
                 return read_size;                  return read_size;
         }          }
   
         override void seek(long value, int whence) {          override void seek(uint64_t value) {
                 if(lseek(f, value, whence)<0)                  if(pa_lseek(f, value, SEEK_SET)<0)
                         throw Exception(IMAGE_FORMAT,                          throw Exception(IMAGE_FORMAT, &file_name, "seek to %.15g failed: %s (%d)", (double)value, 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 uint64_t tell() { return pa_lseek(f, 0, SEEK_CUR); }
   
           override uint64_t length() { return pa_lseek(f, 0, SEEK_END); }
   
 };  };
   
Line 280  public: Line 304  public:
                 return to_read;                  return to_read;
         }          }
   
         override void seek(long value, int whence) {          override void seek(uint64_t value) {
                 size_t new_offset;                  if(value>(uint64_t)size)
                 switch(whence) {                          throw Exception(IMAGE_FORMAT, &file_name, "seek to %.15g failed: out of buffer (%.15g)", value, size);
                 case SEEK_CUR: new_offset=offset+value; break;                  offset=(size_t)value;
                 case SEEK_SET: new_offset=(size_t)value; break;  
                 default:   
                         throw Exception(0,   
                                 0,   
                                 "whence #%d not supported", 0, whence);   
                         break; // never  
                 }  
                   
                 if((ssize_t)new_offset<0 || new_offset>size)  
                         throw Exception(IMAGE_FORMAT,  
                                 &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);  
                 offset=new_offset;  
         }          }
   
         override long tell() { return offset; }          override uint64_t tell() { return offset; }
   
           override uint64_t length() { return size; }
   
 };  };
   
 #endif  
   
 /// PNG file header  struct Measure_info {
 struct PNG_Header {          ushort width;
         char dummy[12];          ushort height;
         char signature[4]; //< must be "IHDR"          Value** exif;
         uchar high_width[2]; //< image width high bytes [we ignore for now]          Value** xmp;
         uchar width[2]; //< image width low bytes          Charset* xmp_charset;
         uchar high_height[2]; //< image height high bytes [we ignore for now]          bool    video;
         uchar height[4]; //< image height  
 };  };
   
 /// GIF file header  
 struct GIF_Header {  
         char       signature[3];         // 'GIF'  
         char       version[3];  
         uchar       width[2];  
         uchar       height[2];  
         char       dif;  
         char       fonColor;  
         char       nulls;  
 };  
   
 /// JPEG record head  inline ushort x_endian_to_ushort(uchar b0, uchar b1) {
           return (ushort)((b1<<8) + b0);
   }
   
   inline uint x_endian_to_uint(uchar b0, uchar b1, uchar b2, uchar b3) {
           return (uint)(((((b3<<8) + b2)<<8)+b1)<<8)+b0;
   }
   
   inline ushort endian_to_ushort(bool is_big, const uchar *b/* [2] */) {
           return is_big ? x_endian_to_ushort(b[1], b[0]) : x_endian_to_ushort(b[0], b[1]);
   }
   
   inline uint endian_to_uint(bool is_big, const uchar *b /* [4] */) {
           return is_big ? x_endian_to_uint(b[3], b[2], b[1], b[0]) : x_endian_to_uint(b[0], b[1], b[2], b[3]);
   }
   
   
   ///*********************************************** JPEG
   
 struct JPG_Segment_head {  struct JPG_Segment_head {
         uchar marker;          uchar marker;
         uchar code;          uchar code;
Line 335  struct JPG_Segment_head { Line 353  struct JPG_Segment_head {
 };  };
 /// JPEG frame header  /// JPEG frame header
 struct JPG_Size_segment_body {  struct JPG_Size_segment_body {
         char data;                    //< data precision of bits/sample          char data;              //< data precision of bits/sample
         uchar height[2];               //< image height          uchar height[2];        //< image height
         uchar width[2];                //< image width          uchar width[2];         //< image width
         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];          char byte_align_identifier[2];
         char dummy[2]; // always 000A [or 0A00]          uchar signature[2]; // always 000A [or 0A00]
         uchar first_IFD_offset[4]; // Usually the first IFD starts immediately next to TIFF header, so this offset has value '0x00000008'.          uchar first_IFD_offset[4]; // Usually the first IFD starts immediately next to TIFF header, so this offset has value '0x00000008'.
 };  };
   
Line 372  struct JPG_Exif_IFD_entry { Line 385  struct JPG_Exif_IFD_entry {
   
 #define JPEG_EXIF_DATE_CHARS 20  #define JPEG_EXIF_DATE_CHARS 20
   
 //  static Value* parse_IFD_entry_formatted_one_value(bool is_big, ushort format, size_t component_size, const uchar *value) {
   
 inline ushort x_endian_to_ushort(uchar b0, uchar b1) {  
         return (ushort)((b1<<8) + b0);  
 }  
   
 inline uint x_endian_to_uint(uchar b0, uchar b1, uchar b2, uchar b3) {  
         return (uint)(((((b3<<8) + b2)<<8)+b1)<<8)+b0;  
 }  
   
 inline ushort endian_to_ushort(bool is_big, const uchar *b/* [2] */) {  
         return is_big?x_endian_to_ushort(b[1], b[0]):  
                 x_endian_to_ushort(b[0], b[1]);  
 }  
   
 inline uint endian_to_uint(bool is_big, const uchar *b /* [4] */) {  
         return is_big?x_endian_to_uint(b[3], b[2], b[1], b[0]):  
                 x_endian_to_uint(b[0], b[1], b[2], b[3]);  
 }  
   
 static void measure_gif(const String& origin_string,   
                          Measure_reader& reader, ushort& width, ushort& height) {  
   
         const char* buf;  
         const size_t head_size=sizeof(GIF_Header);  
         if(reader.read(buf, head_size)<head_size)  
                 throw Exception(IMAGE_FORMAT,   
                         &origin_string,   
                         "not GIF file - too small");  
         GIF_Header *head=(GIF_Header *)buf;  
   
         if(strncmp(head->signature, "GIF", 3)!=0)  
                 throw Exception(IMAGE_FORMAT,   
                         &origin_string,   
                         "not GIF file - wrong signature");        
   
         width=endian_to_ushort(false, head->width);  
         height=endian_to_ushort(false, head->height);  
 }  
   
 static Value* parse_IFD_entry_formatted_one_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 461  static Value* parse_IFD_entry_formatted_ Line 430  static Value* parse_IFD_entry_formatted_
 // date.C  // date.C
 tm cstr_to_time_t(char *, const char **);  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])  
                         && cstr[length-1]==0) {  
                         char cstr_writable[JPEG_EXIF_DATE_CHARS];   
                         strcpy(cstr_writable, cstr);  
   
                         try {                          try {
                                 tm tmIn=cstr_to_time_t(cstr_writable, 0);                                  tm tmIn=cstr_to_time_t(pa_strdup(cstr), 0);
                                 return new VDate(tmIn);                                  return new VDate(tmIn);
                         }                          }
                         catch(...) { /*ignore bad date times*/ }                          catch(...) { /*ignore bad date times*/ }
Line 491  static Value* parse_IFD_entry_formatted_ Line 453  static Value* parse_IFD_entry_formatted_
         HashStringValue& hash=result->hash();          HashStringValue& hash=result->hash();
         for(uint i=0; i<components_count; i++, value+=component_size) {          for(uint i=0; i<components_count; i++, value+=component_size) {
                 hash.put(                  hash.put(
                         String::Body::Format(i),                          pa_uitoa(i),
                         parse_IFD_entry_formatted_one_value(is_big, format, component_size, value));                          parse_IFD_entry_formatted_one_value(is_big, format, component_size, value));
         }          }
   
         return result;          return result;
 }  }
   
 static Value* parse_IFD_entry_value(  static Value* parse_IFD_entry_value(bool is_big, Measure_reader& reader, uint64_t 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 535  static Value* parse_IFD_entry_value( Line 495  static Value* parse_IFD_entry_value(
         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();                  uint64_t 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));
                         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);
         }          }
   
         return result;          return result;
 }  }
   
 static void parse_IFD(HashStringValue& hash,  static void parse_IFD(HashStringValue& hash, bool is_big, Measure_reader& reader, uint64_t tiff_base, bool gps=false);
                       bool is_big, Measure_reader& reader, long tiff_base, bool gps=false);  
   
 static void parse_IFD_entry(HashStringValue& hash,  static void parse_IFD_entry(HashStringValue& hash, bool is_big, Measure_reader& reader, uint64_t tiff_base, JPG_Exif_IFD_entry& entry, bool gps=false) {
                             bool is_big, Measure_reader& reader, long tiff_base,  
                             JPG_Exif_IFD_entry& entry, bool gps=false) {  
         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 || tag==JPG_IFD_TAG_EXIF_GPS_OFFSET){          if(tag==JPG_IFD_TAG_EXIF_OFFSET || tag==JPG_IFD_TAG_EXIF_GPS_OFFSET){
                 long remembered=reader.tell();                  uint64_t 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));
                         parse_IFD(hash, is_big, reader, tiff_base, (tag==JPG_IFD_TAG_EXIF_GPS_OFFSET)?true:gps);                          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);
                 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=(gps)?exif_gps_tag_value2name.get(tag):exif_tag_value2name.get(tag))                  if(const char* name=(gps ? EXIF_gps_tag_value2name::instance().get(tag) : EXIF_tag_value2name::instance().get(tag)))
                         hash.put(String::Body(name), value);                          hash.put(name, value);
                 else                  else
                         hash.put(String::Body::Format(tag), value);                          hash.put(pa_uitoa((int)tag), value);
         }          }
 }  }
   
 static void parse_IFD(  static void parse_IFD(HashStringValue& hash, bool is_big, Measure_reader& reader, uint64_t tiff_base, bool gps) {
                       HashStringValue& hash,  
                       bool is_big, Measure_reader& reader, long tiff_base, bool gps) {  
         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 601  static void parse_IFD( Line 550  static void parse_IFD(
         // 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();          uint64_t 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))
                 return 0;                  return 0;
   
Line 620  static Value* parse_exif(Measure_reader& Line 561  static Value* parse_exif(Measure_reader&
         bool is_big=head->byte_align_identifier[0]=='M'; // [M]otorola vs [I]ntel          bool is_big=head->byte_align_identifier[0]=='M'; // [M]otorola vs [I]ntel
   
         uint first_IFD_offset=endian_to_uint(is_big, head->first_IFD_offset);          uint first_IFD_offset=endian_to_uint(is_big, head->first_IFD_offset);
         reader.seek(tiff_base+first_IFD_offset, SEEK_SET);          reader.seek(tiff_base+first_IFD_offset);
   
         VHash* vhash=new VHash;          VHash* vhash=new VHash;
   
Line 630  static Value* parse_exif(Measure_reader& Line 571  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*/;                  uint64_t segment_base=reader.tell()+2/*marker,code*/;
                 if(reader.read(buf, sizeof(JPG_Segment_head))<sizeof(JPG_Segment_head))                  if(reader.read(buf, sizeof(JPG_Segment_head))<sizeof(JPG_Segment_head))
                         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://park2.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 679  static void measure_jpeg(const String& o Line 644  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 - cannot 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);
         }          }
   
         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,   ///*********************************************** TIFF
                          Measure_reader& reader, ushort& width, ushort& height) {  
   static bool parse_tiff_IFD(bool is_big, Measure_reader& reader, Measure_info &info) {
           const char* buf;
           if(reader.read(buf, sizeof(JPG_Exif_IFD_begin))<sizeof(JPG_Exif_IFD_begin))
                   return false;
           JPG_Exif_IFD_begin *start=(JPG_Exif_IFD_begin *)buf;
   
           ushort directory_entry_count=endian_to_ushort(is_big, start->directory_entry_count);
           for(int i=0; i<directory_entry_count; i++) {
                   if(reader.read(buf, sizeof(JPG_Exif_IFD_entry))<sizeof(JPG_Exif_IFD_entry))
                           return false;
   
                   JPG_Exif_IFD_entry *entry=(JPG_Exif_IFD_entry *)buf;
                   ushort entry_tag=endian_to_ushort(is_big, entry->tag);
   
                   if(entry_tag == 256 || entry_tag == 257){
                           ushort entry_format=endian_to_ushort(is_big, entry->format);
                           if(entry_format != 3 && entry_format != 4 || endian_to_uint(is_big, entry->components_count) != 1)
                                   return false;
                           uint value = (entry_format == 3) ? endian_to_ushort(is_big, entry->value_or_offset_to_it) : endian_to_uint(is_big, entry->value_or_offset_to_it);
                           (entry_tag == 256) ? info.width=(short)value : info.height=(short)value;
                           if(info.width && info.height)
                                   return true;
                   }
           }
   
           return false;
           // then goes: LLLLLLLL Offset to next IFD [not going there]
   }
   
   static void measure_tiff(const String& origin_string, Measure_reader& reader, Measure_info &info) {
           const char* buf;
   
           if(reader.read(buf, sizeof(JPG_Exif_TIFF_header))<sizeof(JPG_Exif_TIFF_header))
                   throw Exception(IMAGE_FORMAT, &origin_string, "not TIFF file - too small");
   
           JPG_Exif_TIFF_header *head=(JPG_Exif_TIFF_header *)buf;
   
           if(strncmp(head->byte_align_identifier, "II", 2)!=0 && strncmp(head->byte_align_identifier, "MM", 2)!=0)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not TIFF file - wrong signature");
   
           bool is_big=head->byte_align_identifier[0]=='M'; // [M]otorola vs [I]ntel
   
           if(endian_to_ushort(is_big, head->signature) != 42)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not TIFF file - wrong signature");
   
           reader.seek(endian_to_uint(is_big, head->first_IFD_offset));
           if(!parse_tiff_IFD(is_big, reader, info))
                   throw Exception(IMAGE_FORMAT, &origin_string, "broken TIFF file - size field entry not found");
   }
   
   
   ///*********************************************** GIF
   
   struct GIF_Header {
           char    signature[3];   // 'GIF'
           char    version[3];
           uchar   width[2];
           uchar   height[2];
           char    dif;
           char    fonColor;
           char    nulls;
   };
   
   static void measure_gif(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
   
           const char* buf;
           const size_t head_size=sizeof(GIF_Header);
           if(reader.read(buf, head_size)<head_size)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not GIF file - too small");
           GIF_Header *head=(GIF_Header *)buf;
   
           if(strncmp(head->signature, "GIF", 3)!=0)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not GIF file - wrong signature");
   
           width=endian_to_ushort(false, head->width);
           height=endian_to_ushort(false, head->height);
   }
   
   
   ///*********************************************** PNG
   
   struct PNG_Header {
           char    dummy[12];
           char    signature[4];   //< must be "IHDR"
           uchar   high_width[2];  //< image width high bytes [we ignore for now]
           uchar   width[2];       //< image width low bytes
           uchar   high_height[2]; //< image height high bytes [we ignore for now]
           uchar   height[4];      //< image height
   };
   
   static void measure_png(const String& origin_string, 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);
 }  }
   
 // measure center  
   
 static void measure(const String& file_name,   ///*********************************************** BMP
                          Measure_reader& reader, ushort& width, ushort& height, Value** exif) {  
   struct BMP_Header {
           char    signature[2];   // 'BM'
           uchar   file_size[4];
           uchar   reserved[4];
           uchar   bitmap_offset[4];
           uchar   header_size[4];
           uchar   width[2];
           uchar   high_width[2];  //< image width high bytes [we ignore for now]
           uchar   height[2];
           uchar   high_height[2]; //< image height high bytes [we ignore for now]
   };
   
   static void measure_bmp(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
   
           const char* buf;
           const size_t head_size=sizeof(BMP_Header);
           if(reader.read(buf, head_size)<head_size)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not BMP file - too small");
           BMP_Header *head=(BMP_Header *)buf;
   
           if(strncmp(head->signature, "BM", 2)!=0)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not BMP file - wrong signature");
   
           if((uint)reader.length() != endian_to_uint(false, head->file_size))
                   throw Exception(IMAGE_FORMAT, &origin_string, "not BMP file - length header and file size do not match");
   
           width=endian_to_ushort(false, head->width);
           height=endian_to_ushort(false, head->height);
   }
   
   
   ///*********************************************** WEBP
   
   struct WEBP_Header {
           char signature_riff[4]; // 'RIFF'
           uchar file_size[4];
           char signature[4];      // 'WEBP'
           char format[4];         // 'VP8 ' or 'VP8L' or 'VP8X'
   };
   
   struct WEBP_VP8_Chunk {
           uchar size[4];
           char tag[3];
           uchar signature[3];    // 0x9D 0x01 0x2A
           uchar width[2];        // 14 bits each
           uchar height[2];       // 14 bits each
   };
   
   struct WEBP_VP8L_Chunk {
           uchar size[4];
           char signature;        // 0x2F
           uchar width_height[4]; // 14 bits each
   };
   
   struct WEBP_X_Chunk {
           uchar size[4];
           char reserved[4];
           uchar width[3];
           uchar height[3];
   };
   
   static void measure_webp(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
           const char* buf;
   
           if(reader.read(buf, sizeof(WEBP_Header))<sizeof(WEBP_Header))
                   throw Exception(IMAGE_FORMAT, &origin_string, "not WEBP file - too small");
   
           WEBP_Header *head=(WEBP_Header *)buf;
   
           if(strncmp(head->signature_riff, "RIFF", 4)!=0 || strncmp(head->signature, "WEBP", 4)!=0)
                   throw Exception(IMAGE_FORMAT, &origin_string, "not WEBP file - wrong signature");
   
           if(strncmp(head->format, "VP8 ", 4)==0){
                   if(reader.read(buf, sizeof(WEBP_VP8_Chunk))<sizeof(WEBP_VP8_Chunk))
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - too small VP8 chunk");
   
                   WEBP_VP8_Chunk *chunk=(WEBP_VP8_Chunk *)buf;
                   if (chunk->signature[0] != 0x9D || chunk->signature[1] != 0x01 || chunk->signature[2] != 0x2A)
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - wrong VP8 chunk signature");
   
                   width=endian_to_ushort(false, chunk->width) & 0x3FFF;
                   height=endian_to_ushort(false, chunk->height) & 0x3FFF;
           } else if(strncmp(head->format, "VP8L", 4)==0){
                   if(reader.read(buf, sizeof(WEBP_VP8L_Chunk))<sizeof(WEBP_VP8L_Chunk))
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - too small VP8L chunk");
   
                   WEBP_VP8L_Chunk *chunk=(WEBP_VP8L_Chunk *)buf;
                   if(chunk->signature != 0x2F)
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - wrong VP8L chunk signature");
   
                   uint wh=endian_to_uint(false, chunk->width_height);
                   width=(wh & 0x3FFF) + 1;
                   height=((wh >> 14) & 0x3FFF) + 1;
           } else if (strncmp(head->format, "VP8X", 4)==0){
                   if(reader.read(buf, sizeof(WEBP_X_Chunk))<sizeof(WEBP_X_Chunk))
                           throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - too small VP8X chunk");
   
                   WEBP_X_Chunk *chunk=(WEBP_X_Chunk *)buf;
   
                   width=endian_to_ushort(false, chunk->width) + 1;   // we ignore third byte to simplify code
                   height=endian_to_ushort(false, chunk->height) + 1; // we ignore third byte to simplify code
           } else throw Exception(IMAGE_FORMAT, &origin_string, "broken WEBP file - invalid chunk signature");
   }
   
   
   ///*********************************************** MP4
   
   struct MP4_Header {
           uchar size[4];
           char signature[4];   // 'ftyp' in first chunk
   };
   
   struct MP4_ExtSize {
           uchar high[4];
           uchar low[4];
   };
   
   struct MP4_Tkhd {
           uchar width[4];
           uchar height[4];
   };
   
   static bool measure_mp4(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height, uint64_t anext, const char* lastTkhd=NULL) {
           for(bool first=anext==0;;){
                   const char* buf;
                   uint64_t next=reader.tell();
   
                   if(reader.read(buf, sizeof(MP4_Header))<sizeof(MP4_Header))
                           throw Exception(IMAGE_FORMAT, &origin_string, first ? "not MP4 file - too small" : "broken MP4 file - truncated chunk header");
   
                   MP4_Header *head=(MP4_Header *)buf;
                   uint64_t size=endian_to_uint(true, head->size);
   
                   if(size==1){
                           if(reader.read(buf, sizeof(MP4_ExtSize))<sizeof(MP4_ExtSize))
                                   throw Exception(IMAGE_FORMAT, &origin_string, "broken MP4 file - truncated chunk extended size header");
                           MP4_ExtSize *ext_size=(MP4_ExtSize *)buf;
                           size=((uint64_t)endian_to_uint(true, ext_size->high) << 32) + endian_to_uint(true, ext_size->low);
                   }
                   next+=size;
   
                   if(first){
                           if(strncmp(head->signature, "ftyp", 4)!=0)
                                   throw Exception(IMAGE_FORMAT, &origin_string, "not MP4 file - wrong signature");
                           first=false;
                           anext=reader.length(); // to avoid reading beyond EOF
                   } else if(strncmp(head->signature, "moov", 4)==0 || strncmp(head->signature, "mdia", 4)==0 || strncmp(head->signature, "trak", 4)==0) {
                           if(measure_mp4(origin_string, reader, width, height, next, lastTkhd))
                                   return true;
                   } else if(strncmp(head->signature, "tkhd", 4)==0) {
                           if(size>8){
                                   reader.seek(next-8);
                                   if(reader.read(lastTkhd, sizeof(MP4_Tkhd))<sizeof(MP4_Tkhd))
                                           throw Exception(IMAGE_FORMAT, &origin_string, "broken MP4 file - bad tkhd chunk");
                           }
                   } else if (strncmp(head->signature, "hdlr", 4)==0) {
                           if(size>12){
                                   const char* hdlr;
                                   if(reader.read(hdlr, 12)<12)
                                           throw Exception(IMAGE_FORMAT, &origin_string, "broken MP4 file - bad hdlr chunk");
                                   if(lastTkhd && strncmp(hdlr+8, "vide", 4)==0) {
                                           MP4_Tkhd *tkhd=(MP4_Tkhd *)lastTkhd;
                                           width=endian_to_ushort(true, tkhd->width);
                                           height=endian_to_ushort(true, tkhd->height);
                                           return true;
                                   }
                           }
                   }
                   if(anext && next>=anext)
                           break;
                   reader.seek(next);
           }
           return false;
   }
   
   static void measure_mp4(const String& origin_string, Measure_reader& reader, ushort& width, ushort& height) {
           if(!measure_mp4(origin_string, reader, width, height, 0))
                   throw Exception(IMAGE_FORMAT, &origin_string, "unsupported MP4 file - size not found");
   }
   
   ///*********************************************** measure center
   
   static void measure(const String& file_name, Measure_reader& reader, Measure_info &info) {
         const char* file_name_cstr=file_name.taint_cstr(String::L_FILE_SPEC);          const char* file_name_cstr=file_name.taint_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 if(strcasecmp(cext, "BMP")==0)
                           measure_bmp(file_name, reader, info.width, info.height);
                   else if(strcasecmp(cext, "WEBP")==0)
                           measure_webp(file_name, reader, info.width, info.height);
                   else if(strcasecmp(cext, "TIF")==0 || strcasecmp(cext, "TIFF")==0)
                           measure_tiff(file_name, reader, info);
                   else if(strcasecmp(cext, "MP4")==0 || strcasecmp(cext, "MOV")==0)
                           if(info.video)
                                   measure_mp4(file_name, reader, info.width, info.height);
                           else
                                   throw Exception(IMAGE_FORMAT, &file_name, "handling disabled for file name extension '%s'", cext);
                 else                  else
                         throw Exception(IMAGE_FORMAT,                           throw Exception(IMAGE_FORMAT, &file_name, "unhandled 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, "cannot determine file 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, false };
   
           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(key == "video") {
                                           info.video=r.process(*value).as_bool();
                                           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.full_disk_path(*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(String::L_AS_IS);                  VFile* vfile=data.as_vfile();
                 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, internal(starting with "line-") attributes and border attribute with empty value  
         if(  
                 (info->skip && info->skip->get(key))  
                 || key.pos("line-")==0  
                 || (key=="border" && !value->is_defined())  
         )  
                 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 823  static void _html(Request& r, MethodPara Line 1055  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], false/*don't intercept string*/);                  Value& vattribs=r.process(params[0]);
                 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.full_disk_path(file_name).taint_cstr(String::L_FILE_SPEC);
         const char* file_name_cstr=r.absolute(file_name).taint_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, "cannot open '%s'", file_name_cstr);
                         0,   
                         "can not open '%s'", file_name_cstr);  
         }          }
 }  }
   
Line 894  static void _gif(Request& r, MethodParam Line 1125  static void _gif(Request& r, MethodParam
   
         vfile.set_binary(false/*not tainted*/, (const char *)buf.ptr, buf.size, file_name, new VString(*new String("image/gif")));          vfile.set_binary(false/*not tainted*/, (const char *)buf.ptr, buf.size, file_name, new VString(*new String("image/gif")));
   
         r.write_no_lang(vfile);          r.write(vfile);
 }  }
   
 static void _line(Request& r, MethodParams& params) {  static void _line(Request& r, MethodParams& params) {
Line 940  static void _bar(Request& r, MethodParam Line 1171  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  #ifndef DOXYGEN
 static void add_point(int x, int y,   static void add_point(int x, int y, gdImage::Point **p) {
                                           gdImage::Point **p) {  
         (**p).x=x;          (**p).x=x;
         (**p).y=y;          (**p).y=y;
         (*p)++;          (*p)++;
Line 968  static void _replace(Request& r, MethodP Line 1195  static void _replace(Request& r, MethodP
         gdImage::Point* all_p=0;          gdImage::Point* all_p=0;
         size_t count=0;          size_t count=0;
         if(params.count() == 3){          if(params.count() == 3){
                 Table* table=params.as_table(2, "coordinates");                  if(Table* table=params.as_table(2, "coordinates")){
                 count=table->count();                          count=table->count();
                 all_p=new(PointerFreeGC) gdImage::Point[count];                          all_p=new(PointerFreeGC) gdImage::Point[count];
                 gdImage::Point* add_p=all_p;                          gdImage::Point* add_p=all_p;
                 table->for_each(add_point, &add_p);                          table->for_each(add_point, &add_p);
                   }
         } else {          } else {
                 int max_x=image.SX()-1;                  int max_x=image.SX()-1;
                 int max_y=image.SY()-1;                  int max_y=image.SY()-1;
Line 994  static void _replace(Request& r, MethodP Line 1222  static void _replace(Request& r, MethodP
 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_table(1, "coordinates");          if(Table* table=params.as_table(1, "coordinates")){
                   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;                      table->for_each(add_point, &add_p);
         table->for_each(add_point, &add_p);                  image.Polygon(all_p, table->count(), image.Color(params.as_int(0, "color must be int", r)), false/*not closed*/);
         image.Polygon(all_p, table->count(),           }
                 image.Color(params.as_int(0, "color must be int", r)),  
                 false/*not closed*/);  
 }  }
   
 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=(Table*)params.as_table(1, "coordinates");          if(Table* table=(Table*)params.as_table(1, "coordinates")){
                   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;                      table->for_each(add_point, &add_p);
         table->for_each(add_point, &add_p);                  image.Polygon(all_p, table->count(), image.Color(params.as_int(0, "color must be int", r)));
         image.Polygon(all_p, table->count(),           }
                 image.Color(params.as_int(0, "color must be int", r)));  
 }  }
   
 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=(Table*)params.as_table(1, "coordinates");          if(Table* table=(Table*)params.as_table(1, "coordinates")){
                   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;                      table->for_each(add_point, &add_p);
         table->for_each(add_point, &add_p);                  image.FilledPolygon(all_p, table->count(), image.Color(params.as_int(0, "color must be int", r)));
         image.FilledPolygon(all_p, table->count(),           }
                 image.Color(params.as_int(0, "color must be int", r)));  
 }  }
   
 // font  // font
Line 1034  static void _polybar(Request& r, MethodP Line 1258  static void _polybar(Request& r, MethodP
   
 // Font class  // Font class
   
 Font::Font(  Font::Font(Charset& asource_charset, const String& aalphabet, gdImage* aifont, int aheight, int amonospace, int aspacebarspace, int aletterspacing):
         Charset& asource_charset,           letterspacing(aletterspacing),
         const String& aalphabet,   
         gdImage* aifont, int aheight, int amonospace, int aspacebarspace, int aletterspacing):  
         fsource_charset(asource_charset),  
         height(aheight),          height(aheight),
         monospace(amonospace),          monospace(amonospace),
         spacebarspace(aspacebarspace),          spacebarspace(aspacebarspace),
         letterspacing(aletterspacing),  
         ifont(aifont),          ifont(aifont),
         alphabet(aalphabet)     {          alphabet(aalphabet),
           fsource_charset(asource_charset){
   
         if(fsource_charset.isUTF8()){          if(fsource_charset.isUTF8()){
                 size_t index=0;                  size_t index=0;
Line 1129  static void _font(Request& r, MethodPara Line 1350  static void _font(Request& r, MethodPara
         const String& alphabet=params.as_string(0, "alphabet must not be code");          const String& alphabet=params.as_string(0, "alphabet must not be code");
         size_t alphabet_length=alphabet.length(r.charsets.source());          size_t alphabet_length=alphabet.length(r.charsets.source());
         if(!alphabet_length)          if(!alphabet_length)
                 throw Exception(PARSER_RUNTIME,                  throw Exception(PARSER_RUNTIME, 0, "alphabet must not be empty");
                         0,  
                         "alphabet must not be empty");  
   
         gdImage* image=load(r, params.as_string(1, FILE_NAME_MUST_NOT_BE_CODE));          gdImage* image=load(r, params.as_string(1, FILE_NAME_MUST_NOT_BE_CODE));
   
Line 1146  static void _font(Request& r, MethodPara Line 1365  static void _font(Request& r, MethodPara
                         int valid_options=0;                          int valid_options=0;
                         if(Value* vspacebar_width=options->get(spacebar_width_name)){                          if(Value* vspacebar_width=options->get(spacebar_width_name)){
                                 valid_options++;                                  valid_options++;
                                 spacebar_width=r.process_to_value(*vspacebar_width).as_int();                                  spacebar_width=r.process(*vspacebar_width).as_int();
                         }                          }
                         if(Value* vmonospace_width=options->get(monospace_width_name)){                          if(Value* vmonospace_width=options->get(monospace_width_name)){
                                 valid_options++;                                  valid_options++;
                                 monospace_width=r.process_to_value(*vmonospace_width).as_int();                                  monospace_width=r.process(*vmonospace_width).as_int();
                                 if(!monospace_width)                                  if(!monospace_width)
                                         monospace_width=image->SX();                                          monospace_width=image->SX();
                         }                          }
                         if(Value* vletter_spacing=options->get(letter_spacing_name)){                          if(Value* vletter_spacing=options->get(letter_spacing_name)){
                                 valid_options++;                                  valid_options++;
                                 letter_spacing=r.process_to_value(*vletter_spacing).as_int();                                  letter_spacing=r.process(*vletter_spacing).as_int();
                         }                          }
                         if(valid_options!=options->count())                          if(valid_options!=options->count())
                                 throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);                                  throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
Line 1172  static void _font(Request& r, MethodPara Line 1391  static void _font(Request& r, MethodPara
         }          }
   
         if(int remainder=image->SY() % 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,  
                         "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));
                 r.charsets.source(),  
                 alphabet,   
                 image,   
                 image->SY() / alphabet_length, monospace_width, spacebar_width, letter_spacing));  
 }  }
   
 static void _text(Request& r, MethodParams& params) {  static void _text(Request& r, MethodParams& params) {
Line 1197  static void _length(Request& r, MethodPa Line 1409  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 1243  static void _circle(Request& r, MethodPa Line 1455  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)) {          if(VImage* vimage=dynamic_cast<VImage *>(&value)) {
                 return static_cast<VImage *>(vimage)->image();                  return 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 1284  static void _pixel(Request& r, MethodPar Line 1494  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 1292  static void _pixel(Request& r, MethodPar Line 1502  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]

Removed from v.1.147  
changed lines
  Added in v.1.192


E-mail: