1 /*
2  * << Haru Free PDF Library >> -- hpdf_pdfa.c
3  *
4  * URL: http://libharu.org
5  *
6  * Copyright (c) 1999-2006 Takeshi Kanno <takeshi_kanno@est.hi-ho.ne.jp>
7  * Copyright (c) 2007-2009 Antony Dovgal <tony@daylessday.org>
8  *
9  * Permission to use, copy, modify, distribute and sell this software
10  * and its documentation for any purpose is hereby granted without fee,
11  * provided that the above copyright notice appear in all copies and
12  * that both that copyright notice and this permission notice appear
13  * in supporting documentation.
14  * It is provided "as is" without express or implied warranty.
15  *
16  */
17 /* This is used to avoid warnings on 'ctime' when compiling in MSVC 9 */
18 #ifndef _CRT_SECURE_NO_WARNINGS
19 #define _CRT_SECURE_NO_WARNINGS
20 #endif
21 
22 #include <time.h>
23 #include "hpdf_utils.h"
24 #include "hpdf.h"
25 #include <string.h>
26 
27 
28 #define HEADER                   "<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?><x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='XMP toolkit 2.9.1-13, framework 1.6'><rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:iX='http://ns.adobe.com/iX/1.0/'>"
29 #define DC_HEADER                "<rdf:Description xmlns:dc='http://purl.org/dc/elements/1.1/' rdf:about=''>"
30 #define DC_TITLE_STARTTAG        "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">"
31 #define DC_TITLE_ENDTAG          "</rdf:li></rdf:Alt></dc:title>"
32 #define DC_CREATOR_STARTTAG      "<dc:creator><rdf:Seq><rdf:li>"
33 #define DC_CREATOR_ENDTAG        "</rdf:li></rdf:Seq></dc:creator>"
34 #define DC_DESCRIPTION_STARTTAG  "<dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">"
35 #define DC_DESCRIPTION_ENDTAG    "</rdf:li></rdf:Alt></dc:description>"
36 #define DC_FOOTER                "</rdf:Description>"
37 #define XMP_HEADER               "<rdf:Description xmlns:xmp='http://ns.adobe.com/xap/1.0/' rdf:about=''>"
38 #define XMP_CREATORTOOL_STARTTAG "<xmp:CreatorTool>"
39 #define XMP_CREATORTOOL_ENDTAG   "</xmp:CreatorTool>"
40 #define XMP_CREATE_DATE_STARTTAG "<xmp:CreateDate>"
41 #define XMP_CREATE_DATE_ENDTAG   "</xmp:CreateDate>"
42 #define XMP_MOD_DATE_STARTTAG    "<xmp:ModifyDate>"
43 #define XMP_MOD_DATE_ENDTAG      "</xmp:ModifyDate>"
44 #define XMP_FOOTER               "</rdf:Description>"
45 #define PDF_HEADER               "<rdf:Description xmlns:pdf='http://ns.adobe.com/pdf/1.3/' rdf:about=''>"
46 #define PDF_KEYWORDS_STARTTAG    "<pdf:Keywords>"
47 #define PDF_KEYWORDS_ENDTAG      "</pdf:Keywords>"
48 #define PDF_PRODUCER_STARTTAG    "<pdf:Producer>"
49 #define PDF_PRODUCER_ENDTAG      "</pdf:Producer>"
50 #define PDF_FOOTER               "</rdf:Description>"
51 #define PDFAID_PDFA1A            "<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/' pdfaid:part='1' pdfaid:conformance='A'/>"
52 #define PDFAID_PDFA1B            "<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/' pdfaid:part='1' pdfaid:conformance='B'/>"
53 #define FOOTER                   "</rdf:RDF></x:xmpmeta><?xpacket end='w'?>"
54 
55 
56 /*
57  * Convert date in PDF specific format: D:YYYYMMDDHHmmSS
58  * to XMP value in format YYYY-MM-DDTHH:mm:SS+offH:offMin
59  */
ConvertDateToXMDate(HPDF_Stream stream,const char * pDate)60 HPDF_STATUS ConvertDateToXMDate(HPDF_Stream stream, const char *pDate)
61 {
62     HPDF_STATUS ret;
63 
64     if(pDate==NULL) return HPDF_INVALID_PARAMETER;
65     if(strlen(pDate)<16) return HPDF_INVALID_PARAMETER;
66     if(pDate[0]!='D'||
67         pDate[1]!=':') return HPDF_INVALID_PARAMETER;
68     pDate+=2;
69     /* Copy YYYY */
70     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 4);
71     if (ret != HPDF_OK)
72         return ret;
73     pDate+=4;
74     /* Write -MM */
75     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)"-", 1);
76     if (ret != HPDF_OK)
77         return ret;
78     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2);
79     if (ret != HPDF_OK)
80         return ret;
81     pDate+=2;
82     /* Write -DD */
83     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)"-", 1);
84     if (ret != HPDF_OK)
85         return ret;
86     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2);
87     if (ret != HPDF_OK)
88         return ret;
89     pDate+=2;
90     /* Write THH */
91     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)"T", 1);
92     if (ret != HPDF_OK)
93         return ret;
94     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2);
95     if (ret != HPDF_OK)
96         return ret;
97     pDate+=2;
98     /* Write :mm */
99     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)":", 1);
100     if (ret != HPDF_OK)
101         return ret;
102     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2);
103     if (ret != HPDF_OK)
104         return ret;
105     pDate+=2;
106     /* Write :SS */
107     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)":", 1);
108     if (ret != HPDF_OK)
109         return ret;
110     ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2);
111     if (ret != HPDF_OK)
112         return ret;
113     pDate+=2;
114     /* Write +... */
115     if(pDate[0]==0) {
116         ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)"Z", 1);
117         return ret;
118     }
119     if(pDate[0]=='+'||pDate[0]=='-') {
120         ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 3);
121         if (ret != HPDF_OK)
122             return ret;
123         pDate+=4;
124         ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)":", 1);
125         if (ret != HPDF_OK)
126             return ret;
127         ret = HPDF_Stream_Write(stream, (const HPDF_BYTE*)pDate, 2);
128         if (ret != HPDF_OK)
129             return ret;
130         return ret;
131     }
132     return HPDF_SetError (stream->error, HPDF_INVALID_PARAMETER, 0);
133 }
134 
135 /* Write XMP Metadata for PDF/A */
136 
137 HPDF_STATUS
HPDF_PDFA_SetPDFAConformance(HPDF_Doc pdf,HPDF_PDFAType pdfatype)138 HPDF_PDFA_SetPDFAConformance (HPDF_Doc pdf,HPDF_PDFAType pdfatype)
139 {
140     HPDF_OutputIntent xmp;
141     HPDF_STATUS ret;
142 
143     const char *dc_title       = NULL;
144     const char *dc_creator     = NULL;
145     const char *dc_description = NULL;
146 
147     const char *xmp_CreatorTool = NULL;
148     const char *xmp_CreateDate  = NULL;
149     const char *xmp_ModifyDate  = NULL;
150 
151     const char *pdf_Keywords    = NULL;
152     const char *pdf_Producer    = NULL;
153 
154     const char *info = NULL;
155 
156     if (!HPDF_HasDoc(pdf)) {
157       return HPDF_INVALID_DOCUMENT;
158     }
159 
160     dc_title       = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_TITLE);
161     dc_creator     = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_AUTHOR);
162     dc_description = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_SUBJECT);
163 
164     xmp_CreateDate  = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_CREATION_DATE);
165     xmp_ModifyDate  = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_MOD_DATE);
166     xmp_CreatorTool = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_CREATOR);
167 
168     pdf_Keywords = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_KEYWORDS);
169     pdf_Producer = (const char *)HPDF_GetInfoAttr(pdf, HPDF_INFO_PRODUCER);
170 
171     if((dc_title != NULL) || (dc_creator != NULL) || (dc_description != NULL)
172        || (xmp_CreateDate != NULL) || (xmp_ModifyDate != NULL) || (xmp_CreatorTool != NULL)
173        || (pdf_Keywords != NULL)) {
174 
175         xmp = HPDF_DictStream_New(pdf->mmgr,pdf->xref);
176         if (!xmp) {
177           return HPDF_INVALID_STREAM;
178         }
179 
180         /* Update the PDF number version */
181         pdf->pdf_version = HPDF_VER_14;
182 
183         HPDF_Dict_AddName(xmp,"Type","Metadata");
184         HPDF_Dict_AddName(xmp,"SubType","XML");
185 
186         ret = HPDF_OK;
187         ret += HPDF_Stream_WriteStr(xmp->stream, HEADER);
188 
189         /* Add the dc block */
190         if((dc_title != NULL) || (dc_creator != NULL) || (dc_description != NULL)) {
191             ret += HPDF_Stream_WriteStr(xmp->stream, DC_HEADER);
192 
193             if(dc_title != NULL) {
194                 ret += HPDF_Stream_WriteStr(xmp->stream, DC_TITLE_STARTTAG);
195                 ret += HPDF_Stream_WriteStr(xmp->stream, dc_title);
196                 ret += HPDF_Stream_WriteStr(xmp->stream, DC_TITLE_ENDTAG);
197             }
198 
199             if(dc_creator != NULL) {
200                 ret += HPDF_Stream_WriteStr(xmp->stream, DC_CREATOR_STARTTAG);
201                 ret += HPDF_Stream_WriteStr(xmp->stream, dc_creator);
202                 ret += HPDF_Stream_WriteStr(xmp->stream, DC_CREATOR_ENDTAG);
203             }
204 
205             if(dc_description != NULL) {
206                 ret += HPDF_Stream_WriteStr(xmp->stream, DC_DESCRIPTION_STARTTAG);
207                 ret += HPDF_Stream_WriteStr(xmp->stream, dc_description);
208                 ret += HPDF_Stream_WriteStr(xmp->stream, DC_DESCRIPTION_ENDTAG);
209             }
210 
211             ret += HPDF_Stream_WriteStr(xmp->stream, DC_FOOTER);
212         }
213 
214         /* Add the xmp block */
215         if((xmp_CreateDate != NULL) || (xmp_ModifyDate != NULL) || (xmp_CreatorTool != NULL)) {
216             ret += HPDF_Stream_WriteStr(xmp->stream, XMP_HEADER);
217 
218             /* Add CreateDate, ModifyDate, and CreatorTool */
219             if(xmp_CreatorTool != NULL) {
220                 ret += HPDF_Stream_WriteStr(xmp->stream, XMP_CREATORTOOL_STARTTAG);
221                 ret += HPDF_Stream_WriteStr(xmp->stream, xmp_CreatorTool);
222                 ret += HPDF_Stream_WriteStr(xmp->stream, XMP_CREATORTOOL_ENDTAG);
223             }
224 
225             if(xmp_CreateDate != NULL) {
226                 ret += HPDF_Stream_WriteStr(xmp->stream, XMP_CREATE_DATE_STARTTAG);
227                 /* Convert date to XMP compatible format */
228                 ret += ConvertDateToXMDate(xmp->stream, xmp_CreateDate);
229                 ret += HPDF_Stream_WriteStr(xmp->stream, XMP_CREATE_DATE_ENDTAG);
230             }
231 
232             if(xmp_ModifyDate != NULL) {
233                 ret += HPDF_Stream_WriteStr(xmp->stream, XMP_MOD_DATE_STARTTAG);
234                 ret += ConvertDateToXMDate(xmp->stream, xmp_ModifyDate);
235                 ret += HPDF_Stream_WriteStr(xmp->stream, XMP_MOD_DATE_ENDTAG);
236             }
237 
238             ret += HPDF_Stream_WriteStr(xmp->stream, XMP_FOOTER);
239         }
240 
241         /* Add the pdf block */
242         if((pdf_Keywords != NULL) || (pdf_Producer != NULL)) {
243             ret += HPDF_Stream_WriteStr(xmp->stream, PDF_HEADER);
244 
245             if(pdf_Keywords != NULL) {
246                 ret += HPDF_Stream_WriteStr(xmp->stream, PDF_KEYWORDS_STARTTAG);
247                 ret += HPDF_Stream_WriteStr(xmp->stream, pdf_Keywords);
248                 ret += HPDF_Stream_WriteStr(xmp->stream, PDF_KEYWORDS_ENDTAG);
249             }
250 
251             if(pdf_Producer != NULL) {
252                 ret += HPDF_Stream_WriteStr(xmp->stream, PDF_PRODUCER_STARTTAG);
253                 ret += HPDF_Stream_WriteStr(xmp->stream, pdf_Producer);
254                 ret += HPDF_Stream_WriteStr(xmp->stream, PDF_PRODUCER_ENDTAG);
255             }
256 
257             ret += HPDF_Stream_WriteStr(xmp->stream, PDF_FOOTER);
258         }
259 
260         /* Add the pdfaid block */
261         switch(pdfatype) {
262           case HPDF_PDFA_1A:
263             ret += HPDF_Stream_WriteStr(xmp->stream, PDFAID_PDFA1A);
264             break;
265           case HPDF_PDFA_1B:
266             ret += HPDF_Stream_WriteStr(xmp->stream, PDFAID_PDFA1B);
267             break;
268         }
269 
270         ret += HPDF_Stream_WriteStr(xmp->stream, FOOTER);
271 
272         if (ret != HPDF_OK) {
273           return HPDF_INVALID_STREAM;
274         }
275 
276         if ((ret = HPDF_Dict_Add(pdf->catalog, "Metadata", xmp)) != HPDF_OK) {
277           return ret;
278         }
279 
280         return HPDF_PDFA_GenerateID(pdf);
281     }
282 
283     return HPDF_OK;
284 }
285 
286 /* Generate an ID for the trailer dict, PDF/A needs this.
287    TODO: Better algorithm for generate unique ID.
288 */
289 HPDF_STATUS
HPDF_PDFA_GenerateID(HPDF_Doc pdf)290 HPDF_PDFA_GenerateID(HPDF_Doc pdf)
291 {
292     HPDF_Array id;
293     HPDF_BYTE *currentTime;
294     HPDF_BYTE idkey[HPDF_MD5_KEY_LEN];
295     HPDF_MD5_CTX md5_ctx;
296     time_t ltime;
297 
298     ltime = time(NULL);
299     currentTime = (HPDF_BYTE *)ctime(&ltime);
300 
301     id = HPDF_Dict_GetItem(pdf->trailer, "ID", HPDF_OCLASS_ARRAY);
302     if (!id) {
303        id = HPDF_Array_New(pdf->mmgr);
304 
305        if (!id || HPDF_Dict_Add(pdf->trailer, "ID", id) != HPDF_OK)
306          return pdf->error.error_no;
307 
308        HPDF_MD5Init(&md5_ctx);
309        HPDF_MD5Update(&md5_ctx, (HPDF_BYTE *) "libHaru", sizeof("libHaru") - 1);
310        HPDF_MD5Update(&md5_ctx, currentTime, HPDF_StrLen((const char *)currentTime, -1));
311        HPDF_MD5Final(idkey, &md5_ctx);
312 
313        if (HPDF_Array_Add (id, HPDF_Binary_New (pdf->mmgr, idkey, HPDF_MD5_KEY_LEN)) != HPDF_OK)
314          return pdf->error.error_no;
315 
316        if (HPDF_Array_Add (id, HPDF_Binary_New (pdf->mmgr,idkey,HPDF_MD5_KEY_LEN)) != HPDF_OK)
317          return pdf->error.error_no;
318 
319        return HPDF_OK;
320     }
321 
322     return HPDF_OK;
323 }
324 
325 /* Function to add one outputintents to the PDF
326  * iccname - name of default ICC profile
327  * iccdict - dictionary containing number of components
328  *           and stream with ICC profile
329  *
330  * How to use:
331  * 1. Create dictionary with ICC profile
332  *    HPDF_Dict icc = HPDF_DictStream_New (pDoc->mmgr, pDoc->xref);
333  *    if(icc==NULL) return false;
334  *    HPDF_Dict_AddNumber (icc, "N", 3);
335  *    HPDF_STATUS ret = HPDF_Stream_Write (icc->stream, (const HPDF_BYTE *)pICCData, dwICCSize);
336  *    if(ret!=HPDF_OK) {
337  *      HPDF_Dict_Free(icc);
338  *      return false;
339  *    }
340  *
341  * 2. Call this function
342  */
343 
344 HPDF_STATUS
HPDF_PDFA_AppendOutputIntents(HPDF_Doc pdf,const char * iccname,HPDF_Dict iccdict)345 HPDF_PDFA_AppendOutputIntents(HPDF_Doc pdf, const char *iccname, HPDF_Dict iccdict)
346 {
347     HPDF_Array intents;
348     HPDF_Dict intent;
349     HPDF_STATUS ret;
350     if (!HPDF_HasDoc (pdf))
351         return HPDF_INVALID_DOCUMENT;
352 
353     /* prepare intent */
354     intent = HPDF_Dict_New (pdf->mmgr);
355     ret = HPDF_Xref_Add (pdf->xref, intent);
356     if ( ret != HPDF_OK) {
357         HPDF_Dict_Free(intent);
358         return ret;
359     }
360     ret += HPDF_Dict_AddName (intent, "Type", "OutputIntent");
361     ret += HPDF_Dict_AddName (intent, "S", "GTS_PDFA1");
362     ret += HPDF_Dict_Add (intent, "OutputConditionIdentifier", HPDF_String_New (pdf->mmgr, iccname, NULL));
363     ret += HPDF_Dict_Add (intent, "OutputCondition", HPDF_String_New (pdf->mmgr, iccname,NULL));
364     ret += HPDF_Dict_Add (intent, "Info", HPDF_String_New (pdf->mmgr, iccname, NULL));
365     ret += HPDF_Dict_Add (intent, "DestOutputProfile ", iccdict);
366     if ( ret != HPDF_OK) {
367         HPDF_Dict_Free(intent);
368         return ret;
369     }
370 
371     /* Copied from HPDF_AddIntent - not public function */
372     intents = HPDF_Dict_GetItem (pdf->catalog, "OutputIntents", HPDF_OCLASS_ARRAY);
373     if (intents == NULL) {
374         intents = HPDF_Array_New (pdf->mmgr);
375         if (intents) {
376             HPDF_STATUS ret = HPDF_Dict_Add (pdf->catalog, "OutputIntents", intents);
377             if (ret != HPDF_OK) {
378                 HPDF_CheckError (&pdf->error);
379                 return HPDF_Error_GetDetailCode (&pdf->error);
380             }
381         }
382     }
383 
384     HPDF_Array_Add(intents,intent);
385     return HPDF_Error_GetDetailCode (&pdf->error);
386 }
387