mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2025-02-07 21:37:14 +01:00
387 lines
14 KiB
C
387 lines
14 KiB
C
|
/*
|
||
|
* << Haru Free PDF Library >> -- hpdf_pdfa.c
|
||
|
*
|
||
|
* URL: http://libharu.org
|
||
|
*
|
||
|
* Copyright (c) 1999-2006 Takeshi Kanno <takeshi_kanno@est.hi-ho.ne.jp>
|
||
|
* Copyright (c) 2007-2009 Antony Dovgal <tony@daylessday.org>
|
||
|
*
|
||
|
* 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 <time.h>
|
||
|
#include "hpdf_utils.h"
|
||
|
#include "hpdf.h"
|
||
|
#include <string.h>
|
||
|
|
||
|
|
||
|
#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/'>"
|
||
|
#define DC_HEADER "<rdf:Description xmlns:dc='http://purl.org/dc/elements/1.1/' rdf:about=''>"
|
||
|
#define DC_TITLE_STARTTAG "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">"
|
||
|
#define DC_TITLE_ENDTAG "</rdf:li></rdf:Alt></dc:title>"
|
||
|
#define DC_CREATOR_STARTTAG "<dc:creator><rdf:Seq><rdf:li>"
|
||
|
#define DC_CREATOR_ENDTAG "</rdf:li></rdf:Seq></dc:creator>"
|
||
|
#define DC_DESCRIPTION_STARTTAG "<dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">"
|
||
|
#define DC_DESCRIPTION_ENDTAG "</rdf:li></rdf:Alt></dc:description>"
|
||
|
#define DC_FOOTER "</rdf:Description>"
|
||
|
#define XMP_HEADER "<rdf:Description xmlns:xmp='http://ns.adobe.com/xap/1.0/' rdf:about=''>"
|
||
|
#define XMP_CREATORTOOL_STARTTAG "<xmp:CreatorTool>"
|
||
|
#define XMP_CREATORTOOL_ENDTAG "</xmp:CreatorTool>"
|
||
|
#define XMP_CREATE_DATE_STARTTAG "<xmp:CreateDate>"
|
||
|
#define XMP_CREATE_DATE_ENDTAG "</xmp:CreateDate>"
|
||
|
#define XMP_MOD_DATE_STARTTAG "<xmp:ModifyDate>"
|
||
|
#define XMP_MOD_DATE_ENDTAG "</xmp:ModifyDate>"
|
||
|
#define XMP_FOOTER "</rdf:Description>"
|
||
|
#define PDF_HEADER "<rdf:Description xmlns:pdf='http://ns.adobe.com/pdf/1.3/' rdf:about=''>"
|
||
|
#define PDF_KEYWORDS_STARTTAG "<pdf:Keywords>"
|
||
|
#define PDF_KEYWORDS_ENDTAG "</pdf:Keywords>"
|
||
|
#define PDF_PRODUCER_STARTTAG "<pdf:Producer>"
|
||
|
#define PDF_PRODUCER_ENDTAG "</pdf:Producer>"
|
||
|
#define PDF_FOOTER "</rdf:Description>"
|
||
|
#define PDFAID_PDFA1A "<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/' pdfaid:part='1' pdfaid:conformance='A'/>"
|
||
|
#define PDFAID_PDFA1B "<rdf:Description rdf:about='' xmlns:pdfaid='http://www.aiim.org/pdfa/ns/id/' pdfaid:part='1' pdfaid:conformance='B'/>"
|
||
|
#define FOOTER "</rdf:RDF></x:xmpmeta><?xpacket end='w'?>"
|
||
|
|
||
|
|
||
|
/*
|
||
|
* 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);
|
||
|
}
|