diff options
author | Dan Sinclair <dsinclair@chromium.org> | 2018-03-22 16:21:04 +0000 |
---|---|---|
committer | Chromium commit bot <commit-bot@chromium.org> | 2018-03-22 16:21:04 +0000 |
commit | cb8768294337f42484b418141792f924d836f9d7 (patch) | |
tree | 1f38478ef41d7f670a48267d4fbac46bff3ce8d8 /samples/pdfium_test_write_helper.cc | |
parent | 0f494c86440debd22449acd9743382ba14c7a805 (diff) | |
download | pdfium-cb8768294337f42484b418141792f924d836f9d7.tar.xz |
Move helper methods out of pdfium_test.
This CL splits some helper code out of pdfium_test into individual
files. This makes it a bit clearer what each chunk of code is doing and
makes pdfium_test easier to read.
Change-Id: I3dcff5a6a85ac4283336108eb3a24fa5f234284c
Reviewed-on: https://pdfium-review.googlesource.com/28990
Reviewed-by: Henrique Nakashima <hnakashima@chromium.org>
Commit-Queue: dsinclair <dsinclair@chromium.org>
Diffstat (limited to 'samples/pdfium_test_write_helper.cc')
-rw-r--r-- | samples/pdfium_test_write_helper.cc | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/samples/pdfium_test_write_helper.cc b/samples/pdfium_test_write_helper.cc new file mode 100644 index 0000000000..27f43a64c4 --- /dev/null +++ b/samples/pdfium_test_write_helper.cc @@ -0,0 +1,644 @@ +// Copyright 2018 The PDFium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "samples/pdfium_test_write_helper.h" + +#include "public/cpp/fpdf_deleters.h" +#include "public/fpdf_annot.h" +#include "public/fpdf_attachment.h" +#include "public/fpdf_edit.h" +#include "testing/image_diff/image_diff_png.h" +#include "testing/test_support.h" +#include "third_party/base/logging.h" + +namespace { + +bool CheckDimensions(int stride, int width, int height) { + if (stride < 0 || width < 0 || height < 0) + return false; + if (height > 0 && width > INT_MAX / height) + return false; + return true; +} + +const char* AnnotSubtypeToCString(FPDF_ANNOTATION_SUBTYPE subtype) { + if (subtype == FPDF_ANNOT_TEXT) + return "Text"; + if (subtype == FPDF_ANNOT_LINK) + return "Link"; + if (subtype == FPDF_ANNOT_FREETEXT) + return "FreeText"; + if (subtype == FPDF_ANNOT_LINE) + return "Line"; + if (subtype == FPDF_ANNOT_SQUARE) + return "Square"; + if (subtype == FPDF_ANNOT_CIRCLE) + return "Circle"; + if (subtype == FPDF_ANNOT_POLYGON) + return "Polygon"; + if (subtype == FPDF_ANNOT_POLYLINE) + return "PolyLine"; + if (subtype == FPDF_ANNOT_HIGHLIGHT) + return "Highlight"; + if (subtype == FPDF_ANNOT_UNDERLINE) + return "Underline"; + if (subtype == FPDF_ANNOT_SQUIGGLY) + return "Squiggly"; + if (subtype == FPDF_ANNOT_STRIKEOUT) + return "StrikeOut"; + if (subtype == FPDF_ANNOT_STAMP) + return "Stamp"; + if (subtype == FPDF_ANNOT_CARET) + return "Caret"; + if (subtype == FPDF_ANNOT_INK) + return "Ink"; + if (subtype == FPDF_ANNOT_POPUP) + return "Popup"; + if (subtype == FPDF_ANNOT_FILEATTACHMENT) + return "FileAttachment"; + if (subtype == FPDF_ANNOT_SOUND) + return "Sound"; + if (subtype == FPDF_ANNOT_MOVIE) + return "Movie"; + if (subtype == FPDF_ANNOT_WIDGET) + return "Widget"; + if (subtype == FPDF_ANNOT_SCREEN) + return "Screen"; + if (subtype == FPDF_ANNOT_PRINTERMARK) + return "PrinterMark"; + if (subtype == FPDF_ANNOT_TRAPNET) + return "TrapNet"; + if (subtype == FPDF_ANNOT_WATERMARK) + return "Watermark"; + if (subtype == FPDF_ANNOT_THREED) + return "3D"; + if (subtype == FPDF_ANNOT_RICHMEDIA) + return "RichMedia"; + if (subtype == FPDF_ANNOT_XFAWIDGET) + return "XFAWidget"; + NOTREACHED(); + return ""; +} + +void AppendFlagString(const char* flag, std::string* output) { + if (!output->empty()) + *output += ", "; + *output += flag; +} + +std::string AnnotFlagsToString(int flags) { + std::string str; + if (flags & FPDF_ANNOT_FLAG_INVISIBLE) + AppendFlagString("Invisible", &str); + if (flags & FPDF_ANNOT_FLAG_HIDDEN) + AppendFlagString("Hidden", &str); + if (flags & FPDF_ANNOT_FLAG_PRINT) + AppendFlagString("Print", &str); + if (flags & FPDF_ANNOT_FLAG_NOZOOM) + AppendFlagString("NoZoom", &str); + if (flags & FPDF_ANNOT_FLAG_NOROTATE) + AppendFlagString("NoRotate", &str); + if (flags & FPDF_ANNOT_FLAG_NOVIEW) + AppendFlagString("NoView", &str); + if (flags & FPDF_ANNOT_FLAG_READONLY) + AppendFlagString("ReadOnly", &str); + if (flags & FPDF_ANNOT_FLAG_LOCKED) + AppendFlagString("Locked", &str); + if (flags & FPDF_ANNOT_FLAG_TOGGLENOVIEW) + AppendFlagString("ToggleNoView", &str); + return str; +} + +const char* PageObjectTypeToCString(int type) { + if (type == FPDF_PAGEOBJ_TEXT) + return "Text"; + if (type == FPDF_PAGEOBJ_PATH) + return "Path"; + if (type == FPDF_PAGEOBJ_IMAGE) + return "Image"; + if (type == FPDF_PAGEOBJ_SHADING) + return "Shading"; + if (type == FPDF_PAGEOBJ_FORM) + return "Form"; + NOTREACHED(); + return ""; +} + +#ifdef _WIN32 +int CALLBACK EnhMetaFileProc(HDC hdc, + HANDLETABLE* handle_table, + const ENHMETARECORD* record, + int objects_count, + LPARAM param) { + std::vector<const ENHMETARECORD*>& items = + *reinterpret_cast<std::vector<const ENHMETARECORD*>*>(param); + items.push_back(record); + return 1; +} +#endif // _WIN32 + +} // namespace + +std::string WritePpm(const char* pdf_name, + int num, + const void* buffer_void, + int stride, + int width, + int height) { + const auto* buffer = reinterpret_cast<const char*>(buffer_void); + + if (!CheckDimensions(stride, width, height)) + return ""; + + int out_len = width * height; + if (out_len > INT_MAX / 3) + return ""; + + out_len *= 3; + + char filename[256]; + snprintf(filename, sizeof(filename), "%s.%d.ppm", pdf_name, num); + FILE* fp = fopen(filename, "wb"); + if (!fp) + return ""; + + fprintf(fp, "P6\n# PDF test render\n%d %d\n255\n", width, height); + // Source data is B, G, R, unused. + // Dest data is R, G, B. + std::vector<char> result(out_len); + for (int h = 0; h < height; ++h) { + const char* src_line = buffer + (stride * h); + char* dest_line = result.data() + (width * h * 3); + for (int w = 0; w < width; ++w) { + // R + dest_line[w * 3] = src_line[(w * 4) + 2]; + // G + dest_line[(w * 3) + 1] = src_line[(w * 4) + 1]; + // B + dest_line[(w * 3) + 2] = src_line[w * 4]; + } + } + if (fwrite(result.data(), out_len, 1, fp) != 1) + fprintf(stderr, "Failed to write to %s\n", filename); + + fclose(fp); + return std::string(filename); +} + +void WriteText(FPDF_PAGE page, const char* pdf_name, int num) { + char filename[256]; + int chars_formatted = + snprintf(filename, sizeof(filename), "%s.%d.txt", pdf_name, num); + if (chars_formatted < 0 || + static_cast<size_t>(chars_formatted) >= sizeof(filename)) { + fprintf(stderr, "Filename %s is too long\n", filename); + return; + } + + FILE* fp = fopen(filename, "w"); + if (!fp) { + fprintf(stderr, "Failed to open %s for output\n", filename); + return; + } + + // Output in UTF32-LE. + uint32_t bom = 0x0000FEFF; + if (fwrite(&bom, sizeof(bom), 1, fp) != 1) { + fprintf(stderr, "Failed to write to %s\n", filename); + (void)fclose(fp); + return; + } + + std::unique_ptr<void, FPDFTextPageDeleter> textpage(FPDFText_LoadPage(page)); + for (int i = 0; i < FPDFText_CountChars(textpage.get()); i++) { + uint32_t c = FPDFText_GetUnicode(textpage.get(), i); + if (fwrite(&c, sizeof(c), 1, fp) != 1) { + fprintf(stderr, "Failed to write to %s\n", filename); + break; + } + } + (void)fclose(fp); +} + +void WriteAnnot(FPDF_PAGE page, const char* pdf_name, int num) { + // Open the output text file. + char filename[256]; + int chars_formatted = + snprintf(filename, sizeof(filename), "%s.%d.annot.txt", pdf_name, num); + if (chars_formatted < 0 || + static_cast<size_t>(chars_formatted) >= sizeof(filename)) { + fprintf(stderr, "Filename %s is too long\n", filename); + return; + } + + FILE* fp = fopen(filename, "w"); + if (!fp) { + fprintf(stderr, "Failed to open %s for output\n", filename); + return; + } + + int annot_count = FPDFPage_GetAnnotCount(page); + fprintf(fp, "Number of annotations: %d\n\n", annot_count); + + // Iterate through all annotations on this page. + for (int i = 0; i < annot_count; ++i) { + // Retrieve the annotation object and its subtype. + fprintf(fp, "Annotation #%d:\n", i + 1); + std::unique_ptr<void, FPDFAnnotationDeleter> annot( + FPDFPage_GetAnnot(page, i)); + if (!annot) { + fprintf(fp, "Failed to retrieve annotation!\n\n"); + continue; + } + + FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot.get()); + fprintf(fp, "Subtype: %s\n", AnnotSubtypeToCString(subtype)); + + // Retrieve the annotation flags. + fprintf(fp, "Flags set: %s\n", + AnnotFlagsToString(FPDFAnnot_GetFlags(annot.get())).c_str()); + + // Retrieve the annotation's object count and object types. + const int obj_count = FPDFAnnot_GetObjectCount(annot.get()); + fprintf(fp, "Number of objects: %d\n", obj_count); + if (obj_count > 0) { + fprintf(fp, "Object types: "); + for (int j = 0; j < obj_count; ++j) { + const char* type = PageObjectTypeToCString( + FPDFPageObj_GetType(FPDFAnnot_GetObject(annot.get(), j))); + fprintf(fp, "%s ", type); + } + fprintf(fp, "\n"); + } + + // Retrieve the annotation's color and interior color. + unsigned int R; + unsigned int G; + unsigned int B; + unsigned int A; + if (FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_Color, &R, &G, &B, + &A)) { + fprintf(fp, "Color in RGBA: %d %d %d %d\n", R, G, B, A); + } else { + fprintf(fp, "Failed to retrieve color.\n"); + } + if (FPDFAnnot_GetColor(annot.get(), FPDFANNOT_COLORTYPE_InteriorColor, &R, + &G, &B, &A)) { + fprintf(fp, "Interior color in RGBA: %d %d %d %d\n", R, G, B, A); + } else { + fprintf(fp, "Failed to retrieve interior color.\n"); + } + + // Retrieve the annotation's contents and author. + static constexpr char kContentsKey[] = "Contents"; + static constexpr char kAuthorKey[] = "T"; + unsigned long len = + FPDFAnnot_GetStringValue(annot.get(), kContentsKey, nullptr, 0); + std::vector<char> buf(len); + FPDFAnnot_GetStringValue(annot.get(), kContentsKey, buf.data(), len); + fprintf(fp, "Content: %ls\n", + GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data())) + .c_str()); + len = FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, nullptr, 0); + buf.clear(); + buf.resize(len); + FPDFAnnot_GetStringValue(annot.get(), kAuthorKey, buf.data(), len); + fprintf(fp, "Author: %ls\n", + GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data())) + .c_str()); + + // Retrieve the annotation's quadpoints if it is a markup annotation. + if (FPDFAnnot_HasAttachmentPoints(annot.get())) { + FS_QUADPOINTSF quadpoints; + if (FPDFAnnot_GetAttachmentPoints(annot.get(), &quadpoints)) { + fprintf(fp, + "Quadpoints: (%.3f, %.3f), (%.3f, %.3f), (%.3f, %.3f), (%.3f, " + "%.3f)\n", + quadpoints.x1, quadpoints.y1, quadpoints.x2, quadpoints.y2, + quadpoints.x3, quadpoints.y3, quadpoints.x4, quadpoints.y4); + } else { + fprintf(fp, "Failed to retrieve quadpoints.\n"); + } + } + + // Retrieve the annotation's rectangle coordinates. + FS_RECTF rect; + if (FPDFAnnot_GetRect(annot.get(), &rect)) { + fprintf(fp, "Rectangle: l - %.3f, b - %.3f, r - %.3f, t - %.3f\n\n", + rect.left, rect.bottom, rect.right, rect.top); + } else { + fprintf(fp, "Failed to retrieve annotation rectangle.\n"); + } + } + + (void)fclose(fp); +} + +std::string WritePng(const char* pdf_name, + int num, + const void* buffer_void, + int stride, + int width, + int height) { + if (!CheckDimensions(stride, width, height)) + return ""; + + std::vector<unsigned char> png_encoding; + const auto* buffer = static_cast<const unsigned char*>(buffer_void); + if (!image_diff_png::EncodeBGRAPNG(buffer, width, height, stride, false, + &png_encoding)) { + fprintf(stderr, "Failed to convert bitmap to PNG\n"); + return ""; + } + + char filename[256]; + int chars_formatted = + snprintf(filename, sizeof(filename), "%s.%d.png", pdf_name, num); + if (chars_formatted < 0 || + static_cast<size_t>(chars_formatted) >= sizeof(filename)) { + fprintf(stderr, "Filename %s is too long\n", filename); + return ""; + } + + FILE* fp = fopen(filename, "wb"); + if (!fp) { + fprintf(stderr, "Failed to open %s for output\n", filename); + return ""; + } + + size_t bytes_written = + fwrite(&png_encoding.front(), 1, png_encoding.size(), fp); + if (bytes_written != png_encoding.size()) + fprintf(stderr, "Failed to write to %s\n", filename); + + (void)fclose(fp); + return std::string(filename); +} + +#ifdef _WIN32 +std::string WriteBmp(const char* pdf_name, + int num, + const void* buffer, + int stride, + int width, + int height) { + if (!CheckDimensions(stride, width, height)) + return ""; + + int out_len = stride * height; + if (out_len > INT_MAX / 3) + return ""; + + char filename[256]; + snprintf(filename, sizeof(filename), "%s.%d.bmp", pdf_name, num); + FILE* fp = fopen(filename, "wb"); + if (!fp) + return ""; + + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(bmi) - sizeof(RGBQUAD); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = -height; // top-down image + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = 0; + + BITMAPFILEHEADER file_header = {}; + file_header.bfType = 0x4d42; + file_header.bfSize = sizeof(file_header) + bmi.bmiHeader.biSize + out_len; + file_header.bfOffBits = file_header.bfSize - out_len; + + if (fwrite(&file_header, sizeof(file_header), 1, fp) != 1 || + fwrite(&bmi, bmi.bmiHeader.biSize, 1, fp) != 1 || + fwrite(buffer, out_len, 1, fp) != 1) { + fprintf(stderr, "Failed to write to %s\n", filename); + } + fclose(fp); + return std::string(filename); +} + +void WriteEmf(FPDF_PAGE page, const char* pdf_name, int num) { + char filename[256]; + snprintf(filename, sizeof(filename), "%s.%d.emf", pdf_name, num); + + HDC dc = CreateEnhMetaFileA(nullptr, filename, nullptr, nullptr); + + int width = static_cast<int>(FPDF_GetPageWidth(page)); + int height = static_cast<int>(FPDF_GetPageHeight(page)); + HRGN rgn = CreateRectRgn(0, 0, width, height); + SelectClipRgn(dc, rgn); + DeleteObject(rgn); + + SelectObject(dc, GetStockObject(NULL_PEN)); + SelectObject(dc, GetStockObject(WHITE_BRUSH)); + // If a PS_NULL pen is used, the dimensions of the rectangle are 1 pixel less. + Rectangle(dc, 0, 0, width + 1, height + 1); + + FPDF_RenderPage(dc, page, 0, 0, width, height, 0, + FPDF_ANNOT | FPDF_PRINTING | FPDF_NO_CATCH); + + DeleteEnhMetaFile(CloseEnhMetaFile(dc)); +} + +void WritePS(FPDF_PAGE page, const char* pdf_name, int num) { + char filename[256]; + snprintf(filename, sizeof(filename), "%s.%d.ps", pdf_name, num); + FILE* fp = fopen(filename, "wb"); + if (!fp) + return; + + HDC dc = CreateEnhMetaFileA(nullptr, nullptr, nullptr, nullptr); + + int width = static_cast<int>(FPDF_GetPageWidth(page)); + int height = static_cast<int>(FPDF_GetPageHeight(page)); + FPDF_RenderPage(dc, page, 0, 0, width, height, 0, + FPDF_ANNOT | FPDF_PRINTING | FPDF_NO_CATCH); + + HENHMETAFILE emf = CloseEnhMetaFile(dc); + std::vector<const ENHMETARECORD*> items; + EnumEnhMetaFile(nullptr, emf, &EnhMetaFileProc, &items, nullptr); + for (const ENHMETARECORD* record : items) { + if (record->iType != EMR_GDICOMMENT) + continue; + + const auto* comment = reinterpret_cast<const EMRGDICOMMENT*>(record); + const char* data = reinterpret_cast<const char*>(comment->Data); + uint16_t size = *reinterpret_cast<const uint16_t*>(data); + if (fwrite(data + sizeof(uint16_t), size, 1, fp) != 1) { + fprintf(stderr, "Failed to write to %s\n", filename); + break; + } + } + fclose(fp); + DeleteEnhMetaFile(emf); +} +#endif // _WIN32 + +#ifdef PDF_ENABLE_SKIA +std::string WriteSkp(const char* pdf_name, + int num, + SkPictureRecorder* recorder) { + char filename[256]; + int chars_formatted = + snprintf(filename, sizeof(filename), "%s.%d.skp", pdf_name, num); + + if (chars_formatted < 0 || + static_cast<size_t>(chars_formatted) >= sizeof(filename)) { + fprintf(stderr, "Filename %s is too long\n", filename); + return ""; + } + + sk_sp<SkPicture> picture(recorder->finishRecordingAsPicture()); + SkFILEWStream wStream(filename); + picture->serialize(&wStream); + return std::string(filename); +} +#endif + +void WriteAttachments(FPDF_DOCUMENT doc, const std::string& name) { + for (int i = 0; i < FPDFDoc_GetAttachmentCount(doc); ++i) { + FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(doc, i); + + // Retrieve the attachment file name. + std::string attachment_name; + unsigned long len = FPDFAttachment_GetName(attachment, nullptr, 0); + if (len) { + std::vector<char> buf(len); + unsigned long actual_len = + FPDFAttachment_GetName(attachment, buf.data(), len); + if (actual_len == len) { + attachment_name = + GetPlatformString(reinterpret_cast<unsigned short*>(buf.data())); + } + } + if (attachment_name.empty()) { + fprintf(stderr, "Attachment #%d has an empty file name.\n", i + 1); + continue; + } + + // Calculate the full attachment file name. + char save_name[256]; + int chars_formatted = + snprintf(save_name, sizeof(save_name), "%s.attachment.%s", name.c_str(), + attachment_name.c_str()); + if (chars_formatted < 0 || + static_cast<size_t>(chars_formatted) >= sizeof(save_name)) { + fprintf(stderr, "Filename %s is too long\n", save_name); + continue; + } + + // Retrieve the attachment. + len = FPDFAttachment_GetFile(attachment, nullptr, 0); + std::vector<char> data_buf(len); + if (len) { + unsigned long actual_len = + FPDFAttachment_GetFile(attachment, data_buf.data(), len); + if (actual_len != len) + data_buf.clear(); + } + if (data_buf.empty()) { + fprintf(stderr, "Attachment \"%s\" is empty.\n", attachment_name.c_str()); + continue; + } + + // Write the attachment file. + FILE* fp = fopen(save_name, "wb"); + if (!fp) { + fprintf(stderr, "Failed to open %s for saving attachment.\n", save_name); + continue; + } + + size_t written_len = fwrite(data_buf.data(), 1, len, fp); + if (written_len == len) { + fprintf(stderr, "Saved attachment \"%s\" as: %s.\n", + attachment_name.c_str(), save_name); + } else { + fprintf(stderr, "Failed to write to %s\n", save_name); + } + fclose(fp); + } +} + +void WriteImages(FPDF_PAGE page, const char* pdf_name, int page_num) { + for (int i = 0; i < FPDFPage_CountObjects(page); ++i) { + FPDF_PAGEOBJECT obj = FPDFPage_GetObject(page, i); + if (FPDFPageObj_GetType(obj) != FPDF_PAGEOBJ_IMAGE) + continue; + + std::unique_ptr<void, FPDFBitmapDeleter> bitmap( + FPDFImageObj_GetBitmap(obj)); + if (!bitmap) { + fprintf(stderr, "Image object #%d on page #%d has an empty bitmap.\n", + i + 1, page_num + 1); + continue; + } + + int format = FPDFBitmap_GetFormat(bitmap.get()); + if (format == FPDFBitmap_Unknown) { + fprintf(stderr, + "Image object #%d on page #%d has a bitmap of unknown format.\n", + i + 1, page_num + 1); + continue; + } + + std::vector<unsigned char> png_encoding; + const unsigned char* buffer = + static_cast<const unsigned char*>(FPDFBitmap_GetBuffer(bitmap.get())); + int width = FPDFBitmap_GetWidth(bitmap.get()); + int height = FPDFBitmap_GetHeight(bitmap.get()); + int stride = FPDFBitmap_GetStride(bitmap.get()); + bool ret = false; + switch (format) { + case FPDFBitmap_Gray: + ret = image_diff_png::EncodeGrayPNG(buffer, width, height, stride, + &png_encoding); + break; + case FPDFBitmap_BGR: + ret = image_diff_png::EncodeBGRPNG(buffer, width, height, stride, + &png_encoding); + break; + case FPDFBitmap_BGRx: + ret = image_diff_png::EncodeBGRAPNG(buffer, width, height, stride, true, + &png_encoding); + break; + case FPDFBitmap_BGRA: + ret = image_diff_png::EncodeBGRAPNG(buffer, width, height, stride, + false, &png_encoding); + break; + default: + NOTREACHED(); + } + if (!ret) { + fprintf(stderr, + "Failed to convert image object #%d on page #%d to png.\n", i + 1, + page_num + 1); + continue; + } + + char filename[256]; + int chars_formatted = snprintf(filename, sizeof(filename), "%s.%d.%d.png", + pdf_name, page_num, i); + if (chars_formatted < 0 || + static_cast<size_t>(chars_formatted) >= sizeof(filename)) { + fprintf(stderr, "Filename %s for saving image is too long\n", filename); + continue; + } + + FILE* fp = fopen(filename, "wb"); + if (!fp) { + fprintf(stderr, "Failed to open %s for saving image.\n", filename); + continue; + } + + size_t bytes_written = + fwrite(&png_encoding.front(), 1, png_encoding.size(), fp); + if (bytes_written != png_encoding.size()) + fprintf(stderr, "Failed to write to %s.\n", filename); + else + fprintf(stderr, "Successfully wrote embedded image %s.\n", filename); + + (void)fclose(fp); + } +} |