/* * << Haru Free PDF Library >> -- hpdf_pdfa.c * * URL: http://libharu.org * * Copyright (c) 1999-2006 Takeshi Kanno * Copyright (c) 2007-2009 Antony Dovgal * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. * It is provided "as is" without express or implied warranty. * */ /* This is used to avoid warnings on 'ctime' when compiling in MSVC 9 */ #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #include #include "hpdf_utils.h" #include "hpdf.h" #include #define HEADER "" #define DC_HEADER "" #define DC_TITLE_STARTTAG "" #define DC_TITLE_ENDTAG "" #define DC_CREATOR_STARTTAG "" #define DC_CREATOR_ENDTAG "" #define DC_DESCRIPTION_STARTTAG "" #define DC_DESCRIPTION_ENDTAG "" #define DC_FOOTER "" #define XMP_HEADER "" #define XMP_CREATORTOOL_STARTTAG "" #define XMP_CREATORTOOL_ENDTAG "" #define XMP_CREATE_DATE_STARTTAG "" #define XMP_CREATE_DATE_ENDTAG "" #define XMP_MOD_DATE_STARTTAG "" #define XMP_MOD_DATE_ENDTAG "" #define XMP_FOOTER "" #define PDF_HEADER "" #define PDF_KEYWORDS_STARTTAG "" #define PDF_KEYWORDS_ENDTAG "" #define PDF_PRODUCER_STARTTAG "" #define PDF_PRODUCER_ENDTAG "" #define PDF_FOOTER "" #define PDFAID_PDFA1A "" #define PDFAID_PDFA1B "" #define FOOTER "" /* * Convert date in PDF specific format: D:YYYYMMDDHHmmSS * to XMP value in format YYYY-MM-DDTHH:mm:SS+offH:offMin */ HPDF_STATUS ConvertDateToXMDate(HPDF_Stream stream, const char *pDate) { HPDF_STATUS ret; if(pDate==NULL) return HPDF_INVALID_PARAMETER; if(strlen(pDate)<16) return HPDF_INVALID_PARAMETER; if(pDate[0]!='D'|| pDate[1]!=':') return HPDF_INVALID_PARAMETER; pDate+=2; /* Copy YYYY */ ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 4); if (ret != HPDF_OK) return ret; pDate+=4; /* Write -MM */ ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)"-", 1); if (ret != HPDF_OK) return ret; ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2); if (ret != HPDF_OK) return ret; pDate+=2; /* Write -DD */ ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)"-", 1); if (ret != HPDF_OK) return ret; ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2); if (ret != HPDF_OK) return ret; pDate+=2; /* Write THH */ ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)"T", 1); if (ret != HPDF_OK) return ret; ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2); if (ret != HPDF_OK) return ret; pDate+=2; /* Write :mm */ ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)":", 1); if (ret != HPDF_OK) return ret; ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2); if (ret != HPDF_OK) return ret; pDate+=2; /* Write :SS */ ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)":", 1); if (ret != HPDF_OK) return ret; ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2); if (ret != HPDF_OK) return ret; pDate+=2; /* Write +... */ if(pDate[0]==0) { ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)"Z", 1); return ret; } if(pDate[0]=='+'||pDate[0]=='-') { ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 3); if (ret != HPDF_OK) return ret; pDate+=4; ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)":", 1); if (ret != HPDF_OK) return ret; ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2); if (ret != HPDF_OK) return ret; return ret; } return HPDF_SetError (stream->error, HPDF_INVALID_PARAMETER, 0); } /* Write XMP Metadata for PDF/A */ HPDF_STATUS HPDF_PDFA_SetPDFAConformance (HPDF_Doc pdf,HPDF_PDFAType pdfatype) { HPDF_OutputIntent xmp; HPDF_STATUS ret; const char *dc_title = NULL; const char *dc_creator = NULL; const char *dc_description = NULL; const char *xmp_CreatorTool = NULL; const char *xmp_CreateDate = NULL; const char *xmp_ModifyDate = NULL; const char *pdf_Keywords = NULL; const char *pdf_Producer = NULL; const char *info = NULL; if (!HPDF_HasDoc(pdf)) { return HPDF_INVALID_DOCUMENT; } dc_title = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_TITLE); dc_creator = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_AUTHOR); dc_description = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_SUBJECT); xmp_CreateDate = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_CREATION_DATE); xmp_ModifyDate = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_MOD_DATE); xmp_CreatorTool = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_CREATOR); pdf_Keywords = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_KEYWORDS); pdf_Producer = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_PRODUCER); if((dc_title != NULL) || (dc_creator != NULL) || (dc_description != NULL) || (xmp_CreateDate != NULL) || (xmp_ModifyDate != NULL) || (xmp_CreatorTool != NULL) || (pdf_Keywords != NULL)) { xmp = HPDF_DictStream_New(pdf->mmgr,pdf->xref); if (!xmp) { return HPDF_INVALID_STREAM; } /* Update the PDF number version */ pdf->pdf_version = HPDF_VER_14; HPDF_Dict_AddName(xmp,"Type","Metadata"); HPDF_Dict_AddName(xmp,"SubType","XML"); ret = HPDF_OK; ret += HPDF_Stream_WriteStr(xmp->stream, HEADER); /* Add the dc block */ if((dc_title != NULL) || (dc_creator != NULL) || (dc_description != NULL)) { ret += HPDF_Stream_WriteStr(xmp->stream, DC_HEADER); if(dc_title != NULL) { ret += HPDF_Stream_WriteStr(xmp->stream, DC_TITLE_STARTTAG); ret += HPDF_Stream_WriteStr(xmp->stream, dc_title); ret += HPDF_Stream_WriteStr(xmp->stream, DC_TITLE_ENDTAG); } if(dc_creator != NULL) { ret += HPDF_Stream_WriteStr(xmp->stream, DC_CREATOR_STARTTAG); ret += HPDF_Stream_WriteStr(xmp->stream, dc_creator); ret += HPDF_Stream_WriteStr(xmp->stream, DC_CREATOR_ENDTAG); } if(dc_description != NULL) { ret += HPDF_Stream_WriteStr(xmp->stream, DC_DESCRIPTION_STARTTAG); ret += HPDF_Stream_WriteStr(xmp->stream, dc_description); ret += HPDF_Stream_WriteStr(xmp->stream, DC_DESCRIPTION_ENDTAG); } ret += HPDF_Stream_WriteStr(xmp->stream, DC_FOOTER); } /* Add the xmp block */ if((xmp_CreateDate != NULL) || (xmp_ModifyDate != NULL) || (xmp_CreatorTool != NULL)) { ret += HPDF_Stream_WriteStr(xmp->stream, XMP_HEADER); /* Add CreateDate, ModifyDate, and CreatorTool */ if(xmp_CreatorTool != NULL) { ret += HPDF_Stream_WriteStr(xmp->stream, XMP_CREATORTOOL_STARTTAG); ret += HPDF_Stream_WriteStr(xmp->stream, xmp_CreatorTool); ret += HPDF_Stream_WriteStr(xmp->stream, XMP_CREATORTOOL_ENDTAG); } if(xmp_CreateDate != NULL) { ret += HPDF_Stream_WriteStr(xmp->stream, XMP_CREATE_DATE_STARTTAG); /* Convert date to XMP compatible format */ ret += ConvertDateToXMDate(xmp->stream, xmp_CreateDate); ret += HPDF_Stream_WriteStr(xmp->stream, XMP_CREATE_DATE_ENDTAG); } if(xmp_ModifyDate != NULL) { ret += HPDF_Stream_WriteStr(xmp->stream, XMP_MOD_DATE_STARTTAG); ret += ConvertDateToXMDate(xmp->stream, xmp_ModifyDate); ret += HPDF_Stream_WriteStr(xmp->stream, XMP_MOD_DATE_ENDTAG); } ret += HPDF_Stream_WriteStr(xmp->stream, XMP_FOOTER); } /* Add the pdf block */ if((pdf_Keywords != NULL) || (pdf_Producer != NULL)) { ret += HPDF_Stream_WriteStr(xmp->stream, PDF_HEADER); if(pdf_Keywords != NULL) { ret += HPDF_Stream_WriteStr(xmp->stream, PDF_KEYWORDS_STARTTAG); ret += HPDF_Stream_WriteStr(xmp->stream, pdf_Keywords); ret += HPDF_Stream_WriteStr(xmp->stream, PDF_KEYWORDS_ENDTAG); } if(pdf_Producer != NULL) { ret += HPDF_Stream_WriteStr(xmp->stream, PDF_PRODUCER_STARTTAG); ret += HPDF_Stream_WriteStr(xmp->stream, pdf_Producer); ret += HPDF_Stream_WriteStr(xmp->stream, PDF_PRODUCER_ENDTAG); } ret += HPDF_Stream_WriteStr(xmp->stream, PDF_FOOTER); } /* Add the pdfaid block */ switch(pdfatype) { case HPDF_PDFA_1A: ret += HPDF_Stream_WriteStr(xmp->stream, PDFAID_PDFA1A); break; case HPDF_PDFA_1B: ret += HPDF_Stream_WriteStr(xmp->stream, PDFAID_PDFA1B); break; } ret += HPDF_Stream_WriteStr(xmp->stream, FOOTER); if (ret != HPDF_OK) { return HPDF_INVALID_STREAM; } if ((ret = HPDF_Dict_Add(pdf->catalog, "Metadata", xmp)) != HPDF_OK) { return ret; } return HPDF_PDFA_GenerateID(pdf); } return HPDF_OK; } /* Generate an ID for the trailer dict, PDF/A needs this. TODO: Better algorithm for generate unique ID. */ HPDF_STATUS HPDF_PDFA_GenerateID(HPDF_Doc pdf) { HPDF_Array id; HPDF_BYTE *currentTime; HPDF_BYTE idkey[HPDF_MD5_KEY_LEN]; HPDF_MD5_CTX md5_ctx; time_t ltime; ltime = time(NULL); currentTime = (HPDF_BYTE *)ctime(<ime); id = HPDF_Dict_GetItem(pdf->trailer, "ID", HPDF_OCLASS_ARRAY); if (!id) { id = HPDF_Array_New(pdf->mmgr); if (!id || HPDF_Dict_Add(pdf->trailer, "ID", id) != HPDF_OK) return pdf->error.error_no; HPDF_MD5Init(&md5_ctx); HPDF_MD5Update(&md5_ctx, (HPDF_BYTE *) "libHaru", sizeof("libHaru") - 1); HPDF_MD5Update(&md5_ctx, currentTime, HPDF_StrLen((const char *)currentTime, -1)); HPDF_MD5Final(idkey, &md5_ctx); if (HPDF_Array_Add (id, HPDF_Binary_New (pdf->mmgr, idkey, HPDF_MD5_KEY_LEN)) != HPDF_OK) return pdf->error.error_no; if (HPDF_Array_Add (id, HPDF_Binary_New (pdf->mmgr,idkey,HPDF_MD5_KEY_LEN)) != HPDF_OK) return pdf->error.error_no; return HPDF_OK; } return HPDF_OK; } /* Function to add one outputintents to the PDF * iccname - name of default ICC profile * iccdict - dictionary containing number of components * and stream with ICC profile * * How to use: * 1. Create dictionary with ICC profile * HPDF_Dict icc = HPDF_DictStream_New (pDoc->mmgr, pDoc->xref); * if(icc==NULL) return false; * HPDF_Dict_AddNumber (icc, "N", 3); * HPDF_STATUS ret = HPDF_Stream_Write (icc->stream, (const HPDF_BYTE *)pICCData, dwICCSize); * if(ret!=HPDF_OK) { * HPDF_Dict_Free(icc); * return false; * } * * 2. Call this function */ HPDF_STATUS HPDF_PDFA_AppendOutputIntents(HPDF_Doc pdf, const char *iccname, HPDF_Dict iccdict) { HPDF_Array intents; HPDF_Dict intent; HPDF_STATUS ret; if (!HPDF_HasDoc (pdf)) return HPDF_INVALID_DOCUMENT; /* prepare intent */ intent = HPDF_Dict_New (pdf->mmgr); ret = HPDF_Xref_Add (pdf->xref, intent); if ( ret != HPDF_OK) { HPDF_Dict_Free(intent); return ret; } ret += HPDF_Dict_AddName (intent, "Type", "OutputIntent"); ret += HPDF_Dict_AddName (intent, "S", "GTS_PDFA1"); ret += HPDF_Dict_Add (intent, "OutputConditionIdentifier", HPDF_String_New (pdf->mmgr, iccname, NULL)); ret += HPDF_Dict_Add (intent, "OutputCondition", HPDF_String_New (pdf->mmgr, iccname,NULL)); ret += HPDF_Dict_Add (intent, "Info", HPDF_String_New (pdf->mmgr, iccname, NULL)); ret += HPDF_Dict_Add (intent, "DestOutputProfile ", iccdict); if ( ret != HPDF_OK) { HPDF_Dict_Free(intent); return ret; } /* Copied from HPDF_AddIntent - not public function */ intents = HPDF_Dict_GetItem (pdf->catalog, "OutputIntents", HPDF_OCLASS_ARRAY); if (intents == NULL) { intents = HPDF_Array_New (pdf->mmgr); if (intents) { HPDF_STATUS ret = HPDF_Dict_Add (pdf->catalog, "OutputIntents", intents); if (ret != HPDF_OK) { HPDF_CheckError (&pdf->error); return HPDF_Error_GetDetailCode (&pdf->error); } } } HPDF_Array_Add(intents,intent); return HPDF_Error_GetDetailCode (&pdf->error); }