/* % Copyright (C) 2003-2018 GraphicsMagick Group % Copyright (C) 2002 ImageMagick Studio % Copyright 1991-1999 E. I. du Pont de Nemours and Company % % This program is covered by multiple licenses, which are described in % Copyright.txt. You should have received a copy of Copyright.txt with this % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html. % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % PPPP DDDD FFFFF % % P P D D F % % PPPP D D FFF % % P D D F % % P DDDD F % % % % % % Read/Write Portable Document Format. % % % % % % Software Design % % John Cristy % % July 1992 % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % */ /* Include declarations. */ #include "magick/studio.h" #include "magick/analyze.h" #include "magick/attribute.h" #include "magick/blob.h" #include "magick/compress.h" #include "magick/constitute.h" #include "magick/delegate.h" #include "magick/enum_strings.h" #include "magick/log.h" #include "magick/magick.h" #include "magick/monitor.h" #include "magick/pixel_cache.h" #include "magick/resize.h" #include "magick/shear.h" #include "magick/tempfile.h" #include "magick/utility.h" #include "magick/version.h" /* Forward declarations. */ static unsigned int WritePDFImage(const ImageInfo *,Image *); #if defined(HasZLIB) static unsigned int ZLIBEncodeImage(Image *,const size_t,const unsigned long,unsigned char *); #endif /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % I s P D F % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method IsPDF returns True if the image format type, identified by the % magick string, is PDF. % % The format of the IsPDF method is: % % unsigned int IsPDF(const unsigned char *magick,const size_t offset) % % A description of each parameter follows: % % o status: Method IsPDF returns True if the image format type is PDF. % % o magick: This string is generally the first few bytes of an image file % or blob. % % o offset: Specifies the offset of the magick string. % % */ static unsigned int IsPDF(const unsigned char *magick,const size_t offset) { if (offset < 5) return(False); if (LocaleNCompare((char *) magick,"%PDF-",5) == 0) return(True); return(False); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e a d P D F I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method ReadPDFImage reads a Portable Document Format image file and % returns it. It allocates the memory necessary for the new Image structure % and returns a pointer to the new image. % % The format of the ReadPDFImage method is: % % Image *ReadPDFImage(const ImageInfo *image_info,ExceptionInfo *exception) % % A description of each parameter follows: % % o image: Method ReadPDFImage returns a pointer to the image after % reading. A null image is returned if there is a memory shortage or % if the image cannot be read. % % o image_info: Specifies a pointer to a ImageInfo structure. % % o exception: return any errors or warnings in this structure. % % */ static Image *ReadPDFImage(const ImageInfo *image_info,ExceptionInfo *exception) { #define MediaBox "/MediaBox" #define RenderPostscriptText "[%s] Rendering postscript..." char density[MaxTextExtent], command[MaxTextExtent], filename[MaxTextExtent], postscript_filename[MaxTextExtent]; const char *value; const DelegateInfo *delegate_info; double dx_resolution, dy_resolution; FILE *file; Image *image, *next_image; ImageInfo *clone_info; int count, status; unsigned int antialias=4; MagickBool use_crop_box = MagickFalse; MagickBool pdf_stop_on_error = MagickFalse; assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickSignature); if ((value=AccessDefinition(image_info,"pdf","use-cropbox"))) { if (strcmp(value,"true") == 0) use_crop_box = True; } if ((value=AccessDefinition(image_info,"pdf","stop-on-error"))) { if (strcmp(value,"true") == 0) pdf_stop_on_error = True; } /* Select Postscript delegate driver */ delegate_info=GetPostscriptDelegateInfo(image_info,&antialias,exception); if (delegate_info == (const DelegateInfo *) NULL) return((Image *) NULL); /* Open image file. */ image=AllocateImage(image_info); status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); if (status == False) ThrowReaderException(FileOpenError,UnableToOpenFile,image); /* Open temporary output file. */ file=AcquireTemporaryFileStream(postscript_filename,BinaryFileIOMode); if (file == (FILE *) NULL) ThrowReaderTemporaryFileException(postscript_filename); /* Set the page density. */ dx_resolution=72.0; dy_resolution=72.0; if ((image->x_resolution == 0.0) || (image->y_resolution == 0.0)) { (void) strlcpy(density,PSDensityGeometry,sizeof(density)); count=GetMagickDimension(density,&image->x_resolution,&image->y_resolution,NULL,NULL); if (count != 2) image->y_resolution=image->x_resolution; } FormatString(density,"%gx%g",image->x_resolution,image->y_resolution); { /* Determine page geometry from the PDF media box. Note that we can use Ghostscript to obtain the bounding box info like gs -q -dBATCH -dNOPAUSE -sDEVICE=bbox ENV.003.01.pdf %%BoundingBox: 70 61 2089 2954 %%HiResBoundingBox: 70.737537 61.199998 2088.587889 2953.601629 */ char geometry[MaxTextExtent]; RectangleInfo box, page; SegmentInfo bounds; unsigned long height, width; int rotate; register char *p, *q; register long c; rotate=0; (void) memset(&page,0,sizeof(RectangleInfo)); (void) memset(&box,0,sizeof(RectangleInfo)); for (p=command; ; ) { c=ReadBlobByte(image); if (c == EOF) break; (void) fputc(c,file); *p++=(char) c; if ((c != '\n') && (c != '\r') && ((p-command) < (MaxTextExtent-1))) continue; *p='\0'; p=command; /* Continue unless this is a MediaBox statement. */ if (LocaleNCompare(command,"/Rotate ",8) == 0) { count=sscanf(command,"/Rotate %d",&rotate); if (count > 0) { (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Rotate by %d degrees",rotate); } } q=strstr(command,MediaBox); if (q == (char *) NULL) continue; count=sscanf(q,"/MediaBox [%lf %lf %lf %lf",&bounds.x1,&bounds.y1, &bounds.x2,&bounds.y2); if (count != 4) count=sscanf(q,"/MediaBox[%lf %lf %lf %lf",&bounds.x1,&bounds.y1, &bounds.x2,&bounds.y2); if (count == 4) { (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Parsed: MediaBox %lf %lf %lf %lf", bounds.x1,bounds.y1, bounds.x2,bounds.y2); } if (count != 4) continue; if ((bounds.x1 > bounds.x2) || (bounds.y1 > bounds.y2)) continue; /* Set Postscript render geometry. */ width=(unsigned long) (bounds.x2-bounds.x1+0.5); height=(unsigned long) (bounds.y2-bounds.y1+0.5); if ((width <= box.width) && (height <= box.height)) continue; page.width=width; page.height=height; box=page; } /* If page is rotated right or left, then swap width and height values. */ if ((90 == AbsoluteValue(rotate)) || (270 == AbsoluteValue(rotate))) { double value; value=page.width; page.width=page.height; page.height=value; } if ((page.width == 0) || (page.height == 0)) { SetGeometry(image,&page); (void) GetGeometry(PSPageGeometry,&page.x,&page.y,&page.width, &page.height); } if (image_info->page != (char *) NULL) (void) GetGeometry(image_info->page,&page.x,&page.y,&page.width, &page.height); geometry[0]='\0'; FormatString(geometry,"%lux%lu", (unsigned long) ceil(page.width*image->x_resolution/dx_resolution-0.5), (unsigned long) ceil(page.height*image->y_resolution/dy_resolution-0.5)); if (ferror(file)) { (void) fclose(file); ThrowReaderException(CorruptImageError,AnErrorHasOccurredWritingToFile, image); } } (void) fclose(file); CloseBlob(image); /* Use Ghostscript to convert Postscript image. */ { char options[MaxTextExtent], arg[MaxTextExtent]; options[0]='\0'; if (use_crop_box) (void) strlcat(options,"-dUseCropBox",sizeof(options)); if (pdf_stop_on_error) { if (options[0] != '\0') (void) strlcat(options," ",sizeof(options)); (void) strlcat(options,"-dPDFSTOPONERROR",sizeof(options)); } /* Append subrange. */ if (image_info->subrange != 0) { FormatString(arg,"-dFirstPage=%lu -dLastPage=%lu", image_info->subimage+1, image_info->subimage+image_info->subrange); if (options[0] != '\0') (void) strlcat(options," ",sizeof(options)); (void) strlcat(options,arg,sizeof(options)); } /* Append authentication string. */ if (image_info->authenticate != (char *) NULL) { FormatString(arg,"-sPDFPassword=%.1024s", image_info->authenticate); if (options[0] != '\0') (void) strlcat(options," ",sizeof(options)); (void) strlcat(options,arg,sizeof(options)); } (void) strlcpy(filename,image_info->filename,MaxTextExtent); clone_info=CloneImageInfo(image_info); if (!AcquireTemporaryFileName(clone_info->filename)) { DestroyImageInfo(clone_info); ThrowReaderTemporaryFileException(clone_info->filename); } FormatString(command,delegate_info->commands,antialias, antialias,density,options,clone_info->filename, postscript_filename); } (void) MagickMonitorFormatted(0,8,&image->exception,RenderPostscriptText, image->filename); status=InvokePostscriptDelegate(clone_info->verbose,command,exception); (void) MagickMonitorFormatted(7,8,&image->exception,RenderPostscriptText, image->filename); DestroyImage(image); image=(Image *) NULL; if (IsAccessibleAndNotEmpty(clone_info->filename)) { /* Read Ghostscript output. */ clone_info->blob=(void *) NULL; clone_info->length=0; clone_info->magick[0]='\0'; clone_info->subimage=0; clone_info->subrange=0; MagickFreeMemory(clone_info->tile); image=ReadImage(clone_info,exception); } (void) LiberateTemporaryFile(postscript_filename); (void) LiberateTemporaryFile(clone_info->filename); DestroyImageInfo(clone_info); if (image == (Image *) NULL) { if (UndefinedException == exception->severity) ThrowException(exception,DelegateError,PostscriptDelegateFailed,filename); } else { do { (void) strlcpy(image->magick,"PDF",sizeof(image->magick)); (void) strlcpy(image->filename,filename,sizeof(image->filename)); next_image=SyncNextImageInList(image); if (next_image != (Image *) NULL) image=next_image; } while (next_image != (Image *) NULL); while (image->previous != (Image *) NULL) image=image->previous; if (image_info->subimage != 0) { unsigned long scene = image_info->subimage; for (next_image=image; next_image != (Image *) NULL; next_image=next_image->next) next_image->scene = scene++; } } return(image); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e g i s t e r P D F I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method RegisterPDFImage adds attributes for the PDF image format to % the list of supported formats. The attributes include the image format % tag, a method to read and/or write the format, whether the format % supports the saving of more than one frame to the same file or blob, % whether the format supports native in-memory I/O, and a brief % description of the format. % % The format of the RegisterPDFImage method is: % % RegisterPDFImage(void) % */ ModuleExport void RegisterPDFImage(void) { MagickInfo *entry; entry=SetMagickInfo("EPDF"); entry->decoder=(DecoderHandler) ReadPDFImage; entry->encoder=(EncoderHandler) WritePDFImage; entry->adjoin=False; entry->blob_support=False; entry->seekable_stream=True; entry->description="Encapsulated Portable Document Format"; entry->module="PDF"; entry->coder_class=PrimaryCoderClass; (void) RegisterMagickInfo(entry); entry=SetMagickInfo("PDF"); entry->decoder=(DecoderHandler) ReadPDFImage; entry->encoder=(EncoderHandler) WritePDFImage; entry->magick=(MagickHandler) IsPDF; entry->blob_support=False; entry->seekable_stream=True; entry->description="Portable Document Format"; entry->module="PDF"; entry->coder_class=PrimaryCoderClass; (void) RegisterMagickInfo(entry); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % U n r e g i s t e r P D F I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method UnregisterPDFImage removes format registrations made by the % PDF module from the list of supported formats. % % The format of the UnregisterPDFImage method is: % % UnregisterPDFImage(void) % */ ModuleExport void UnregisterPDFImage(void) { (void) UnregisterMagickInfo("EPDF"); (void) UnregisterMagickInfo("PDF"); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % W r i t e P D F I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method WritePDFImage writes an image in the Portable Document image % format. % % The format of the WritePDFImage method is: % % unsigned int WritePDFImage(const ImageInfo *image_info,Image *image) % % A description of each parameter follows. % % o status: Method WritePDFImage return True if the image is written. % False is returned is there is a memory shortage or if the image file % fails to write. % % o image_info: Specifies a pointer to a ImageInfo structure. % % o image: A pointer to an Image structure. % % */ static char *EscapeParenthesis(const char *text) { register char *p; register long i; /* FIXME: use of a static buffer here makes this function not thread safe! */ static char buffer[MaxTextExtent]; unsigned long escapes; escapes=0; p=buffer; for (i=0; i < (long) Min(strlen(text),(MaxTextExtent-escapes-1)); i++) { if ((text[i] == '(') || (text[i] == ')')) { *p++='\\'; escapes++; } *p++=text[i]; } *p='\0'; return(buffer); } #define ThrowPDFWriterException(code_,reason_,image_) \ { \ MagickFreeMemory(fax_blob); \ MagickFreeMemory(xref); \ ThrowWriterException(code_,reason_,image_); \ } #define ThrowPDFWriterException2(code_,reason_,image_) \ { \ MagickFreeMemory(fax_blob); \ MagickFreeMemory(xref); \ ThrowWriterException2(code_,reason_,image_); \ } static unsigned int WritePDFImage(const ImageInfo *image_info,Image *image) { #define CFormat "/Filter [ /%.1024s ]\n" #define ObjectsPerImage 9 char basename[MaxTextExtent], buffer[MaxTextExtent], date[MaxTextExtent], density[MaxTextExtent], **labels, page_geometry[MaxTextExtent]; unsigned char *fax_blob=(unsigned char *) NULL; const ImageAttribute *attribute; double dx_resolution, dy_resolution, x_resolution, x_scale, y_resolution, y_scale; ExtendedSignedIntegralType offset; long count, y; RectangleInfo geometry, media_info; register const PixelPacket *p; register const IndexPacket *indexes; register unsigned char *q; register long i, x; size_t fax_blob_length, length; struct tm *time_meridian; time_t seconds; unsigned char *pixels; MagickPassFail status; unsigned long info_id, number_pixels, object, pages_id, root_id, scene, text_size; ExtendedSignedIntegralType *xref = (ExtendedSignedIntegralType *) NULL; size_t image_list_length; /* Open output image file. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickSignature); assert(image != (Image *) NULL); assert(image->signature == MagickSignature); image_list_length=GetImageListLength(image); status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception); if (status == False) ThrowPDFWriterException(FileOpenError,UnableToOpenFile,image); /* Allocate X ref memory. */ xref=MagickAllocateMemory(ExtendedSignedIntegralType *, 2048*sizeof(ExtendedSignedIntegralType)); if (xref == (ExtendedSignedIntegralType *) NULL) ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed,image); (void) memset(xref,0,2048*sizeof(ExtendedSignedIntegralType)); /* Write Documentation Information Dictionary */ object=0; (void) WriteBlobString(image,"%PDF-1.2 \n"); xref[object++]=TellBlob(image); info_id=object; FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"<<\n"); seconds=time((time_t *) NULL); time_meridian=localtime(&seconds); FormatString(date,"D:%04d%02d%02d%02d%02d%02d",time_meridian->tm_year+1900, time_meridian->tm_mon+1,time_meridian->tm_mday,time_meridian->tm_hour, time_meridian->tm_min,time_meridian->tm_sec); GetPathComponent(image->filename,BasePath,basename); FormatString(buffer,"/Title (%.1024s)\n",EscapeParenthesis(basename)); (void) WriteBlobString(image,buffer); FormatString(buffer,"/CreationDate (%.1024s)\n",date); (void) WriteBlobString(image,buffer); FormatString(buffer,"/ModDate (%.1024s)\n",date); (void) WriteBlobString(image,buffer); FormatString(buffer,"/Producer (%.1024s)\n", EscapeParenthesis(GetMagickVersion((unsigned long *) NULL))); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,">>\n"); (void) WriteBlobString(image,"endobj\n"); /* Write Catalog object. */ xref[object++]=TellBlob(image); root_id=object; FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"<<\n"); (void) WriteBlobString(image,"/Type /Catalog\n"); FormatString(buffer,"/Pages %lu 0 R\n",object+1); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,">>\n"); (void) WriteBlobString(image,"endobj\n"); /* Write Pages object. */ xref[object++]=TellBlob(image); pages_id=object; FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"<<\n"); (void) WriteBlobString(image,"/Type /Pages\n"); FormatString(buffer,"/Kids [ %lu 0 R ",object+1); (void) WriteBlobString(image,buffer); count=(long) (pages_id+ObjectsPerImage+1); if (image_info->adjoin) { Image *kid_image; /* Predict page object id's. */ kid_image=image; for ( ; kid_image->next != (Image *) NULL; count+=ObjectsPerImage) { FormatString(buffer,"%ld 0 R ",count); (void) WriteBlobString(image,buffer); kid_image=kid_image->next; } MagickReallocMemory(ExtendedSignedIntegralType *,xref, (count+2048)*sizeof(ExtendedSignedIntegralType)); if (xref == (ExtendedSignedIntegralType *) NULL) ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed,image); } (void) WriteBlobString(image,"]\n"); FormatString(buffer,"/Count %lu\n",(count-pages_id)/ObjectsPerImage); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,">>\n"); (void) WriteBlobString(image,"endobj\n"); scene=0; do { ImageCharacteristics characteristics; CompressionType compression; /* Analyze image properties. */ (void) GetImageCharacteristics(image,&characteristics, (OptimizeType == image_info->type), &image->exception); compression=image->compression; if (image_info->compression != UndefinedCompression) { /* ImageInfo compression always prevails if it is set. */ compression=image_info->compression; } else { /* Default to Zip compression unless the image was JPEG compressed and is not now monochrome or colormapped. */ if ((JPEGCompression != compression) || (characteristics.monochrome) || (characteristics.palette)) { #if defined(HasZLIB) compression=ZipCompression; #else compression=LZWCompression; #endif } } switch (compression) { #if !defined(HasJPEG) case JPEGCompression: { /* If JPEG compression is not supported, then use RLE compression and report a warning to user. */ compression=RLECompression; ThrowException(&image->exception,MissingDelegateError,JPEGLibraryIsNotAvailable,image->filename); break; } #endif #if !defined(HasZLIB) case ZipCompression: { /* If ZIP compression is not supported, then use RLE compression and report a warning to user. */ compression=RLECompression; ThrowException(&image->exception,MissingDelegateError,ZipLibraryIsNotAvailable,image->filename); break; } #endif default: break; } (void) LogMagickEvent(CoderEvent,GetMagickModule(), "%s compression.", CompressionTypeToString(compression)); /* Scale image to size of Portable Document page. */ text_size=0; attribute=GetImageAttribute(image,"label"); if (attribute != (const ImageAttribute *) NULL) text_size=(unsigned int) (MultilineCensus(attribute->value)*image_info->pointsize+12); SetGeometry(image,&geometry); geometry.y=(long) text_size; FormatString(page_geometry,"%lux%lu",image->columns,image->rows); if (image_info->page != (char *) NULL) { (void) strlcpy(page_geometry,image_info->page,MaxTextExtent); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Page: %s (from ImageInfo) ", page_geometry); } else { if ((image->page.width != 0) && (image->page.height != 0)) { (void) FormatString(page_geometry,"%lux%lu%+ld%+ld",image->page.width, image->page.height,image->page.x,image->page.y); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Page: %s (from Image)", page_geometry); } else { if (LocaleCompare(image_info->magick,"PDF") == 0) { (void) strlcpy(page_geometry,PSPageGeometry,sizeof(page_geometry)); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Page: %s (defaulted)", page_geometry); } else { (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Page: %s (from Image columns/rows)", page_geometry); } } } (void) GetMagickGeometry(page_geometry,&geometry.x,&geometry.y, &geometry.width,&geometry.height); (void) GetGeometry(page_geometry,&media_info.x,&media_info.y, &media_info.width,&media_info.height); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Image Resolution: %gx%g %s", image->x_resolution, image->y_resolution, ResolutionTypeToString(image->units)); /* Scale relative to dots-per-inch. */ dx_resolution=72.0; dy_resolution=72.0; x_resolution=72.0; (void) strlcpy(density,PSDensityGeometry,sizeof(density)); count=GetMagickDimension(density,&x_resolution,&y_resolution,NULL,NULL); if (count != 2) y_resolution=x_resolution; /* Use override resolution information if it appears to be valid. */ if ((image_info->density != (char *) NULL) && ((image_info->units == PixelsPerInchResolution) || (image_info->units == PixelsPerCentimeterResolution))) { count=GetMagickDimension(image_info->density,&x_resolution, &y_resolution,NULL,NULL); if (count != 2) y_resolution=x_resolution; if (image_info->units == PixelsPerCentimeterResolution) { x_resolution *= 2.54; y_resolution *= 2.54; } } /* Use image resolution information if it appears to be valid. */ else if ((image->x_resolution > 0.0) && (image->y_resolution > 0.0) && ((image->units == PixelsPerInchResolution) || (image->units == PixelsPerCentimeterResolution))) { x_resolution = image->x_resolution; y_resolution = image->y_resolution; if (image->units == PixelsPerCentimeterResolution) { x_resolution *= 2.54; y_resolution *= 2.54; } } (void) LogMagickEvent(CoderEvent,GetMagickModule(), "PDF Resolution: %gx%g DPI", x_resolution,y_resolution); x_scale=(geometry.width*dx_resolution)/x_resolution; geometry.width=(unsigned long) (x_scale+0.5); y_scale=(geometry.height*dy_resolution)/y_resolution; geometry.height=(unsigned long) (y_scale+0.5); /* Write Page object. */ xref[object++]=TellBlob(image); FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"<<\n"); (void) WriteBlobString(image,"/Type /Page\n"); FormatString(buffer,"/Parent %lu 0 R\n",pages_id); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"/Resources <<\n"); FormatString(buffer,"/XObject << /Im%lu %lu 0 R >>\n",image->scene, object+4); (void) WriteBlobString(image,buffer); FormatString(buffer,"/ProcSet %lu 0 R >>\n",object+3); (void) WriteBlobString(image,buffer); FormatString(buffer,"/MediaBox [0 0 %ld %ld]\n", media_info.width,media_info.height); (void) WriteBlobString(image,buffer); FormatString(buffer,"/CropBox [%ld %ld %ld %ld]\n",geometry.x,geometry.y, geometry.x+geometry.width,geometry.y+geometry.height); (void) WriteBlobString(image,buffer); FormatString(buffer,"/Contents %lu 0 R\n",object+1); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,">>\n"); (void) WriteBlobString(image,"endobj\n"); /* Write Contents object. */ xref[object++]=TellBlob(image); FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"<<\n"); FormatString(buffer,"/Length %lu 0 R\n",object+1); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,">>\n"); (void) WriteBlobString(image,"stream\n"); offset=TellBlob(image); (void) WriteBlobString(image,"q\n"); labels=(char **) NULL; attribute=GetImageAttribute(image,"label"); if (attribute != (const ImageAttribute *) NULL) labels=StringToList(attribute->value); if (labels != (char **) NULL) { for (i=0; labels[i] != (char *) NULL; i++) { (void) WriteBlobString(image,"BT\n"); FormatString(buffer,"/F%lu %g Tf\n",image->scene, image_info->pointsize); (void) WriteBlobString(image,buffer); FormatString(buffer,"%ld %g Td\n",geometry.x,geometry.y+ geometry.height+i*image_info->pointsize+12); (void) WriteBlobString(image,buffer); FormatString(buffer,"(%.1024s) Tj\n",labels[i]); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"ET\n"); MagickFreeMemory(labels[i]); } MagickFreeMemory(labels); } FormatString(buffer,"%g 0 0 %g %ld %ld cm\n",x_scale,y_scale,geometry.x, geometry.y); (void) WriteBlobString(image,buffer); FormatString(buffer,"/Im%lu Do\n",image->scene); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"Q\n"); offset=TellBlob(image)-offset; (void) WriteBlobString(image,"endstream\n"); (void) WriteBlobString(image,"endobj\n"); /* Write Length object. */ xref[object++]=TellBlob(image); FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); FormatString(buffer,"%lu\n",(unsigned long) offset); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"endobj\n"); /* Write Procset object. */ xref[object++]=TellBlob(image); FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); if ((image->storage_class == DirectClass) || (image->colors > 256)) (void) strlcpy(buffer,"[ /PDF /Text /ImageC",sizeof(buffer)); else if (compression == FaxCompression) (void) strlcpy(buffer,"[ /PDF /Text /ImageB",sizeof(buffer)); else (void) strlcpy(buffer,"[ /PDF /Text /ImageI",sizeof(buffer)); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image," ]\n"); (void) WriteBlobString(image,"endobj\n"); /* Write XObject object. */ xref[object++]=TellBlob(image); FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"<<\n"); (void) WriteBlobString(image,"/Type /XObject\n"); (void) WriteBlobString(image,"/Subtype /Image\n"); FormatString(buffer,"/Name /Im%lu\n",image->scene); (void) WriteBlobString(image,buffer); switch (compression) { case NoCompression: { FormatString(buffer,CFormat,"ASCII85Decode"); break; } case JPEGCompression: { FormatString(buffer,CFormat,"DCTDecode"); if (image->colorspace != CMYKColorspace) break; (void) WriteBlobString(image,buffer); (void) strlcpy(buffer,"/Decode [1 0 1 0 1 0 1 0]\n",sizeof(buffer)); break; } case LZWCompression: { FormatString(buffer,CFormat,"LZWDecode"); break; } case ZipCompression: { FormatString(buffer,CFormat,"FlateDecode"); break; } case FaxCompression: { char CCITTParam[4]; ExceptionInfo exception; /* Try compressing page to Group4 to see if it is supported, otherwise we will fall back to Group3. */ (void) strlcpy(CCITTParam,"0",sizeof(CCITTParam)); GetExceptionInfo(&exception); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Executing ImageToHuffman2DBlob for CCITT Fax4 ..."); fax_blob=ImageToHuffman2DBlob(image,image_info,&fax_blob_length, &exception); if (fax_blob != (unsigned char *) NULL) { (void) strlcpy(CCITTParam,"-1",sizeof(CCITTParam)); (void) LogMagickEvent(CoderEvent,GetMagickModule(), "ImageToHuffman2DBlob reports success!"); } DestroyExceptionInfo(&exception); (void) strlcpy(buffer,"/Filter [ /CCITTFaxDecode ]\n",sizeof(buffer)); (void) WriteBlobString(image,buffer); (void) strlcpy(buffer,"/Interpolate false\n",sizeof(buffer)); (void) WriteBlobString(image,buffer); FormatString(buffer, "/DecodeParms [ << /K %.1024s /BlackIs1 false /Columns %ld /Rows %ld >> ]\n", CCITTParam,image->columns,image->rows); break; } default: { FormatString(buffer,CFormat,"RunLengthDecode"); break; } } (void) WriteBlobString(image,buffer); FormatString(buffer,"/Width %lu\n",image->columns); (void) WriteBlobString(image,buffer); FormatString(buffer,"/Height %lu\n",image->rows); (void) WriteBlobString(image,buffer); if (compression == FaxCompression) strlcpy(buffer,"/ColorSpace /DeviceGray\n",MaxTextExtent); else FormatString(buffer,"/ColorSpace %lu 0 R\n",object+2); (void) WriteBlobString(image,buffer); FormatString(buffer,"/BitsPerComponent %d\n", compression == FaxCompression ? 1 : 8); (void) WriteBlobString(image,buffer); FormatString(buffer,"/Length %lu 0 R\n",object+1); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,">>\n"); (void) WriteBlobString(image,"stream\n"); offset=TellBlob(image); number_pixels=image->columns*image->rows; if ((compression == FaxCompression) || ((image_info->type != TrueColorType) && characteristics.grayscale)) { /* Write grayscale output. */ switch (compression) { case FaxCompression: { /* Try Group4 first and use Group3 as a fallback. */ if (fax_blob != (unsigned char *) NULL) { (void) WriteBlob(image,fax_blob_length,fax_blob); MagickFreeMemory(fax_blob); } else { (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Executing HuffmanEncodeImage for CCITT Fax3 ..."); if (!HuffmanEncodeImage(image_info,image)) (void) LogMagickEvent(CoderEvent,GetMagickModule(), "HuffmanEncodeImage reports failure!"); } break; } case JPEGCompression: { unsigned char *jpeg_blob; /* Write image in JPEG format. */ jpeg_blob=ImageToJPEGBlob(image,image_info,&length,&image->exception); if (jpeg_blob == (unsigned char *) NULL) ThrowPDFWriterException2(CoderError,image->exception.reason,image); (void) WriteBlob(image,length,jpeg_blob); MagickFreeMemory(jpeg_blob); break; } case RLECompression: default: { /* Allocate pixel array. */ length=number_pixels; pixels=MagickAllocateMemory(unsigned char *,length); if (pixels == (unsigned char *) NULL) ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed, image); /* Dump Runlength encoded pixels. */ q=pixels; for (y=0; y < (long) image->rows; y++) { p=AcquireImagePixels(image,0,y,image->columns,1, &image->exception); if (p == (const PixelPacket *) NULL) break; for (x=0; x < (long) image->columns; x++) { *q++=ScaleQuantumToChar(PixelIntensityToQuantum(p)); p++; } if (image->previous == (Image *) NULL) if (QuantumTick(y,image->rows)) { status=MagickMonitorFormatted(y,image->rows, &image->exception, SaveImageText, image->filename, image->columns,image->rows); if (status == False) break; } } #if defined(HasZLIB) if (compression == ZipCompression) status=ZLIBEncodeImage(image,length,image_info->quality,pixels); else #endif /* defined(HasZLIB) */ if (compression == LZWCompression) status=LZWEncodeImage(image,length,pixels); else status=PackbitsEncodeImage(image,length,pixels); MagickFreeMemory(pixels); if (!status) { CloseBlob(image); MagickFreeMemory(xref); return(MagickFail); } break; } case NoCompression: { /* Dump uncompressed PseudoColor packets. */ Ascii85Initialize(image); for (y=0; y < (long) image->rows; y++) { p=AcquireImagePixels(image,0,y,image->columns,1, &image->exception); if (p == (const PixelPacket *) NULL) break; for (x=0; x < (long) image->columns; x++) { Ascii85Encode(image, ScaleQuantumToChar(PixelIntensityToQuantum(p))); p++; } if (image->previous == (Image *) NULL) if (QuantumTick(y,image->rows)) { status=MagickMonitorFormatted(y,image->rows, &image->exception, SaveImageText, image->filename, image->columns,image->rows); if (status == False) break; } } Ascii85Flush(image); break; } } } else if ((image->storage_class == DirectClass) || (image->colors > 256) || (compression == JPEGCompression)) switch (compression) { case JPEGCompression: { unsigned char *jpeg_blob; /* Write image in JPEG format. */ jpeg_blob=ImageToJPEGBlob(image,image_info,&length,&image->exception); if (jpeg_blob == (unsigned char *) NULL) ThrowPDFWriterException2(CoderError,image->exception.reason,image); (void) WriteBlob(image,length,jpeg_blob); MagickFreeMemory(jpeg_blob); break; } case RLECompression: default: { /* Allocate pixel array. */ length=(image->colorspace == CMYKColorspace ? 4 : 3)*number_pixels; pixels=MagickAllocateMemory(unsigned char *,length); if (pixels == (unsigned char *) NULL) ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed, image); /* Dump runoffset encoded pixels. */ q=pixels; for (y=0; y < (long) image->rows; y++) { p=AcquireImagePixels(image,0,y,image->columns,1, &image->exception); if (p == (const PixelPacket *) NULL) break; for (x=0; x < (long) image->columns; x++) { if (image->matte && (p->opacity == TransparentOpacity)) { *q++=ScaleQuantumToChar(MaxRGB); *q++=ScaleQuantumToChar(MaxRGB); *q++=ScaleQuantumToChar(MaxRGB); p++; continue; } *q++=ScaleQuantumToChar(p->red); *q++=ScaleQuantumToChar(p->green); *q++=ScaleQuantumToChar(p->blue); if (image->colorspace == CMYKColorspace) *q++=ScaleQuantumToChar(p->opacity); p++; } if (image->previous == (Image *) NULL) if (QuantumTick(y,image->rows)) { status=MagickMonitorFormatted(y,image->rows, &image->exception, SaveImageText, image->filename, image->columns,image->rows); if (status == False) break; } } #if defined(HasZLIB) if (compression == ZipCompression) status=ZLIBEncodeImage(image,length,image_info->quality,pixels); else #endif /* defined(HasZLIB) */ if (compression == LZWCompression) status=LZWEncodeImage(image,length,pixels); else status=PackbitsEncodeImage(image,length,pixels); MagickFreeMemory(pixels); if (!status) { CloseBlob(image); return(False); } break; } case NoCompression: { /* Dump uncompressed DirectColor packets. */ Ascii85Initialize(image); for (y=0; y < (long) image->rows; y++) { p=AcquireImagePixels(image,0,y,image->columns,1, &image->exception); if (p == (const PixelPacket *) NULL) break; for (x=0; x < (long) image->columns; x++) { if (image->matte && (p->opacity == TransparentOpacity)) { Ascii85Encode(image,ScaleQuantumToChar(MaxRGB)); Ascii85Encode(image,ScaleQuantumToChar(MaxRGB)); Ascii85Encode(image,ScaleQuantumToChar(MaxRGB)); continue; } Ascii85Encode(image,ScaleQuantumToChar(p->red)); Ascii85Encode(image,ScaleQuantumToChar(p->green)); Ascii85Encode(image,ScaleQuantumToChar(p->blue)); if (image->colorspace == CMYKColorspace) Ascii85Encode(image,ScaleQuantumToChar(p->opacity)); p++; } if (image->previous == (Image *) NULL) if (QuantumTick(y,image->rows)) { status=MagickMonitorFormatted(y,image->rows, &image->exception, SaveImageText, image->filename, image->columns,image->rows); if (status == False) break; } } Ascii85Flush(image); break; } } else { /* Dump number of colors and colormap. */ switch (compression) { case RLECompression: default: { /* Allocate pixel array. */ length=number_pixels; pixels=MagickAllocateMemory(unsigned char *,length); if (pixels == (unsigned char *) NULL) ThrowPDFWriterException(ResourceLimitError,MemoryAllocationFailed,image); /* Dump Runlength encoded pixels. */ q=pixels; for (y=0; y < (long) image->rows; y++) { p=AcquireImagePixels(image,0,y,image->columns,1, &image->exception); if (p == (const PixelPacket *) NULL) break; indexes=AccessImmutableIndexes(image); for (x=0; x < (long) image->columns; x++) *q++=indexes[x]; if (image->previous == (Image *) NULL) if (QuantumTick(y,image->rows)) { status=MagickMonitorFormatted(y,image->rows, &image->exception, SaveImageText, image->filename, image->columns,image->rows); if (status == False) break; } } #if defined(HasZLIB) if (compression == ZipCompression) status=ZLIBEncodeImage(image,length,image_info->quality,pixels); else #endif /* defined(HasZLIB) */ if (compression == LZWCompression) status=LZWEncodeImage(image,length,pixels); else status=PackbitsEncodeImage(image,length,pixels); MagickFreeMemory(pixels); if (!status) { CloseBlob(image); return(False); } break; } case NoCompression: { /* Dump uncompressed PseudoColor packets. */ Ascii85Initialize(image); for (y=0; y < (long) image->rows; y++) { p=AcquireImagePixels(image,0,y,image->columns,1, &image->exception); if (p == (const PixelPacket *) NULL) break; indexes=AccessImmutableIndexes(image); for (x=0; x < (long) image->columns; x++) Ascii85Encode(image,indexes[x]); if (image->previous == (Image *) NULL) if (QuantumTick(y,image->rows)) { status=MagickMonitorFormatted(y,image->rows, &image->exception, SaveImageText, image->filename, image->columns,image->rows); if (status == False) break; } } Ascii85Flush(image); break; } } } offset=TellBlob(image)-offset; (void) WriteBlobString(image,"\nendstream\n"); (void) WriteBlobString(image,"endobj\n"); /* Write Length object. */ xref[object++]=TellBlob(image); FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); FormatString(buffer,"%lu\n",(unsigned long) offset); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"endobj\n"); /* Write Colorspace object. */ xref[object++]=TellBlob(image); FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); if (image->colorspace == CMYKColorspace) { (void) strlcpy(buffer,"/DeviceCMYK\n",sizeof(buffer)); } else { if ((compression == FaxCompression) || (compression == Group4Compression) || ((image_info->type != TrueColorType) && (characteristics.grayscale))) { (void) strlcpy(buffer,"/DeviceGray\n",sizeof(buffer)); } else { if ((image->storage_class == DirectClass) || (image->colors > 256) || (compression == JPEGCompression)) (void) strlcpy(buffer,"/DeviceRGB\n",sizeof(buffer)); else FormatString(buffer,"[ /Indexed /DeviceRGB %u %lu 0 R ]\n", (unsigned int) image->colors-1,object+1); } } (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"endobj\n"); /* Write Colormap object. */ xref[object++]=TellBlob(image); FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"<<\n"); if ((image->storage_class != DirectClass) && (image->colors <= 256) && (compression != FaxCompression)) { if (compression == NoCompression) (void) WriteBlobString(image,"/Filter [ /ASCII85Decode ]\n"); FormatString(buffer,"/Length %lu 0 R\n",object+1); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,">>\n"); (void) WriteBlobString(image,"stream\n"); offset=TellBlob(image); if (compression == NoCompression) Ascii85Initialize(image); for (i=0; i < (long) image->colors; i++) { if (compression == NoCompression) { Ascii85Encode(image,ScaleQuantumToChar(image->colormap[i].red)); Ascii85Encode(image,ScaleQuantumToChar(image->colormap[i].green)); Ascii85Encode(image,ScaleQuantumToChar(image->colormap[i].blue)); continue; } (void) WriteBlobByte(image, ScaleQuantumToChar(image->colormap[i].red)); (void) WriteBlobByte(image, ScaleQuantumToChar(image->colormap[i].green)); (void) WriteBlobByte(image, ScaleQuantumToChar(image->colormap[i].blue)); } if (compression == NoCompression) Ascii85Flush(image); } offset=TellBlob(image)-offset; (void) WriteBlobString(image,"\nendstream\n"); (void) WriteBlobString(image,"endobj\n"); /* Write Length object. */ xref[object++]=TellBlob(image); FormatString(buffer,"%lu 0 obj\n",object); (void) WriteBlobString(image,buffer); FormatString(buffer,"%lu\n",(unsigned long) offset); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"endobj\n"); if (image->next == (Image *) NULL) break; image=SyncNextImageInList(image); status=MagickMonitorFormatted(scene++,image_list_length, &image->exception,SaveImagesText, image->filename); if (status == False) break; } while (image_info->adjoin); if (image_info->adjoin) while (image->previous != (Image *) NULL) image=image->previous; /* Write Xref object. */ offset=TellBlob(image)-xref[0]+10; (void) WriteBlobString(image,"xref\n"); FormatString(buffer,"0 %lu\n",object+1); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"0000000000 65535 f \n"); for (i=0; i < (long) object; i++) { FormatString(buffer,"%010lu 00000 n \n",(unsigned long) xref[i]); (void) WriteBlobString(image,buffer); } (void) WriteBlobString(image,"trailer\n"); (void) WriteBlobString(image,"<<\n"); FormatString(buffer,"/Size %lu\n",object+1); (void) WriteBlobString(image,buffer); FormatString(buffer,"/Info %lu 0 R\n",info_id); (void) WriteBlobString(image,buffer); FormatString(buffer,"/Root %lu 0 R\n",root_id); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,">>\n"); (void) WriteBlobString(image,"startxref\n"); FormatString(buffer,"%lu\n",(unsigned long) offset); (void) WriteBlobString(image,buffer); (void) WriteBlobString(image,"%%EOF\n"); MagickFreeMemory(xref); CloseBlob(image); MagickFreeMemory(fax_blob); return(MagickPass); } #if defined(HasZLIB) #include "zlib.h" /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % Z L I B E n c o d e I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % Method ZLIBEncodeImage compresses an image via ZLIB-coding specific to % Postscript Level II or Portable Document Format. To ensure portability, the % binary ZLIB bytes are encoded as ASCII base-85. % % The format of the ZLIBEncodeImage method is: % % unsigned int ZLIBEncodeImage(Image *image,const size_t length, % const unsigned long quality,unsigned char *pixels) % % A description of each parameter follows: % % o status: Method ZLIBEncodeImage returns True if all the pixels are % compressed without error, otherwise False. % % o file: The address of a structure of type FILE. ZLIB encoded pixels % are written to this file. % % o length: A value that specifies the number of pixels to compress. % % o quality: the compression level (0-100). % % o pixels: The address of an unsigned array of characters containing the % pixels to compress. % % */ static voidpf ZLIBAllocFunc(voidpf opaque, uInt items, uInt size) MAGICK_FUNC_MALLOC; static voidpf ZLIBAllocFunc(voidpf opaque, uInt items, uInt size) { ARG_NOT_USED(opaque); return MagickMallocCleared(MagickArraySize(items,size)); } static void ZLIBFreeFunc(voidpf opaque, voidpf address) { ARG_NOT_USED(opaque); MagickFree(address); } static unsigned int ZLIBEncodeImage(Image *image,const size_t length, const unsigned long quality,unsigned char *pixels) { int status; register long i; unsigned char *compressed_pixels; unsigned long compressed_packets; z_stream stream; assert(image != (Image *) NULL); assert(image->signature == MagickSignature); compressed_packets=(unsigned long) (1.001*length+12); compressed_pixels=MagickAllocateMemory(unsigned char *,compressed_packets); if (compressed_pixels == (unsigned char *) NULL) ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed, (char *) NULL); (void) memset(&stream,0,sizeof(stream)); stream.next_in=pixels; stream.avail_in=(unsigned int) length; stream.next_out=compressed_pixels; stream.avail_out=(unsigned int) compressed_packets; stream.zalloc=ZLIBAllocFunc; stream.zfree=ZLIBFreeFunc; stream.opaque=(voidpf) NULL; status=deflateInit(&stream,(int) Min(quality/10,9)); if (status == Z_OK) { status=deflate(&stream,Z_FINISH); if (status == Z_STREAM_END) status=deflateEnd(&stream); else (void) deflateEnd(&stream); compressed_packets=stream.total_out; } if (status) ThrowBinaryException(CoderError,UnableToZipCompressImage,(char *) NULL) else for (i=0; i < (long) compressed_packets; i++) (void) WriteBlobByte(image,compressed_pixels[i]); MagickFreeMemory(compressed_pixels); return(!status); } #endif