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 "<?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 "<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 "</rdf:Description>" |
37 | #define "<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 "</rdf:Description>" |
45 | #define "<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 "</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 "</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 | */ |
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 |
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 |
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(<ime); |
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 |
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 | |