summaryrefslogtreecommitdiff
path: root/plugins/FreeImage/Source/Metadata/IPTC.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/FreeImage/Source/Metadata/IPTC.cpp')
-rw-r--r--plugins/FreeImage/Source/Metadata/IPTC.cpp650
1 files changed, 325 insertions, 325 deletions
diff --git a/plugins/FreeImage/Source/Metadata/IPTC.cpp b/plugins/FreeImage/Source/Metadata/IPTC.cpp
index 8bfb1b18c7..1aba46c094 100644
--- a/plugins/FreeImage/Source/Metadata/IPTC.cpp
+++ b/plugins/FreeImage/Source/Metadata/IPTC.cpp
@@ -1,325 +1,325 @@
-// ==========================================================
-// Metadata functions implementation
-//
-// Design and implementation by
-// - Hervé Drolon (drolon@infonie.fr)
-//
-// This file is part of FreeImage 3
-//
-// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
-// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
-// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
-// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
-// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
-// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
-// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
-// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
-// THIS DISCLAIMER.
-//
-// Use at your own risk!
-// ==========================================================
-
-#ifdef _MSC_VER
-#pragma warning (disable : 4786) // identifier was truncated to 'number' characters
-#endif
-
-#include "FreeImage.h"
-#include "Utilities.h"
-#include "FreeImageTag.h"
-
-// ----------------------------------------------------------
-// IPTC JPEG / TIFF markers routines
-// ----------------------------------------------------------
-
-static const char* IPTC_DELIMITER = ";"; // keywords/supplemental category delimiter
-/**
- Read and decode IPTC binary data
-*/
-BOOL
-read_iptc_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
- char defaultKey[16];
- size_t length = datalen;
- BYTE *profile = (BYTE*)dataptr;
-
- std::string Keywords;
- std::string SupplementalCategory;
-
- WORD tag_id;
-
- if(!dataptr || (datalen == 0)) {
- return FALSE;
- }
-
- // create a tag
-
- FITAG *tag = FreeImage_CreateTag();
-
- TagLib& tag_lib = TagLib::instance();
-
- // find start of the BIM portion of the binary data
- size_t offset = 0;
- while(offset < length - 1) {
- if((profile[offset] == 0x1C) && (profile[offset+1] == 0x02))
- break;
- offset++;
- }
-
- // for each tag
- while (offset < length) {
-
- // identifies start of a tag
- if (profile[offset] != 0x1c) {
- break;
- }
- // we need at least five bytes left to read a tag
- if ((offset + 5) >= length) {
- break;
- }
-
- offset++;
-
- int directoryType = profile[offset++];
- int tagType = profile[offset++];;
- int tagByteCount = ((profile[offset] & 0xFF) << 8) | (profile[offset + 1] & 0xFF);
- offset += 2;
-
- if ((offset + tagByteCount) > length) {
- // data for tag extends beyond end of iptc segment
- break;
- }
-
- // process the tag
-
- tag_id = (WORD)(tagType | (directoryType << 8));
-
- FreeImage_SetTagID(tag, tag_id);
- FreeImage_SetTagLength(tag, tagByteCount);
-
- // allocate a buffer to store the tag value
- BYTE *iptc_value = (BYTE*)malloc((tagByteCount + 1) * sizeof(BYTE));
- memset(iptc_value, 0, (tagByteCount + 1) * sizeof(BYTE));
-
- // get the tag value
-
- switch (tag_id) {
- case TAG_RECORD_VERSION:
- {
- // short
- FreeImage_SetTagType(tag, FIDT_SSHORT);
- FreeImage_SetTagCount(tag, 1);
- short *pvalue = (short*)&iptc_value[0];
- *pvalue = (short)((profile[offset] << 8) | profile[offset + 1]);
- FreeImage_SetTagValue(tag, pvalue);
- break;
- }
-
- case TAG_RELEASE_DATE:
- case TAG_DATE_CREATED:
- // Date object
- case TAG_RELEASE_TIME:
- case TAG_TIME_CREATED:
- // time
- default:
- {
- // string
- FreeImage_SetTagType(tag, FIDT_ASCII);
- FreeImage_SetTagCount(tag, tagByteCount);
- for(int i = 0; i < tagByteCount; i++) {
- iptc_value[i] = profile[offset + i];
- }
- iptc_value[tagByteCount] = '\0';
- FreeImage_SetTagValue(tag, (char*)&iptc_value[0]);
- break;
- }
- }
-
- if(tag_id == TAG_SUPPLEMENTAL_CATEGORIES) {
- // concatenate the categories
- if(SupplementalCategory.length() == 0) {
- SupplementalCategory.append((char*)iptc_value);
- } else {
- SupplementalCategory.append(IPTC_DELIMITER);
- SupplementalCategory.append((char*)iptc_value);
- }
- }
- else if(tag_id == TAG_KEYWORDS) {
- // concatenate the keywords
- if(Keywords.length() == 0) {
- Keywords.append((char*)iptc_value);
- } else {
- Keywords.append(IPTC_DELIMITER);
- Keywords.append((char*)iptc_value);
- }
- }
- else {
- // get the tag key and description
- const char *key = tag_lib.getTagFieldName(TagLib::IPTC, tag_id, defaultKey);
- FreeImage_SetTagKey(tag, key);
- const char *description = tag_lib.getTagDescription(TagLib::IPTC, tag_id);
- FreeImage_SetTagDescription(tag, description);
-
- // store the tag
- if(key) {
- FreeImage_SetMetadata(FIMD_IPTC, dib, key, tag);
- }
- }
-
- free(iptc_value);
-
- // next tag
- offset += tagByteCount;
-
- }
-
- // store the 'keywords' tag
- if(Keywords.length()) {
- FreeImage_SetTagType(tag, FIDT_ASCII);
- FreeImage_SetTagID(tag, TAG_KEYWORDS);
- FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_KEYWORDS, defaultKey));
- FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_KEYWORDS));
- FreeImage_SetTagLength(tag, (DWORD)Keywords.length());
- FreeImage_SetTagCount(tag, (DWORD)Keywords.length());
- FreeImage_SetTagValue(tag, (char*)Keywords.c_str());
- FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
- }
-
- // store the 'supplemental category' tag
- if(SupplementalCategory.length()) {
- FreeImage_SetTagType(tag, FIDT_ASCII);
- FreeImage_SetTagID(tag, TAG_SUPPLEMENTAL_CATEGORIES);
- FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES, defaultKey));
- FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES));
- FreeImage_SetTagLength(tag, (DWORD)SupplementalCategory.length());
- FreeImage_SetTagCount(tag, (DWORD)SupplementalCategory.length());
- FreeImage_SetTagValue(tag, (char*)SupplementalCategory.c_str());
- FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
- }
-
- // delete the tag
-
- FreeImage_DeleteTag(tag);
-
- return TRUE;
-}
-
-// --------------------------------------------------------------------------
-
-static BYTE*
-append_iptc_tag(BYTE *profile, unsigned *profile_size, WORD id, DWORD length, const void *value) {
- BYTE *buffer = NULL;
-
- // calculate the new buffer size
- size_t buffer_size = (5 + *profile_size + length) * sizeof(BYTE);
- buffer = (BYTE*)malloc(buffer_size);
- if(!buffer)
- return NULL;
-
- // add the header
- buffer[0] = 0x1C;
- buffer[1] = 0x02;
- // add the tag type
- buffer[2] = (BYTE)(id & 0x00FF);
- // add the tag length
- buffer[3] = (BYTE)(length >> 8);
- buffer[4] = (BYTE)(length & 0xFF);
- // add the tag value
- memcpy(buffer + 5, (BYTE*)value, length);
- // append the previous profile
- if(NULL == profile) {
- *profile_size = (5 + length);
- }
- else {
- memcpy(buffer + 5 + length, profile, *profile_size);
- *profile_size += (5 + length);
- free(profile);
- }
-
- return buffer;
-}
-
-/**
-Encode IPTC metadata into a binary buffer.
-The buffer is allocated by the function and must be freed by the caller.
-*/
-BOOL
-write_iptc_profile(FIBITMAP *dib, BYTE **profile, unsigned *profile_size) {
- FITAG *tag = NULL;
- FIMETADATA *mdhandle = NULL;
-
- BYTE *buffer = NULL;
- unsigned buffer_size = 0;
-
- // parse all IPTC tags and rebuild a IPTC profile
- mdhandle = FreeImage_FindFirstMetadata(FIMD_IPTC, dib, &tag);
-
- if(mdhandle) {
- do {
- WORD tag_id = FreeImage_GetTagID(tag);
-
- // append the tag to the profile
-
- switch(tag_id) {
- case TAG_RECORD_VERSION:
- // ignore (already handled)
- break;
-
- case TAG_SUPPLEMENTAL_CATEGORIES:
- case TAG_KEYWORDS:
- if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
- std::string value = (const char*)FreeImage_GetTagValue(tag);
-
- // split the tag value
- std::vector<std::string> output;
- std::string delimiter = IPTC_DELIMITER;
-
- size_t offset = 0;
- size_t delimiterIndex = 0;
-
- delimiterIndex = value.find(delimiter, offset);
- while (delimiterIndex != std::string::npos) {
- output.push_back(value.substr(offset, delimiterIndex - offset));
- offset += delimiterIndex - offset + delimiter.length();
- delimiterIndex = value.find(delimiter, offset);
- }
- output.push_back(value.substr(offset));
-
- // add as many tags as there are comma separated strings
- for(int i = 0; i < (int)output.size(); i++) {
- std::string& tag_value = output[i];
- buffer = append_iptc_tag(buffer, &buffer_size, tag_id, (DWORD)tag_value.length(), tag_value.c_str());
- }
-
- }
- break;
-
- case TAG_URGENCY:
- if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
- DWORD length = 1; // keep the first octet only
- buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
- }
- break;
-
- default:
- if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
- DWORD length = FreeImage_GetTagLength(tag);
- buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
- }
- break;
- }
-
- } while(FreeImage_FindNextMetadata(mdhandle, &tag));
-
- FreeImage_FindCloseMetadata(mdhandle);
-
- // add the DirectoryVersion tag
- const short version = 0x0200;
- buffer = append_iptc_tag(buffer, &buffer_size, TAG_RECORD_VERSION, sizeof(version), &version);
-
- *profile = buffer;
- *profile_size = buffer_size;
-
- return TRUE;
- }
-
- return FALSE;
-}
+// ==========================================================
+// Metadata functions implementation
+//
+// Design and implementation by
+// - Hervé Drolon (drolon@infonie.fr)
+//
+// This file is part of FreeImage 3
+//
+// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
+// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
+// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
+// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
+// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
+// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
+// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
+// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
+// THIS DISCLAIMER.
+//
+// Use at your own risk!
+// ==========================================================
+
+#ifdef _MSC_VER
+#pragma warning (disable : 4786) // identifier was truncated to 'number' characters
+#endif
+
+#include "FreeImage.h"
+#include "Utilities.h"
+#include "FreeImageTag.h"
+
+// ----------------------------------------------------------
+// IPTC JPEG / TIFF markers routines
+// ----------------------------------------------------------
+
+static const char* IPTC_DELIMITER = ";"; // keywords/supplemental category delimiter
+/**
+ Read and decode IPTC binary data
+*/
+BOOL
+read_iptc_profile(FIBITMAP *dib, const BYTE *dataptr, unsigned int datalen) {
+ char defaultKey[16];
+ size_t length = datalen;
+ BYTE *profile = (BYTE*)dataptr;
+
+ std::string Keywords;
+ std::string SupplementalCategory;
+
+ WORD tag_id;
+
+ if(!dataptr || (datalen == 0)) {
+ return FALSE;
+ }
+
+ // create a tag
+
+ FITAG *tag = FreeImage_CreateTag();
+
+ TagLib& tag_lib = TagLib::instance();
+
+ // find start of the BIM portion of the binary data
+ size_t offset = 0;
+ while(offset < length - 1) {
+ if((profile[offset] == 0x1C) && (profile[offset+1] == 0x02))
+ break;
+ offset++;
+ }
+
+ // for each tag
+ while (offset < length) {
+
+ // identifies start of a tag
+ if (profile[offset] != 0x1c) {
+ break;
+ }
+ // we need at least five bytes left to read a tag
+ if ((offset + 5) >= length) {
+ break;
+ }
+
+ offset++;
+
+ int directoryType = profile[offset++];
+ int tagType = profile[offset++];;
+ int tagByteCount = ((profile[offset] & 0xFF) << 8) | (profile[offset + 1] & 0xFF);
+ offset += 2;
+
+ if ((offset + tagByteCount) > length) {
+ // data for tag extends beyond end of iptc segment
+ break;
+ }
+
+ // process the tag
+
+ tag_id = (WORD)(tagType | (directoryType << 8));
+
+ FreeImage_SetTagID(tag, tag_id);
+ FreeImage_SetTagLength(tag, tagByteCount);
+
+ // allocate a buffer to store the tag value
+ BYTE *iptc_value = (BYTE*)malloc((tagByteCount + 1) * sizeof(BYTE));
+ memset(iptc_value, 0, (tagByteCount + 1) * sizeof(BYTE));
+
+ // get the tag value
+
+ switch (tag_id) {
+ case TAG_RECORD_VERSION:
+ {
+ // short
+ FreeImage_SetTagType(tag, FIDT_SSHORT);
+ FreeImage_SetTagCount(tag, 1);
+ short *pvalue = (short*)&iptc_value[0];
+ *pvalue = (short)((profile[offset] << 8) | profile[offset + 1]);
+ FreeImage_SetTagValue(tag, pvalue);
+ break;
+ }
+
+ case TAG_RELEASE_DATE:
+ case TAG_DATE_CREATED:
+ // Date object
+ case TAG_RELEASE_TIME:
+ case TAG_TIME_CREATED:
+ // time
+ default:
+ {
+ // string
+ FreeImage_SetTagType(tag, FIDT_ASCII);
+ FreeImage_SetTagCount(tag, tagByteCount);
+ for(int i = 0; i < tagByteCount; i++) {
+ iptc_value[i] = profile[offset + i];
+ }
+ iptc_value[tagByteCount] = '\0';
+ FreeImage_SetTagValue(tag, (char*)&iptc_value[0]);
+ break;
+ }
+ }
+
+ if(tag_id == TAG_SUPPLEMENTAL_CATEGORIES) {
+ // concatenate the categories
+ if(SupplementalCategory.length() == 0) {
+ SupplementalCategory.append((char*)iptc_value);
+ } else {
+ SupplementalCategory.append(IPTC_DELIMITER);
+ SupplementalCategory.append((char*)iptc_value);
+ }
+ }
+ else if(tag_id == TAG_KEYWORDS) {
+ // concatenate the keywords
+ if(Keywords.length() == 0) {
+ Keywords.append((char*)iptc_value);
+ } else {
+ Keywords.append(IPTC_DELIMITER);
+ Keywords.append((char*)iptc_value);
+ }
+ }
+ else {
+ // get the tag key and description
+ const char *key = tag_lib.getTagFieldName(TagLib::IPTC, tag_id, defaultKey);
+ FreeImage_SetTagKey(tag, key);
+ const char *description = tag_lib.getTagDescription(TagLib::IPTC, tag_id);
+ FreeImage_SetTagDescription(tag, description);
+
+ // store the tag
+ if(key) {
+ FreeImage_SetMetadata(FIMD_IPTC, dib, key, tag);
+ }
+ }
+
+ free(iptc_value);
+
+ // next tag
+ offset += tagByteCount;
+
+ }
+
+ // store the 'keywords' tag
+ if(Keywords.length()) {
+ FreeImage_SetTagType(tag, FIDT_ASCII);
+ FreeImage_SetTagID(tag, TAG_KEYWORDS);
+ FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_KEYWORDS, defaultKey));
+ FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_KEYWORDS));
+ FreeImage_SetTagLength(tag, (DWORD)Keywords.length());
+ FreeImage_SetTagCount(tag, (DWORD)Keywords.length());
+ FreeImage_SetTagValue(tag, (char*)Keywords.c_str());
+ FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
+ }
+
+ // store the 'supplemental category' tag
+ if(SupplementalCategory.length()) {
+ FreeImage_SetTagType(tag, FIDT_ASCII);
+ FreeImage_SetTagID(tag, TAG_SUPPLEMENTAL_CATEGORIES);
+ FreeImage_SetTagKey(tag, tag_lib.getTagFieldName(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES, defaultKey));
+ FreeImage_SetTagDescription(tag, tag_lib.getTagDescription(TagLib::IPTC, TAG_SUPPLEMENTAL_CATEGORIES));
+ FreeImage_SetTagLength(tag, (DWORD)SupplementalCategory.length());
+ FreeImage_SetTagCount(tag, (DWORD)SupplementalCategory.length());
+ FreeImage_SetTagValue(tag, (char*)SupplementalCategory.c_str());
+ FreeImage_SetMetadata(FIMD_IPTC, dib, FreeImage_GetTagKey(tag), tag);
+ }
+
+ // delete the tag
+
+ FreeImage_DeleteTag(tag);
+
+ return TRUE;
+}
+
+// --------------------------------------------------------------------------
+
+static BYTE*
+append_iptc_tag(BYTE *profile, unsigned *profile_size, WORD id, DWORD length, const void *value) {
+ BYTE *buffer = NULL;
+
+ // calculate the new buffer size
+ size_t buffer_size = (5 + *profile_size + length) * sizeof(BYTE);
+ buffer = (BYTE*)malloc(buffer_size);
+ if(!buffer)
+ return NULL;
+
+ // add the header
+ buffer[0] = 0x1C;
+ buffer[1] = 0x02;
+ // add the tag type
+ buffer[2] = (BYTE)(id & 0x00FF);
+ // add the tag length
+ buffer[3] = (BYTE)(length >> 8);
+ buffer[4] = (BYTE)(length & 0xFF);
+ // add the tag value
+ memcpy(buffer + 5, (BYTE*)value, length);
+ // append the previous profile
+ if(NULL == profile) {
+ *profile_size = (5 + length);
+ }
+ else {
+ memcpy(buffer + 5 + length, profile, *profile_size);
+ *profile_size += (5 + length);
+ free(profile);
+ }
+
+ return buffer;
+}
+
+/**
+Encode IPTC metadata into a binary buffer.
+The buffer is allocated by the function and must be freed by the caller.
+*/
+BOOL
+write_iptc_profile(FIBITMAP *dib, BYTE **profile, unsigned *profile_size) {
+ FITAG *tag = NULL;
+ FIMETADATA *mdhandle = NULL;
+
+ BYTE *buffer = NULL;
+ unsigned buffer_size = 0;
+
+ // parse all IPTC tags and rebuild a IPTC profile
+ mdhandle = FreeImage_FindFirstMetadata(FIMD_IPTC, dib, &tag);
+
+ if(mdhandle) {
+ do {
+ WORD tag_id = FreeImage_GetTagID(tag);
+
+ // append the tag to the profile
+
+ switch(tag_id) {
+ case TAG_RECORD_VERSION:
+ // ignore (already handled)
+ break;
+
+ case TAG_SUPPLEMENTAL_CATEGORIES:
+ case TAG_KEYWORDS:
+ if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
+ std::string value = (const char*)FreeImage_GetTagValue(tag);
+
+ // split the tag value
+ std::vector<std::string> output;
+ std::string delimiter = IPTC_DELIMITER;
+
+ size_t offset = 0;
+ size_t delimiterIndex = 0;
+
+ delimiterIndex = value.find(delimiter, offset);
+ while (delimiterIndex != std::string::npos) {
+ output.push_back(value.substr(offset, delimiterIndex - offset));
+ offset += delimiterIndex - offset + delimiter.length();
+ delimiterIndex = value.find(delimiter, offset);
+ }
+ output.push_back(value.substr(offset));
+
+ // add as many tags as there are comma separated strings
+ for(int i = 0; i < (int)output.size(); i++) {
+ std::string& tag_value = output[i];
+ buffer = append_iptc_tag(buffer, &buffer_size, tag_id, (DWORD)tag_value.length(), tag_value.c_str());
+ }
+
+ }
+ break;
+
+ case TAG_URGENCY:
+ if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
+ DWORD length = 1; // keep the first octet only
+ buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
+ }
+ break;
+
+ default:
+ if(FreeImage_GetTagType(tag) == FIDT_ASCII) {
+ DWORD length = FreeImage_GetTagLength(tag);
+ buffer = append_iptc_tag(buffer, &buffer_size, tag_id, length, FreeImage_GetTagValue(tag));
+ }
+ break;
+ }
+
+ } while(FreeImage_FindNextMetadata(mdhandle, &tag));
+
+ FreeImage_FindCloseMetadata(mdhandle);
+
+ // add the DirectoryVersion tag
+ const short version = 0x0200;
+ buffer = append_iptc_tag(buffer, &buffer_size, TAG_RECORD_VERSION, sizeof(version), &version);
+
+ *profile = buffer;
+ *profile_size = buffer_size;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}