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 */
60HPDF_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
137HPDF_STATUS
138HPDF_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*/
289HPDF_STATUS
290HPDF_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
344HPDF_STATUS
345HPDF_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