diff --git a/.gitignore b/.gitignore index ec396240..e0a243b6 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,10 @@ !/Examples/*.cpp !/Examples/external !/Examples/external/* +!/Examples/files +!/Examples/files/* +!/Examples/images +!/Examples/images/* # include gnu-make-crutch folder !/gnu-make-crutch diff --git a/Examples/CMakeLists.txt b/Examples/CMakeLists.txt index 29ad43ad..9c1ccdfd 100644 --- a/Examples/CMakeLists.txt +++ b/Examples/CMakeLists.txt @@ -87,3 +87,9 @@ target_link_libraries(Demo9 PRIVATE OpenXLSX::OpenXLSX) #======================================================================================================================= add_executable(Demo10 Demo10.cpp) target_link_libraries(Demo10 PRIVATE OpenXLSX::OpenXLSX) + +#======================================================================================================================= +# Define Demo11 target +#======================================================================================================================= +add_executable(Demo11 Demo11.cpp) +target_link_libraries(Demo11 PRIVATE OpenXLSX::OpenXLSX) diff --git a/Examples/Demo1.cpp b/Examples/Demo1.cpp index 8c0f244f..b3c51447 100644 --- a/Examples/Demo1.cpp +++ b/Examples/Demo1.cpp @@ -13,7 +13,7 @@ using namespace OpenXLSX; void printAllDocumentComments(XLDocument const & doc) { for( size_t i = 1; i <= doc.workbook().worksheetCount(); ++i ) { - auto wks = doc.workbook().worksheet(i); + auto wks = doc.workbook().worksheet(static_cast(i)); if( wks.hasComments() ) { std::cout << "worksheet(" << i << ") with name \"" << wks.name() << "\" has comments" << std::endl; XLComments wksComments = wks.comments(); @@ -114,7 +114,7 @@ int main() // date/time data. See https://en.cppreference.com/w/cpp/chrono/c/tm for more information. // An XLDateTime object can be created from a std::tm object: - std::tm tm; + std::tm tm = {}; // Initialize all members to zero tm.tm_year = 121; tm.tm_mon = 8; tm.tm_mday = 1; diff --git a/Examples/Demo11.cpp b/Examples/Demo11.cpp new file mode 100644 index 00000000..df769d3d --- /dev/null +++ b/Examples/Demo11.cpp @@ -0,0 +1,1006 @@ +/* + * Demo11.cpp - Comprehensive Image Embedding Test Matrix + * + * This demo tests various combinations of image embedding options: + * - Anchor types: oneCellAnchor vs twoCellAnchor + * - File formats: PNG, JPEG, GIF, BMP + * - Sizing strategies: aspect ratio preservation, original size, exact dimensions + * - Units: pixels, EMUs, cell spacing + */ + +#include +#include +#include +#include +#include +#include "OpenXLSX.hpp" +#include "XLImage.hpp" // For XLImageUtils + +using namespace OpenXLSX; + +/** + * @brief Search upward through directory tree to find a directory + * @param relativePath The relative path to search for + * @return The full path to the directory if found, empty string if not found + */ +std::string findDirectory(const std::string& relativePath) +{ + const std::filesystem::path currentPath = std::filesystem::current_path(); + std::filesystem::path testPath = currentPath; + std::string result; + + // Search upward through the directory tree + for (int levels = 0; (levels < 10) && result.empty(); ++levels) { + // Append the relative path + const std::filesystem::path testImagePath = testPath / relativePath; + + // Check if the directory exists + if (std::filesystem::exists(testImagePath) && + std::filesystem::is_directory(testImagePath)) { + result = testImagePath.string(); + } + else{ + testPath = testPath.parent_path(); + } + } + + return result; // Not found +} + + + +/* + a. get image ID + b. get relationshiop ID + c. get anchor cell location(s) + d. get image width & height in pixels + e. get image displayed width and displayed height in EMUs + +Return image information in the following format +"img_id:ABCD r_id:EFGH oneCellAnchor:C44 330x492 px 1249900x1700000 emu" +*/ +std::string embeddedImageStr(const XLEmbeddedImage& embImage) { + std::string result; + + // a. get image ID + std::string imageId = embImage.id(); + result += "img_id:" + imageId; + + // b. get relationship ID + result += " r_id:" + embImage.relationshipId(); + + // c. get anchor type and cell location + const XLImageAnchor& imageAnchor = embImage.getImageAnchor(); + std::string anchorCell = XLEmbeddedImage::rowColToCellRef(imageAnchor.fromRow, imageAnchor.fromCol); + result += " " + XLImageAnchorUtils::anchorTypeToString(imageAnchor.anchorType) + ":" + anchorCell; + + // d. get image width & height in pixels + result += " " + std::to_string(embImage.widthPixels()) + "x" + std::to_string(embImage.heightPixels()) + " px"; + + // e. get image displayed width and displayed height in EMUs + result += " " + std::to_string(embImage.displayWidthEMUs()) + "x" + std::to_string(embImage.displayHeightEMUs()) + " EMU"; + + // f. additional debugging info + if (embImage.getImage()) { + result += " package:" + embImage.getImage()->filePackagePath(); + result += " mime:" + XLImageUtils::mimeTypeToString(embImage.mimeType()); + } else { + result += " [NO_IMAGE_OBJECT]"; + } + + return result; +} + +// Helper function to print embedded images for a worksheet +void printEmbeddedImages(const XLWorksheet& worksheet, const std::string& sheetName, int* totalImages = nullptr) { + try { + const auto& embImages = worksheet.getEmbImages(); + size_t imageCount = embImages.size(); + + // Update total count if pointer provided + if (totalImages != nullptr) { + *totalImages += static_cast(imageCount); + } + + // Print header + const OpenXLSX::XLDrawingML& drawing = worksheet.drawingML(); + const size_t drawingImageCount = drawing.imageCount(); + std::cout << " Worksheet '" << sheetName << "': " << imageCount << " image(s) " + << imageCount << " embedded images" + << " drawing valid:" << (drawing.valid() ? "Yes" : "No") + << " drawing images:" << drawingImageCount + << std::endl; + + // Print image details if any images exist + if (imageCount > 0) { + for (size_t i = 0; i < embImages.size(); ++i) { + std::cout << " " << (i + 1) << ". " << embeddedImageStr(embImages[i]) << std::endl; + } + } + } catch (const std::exception& e) { + std::cout << "Error getting image info for worksheet '" << sheetName << "': " << e.what() << std::endl; + } +} + +// Helper function to print embedded image information for all worksheets +void printAllEmbeddedImages(const XLDocument& doc, int* totalImages = nullptr) { + std::cout << "\n=== EMBEDDED IMAGES ===" << std::endl; + + int totalCount = 0; + if (totalImages != nullptr) { + *totalImages = 0; + } + + // Iterate through all worksheets using workbook + auto workbook = doc.workbook(); + auto sheetNames = workbook.worksheetNames(); + + for (const auto& sheetName : sheetNames) { + try { + XLWorksheet worksheet = workbook.worksheet(sheetName); + printEmbeddedImages(worksheet, sheetName, &totalCount); + } catch (const std::exception& e) { + std::cout << "Error accessing worksheet '" << sheetName << "': " << e.what() << std::endl; + } + } + + std::cout << "\nTotal embedded images across all worksheets: " << totalCount << std::endl; + if (totalImages != nullptr) { + *totalImages = totalCount; + } +} + +// Comprehensive cross-reference verification function +void verifyImageConsistency(const XLDocument& doc) { + std::cout << "\n=== IMAGE CONSISTENCY VERIFICATION ===" << std::endl; + + int totalErrors = 0; + + // Iterate through all worksheets using workbook + auto workbook = doc.workbook(); + auto sheetNames = workbook.worksheetNames(); + + for (const auto& sheetName : sheetNames) { + try { + XLWorksheet worksheet = workbook.worksheet(sheetName); + std::cout << "\n--- Verifying worksheet: " << sheetName << " ---" << std::endl; + + // Get embedded images data + const auto& embImages = worksheet.getEmbImages(); + + std::cout << "EmbeddedImages count: " << embImages.size() << std::endl; + + // Check count consistency (no longer needed since we only have one source) + std::cout << "Embedded images verification complete." << std::endl; + + // Cross-reference each image + size_t minCount = embImages.size(); + for (size_t i = 0; i < minCount; ++i) { + const auto& embImage = embImages[i]; + + std::cout << " Image " << (i+1) << ":" << std::endl; + + // Check image ID consistency (no longer needed since we only have one source) + std::cout << " Image ID: " << embImage.id() << " ✓" << std::endl; + + // Check relationship ID consistency + std::cout << " Relationship ID: " << embImage.relationshipId() << " ✓" << std::endl; + + // Check anchor cell consistency + std::cout << " Anchor Cell: " << embImage.anchorCell() << " ✓" << std::endl; + + // Check anchor type consistency + std::cout << " Anchor Type: " << XLImageAnchorUtils::anchorTypeToString( + embImage.anchorType()) << " ✓" << std::endl; + + // Check dimensions consistency + std::cout << " Dimensions: " << embImage.widthPixels() << "x" + << embImage.heightPixels() << " px ✓" << std::endl; + + // Check display dimensions consistency + std::cout << " Display Dimensions: " << embImage.displayWidthEMUs() << "x" + << embImage.displayHeightEMUs() << " EMUs ✓" << std::endl; + + std::cout << " Summary: " << embeddedImageStr(embImage) << std::endl; + } + + // Run built-in verifyData + std::string verifyMsg; + int verifyErrors = worksheet.verifyData(&verifyMsg); + if (verifyErrors > 0) { + std::cout << "Built-in verifyData found " << verifyErrors << " errors:" << std::endl; + std::cout << verifyMsg << std::endl; + totalErrors += verifyErrors; + } else { + std::cout << "Built-in verifyData: No errors ✓" << std::endl; + } + + } catch (const std::exception& e) { + std::cout << "Error verifying worksheet '" << sheetName << "': " << e.what() << std::endl; + totalErrors++; + } + } + + std::cout << "\n=== VERIFICATION SUMMARY ===" << std::endl; + std::cout << "Total errors found: " << totalErrors << std::endl; + if (totalErrors == 0) { + std::cout << "All image data is consistent! ✓" << std::endl; + } else { + std::cout << "Image data inconsistencies detected!" << std::endl; + } +} + +// Helper function to print detailed image analysis for a worksheet +void printDetailedImageAnalysis(const XLWorksheet& worksheet, const std::string& sheetName) { + int imageCount = static_cast(worksheet.imageCount()); + std::cout << " Worksheet '" << sheetName << "' imageCount: " << imageCount << std::endl; + + if (imageCount > 0) { + try { + const auto& embImages = worksheet.getEmbImages(); + std::vector anchorCells; + std::vector imageIds; + std::vector relationshipIds; + for (size_t i = 0; i < embImages.size(); ++i) { + std::cout << " " << (i + 1) << ". " << embeddedImageStr(embImages[i]) << std::endl; + anchorCells.push_back(embImages[i].anchorCell()); + imageIds.push_back(embImages[i].id()); + relationshipIds.push_back(embImages[i].relationshipId()); + } + + // Test getEmbImagesAtCell() + for( const std::string& anchorCell : anchorCells ){ + const auto& cellImgs = worksheet.getEmbImagesAtCell(anchorCell); + std::cout << " getEmbImagesAtCell('" << anchorCell << "') returned " + << cellImgs.size() << " image(s)" << std::endl; + } + + // Test getImageInfoByImageID() + for( const std::string& imageId : imageIds ){ + const auto& foundImg = worksheet.getEmbImageByImageID(imageId); + if (foundImg.isValid()) { + std::cout << " getImageInfoByImageID( " << imageId << ") found image" << std::endl; + } else { + std::cout << " getImageInfoByImageID( " << imageId << ") failed to find image" << std::endl; + } + } + + // Test getImageInfoByRelationshipId() + for( const std::string& relationshipId : relationshipIds ){ + const auto& foundRel = worksheet.getEmbImageByRelationshipId(relationshipId); + if (foundRel.isValid()) { + std::cout << " ✓ getImageInfoByRelationshipId( " << relationshipId << ") found the image" << std::endl; + } else { + std::cout << " ✗ getImageInfoByRelationshipId( " << relationshipId << ") failed to find the image" << std::endl; + } + } + + // Test getEmbImagesInRange() + const auto& rangeImgs = worksheet.getEmbImagesInRange("A1:Z100"); + std::cout << " getEmbImagesInRange('A1:Z100') returned " << rangeImgs.size() << " image(s)" << std::endl; + + // Test validation + bool isRegstryValid = worksheet.validateImageRegistry(); + std::cout << " validateImageRegistry() returned: " << (isRegstryValid ? "Valid" : "Invalid") << std::endl; + + } catch (const std::exception& e) { + std::cout << " Error getting image info: " << e.what() << std::endl; + } + } +} + +// Data integrity verification functions - checking for data integrity can sometimes be expensive, +// but is handy for debugging +void verifyDocData(const XLDocument& doc) +{ + std::string dbgMsg; + int errorCount = doc.verifyData(&dbgMsg); + if (errorCount > 0 || !dbgMsg.empty()) { + std::cout << "ERROR: Document data integrity check failed with " << errorCount + << " error(s): " << dbgMsg << std::endl; + } +} + +void verifyWorksheetData(const XLWorksheet& worksheet) +{ + std::string dbgMsg; + int errorCount = worksheet.verifyData(&dbgMsg); + if (errorCount > 0 || !dbgMsg.empty()) { + std::cout << "ERROR: Worksheet '" << worksheet.name() << "' data integrity check failed with " << errorCount + << " error(s): " << dbgMsg << std::endl; + } +} + + + +int main() +{ + std::cout << "OpenXLSX Image Test" << std::endl; + std::cout << "===================" << std::endl; + std::cout << std::endl; + + // Error tracking + int errorCount = 0; + + // ======================================== + // I. CREATE NEW DOCUMENT + // ======================================== + std::cout << "I. CREATE NEW DOCUMENT" << std::endl; + std::cout << "======================" << std::endl; + + // Create a new workbook + XLDocument doc; + const std::string xlsxFileNameA = "Demo11A.xlsx"; + doc.create(xlsxFileNameA); + auto wb = doc.workbook(); + + // Create test worksheets + wb.addWorksheet("Anchor Types"); + XLWorksheet anchorSheet = wb.worksheet("Anchor Types"); + wb.addWorksheet("File Formats"); + XLWorksheet formatSheet = wb.worksheet("File Formats"); + wb.addWorksheet("Sizing Strategies"); + XLWorksheet sizingSheet = wb.worksheet("Sizing Strategies"); + wb.addWorksheet("Units & Scaling"); + XLWorksheet unitsSheet = wb.worksheet("Units & Scaling"); + wb.addWorksheet("File Loading"); + XLWorksheet fileSheet = wb.worksheet("File Loading"); + + // 1. Sheet 1 (empty) + std::cout << "1. Sheet 1 (empty)" << std::endl; + // Sheet1 is created by default and remains empty + + // 2. Sheet 2: Anchor Types (oneCellAnchor vs twoCellAnchor) + std::cout << "2. Sheet 2: Anchor Types (oneCellAnchor vs twoCellAnchor)" << std::endl; + + // Test 1: oneCellAnchor vs twoCellAnchor with same image + + // Add headers + anchorSheet.cell("A1").value() = "oneCellAnchor (PNG, preserve aspect ratio within 2x2 cell height bounding box)"; + anchorSheet.cell("A7").value() = "twoCellAnchor (PNG, 4x3 cells)"; + anchorSheet.cell("A13").value() = "oneCellAnchor (JPEG, original size)"; + anchorSheet.cell("A19").value() = "twoCellAnchor (JPEG, 3x2 cells)"; + + // Add images + XLImageAnchor imageAnchor1; + imageAnchor1.initOneCell( XLCellReference("A2") ); + imageAnchor1.setDisplaySizeWithAspectRatio(XLImageUtils::png31x15Data, XLMimeType::PNG, + 2 * EXCEL_CELL_Y_SPACING_EMUS, 2 * EXCEL_CELL_Y_SPACING_EMUS); + const std::string imageId1 = anchorSheet.embedImageFromImageData(imageAnchor1, + XLImageUtils::png31x15Data, XLMimeType::PNG ); + bool success1 = !imageId1.empty(); + std::cout << " Added oneCellAnchor PNG: " << (success1 ? "Success" : "Failed") << std::endl; + if (!success1) ++errorCount; + + // Add twoCellAnchor image + XLImageAnchor imageAnchor2; + imageAnchor2.initTwoCell( XLCellReference("A8"), XLCellReference("E11") ); + const std::string imageId2 = anchorSheet.embedImageFromImageData(imageAnchor2, + XLImageUtils::png31x15Data, XLMimeType::PNG ); + bool success2 = !imageId2.empty(); + std::cout << " Added twoCellAnchor PNG: " << (success2 ? "Success" : "Failed") << std::endl; + if (!success2) ++errorCount; + + // Add more twoCellAnchor tests + const std::string imageId3 = anchorSheet.embedImageFromImageData(XLCellReference("A14" ), + XLImageUtils::jpeg32x18Data, XLMimeType::JPEG ); + bool success3 = !imageId3.empty(); + std::cout << " Added oneCellAnchor JPEG: " << (success3 ? "Success" : "Failed") << std::endl; + if (!success3) ++errorCount; + + XLImageAnchor imageAnchor4; + imageAnchor4.initTwoCell( XLCellReference("A20"), XLCellReference("D22") ); + const std::string imageId4 = anchorSheet.embedImageFromImageData(imageAnchor4, + XLImageUtils::jpeg32x18Data, XLMimeType::JPEG ); + bool success4 = !imageId4.empty(); + std::cout << " Added twoCellAnchor JPEG: " << (success4 ? "Success" : "Failed") << std::endl; + if (!success4) ++errorCount; + + // Check data integrity -- typically only done for debugging + verifyWorksheetData(anchorSheet); + + // 3. Sheet 3: File Formats (PNG, JPEG, GIF, BMP) + std::cout << "3. Sheet 3: File Formats (PNG, JPEG, GIF, BMP)" << std::endl; + + // Test different file formats + static const std::vector, std::string, XLMimeType>> formats = { + {XLImageUtils::png31x15Data, "PNG", XLMimeType::PNG}, + {XLImageUtils::jpeg32x18Data, "JPEG", XLMimeType::JPEG}, + {XLImageUtils::gif26x16Data, "GIF", XLMimeType::GIF}, + {XLImageUtils::bmp33x16Data, "BMP", XLMimeType::BMP} + }; + + for (size_t i = 0; i < formats.size(); ++i) { + std::string textCell = "A" + std::to_string(1 + i * 6); + std::string imageCell = "A" + std::to_string(2 + i * 6); + formatSheet.cell(textCell).value() = std::get<1>(formats[i]) + " Format Test"; + XLImageAnchor imageAnchor; + imageAnchor.initOneCell( XLCellReference(imageCell) ); + imageAnchor.setDisplaySizeWithAspectRatio(std::get<0>(formats[i]), std::get<2>(formats[i]), + 4 * EXCEL_CELL_Y_SPACING_EMUS, 2 * EXCEL_CELL_Y_SPACING_EMUS); + const std::string imageId = formatSheet.embedImageFromImageData(imageAnchor, + std::get<0>(formats[i]), std::get<2>(formats[i]) ); + bool success = !imageId.empty(); + std::cout << " Added " << std::get<1>(formats[i]) << " image: " << (success ? "Success" : "Failed") << std::endl; + if (!success) ++errorCount; + } + + // Check data integrity -- typically only done for debugging + verifyWorksheetData(formatSheet); + + // 4. Sheet 4: Sizing Strategy (aspect ratio, original, exact) + std::cout << "4. Sheet 4: Sizing Strategy (aspect ratio, original, exact)" << std::endl; + + // Test different sizing strategies + // Strategy 1: Preserve aspect ratio + bounding box + sizingSheet.cell("A1").value() = "Aspect Ratio + Bounding Box (7x3 cell height units)"; + XLImageAnchor sizingImageAnchor; + sizingImageAnchor.initOneCell( XLCellReference("A2") ); + sizingImageAnchor.setDisplaySizeWithAspectRatio(XLImageUtils::png31x15Data, XLMimeType::PNG, + 7 * EXCEL_CELL_Y_SPACING_EMUS, 3 * EXCEL_CELL_Y_SPACING_EMUS); + const std::string imageId3a = sizingSheet.embedImageFromImageData(sizingImageAnchor, + XLImageUtils::png31x15Data, XLMimeType::PNG ); + bool success3a = !imageId3a.empty(); + std::cout << " Aspect ratio + bounding box: " << (success3a ? "Success" : "Failed") << std::endl; + if (!success3a) ++errorCount; + + // Strategy 2: Original size (no scaling) + // Don't call setDisplaySize - use original dimensions + sizingSheet.cell("A7").value() = "Original Size (no scaling)"; + const std::string imageId3b = sizingSheet.embedImageFromImageData(XLCellReference("A8" ), + XLImageUtils::png31x15Data, XLMimeType::PNG ); + bool success3b = !imageId3b.empty(); + std::cout << " Original size: " << (success3b ? "Success" : "Failed") << std::endl; + if (!success3b) ++errorCount; + + // Strategy 3: Force exact dimensions + sizingSheet.cell("A13").value() = "Exact Dimensions (11x3 cell height units, may distort)"; + XLImageAnchor exactImageAnchor; + exactImageAnchor.initOneCell( XLCellReference("A14") ); + exactImageAnchor.displayWidthEMUs = 11 * EXCEL_CELL_Y_SPACING_EMUS; + exactImageAnchor.displayHeightEMUs = 3 * EXCEL_CELL_Y_SPACING_EMUS; + const std::string imageId3c = sizingSheet.embedImageFromImageData(exactImageAnchor, + XLImageUtils::png31x15Data, XLMimeType::PNG ); + bool success3c = !imageId3c.empty(); + std::cout << " Exact dimensions: " << (success3c ? "Success" : "Failed") << std::endl; + if (!success3c) ++errorCount; + + // Check data integrity -- typically only done for debugging + verifyWorksheetData(sizingSheet); + + // 5. Sheet 5: Units & Scaling Testing (pixels, EMUs, cells) + std::cout << "5. Sheet 5: Units & Scaling Testing" << std::endl; + + // Test different unit systems + + // Pixels + unitsSheet.cell("A1").value() = "Pixel-based (100x50 pixels)"; + XLImageAnchor pixelUnitsImageAnchor; + pixelUnitsImageAnchor.initOneCell( XLCellReference("A2") ); + const uint32_t maxWidthEmus = XLImageUtils::pixelsToEmus(100); + const uint32_t maxHeightEmus = XLImageUtils::pixelsToEmus(50); + pixelUnitsImageAnchor.setDisplaySizeWithAspectRatio(XLImageUtils::png31x15Data, XLMimeType::PNG, + maxWidthEmus, maxHeightEmus); + const std::string imageId4a = unitsSheet.embedImageFromImageData(pixelUnitsImageAnchor, + XLImageUtils::png31x15Data, XLMimeType::PNG ); + bool success4a = !imageId4a.empty(); + std::cout << " Pixel-based sizing: " << (success4a ? "Success" : "Failed") << std::endl; + if (!success4a) ++errorCount; + + // EMUs + unitsSheet.cell("A7").value() = "EMU-based (476250x238125 EMUs)"; + XLImageAnchor emuUnitsImageAnchor; + emuUnitsImageAnchor.initOneCell( XLCellReference("A8") ); + emuUnitsImageAnchor.displayWidthEMUs = 476250; + emuUnitsImageAnchor.displayHeightEMUs = 238125; + const std::string imageId4b = unitsSheet.embedImageFromImageData(emuUnitsImageAnchor, + XLImageUtils::png31x15Data, XLMimeType::PNG ); + bool success4b = !imageId4b.empty(); + std::cout << " EMU-based sizing: " << (success4b ? "Success" : "Failed") << std::endl; + if (!success4b) ++errorCount; + + // Cell spacing + unitsSheet.cell("A13").value() = "Cell-based (19x5 cell height units) -- preserve aspect ratio"; + XLImageAnchor cellSpacingImageAnchor; + cellSpacingImageAnchor.initOneCell( XLCellReference("A14") ); + cellSpacingImageAnchor.setDisplaySizeWithAspectRatio(XLImageUtils::png31x15Data, XLMimeType::PNG, + 19 * EXCEL_CELL_Y_SPACING_EMUS, 5 * EXCEL_CELL_Y_SPACING_EMUS); + const std::string imageId4c = unitsSheet.embedImageFromImageData(cellSpacingImageAnchor, + XLImageUtils::png31x15Data, XLMimeType::PNG ); + bool success4c = !imageId4c.empty(); + std::cout << " Cell-based sizing: " << (success4c ? "Success" : "Failed") << std::endl; + if (!success4c) ++errorCount; + + // Check data integrity -- typically only done for debugging + verifyWorksheetData(unitsSheet); + + // 6. Sheet 6: Loading Images from Disk Files + std::cout << "6. Sheet 6: Loading Images from Disk Files" << std::endl; + + // Find the images directory by searching upward through the directory tree + std::string imageDir = findDirectory("OpenXLSX/Examples/images/"); + if (imageDir.empty()) { + std::cout << " ERROR: Could not find images directory!" << std::endl; + std::cout << " Searched for: OpenXLSX/Examples/images/ in current " + "directory and up to 10 levels up" << std::endl; + ++errorCount; + } else { + std::cout << " Found images directory: " << imageDir << std::endl; + } + + // Only proceed with file loading tests if we found the images directory + if (!imageDir.empty()) { + // Image 1: PNG - Original size + std::string pngPath = imageDir + "tiny_png.png"; + fileSheet.cell("A1").value() = "PNG - Original Size"; + const std::string imageIdFile1 = + fileSheet.embedImageFromFile(XLCellReference("A2"), pngPath ); + bool fileSuccess1 = !imageIdFile1.empty(); + std::cout << " PNG original size: " << (fileSuccess1 ? "Success" : "Failed") << std::endl; + if (!fileSuccess1) ++errorCount; + + // Image 2: JPEG - Aspect ratio preserved (2x1 cells) + std::string jpegPath = imageDir + "tiny_jpeg.jpg"; + fileSheet.cell("A7").value() = "JPEG - Aspect Ratio (2x1 cells)"; + XLImageAnchor jpegImageAnchor; + jpegImageAnchor.initOneCell( XLCellReference("A8") ); + jpegImageAnchor.setDisplaySizeWithAspectRatio(jpegPath, XLMimeType::JPEG, + 2 * EXCEL_CELL_Y_SPACING_EMUS, 1 * EXCEL_CELL_Y_SPACING_EMUS); + const std::string imageIdFile2 = fileSheet.embedImageFromFile(jpegImageAnchor, + jpegPath, XLMimeType::JPEG ); + bool fileSuccess2 = !imageIdFile2.empty(); + std::cout << " JPEG aspect ratio: " << (fileSuccess2 ? "Success" : "Failed") << std::endl; + if (!fileSuccess2) ++errorCount; + + // Image 3: GIF - Exact dimensions (may distort) + std::string gifPath = imageDir + "tiny_gif.gif"; + fileSheet.cell("A13").value() = "GIF - Exact Dimensions (3x2 cells)"; + XLImageAnchor gifImageAnchor; + gifImageAnchor.initOneCell( XLCellReference("A14") ); + gifImageAnchor.displayWidthEMUs = 3 * EXCEL_CELL_Y_SPACING_EMUS; + gifImageAnchor.displayHeightEMUs = 2 * EXCEL_CELL_Y_SPACING_EMUS; + const std::string imageIdFile3 = fileSheet.embedImageFromFile(gifImageAnchor, + gifPath, XLMimeType::GIF ); + bool fileSuccess3 = !imageIdFile3.empty(); + std::cout << " GIF exact dimensions: " << (fileSuccess3 ? "Success" : "Failed") << std::endl; + if (!fileSuccess3) ++errorCount; + + // Image 4: BMP - Two-cell anchor spanning multiple cells + std::string bmpPath = imageDir + "tiny_bmp.bmp"; + fileSheet.cell("A19").value() = "BMP - Two-Cell Anchor (A20:D23)"; + XLImageAnchor bmpImageAnchor; + bmpImageAnchor.initTwoCell( XLCellReference("A20"), XLCellReference("D23") ); + bmpImageAnchor.setDisplaySizeWithAspectRatio(bmpPath, XLMimeType::BMP, + 3 * EXCEL_CELL_Y_SPACING_EMUS, 2 * EXCEL_CELL_Y_SPACING_EMUS); + const std::string imageIdFile4 = fileSheet.embedImageFromFile(bmpImageAnchor, + bmpPath, XLMimeType::BMP ); + bool fileSuccess4 = !imageIdFile4.empty(); + std::cout << " BMP two-cell anchor: " << (fileSuccess4 ? "Success" : "Failed") << std::endl; + if (!fileSuccess4) ++errorCount; + + // Image 5: PNG - Precise positioning with offset (using previously unused embedImageWithOffset) + std::string pngPath2 = imageDir + "tiny_png.png"; + fileSheet.cell("A25").value() = "PNG - Precise Offset (A26 + 50% cell height offset)"; + XLImageAnchor pngImageAnchor2; + pngImageAnchor2.initOneCell( XLCellReference("A26") ); + pngImageAnchor2.setDisplaySizeWithAspectRatio(pngPath2, XLMimeType::PNG, + 3 * EXCEL_CELL_Y_SPACING_EMUS, 2 * EXCEL_CELL_Y_SPACING_EMUS); + pngImageAnchor2.fromColOffset = EXCEL_CELL_Y_SPACING_EMUS / 2; // 50% of cell height + pngImageAnchor2.fromRowOffset = EXCEL_CELL_Y_SPACING_EMUS / 2; // 50% of cell height + const std::string imageIdFile5 = fileSheet.embedImageFromFile(pngImageAnchor2, + pngPath2, XLMimeType::PNG ); + bool fileSuccess5 = !imageIdFile5.empty(); + std::cout << " PNG with offset: " << (fileSuccess5 ? "Success" : "Failed") << std::endl; + if (!fileSuccess5) ++errorCount; + + // Image 6: JPEG - Larger size with offset (demonstrates variety in offset sizing) + std::string jpegPath2 = imageDir + "tiny_jpeg.jpg"; + fileSheet.cell("A31").value() = "JPEG - Large Size with Offset (A32 + 25% cell offset)"; + XLImageAnchor jpegImageAnchor2; + jpegImageAnchor2.initOneCell( XLCellReference("A32") ); + jpegImageAnchor2.setDisplaySizeWithAspectRatio(jpegPath2, XLMimeType::JPEG, + 4 * EXCEL_CELL_Y_SPACING_EMUS, 3 * EXCEL_CELL_Y_SPACING_EMUS); + jpegImageAnchor2.fromColOffset = EXCEL_CELL_Y_SPACING_EMUS / 4; // 25% of cell height + jpegImageAnchor2.fromRowOffset = EXCEL_CELL_Y_SPACING_EMUS / 4; // 25% of cell height + const std::string imageIdFile6 = fileSheet.embedImageFromFile(jpegImageAnchor2, + jpegPath2, XLMimeType::JPEG ); + bool fileSuccess6 = !imageIdFile6.empty(); + std::cout << " JPEG large with offset: " << (fileSuccess6 ? "Success" : "Failed") << std::endl; + if (!fileSuccess6) ++errorCount; + + // Check data integrity -- typically only done for debugging + verifyWorksheetData(fileSheet); + } // End of file loading tests + + // Check data integrity -- typically only done for debugging + verifyDocData(doc); + + // 7. Inspect Images + std::cout << "7. Inspect Embedded Images" << std::endl; + printAllEmbeddedImages(doc); + + // 7a. Verify Image Consistency (cross-reference old vs new system) + std::cout << "7a. Verify Image Consistency" << std::endl; + verifyImageConsistency(doc); + + // Check data integrity -- typically only done for debugging + verifyDocData(doc); + + // 8. Set document properties + std::cout << "8. Set document properties" << std::endl; + doc.setCreator("OpenXLSX Demo11"); + doc.setTitle("Comprehensive Image Embedding Test Matrix"); + doc.setSubject("Testing various image embedding options in Excel"); + doc.setDescription("This document demonstrates different combinations of image embedding options including anchor types, file formats, sizing strategies, and units"); + doc.setKeywords("OpenXLSX, images, embedding, Excel, testing, demo"); + doc.setLastModifiedBy("Demo11 Application"); + doc.setCategory("Test Document"); + doc.setApplication("OpenXLSX Demo11 Application"); + doc.setCreationDateToNow(); + doc.setModificationDateToNow(); + doc.setAbsPath(""); + + // Check data integrity -- typically only done for debugging + verifyDocData(doc); + + // Display the properties that were set + std::cout << " Creator: " << doc.creator() << std::endl; + std::cout << " Title: " << doc.title() << std::endl; + std::cout << " Subject: " << doc.subject() << std::endl; + std::cout << " Description: " << doc.description() << std::endl; + std::cout << " Keywords: " << doc.keywords() << std::endl; + std::cout << " Last Modified By: " << doc.lastModifiedBy() << std::endl; + std::cout << " Category: " << doc.category() << std::endl; + std::cout << " Application: " << doc.application() << std::endl; + std::cout << " Creation Date: " << doc.creationDate() << std::endl; + std::cout << " Modification Date: " << doc.modificationDate() << std::endl; + + // 9. Save the workbook + std::cout << "9. Save" << std::endl; + std::cout << " Saving workbook as '" << xlsxFileNameA << "'..." << std::endl; + try { + doc.save(); + std::cout << " Workbook saved successfully." << std::endl; + + } catch (const std::exception& e) { + std::cout << " Error saving workbook: " << e.what() << std::endl; + return 1; + } + + // ======================================== + // II. READ-MODIFY-WRITE + // ======================================== + std::cout << std::endl; + std::cout << "II. READ-MODIFY-WRITE" << std::endl; + std::cout << "====================" << std::endl; + + // 1. Read back the .xlsx file + std::cout << "1. Read back the .xlsx file" << std::endl; + OpenXLSX::XLDocument readDoc; + try { + readDoc.open(xlsxFileNameA); + std::cout << " Successfully opened: " << xlsxFileNameA << std::endl; + + // Check data integrity -- typically only done for debugging + verifyDocData(readDoc); + + // 2. Compare to original + std::cout << "2. Compare to original" << std::endl; + + // Compare the original doc (that was saved) with the readDoc (that was loaded) + std::string diffMsg; + int result = doc.compare(readDoc, &diffMsg); + + if (result == 0) { + std::cout << " ✓ Documents are identical - no differences found" << std::endl; + } else { + std::cout << " ✗ Documents differ (result: " << result << ")" << std::endl; + std::cout << " Differences: " << diffMsg << std::endl; + } + + // 3. Inspect Image Registry + std::cout << "3. Inspect Embedded Images" << std::endl; + + int totalImagesFound = 0; + printAllEmbeddedImages(readDoc,&totalImagesFound); + + // 4. Compare to original, after search + std::cout << "4. Compare to original, after search" << std::endl; + std::string diffMsgAfterSearch; + int resultAfterSearch = doc.compare(readDoc, &diffMsgAfterSearch); + if (resultAfterSearch == 0) { + std::cout << " ✓ Documents still identical after search" << std::endl; + } else { + std::cout << " ✗ Documents differ after search (result: " << resultAfterSearch << ")" << std::endl; + std::cout << " Differences: " << diffMsgAfterSearch << std::endl; + } + + // 5. Remove images + std::cout << "5. Remove images" << std::endl; + + std::string newImageId = ""; // Declare outside the if block for use in verification summary + OpenXLSX::XLWorksheet fileLoadingSheet; // Declare outside the if block + + if (readDoc.workbook().worksheetExists("File Loading")) { + fileLoadingSheet = readDoc.workbook().worksheet("File Loading"); + + // Print embedded images BEFORE removal + std::cout << "5a. Embedded Images BEFORE Removal:" << std::endl; + printAllEmbeddedImages(readDoc); + + // Print XLImageManager reference counts BEFORE removal + readDoc.getImageManager().printReferenceCounts("XLImageManager Reference Counts BEFORE Removal"); + + // Remove image 3 by Image ID and image 4 by Relationship ID (testing both methods) + std::cout << " Removing image 3 by Image ID and image 4 by Relationship ID..." << std::endl; + bool removedImage3 = fileLoadingSheet.removeImageByImageID("3"); // Third image (A14) + bool removedImage4 = fileLoadingSheet.removeImageByRelationshipId("rId4"); // Fourth image (A20) + std::cout << " Removed image 3 (by Image ID): " << (removedImage3 ? "Success" : "Failed") << std::endl; + if (!removedImage3) ++errorCount; + std::cout << " Removed image 4 (by Relationship ID): " << (removedImage4 ? "Success" : "Failed") << std::endl; + if (!removedImage4) ++errorCount; + + // Set "REMOVED" text above the cells where images were removed + fileLoadingSheet.cell("B13").value() = (removedImage3) ? "REMOVED" : "FAILED TO REMOVE"; // Near image 3 at A14 + fileLoadingSheet.cell("B19").value() = (removedImage4) ? "REMOVED" : "FAILED TO REMOVE"; // Near image 4 at A20 + + // Print embedded images AFTER removal + std::cout << "5. Embedded Images AFTER Removal:" << std::endl; + printAllEmbeddedImages(readDoc); + + // Print XLImageManager reference counts AFTER removal + readDoc.getImageManager().printReferenceCounts("XLImageManager Reference Counts AFTER Removal"); + + // Check data integrity -- typically only done for debugging + verifyWorksheetData(fileLoadingSheet); + + // 7. Add image + std::cout << "7. Add image" << std::endl; + XLImageAnchor newImageAnchor; + newImageAnchor.initOneCell( XLCellReference("A38") ); + newImageAnchor.setDisplaySizeWithAspectRatio(XLImageUtils::png31x15Data, XLMimeType::PNG, + 2 * EXCEL_CELL_Y_SPACING_EMUS, 1 * EXCEL_CELL_Y_SPACING_EMUS); + newImageId = fileLoadingSheet.embedImageFromImageData(newImageAnchor, + XLImageUtils::png31x15Data, XLMimeType::PNG ); + std::cout << " Generated new image ID: " << newImageId << std::endl; + OpenXLSX::XLEmbeddedImage newImage = fileSheet.getEmbImageByImageID(newImageId); + if(!newImageId.empty()){ + std::cout << " Successfully loaded image data" << std::endl; + + // Add it to cell A37 (after the existing images) + fileLoadingSheet.cell("A37").value() = "NEW IMAGE - Added via Read-Modify-Write Cycle"; + std::cout << " Set cell A37 text" << std::endl; + + // Check if image is valid before adding + std::cout << " Image valid: " << (newImage.isValid() ? "Yes" : "No") << std::endl; + std::cout << " Image data size: " << newImage.data().size() << " bytes" << std::endl; + std::cout << " Image MIME type: " << XLImageUtils::mimeTypeToString(newImage.mimeType()) << std::endl; + + if (newImage.isValid()) { + std::cout << " Successfully added new image to 'File Loading' worksheet" << std::endl; + std::cout << " New image ID: " << newImageId << std::endl; + std::cout << " Image positioned at: A38" << std::endl; + + // Verify the image was actually added by checking the embedded images + try { + // Check if the new image was added + const auto& embImages = fileLoadingSheet.getEmbImages(); + bool foundNewImage = false; + for (const auto& embImage : embImages) { + if (embImage.id() == newImageId) { + foundNewImage = true; + std::cout << " Found new image in embedded images: " << embeddedImageStr(embImage) << std::endl; + } + } + if (!foundNewImage) { + std::cout << " WARNING: New image '" << newImageId << "' not found in embedded images after addition!" << std::endl; + } + } catch (const std::exception& e) { + std::cout << " ERROR: Failed to verify image addition: " << e.what() << std::endl; + } + } else { + std::cout << " Failed to add new image to worksheet" << std::endl; + ++errorCount; + } + + // Print embedded images AFTER addition + std::cout << "7a. Embedded Images AFTER Addition:" << std::endl; + printAllEmbeddedImages(readDoc); + + } else { + std::cout << " Failed to create new image from data" << std::endl; + ++errorCount; + } + } else { + std::cout << " 'File Loading' worksheet not found!" << std::endl; + ++errorCount; + } + + // Check data integrity -- typically only done for debugging + verifyWorksheetData(fileLoadingSheet); + + // 8. Compare to original, after modification + // TODO: XLDocument::compare() should compare list m_data / std::unique_ptr m_xmlDoc / xml_node/type()/name()/value() + std::cout << "8. Compare to original, after modification" << std::endl; + std::string diffMsgAfterModify; + int resultAfterModify = doc.compare(readDoc, &diffMsgAfterModify); + + if (resultAfterModify == 0) { + std::cout << " ✓ Documents still identical after modifications" << std::endl; + } else { + std::cout << " ✗ Documents differ after modifications (result: " << resultAfterModify << ")" << std::endl; + std::cout << " Differences: " << diffMsgAfterModify << std::endl; + } + + // 9. Inspect Embedded Images + std::cout << "9. Inspect Embedded Images" << std::endl; + try { + if (readDoc.workbook().worksheetExists("File Loading")) { + printEmbeddedImages(fileLoadingSheet, "File Loading"); + std::cout << " Total images after modifications: " << fileLoadingSheet.imageCount() << std::endl; + } else { + std::cout << " 'File Loading' worksheet not found!" << std::endl; + } + } catch (const std::exception& e) { + std::cout << " Error getting final images: " << e.what() << std::endl; + } + + // 10. Set Document properties + std::cout << "10. Set Document properties" << std::endl; + + // Set document properties for the modified version + readDoc.setCreator("OpenXLSX Demo11 - Modified"); + readDoc.setTitle("Comprehensive Image Embedding Test Matrix - Modified"); + readDoc.setSubject("Testing image embedding with modifications (add/remove operations)"); + readDoc.setDescription("This document demonstrates image embedding operations including adding and removing images during read-modify-write cycles"); + readDoc.setKeywords("OpenXLSX, images, embedding, Excel, testing, demo, modified, read-modify-write"); + readDoc.setLastModifiedBy("Demo11 Application - Modified"); + readDoc.setCategory("Test Document - Modified"); + readDoc.setApplication("OpenXLSX Demo11 Application - Modified"); + readDoc.setModificationDateToNow(); // Update modification date for the modified version + + // Display the properties that were set for the modified version + std::cout << " Creator: " << readDoc.creator() << std::endl; + std::cout << " Title: " << readDoc.title() << std::endl; + std::cout << " Subject: " << readDoc.subject() << std::endl; + std::cout << " Keywords: " << readDoc.keywords() << std::endl; + std::cout << " Last Modified By: " << readDoc.lastModifiedBy() << std::endl; + std::cout << " Category: " << readDoc.category() << std::endl; + std::cout << " Application: " << readDoc.application() << std::endl; + std::cout << " Modification Date: " << readDoc.modificationDate() << std::endl; + + // Check data integrity -- typically only done for debugging + verifyDocData(readDoc); + + // print reference counts + readDoc.getImageManager().printReferenceCounts("XLImageManager Reference Counts before saving"); + + // 11. Save modified file + const std::string xlsxFileNameB = "Demo11B.xlsx"; + std::cout << "11. Save modified file: " << xlsxFileNameB << std::endl; + try { + readDoc.saveAs(xlsxFileNameB); + } catch (const std::exception& e) { + std::cout << " Error saving modified workbook: " << e.what() << std::endl; + } + + readDoc.close(); + + } catch (const std::exception& e) { + std::cout << " Error during read-modify-write cycle: " << e.what() << std::endl; + } + + // ======================================== + // III. READ EXCEL-GENERATED FILE + // ======================================== + std::cout << std::endl; + std::cout << "III. READ EXCEL-GENERATED FILE" << std::endl; + std::cout << "=============================" << std::endl; + + // 1. Read file + std::cout << "1. Read file" << std::endl; + + // Find the Excel-generated file + std::string excelFileDir = findDirectory("OpenXLSX/Examples/files/"); + std::string excelFilePath = excelFileDir + "from_excel_embedded_images.xlsx"; + + if (excelFileDir.empty() || !std::filesystem::exists(excelFilePath)) { + std::cout << " ERROR: Could not find Excel-generated file!" << std::endl; + std::cout << " Expected: " << excelFilePath << std::endl; + std::cout << " Searched for: OpenXLSX/Examples/files/ in current directory and up to 10 levels up" << std::endl; + ++errorCount; + } else { + std::cout << " Found Excel file: " << excelFilePath << std::endl; + + // Load the Excel-generated file + XLDocument excelDoc; + try { + excelDoc.open(excelFilePath); + + // Check data integrity -- typically only done for debugging + verifyDocData(excelDoc); + + + // 2. Inspect Embedded Images + std::cout << "2 Inspect Embedded Images" << std::endl; + int totalEmbeddedImages = 0; + printAllEmbeddedImages(excelDoc, &totalEmbeddedImages); + std::cout << " Total embedded images in Excel file: " << totalEmbeddedImages << std::endl; + + // 2c. Verify Image Consistency (cross-reference old vs new system) + std::cout << "2c. Verify Image Consistency" << std::endl; + verifyImageConsistency(excelDoc); + } catch (const std::exception& e) { + std::cout << " ERROR: Failed to open Excel file: " << e.what() << std::endl; + ++errorCount; + } + + // Display the properties that were set for the Excel-generated file + std::cout << "3. Properties" << std::endl; + std::cout << " Creator: " << excelDoc.creator() << std::endl; + std::cout << " Title: " << excelDoc.title() << std::endl; + std::cout << " Subject: " << excelDoc.subject() << std::endl; + std::cout << " Keywords: " << excelDoc.keywords() << std::endl; + std::cout << " Last Modified By: " << excelDoc.lastModifiedBy() << std::endl; + std::cout << " Category: " << excelDoc.category() << std::endl; + std::cout << " Application: " << excelDoc.application() << std::endl; + std::cout << " Modification Date: " << excelDoc.modificationDate() << std::endl; + } + + // ======================================== + // IV. IMAGE QUERY + // ======================================== + std::cout << std::endl; + std::cout << "IV. IMAGE QUERY" << std::endl; + std::cout << "===============" << std::endl; + + // 1. Read file + std::cout << "1. Read file" << std::endl; + + // Re-open the Demo11 file to test image queries + XLDocument testDoc; + try { + testDoc.open(xlsxFileNameA); + std::cout << " Successfully opened " << xlsxFileNameA << std::endl; + + // Check data integrity -- typically only done for debugging + verifyDocData(testDoc); + + // 2. Queries + std::cout << "2. Queries" << std::endl; + + // Test each worksheet with images + for (uint16_t i = 1; i <= testDoc.workbook().sheetCount(); ++i) { + XLSheet sheet = testDoc.workbook().sheet(i); + std::string sheetName = sheet.name(); + + if (sheet.isType()) { + std::cout << std::endl; + XLWorksheet worksheet = sheet.get(); + printDetailedImageAnalysis(worksheet, sheetName); + } + } + + testDoc.close(); + std::cout << "\n Image query testing completed successfully!" << std::endl; + + } catch (const std::exception& e) { + std::cout << " ERROR: Failed to test image queries: " << e.what() << std::endl; + ++errorCount; + } + + // Print final error summary + std::cout << std::endl; + std::cout << "=========================================" << std::endl; + std::cout << "FINAL ERROR SUMMARY" << std::endl; + std::cout << "=========================================" << std::endl; + std::cout << "Total errors encountered: " << errorCount << std::endl; + if (errorCount == 0) { + std::cout << "All operations completed successfully!" << std::endl; + } else { + std::cout << "Some operations failed. Check the output above for details." << std::endl; + } + std::cout << "=========================================" << std::endl; + + return 0; +} diff --git a/Examples/files/from_excel_embedded_images.xlsx b/Examples/files/from_excel_embedded_images.xlsx new file mode 100644 index 00000000..a2b9f1b5 Binary files /dev/null and b/Examples/files/from_excel_embedded_images.xlsx differ diff --git a/Examples/images/bmp_40x40.bmp b/Examples/images/bmp_40x40.bmp new file mode 100644 index 00000000..6d175f51 Binary files /dev/null and b/Examples/images/bmp_40x40.bmp differ diff --git a/Examples/images/gif_60x60.gif b/Examples/images/gif_60x60.gif new file mode 100644 index 00000000..e2f375e8 Binary files /dev/null and b/Examples/images/gif_60x60.gif differ diff --git a/Examples/images/jpeg_60x40.jpg b/Examples/images/jpeg_60x40.jpg new file mode 100644 index 00000000..aba410c6 Binary files /dev/null and b/Examples/images/jpeg_60x40.jpg differ diff --git a/Examples/images/png_40x60.png b/Examples/images/png_40x60.png new file mode 100644 index 00000000..053aec33 Binary files /dev/null and b/Examples/images/png_40x60.png differ diff --git a/Examples/images/tiny_bmp.bmp b/Examples/images/tiny_bmp.bmp new file mode 100644 index 00000000..ed4d948a Binary files /dev/null and b/Examples/images/tiny_bmp.bmp differ diff --git a/Examples/images/tiny_bmp.h b/Examples/images/tiny_bmp.h new file mode 100644 index 00000000..4239e548 --- /dev/null +++ b/Examples/images/tiny_bmp.h @@ -0,0 +1,141 @@ +unsigned char tiny_bmp_bmp[] = { + 0x42, 0x4d, 0x76, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x6f, 0xc7, 0xef, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, + 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x57, 0x1c, 0xed, 0xb0, 0xe4, 0xee, + 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0xb0, 0xe4, 0xef, 0x6f, 0xd4, 0xef, 0x5e, 0xb1, 0x22, 0xb0, 0xdb, 0x91, + 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, + 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x3e, 0x89, + 0xef, 0x6f, 0x44, 0xed, 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0xb0, 0xe4, 0xef, 0x4c, 0xc2, 0xb1, 0x4c, 0xb1, + 0x22, 0xa0, 0xcb, 0x4b, 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, + 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, + 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0x85, 0xe4, 0xef, 0x3e, 0x1c, 0xee, 0xb0, 0xc7, 0xee, 0x80, + 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, 0x91, 0xe4, 0xef, 0x5e, + 0xb1, 0x6f, 0x80, 0xd4, 0x91, 0x80, 0xba, 0x4b, 0xb0, 0xe4, 0xd0, 0x80, + 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, + 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x85, 0xe4, 0xef, 0x24, 0x1c, 0xee, + 0xb0, 0xa8, 0xee, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0x80, 0xdb, 0xef, 0x80, 0xba, 0x4b, 0x91, 0xe4, 0xd0, 0x6f, 0xb1, 0x6f, + 0xb0, 0xe4, 0xb1, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, + 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, + 0xef, 0x3e, 0x1c, 0xed, 0xb0, 0xc7, 0xee, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0x5e, 0xcb, 0xd0, 0xa0, 0xcb, 0x4b, 0xb0, 0xe4, + 0xef, 0x4c, 0xc2, 0xb1, 0xb0, 0xd4, 0x6f, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xcc, 0x48, 0x60, 0xcc, 0x48, + 0x3f, 0xcc, 0x48, 0x3f, 0xcc, 0x48, 0x3f, 0xcc, 0x48, 0x3f, 0xc8, 0x48, + 0x3f, 0xb0, 0xcc, 0x9d, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x24, + 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, + 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x9b, 0x89, 0xed, 0xb0, 0xe4, 0xef, 0x80, + 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xa0, 0xe4, 0xb1, 0x5e, 0xba, 0x91, 0xb0, + 0xdb, 0x91, 0xb0, 0xe4, 0xef, 0x6f, 0xd4, 0xef, 0x91, 0xc2, 0x22, 0x80, + 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, + 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xc2, 0xb3, 0xef, 0xc2, 0x48, 0x3f, 0xb0, 0xe4, 0xb9, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x57, 0xa8, 0xef, 0x85, 0x67, 0xed, + 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0x80, 0xdb, 0xb1, + 0x80, 0xba, 0x4b, 0xb0, 0xe4, 0xd0, 0xb0, 0xe4, 0xef, 0x91, 0xe4, 0xef, + 0x6f, 0xb1, 0x6f, 0x80, 0xdb, 0xb1, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb4, 0xe4, 0xef, 0xcc, 0x65, 0x9d, + 0xb0, 0xb3, 0x80, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, + 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x85, 0xe4, + 0xef, 0x3e, 0x1c, 0xee, 0xb0, 0xc7, 0xee, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0x5e, 0xcb, 0x91, 0xa0, 0xcb, 0x4b, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0x4c, 0xc2, 0xb1, 0x80, 0xcb, 0x6f, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, + 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb4, 0xe4, + 0xef, 0xcc, 0x65, 0x9d, 0xb0, 0xb3, 0x80, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, + 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0x57, 0xa8, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0x80, + 0xdb, 0xef, 0x5e, 0xb1, 0x4b, 0x4c, 0xba, 0x4b, 0xb0, 0xd4, 0x6f, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xd4, 0xef, 0x5e, + 0xba, 0x22, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, + 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xc2, 0xb3, 0xef, 0xc8, 0x48, 0x3f, 0xb0, 0xcc, 0x9d, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x6f, 0xc7, 0xef, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, + 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x3e, 0x1c, 0xed, 0xb0, 0xc7, 0xee, + 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x4c, 0xb1, 0x4b, 0x6f, 0xb1, 0x22, + 0xb0, 0xe4, 0xb1, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x91, 0xe4, 0xef, 0x4c, 0xb1, 0x6f, 0x6f, 0xb1, 0x22, 0xb0, 0xe4, 0xb1, + 0xbe, 0xcc, 0xef, 0xcc, 0x48, 0x60, 0xcc, 0x48, 0x3f, 0xcc, 0x48, 0x3f, + 0xcc, 0x48, 0x3f, 0xcc, 0x48, 0x3f, 0xc8, 0x48, 0x3f, 0xb0, 0xcc, 0x9d, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00 +}; +unsigned int tiny_bmp_bmp_len = 1654; diff --git a/Examples/images/tiny_gif.gif b/Examples/images/tiny_gif.gif new file mode 100644 index 00000000..f9b76d8a Binary files /dev/null and b/Examples/images/tiny_gif.gif differ diff --git a/Examples/images/tiny_gif.h b/Examples/images/tiny_gif.h new file mode 100644 index 00000000..0d0e8ac8 --- /dev/null +++ b/Examples/images/tiny_gif.h @@ -0,0 +1,93 @@ +unsigned char tiny_gif_gif[] = { + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x1a, 0x00, 0x10, 0x00, 0x70, 0x00, + 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x2c, 0x00, 0x00, + 0x00, 0x00, 0x1a, 0x00, 0x10, 0x00, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x66, 0x00, 0x00, 0x99, 0x00, 0x00, 0xcc, 0x00, 0x00, + 0xff, 0x00, 0x2b, 0x00, 0x00, 0x2b, 0x33, 0x00, 0x2b, 0x66, 0x00, 0x2b, + 0x99, 0x00, 0x2b, 0xcc, 0x00, 0x2b, 0xff, 0x00, 0x55, 0x00, 0x00, 0x55, + 0x33, 0x00, 0x55, 0x66, 0x00, 0x55, 0x99, 0x00, 0x55, 0xcc, 0x00, 0x55, + 0xff, 0x00, 0x80, 0x00, 0x00, 0x80, 0x33, 0x00, 0x80, 0x66, 0x00, 0x80, + 0x99, 0x00, 0x80, 0xcc, 0x00, 0x80, 0xff, 0x00, 0xaa, 0x00, 0x00, 0xaa, + 0x33, 0x00, 0xaa, 0x66, 0x00, 0xaa, 0x99, 0x00, 0xaa, 0xcc, 0x00, 0xaa, + 0xff, 0x00, 0xd5, 0x00, 0x00, 0xd5, 0x33, 0x00, 0xd5, 0x66, 0x00, 0xd5, + 0x99, 0x00, 0xd5, 0xcc, 0x00, 0xd5, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, + 0x33, 0x00, 0xff, 0x66, 0x00, 0xff, 0x99, 0x00, 0xff, 0xcc, 0x00, 0xff, + 0xff, 0x33, 0x00, 0x00, 0x33, 0x00, 0x33, 0x33, 0x00, 0x66, 0x33, 0x00, + 0x99, 0x33, 0x00, 0xcc, 0x33, 0x00, 0xff, 0x33, 0x2b, 0x00, 0x33, 0x2b, + 0x33, 0x33, 0x2b, 0x66, 0x33, 0x2b, 0x99, 0x33, 0x2b, 0xcc, 0x33, 0x2b, + 0xff, 0x33, 0x55, 0x00, 0x33, 0x55, 0x33, 0x33, 0x55, 0x66, 0x33, 0x55, + 0x99, 0x33, 0x55, 0xcc, 0x33, 0x55, 0xff, 0x33, 0x80, 0x00, 0x33, 0x80, + 0x33, 0x33, 0x80, 0x66, 0x33, 0x80, 0x99, 0x33, 0x80, 0xcc, 0x33, 0x80, + 0xff, 0x33, 0xaa, 0x00, 0x33, 0xaa, 0x33, 0x33, 0xaa, 0x66, 0x33, 0xaa, + 0x99, 0x33, 0xaa, 0xcc, 0x33, 0xaa, 0xff, 0x33, 0xd5, 0x00, 0x33, 0xd5, + 0x33, 0x33, 0xd5, 0x66, 0x33, 0xd5, 0x99, 0x33, 0xd5, 0xcc, 0x33, 0xd5, + 0xff, 0x33, 0xff, 0x00, 0x33, 0xff, 0x33, 0x33, 0xff, 0x66, 0x33, 0xff, + 0x99, 0x33, 0xff, 0xcc, 0x33, 0xff, 0xff, 0x66, 0x00, 0x00, 0x66, 0x00, + 0x33, 0x66, 0x00, 0x66, 0x66, 0x00, 0x99, 0x66, 0x00, 0xcc, 0x66, 0x00, + 0xff, 0x66, 0x2b, 0x00, 0x66, 0x2b, 0x33, 0x66, 0x2b, 0x66, 0x66, 0x2b, + 0x99, 0x66, 0x2b, 0xcc, 0x66, 0x2b, 0xff, 0x66, 0x55, 0x00, 0x66, 0x55, + 0x33, 0x66, 0x55, 0x66, 0x66, 0x55, 0x99, 0x66, 0x55, 0xcc, 0x66, 0x55, + 0xff, 0x66, 0x80, 0x00, 0x66, 0x80, 0x33, 0x66, 0x80, 0x66, 0x66, 0x80, + 0x99, 0x66, 0x80, 0xcc, 0x66, 0x80, 0xff, 0x66, 0xaa, 0x00, 0x66, 0xaa, + 0x33, 0x66, 0xaa, 0x66, 0x66, 0xaa, 0x99, 0x66, 0xaa, 0xcc, 0x66, 0xaa, + 0xff, 0x66, 0xd5, 0x00, 0x66, 0xd5, 0x33, 0x66, 0xd5, 0x66, 0x66, 0xd5, + 0x99, 0x66, 0xd5, 0xcc, 0x66, 0xd5, 0xff, 0x66, 0xff, 0x00, 0x66, 0xff, + 0x33, 0x66, 0xff, 0x66, 0x66, 0xff, 0x99, 0x66, 0xff, 0xcc, 0x66, 0xff, + 0xff, 0x99, 0x00, 0x00, 0x99, 0x00, 0x33, 0x99, 0x00, 0x66, 0x99, 0x00, + 0x99, 0x99, 0x00, 0xcc, 0x99, 0x00, 0xff, 0x99, 0x2b, 0x00, 0x99, 0x2b, + 0x33, 0x99, 0x2b, 0x66, 0x99, 0x2b, 0x99, 0x99, 0x2b, 0xcc, 0x99, 0x2b, + 0xff, 0x99, 0x55, 0x00, 0x99, 0x55, 0x33, 0x99, 0x55, 0x66, 0x99, 0x55, + 0x99, 0x99, 0x55, 0xcc, 0x99, 0x55, 0xff, 0x99, 0x80, 0x00, 0x99, 0x80, + 0x33, 0x99, 0x80, 0x66, 0x99, 0x80, 0x99, 0x99, 0x80, 0xcc, 0x99, 0x80, + 0xff, 0x99, 0xaa, 0x00, 0x99, 0xaa, 0x33, 0x99, 0xaa, 0x66, 0x99, 0xaa, + 0x99, 0x99, 0xaa, 0xcc, 0x99, 0xaa, 0xff, 0x99, 0xd5, 0x00, 0x99, 0xd5, + 0x33, 0x99, 0xd5, 0x66, 0x99, 0xd5, 0x99, 0x99, 0xd5, 0xcc, 0x99, 0xd5, + 0xff, 0x99, 0xff, 0x00, 0x99, 0xff, 0x33, 0x99, 0xff, 0x66, 0x99, 0xff, + 0x99, 0x99, 0xff, 0xcc, 0x99, 0xff, 0xff, 0xcc, 0x00, 0x00, 0xcc, 0x00, + 0x33, 0xcc, 0x00, 0x66, 0xcc, 0x00, 0x99, 0xcc, 0x00, 0xcc, 0xcc, 0x00, + 0xff, 0xcc, 0x2b, 0x00, 0xcc, 0x2b, 0x33, 0xcc, 0x2b, 0x66, 0xcc, 0x2b, + 0x99, 0xcc, 0x2b, 0xcc, 0xcc, 0x2b, 0xff, 0xcc, 0x55, 0x00, 0xcc, 0x55, + 0x33, 0xcc, 0x55, 0x66, 0xcc, 0x55, 0x99, 0xcc, 0x55, 0xcc, 0xcc, 0x55, + 0xff, 0xcc, 0x80, 0x00, 0xcc, 0x80, 0x33, 0xcc, 0x80, 0x66, 0xcc, 0x80, + 0x99, 0xcc, 0x80, 0xcc, 0xcc, 0x80, 0xff, 0xcc, 0xaa, 0x00, 0xcc, 0xaa, + 0x33, 0xcc, 0xaa, 0x66, 0xcc, 0xaa, 0x99, 0xcc, 0xaa, 0xcc, 0xcc, 0xaa, + 0xff, 0xcc, 0xd5, 0x00, 0xcc, 0xd5, 0x33, 0xcc, 0xd5, 0x66, 0xcc, 0xd5, + 0x99, 0xcc, 0xd5, 0xcc, 0xcc, 0xd5, 0xff, 0xcc, 0xff, 0x00, 0xcc, 0xff, + 0x33, 0xcc, 0xff, 0x66, 0xcc, 0xff, 0x99, 0xcc, 0xff, 0xcc, 0xcc, 0xff, + 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x33, 0xff, 0x00, 0x66, 0xff, 0x00, + 0x99, 0xff, 0x00, 0xcc, 0xff, 0x00, 0xff, 0xff, 0x2b, 0x00, 0xff, 0x2b, + 0x33, 0xff, 0x2b, 0x66, 0xff, 0x2b, 0x99, 0xff, 0x2b, 0xcc, 0xff, 0x2b, + 0xff, 0xff, 0x55, 0x00, 0xff, 0x55, 0x33, 0xff, 0x55, 0x66, 0xff, 0x55, + 0x99, 0xff, 0x55, 0xcc, 0xff, 0x55, 0xff, 0xff, 0x80, 0x00, 0xff, 0x80, + 0x33, 0xff, 0x80, 0x66, 0xff, 0x80, 0x99, 0xff, 0x80, 0xcc, 0xff, 0x80, + 0xff, 0xff, 0xaa, 0x00, 0xff, 0xaa, 0x33, 0xff, 0xaa, 0x66, 0xff, 0xaa, + 0x99, 0xff, 0xaa, 0xcc, 0xff, 0xaa, 0xff, 0xff, 0xd5, 0x00, 0xff, 0xd5, + 0x33, 0xff, 0xd5, 0x66, 0xff, 0xd5, 0x99, 0xff, 0xd5, 0xcc, 0xff, 0xd5, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x33, 0xff, 0xff, 0x66, 0xff, 0xff, + 0x99, 0xff, 0xff, 0xcc, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0x00, 0xe7, 0xe9, + 0x13, 0x48, 0x70, 0xa0, 0xc1, 0x82, 0x08, 0x0f, 0xea, 0xd3, 0x97, 0x8c, + 0x5e, 0x32, 0x86, 0x0e, 0x21, 0x3e, 0x6c, 0x38, 0x31, 0x22, 0xc5, 0x7c, + 0xca, 0x04, 0x26, 0xc3, 0x38, 0x8f, 0x23, 0xc6, 0x7c, 0x0d, 0x41, 0xd2, + 0x03, 0xc9, 0x50, 0xe3, 0x48, 0x88, 0x1d, 0x51, 0x4d, 0xc3, 0x96, 0xea, + 0x5c, 0x3e, 0x7a, 0xf2, 0x88, 0x98, 0x72, 0x47, 0x86, 0x86, 0x0e, 0x9b, + 0x3a, 0xde, 0x0c, 0xcc, 0x38, 0x0f, 0x9a, 0x3c, 0x54, 0xee, 0xf2, 0x41, + 0xcb, 0x37, 0xad, 0x17, 0x48, 0x37, 0x9f, 0xe6, 0xe9, 0x78, 0x36, 0xb2, + 0x69, 0xc8, 0x92, 0xdf, 0xce, 0x41, 0x9b, 0x37, 0x2f, 0x99, 0x3c, 0x55, + 0xfa, 0x62, 0x42, 0xa3, 0xf9, 0xc9, 0xe1, 0x4b, 0x93, 0x0f, 0xe5, 0x51, + 0x6b, 0xc8, 0x54, 0x9f, 0xb2, 0x7c, 0x54, 0x89, 0x80, 0x9a, 0x47, 0x06, + 0xd4, 0xd7, 0xa9, 0x4c, 0xbf, 0x0a, 0x03, 0xf7, 0x75, 0xe3, 0x3c, 0x65, + 0xfa, 0x90, 0xb9, 0x99, 0x59, 0x53, 0xc7, 0x4d, 0x20, 0xf3, 0xe8, 0x31, + 0x9d, 0xf7, 0x8c, 0x1d, 0xac, 0x88, 0xdf, 0x52, 0x65, 0x4b, 0x45, 0x15, + 0xa9, 0xd2, 0x67, 0x81, 0xd1, 0x42, 0xcc, 0x37, 0xf0, 0x2a, 0xe5, 0x8d, + 0x59, 0xa7, 0x29, 0xd3, 0xca, 0x15, 0x62, 0x5c, 0x8a, 0x0c, 0xa3, 0x26, + 0x0b, 0xbc, 0xf1, 0x55, 0xbb, 0x7c, 0x44, 0x3e, 0x29, 0x23, 0xd3, 0xb0, + 0x27, 0xe9, 0x93, 0x54, 0x91, 0x4d, 0x6b, 0x37, 0x35, 0xdf, 0xab, 0x69, + 0xf4, 0xf4, 0x6e, 0x1d, 0xe3, 0xf6, 0xac, 0x6b, 0x86, 0x64, 0x47, 0xd6, + 0x92, 0x96, 0x6a, 0x1a, 0xb8, 0xa1, 0xf3, 0x64, 0xce, 0xf3, 0x62, 0xca, + 0x24, 0xc7, 0x85, 0x81, 0x21, 0x8f, 0x1c, 0x9a, 0x51, 0x30, 0xbd, 0xbb, + 0x68, 0x23, 0x56, 0x1d, 0x38, 0xfa, 0x21, 0xf6, 0x8c, 0x03, 0xa7, 0xba, + 0x11, 0x7e, 0xf9, 0x79, 0x24, 0xf8, 0x9d, 0x5f, 0x4b, 0x86, 0x7f, 0x3d, + 0xf8, 0xac, 0x76, 0x65, 0x01, 0x01, 0x00, 0x3b +}; +unsigned int tiny_gif_gif_len = 1076; diff --git a/Examples/images/tiny_jpeg.h b/Examples/images/tiny_jpeg.h new file mode 100644 index 00000000..93a940ce --- /dev/null +++ b/Examples/images/tiny_jpeg.h @@ -0,0 +1,100 @@ +unsigned char tiny_jpeg_jpg[] = { + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0xff, 0xe1, 0x00, 0x22, + 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x01, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, + 0x00, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x03, 0x05, 0x03, 0x03, 0x03, 0x03, 0x03, 0x06, 0x04, + 0x04, 0x03, 0x05, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x08, + 0x09, 0x0b, 0x09, 0x08, 0x08, 0x0a, 0x08, 0x07, 0x07, 0x0a, 0x0d, 0x0a, + 0x0a, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x07, 0x09, 0x0e, 0x0f, 0x0d, 0x0c, + 0x0e, 0x0b, 0x0c, 0x0c, 0x0c, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x02, 0x02, + 0x02, 0x03, 0x03, 0x03, 0x06, 0x03, 0x03, 0x06, 0x0c, 0x08, 0x07, 0x08, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x12, 0x00, 0x20, 0x03, + 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, + 0x1f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, + 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, + 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, + 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, + 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, + 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, + 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, + 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, + 0x1f, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00, + 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, + 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, + 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, + 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, + 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, + 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, + 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xfd, + 0x5e, 0xf8, 0xcf, 0xf1, 0x17, 0xfe, 0x15, 0x17, 0xc2, 0x5f, 0x12, 0x78, + 0xa7, 0xec, 0x7f, 0xda, 0x1f, 0xf0, 0x8f, 0xe9, 0xd3, 0xea, 0x1f, 0x65, + 0xf3, 0x7c, 0x9f, 0xb4, 0x79, 0x68, 0x5b, 0x66, 0xfd, 0xad, 0xb7, 0x38, + 0xc6, 0x70, 0x71, 0xe8, 0x6b, 0x26, 0x4f, 0x88, 0xfe, 0x29, 0xd6, 0x3c, + 0x59, 0xe2, 0x0d, 0x3f, 0x43, 0xf0, 0xee, 0x83, 0x79, 0x6d, 0xa0, 0x5c, + 0xa5, 0xab, 0xcd, 0x7b, 0xae, 0x4b, 0x6b, 0x24, 0xce, 0xd0, 0x47, 0x37, + 0x08, 0xb6, 0xb2, 0x00, 0x31, 0x20, 0x1c, 0xbf, 0x51, 0xda, 0xb9, 0xdf, + 0xda, 0x7b, 0x4d, 0xf1, 0xaf, 0x8e, 0xbc, 0x3d, 0xe2, 0x3f, 0x06, 0xe9, + 0x3e, 0x1b, 0x5d, 0x4f, 0x45, 0xf1, 0x76, 0x86, 0x34, 0xdb, 0x6d, 0x4e, + 0x2b, 0xa8, 0x22, 0xfe, 0xc9, 0xba, 0x95, 0xe5, 0x8e, 0x79, 0x2e, 0x84, + 0x92, 0xab, 0xb4, 0x2b, 0x13, 0x42, 0xeb, 0xe4, 0x47, 0x23, 0xe5, 0x25, + 0x04, 0x72, 0x95, 0xa9, 0xa6, 0xfc, 0x0f, 0x8b, 0x5b, 0xf1, 0xe7, 0x8c, + 0x35, 0x1d, 0x5c, 0xeb, 0xd6, 0xd0, 0xea, 0x7a, 0x84, 0x52, 0x5a, 0x7d, + 0x8b, 0x5f, 0xbb, 0xb2, 0x8e, 0x78, 0x85, 0xac, 0x08, 0x49, 0x8e, 0xde, + 0x65, 0x5c, 0xef, 0x57, 0x19, 0x61, 0xb8, 0x81, 0xe9, 0x8a, 0xfc, 0x3f, + 0x56, 0xfe, 0x4f, 0xef, 0xba, 0xff, 0x00, 0x83, 0xf9, 0x9f, 0x53, 0xc4, + 0x58, 0x4a, 0xd4, 0xb0, 0x18, 0x47, 0x82, 0x92, 0xf6, 0xb5, 0x2a, 0x4f, + 0x9f, 0x96, 0x49, 0xb5, 0x4f, 0xd9, 0xa7, 0x1e, 0x6f, 0x76, 0x7c, 0x9e, + 0xfd, 0xd6, 0xb1, 0x4d, 0xbd, 0x2f, 0x6b, 0x15, 0x57, 0xf6, 0x8a, 0xbf, + 0xf1, 0x8c, 0xda, 0x3e, 0x9f, 0xe0, 0xdf, 0x0d, 0xc5, 0xab, 0xeb, 0xba, + 0x86, 0x9c, 0xba, 0xb5, 0xe4, 0x1a, 0x9e, 0xa3, 0xfd, 0x9f, 0x69, 0xa4, + 0xdb, 0x33, 0xbc, 0x6b, 0xe7, 0x4f, 0x1c, 0x53, 0x93, 0x23, 0xc9, 0x1c, + 0x8a, 0x88, 0x91, 0xb0, 0x61, 0x14, 0x8c, 0x59, 0x40, 0x1b, 0xba, 0xff, + 0x00, 0x86, 0x3e, 0x34, 0xd5, 0x3c, 0x61, 0xa5, 0xde, 0xae, 0xb7, 0xe1, + 0xfb, 0x8f, 0x0e, 0xea, 0xda, 0x5d, 0xe3, 0xd9, 0xdc, 0xdb, 0x99, 0x0c, + 0xf6, 0xd3, 0x10, 0x15, 0x96, 0x6b, 0x69, 0xca, 0x27, 0x9d, 0x0b, 0xab, + 0xa9, 0x0d, 0xb1, 0x48, 0x3b, 0x95, 0x95, 0x59, 0x58, 0x0e, 0x23, 0xc4, + 0x7e, 0x00, 0xd5, 0x3e, 0x14, 0x7c, 0x46, 0x9b, 0x5c, 0xf0, 0xcf, 0x86, + 0xae, 0x35, 0xff, 0x00, 0x0f, 0xeb, 0x1a, 0x1d, 0xb6, 0x83, 0x7d, 0xa4, + 0xe9, 0x57, 0x70, 0xd9, 0x5f, 0xd8, 0x0b, 0x66, 0x9d, 0xa0, 0x96, 0xdd, + 0xa6, 0x92, 0x28, 0xca, 0x15, 0xb8, 0x74, 0x71, 0xe6, 0xa3, 0xa9, 0x58, + 0xd9, 0x77, 0x1d, 0xc0, 0x5a, 0xfd, 0x98, 0xfc, 0x11, 0xe2, 0x2f, 0x09, + 0x5b, 0x78, 0x9a, 0xe3, 0x5a, 0x87, 0x5c, 0xd3, 0xb4, 0xfd, 0x5b, 0x52, + 0x59, 0xf4, 0x7d, 0x2b, 0x58, 0xf1, 0x0c, 0xda, 0xe5, 0xf6, 0x99, 0x6c, + 0xb0, 0xc7, 0x11, 0x59, 0x66, 0x92, 0x49, 0x42, 0xb3, 0xc8, 0x8f, 0x2e, + 0xc8, 0xe5, 0x91, 0x54, 0x48, 0x06, 0xec, 0x82, 0x07, 0xd7, 0x63, 0x30, + 0x79, 0x7f, 0xf6, 0x73, 0xab, 0x47, 0x92, 0xe9, 0x41, 0xa7, 0xcd, 0xef, + 0xca, 0x4d, 0xfb, 0xf1, 0x71, 0xf6, 0x8e, 0xdc, 0xad, 0xbb, 0x7e, 0xee, + 0xce, 0x31, 0x4f, 0x9e, 0xef, 0xde, 0xf2, 0xf0, 0xaf, 0x13, 0x1e, 0x48, + 0x57, 0x77, 0x95, 0x92, 0x93, 0x4b, 0x46, 0xf9, 0x55, 0xda, 0x76, 0x5b, + 0xca, 0xfd, 0xad, 0xb5, 0x8f, 0x52, 0xa2, 0x8a, 0x2b, 0xe4, 0x4f, 0x4c, + 0x28, 0xa2, 0x8a, 0x00, 0xff, 0xd9 +}; +unsigned int tiny_jpeg_jpg_len = 1158; diff --git a/Examples/images/tiny_jpeg.jpg b/Examples/images/tiny_jpeg.jpg new file mode 100644 index 00000000..f67f41c0 Binary files /dev/null and b/Examples/images/tiny_jpeg.jpg differ diff --git a/Examples/images/tiny_png.h b/Examples/images/tiny_png.h new file mode 100644 index 00000000..b8606fa5 --- /dev/null +++ b/Examples/images/tiny_png.h @@ -0,0 +1,48 @@ +unsigned char tiny_png_png[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0f, + 0x08, 0x02, 0x00, 0x00, 0x00, 0x93, 0x1a, 0x83, 0xf5, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00, + 0x00, 0x01, 0xa8, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4f, 0x63, 0x7c, 0xff, + 0x64, 0x03, 0x03, 0xcd, 0x00, 0x13, 0x94, 0xa6, 0x0d, 0x00, 0xbb, 0xfd, + 0xf1, 0xfe, 0x7f, 0xa1, 0x5b, 0xa1, 0x02, 0x50, 0x20, 0xc5, 0xb8, 0xba, + 0x88, 0x51, 0x96, 0x81, 0xe1, 0xfb, 0x85, 0x98, 0x6b, 0x67, 0x18, 0xa4, + 0x42, 0x96, 0x48, 0x0a, 0x40, 0x65, 0x40, 0x22, 0xef, 0xd3, 0x8c, 0x1d, + 0xed, 0xa0, 0x7c, 0xce, 0x89, 0x29, 0xc2, 0xfb, 0xa1, 0x6c, 0x06, 0xc5, + 0xe0, 0x17, 0xfd, 0x9e, 0x7f, 0xa0, 0x1c, 0x06, 0x06, 0x16, 0x28, 0xcd, + 0x60, 0xcc, 0x78, 0x22, 0x92, 0x11, 0xca, 0x66, 0x60, 0x38, 0xb2, 0xfc, + 0x5f, 0x68, 0x1f, 0x03, 0xd0, 0x02, 0x11, 0x10, 0xef, 0xfa, 0xb3, 0xf3, + 0x87, 0x24, 0xe1, 0xc6, 0x21, 0x81, 0x17, 0x7c, 0x85, 0x35, 0x7c, 0x0a, + 0xd9, 0x4f, 0x36, 0x18, 0x42, 0xf8, 0x40, 0x9b, 0x24, 0x0a, 0x19, 0x10, + 0x16, 0xe0, 0x08, 0x19, 0x1b, 0x17, 0x46, 0x8d, 0x67, 0x0c, 0x0f, 0x21, + 0x1c, 0xe5, 0x7c, 0xa9, 0xf7, 0xb3, 0x9e, 0x7f, 0x80, 0x70, 0x90, 0x00, + 0xcb, 0xba, 0x99, 0x7c, 0x0c, 0xc1, 0x2f, 0xf2, 0xa1, 0x46, 0x03, 0xc1, + 0xf7, 0xfc, 0x96, 0x4f, 0x0c, 0x6b, 0xf9, 0x4e, 0x41, 0xb9, 0xc4, 0x85, + 0xbb, 0x82, 0xa4, 0x93, 0xdb, 0xfb, 0xf3, 0x87, 0xa0, 0x3c, 0x18, 0x78, + 0xc1, 0x75, 0xf8, 0xf1, 0xb7, 0x48, 0xa4, 0x70, 0x00, 0x01, 0x89, 0x4f, + 0xfd, 0x73, 0xde, 0x99, 0x41, 0x39, 0xb8, 0x4c, 0x3f, 0xb2, 0xe7, 0xff, + 0x0d, 0x63, 0x06, 0x1b, 0x28, 0x8f, 0x81, 0x41, 0x20, 0x4e, 0x8a, 0xa1, + 0xf0, 0x1e, 0xd4, 0x2f, 0x50, 0xf0, 0x9c, 0xe5, 0xbe, 0xec, 0x1f, 0x19, + 0x28, 0x07, 0x3b, 0x80, 0x87, 0xfb, 0xd9, 0xff, 0x16, 0x67, 0xff, 0x43, + 0xd9, 0x40, 0x00, 0x8e, 0x55, 0x28, 0x1b, 0x0c, 0x04, 0x0c, 0xf3, 0x9f, + 0xed, 0x5b, 0xf4, 0x5d, 0x3e, 0x0e, 0xca, 0x47, 0x07, 0xa7, 0xe6, 0xc8, + 0xb4, 0x9d, 0x80, 0x30, 0xff, 0xc4, 0xb5, 0xbc, 0x08, 0x92, 0x00, 0xb1, + 0x70, 0xc4, 0x2a, 0x16, 0x20, 0x10, 0xa7, 0xa8, 0x14, 0xf3, 0xfc, 0x61, + 0x9c, 0x24, 0x94, 0xcf, 0x20, 0xf9, 0x47, 0xf1, 0x31, 0xcb, 0x13, 0xa0, + 0x43, 0xc0, 0x3c, 0xb3, 0x94, 0x27, 0x1b, 0x52, 0x80, 0x34, 0x30, 0x62, + 0xf9, 0xc1, 0x02, 0x20, 0x40, 0x4a, 0x7a, 0xe7, 0x34, 0x48, 0x63, 0xd8, + 0xdd, 0x00, 0x8f, 0x5d, 0x89, 0x6f, 0xb6, 0xb2, 0x5c, 0xcb, 0xb7, 0xc3, + 0xdd, 0x87, 0x05, 0x90, 0x96, 0x9b, 0xec, 0x24, 0x4d, 0xee, 0x3c, 0x3b, + 0x73, 0x1d, 0xca, 0xfb, 0x13, 0x94, 0x0e, 0x4c, 0x21, 0x12, 0x85, 0x08, + 0x0b, 0x58, 0xd6, 0x35, 0x22, 0xd2, 0x3e, 0x10, 0xe0, 0xb3, 0x19, 0x0b, + 0xe0, 0x34, 0x68, 0x95, 0xba, 0x17, 0xf4, 0x0c, 0xca, 0x03, 0xa7, 0x90, + 0x6f, 0xeb, 0x1a, 0x25, 0x02, 0xd6, 0x42, 0x05, 0x18, 0x2c, 0xde, 0x6e, + 0xa8, 0xff, 0x0e, 0x65, 0x43, 0xf3, 0x2a, 0xcd, 0x00, 0x69, 0x21, 0x43, + 0x2a, 0xa0, 0xa5, 0xe9, 0x0c, 0x0c, 0x00, 0xe4, 0x3f, 0x87, 0xe1, 0xb6, + 0x42, 0x8a, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, + 0x42, 0x60, 0x82 +}; +unsigned int tiny_png_png_len = 531; diff --git a/Examples/images/tiny_png.png b/Examples/images/tiny_png.png new file mode 100644 index 00000000..c3618739 Binary files /dev/null and b/Examples/images/tiny_png.png differ diff --git a/OpenXLSX/CMakeLists.txt b/OpenXLSX/CMakeLists.txt index 95c56f94..6fe203bd 100644 --- a/OpenXLSX/CMakeLists.txt +++ b/OpenXLSX/CMakeLists.txt @@ -111,6 +111,10 @@ set(OPENXLSX_SOURCES ${CMAKE_CURRENT_LIST_DIR}/sources/XLSheet.cpp ${CMAKE_CURRENT_LIST_DIR}/sources/XLStyles.cpp ${CMAKE_CURRENT_LIST_DIR}/sources/XLTables.cpp + ${CMAKE_CURRENT_LIST_DIR}/sources/XLImage.cpp + ${CMAKE_CURRENT_LIST_DIR}/sources/XLImageUtils.cpp + ${CMAKE_CURRENT_LIST_DIR}/sources/XLDrawingML.cpp + ${CMAKE_CURRENT_LIST_DIR}/sources/XLWorksheetImageQuery.cpp ${CMAKE_CURRENT_LIST_DIR}/sources/XLWorkbook.cpp ${CMAKE_CURRENT_LIST_DIR}/sources/XLXmlData.cpp ${CMAKE_CURRENT_LIST_DIR}/sources/XLXmlFile.cpp diff --git a/OpenXLSX/OpenXLSX.hpp b/OpenXLSX/OpenXLSX.hpp index 568a90bf..1da0f5e8 100644 --- a/OpenXLSX/OpenXLSX.hpp +++ b/OpenXLSX/OpenXLSX.hpp @@ -59,5 +59,6 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM. #include "headers/XLSheet.hpp" #include "headers/XLWorkbook.hpp" #include "headers/XLZipArchive.hpp" +#include "sources/utilities/XLUtilities.hpp" #endif // OPENXLSX_OPENXLSX_HPP diff --git a/OpenXLSX/headers/IZipArchive.hpp b/OpenXLSX/headers/IZipArchive.hpp index e7db7bf7..febd7626 100644 --- a/OpenXLSX/headers/IZipArchive.hpp +++ b/OpenXLSX/headers/IZipArchive.hpp @@ -171,7 +171,7 @@ namespace OpenXLSX m_zipArchive->deleteEntry(entryName); } - inline std::string getEntry(const std::string& name) { + inline std::string getEntry(const std::string& name) const { return m_zipArchive->getEntry(name); } @@ -238,7 +238,7 @@ namespace OpenXLSX inline virtual void deleteEntry(const std::string& entryName) = 0; - inline virtual std::string getEntry(const std::string& name) = 0; + inline virtual std::string getEntry(const std::string& name) const = 0; inline virtual bool hasEntry(const std::string& entryName) const = 0; @@ -326,7 +326,7 @@ namespace OpenXLSX ZipType.deleteEntry(entryName); } - inline std::string getEntry(const std::string& name) override { + inline std::string getEntry(const std::string& name) const override { return ZipType.getEntry(name); } diff --git a/OpenXLSX/headers/XLCell.hpp b/OpenXLSX/headers/XLCell.hpp index 6500c42e..a9e5711f 100644 --- a/OpenXLSX/headers/XLCell.hpp +++ b/OpenXLSX/headers/XLCell.hpp @@ -267,6 +267,18 @@ namespace OpenXLSX */ XLCellAssignable (XLCell && other); + /** + * @brief Copy constructor for XLCellAssignable + * @param other the XLCellAssignable to construct from + */ + XLCellAssignable (const XLCellAssignable& other); + + /** + * @brief Move constructor for XLCellAssignable + * @param other the XLCellAssignable to construct from + */ + XLCellAssignable (XLCellAssignable&& other) noexcept; + // /** // * @brief Inherit all constructors with parameters from XLCell // */ diff --git a/OpenXLSX/headers/XLCellValue.hpp b/OpenXLSX/headers/XLCellValue.hpp index dc7e8205..9e70273d 100644 --- a/OpenXLSX/headers/XLCellValue.hpp +++ b/OpenXLSX/headers/XLCellValue.hpp @@ -84,9 +84,9 @@ namespace OpenXLSX struct VisitXLCellValueTypeToDouble { std::string packageName = "VisitXLCellValueTypeToDouble"; - double operator()(int64_t v) const { return v; } + double operator()(int64_t v) const { return static_cast(v); } double operator()(double v) const { return v; } - double operator()(bool v) const { return v; } + double operator()(bool v) const { return static_cast(v); } // double operator()( struct timestamp v ) { /* to be implemented if this type ever gets supported */ } double operator()(std::string v) const { throw XLValueTypeError("string is not convertible to double."); // disable if implicit conversion of string to double shall be allowed diff --git a/OpenXLSX/headers/XLContentTypes.hpp b/OpenXLSX/headers/XLContentTypes.hpp index 3319c332..18a6227e 100644 --- a/OpenXLSX/headers/XLContentTypes.hpp +++ b/OpenXLSX/headers/XLContentTypes.hpp @@ -91,6 +91,10 @@ namespace OpenXLSX Comments, Table, VMLDrawing, + ImagePNG, + ImageJPEG, + ImageBMP, + ImageGIF, Unknown }; @@ -228,6 +232,13 @@ namespace OpenXLSX */ void addOverride(const std::string& path, XLContentType type); + /** + * @brief Check if an override with the given path already exists + * @param path The path to check for + * @return true if override exists, false otherwise + */ + bool hasOverride(const std::string& path) const; + /** * @brief * @param path @@ -253,6 +264,13 @@ namespace OpenXLSX */ std::vector getContentItems(); + /** + * @brief Verify internal data integrity and class invariants + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyData(std::string* dbgMsg = nullptr) const; + private: // ---------- Private Member Variables ---------- // }; } // namespace OpenXLSX diff --git a/OpenXLSX/headers/XLDocument.hpp b/OpenXLSX/headers/XLDocument.hpp index 070651c8..8bd1a2bd 100644 --- a/OpenXLSX/headers/XLDocument.hpp +++ b/OpenXLSX/headers/XLDocument.hpp @@ -54,6 +54,7 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM. // ===== External Includes ===== // #include // std::find_if +#include // time_t #include #include @@ -71,6 +72,7 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM. #include "XLTables.hpp" #include "XLWorkbook.hpp" #include "XLXmlData.hpp" +#include "XLImage.hpp" #include "XLZipArchive.hpp" namespace OpenXLSX @@ -269,6 +271,152 @@ namespace OpenXLSX */ void deleteProperty(XLProperty theProperty); + //---------------------------------------------------------------------------------------------------------------------- + // Convenience Property Methods + //---------------------------------------------------------------------------------------------------------------------- + + /** + * @brief Set the document creator (dc:creator) + * @param creator The creator name + */ + void setCreator(const std::string& creator); + + /** + * @brief Get the document creator (dc:creator) + * @return The creator name + */ + std::string creator() const; + + /** + * @brief Set the document title (dc:title) + * @param title The document title + */ + void setTitle(const std::string& title); + + /** + * @brief Get the document title (dc:title) + * @return The document title + */ + std::string title() const; + + /** + * @brief Set the document subject (dc:subject) + * @param subject The document subject + */ + void setSubject(const std::string& subject); + + /** + * @brief Get the document subject (dc:subject) + * @return The document subject + */ + std::string subject() const; + + /** + * @brief Set the document description (dc:description) + * @param description The document description + */ + void setDescription(const std::string& description); + + /** + * @brief Get the document description (dc:description) + * @return The document description + */ + std::string description() const; + + /** + * @brief Set the document keywords (cp:keywords) + * @param keywords The document keywords + */ + void setKeywords(const std::string& keywords); + + /** + * @brief Get the document keywords (cp:keywords) + * @return The document keywords + */ + std::string keywords() const; + + /** + * @brief Set the last modified by (cp:lastModifiedBy) + * @param lastModifiedBy The last modified by name + */ + void setLastModifiedBy(const std::string& lastModifiedBy); + + /** + * @brief Get the last modified by (cp:lastModifiedBy) + * @return The last modified by name + */ + std::string lastModifiedBy() const; + + /** + * @brief Set the document category (cp:category) + * @param category The document category + */ + void setCategory(const std::string& category); + + /** + * @brief Get the document category (cp:category) + * @return The document category + */ + std::string category() const; + + /** + * @brief Set the creation date to the current time (dcterms:created) + */ + void setCreationDateToNow(); + + /** + * @brief Set the creation date to a specific time (dcterms:created) + * @param timestamp The time_t timestamp to set as creation date + */ + void setCreationDate(time_t timestamp); + + /** + * @brief Get the creation date (dcterms:created) + * @return The creation date in W3CDTF format + */ + std::string creationDate() const; + + /** + * @brief Set the modification date to the current time (dcterms:modified) + */ + void setModificationDateToNow(); + + /** + * @brief Set the modification date to a specific time (dcterms:modified) + * @param timestamp The time_t timestamp to set as modification date + */ + void setModificationDate(time_t timestamp); + + /** + * @brief Get the modification date (dcterms:modified) + * @return The modification date in W3CDTF format + */ + std::string modificationDate() const; + + /** + * @brief Set the absolute path (x15ac:absPath) + * @param absPath The absolute path URL to set + */ + void setAbsPath(const std::string& absPath); + + /** + * @brief Get the absolute path (x15ac:absPath) + * @return The absolute path URL + */ + std::string absPath() const; + + /** + * @brief Set the application name (Application) + * @param application The application name + */ + void setApplication(const std::string& application); + + /** + * @brief Get the application name (Application) + * @return The application name + */ + std::string application() const; + /** * @brief * @return @@ -336,6 +484,12 @@ namespace OpenXLSX */ XLComments sheetComments(uint16_t sheetXmlNo); + /** + * @brief Generate the next globally unique image ID for this document + * @return The next sequential image ID (e.g., "img1", "img2", etc.) + */ + std::string generateNextImageId() const; + /** * @brief fetch the worksheet tables for sheetXmlNo, create the file if it does not exist * @param sheetXmlNo fetch for this sheet # @@ -343,6 +497,13 @@ namespace OpenXLSX */ XLTables sheetTables(uint16_t sheetXmlNo); + /** + * @brief Verify internal data integrity and class invariants + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyData(std::string* dbgMsg = nullptr) const; + public: /** * @brief validate whether sheetName is a valid Excel worksheet name @@ -352,6 +513,103 @@ namespace OpenXLSX */ bool validateSheetName(std::string sheetName, bool throwOnInvalid = false); + /** + * @brief Add an image entry to the archive + * @param imageFilename The filename for the image in the archive + * @param imageData The binary image data + * @param contentType The content type for the image + * @return true if successful, false otherwise + */ + bool addImageEntry(const std::string& imageFilename, const std::vector& imageData, XLContentType contentType); + + /** + * @brief Compare two documents for debugging + * @param other The other XLDocument to compare with + * @param diffMsg Optional pointer to append difference message (up to 16384 characters) + * @return 0 if identical, <0 if this precedes other, >0 if this follows other + */ + int compare(const XLDocument& other, std::string* diffMsg = nullptr) const; + + /** + * @brief Verify XML data integrity for all XML files in m_data + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyXLXmlData(std::string* dbgMsg = nullptr) const; + + /** + * @brief Add a drawing file to the archive + * @param drawingFilename The filename for the drawing in the archive + * @param drawingXml The drawing XML content + * @return true if successful, false otherwise + */ + bool addDrawingFile(const std::string& drawingFilename, const std::string& drawingXml); + + /** + * @brief Get XML data for a drawing file + * @param drawingFilename The filename for the drawing in the archive + * @return Pointer to XLXmlData, or nullptr if not found + */ + XLXmlData* getDrawingXmlData(const std::string& drawingFilename); + + /** + * @brief Get XML data for a relationships file + * @param relsFilename The filename for the relationships file in the archive + * @return Pointer to XLXmlData, or nullptr if not found + */ + XLXmlData* getRelationshipsXmlData(const std::string& relsFilename); + + /** + * @brief Update relationships XML data in-memory without archive access + * @param relsFilename The filename for the relationships file + * @param relsXml The relationships XML content + * @return true if successful, false otherwise + */ + bool updateRelationshipsXmlData(const std::string& relsFilename, const std::string& relsXml); + + /** + * @brief Add a relationships file to the archive + * @param relsFilename The filename for the relationships file in the archive + * @param relsXml The relationships XML content + * @return true if successful, false otherwise + */ + bool addRelationshipsFile(const std::string& relsFilename, const std::string& relsXml); + + /** + * @brief Check if a relationships file exists in the archive + * @param relsFilename The filename for the relationships file in the archive + * @return true if the file exists, false otherwise + */ + bool hasRelationshipsFile(const std::string& relsFilename) const; + + /** + * @brief Read the content of a relationships file from the archive + * @param relsFilename The filename for the relationships file in the archive + * @return The content of the file, or empty string if not found + */ + std::string readRelationshipsFile(const std::string& relsFilename) const; + + /** + * @brief Read the content of any file from the archive + * @param filePath The path of the file in the archive + * @return The content of the file, or empty string if not found + */ + std::string readFile(const std::string& filePath) const; + + /** + * @brief Delete an entry from the archive + * @param entryPath The path of the entry to delete + * @return true if the entry was deleted successfully, false otherwise + */ + bool deleteEntry(const std::string& entryPath); + + /** + * @brief Check if a binary file is referenced by any relationships in any worksheet + * @param imagePath The relative image path (e.g., "../media/image_3.gif") + * @return True if the file is referenced by at least one relationship in any worksheet + */ + bool isBinaryFileReferenced(const std::string& imagePath) const; + /** * @brief * @param command @@ -420,6 +678,24 @@ namespace OpenXLSX */ const XLXmlData* getXmlData(const std::string& path, bool doNotThrow = false) const; + /** + * @brief Load all XML files from the archive into m_data list + */ + void loadAllXmlFilesFromArchive(); + + public: + /** + * @brief Get the image manager for this document + * @return Const reference to the XLImageManager + */ + const XLImageManager& getImageManager() const { return m_imageManager; } + + /** + * @brief Get the image manager for this document + * @return Reference to the XLImageManager + */ + XLImageManager& getImageManager() { return m_imageManager; } + /** * @brief * @param path @@ -449,6 +725,7 @@ namespace OpenXLSX XLProperties m_coreProperties {}; /**< A pointer to the Core properties object*/ XLStyles m_styles {}; /**< A pointer to the document styles object*/ XLWorkbook m_workbook {}; /**< A pointer to the workbook object */ + XLImageManager m_imageManager {this}; /**< A pointer to the image manager object */ IZipArchive m_archive {}; /**< */ }; diff --git a/OpenXLSX/headers/XLDrawingML.hpp b/OpenXLSX/headers/XLDrawingML.hpp new file mode 100644 index 00000000..74dcf0b0 --- /dev/null +++ b/OpenXLSX/headers/XLDrawingML.hpp @@ -0,0 +1,250 @@ +/* + * XLDrawingML.hpp + * + * DrawingML (Drawing Markup Language) support for OpenXLSX + * This provides modern Excel-compatible drawing format instead of legacy VML + */ + +#ifndef OPENXLSX_XLDRAWINGML_HPP +#define OPENXLSX_XLDRAWINGML_HPP + +// ===== External Includes ===== // +#include +#include +#include +#include // std::pair + +// ===== OpenXLSX Includes ===== // +#include "XLXmlFile.hpp" +#include "XLCellReference.hpp" + +namespace OpenXLSX +{ + // Forward declarations + class XLEmbeddedImage; + class XLImageAnchor; + enum class XLMimeType : uint8_t; + + /** + * @brief Image anchor type enumeration + * @details Defines the types of image anchors supported in Excel worksheets + */ + enum class XLImageAnchorType : uint8_t { + OneCellAnchor, /**< Single cell anchor - image positioned relative to one cell */ + TwoCellAnchor, /**< Two cell anchor - image spans between two cells */ + Unknown /**< Unknown or unsupported anchor type */ + }; + + /** + * @brief Utility functions for XLImageAnchorType enum + */ + namespace XLImageAnchorUtils { + /** + * @brief Convert anchor type string to XLImageAnchorType enum + * @param anchorTypeStr Anchor type string (e.g., "oneCellAnchor", "twoCellAnchor") + * @return Corresponding XLImageAnchorType enum value + */ + XLImageAnchorType stringToAnchorType(const std::string& anchorTypeStr); + + /** + * @brief Convert XLImageAnchorType enum to anchor type string + * @param anchorType XLImageAnchorType enum value + * @return Corresponding anchor type string (e.g., "oneCellAnchor", "twoCellAnchor") + */ + std::string anchorTypeToString(XLImageAnchorType anchorType); + } + + /** + * @brief Class for managing DrawingML drawings in worksheets + */ + class OPENXLSX_EXPORT XLDrawingML : public XLXmlFile + { + public: + // ===== Constructors & Destructors ===== // + XLDrawingML() = default; + XLDrawingML(XLXmlData* xmlData); + ~XLDrawingML() = default; + + // ===== Copy & Move Constructors & Assignment Operators ===== // + XLDrawingML(const XLDrawingML& other) = default; + XLDrawingML(XLDrawingML&& other) noexcept = default; + XLDrawingML& operator=(const XLDrawingML& other) = default; + XLDrawingML& operator=(XLDrawingML&& other) noexcept = default; + + void addImage(const XLEmbeddedImage& embImage); + + // ===== Public Methods ===== // + /** + * @brief Get the number of images in the drawing + * @return Number of images + */ + size_t imageCount() const; + + /** + * @brief Verify internal data integrity and class invariants + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyData(std::string* dbgMsg = nullptr) const; + + /** + * @brief Get the XML root node for verification purposes + * @return XML root node (empty if invalid) + */ + XMLNode getRootNode() const; + + // ===== Static Utility Functions ===== // + + /** + * @brief Create XML anchor node from XLImageAnchor data + * @param rootNode The root XML node to append the anchor to + * @param imgAnchorInfo The anchor data to convert to XML + * @return The created anchor XML node + */ + static XMLNode createXMLNode(XMLNode rootNode, const XLImageAnchor& imgAnchorInfo); + + /** + * @brief Delete XML anchor node by relationship ID + * @param rootNode The root XML node to search in + * @param relationshipId The relationship ID to find and delete + * @return True if the anchor was found and deleted, false otherwise + */ + static bool deleteXMLNode(XMLNode rootNode, const std::string& relationshipId); + + /** + * @brief Parse XML anchor node into XLImageAnchor data + * @param anchorXMLNode The XML node containing anchor data + * @param imgAnchorInfo Pointer to XLImageAnchor to populate + * @return True if parsing was successful, false otherwise + */ + static bool parseXMLNode(const pugi::xml_node& anchorXMLNode, XLImageAnchor* imgAnchorInfo); + + private: + // ===== Private Methods ===== // + + /** + * @brief Calculate cell position in Excel units + * @param row Row number (1-based) + * @param column Column number (1-based) + * @return Position in Excel units + */ + uint32_t calculateCellPosition(uint32_t row, uint16_t column) const; + + }; + + // ========== XLImageAnchor Class ========== // + + /** + * @brief Represents data from and XML elements + * + * This class directly maps to the XML structure found in Excel's DrawingML + * anchor elements. All data members correspond exactly to XML attributes and + * text content, with no automatic coordinate conversions. + */ + class OPENXLSX_EXPORT XLImageAnchor + { + public: + // ===== Anchor Type and Identification ===== // + XLImageAnchorType anchorType; /**< OneCellAnchor or TwoCellAnchor */ + std::string imageId; /**< Worksheet-level image identifier (e.g., "1", "2") + from xdr:cNvPr id attribute. Maps to XLEmbeddedImage::m_id. + XML: */ + std::string relationshipId; /**< Relationship ID from a:blip r:embed attribute */ + + // ===== Cell Positioning (From Element) - Direct XML mapping ===== // + uint32_t fromRow; /**< Row number from xdr:row (0-based in XML) */ + uint16_t fromCol; /**< Column number from xdr:col (0-based in XML) */ + int32_t fromRowOffset; /**< Row offset from xdr:rowOff (in EMUs) */ + int32_t fromColOffset; /**< Column offset from xdr:colOff (in EMUs) */ + + // ===== Cell Positioning (To Element) - for twoCellAnchor ===== // + uint32_t toRow; /**< To row number from xdr:row (0-based in XML) */ + uint16_t toCol; /**< To column number from xdr:col (0-based in XML) */ + int32_t toRowOffset; /**< To row offset from xdr:rowOff (in EMUs) */ + int32_t toColOffset; /**< To column offset from xdr:colOff (in EMUs) */ + + // ===== Display Dimensions (xdr:ext element) ===== // + uint32_t displayWidthEMUs; /**< Display width from xdr:ext cx attribute (in EMUs) */ + uint32_t displayHeightEMUs; /**< Display height from xdr:ext cy attribute (in EMUs) */ + + // ===== Actual Image Dimensions (a:xfrm/a:ext element) ===== // + uint32_t actualWidthEMUs; /**< Actual image width from a:xfrm/a:ext cx (in EMUs) */ + uint32_t actualHeightEMUs; /**< Actual image height from a:xfrm/a:ext cy (in EMUs) */ + + // ===== Constructors ===== // + XLImageAnchor() = default; + + bool operator==( const XLImageAnchor& other ) const = default; + + void reset(); + + void initOneCell( const XLCellReference& cellRef, int32_t rowOffset = 0, + int32_t colOffset = 0); + + void initTwoCell( const XLCellReference& fromCellRef, + const XLCellReference& toCellRef, int32_t fromROffset = 0, + int32_t fromCOffset = 0, int32_t toROffset = 0, int32_t toCOffset = 0); + + + // ===== Utility Methods ===== // + + XLCellReference getFromCellReference() const; + + void setFromCellReference( const XLCellReference& fromCellRef ); + + XLCellReference getToCellReference() const; + + void setToCellReference( const XLCellReference& toCellRef ); + + void setDisplaySizeWithAspectRatio( + const uint32_t& widthPixels, const uint32_t& heightPixels, + const uint32_t& maxWidthEmus, const uint32_t& maxHeightEmus ); + + void setDisplaySizeWithAspectRatio( + const std::vector& imageData, const XLMimeType& mimeType, + const uint32_t& maxWidthEmus, const uint32_t& maxHeightEmus ); + + void setDisplaySizeWithAspectRatio( + const std::string& imageFileName, const XLMimeType& mimeType, + const uint32_t& maxWidthEmus, const uint32_t& maxHeightEmus ); + + /** + * @brief Check if this is a twoCellAnchor + * @return True if twoCellAnchor, false if oneCellAnchor + */ + bool isTwoCell() const; + + /** + * @brief Get display dimensions as a pair + * @return Pair of (width, height) in EMUs + */ + std::pair getDisplayDimensions() const; + + /** + * @brief Get actual image dimensions as a pair + * @return Pair of (width, height) in EMUs + */ + std::pair getActualDimensions() const; + + /** + * @brief Set actual image dimensions + * @param width Width in EMUs + * @param height Height in EMUs + */ + void setActualDimensions(uint32_t width, uint32_t height); + + /** + * @brief Convert EMUs to pixels for display dimensions + * @return Pair of (width, height) in pixels + */ + std::pair getDisplayDimensionsPixels() const; + + /** + * @brief Convert EMUs to pixels for actual dimensions + * @return Pair of (width, height) in pixels + */ + std::pair getActualDimensionsPixels() const; + }; +} + +#endif // OPENXLSX_XLDRAWINGML_HPP diff --git a/OpenXLSX/headers/XLImage.hpp b/OpenXLSX/headers/XLImage.hpp new file mode 100644 index 00000000..b56b9332 --- /dev/null +++ b/OpenXLSX/headers/XLImage.hpp @@ -0,0 +1,853 @@ +/* + + ____ ____ ___ ____ ____ ____ ___ + 6MMMMb `MM( )M' `MM' 6MMMMb\`MM( )M' + 8P Y8 `MM. d' MM 6M' ` `MM. d' +6M Mb __ ____ ____ ___ __ `MM. d' MM MM `MM. d' +MM MM `M6MMMMb 6MMMMb `MM 6MMb `MM. d' MM YM. `MM. d' +MM MM MM MM MM MM MM' MM `MMd MM YMMMMb `MMd +MM MM MM MM MMMMMMMM MM MM d'`MM. MM `Mb dMM. +YM M9 MM MM MM MM MM d' `MM. MM MM d'`MM. + 8b d8 MM. ,M9 YM d9 MM MM d' `MM. MM / L ,M9 d' `MM. + YMMMM9 MMYMMM9 YMMMM9 _MM_ _MM_M(_ _)MM_ _MMMMMMM MYMMMM9 _M(_ _)MM_ + MM + MM + _MM_ + + Copyright (c) 2018, Kenneth Troldal Balslev + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + - Neither the name of the author nor the + names of any contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + +#ifndef OPENXLSX_XLIMAGE_HPP +#define OPENXLSX_XLIMAGE_HPP + +// ===== External Includes ===== // +#include // uint8_t, uint16_t, uint32_t +#include // std::map +#include // std::string +#include // std::vector +#include // std::pair + +// ===== OpenXLSX Includes ===== // +#include "OpenXLSX-Exports.hpp" +#include "XLContentTypes.hpp" +#include "XLDrawingML.hpp" + +/* + * ================================================================================ + * IMAGE EMBEDDING RELATIONSHIP CREATION IN OPENXLSX + * ================================================================================ + * + * When images are embedded in Excel worksheets, OpenXLSX creates TWO types of + * relationships that work together to link worksheets → drawings → images. + * Each relationship type uses different ID management strategies appropriate + * to their scope and purpose. + * + * ┌─────────────────────────────────────────────────────────────────────────────┐ + * │ RELATIONSHIP CREATION FLOW │ + * ├─────────────────────────────────────────────────────────────────────────────┤ + * │ │ + * │ 1. Worksheet.addImage() called │ + * │ ↓ │ + * │ 2. drawingML() ensures drawing.xml exists │ + * │ ↓ │ + * │ 3. Creates Worksheet → Drawing relationship │ + * │ ↓ │ + * │ 4. addImageToDrawingML() adds image to drawing.xml │ + * │ ↓ │ + * │ 5. createDrawingRelationshipsFile() creates Drawing → Image relationships │ + * │ │ + * └─────────────────────────────────────────────────────────────────────────────┘ + * + * RELATIONSHIP TYPE 1: WORKSHEET-LEVEL RELATIONSHIPS + * ────────────────────────────────────────────────────────────────────────────── + * + * Location: Lines 1764-1778 in XLWorksheet::drawingML() + * + * Code: + * std::string drawingRelativePath = getPathARelativeToPathB(drawingFilename, getXmlPath()); + * XLRelationshipItem drawingRelationship; + * if (!m_relationships.targetExists(drawingRelativePath)) + * drawingRelationship = m_relationships.addRelationship(XLRelationshipType::Drawing, drawingRelativePath); + * else + * drawingRelationship = m_relationships.relationshipByTarget(drawingRelativePath); + * + * XMLNode drawing = appendAndGetNode(docElement, "drawing", m_nodeOrder); + * appendAndSetAttribute(drawing, "r:id", drawingRelationship.id()); + * + * Creates: + * File: xl/worksheets/_rels/sheet1.xml.rels + * Content: + * + * Purpose: Links worksheet to drawing.xml file + * Uses: XLRelationships::addRelationship() with intelligent ID management + * + * ID Management Strategy: + * - Uses GetNewRelsID() function with intelligent scanning + * - Checks existing relationships to avoid conflicts + * - Handles complex scenarios with multiple relationship types + * - Ensures uniqueness within the worksheet's relationship file + * + * RELATIONSHIP TYPE 2: DRAWING-LEVEL RELATIONSHIPS + * ────────────────────────────────────────────────────────────────────────────── + * + * Location: Lines 2147-2175 in XLWorksheet::createDrawingRelationshipsFile() + * + * Code: + * int relationshipId = 1; + * for (const auto& embImage : getEmbImages()) { + * std::string packageFilename = embImage.getImage()->filePackagePath(); + * std::string imageRelativePath = "../media/" + packageFilename.substr(packageFilename.find_last_of('/') + 1); + * + * relsXml += "\n"; + * relationshipId++; + * } + * + * Creates: + * File: xl/drawings/_rels/drawing1.xml.rels + * Content: + * + * Purpose: Links drawing.xml to image files + * Uses: Simple sequential counter (rId1, rId2, rId3...) + * + * ID Management Strategy: + * - Uses simple sequential numbering starting from 1 + * - Regenerates entire relationship file each time + * - No conflict checking needed (file is recreated) + * - Fast and predictable for drawing-specific relationships + * + * WHY TWO DIFFERENT STRATEGIES? + * ────────────────────────────────────────────────────────────────────────────── + * + * 1. SCOPE DIFFERENCES: + * - Worksheet relationships: Complex, shared namespace with other worksheet parts + * - Drawing relationships: Simple, isolated to drawing objects only + * + * 2. FREQUENCY OF CHANGES: + * - Worksheet relationships: Infrequent, stable (drawing, comments, tables) + * - Drawing relationships: Frequent, dynamic (images added/removed often) + * + * 3. COMPLEXITY REQUIREMENTS: + * - Worksheet relationships: Must avoid conflicts with existing relationships + * - Drawing relationships: Can use simple sequential approach + * + * 4. PERFORMANCE CONSIDERATIONS: + * - Worksheet relationships: Intelligent scanning for uniqueness + * - Drawing relationships: Fast regeneration of entire file + * + * RELATIONSHIP FILE STRUCTURE: + * ────────────────────────────────────────────────────────────────────────────── + * + * xl/worksheets/_rels/sheet1.xml.rels: + * + * + * + * + * + * + * xl/drawings/_rels/drawing1.xml.rels: + * + * + * + * + * + * + * KEY INSIGHT: + * Both relationship types can use the same ID (e.g., "rId1") because they exist + * in separate relationship files with independent namespaces. This is the + * fundamental principle of Excel's relationship scoping system. + * + * ================================================================================ + */ + +namespace OpenXLSX +{ + // Forward declarations + class XLDocument; + + // ========== Global Helper Functions ========== // + + /** + * @brief Helper function to append debug messages for debugging + * @param dbgMsg Optional pointer to append debug message (up to 16384 characters) + * @param msg The message to append + */ + inline void appendDbgMsg(std::string* dbgMsg, const std::string& msg) + { + if (dbgMsg && dbgMsg->length() < 16000) { // Leave some buffer + if (!dbgMsg->empty()){ + dbgMsg->append("\n "); + } + dbgMsg->append(msg); + } + } + + // ========== Excel Constants ========== // + + /** + * @brief Standard Excel cell height in EMUs (15 points × 12700 EMUs/point) + */ + constexpr uint32_t EXCEL_CELL_HEIGHT_EMUS = 190500; + + /** + * @brief Excel cell vertical spacing in EMUs (includes cell height + divider) + * This is approximately 198000 EMUs (cell height × 26/25) + */ + constexpr uint32_t EXCEL_CELL_Y_SPACING_EMUS = 198000; + + /** + * @brief Standard EMU-to-pixel conversion ratio (approximately) + * Note: This may vary based on screen resolution + */ + constexpr uint32_t EMU_TO_PIXEL_RATIO = 9525; + + /** + * @brief MIME type enumeration for image formats + * @details This enum provides type-safe representation of image MIME types, + * replacing string-based MIME types for better performance and type safety. + */ + enum class XLMimeType : uint8_t { + PNG, /**< PNG image format */ + JPEG, /**< JPEG image format */ + BMP, /**< BMP image format */ + GIF, /**< GIF image format */ + Unknown /**< Unknown or unsupported image format */ + }; + + /** + * @brief Class representing shared binary data and metadata for an image file + * @details This class stores metadata about an image and provides access to shared binary data + * through the parent document. It uses a hash-based approach to avoid storing + * duplicate binary data in memory. + */ + class OPENXLSX_EXPORT XLImage { + public: + /** + * @brief Default constructor + */ + XLImage() = default; + + /** + * @brief Constructor with all parameters + * @param parentDoc Pointer to the parent XLDocument + * @param imageDataHash Hash of the image binary data + * @param mimeType MIME type of the image + * @param extension File extension of the image + * @param filePackagePath Package path in archive + * @param widthPixels Width in pixels + * @param heightPixels Height in pixels + */ + XLImage(XLDocument* parentDoc, size_t imageDataHash, XLMimeType mimeType, + const std::string& extension, const std::string& filePackagePath, + uint32_t widthPixels, uint32_t heightPixels); + + /** + * @brief Get the image binary data + * @return Vector containing the image binary data + */ + std::vector getImageData() const; + + /** + * @brief Verify the image data integrity and metadata consistency + * @param dbgMsg Optional pointer to append debug message + * @return Number of issues found (0 = no issues) + */ + int verifyData(std::string* dbgMsg = nullptr) const; + + // Getters + XLDocument* parentDoc() const { return m_parentDoc; } + size_t imageDataHash() const { return m_imageDataHash; } + XLMimeType mimeType() const { return m_mimeType; } + const std::string& extension() const { return m_extension; } + const std::string& filePackagePath() const { return m_filePackagePath; } + uint32_t widthPixels() const { return m_widthPixels; } + uint32_t heightPixels() const { return m_heightPixels; } + + // Setters + void setParentDoc(XLDocument* parentDoc) { m_parentDoc = parentDoc; } + void setImageDataHash(size_t imageDataHash) { m_imageDataHash = imageDataHash; } + void setMimeType(XLMimeType mimeType) { m_mimeType = mimeType; } + void setExtension(const std::string& extension) { m_extension = extension; } + void setFilePackagePath(const std::string& filePackagePath) { m_filePackagePath = filePackagePath; } + void setWidthPixels(uint32_t widthPixels) { m_widthPixels = widthPixels; } + void setHeightPixels(uint32_t heightPixels) { m_heightPixels = heightPixels; } + + private: + XLDocument* m_parentDoc = nullptr; /**< Pointer to parent document */ + size_t m_imageDataHash = 0; /**< Hash of the image binary data */ + XLMimeType m_mimeType = XLMimeType::Unknown; /**< MIME type of the image */ + std::string m_extension; /**< File extension of the image */ + std::string m_filePackagePath; /**< Package path in archive */ + uint32_t m_widthPixels = 0; /**< Width in pixels */ + uint32_t m_heightPixels = 0; /**< Height in pixels */ + }; + + /** + * @brief Shared pointer type for XLImage + */ + using XLImageShPtr = std::shared_ptr; + + /** + * @brief Vector type for embedded images + */ + using XLEmbImgVec = std::vector; + + /** + * @brief Shared pointer type for embedded image vector + */ + using XLEmbImgVecShPtr = std::shared_ptr; + + /** + * @brief Class managing a collection of XLImage objects and caching XLEmbImgVec objects + * @details This class manages a collection of shared pointers to XLImage objects, + * providing functionality to find, add, and prune images based on usage. + * In addition, it keeps XLEmbImgVec containers for XLWorksheet's when + * the XLWorksheet's go out of scope. + */ + class OPENXLSX_EXPORT XLImageManager { + public: + /** + * @brief Constructor + * @param parentDoc Pointer to the parent XLDocument + */ + explicit XLImageManager(XLDocument* parentDoc); + + /** + * @brief Destructor + */ + ~XLImageManager() = default; + + /** + * @brief Remove unused images from the collection and archive + * @details For each XLImageShPtr image in images, if image.get() == nullptr or + * image->use_count() <= 1, then remove image from images and remove + * image from archive by calling parentDoc->deleteEntry() + */ + void prune(); + + /** + * @brief Find or add an image to the collection + * @param packagePath The image package path (e.g., "xl/media/image1.png") or empty string to generate new + * @param imageData The image binary data + * @param contentType The content type of the image + * @return Shared pointer to the XLImage object + * @details If image already exists (findImageByImageData), return image from images. + * Otherwise, call parentDoc->addImageEntry() and add image to images. + */ + XLImageShPtr findOrAddImage(const std::string& packagePath, + const std::vector& imageData, + XLContentType contentType); + + /** + * @brief Find an image by its binary data hash + * @param imageData The image binary data to search for + * @return Shared pointer to the XLImage object, or nullptr if not found + */ + XLImageShPtr findImageByImageData(const std::vector& imageData) const; + + + /** + * @brief Generate a unique package filename for an image + * @param extension The file extension (e.g., ".png", ".jpg") + * @return A unique package filename (e.g., "xl/media/image1.png") + * @details Generates sequential filenames like Excel does, but ensures uniqueness + * across all extensions (image1.jpg, image2.png, image3.gif, etc.) + * Uses O(n log n) efficiency with sorted vector and binary search. + * Relies on XLImageManager being the authoritative source for all image paths. + */ + std::string generateUniquePackageFilename(const std::string& extension) const; + + + /** + * @brief Find an image by its file package path + * @param filePackagePath The file package path to search for + * @return Shared pointer to the XLImage object, or nullptr if not found + */ + XLImageShPtr findImageByFilePackagePath(const std::string& filePackagePath) const; + + /** + * @brief Get the number of images in the collection + * @return Number of images + */ + size_t getImageCount() const; + + /** + * @brief Get iterator to the beginning of the images collection + * @return Const iterator to the beginning + */ + std::vector::const_iterator begin() const; + + /** + * @brief Get iterator to the end of the images collection + * @return Const iterator to the end + */ + std::vector::const_iterator end() const; + + /** + * @brief Remove embedded image vector for worksheet + * @param sheetName The name of the worksheet + */ + void eraseEmbImgsForSheetName(const std::string& sheetName); + + /** + * @brief Find or create embedded image vector for worksheet + * @param sheetName The name of the worksheet + * @return Shared pointer to the embedded image vector + */ + XLEmbImgVecShPtr getEmbImgsForSheetName(const std::string& sheetName); + + /** + * @brief Find embedded image vector for worksheet, return null pointer if not found + * @param sheetName The name of the worksheet + * @return Shared pointer to the embedded image vector, or nullptr if not found + */ + XLEmbImgVecShPtr getEmbImgsForSheetName(const std::string& sheetName) const; + + /** + * @brief Verify data integrity for all images + * @param dbgMsg Optional pointer to append debug message + * @return Number of issues found (0 = no issues) + * @details Call verifyData() for each non-null image, check each image has same parentDoc, + * check each image has unique imageData + */ + int verifyData(std::string* dbgMsg = nullptr) const; + + /** + * @brief Debug function to print reference counts and brief info for all XLImageShPtr objects + * @param title Optional title for the debug output + */ + void printReferenceCounts(const std::string& title = "XLImageManager Reference Counts") const; + + private: + XLDocument* m_parentDoc; /**< Pointer to parent document */ + std::vector m_images; /**< Collection of image shared pointers */ + std::map m_sheetNmEmbImgVMap; /* cache embedded images for each worksheet */ + }; + + /** + * @brief The XLEmbeddedImage class represents an image that can be embedded in a worksheet + */ + class OPENXLSX_EXPORT XLEmbeddedImage + { + public: + /** + * @brief Default constructor + */ + XLEmbeddedImage() = default; + + /** + * @brief Copy constructor + * @param other The XLEmbeddedImage to copy + */ + XLEmbeddedImage(const XLEmbeddedImage& other) = default; + + /** + * @brief Move constructor + * @param other The XLEmbeddedImage to move + */ + XLEmbeddedImage(XLEmbeddedImage&& other) noexcept = default; + + /** + * @brief Destructor + */ + ~XLEmbeddedImage() = default; + + /** + * @brief Copy assignment operator + * @param other The XLEmbeddedImage to copy + * @return Reference to this XLEmbeddedImage + */ + XLEmbeddedImage& operator=(const XLEmbeddedImage& other) = default; + + /** + * @brief Move assignment operator + * @param other The XLEmbeddedImage to move + * @return Reference to this XLEmbeddedImage + */ + XLEmbeddedImage& operator=(XLEmbeddedImage&& other) noexcept = default; + + bool operator==( const XLEmbeddedImage& other ) const = default; + + /** + * @brief Set the image ID + * @param imageId The unique ID for this image + */ + void setId(const std::string& imageId); + + /** + * @brief Get the image data + * @return Reference to the image data vector + */ + std::vector data() const; + + /** + * @brief Get the file extension + * @return The file extension (e.g., ".png", ".jpg") + */ + const std::string& extension() const; + + /** + * @brief Get the unique image ID + * @return The unique image ID + */ + const std::string& id() const; + + /** + * @brief Get the image width in pixels + * @return The width in pixels + */ + uint32_t widthPixels() const; + + /** + * @brief Get the image height in pixels + * @return The height in pixels + */ + uint32_t heightPixels() const; + + /** + * @brief Get the image MIME type + * @return The MIME type as enum + */ + XLMimeType mimeType() const; + + /** + * @brief Set the display width in Excel units (EMUs) + * @param width The display width in EMUs + */ + void setDisplayWidthEMUs(uint32_t width); + + /** + * @brief Set the display height in Excel units (EMUs) + * @param height The display height in EMUs + */ + void setDisplayHeightEMUs(uint32_t height); + + /** + * @brief Get the display width in Excel units (EMUs) + * @return The display width in EMUs + */ + uint32_t displayWidthEMUs() const; + + /** + * @brief Get the display height in Excel units (EMUs) + * @return The display height in EMUs + */ + uint32_t displayHeightEMUs() const; + + /** + * @brief Set display size in pixels (converts to EMUs automatically) + * @param widthPixels The display width in pixels + * @param heightPixels The display height in pixels + */ + void setDisplaySizePixels(uint32_t widthPixels, uint32_t heightPixels); + + /** + * @brief Check if the image is valid + * @return True if the image has valid data + */ + bool isValid() const; + + /** + * @brief Get the content type for this image + * @return The XLContentType enum value + */ + XLContentType contentType() const; + + /** + * @brief Compare two images for debugging + * @param other The other XLEmbeddedImage to compare with + * @param diffMsg Optional pointer to append difference message (up to 16384 characters) + * @return 0 if identical, <0 if this precedes other, >0 if this follows other + */ + int compare(const XLEmbeddedImage& other, std::string* diffMsg = nullptr) const; + + /** + * @brief Verify internal data integrity and class invariants + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyData(std::string* dbgMsg = nullptr) const; + + // Registry/relationship setter functions (for XLImageInfo compatibility) + /** + * @brief Set the relationship ID + * @param relationshipId The relationship ID in drawing.xml.rels + */ + void setRelationshipId(const std::string& relationshipId); + + /** + * @brief Set the anchor cell reference + * @param anchorCell The primary anchor cell reference (e.g., "A1", "B5") + */ + void setAnchorCell(const std::string& anchorCell); + + + /** + * @brief Set the anchor type + * @param anchorType The anchor type enum + */ + void setAnchorType(XLImageAnchorType anchorType); + + /** + * @brief Convert row/column coordinates to Excel cell reference + * @param row Row number (0-based) + * @param col Column number (0-based) + * @return Excel cell reference (e.g., "A1", "B5") + */ + static std::string rowColToCellRef(uint32_t row, uint16_t col); + + /** + * @brief Convert Excel cell reference to row/column coordinates + * @param cellRef Excel cell reference (e.g., "A1", "B5") + * @return Pair of (row, col) coordinates (0-based) + */ + static std::pair cellRefToRowCol(const std::string& cellRef); + + // Registry/relationship getter functions (for XLImageInfo compatibility) + /** + * @brief Get the relationship ID + * @return The relationship ID string + */ + const std::string& relationshipId() const; + + /** + * @brief Get the anchor cell reference + * @return The anchor cell reference string + */ + std::string anchorCell() const; + + /** + * @brief Get the anchor type + * @return anchor type + */ + const XLImageAnchorType& anchorType() const; + + /** + * @brief Get the shared pointer to the image data + * @return Shared pointer to the XLImage + */ + XLImageShPtr getImage() const; + + /** + * @brief Set the shared pointer to the image data + * @param image Shared pointer to the XLImage + */ + void setImage(XLImageShPtr image); + + const XLImageAnchor& getImageAnchor() const{ return m_imageAnchor; } + + void setImageAnchor(const XLImageAnchor& a){ m_imageAnchor = a; } + + /** + * @brief Check if this image is empty (default constructed) + * @return True if empty, false otherwise + */ + bool isEmpty() const; + + private: + XLImageShPtr m_image; /**< Shared pointer to the image data */ + XLImageAnchor m_imageAnchor; /**< Image anchor information (positioning, IDs, display dimensions) */ + + /** + * @brief Generate a unique image ID + * @return A unique image ID string + */ + static std::string generateId(uint32_t imageNumber); + }; + + /** + * @brief Utility class for image operations and test data + */ + class OPENXLSX_EXPORT XLImageUtils + { + public: + // Static const sample binary data for testing + static const std::vector red1x1PNGData; + static const std::vector png31x15Data; + static const std::vector jpeg32x18Data; + static const std::vector bmp33x16Data; + static const std::vector gif26x16Data; + + /** + * @brief Detect MIME type from binary image data and return as enum + * @param data Binary image data + * @return Corresponding XLMimeType enum value + */ + static XLMimeType detectMimeType(const std::vector& data); + + /** + * @brief Convert XLMimeType enum to MIME type string + * @param mimeType XLMimeType enum value + * @return Corresponding MIME type string (e.g., "image/png") + */ + static std::string mimeTypeToString(XLMimeType mimeType); + + /** + * @brief Convert MIME type string to XLMimeType enum + * @param mimeType MIME type string (e.g., "image/png") + * @return Corresponding XLMimeType enum value + */ + static XLMimeType stringToMimeType(const std::string& mimeType); + + /** + * @brief Determine file extension from MIME type + * @param mimeType MIME type string + * @return File extension string + */ + static std::string mimeTypeToExtension(XLMimeType mimeType); + + /** + * @brief Determine MIME type from file extension + * @param extension File extension + * @return MIME type + */ + static XLMimeType extensionToMimeType(const std::string& extension); + + /** + * @brief Convert MIME type to XLContentType enum + * @param mimeType MIME type enum + * @return Corresponding XLContentType enum value + */ + static XLContentType mimeTypeToContentType(const XLMimeType& mimeType); + + /** + * @brief Convert XLContentType enum to MIME type + * @param contentType XLContentType enum value + * @return Corresponding MIME type + */ + static XLMimeType contentTypeToMimeType(XLContentType contentType); + + /** + * @brief Convert pixels to EMUs (Excel units) + * @param pixels Number of pixels + * @return Equivalent EMUs + @deprecated -- duplicate of pixelsToEMUs() + */ + static uint32_t pixelsToEmus(uint32_t pixels); + + /** + * @brief Convert EMUs to pixels + * @param emus Number of EMUs + * @return Equivalent pixels + */ + static uint32_t emusToPixels(uint32_t emus); + + /** + * @brief Convert points to EMUs + * @param points Point value + * @return EMU value + */ + static uint32_t pointsToEmus(double points); + + /** + * @brief Calculate aspect ratio from dimensions + * @param width Width in pixels + * @param height Height in pixels + * @return Aspect ratio (width/height) + */ + static double calculateAspectRatio(uint32_t width, uint32_t height); + + /** + * @brief Get image dimensions from binary data + * @param data Binary image data + * @param mimeType MIME type of the image + * @return Pair of (width, height) in pixels + */ + static std::pair getImageDimensions(const std::vector& data, XLMimeType mimeType); + + /** + * @brief Get image dimensions from binary data (auto-detect MIME type) + * @param data Binary image data + * @return Pair of (width, height) in pixels, or {0,0} if detection fails + */ + static std::pair getImageDimensions(const std::vector& data); + + /** + * @brief Check if image dimensions are reasonable + * @param width Width in pixels + * @param height Height in pixels + * @return True if dimensions are within reasonable bounds + */ + static bool isValidImageSize(uint32_t width, uint32_t height); + + /** + * @brief Validate image data integrity + * @param data Binary image data + * @param mimeType MIME type of the image + * @return True if image data is valid + */ + static bool validateImageData(const std::vector& data, const XLMimeType& mimeType); + + /** + * @brief Calculate hash value for binary image data + * @param imageData Binary image data as vector + * @return Hash value for the image data + */ + static size_t imageDataHash(const std::vector& imageData); + + /** + * @brief Calculate hash value for image data stored as string + * @param imageDataStr Binary image data as string + * @return Hash value for the image data (same as imageDataHash for equivalent data) + */ + static size_t imageDataStrHash(const std::string& imageDataStr); + + /** + * @brief Match XML element name with target name (handles namespace prefixes) + * @param elementName The XML element name (e.g., "xdr:oneCellAnchor") + * @param targetName The target name to match (e.g., "oneCellAnchor") + * @return True if the element name matches the target name + */ + static bool matchesElementName(const std::string& elementName, const std::string& targetName); + + /** + * @brief Get file extension from MIME type + * @param mime MIME type string + * @return File extension (e.g., ".png") + */ + static std::string extensionFromMime(const std::string& mime); + + /** + * @brief Convert pixels to EMUs (Excel Measurement Units) + * @param pixels Number of pixels + * @return Number of EMUs + */ + static uint32_t pixelsToEMUs(uint32_t pixels); + + /** + * @brief Convert EMUs to Excel units + * @param emus EMU value + * @return Excel unit value + */ + static uint32_t emusToExcelUnits(uint32_t emus); + }; + +} // namespace OpenXLSX + +#endif // OPENXLSX_XLIMAGE_HPP diff --git a/OpenXLSX/headers/XLRelationships.hpp b/OpenXLSX/headers/XLRelationships.hpp index bb572ab6..fba60078 100644 --- a/OpenXLSX/headers/XLRelationships.hpp +++ b/OpenXLSX/headers/XLRelationships.hpp @@ -62,6 +62,78 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM. #include "XLXmlFile.hpp" #include "XLXmlParser.hpp" +/* + * ================================================================================ + * RELATIONSHIP ID SCOPING IN OPENXLSX + * ================================================================================ + * + * In .xlsx files, relationship IDs are scoped to specific XML parts and exist in + * separate namespaces. Each XML part has its own corresponding .rels file containing + * relationship definitions for that specific part. + * + * ┌─────────────────────────────────────────────────────────────────────────────┐ + * │ HIERARCHICAL RELATIONSHIP STRUCTURE │ + * ├─────────────────────────────────────────────────────────────────────────────┤ + * │ │ + * │ _rels/.rels (Package-level relationships) │ + * │ ├─ rId1 → xl/workbook.xml │ + * │ ├─ rId2 → docProps/core.xml │ + * │ └─ rId3 → docProps/app.xml │ + * │ │ + * │ xl/_rels/workbook.xml.rels (Workbook-level relationships) │ + * │ ├─ rId1 → worksheets/sheet1.xml │ + * │ ├─ rId2 → worksheets/sheet2.xml │ + * │ ├─ rId3 → sharedStrings.xml │ + * │ ├─ rId4 → styles.xml │ + * │ └─ rId5 → theme/theme1.xml │ + * │ │ + * │ xl/worksheets/_rels/sheet1.xml.rels (Worksheet-level relationships) │ + * │ ├─ rId1 → ../drawings/drawing1.xml │ + * │ ├─ rId2 → ../comments/comments1.xml │ + * │ └─ rId3 → ../tables/table1.xml │ + * │ │ + * │ xl/drawings/_rels/drawing1.xml.rels (Drawing-level relationships) │ + * │ ├─ rId1 → ../media/image_img1.png │ + * │ ├─ rId2 → ../media/image_img2.jpg │ + * │ └─ rId3 → ../media/image_img3.gif │ + * │ │ + * └─────────────────────────────────────────────────────────────────────────────┘ + * + * RELATIONSHIP ID USAGE IN OPENXLSX: + * + * 1. DOCUMENT-LEVEL RELATIONSHIPS (XLRelationships class): + * - Location: _rels/.rels, xl/_rels/workbook.xml.rels, etc. + * - Purpose: Link core document components (worksheets, styles, themes) + * - ID Generation: Uses GetNewRelsID() with intelligent scanning + * - Scope: Per relationship file + * - Example: rId1 in workbook.xml.rels ≠ rId1 in sheet1.xml.rels + * + * 2. DRAWING-LEVEL RELATIONSHIPS (createDrawingRelationshipsFile): + * - Location: xl/drawings/_rels/drawing1.xml.rels, etc. + * - Purpose: Link drawing objects (images, shapes) to media files + * - ID Generation: Simple sequential counter (rId1, rId2, rId3...) + * - Scope: Per drawing file + * - Example: rId1 in drawing1.xml.rels ≠ rId1 in drawing2.xml.rels + * + * 3. WORKSHEET-LEVEL RELATIONSHIPS (XLWorksheet relationships): + * - Location: xl/worksheets/_rels/sheet1.xml.rels, etc. + * - Purpose: Link worksheet-specific parts (drawings, comments, tables) + * - ID Generation: Uses XLRelationships::addRelationship() + * - Scope: Per worksheet + * + * KEY PRINCIPLES: + * - Each relationship file maintains its own ID namespace + * - IDs are unique only within their respective .rels file + * - Different relationship types use different management strategies + * - Document relationships use complex ID management + * - Drawing relationships use simple sequential numbering + * + * This scoping system ensures that relationship IDs don't conflict across + * different parts of the Excel document structure. + * + * ================================================================================ + */ + namespace OpenXLSX { /** @@ -330,6 +402,13 @@ namespace OpenXLSX { */ void print(std::basic_ostream& ostr) const; + /** + * @brief Verify internal data integrity and class invariants + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyData(std::string* dbgMsg = nullptr) const; + // ---------- Protected Member Functions ---------- // protected: diff --git a/OpenXLSX/headers/XLRowData.hpp b/OpenXLSX/headers/XLRowData.hpp index 6c489ea8..416b9dbb 100644 --- a/OpenXLSX/headers/XLRowData.hpp +++ b/OpenXLSX/headers/XLRowData.hpp @@ -283,12 +283,12 @@ namespace OpenXLSX // ===== If the container value_type is XLCellValue, the values can be copied directly. if constexpr (std::is_same_v) { // ===== First, delete the values in the first N columns. - deleteCellValues(values.size()); // 2024-04-30: whitespace support + deleteCellValues(static_cast(values.size())); // 2024-04-30: whitespace support // ===== Then, prepend new cell nodes to current row node auto colNo = values.size(); for (auto value = values.rbegin(); value != values.rend(); ++value) { // NOLINT - prependCellValue(*value, colNo); // 2024-04-30: whitespace support: this is safe because only prependCellValue (with + prependCellValue(*value, static_cast(colNo)); // 2024-04-30: whitespace support: this is safe because only prependCellValue (with // whitespace support) touches the row data --colNo; } @@ -296,7 +296,7 @@ namespace OpenXLSX // ===== If the container value_type is a POD type, use the overloaded operator= on each cell. else { - auto range = XLRowDataRange(*m_rowNode, 1, values.size(), getSharedStrings()); + auto range = XLRowDataRange(*m_rowNode, 1, static_cast(values.size()), getSharedStrings()); auto dst = range.begin(); // 2024-04-30: whitespace support: safe because XLRowDataRange::begin invokes whitespace-safe // getCellNode for column 1 auto src = values.begin(); diff --git a/OpenXLSX/headers/XLSharedStrings.hpp b/OpenXLSX/headers/XLSharedStrings.hpp index 9f695bde..8e87d805 100644 --- a/OpenXLSX/headers/XLSharedStrings.hpp +++ b/OpenXLSX/headers/XLSharedStrings.hpp @@ -133,7 +133,7 @@ namespace OpenXLSX * @brief return the amount of shared string entries currently in the cache * @return */ - int32_t stringCount() const { return m_stringCache->size(); } + int32_t stringCount() const { return static_cast( m_stringCache->size() ); } /** * @brief diff --git a/OpenXLSX/headers/XLSheet.hpp b/OpenXLSX/headers/XLSheet.hpp index 78e61862..bd45c3a5 100644 --- a/OpenXLSX/headers/XLSheet.hpp +++ b/OpenXLSX/headers/XLSheet.hpp @@ -75,6 +75,8 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM. #include "XLRow.hpp" #include "XLStyles.hpp" // XLStyleIndex #include "XLTables.hpp" // XLTables +#include "XLImage.hpp" // XLImage +#include "XLDrawingML.hpp" // XLDrawingML #include "XLXmlFile.hpp" namespace OpenXLSX @@ -909,6 +911,14 @@ namespace OpenXLSX */ XLWorksheet& operator=(XLWorksheet&& other); + /** + Compare two worksheets -- for debugging + Result = 0 => worksheets same + Result < 0 => this worksheet precedes other, in a sorted list -- (arbitrary sorting method) + @param diffMsg (optional) if result is non-zero, message exlaining difference will be appended here, up to maximum of 16384 charcaters + */ + int compare(const XLWorksheet& other, std::string *diffMsg = nullptr) const; + /** * @brief * @param ref @@ -1251,6 +1261,18 @@ namespace OpenXLSX */ XLVmlDrawing& vmlDrawing(); + /** + * @brief Get the DrawingML object for this worksheet + * @return Reference to the DrawingML object + */ + XLDrawingML& drawingML(); + + /** + * @brief Get the DrawingML object for this worksheet (const version) + * @return Reference to the DrawingML object + */ + const XLDrawingML& drawingML() const; + /** * @brief fetch a reference to the worksheet comments */ @@ -1261,7 +1283,305 @@ namespace OpenXLSX */ XLTables& tables(); + //---------------------------------------------------------------------------------------------------------------------- + // Image Methods + //---------------------------------------------------------------------------------------------------------------------- + + /* + Construct oneCellAnchor XLImageAnchor from cellRef. + Construct XLImage from imageData and mimeType + return Image ID + */ + std::string embedImageFromImageData( const XLCellReference& cellRef, + const std::vector& imageData, + XLMimeType mimeType = XLMimeType::Unknown ); + + /* + Construct XLImage from imageData and mimeType + return Image ID + */ + std::string embedImageFromImageData( const XLImageAnchor& imageAnchor, + const std::vector& imageData, + XLMimeType mimeType = XLMimeType::Unknown ); + + /* + Construct oneCellAnchor XLImageAnchor from cellRef. + Construct XLImage from imageFileName and mimeType + return Image ID + */ + std::string embedImageFromFile( const XLCellReference& cellRef, + const std::string& imageFileName, + XLMimeType mimeType = XLMimeType::Unknown ); + + /* + Construct XLImage from imageFileName and mimeType + return Image ID + */ + std::string embedImageFromFile( const XLImageAnchor& imageAnchor, + const std::string& imageFileName, + XLMimeType mimeType = XLMimeType::Unknown ); + + /** + * @brief Get the number of images in the worksheet + * @return Number of images + */ + size_t imageCount() const; + + /** + * @brief Check if the worksheet has any images + * @return True if the worksheet has images, false otherwise + */ + bool hasImages() const; + + /** + * @brief Check if the worksheet has images by examining the XML directly + * @return True if the worksheet XML contains a element, false otherwise + * @note This method does not modify the document and can be called safely on temporary objects + */ + bool hasImagesInXML() const; + + /** + * @brief Generate the next available image ID for this worksheet + * @return The next globally unique image ID (e.g., "img1", "img2", etc.) + * @note This uses the document-level counter to ensure uniqueness across all worksheets + */ + std::string generateNextImageId() const; + + /** + * @brief Generate a relationship ID based on the current image count + * @return A relationship ID string (e.g., "rId1", "rId2", etc.) + * @note This generates IDs for DrawingML XML based on the current number of images + */ + std::string getNextUnusedRelationshipId() const; + + //---------------------------------------------------------------------------------------------------------------------- + // Image Query Methods + //---------------------------------------------------------------------------------------------------------------------- + + /** + * @brief Get specific embedded image by its image ID + * @param imageId The unique image ID (e.g., "img1", "img2") + * @return Const reference to XLEmbeddedImage object, or empty XLEmbeddedImage if not found + * @note Returns a const reference for efficiency - no copying of the object + */ + const XLEmbeddedImage& getEmbImageByImageID(const std::string& imageId) const; + + /** + * @brief Get specific embedded image by its relationship ID + * @param relationshipId The relationship ID in drawing.xml.rels (e.g., "rId1", "rId2") + * @return Const reference to XLEmbeddedImage object, or empty XLEmbeddedImage if not found + * @note Returns a const reference for efficiency - no copying of the object + */ + const XLEmbeddedImage& getEmbImageByRelationshipId(const std::string& relationshipId) const; + + /** + * @brief Get all embedded images at a specific cell + * @param cellRef The cell reference (e.g., "A1", "B5") + * @return Vector of XLEmbeddedImage objects at that cell + */ + std::vector getEmbImagesAtCell(const std::string& cellRef) const; + + /** + * @brief Get all embedded images in a specific range + * @param cellRange The cell range (e.g., "A1:B5", "C10:D20") + * @return Vector of XLEmbeddedImage objects in that range + */ + std::vector getEmbImagesInRange(const std::string& cellRange) const; + + /** + * @brief Get the embedded images vector, ensuring it's initialized + * @return Reference to the embedded images vector + * @details This function ensures m_embImages is properly initialized from XLImageManager + */ + std::vector& getEmbImages(); + + /** + * @brief Get the embedded images vector, ensuring it's initialized (const version) + * @return Const reference to the embedded images vector + * @details This function ensures m_embImages is properly initialized from XLImageManager + */ + const std::vector& getEmbImages() const; + + + //---------------------------------------------------------------------------------------------------------------------- + // Image Modification Methods + //---------------------------------------------------------------------------------------------------------------------- + + /** + * @brief Remove an image by its ID + * @param imageId The unique image identifier to remove + * @return True if the image was found and removed, false otherwise + * @note This will update the image registry and remove the image from DrawingML + */ + bool removeImageByImageID(const std::string& imageId); + + /** + * @brief Remove an image by its relationship ID + * @param relationshipId The relationship ID to remove + * @return True if the image was found and removed, false otherwise + * @note This will update the image registry and remove the image from DrawingML + */ + bool removeImageByRelationshipId(const std::string& relationshipId); + + /** + * @brief Remove all images from the worksheet + * @note This will clear the image registry and remove all images from DrawingML + */ + void clearImages(); + + //---------------------------------------------------------------------------------------------------------------------- + // Image Removal Helper Functions (Private) + //---------------------------------------------------------------------------------------------------------------------- + private: + /* add Embedded Image, assuming imageAnchor and image are ready to add */ + std::string embedImage( const XLImageAnchor& imageAnchor, XLImageShPtr image ); + + /** + * @brief Remove image node from DrawingML XML (in-memory document) + * @param relationshipId The relationship ID of the image to remove + * @return True if the image node was found and removed + */ + bool removeImageFromDrawingXML(const std::string& relationshipId) const; + + /** + * @brief Remove image relationship from relationships file (in-memory document) + * @param relationshipId The relationship ID to remove + * @return True if the relationship was found and removed + */ + bool removeImageFromRelationships(const std::string& relationshipId) const; + + /** + * @brief Remove the element from worksheet XML if there are no more images + * @note This is a helper function called after removing images to clean up the XML + */ + void removeDrawingElementIfEmpty(); + + /** + * @brief Remove image file from archive (delete binary file entry) + * @param relationshipId The relationship ID of the image file to remove + * @return True if the file was found and removed from archive + */ + + /** + * @brief Get image path from relationship ID + * @param relationshipId The relationship ID to look up + * @return The image path, or empty string if not found + */ + std::string getImagePathFromRelationship(const std::string& relationshipId) const; + + /** + * @brief Remove image file by direct path + * @param imagePath The relative image path (e.g., "../media/image_img3.png") + * @return True if the file was found and removed + */ + + public: + //---------------------------------------------------------------------------------------------------------------------- + // Image Registry Management + //---------------------------------------------------------------------------------------------------------------------- + + /** + * @brief Refresh the image registry from DrawingML XML + * @note This method parses the DrawingML XML and rebuilds the image registry. + * The registry is automatically refreshed when needed (when images are queried + * and the registry is empty), but this method can be called manually if needed. + */ + void refreshImageRegistry() const; + + /** + * @brief Populate m_images vector from existing XML data + * @note This method calls refreshImageRegistry() to get registry data, + * then converts registry entries to XLImage objects and populates m_images. + * Only populates if m_images is empty to avoid duplicates. + */ + void populateImagesFromXML(); + + /** + * @brief Validate image registry for data integrity + * @return True if registry is valid (no duplicate IDs), false otherwise + * @note Checks for duplicate image IDs, relationship IDs, and other corruption + */ + bool validateImageRegistry() const; + + /** + * @brief Check if an image ID already exists + * @param imageId The image ID to check + * @return True if the ID exists, false otherwise + */ + bool hasEmbImageWithId(const std::string& imageId) const; + + /** + * @brief Check if a relationship ID already exists + * @param relationshipId The relationship ID to check + * @return True if the ID exists, false otherwise + */ + bool hasImageWithRelationshipId(const std::string& relationshipId) const; + + //---------------------------------------------------------------------------------------------------------------------- + // Private Helper Methods for Image Registry + //---------------------------------------------------------------------------------------------------------------------- + + private: + /** + * @brief Read the drawing relationships file to map relationship IDs to image files + * @param relationshipMap Output map of relationship ID to image file path + */ + void readDrawingRelationships(std::map& relationshipMap) const; + + /** + * @brief Process a single XLImageAnchor into an XLEmbeddedImage and add to m_embImages + * @param anchorInfo The parsed anchor information + * @param relationshipMap Map of relationship IDs to image files + */ + void processAnchorToEmbeddedImage(const XLImageAnchor& anchorInfo, const std::map& relationshipMap); + + /** + * @brief Extract image ID from XML pic element + * @param pic The pic XML node containing the image information + * @return The image ID from the cNvPr id attribute + */ + std::string extractImageIdFromXML(const pugi::xml_node& pic) const; + + /** + * @brief Convert EMUs to pixels (approximate conversion) + * @param emus EMUs value + * @return Approximate pixel value + @deprected -> move to XLImageUtils + */ + uint32_t emusToPixels(uint32_t emus) const; + + /** + * @brief Create the drawing relationships file + * @param drawingFilename The drawing filename + */ + void createDrawingRelationshipsFile(const std::string& drawingFilename); + + /** + * @brief Create an empty DrawingML object for worksheets without images + * This is needed when embedImage() is called on a worksheet that doesn't have images yet + * @return Reference to the newly created DrawingML object + */ + XLDrawingML& createEmptyDrawingML(); + + void addImageRelationship(const XLEmbeddedImage& image); + + + /** + * @brief Add a relationship for a new image to the existing relationships file + * @param image The image to add a relationship for + * @param relationshipId The relationship ID to use +* @deprecated + */ + void addImageRelationship(const XLEmbeddedImage& image, const std::string& relationshipId); + + /** + * @brief Helper function to create new relationships XML with a single relationship + * @param relationshipId The relationship ID to use + * @param imageRelativePath The relative path to the image file + * @return The complete XML string for the relationships file + */ + std::string createNewRelationshipsXml(const std::string& relationshipId, const std::string& imageRelativePath); /** * @brief fetch the # number from the xml path xl/worksheets/sheet#.xml @@ -1309,12 +1629,91 @@ namespace OpenXLSX */ bool setActive_impl(); + public: + /** + * @brief Get all image IDs in this worksheet + * @return Vector of all image ID strings, sorted and unique (duplicates removed) + */ + std::vector getImageIDs() const; + + /** + * @brief Verify internal data integrity and class invariants + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyData(std::string* dbgMsg = nullptr) const; + + /** + * @brief Verify that all image IDs are unique within this worksheet + * @param dbgMsg Optional pointer to append debug message explaining any duplicate IDs found + * @return Number of duplicate IDs found (0 = all IDs are unique) + */ + int verifyUniqueImageIDs(std::string* dbgMsg = nullptr) const; + + /** + * @brief Verify DrawingML XML consistency with image registry data + * @param dbgMsg Optional pointer to append debug message explaining any inconsistencies found + * @return Number of inconsistencies found (0 = XML and registry are consistent) + */ + int verifyDrawingMLConsistency(std::string* dbgMsg = nullptr) const; + + /** + * @brief Verify m_images vector consistency with DrawingML XML data + * @param dbgMsg Optional pointer to append debug message explaining any inconsistencies found + * @return Number of inconsistencies found (0 = m_images and XML are consistent) + */ + int verifyImagesVectorConsistency(std::string* dbgMsg = nullptr) const; + + /** + * @brief Verify that binary image data exists in the archive + * @param dbgMsg Optional pointer to append debug message explaining any issues found + * @return Number of issues found (0 = all referenced image files exist in archive) + */ + int verifyBinaryImageData(std::string* dbgMsg = nullptr) const; + bool isBinaryFileReferenced(const std::string& imagePath) const; + + /** + * @brief Get the drawing relationships filename for this worksheet + * @return The relationships filename (e.g., "xl/drawings/_rels/drawing1.xml.rels") + */ + std::string getDrawingRelsFilename() const; + + /** + * @brief Get the drawing filename for this worksheet + * @return The drawing filename (e.g., "xl/drawings/drawing1.xml") + */ + std::string getDrawingFilename() const; + + /** + * @brief Get the image media filename for an embedded image + * @param imageId The image ID + * @param extension The image file extension (e.g., ".png", ".jpg") + * @return The image media filename (e.g., "xl/media/image_1.png") + */ + static std::string getImageMediaFilename(const std::string& imageId, const std::string& extension); + + /** + * @brief Convert a relative image path to absolute archive path + * @param relativePath The relative path (e.g., "../media/image_1.png" or "media/image_1.png") + * @return The absolute archive path (e.g., "xl/media/image_1.png") + */ + static std::string getAbsoluteImagePath(const std::string& relativePath); + + /** + * @brief Extract filename from a full image path + * @param imagePath The full image path + * @return The absolute archive path with extracted filename (e.g., "xl/media/image_1.png") + */ + static std::string getImageMediaPathFromFilename(const std::string& imagePath); + private: // ---------- Private Member Variables ---------- // XLRelationships m_relationships{}; /**< class handling the worksheet relationships */ XLMergeCells m_merges{}; /**< class handling the */ XLVmlDrawing m_vmlDrawing{}; /**< class handling the worksheet VML drawing object */ + XLDrawingML m_drawingML{}; /**< class handling the worksheet DrawingML object */ XLComments m_comments{}; /**< class handling the worksheet comments */ XLTables m_tables{}; /**< class handling the worksheet table settings */ + XLEmbImgVecShPtr m_embImages; /**< shared pointer to vector storing images in the worksheet */ const std::vector< std::string_view >& m_nodeOrder = XLWorksheetNodeOrder; // worksheet XML root node required child sequence }; @@ -1561,9 +1960,24 @@ namespace OpenXLSX T get() const { try { - if constexpr (std::is_same::value) - return std::get(m_sheet); - + if constexpr (std::is_same::value) { + XLWorksheet worksheet = std::get(m_sheet); + + // Only initialize DrawingML if the worksheet actually has images + // This avoids unnecessary XML modifications for worksheets without images + if (worksheet.hasImagesInXML()) { + try { + std::ignore = worksheet.drawingML(); // Initialize DrawingML + // Populate m_images vector from existing XML data + worksheet.populateImagesFromXML(); + } catch (...) { + // DrawingML initialization failed, but worksheet is still usable + } + } + // If no images, leave DrawingML invalid (fast path) + + return worksheet; + } else if constexpr (std::is_same::value) return std::get(m_sheet); } diff --git a/OpenXLSX/headers/XLXmlData.hpp b/OpenXLSX/headers/XLXmlData.hpp index 3e2446c3..fabb193f 100644 --- a/OpenXLSX/headers/XLXmlData.hpp +++ b/OpenXLSX/headers/XLXmlData.hpp @@ -232,6 +232,44 @@ namespace OpenXLSX */ bool empty() const; + /** + * @brief Verify internal data integrity and class invariants + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyData(std::string* dbgMsg = nullptr) const; + + /** + * @brief Verify unique XML records across the entire XML tree + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyUniqueXMLRecords(std::string* dbgMsg = nullptr) const; + + /** + * @brief Compare two XML nodes for ordering + * @param x First XML node to compare + * @param y Second XML node to compare + * @return -1 if x < y, 0 if x == y, 1 if x > y + */ + static int compareXMLNode(const pugi::xml_node& x, const pugi::xml_node& y); + + /** + * @brief Less-than comparison for XML nodes (for use with std::sort) + * @param x First XML node to compare + * @param y Second XML node to compare + * @return true if x < y, false otherwise + */ + static bool lessXMLNode(const pugi::xml_node& x, const pugi::xml_node& y); + + /** + * @brief Verify unique XML records recursively starting from a root node + * @param rootNode The root XML node to start verification from + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + static int verifyUniqueXMLRecordsRecursive(const pugi::xml_node& rootNode, std::string* dbgMsg = nullptr); + private: // ===== PRIVATE MEMBER VARIABLES ===== // diff --git a/OpenXLSX/headers/XLXmlFile.hpp b/OpenXLSX/headers/XLXmlFile.hpp index 09280586..e6da5a02 100644 --- a/OpenXLSX/headers/XLXmlFile.hpp +++ b/OpenXLSX/headers/XLXmlFile.hpp @@ -182,6 +182,13 @@ namespace OpenXLSX */ std::string getXmlPath() const; + /** + * @brief Verify internal data integrity and class invariants + * @param dbgMsg Optional pointer to append debug message explaining any errors found + * @return Number of errors found (0 = EXIT_SUCCESS, data integrity OK) + */ + int verifyData(std::string* dbgMsg = nullptr) const; + protected: // ===== PROTECTED MEMBER VARIABLES XLXmlData* m_xmlData { nullptr }; /**< The underlying XML data object. */ }; diff --git a/OpenXLSX/sources/XLCell.cpp b/OpenXLSX/sources/XLCell.cpp index 7b1fdd49..5f955505 100644 --- a/OpenXLSX/sources/XLCell.cpp +++ b/OpenXLSX/sources/XLCell.cpp @@ -242,6 +242,16 @@ XLCellAssignable::XLCellAssignable (XLCell const & other) : XLCell(other) {} */ XLCellAssignable::XLCellAssignable (XLCell && other) : XLCell(std::move(other)) {} +/** + * @details + */ +XLCellAssignable::XLCellAssignable (const XLCellAssignable& other) : XLCell(other) {} + +/** + * @details + */ +XLCellAssignable::XLCellAssignable (XLCellAssignable&& other) noexcept : XLCell(std::move(other)) {} + /** * @details */ diff --git a/OpenXLSX/sources/XLCellIterator.cpp b/OpenXLSX/sources/XLCellIterator.cpp index 04914b45..e9ddac4d 100644 --- a/OpenXLSX/sources/XLCellIterator.cpp +++ b/OpenXLSX/sources/XLCellIterator.cpp @@ -283,7 +283,7 @@ void XLCellIterator::updateCurrentCell(bool createIfMissing) XMLNode rowNode = m_hintNode.parent().next_sibling_of_type(pugi::node_element); uint32_t rowNo = 0; while (not rowNode.empty()) { - rowNo = rowNode.attribute("r").as_ullong(); + rowNo = static_cast( rowNode.attribute("r").as_ullong() ); if (rowNo >= m_currentRow) break; // if desired row was reached / passed, break before incrementing rowNode rowNode = rowNode.next_sibling_of_type(pugi::node_element); } diff --git a/OpenXLSX/sources/XLCellReference.cpp b/OpenXLSX/sources/XLCellReference.cpp index 214ecaf0..0e72c2a2 100644 --- a/OpenXLSX/sources/XLCellReference.cpp +++ b/OpenXLSX/sources/XLCellReference.cpp @@ -382,7 +382,7 @@ XLCoordinates XLCellReference::coordinatesFromAddress(const std::string& address for (; pos < address.length() && std::isdigit(address[pos]); ++pos) // check digits rowNo = rowNo * 10 + (address[pos] - '0'); if (pos == address.length() && rowNo <= MAX_ROWS) // full address was < 4 letters + only digits - return std::make_pair(rowNo, colNo); + return XLCoordinates(static_cast(rowNo), static_cast(colNo)); } throw XLInputError("XLCellReference::coordinatesFromAddress - address \"" + address + "\" is invalid"); diff --git a/OpenXLSX/sources/XLCellValue.cpp b/OpenXLSX/sources/XLCellValue.cpp index 5701e093..c5ff6d07 100644 --- a/OpenXLSX/sources/XLCellValue.cpp +++ b/OpenXLSX/sources/XLCellValue.cpp @@ -494,7 +494,7 @@ XLCellValue XLCellValueProxy::getValue() const int32_t XLCellValueProxy::stringIndex() const { if (strcmp(m_cellNode->attribute("t").value(), "s") != 0) return -1; // cell value is not a shared string - return m_cellNode->child("v").text().as_ullong(-1); // return the shared string index stored for this cell + return static_cast( m_cellNode->child("v").text().as_ullong(-1) );// return the shared string index stored for this cell /**/ // if, for whatever reason, the underlying XML has no reference stored, also return -1 } diff --git a/OpenXLSX/sources/XLComments.cpp b/OpenXLSX/sources/XLComments.cpp index 4b396967..62498911 100644 --- a/OpenXLSX/sources/XLComments.cpp +++ b/OpenXLSX/sources/XLComments.cpp @@ -468,7 +468,7 @@ bool XLComments::set(std::string const& cellRef, std::string const& commentText, try { cShape = shape(cellRef); // for existing comments, try to access existing shape } - catch (XLException const& e) { + catch (XLException const& ) { newShapeNeeded = true; // not found: create fresh } } diff --git a/OpenXLSX/sources/XLContentTypes.cpp b/OpenXLSX/sources/XLContentTypes.cpp index f977da7a..55bbb349 100644 --- a/OpenXLSX/sources/XLContentTypes.cpp +++ b/OpenXLSX/sources/XLContentTypes.cpp @@ -115,6 +115,14 @@ namespace { // anonymous namespace for local functions type = XLContentType::Table; else if (typeString == applicationOpenXmlOfficeDocument + ".vmlDrawing") type = XLContentType::VMLDrawing; + else if (typeString == "image/png") + type = XLContentType::ImagePNG; + else if (typeString == "image/jpeg") + type = XLContentType::ImageJPEG; + else if (typeString == "image/bmp") + type = XLContentType::ImageBMP; + else if (typeString == "image/gif") + type = XLContentType::ImageGIF; else type = XLContentType::Unknown; @@ -172,6 +180,14 @@ namespace { // anonymous namespace for local functions typeString = applicationOpenXmlOfficeDocument + ".spreadsheetml.table+xml"; else if (type == XLContentType::VMLDrawing) typeString = applicationOpenXmlOfficeDocument + ".vmlDrawing"; + else if (type == XLContentType::ImagePNG) + typeString = "image/png"; + else if (type == XLContentType::ImageJPEG) + typeString = "image/jpeg"; + else if (type == XLContentType::ImageBMP) + typeString = "image/bmp"; + else if (type == XLContentType::ImageGIF) + typeString = "image/gif"; else throw XLInternalError("Unknown ContentType"); @@ -266,6 +282,11 @@ XLContentTypes& XLContentTypes::operator=(XLContentTypes&& other) noexcept = def */ void XLContentTypes::addOverride(const std::string& path, XLContentType type) { + // Check if Override already exists to prevent duplicates + if (hasOverride(path)) { + return; // Don't add duplicate + } + const std::string typeString = GetStringFromType(type); XMLNode lastOverride = xmlDocument().document_element().last_child_of_type(pugi::node_element); // see if there's a last element @@ -293,6 +314,15 @@ void XLContentTypes::addOverride(const std::string& path, XLContentType type) node.append_attribute("ContentType").set_value(typeString.c_str()); } +/** + * @details Check if an override with the given path already exists + */ +bool XLContentTypes::hasOverride(const std::string& path) const +{ + XMLNode existingOverride = xmlDocument().document_element().find_child_by_attribute("PartName", path.c_str()); + return !existingOverride.empty(); +} + /** * @details */ @@ -328,3 +358,13 @@ std::vector XLContentTypes::getContentItems() return result; } + +int XLContentTypes::verifyData(std::string* dbgMsg) const +{ + int errorCount = 0; + + // Call base class verifyData() which calls m_xmlData->verifyData() + errorCount += XLXmlFile::verifyData(dbgMsg); + + return errorCount; +} diff --git a/OpenXLSX/sources/XLDocument.cpp b/OpenXLSX/sources/XLDocument.cpp index 5e26fe1f..10d1ae69 100644 --- a/OpenXLSX/sources/XLDocument.cpp +++ b/OpenXLSX/sources/XLDocument.cpp @@ -45,6 +45,9 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM. // ===== External Includes ===== // #include +#include +#include +#include #ifdef ENABLE_NOWIDE # include #endif @@ -432,13 +435,13 @@ namespace } // namespace -XLDocument::XLDocument(const IZipArchive& zipArchive) : m_xmlSavingDeclaration{}, m_archive(zipArchive) {} +XLDocument::XLDocument(const IZipArchive& zipArchive) : m_xmlSavingDeclaration{}, m_archive(zipArchive), m_imageManager(this) {} /** * @details An alternative constructor, taking a std::string with the path to the .xlsx package as an argument. */ XLDocument::XLDocument(const std::string& docPath, const IZipArchive& zipArchive) - : m_xmlSavingDeclaration{}, m_archive(zipArchive) + : m_xmlSavingDeclaration{}, m_archive(zipArchive), m_imageManager(this) { open(docPath); } @@ -621,6 +624,9 @@ void XLDocument::open(const std::string& fileName) m_sharedStrings = XLSharedStrings(getXmlData("xl/sharedStrings.xml"), &m_sharedStringCache); m_styles = XLStyles(getXmlData("xl/styles.xml"), m_suppressWarnings); // 2024-10-14: forward supress warnings setting to XLStyles + + // ===== Load all remaining XML files from the archive (including drawing files) + loadAllXmlFilesFromArchive(); } namespace { @@ -758,6 +764,10 @@ void XLDocument::saveAs(const std::string& fileName, bool forceOverwrite) m_archive.addEntry(item.getXmlPath(), item.getRawData(XLXmlSavingDeclaration(m_xmlSavingDeclaration.version(), m_xmlSavingDeclaration.encoding(),xmlIsStandalone))); } + + // Prune unused images before saving + m_imageManager.prune(); + m_archive.save(m_filePath); } @@ -1008,6 +1018,256 @@ void XLDocument::setProperty(XLProperty prop, const std::string& value) // NO */ void XLDocument::deleteProperty(XLProperty theProperty) { setProperty(theProperty, ""); } +//---------------------------------------------------------------------------------------------------------------------- +// Convenience Property Methods +//---------------------------------------------------------------------------------------------------------------------- + +/** + * @details Set the document creator (dc:creator) + */ +void XLDocument::setCreator(const std::string& creator) +{ + setProperty(XLProperty::Creator, creator); +} + +/** + * @details Get the document creator (dc:creator) + */ +std::string XLDocument::creator() const +{ + return property(XLProperty::Creator); +} + +/** + * @details Set the document title (dc:title) + */ +void XLDocument::setTitle(const std::string& title) +{ + setProperty(XLProperty::Title, title); +} + +/** + * @details Get the document title (dc:title) + */ +std::string XLDocument::title() const +{ + return property(XLProperty::Title); +} + +/** + * @details Set the document subject (dc:subject) + */ +void XLDocument::setSubject(const std::string& subject) +{ + setProperty(XLProperty::Subject, subject); +} + +/** + * @details Get the document subject (dc:subject) + */ +std::string XLDocument::subject() const +{ + return property(XLProperty::Subject); +} + +/** + * @details Set the document description (dc:description) + */ +void XLDocument::setDescription(const std::string& description) +{ + setProperty(XLProperty::Description, description); +} + +/** + * @details Get the document description (dc:description) + */ +std::string XLDocument::description() const +{ + return property(XLProperty::Description); +} + +/** + * @details Set the document keywords (cp:keywords) + */ +void XLDocument::setKeywords(const std::string& keywords) +{ + setProperty(XLProperty::Keywords, keywords); +} + +/** + * @details Get the document keywords (cp:keywords) + */ +std::string XLDocument::keywords() const +{ + return property(XLProperty::Keywords); +} + +/** + * @details Set the last modified by (cp:lastModifiedBy) + */ +void XLDocument::setLastModifiedBy(const std::string& lastModifiedBy) +{ + setProperty(XLProperty::LastModifiedBy, lastModifiedBy); +} + +/** + * @details Get the last modified by (cp:lastModifiedBy) + */ +std::string XLDocument::lastModifiedBy() const +{ + return property(XLProperty::LastModifiedBy); +} + +/** + * @details Set the document category (cp:category) + */ +void XLDocument::setCategory(const std::string& category) +{ + setProperty(XLProperty::Category, category); +} + +/** + * @details Get the document category (cp:category) + */ +std::string XLDocument::category() const +{ + return property(XLProperty::Category); +} + +/** + * @details Set the creation date to the current time (dcterms:created) + */ +void XLDocument::setCreationDateToNow() +{ + setCreationDate(std::time(nullptr)); +} + +/** + * @details Set the creation date to a specific time (dcterms:created) + */ +void XLDocument::setCreationDate(time_t timestamp) +{ + // Convert time_t to GMT tm struct + std::tm tm = *std::gmtime(×tamp); + + // Format as W3CDTF (YYYY-MM-DDTHH:MM:SSZ) + std::ostringstream ss; + ss << std::put_time(&tm, "%FT%TZ"); + std::string datetime = ss.str(); + + setProperty(XLProperty::CreationDate, datetime); +} + +/** + * @details Get the creation date (dcterms:created) + */ +std::string XLDocument::creationDate() const +{ + return property(XLProperty::CreationDate); +} + +/** + * @details Set the modification date to the current time (dcterms:modified) + */ +void XLDocument::setModificationDateToNow() +{ + setModificationDate(std::time(nullptr)); +} + +/** + * @details Set the modification date to a specific time (dcterms:modified) + */ +void XLDocument::setModificationDate(time_t timestamp) +{ + // Convert time_t to GMT tm struct + std::tm tm = *std::gmtime(×tamp); + + // Format as W3CDTF (YYYY-MM-DDTHH:MM:SSZ) + std::ostringstream ss; + ss << std::put_time(&tm, "%FT%TZ"); + std::string datetime = ss.str(); + + setProperty(XLProperty::ModificationDate, datetime); +} + +/** + * @details Get the modification date (dcterms:modified) + */ +std::string XLDocument::modificationDate() const +{ + return property(XLProperty::ModificationDate); +} + +/** + * @details Set the absolute path (x15ac:absPath) + */ +void XLDocument::setAbsPath(const std::string& absPath) +{ + if (m_workbook.xmlDocument().empty()) return; + + XMLNode workbookRoot = m_workbook.xmlDocument().document_element(); + + // Find or create the mc:AlternateContent node + XMLNode alternateContent = workbookRoot.child("mc:AlternateContent"); + if (alternateContent.empty()) { + alternateContent = workbookRoot.append_child("mc:AlternateContent"); + alternateContent.append_attribute("xmlns:mc") = "http://schemas.openxmlformats.org/markup-compatibility/2006"; + } + + // Find or create the mc:Choice node + XMLNode choice = alternateContent.child("mc:Choice"); + if (choice.empty()) { + choice = alternateContent.append_child("mc:Choice"); + choice.append_attribute("Requires") = "x15"; + } + + // Find or create the x15ac:absPath node + XMLNode absPathNode = choice.child("x15ac:absPath"); + if (absPathNode.empty()) { + absPathNode = choice.append_child("x15ac:absPath"); + absPathNode.append_attribute("xmlns:x15ac") = "http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac"; + } + + // Set the url attribute + absPathNode.attribute("url").set_value(absPath.c_str()); +} + +/** + * @details Get the absolute path (x15ac:absPath) + */ +std::string XLDocument::absPath() const +{ + if (m_workbook.xmlDocument().empty()) return ""; + + XMLNode workbookRoot = m_workbook.xmlDocument().document_element(); + XMLNode alternateContent = workbookRoot.child("mc:AlternateContent"); + if (alternateContent.empty()) return ""; + + XMLNode choice = alternateContent.child("mc:Choice"); + if (choice.empty()) return ""; + + XMLNode absPathNode = choice.child("x15ac:absPath"); + if (absPathNode.empty()) return ""; + + return absPathNode.attribute("url").value(); +} + +/** + * @details Set the application name (Application) + */ +void XLDocument::setApplication(const std::string& application) +{ + setProperty(XLProperty::Application, application); +} + +/** + * @details Get the application name (Application) + */ +std::string XLDocument::application() const +{ + return property(XLProperty::Application); +} + /** * @details */ @@ -1201,6 +1461,206 @@ bool XLDocument::validateSheetName(std::string sheetName, bool throwOnInvalid) return valid; } +/** + * @details Add an image entry to the archive + */ +bool XLDocument::addImageEntry(const std::string& imageFilename, const std::vector& imageData, XLContentType contentType) +{ + try { + if (!m_archive.hasEntry(imageFilename)) { + // Convert vector to string for addEntry + std::string imageDataString(imageData.begin(), imageData.end()); + m_archive.addEntry(imageFilename, imageDataString); + m_contentTypes.addOverride("/" + imageFilename, contentType); + } + return true; + } + catch (const std::exception&) { + return false; + } +} + +/** + * @details Add a drawing file to the archive + */ +bool XLDocument::addDrawingFile(const std::string& drawingFilename, const std::string& drawingXml) +{ + try { + if (!m_archive.hasEntry(drawingFilename)) { + m_archive.addEntry(drawingFilename, drawingXml); + m_contentTypes.addOverride("/" + drawingFilename, XLContentType::Drawing); + + // Add XLXmlData object to the data list so getXmlData can find it + m_data.emplace_back(this, drawingFilename, "", XLContentType::Drawing); + } + return true; + } + catch (const std::exception&) { + return false; + } +} + +/** + * @details Get XML data for a drawing file + */ +XLXmlData* XLDocument::getDrawingXmlData(const std::string& drawingFilename) +{ + return getXmlData(drawingFilename, true); // Force loading the XML data +} + +/** + * @details Get XML data for a relationships file + */ +XLXmlData* XLDocument::getRelationshipsXmlData(const std::string& relsFilename) +{ + XLXmlData* result = getXmlData(relsFilename, true); // Force loading the XML data + + if (!result) { + // Try to load from archive if not in cache + if (hasRelationshipsFile(relsFilename)) { + std::string relsContent = readRelationshipsFile(relsFilename); + if (!relsContent.empty()) { + // Add to m_data cache + m_data.emplace_back(this, relsFilename, relsContent, XLContentType::Relationships); + result = &m_data.back(); + } + } + } + + return result; +} + +/** + * @details Update relationships XML data in-memory without archive access + */ +bool XLDocument::updateRelationshipsXmlData(const std::string& relsFilename, const std::string& relsXml) +{ + try { + // Get existing XLXmlData or create new one + XLXmlData* relsXmlData = getRelationshipsXmlData(relsFilename); + + if (!relsXmlData) { + // Create new XLXmlData entry if it doesn't exist + m_data.emplace_back(this, relsFilename, "", XLContentType::Relationships); + relsXmlData = &m_data.back(); + } + + // Update the XML data using setRawData() + relsXmlData->setRawData(relsXml); + + // Add content type override if needed + m_contentTypes.addOverride("/" + relsFilename, XLContentType::Relationships); + + return true; + } + catch (const std::exception&) { + return false; + } +} + +/** + * @details Add a relationships file to the archive + */ +bool XLDocument::addRelationshipsFile(const std::string& relsFilename, const std::string& relsXml) +{ + try { + bool isNewFile = !m_archive.hasEntry(relsFilename); + + // Always update the relationships file (allow overwriting) + m_archive.addEntry(relsFilename, relsXml); + + // Only add content type and XML data if this is a new file + if (isNewFile) { + m_contentTypes.addOverride("/" + relsFilename, XLContentType::Relationships); + + // Add XLXmlData object to the data list so getXmlData can find it + m_data.emplace_back(this, relsFilename, "", XLContentType::Relationships); + } + return true; + } + catch (const std::exception&) { + return false; + } +} + +/** + * @details Check if a relationships file exists in the archive + */ +bool XLDocument::hasRelationshipsFile(const std::string& relsFilename) const +{ + return m_archive.hasEntry(relsFilename); +} + +/** + * @details Read the content of a relationships file from the archive + */ +std::string XLDocument::readRelationshipsFile(const std::string& relsFilename) const +{ + try { + if (m_archive.hasEntry(relsFilename)) { + return m_archive.getEntry(relsFilename); + } + } catch (const std::exception&) { + // Return empty string if there's an error + } + return ""; +} + +/** + * @details Read the content of any file from the archive + */ +std::string XLDocument::readFile(const std::string& filePath) const +{ + try { + if (m_archive.hasEntry(filePath)) { + return m_archive.getEntry(filePath); + } + } catch (const std::exception&) { + // Return empty string if there's an error + } + return ""; +} + +/** + * @details Delete an entry from the archive + */ +bool XLDocument::deleteEntry(const std::string& entryPath) +{ + try { + if (m_archive.hasEntry(entryPath)) { + m_archive.deleteEntry(entryPath); + return true; + } + } catch (const std::exception&) { + // Return false if there's an error + } + return false; +} + +bool XLDocument::isBinaryFileReferenced(const std::string& imagePath) const +{ + try { + // Get all worksheet names + auto sheetNames = m_workbook.worksheetNames(); + + // Check each worksheet's relationships + for (const auto& sheetName : sheetNames) { + // Use const_cast to access non-const worksheet() method + auto worksheet = const_cast(this)->m_workbook.worksheet(sheetName); + + // Check if this worksheet references the binary file + if (worksheet.isBinaryFileReferenced(imagePath)) { + return true; // Found a reference, early exit + } + } + + return false; // No references found in any worksheet + } + catch (const std::exception&) { + return false; // Error occurred, assume not referenced + } +} + /** * @details return value defaults to true, false only where the XLCommandType implements it */ @@ -1318,6 +1778,10 @@ bool XLDocument::execCommand(const XLCommand& command) m_appProperties.deleteSheetName(command.getParam("sheetName")); std::string sheetPath = m_wbkRelationships.relationshipById(command.getParam("sheetID")).target(); if (sheetPath.substr(0, 4) != "/xl/") sheetPath = "/xl/" + sheetPath; // 2024-12-15: respect absolute sheet path + + // Clean up embedded images for this worksheet before deleting the sheet + m_imageManager.eraseEmbImgsForSheetName(command.getParam("sheetName")); + m_archive.deleteEntry(sheetPath.substr(1)); m_contentTypes.deleteOverride(sheetPath); m_wbkRelationships.deleteRelationship(command.getParam("sheetID")); @@ -1459,7 +1923,7 @@ void XLDocument::setSavingDeclaration(XLXmlSavingDeclaration const& savingDeclar */ void XLDocument::cleanupSharedStrings() { - int32_t oldStringCount = m_sharedStringCache.size(); + int32_t oldStringCount = static_cast( m_sharedStringCache.size() ); std::vector< int32_t > indexMap(oldStringCount, -1); // indexMap[ oldIndex ] :== newIndex, -1 = not yet assigned int32_t newStringCount = 1; // reserve index 0 for empty string, count here +1 for each unique shared string index that is in use in the worksheet @@ -1522,12 +1986,79 @@ std::string XLDocument::extractXmlFromArchive(const std::string& path) return (m_archive.hasEntry(path) ? m_archive.getEntry(path) : ""); } +/** + * @details Load all XML files from the archive into m_data list + */ +void XLDocument::loadAllXmlFilesFromArchive() +{ + // Get all content items from the content types + auto contentItems = m_contentTypes.getContentItems(); + + for (const auto& item : contentItems) { + std::string path = item.path(); + + // Remove leading slash if present (standardize path format) + if (path.length() > 0 && path[0] == '/') { + path = path.substr(1); + } + + // Skip if already loaded + if (hasXmlData(path)) { + continue; + } + + // Load XML files and media files + bool shouldLoad = false; + XLContentType contentType = item.type(); + + // Extract file extension + std::string extension = ""; + size_t dotPos = path.find_last_of('.'); + if (dotPos != std::string::npos) { + extension = path.substr(dotPos); + } + + if (extension == ".xml" || extension == ".rels") { + // Load drawing files and other safe XML files + switch (contentType) { + case XLContentType::Drawing: + case XLContentType::Chart: + case XLContentType::Comments: + case XLContentType::Table: + case XLContentType::VMLDrawing: + case XLContentType::Relationships: + shouldLoad = true; + break; + default: + // Also load any XML file that starts with xl/drawings/, xl/charts/, or is a .rels file + if (path.find("xl/drawings/") == 0 || path.find("xl/charts/") == 0 || + path.find("_rels/") != std::string::npos || path.find(".rels") != std::string::npos) { + shouldLoad = true; + // Set correct content type for relationship files + if (path.find(".rels") != std::string::npos) { + contentType = XLContentType::Relationships; + } + } + break; + } + } else { + // Don't load media files (images) into m_data - they should remain as binary files in the archive + // Image files will be preserved automatically by the archive when saving + shouldLoad = false; + } + + if (shouldLoad) { + // Add to m_data list + m_data.emplace_back(this, path, "", contentType); + } + } +} + /** * @details */ XLXmlData* XLDocument::getXmlData(const std::string& path, bool doNotThrow) { - // avoid duplication of code: use const_cast to invoke the const function overload and return a non-const value return const_cast(const_cast(this)->getXmlData(path, doNotThrow)); } @@ -1701,4 +2232,163 @@ namespace OpenXLSX return ((result.length() > 1 || result.front() != '/') && path.back() == '/') ? result + "/" : result; } + /** + * @details Compare two documents for debugging + */ + int XLDocument::compare(const XLDocument& other, std::string* diffMsg) const + { + + // Compare document names/paths + std::string thisPath = path(); + std::string otherPath = other.path(); + int pathCompare = thisPath.compare(otherPath); + if (pathCompare != 0) { + appendDbgMsg(diffMsg, "document path differs: '" + thisPath + "' vs '" + otherPath + "'"); + return pathCompare; + } + + // Compare workbook worksheets + if (m_workbook.valid() && other.m_workbook.valid()) { + // Get worksheet names + auto thisSheetNames = m_workbook.sheetNames(); + auto otherSheetNames = other.m_workbook.sheetNames(); + + if (thisSheetNames.size() != otherSheetNames.size()) { + appendDbgMsg(diffMsg, "worksheet count differs: " + std::to_string(thisSheetNames.size()) + + " vs " + std::to_string(otherSheetNames.size())); + return thisSheetNames.size() < otherSheetNames.size() ? -1 : 1; + } + + // Compare each worksheet + for (size_t i = 0; i < thisSheetNames.size(); ++i) { + if (thisSheetNames[i] != otherSheetNames[i]) { + appendDbgMsg(diffMsg, "worksheet name differs at index " + std::to_string(i) + + ": '" + thisSheetNames[i] + "' vs '" + otherSheetNames[i] + "'"); + return thisSheetNames[i].compare(otherSheetNames[i]); + } + + // Compare worksheet content + try { + const XLWorksheet thisSheet = const_cast(this)->m_workbook.worksheet(thisSheetNames[i]); + const XLWorksheet otherSheet = const_cast(&other)->m_workbook.worksheet(otherSheetNames[i]); + + int sheetCompare = thisSheet.compare(otherSheet, diffMsg); + if (sheetCompare != 0) { + appendDbgMsg(diffMsg, "worksheet '" + thisSheetNames[i] + "' differs"); + return sheetCompare; + } + } catch (const std::exception&) { + appendDbgMsg(diffMsg, "error comparing worksheet '" + thisSheetNames[i] + "'"); + return -1; + } + } + } else if (m_workbook.valid() != other.m_workbook.valid()) { + appendDbgMsg(diffMsg, "workbook validity differs: " + std::string(m_workbook.valid() ? "valid" : "invalid") + + " vs " + std::string(other.m_workbook.valid() ? "valid" : "invalid")); + return m_workbook.valid() ? 1 : -1; + } + + // Compare document-level relationships (simplified) + try { + // Just check if both have valid relationships objects + bool thisDocRelValid = m_docRelationships.valid(); + bool otherDocRelValid = other.m_docRelationships.valid(); + if (thisDocRelValid != otherDocRelValid) { + appendDbgMsg(diffMsg, "document relationships validity differs: " + std::string(thisDocRelValid ? "valid" : "invalid") + + " vs " + std::string(otherDocRelValid ? "valid" : "invalid")); + return thisDocRelValid ? 1 : -1; + } + + bool thisWbkRelValid = m_wbkRelationships.valid(); + bool otherWbkRelValid = other.m_wbkRelationships.valid(); + if (thisWbkRelValid != otherWbkRelValid) { + appendDbgMsg(diffMsg, "workbook relationships validity differs: " + std::string(thisWbkRelValid ? "valid" : "invalid") + + " vs " + std::string(otherWbkRelValid ? "valid" : "invalid")); + return thisWbkRelValid ? 1 : -1; + } + } catch (const std::exception&) { + appendDbgMsg(diffMsg, "error comparing document relationships"); + return -1; + } + + // Compare content types (simplified) + try { + // Just check if both have valid content types objects + bool thisContentTypeValid = m_contentTypes.valid(); + bool otherContentTypeValid = other.m_contentTypes.valid(); + if (thisContentTypeValid != otherContentTypeValid) { + appendDbgMsg(diffMsg, "content types validity differs: " + std::string(thisContentTypeValid ? "valid" : "invalid") + + " vs " + std::string(otherContentTypeValid ? "valid" : "invalid")); + return thisContentTypeValid ? 1 : -1; + } + } catch (const std::exception&) { + appendDbgMsg(diffMsg, "error comparing content types"); + return -1; + } + + // TODO: Compare other document properties + // - Core properties (m_coreProperties) + // - App properties (m_appProperties) + // - Styles (m_styles) + // - Shared strings (m_sharedStrings) + // - Content types (m_contentTypes) + // - Document relationships (m_docRelationships) + // - Workbook relationships (m_wbkRelationships) + + // Documents are identical + return 0; +} + +int XLDocument::verifyXLXmlData(std::string* dbgMsg) const +{ + int errorCount = 0; + + // Check each XLXmlData object for duplicates using verifyData() + for (const auto& xmlData : m_data) { + errorCount += xmlData.verifyData(dbgMsg); + } + + return errorCount; +} + +int XLDocument::verifyData(std::string* dbgMsg) const + { + int errorCount = 0; + + // Verify sub-objects that have verifyData() implemented + errorCount += m_contentTypes.verifyData(dbgMsg); + errorCount += m_docRelationships.verifyData(dbgMsg); + errorCount += m_wbkRelationships.verifyData(dbgMsg); + + // Verify all XML files in m_data (complete XML forest coverage) + errorCount += verifyXLXmlData(dbgMsg); + + // TODO: Add verifyData() methods to remaining classes + // errorCount += m_workbook.verifyData(dbgMsg); + // errorCount += m_coreProperties.verifyData(dbgMsg); + // errorCount += m_appProperties.verifyData(dbgMsg); + // errorCount += m_styles.verifyData(dbgMsg); + // errorCount += m_sharedStrings.verifyData(dbgMsg); + + + if (m_workbook.valid()) { + try { + auto sheetNames = m_workbook.worksheetNames(); + for (const auto& sheetName : sheetNames) { + const XLWorksheet worksheet = const_cast(this)->m_workbook.worksheet(sheetName); + int worksheetErrors = worksheet.verifyData(dbgMsg); + if (worksheetErrors > 0) { + appendDbgMsg(dbgMsg, "worksheet '" + sheetName + "' has " + std::to_string(worksheetErrors) + " errors"); + errorCount += worksheetErrors; + } + } + } catch (const std::exception&) { + appendDbgMsg(dbgMsg, "error accessing worksheets"); + errorCount++; + } + } + + return errorCount; + } + } // namespace OpenXLSX diff --git a/OpenXLSX/sources/XLDrawing.cpp b/OpenXLSX/sources/XLDrawing.cpp index 1befa63e..8bbf6281 100644 --- a/OpenXLSX/sources/XLDrawing.cpp +++ b/OpenXLSX/sources/XLDrawing.cpp @@ -278,7 +278,7 @@ int16_t XLShapeStyle::attributeOrderIndex(std::string const& attributeName) cons auto attributeIterator = std::find(m_nodeOrder.begin(), m_nodeOrder.end(), attributeName); if (attributeIterator == m_nodeOrder.end()) return -1; - return attributeIterator - m_nodeOrder.begin(); + return static_cast( attributeIterator - m_nodeOrder.begin() ); } /** diff --git a/OpenXLSX/sources/XLDrawingML.cpp b/OpenXLSX/sources/XLDrawingML.cpp new file mode 100644 index 00000000..d04cc99d --- /dev/null +++ b/OpenXLSX/sources/XLDrawingML.cpp @@ -0,0 +1,659 @@ +/* + * XLDrawingML.cpp + * + * DrawingML (Drawing Markup Language) implementation for OpenXLSX + */ + +// ===== External Includes ===== // +#include +#include +#include + +// ===== OpenXLSX Includes ===== // +#include "XLDrawingML.hpp" +#include "XLImage.hpp" +#include "XLXmlData.hpp" +#include "XLCellReference.hpp" +#include "utilities/XLUtilities.hpp" + +namespace OpenXLSX +{ + // ===== Constructors & Destructors ===== // + XLDrawingML::XLDrawingML(XLXmlData* xmlData) : XLXmlFile(xmlData) + { + } + + void XLDrawingML::addImage(const XLEmbeddedImage& embImage){ + const XLImageAnchor& imageAnchor = embImage.getImageAnchor(); + XMLNode rootNode = xmlDocument().document_element(); + (void)XLDrawingML::createXMLNode(rootNode, imageAnchor); + } + + // ===== Public Methods ===== // + + size_t XLDrawingML::imageCount() const + { + if (!valid()) { + return 0; + } + + XMLNode rootNode = xmlDocument().document_element(); + size_t count = 0; + + for (XMLNode child : rootNode.children("xdr:oneCellAnchor")) { + count++; + } + for (XMLNode child : rootNode.children("xdr:twoCellAnchor")) { + count++; + } + + return count; + } + + // ===== Private Methods ===== // + + uint32_t XLDrawingML::calculateCellPosition(uint32_t row, uint16_t column) const + { + // Calculate cell position in Excel units + // This is a simplified calculation - Excel uses more complex formulas + return (row - 1) * 15 + (column - 1) * 64; + } + + int XLDrawingML::verifyData(std::string* dbgMsg) const + { + int errorCount = 0; + + // Check if XML is valid + if (!valid()) { + appendDbgMsg(dbgMsg, "DrawingML XML is invalid"); + errorCount++; + return errorCount; + } + + try { + // Check XML structure + XMLNode rootNode = xmlDocument().document_element(); + if (rootNode.empty()) { + appendDbgMsg(dbgMsg, "DrawingML root node is empty"); + errorCount++; + } + + // Count images in XML and verify structure + size_t xmlImageCount = 0; + for (auto anchor : rootNode.children()) { + // Check anchor type using enum conversion + XLImageAnchorType anchorType = XLImageAnchorUtils::stringToAnchorType(anchor.name()); + if (anchorType != XLImageAnchorType::Unknown) { + xmlImageCount++; + + // Check for required elements + if (anchor.child("xdr:pic").empty()) { + appendDbgMsg(dbgMsg, "image anchor missing xdr:pic element"); + errorCount++; + } + + if (anchor.child("xdr:clientData").empty()) { + appendDbgMsg(dbgMsg, "image anchor missing xdr:clientData element"); + errorCount++; + } + } + } + + // Verify image count consistency + size_t reportedCount = imageCount(); + if (xmlImageCount != reportedCount) { + appendDbgMsg(dbgMsg, "XML image count (" + std::to_string(xmlImageCount) + + ") does not match reported count (" + std::to_string(reportedCount) + ")"); + errorCount++; + } + + } catch (const std::exception&) { + appendDbgMsg(dbgMsg, "error parsing DrawingML XML"); + errorCount++; + } + + return errorCount; + } + + XMLNode XLDrawingML::getRootNode() const + { + if (!valid()) { + return XMLNode(); // Return empty node if invalid + } + + try { + return xmlDocument().document_element(); + } catch (const std::exception&) { + return XMLNode(); // Return empty node on error + } + } + + // ========== XLImageAnchor Implementation ========== // + + void XLImageAnchor::reset(){ + anchorType = XLImageAnchorType::Unknown; + imageId.erase(); + relationshipId.erase(); + fromRow = 0; + fromCol = 0; + fromRowOffset = 0; + fromColOffset = 0; + toRow = 0; + toCol = 0; + toRowOffset = 0; + toColOffset = 0; + displayWidthEMUs = 0; + displayHeightEMUs = 0; + actualWidthEMUs = 0; + actualHeightEMUs = 0; + } + + void XLImageAnchor::initOneCell( const XLCellReference& cellRef, int32_t rowOffset, + int32_t colOffset){ + reset(); + anchorType = XLImageAnchorType::OneCellAnchor; + setFromCellReference(cellRef); + fromRowOffset = rowOffset; + fromColOffset = colOffset; + } + + void XLImageAnchor::initTwoCell( const XLCellReference& fromCellRef, + const XLCellReference& toCellRef, int32_t fromROffset, + int32_t fromCOffset, int32_t toROffset, int32_t toCOffset){ + reset(); + anchorType = XLImageAnchorType::TwoCellAnchor; + setFromCellReference(fromCellRef); + setToCellReference(toCellRef); + fromRowOffset = fromROffset; + fromColOffset = fromCOffset; + toRowOffset = toROffset; + toColOffset = toCOffset; + } + + XLCellReference XLImageAnchor::getFromCellReference() const{ + const XLCellReference fromCellReference( fromRow + 1, fromCol + 1 ); + return fromCellReference; + } + + void XLImageAnchor::setFromCellReference( const XLCellReference& fromCellRef ){ + const uint32_t cellRefRow = fromCellRef.row(); + const uint16_t cellRefColumn = fromCellRef.column(); + fromRow = ( cellRefRow > 0 ) ? (cellRefRow - 1) : 0; + fromCol = ( cellRefColumn > 0 ) ? (cellRefColumn - 1) : 0; + } + + XLCellReference XLImageAnchor::getToCellReference() const{ + const XLCellReference toCellReference( toRow + 1, toCol + 1 ); + return toCellReference; + } + + void XLImageAnchor::setToCellReference( const XLCellReference& toCellRef ){ + const uint32_t cellRefRow = toCellRef.row(); + const uint16_t cellRefColumn = toCellRef.column(); + toRow = ( cellRefRow > 0 ) ? (cellRefRow - 1) : 0; + toCol = ( cellRefColumn > 0 ) ? (cellRefColumn - 1) : 0; + } + + void XLImageAnchor::setDisplaySizeWithAspectRatio( + const uint32_t& widthPixels, const uint32_t& heightPixels, + const uint32_t& maxWidthEmus, const uint32_t& maxHeightEmus ){ + uint32_t targetWidth = XLImageUtils::pixelsToEmus(widthPixels); + uint32_t targetHeight = XLImageUtils::pixelsToEmus(heightPixels); + + if (widthPixels > 0 || heightPixels > 0) { + double aspectRatio = XLImageUtils::calculateAspectRatio(widthPixels, heightPixels); + assert(aspectRatio > 0.0); + + const double maxWidth = static_cast(maxWidthEmus); + const double maxHeight = static_cast(maxHeightEmus); + const double widthA = maxHeight * aspectRatio; + + if (widthA <= maxWidth) { + /* + +------------+--------+ + | | | + | | | + | | | + | | |maxHeight + | | | + | | | + | | | + | widthA | | + +------------+--------+ + maxWidth + */ + targetWidth = static_cast(round(widthA)); + targetHeight = maxHeightEmus; + } + else { + /* + +---------------------+ + | | + | | + +---------------------+ + | |maxHeight + | | + | heightB| + | | + | | + +---------------------+ + maxWidth + */ + const double heightB = maxWidth / aspectRatio; + assert(heightB <= (1.00000001 * maxHeight)); + targetWidth = maxWidthEmus; + targetHeight = static_cast(round(heightB)); + } + + assert(fabs(aspectRatio - (static_cast(targetWidth) / + static_cast(targetHeight))) < 1e-4); + } + + displayWidthEMUs = targetWidth; + displayHeightEMUs = targetHeight; + } + + void XLImageAnchor::setDisplaySizeWithAspectRatio( + const std::vector& imageData, const XLMimeType& mimeType, + const uint32_t& maxWidthEmus, const uint32_t& maxHeightEmus ){ + + // Convert string mimeType to enum, auto-detect if unknown + XLMimeType mimeTypeEnum = mimeType; + if (mimeTypeEnum == XLMimeType::Unknown) { + mimeTypeEnum = XLImageUtils::detectMimeType(imageData); + } + + const std::pair imageDims = + XLImageUtils::getImageDimensions(imageData, mimeTypeEnum); + + setDisplaySizeWithAspectRatio( imageDims.first, imageDims.second, + maxWidthEmus, maxHeightEmus ); + } + + void XLImageAnchor::setDisplaySizeWithAspectRatio( + const std::string& imageFileName, const XLMimeType& mimeType, + const uint32_t& maxWidthEmus, const uint32_t& maxHeightEmus ){ + + std::ifstream file(imageFileName, std::ios::binary); + if (file.is_open()) { + // Read the entire file into a vector + file.seekg(0, std::ios::end); + size_t fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector imageData(fileSize); + file.read(reinterpret_cast(imageData.data()), fileSize); + file.close(); + + setDisplaySizeWithAspectRatio( imageData, mimeType, maxWidthEmus, + maxHeightEmus ); + } + } + + bool XLImageAnchor::isTwoCell() const + { + return anchorType == XLImageAnchorType::TwoCellAnchor; + } + + std::pair XLImageAnchor::getDisplayDimensions() const + { + return std::make_pair(displayWidthEMUs, displayHeightEMUs); + } + + std::pair XLImageAnchor::getActualDimensions() const + { + return std::make_pair(actualWidthEMUs, actualHeightEMUs); + } + + void XLImageAnchor::setActualDimensions(uint32_t width, uint32_t height) + { + actualWidthEMUs = width; + actualHeightEMUs = height; + } + + std::pair XLImageAnchor::getDisplayDimensionsPixels() const + { + return std::make_pair( + XLImageUtils::emusToPixels(displayWidthEMUs), + XLImageUtils::emusToPixels(displayHeightEMUs) + ); + } + + std::pair XLImageAnchor::getActualDimensionsPixels() const + { + return std::make_pair( + XLImageUtils::emusToPixels(actualWidthEMUs), + XLImageUtils::emusToPixels(actualHeightEMUs) + ); + } + + // ========== XLDrawingML Static Utility Functions ========== // + + XMLNode XLDrawingML::createXMLNode(XMLNode rootNode, const XLImageAnchor& imgAnchorInfo) + { + if (rootNode.empty()) { + return XMLNode(); + } + + // Create the appropriate anchor element + XMLNode anchor; + if (imgAnchorInfo.isTwoCell()) { + anchor = rootNode.append_child("xdr:twoCellAnchor"); + anchor.append_attribute("editAs").set_value("oneCell"); + } else { + anchor = rootNode.append_child("xdr:oneCellAnchor"); + } + + // Create the 'from' element + XMLNode from = anchor.append_child("xdr:from"); + from.append_child("xdr:col").append_child(pugi::node_pcdata).set_value(std::to_string(imgAnchorInfo.fromCol).c_str()); + from.append_child("xdr:colOff").append_child(pugi::node_pcdata).set_value(std::to_string(imgAnchorInfo.fromColOffset).c_str()); + from.append_child("xdr:row").append_child(pugi::node_pcdata).set_value(std::to_string(imgAnchorInfo.fromRow).c_str()); + from.append_child("xdr:rowOff").append_child(pugi::node_pcdata).set_value(std::to_string(imgAnchorInfo.fromRowOffset).c_str()); + + // Create the 'to' element (for twoCellAnchor) + if (imgAnchorInfo.isTwoCell()) { + XMLNode to = anchor.append_child("xdr:to"); + to.append_child("xdr:col").append_child(pugi::node_pcdata).set_value(std::to_string(imgAnchorInfo.toCol).c_str()); + to.append_child("xdr:colOff").append_child(pugi::node_pcdata).set_value(std::to_string(imgAnchorInfo.toColOffset).c_str()); + to.append_child("xdr:row").append_child(pugi::node_pcdata).set_value(std::to_string(imgAnchorInfo.toRow).c_str()); + to.append_child("xdr:rowOff").append_child(pugi::node_pcdata).set_value(std::to_string(imgAnchorInfo.toRowOffset).c_str()); + } + + // Create the 'ext' element (required for both anchor types) + XMLNode ext = anchor.append_child("xdr:ext"); + ext.append_attribute("cx").set_value(std::to_string(imgAnchorInfo.displayWidthEMUs).c_str()); + ext.append_attribute("cy").set_value(std::to_string(imgAnchorInfo.displayHeightEMUs).c_str()); + + // Create the picture element + XMLNode pic = anchor.append_child("xdr:pic"); + + // Create non-visual picture properties + XMLNode nvPicPr = pic.append_child("xdr:nvPicPr"); + XMLNode cNvPr = nvPicPr.append_child("xdr:cNvPr"); + cNvPr.append_attribute("id").set_value(imgAnchorInfo.imageId.c_str()); + cNvPr.append_attribute("name").set_value(("Picture " + imgAnchorInfo.imageId).c_str()); + + XMLNode cNvPicPr = nvPicPr.append_child("xdr:cNvPicPr"); + XMLNode picLocks = cNvPicPr.append_child("a:picLocks"); + picLocks.append_attribute("noChangeAspect").set_value("1"); + picLocks.append_attribute("noChangeArrowheads").set_value("1"); + + // Create blip fill + XMLNode blipFill = pic.append_child("xdr:blipFill"); + XMLNode blip = blipFill.append_child("a:blip"); + blip.append_attribute("r:embed").set_value(imgAnchorInfo.relationshipId.c_str()); + + XMLNode srcRect = blipFill.append_child("a:srcRect"); + XMLNode stretch = blipFill.append_child("a:stretch"); + stretch.append_child("a:fillRect"); + + // Create shape properties + XMLNode spPr = pic.append_child("xdr:spPr"); + XMLNode xfrm = spPr.append_child("a:xfrm"); + XMLNode off = xfrm.append_child("a:off"); + off.append_attribute("x").set_value("0"); + off.append_attribute("y").set_value("0"); + + XMLNode xfrmExt = xfrm.append_child("a:ext"); + xfrmExt.append_attribute("cx").set_value(std::to_string(imgAnchorInfo.actualWidthEMUs).c_str()); + xfrmExt.append_attribute("cy").set_value(std::to_string(imgAnchorInfo.actualHeightEMUs).c_str()); + + XMLNode prstGeom = spPr.append_child("a:prstGeom"); + prstGeom.append_attribute("prst").set_value("rect"); + prstGeom.append_child("a:avLst"); + + // Use noFill to match other methods (no borders) + spPr.append_child("a:noFill"); + + // Add clientData element (required for Excel compatibility) + anchor.append_child("xdr:clientData"); + + return anchor; + } + + bool XLDrawingML::deleteXMLNode(XMLNode rootNode, const std::string& relationshipId) + { + if (rootNode.empty() || relationshipId.empty()) { + return false; + } + + // Find and remove the image node + for (auto anchor : rootNode.children()) { + // Check both oneCellAnchor and twoCellAnchor using enum conversion + XLImageAnchorType anchorType = XLImageAnchorUtils::stringToAnchorType(anchor.name()); + if (anchorType != XLImageAnchorType::Unknown) { + + // Look for the pic element with the matching relationship ID + XMLNode pic = anchor.child("xdr:pic"); + if (pic.empty()) { + pic = anchor.child("pic"); + } + + if (!pic.empty()) { + // Check blipFill -> blip -> r:embed + XMLNode blipFill = pic.child("xdr:blipFill"); + if (blipFill.empty()) { + blipFill = pic.child("a:blipFill"); + } + if (blipFill.empty()) { + blipFill = pic.child("blipFill"); + } + + if (!blipFill.empty()) { + XMLNode blip = blipFill.child("a:blip"); + if (blip.empty()) { + blip = blipFill.child("blip"); + } + + if (!blip.empty()) { + std::string embedId = blip.attribute("r:embed").value(); + if (embedId == relationshipId) { + // Found the matching image, remove the entire anchor + rootNode.remove_child(anchor); + return true; + } + } + } + } + } + } + + return false; // Image not found + } + + bool XLDrawingML::parseXMLNode(const pugi::xml_node& anchorXMLNode, XLImageAnchor* imgAnchorInfo) + { + if (anchorXMLNode.empty() || !imgAnchorInfo) { + return false; + } + + try { + // Determine anchor type + std::string nodeName = anchorXMLNode.name(); + // Determine anchor type using utility function + imgAnchorInfo->anchorType = XLImageAnchorUtils::stringToAnchorType(nodeName); + if (imgAnchorInfo->anchorType == XLImageAnchorType::Unknown) { + return false; // Unknown anchor type + } + + // Extract cell position from 'from' element + XMLNode from = anchorXMLNode.child("xdr:from"); + if (from.empty()) { + from = anchorXMLNode.child("from"); + } + if (from.empty()) { + return false; + } + + XMLNode colNode = from.child("xdr:col"); + if (colNode.empty()) { + colNode = from.child("col"); + } + XMLNode rowNode = from.child("xdr:row"); + if (rowNode.empty()) { + rowNode = from.child("row"); + } + + if (colNode.empty() || rowNode.empty()) { + return false; + } + + imgAnchorInfo->fromCol = colNode.text().as_uint(); + imgAnchorInfo->fromRow = rowNode.text().as_uint(); + + // Extract offsets + XMLNode colOffNode = from.child("xdr:colOff"); + if (colOffNode.empty()) { + colOffNode = from.child("colOff"); + } + XMLNode rowOffNode = from.child("xdr:rowOff"); + if (rowOffNode.empty()) { + rowOffNode = from.child("rowOff"); + } + + imgAnchorInfo->fromColOffset = colOffNode.empty() ? 0 : colOffNode.text().as_int(); + imgAnchorInfo->fromRowOffset = rowOffNode.empty() ? 0 : rowOffNode.text().as_int(); + + // Extract 'to' element for twoCellAnchor + if (imgAnchorInfo->isTwoCell()) { + XMLNode to = anchorXMLNode.child("xdr:to"); + if (to.empty()) { + to = anchorXMLNode.child("to"); + } + if (!to.empty()) { + XMLNode toColNode = to.child("xdr:col"); + if (toColNode.empty()) { + toColNode = to.child("col"); + } + XMLNode toRowNode = to.child("xdr:row"); + if (toRowNode.empty()) { + toRowNode = to.child("row"); + } + + if (!toColNode.empty() && !toRowNode.empty()) { + imgAnchorInfo->toCol = toColNode.text().as_uint(); + imgAnchorInfo->toRow = toRowNode.text().as_uint(); + } + + // Extract 'to' offsets + XMLNode toColOffNode = to.child("xdr:colOff"); + if (toColOffNode.empty()) { + toColOffNode = to.child("colOff"); + } + XMLNode toRowOffNode = to.child("xdr:rowOff"); + if (toRowOffNode.empty()) { + toRowOffNode = to.child("rowOff"); + } + + imgAnchorInfo->toColOffset = toColOffNode.empty() ? 0 : toColOffNode.text().as_int(); + imgAnchorInfo->toRowOffset = toRowOffNode.empty() ? 0 : toRowOffNode.text().as_int(); + } + } else { + // For oneCellAnchor, to = from + imgAnchorInfo->toCol = imgAnchorInfo->fromCol; + imgAnchorInfo->toRow = imgAnchorInfo->fromRow; + imgAnchorInfo->toColOffset = 0; + imgAnchorInfo->toRowOffset = 0; + } + + // Extract dimensions from xdr:ext element + XMLNode ext = anchorXMLNode.child("xdr:ext"); + if (ext.empty()) { + ext = anchorXMLNode.child("ext"); + } + if (!ext.empty()) { + imgAnchorInfo->displayWidthEMUs = ext.attribute("cx").as_uint(); + imgAnchorInfo->displayHeightEMUs = ext.attribute("cy").as_uint(); + } + + // Extract image information from pic element + XMLNode pic = anchorXMLNode.child("xdr:pic"); + if (pic.empty()) { + pic = anchorXMLNode.child("pic"); + } + if (!pic.empty()) { + // Extract image ID + XMLNode nvPicPr = pic.child("xdr:nvPicPr"); + if (nvPicPr.empty()) { + nvPicPr = pic.child("nvPicPr"); + } + if (!nvPicPr.empty()) { + XMLNode cNvPr = nvPicPr.child("xdr:cNvPr"); + if (cNvPr.empty()) { + cNvPr = nvPicPr.child("cNvPr"); + } + if (!cNvPr.empty()) { + imgAnchorInfo->imageId = cNvPr.attribute("id").value(); + } + } + + // Extract relationship ID + XMLNode blipFill = pic.child("xdr:blipFill"); + if (blipFill.empty()) { + blipFill = pic.child("a:blipFill"); + } + if (blipFill.empty()) { + blipFill = pic.child("blipFill"); + } + if (!blipFill.empty()) { + XMLNode blip = blipFill.child("a:blip"); + if (blip.empty()) { + blip = blipFill.child("blip"); + } + if (!blip.empty()) { + imgAnchorInfo->relationshipId = blip.attribute("r:embed").value(); + } + } + + // Extract actual dimensions from shape properties + XMLNode spPr = pic.child("xdr:spPr"); + if (spPr.empty()) { + spPr = pic.child("spPr"); + } + if (!spPr.empty()) { + XMLNode xfrm = spPr.child("a:xfrm"); + if (!xfrm.empty()) { + XMLNode xfrmExt = xfrm.child("a:ext"); + if (!xfrmExt.empty()) { + imgAnchorInfo->actualWidthEMUs = xfrmExt.attribute("cx").as_uint(); + imgAnchorInfo->actualHeightEMUs = xfrmExt.attribute("cy").as_uint(); + } + } + } + } + + // If actual dimensions weren't found, use display dimensions + if (imgAnchorInfo->actualWidthEMUs == 0) { + imgAnchorInfo->actualWidthEMUs = imgAnchorInfo->displayWidthEMUs; + } + if (imgAnchorInfo->actualHeightEMUs == 0) { + imgAnchorInfo->actualHeightEMUs = imgAnchorInfo->displayHeightEMUs; + } + + return true; + } + catch (const std::exception&) { + return false; + } + } +} + +// ========== XLImageAnchorUtils Implementation ========== // + +OpenXLSX::XLImageAnchorType OpenXLSX::XLImageAnchorUtils::stringToAnchorType(const std::string& anchorTypeStr) +{ + if (anchorTypeStr.find("oneCellAnchor") != std::string::npos) return XLImageAnchorType::OneCellAnchor; + if (anchorTypeStr.find("twoCellAnchor") != std::string::npos) return XLImageAnchorType::TwoCellAnchor; + return XLImageAnchorType::Unknown; +} + +std::string OpenXLSX::XLImageAnchorUtils::anchorTypeToString(XLImageAnchorType anchorType) +{ + switch (anchorType) { + case XLImageAnchorType::OneCellAnchor: return "oneCellAnchor"; + case XLImageAnchorType::TwoCellAnchor: return "twoCellAnchor"; + case XLImageAnchorType::Unknown: + default: return ""; + } +} diff --git a/OpenXLSX/sources/XLImage.cpp b/OpenXLSX/sources/XLImage.cpp new file mode 100644 index 00000000..c5ec5b3e --- /dev/null +++ b/OpenXLSX/sources/XLImage.cpp @@ -0,0 +1,985 @@ +/* + + ____ ____ ___ ____ ____ ____ ___ + 6MMMMb `MM( )M' `MM' 6MMMMb\`MM( )M' + 8P Y8 `MM. d' MM 6M' ` `MM. d' +6M Mb __ ____ ____ ___ __ `MM. d' MM MM `MM. d' +MM MM `M6MMMMb 6MMMMb `MM 6MMb `MM. d' MM YM. `MM. d' +MM MM MM MM MM MM MM' MM `MMd MM YMMMMb `MMd +MM MM MM MM MMMMMMMM MM MM d'`MM. MM `Mb dMM. +YM M9 MM MM MM MM MM d' `MM. MM MM d'`MM. + 8b d8 MM. ,M9 YM d9 MM MM d' `MM. MM / L ,M9 d' `MM. + YMMMM9 MMYMMM9 YMMMM9 _MM_ _MM_M(_ _)MM_ _MMMMMMM MYMMMM9 _M(_ _)MM_ + MM + MM + _MM_ + + Copyright (c) 2018, Kenneth Troldal Balslev + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + - Neither the name of the author nor the + names of any contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + +// ===== External Includes ===== // +#include // std::find +#include // std::round, std::fabs +#include // std::ifstream +#include // std::ifstream +#include // std::stringstream +#include // std::stringstream + +// ===== OpenXLSX Includes ===== // +#include "XLImage.hpp" +#include "XLDocument.hpp" +#include "XLException.hpp" +#include "XLCellReference.hpp" +#include +#include + +using namespace OpenXLSX; + +namespace OpenXLSX +{ + //============================================================================= + // XLImage Implementation + //============================================================================= + + XLImage::XLImage(XLDocument* parentDoc, size_t imageDataHash, XLMimeType mimeType, + const std::string& extension, const std::string& filePackagePath, + uint32_t widthPixels, uint32_t heightPixels) + : m_parentDoc(parentDoc) + , m_imageDataHash(imageDataHash) + , m_mimeType(mimeType) + , m_extension(extension) + , m_filePackagePath(filePackagePath) + , m_widthPixels(widthPixels) + , m_heightPixels(heightPixels) + { + } + + std::vector XLImage::getImageData() const + { + if (!m_parentDoc || m_filePackagePath.empty()) { + return {}; + } + + try { + std::string dataStr = m_parentDoc->readFile(m_filePackagePath); + return std::vector(dataStr.begin(), dataStr.end()); + } catch (...) { + return {}; + } + } + + int XLImage::verifyData(std::string* dbgMsg) const + { + int issueCount = 0; + + if (!m_parentDoc) { + appendDbgMsg(dbgMsg, "XLImage: parentDoc is null"); + issueCount++; + return issueCount; + } + + if (m_filePackagePath.empty()) { + appendDbgMsg(dbgMsg, "XLImage: filePackagePath is empty"); + issueCount++; + return issueCount; + } + + // Get image data from archive + std::vector imageData = getImageData(); + if (imageData.empty()) { + appendDbgMsg(dbgMsg, "XLImage: image data is empty"); + issueCount++; + return issueCount; + } + + // Verify hash matches + size_t computedHash = XLImageUtils::imageDataHash(imageData); + if (computedHash != m_imageDataHash) { + appendDbgMsg(dbgMsg, "XLImage: hash mismatch - stored: " + std::to_string(m_imageDataHash) + + ", computed: " + std::to_string(computedHash)); + issueCount++; + } + + // Compute metadata from image data + XLMimeType computedMimeType = XLImageUtils::extensionToMimeType(m_extension); + if (computedMimeType != m_mimeType) { + std::string storedMimeTypeStr = XLImageUtils::mimeTypeToString(m_mimeType); + std::string computedMimeTypeStr = XLImageUtils::mimeTypeToString(computedMimeType); + appendDbgMsg(dbgMsg, "XLImage: MIME type mismatch - stored: " + storedMimeTypeStr + + ", computed: " + computedMimeTypeStr); + issueCount++; + } + + // Get dimensions from image data + auto dimensions = XLImageUtils::getImageDimensions(imageData); + uint32_t computedWidth = dimensions.first; + uint32_t computedHeight = dimensions.second; + + if (computedWidth != m_widthPixels) { + appendDbgMsg(dbgMsg, "XLImage: width mismatch - stored: " + std::to_string(m_widthPixels) + + ", computed: " + std::to_string(computedWidth)); + issueCount++; + } + if (computedHeight != m_heightPixels) { + appendDbgMsg(dbgMsg, "XLImage: height mismatch - stored: " + std::to_string(m_heightPixels) + + ", computed: " + std::to_string(computedHeight)); + issueCount++; + } + + return issueCount; + } + + //============================================================================= + // XLImageManager Implementation + //============================================================================= + + XLImageManager::XLImageManager(XLDocument* parentDoc) + : m_parentDoc(parentDoc) + { + } + + void XLImageManager::prune() + { + auto it = m_images.begin(); + while (it != m_images.end()) { + if (!it->get() || it->use_count() <= 1) { + // Remove from archive if it has a file package path + if (it->get() && !it->get()->filePackagePath().empty()) { + try { + m_parentDoc->deleteEntry(it->get()->filePackagePath()); + } catch (...) { + // Ignore deletion errors + } + } + it = m_images.erase(it); + } else { + ++it; + } + } + } + + XLImageShPtr XLImageManager::findOrAddImage(const std::string& packagePath, + const std::vector& imageData, + XLContentType contentType) + { + // First, try to find existing image by data hash + XLImageShPtr existingImage = findImageByImageData(imageData); + if (existingImage) { + return existingImage; + } + + // Image doesn't exist, create new one + size_t imageDataHash = XLImageUtils::imageDataHash(imageData); + XLMimeType mimeType = XLImageUtils::contentTypeToMimeType(contentType); + std::string extension = XLImageUtils::mimeTypeToExtension(mimeType); + + // Generate package filename + std::string packageImageFilename; + + if (!packagePath.empty()) { + // Use the provided package path (from XML when loading existing files) + packageImageFilename = packagePath; + } else { + // Generate unique package filename using efficient algorithm + packageImageFilename = generateUniquePackageFilename(extension); + } + + // Get image dimensions + auto dimensions = XLImageUtils::getImageDimensions(imageData); + uint32_t widthPixels = dimensions.first; + uint32_t heightPixels = dimensions.second; + + // Add image entry to document + if (!m_parentDoc->addImageEntry(packageImageFilename, imageData, contentType)) { + return nullptr; + } + + // Create new XLImage object + auto newImage = std::make_shared(m_parentDoc, imageDataHash, mimeType, extension, + packageImageFilename, widthPixels, heightPixels); + + // Add to collection + m_images.push_back(newImage); + + return newImage; + } + + XLImageShPtr XLImageManager::findImageByImageData(const std::vector& imageData) const + { + size_t targetHash = XLImageUtils::imageDataHash(imageData); + + for (const auto& image : m_images) { + if (image && image->imageDataHash() == targetHash) { + return image; + } + } + + return nullptr; + } + + + std::string XLImageManager::generateUniquePackageFilename(const std::string& extension) const + { + // Collect all existing package filenames and extract base names (without extension) + std::vector existingBaseNames; + for (const auto& image : m_images) { + if (image && !image->filePackagePath().empty()) { + std::string packagePath = image->filePackagePath(); + // Extract filename from "xl/media/image1.png" -> "image1.png" + size_t lastSlash = packagePath.find_last_of('/'); + if (lastSlash != std::string::npos) { + std::string filename = packagePath.substr(lastSlash + 1); + // Extract base name without extension: "image1.png" -> "image1" + size_t lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + std::string baseName = filename.substr(0, lastDot); + existingBaseNames.push_back(baseName); + } + } + } + } + + // Sort the base names for efficient binary search + std::sort(existingBaseNames.begin(), existingBaseNames.end()); + + // Find the next available image number + int imageNumber = 1; + std::string candidateBaseName; + + do { + candidateBaseName = "image" + std::to_string(imageNumber); + imageNumber++; + } while (std::binary_search(existingBaseNames.begin(), existingBaseNames.end(), candidateBaseName)); + + // Return the unique package filename + return "xl/media/" + candidateBaseName + extension; + } + + + XLImageShPtr XLImageManager::findImageByFilePackagePath(const std::string& filePackagePath) const + { + for (const auto& image : m_images) { + if (image && image->filePackagePath() == filePackagePath) { + return image; + } + } + + return nullptr; + } + + size_t XLImageManager::getImageCount() const + { + return m_images.size(); + } + + std::vector::const_iterator XLImageManager::begin() const + { + return m_images.begin(); + } + + std::vector::const_iterator XLImageManager::end() const + { + return m_images.end(); + } + + + /** + * @brief Remove embedded image vector for worksheet + * @param sheetName The name of the worksheet + */ + void XLImageManager::eraseEmbImgsForSheetName(const std::string& sheetName) + { + m_sheetNmEmbImgVMap.erase(sheetName); + } + + /** + * @brief Find or create embedded image vector for worksheet + * @param sheetName The name of the worksheet + * @return Shared pointer to the embedded image vector + */ + XLEmbImgVecShPtr XLImageManager::getEmbImgsForSheetName(const std::string& sheetName) + { + auto itr = m_sheetNmEmbImgVMap.lower_bound(sheetName); + if ((itr != m_sheetNmEmbImgVMap.end()) && (sheetName == itr->first)) { + return itr->second; + } else { + auto result = std::make_shared(); + m_sheetNmEmbImgVMap.insert(itr, std::make_pair(sheetName, result)); + return result; + } + } + + /** + * @brief Find embedded image vector for worksheet, return null pointer if not found + * @param sheetName The name of the worksheet + * @return Shared pointer to the embedded image vector, or nullptr if not found + */ + XLEmbImgVecShPtr XLImageManager::getEmbImgsForSheetName(const std::string& sheetName) const + { + auto itr = m_sheetNmEmbImgVMap.find(sheetName); + if (itr != m_sheetNmEmbImgVMap.end()) { + return itr->second; + } + return nullptr; + } + + int XLImageManager::verifyData(std::string* dbgMsg) const + { + int totalIssues = 0; + + // Check each image has same parentDoc + for (const auto& image : m_images) { + if (image && image->parentDoc() != m_parentDoc) { + appendDbgMsg(dbgMsg, "XLImageManager: image has different parentDoc"); + totalIssues++; + } + } + + + // Check each image has unique imageData hash + // TODO: allow for hash collision -- compare binary image data when hash values are same. + std::set imageDataHashes; + for (const auto& image : m_images) { + if (image) { + if (imageDataHashes.find(image->imageDataHash()) != imageDataHashes.end()) { + appendDbgMsg(dbgMsg, "XLImageManager: duplicate imageDataHash: " + std::to_string(image->imageDataHash())); + totalIssues++; + } else { + imageDataHashes.insert(image->imageDataHash()); + } + } + } + + // Call verifyData() for each non-null image + for (const auto& image : m_images) { + if (image) { + totalIssues += image->verifyData(dbgMsg); + } + } + + // Check package path uniqueness and format + std::set packagePaths; + for (const auto& image : m_images) { + if (image && !image->filePackagePath().empty()) { + std::string packagePath = image->filePackagePath(); + + // Check format + if (packagePath.find("xl/media/") != 0) { + appendDbgMsg(dbgMsg, "XLImageManager: package path '" + packagePath + "' does not start with 'xl/media/'"); + totalIssues++; + } + + // Check uniqueness + if (packagePaths.find(packagePath) != packagePaths.end()) { + appendDbgMsg(dbgMsg, "XLImageManager: duplicate package path: " + packagePath); + totalIssues++; + } else { + packagePaths.insert(packagePath); + } + } + } + + // Check that all images have valid parent document + for (const auto& image : m_images) { + if (image && !image->parentDoc()) { + appendDbgMsg(dbgMsg, "XLImageManager: image has null parentDoc"); + totalIssues++; + } + } + + // Check that all images have valid image data + for (const auto& image : m_images) { + if (image && image->getImageData().empty()) { + appendDbgMsg(dbgMsg, "XLImageManager: image has empty image data"); + totalIssues++; + } + } + + // Check consistency between m_images vector and archive + if (m_parentDoc) { + // Check that all images in m_images actually exist in the archive + for (const auto& image : m_images) { + if (image && !image->filePackagePath().empty()) { + std::string archiveContent = m_parentDoc->readFile(image->filePackagePath()); + if (archiveContent.empty()) { + appendDbgMsg(dbgMsg, "XLImageManager: image '" + image->filePackagePath() + "' not found in archive"); + totalIssues++; + } + } + } + + // Check for orphaned files in archive (files that exist in archive but not in m_images) + // This is more complex and would require iterating through all archive entries + // For now, we'll skip this check as it's expensive and the above check is more critical + } + + std::set uniqueEmbImgPtrs; + // Check that each ptr in m_sheetNmEmbImgVMap is unique + std::set uniquePtrs; + for (const auto& pair : m_sheetNmEmbImgVMap) { + if (pair.second) { + if (uniquePtrs.find(pair.second) != uniquePtrs.end()) { + appendDbgMsg(dbgMsg, "XLImageManager: duplicate XLEmbImgVecShPtr for worksheet: " + pair.first); + totalIssues++; + } else { + uniquePtrs.insert(pair.second); + } + } + } + + // Check that each ptr in m_sheetNmEmbImgVMap is non-NULL + for (const auto& pair : m_sheetNmEmbImgVMap) { + if (!pair.second) { + appendDbgMsg(dbgMsg, "XLImageManager: NULL XLEmbImgVecShPtr for worksheet: " + pair.first); + totalIssues++; + } + } + + // For each ptr in m_sheetNmEmbImgVMap, call ptr->verifyData() + for (const auto& pair : m_sheetNmEmbImgVMap) { + if (pair.second) { + for (const auto& embImage : *pair.second) { + std::string embImageDbgMsg; + int embImageIssues = embImage.verifyData(&embImageDbgMsg); + if (embImageIssues > 0) { + appendDbgMsg(dbgMsg, "XLImageManager: worksheet '" + pair.first + "' has embedded image issues: " + embImageDbgMsg); + totalIssues += embImageIssues; + } + } + } + } + + return totalIssues; + } + + void XLImageManager::printReferenceCounts(const std::string& title) const + { + std::cout << "\n=== " << title << " ===" << std::endl; + std::cout << "Total XLImageShPtr objects: " << m_images.size() << std::endl; + + for (size_t i = 0; i < m_images.size(); ++i) { + const auto& image = m_images[i]; + if (image) { + std::cout << " [" << i << "] refs=" << image.use_count() + << " package=" << image->filePackagePath() + << " mime=" << XLImageUtils::mimeTypeToString(image->mimeType()) + << " size=" << image->widthPixels() << "x" << image->heightPixels() + << " hash=" << image->imageDataHash() << std::endl; + } else { + std::cout << " [" << i << "] NULL pointer" << std::endl; + } + } + std::cout << "=== End " << title << " ===" << std::endl; + } + + + // ========== XLEmbeddedImage Member Functions ========== // + + /** + * @details Set the image ID + */ + void XLEmbeddedImage::setId(const std::string& imageId) + { + m_imageAnchor.imageId = imageId; + } + + /** + * @details Get the image data + */ + std::vector XLEmbeddedImage::data() const + { + static const std::vector empty; + if (!m_image) return empty; + return m_image->getImageData(); + } + + /** + * @details Get the MIME type + */ + /** + * @details Get the file extension + */ + const std::string& XLEmbeddedImage::extension() const + { + static std::string empty; + if (!m_image) return empty; + return m_image->extension(); + } + + /** + * @details Get the unique image ID + */ + const std::string& XLEmbeddedImage::id() const + { + return m_imageAnchor.imageId; + } + + /** + * @details Get the image width in pixels + */ + uint32_t XLEmbeddedImage::widthPixels() const + { + if (!m_image) return 0; + return m_image->widthPixels(); + } + + /** + * @details Get the image height in pixels + */ + uint32_t XLEmbeddedImage::heightPixels() const + { + if (!m_image) return 0; + return m_image->heightPixels(); + } + + /** + * @details Get the image MIME type + */ + XLMimeType XLEmbeddedImage::mimeType() const + { + if (!m_image) return XLMimeType::Unknown; + return m_image->mimeType(); + } + + /** + * @details Set the display width in Excel units (EMUs) + */ + void XLEmbeddedImage::setDisplayWidthEMUs(uint32_t width) + { + m_imageAnchor.displayWidthEMUs = width; + } + + /** + * @details Set the display height in Excel units (EMUs) + */ + void XLEmbeddedImage::setDisplayHeightEMUs(uint32_t height) + { + m_imageAnchor.displayHeightEMUs = height; + } + + /** + * @details Get the display width in Excel units (EMUs) + */ + uint32_t XLEmbeddedImage::displayWidthEMUs() const + { + return m_imageAnchor.displayWidthEMUs; + } + + /** + * @details Get the display height in Excel units (EMUs) + */ + uint32_t XLEmbeddedImage::displayHeightEMUs() const + { + return m_imageAnchor.displayHeightEMUs; + } + + /** + * @details Check if the image is valid + */ + bool XLEmbeddedImage::isValid() const + { + return m_image && !m_imageAnchor.imageId.empty(); + } + + /** + * @details Get the content type for this image + */ + XLContentType XLEmbeddedImage::contentType() const + { + if (!m_image) return XLContentType::Unknown; + return XLImageUtils::mimeTypeToContentType(m_image->mimeType()); + } + + // ========== XLEmbeddedImage Private Member Functions ========== // + + /** + * @details Generate a unique image ID + */ + std::string XLEmbeddedImage::generateId(uint32_t imageNumber) + { + return "img" + std::to_string(imageNumber); + } + + /** + * @details Set display size in pixels (converts to EMUs automatically) + */ + void XLEmbeddedImage::setDisplaySizePixels(uint32_t widthPixels, uint32_t heightPixels) + { + m_imageAnchor.displayWidthEMUs = XLImageUtils::pixelsToEmus(widthPixels); + m_imageAnchor.displayHeightEMUs = XLImageUtils::pixelsToEmus(heightPixels); + } + + /** + * @details Compare two images for debugging + */ + int XLEmbeddedImage::compare(const XLEmbeddedImage& other, std::string* diffMsg) const + { + + // Compare image data (most important for embedded images) + if (m_image != other.m_image) { + auto thisData = m_image ? m_image->getImageData() : std::vector(); + auto otherData = other.m_image ? other.m_image->getImageData() : std::vector(); + if (thisData != otherData) { + appendDbgMsg(diffMsg, "image data differs (size: " + std::to_string(thisData.size()) + + " vs " + std::to_string(otherData.size()) + " bytes)"); + return thisData.size() < otherData.size() ? -1 : 1; + } + } + + // Compare MIME type + const XLMimeType thisMimeType = mimeType(); + const XLMimeType otherMimeType = other.mimeType(); + int mimeCompare = static_cast(thisMimeType) - static_cast(otherMimeType); + if (mimeCompare != 0) { + std::string thisMimeTypeStr = m_image ? XLImageUtils::mimeTypeToString(m_image->mimeType()) : ""; + std::string otherMimeTypeStr = other.m_image ? XLImageUtils::mimeTypeToString(other.m_image->mimeType()) : ""; + appendDbgMsg(diffMsg, "MIME type differs: '" + thisMimeTypeStr + "' vs '" + otherMimeTypeStr + "'"); + return mimeCompare; + } + + // Compare extension + std::string thisExtension = m_image ? m_image->extension() : ""; + std::string otherExtension = other.m_image ? other.m_image->extension() : ""; + int extCompare = thisExtension.compare(otherExtension); + if (extCompare != 0) { + appendDbgMsg(diffMsg, "extension differs: '" + thisExtension + "' vs '" + otherExtension + "'"); + return extCompare; + } + + // Compare ID + int idCompare = m_imageAnchor.imageId.compare(other.m_imageAnchor.imageId); + if (idCompare != 0) { + appendDbgMsg(diffMsg, "image ID differs: '" + m_imageAnchor.imageId + "' vs '" + other.m_imageAnchor.imageId + "'"); + return idCompare; + } + + // Compare dimensions + uint32_t thisWidth = m_image ? m_image->widthPixels() : 0; + uint32_t otherWidth = other.m_image ? other.m_image->widthPixels() : 0; + if (thisWidth != otherWidth) { + appendDbgMsg(diffMsg, "width differs: " + std::to_string(thisWidth) + + " vs " + std::to_string(otherWidth) + " pixels"); + return thisWidth < otherWidth ? -1 : 1; + } + + uint32_t thisHeight = m_image ? m_image->heightPixels() : 0; + uint32_t otherHeight = other.m_image ? other.m_image->heightPixels() : 0; + if (thisHeight != otherHeight) { + appendDbgMsg(diffMsg, "height differs: " + std::to_string(thisHeight) + + " vs " + std::to_string(otherHeight) + " pixels"); + return thisHeight < otherHeight ? -1 : 1; + } + + // Compare display dimensions + if (m_imageAnchor.displayWidthEMUs != other.m_imageAnchor.displayWidthEMUs) { + appendDbgMsg(diffMsg, "display width differs: " + std::to_string(m_imageAnchor.displayWidthEMUs) + + " vs " + std::to_string(other.m_imageAnchor.displayWidthEMUs) + " EMUs"); + return m_imageAnchor.displayWidthEMUs < other.m_imageAnchor.displayWidthEMUs ? -1 : 1; + } + + if (m_imageAnchor.displayHeightEMUs != other.m_imageAnchor.displayHeightEMUs) { + appendDbgMsg(diffMsg, "display height differs: " + std::to_string(m_imageAnchor.displayHeightEMUs) + + " vs " + std::to_string(other.m_imageAnchor.displayHeightEMUs) + " EMUs"); + return m_imageAnchor.displayHeightEMUs < other.m_imageAnchor.displayHeightEMUs ? -1 : 1; + } + + // Compare registry/relationship fields + int relIdCompare = m_imageAnchor.relationshipId.compare(other.m_imageAnchor.relationshipId); + if (relIdCompare != 0) { + appendDbgMsg(diffMsg, "relationship ID differs: '" + m_imageAnchor.relationshipId + "' vs '" + other.m_imageAnchor.relationshipId + "'"); + return relIdCompare; + } + + int cellCompare = anchorCell().compare(other.anchorCell()); + if (cellCompare != 0) { + appendDbgMsg(diffMsg, "anchor cell differs: '" + anchorCell() + "' vs '" + other.anchorCell() + "'"); + return cellCompare; + } + + int typeCompare = static_cast(m_imageAnchor.anchorType) - static_cast(other.m_imageAnchor.anchorType); + if (typeCompare != 0) { + std::string thisAnchorTypeStr = XLImageAnchorUtils::anchorTypeToString(m_imageAnchor.anchorType); + std::string otherAnchorTypeStr = XLImageAnchorUtils::anchorTypeToString(other.m_imageAnchor.anchorType); + appendDbgMsg(diffMsg, "anchor type differs: '" + thisAnchorTypeStr + "' vs '" + otherAnchorTypeStr + "'"); + return typeCompare; + } + + // Images are identical + return 0; + } + + /** + * @details Verify internal data integrity and class invariants + */ + int XLEmbeddedImage::verifyData(std::string* dbgMsg) const + { + int errorCount = 0; + + // Check basic data integrity + if (!m_image || m_image->getImageData().empty()) { + appendDbgMsg(dbgMsg, "image data is empty"); + errorCount++; + } + + // MIME type + const XLMimeType thisMimeType = mimeType(); + const std::string mimeTypeStr = XLImageUtils::mimeTypeToString(thisMimeType); + + // Check extension consistency + std::string extension = m_image ? m_image->extension() : ""; + if (extension.empty()) { + appendDbgMsg(dbgMsg, "file extension is empty"); + errorCount++; + } + + // Check ID consistency + if (m_imageAnchor.imageId.empty()) { + appendDbgMsg(dbgMsg, "image ID is empty"); + errorCount++; + } + + // Check relationship ID consistency + if (m_imageAnchor.relationshipId.empty()) { + appendDbgMsg(dbgMsg, "relationship ID is empty"); + errorCount++; + } + + // Check anchor type consistency + if (m_imageAnchor.anchorType == XLImageAnchorType::Unknown) { + appendDbgMsg(dbgMsg, "anchor type is unknown"); + errorCount++; + } + + // Check dimension consistency + uint32_t widthPixels = m_image ? m_image->widthPixels() : 0; + uint32_t heightPixels = m_image ? m_image->heightPixels() : 0; + if (widthPixels == 0) { + appendDbgMsg(dbgMsg, "width in pixels is zero"); + errorCount++; + } + + if (heightPixels == 0) { + appendDbgMsg(dbgMsg, "height in pixels is zero"); + errorCount++; + } + + // Check display dimension consistency + if (m_imageAnchor.displayWidthEMUs == 0) { + appendDbgMsg(dbgMsg, "display width in EMUs is zero"); + errorCount++; + } + + if (m_imageAnchor.displayHeightEMUs == 0) { + // For twoCellAnchor, Excel may legitimately store zero values in the ext element + // Excel handles the sizing based on cell positions, so zero is acceptable + if (!m_imageAnchor.isTwoCell()) { + appendDbgMsg(dbgMsg, "display height in EMUs is zero"); + errorCount++; + } + } + + // Check MIME type and extension consistency + if ( (thisMimeType != XLMimeType::Unknown) && !extension.empty()) { + if (( XLMimeType::PNG == thisMimeType && extension != ".png") || + ( XLMimeType::JPEG == thisMimeType && extension != ".jpg" && extension != ".jpeg") || + ( XLMimeType::GIF == thisMimeType && extension != ".gif") || + ( XLMimeType::BMP == thisMimeType && extension != ".bmp")) { + appendDbgMsg(dbgMsg, "MIME type '" + mimeTypeStr + + "' does not match extension '" + extension + "'"); + errorCount++; + } + } + + // Check package path consistency + if (m_image && !m_image->filePackagePath().empty()) { + std::string packagePath = m_image->filePackagePath(); + if (packagePath.find("xl/media/") != 0) { + appendDbgMsg(dbgMsg, "package path '" + packagePath + "' does not start with 'xl/media/'"); + errorCount++; + } + } + + return errorCount; + } + + // Registry/relationship setter functions (for XLImageInfo compatibility) + /** + * @details Set the relationship ID + */ + void XLEmbeddedImage::setRelationshipId(const std::string& relationshipId) + { + m_imageAnchor.relationshipId = relationshipId; + } + + /** + * @details Set the anchor cell reference + */ + void XLEmbeddedImage::setAnchorCell(const std::string& anchorCell) + { + auto [row, col] = cellRefToRowCol(anchorCell); + m_imageAnchor.fromRow = row; + m_imageAnchor.fromCol = col; + } + + /** + * @details Set the anchor type + */ + void XLEmbeddedImage::setAnchorType(XLImageAnchorType anchorType) + { + m_imageAnchor.anchorType = anchorType; + } + + // Registry/relationship getter functions (for XLImageInfo compatibility) + /** + * @details Get the relationship ID + */ + const std::string& XLEmbeddedImage::relationshipId() const + { + return m_imageAnchor.relationshipId; + } + + /** + * @details Get the anchor cell reference + */ + std::string XLEmbeddedImage::anchorCell() const + { + return rowColToCellRef(m_imageAnchor.fromRow, m_imageAnchor.fromCol); + } + + /** + * @details Get the anchor type + */ + const XLImageAnchorType& XLEmbeddedImage::anchorType() const + { + return m_imageAnchor.anchorType; + } + + /** + * @details Check if this image is empty (default constructed) + */ + bool XLEmbeddedImage::isEmpty() const + { + return m_imageAnchor.imageId.empty(); + } + + /** + * @details Get the shared pointer to the image data + */ + XLImageShPtr XLEmbeddedImage::getImage() const + { + return m_image; + } + + /** + * @details Set the shared pointer to the image data + */ + void XLEmbeddedImage::setImage(XLImageShPtr image) + { + m_image = image; + } + + /* TODO: add unit test */ + std::string XLEmbeddedImage::rowColToCellRef(uint32_t row, uint16_t col) + { + std::string result; + + // Convert column to letters (A, B, C, ..., Z, AA, AB, ...) + ++col; + while (col > 0) { + col--; // Convert to 0-based first + result = static_cast('A' + (col % 26)) + result; + col = col / 26; + } + + // Add row number (1-based) + result += std::to_string(row + 1); + + return result; + } + + /* TODO: add unit test */ + std::pair XLEmbeddedImage::cellRefToRowCol(const std::string& cellRef) + { + uint32_t row = 0; + uint16_t col = 0; + + // Parse the cell reference (e.g., "A1", "B5", "AA10") + size_t i = 0; + while (i < cellRef.length() && std::isalpha(cellRef[i])) { + col = col * 26 + (cellRef[i] - 'A' + 1); + i++; + } + col--; // Convert to 0-based + + if (i < cellRef.length()) { + row = std::stoul(cellRef.substr(i)) - 1; // Convert to 0-based + } + + return {row, col}; + } + + /** + * @brief Robust XML element name matching that handles namespace prefixes + * @param elementName The actual element name (may include namespace prefix) + * @param targetName The target element name to match + * @return True if the element name matches the target (with or without namespace) + * + * @example + * Exact match: "oneCellAnchor" matches "oneCellAnchor" + * Namespace prefix: "xdr:oneCellAnchor" matches "oneCellAnchor" + * Different namespace: "a:oneCellAnchor" matches "oneCellAnchor" + */ + bool XLImageUtils::matchesElementName(const std::string& elementName, const std::string& targetName) + { + // Exact match + if (elementName == targetName) { + return true; + } + + // Check if element name ends with target name (handles namespace prefixes) + if (elementName.length() > targetName.length() + 1) { + size_t pos = elementName.find_last_of(':'); + if (pos != std::string::npos && pos + 1 < elementName.length()) { + std::string localName = elementName.substr(pos + 1); + if (localName == targetName) { + return true; + } + } + } + + // Check if element name starts with target name followed by colon + if (elementName.length() == targetName.length() + 1 && elementName[targetName.length()] == ':') { + if (elementName.substr(0, targetName.length()) == targetName) { + return true; + } + } + + return false; + } + +} // namespace OpenXLSX diff --git a/OpenXLSX/sources/XLImageUtils.cpp b/OpenXLSX/sources/XLImageUtils.cpp new file mode 100644 index 00000000..f1b21d4f --- /dev/null +++ b/OpenXLSX/sources/XLImageUtils.cpp @@ -0,0 +1,755 @@ +/* + + ____ ____ ___ ____ ____ ____ ___ + 6MMMMb `MM( )M' `MM' 6MMMMb\`MM( )M' + 8P Y8 `MM. d' MM 6M' ` `MM. d' +6M Mb __ ____ ____ ___ __ `MM. d' MM MM `MM. d' +MM MM `M6MMMMb 6MMMMb `MM 6MMb `MM. d' MM YM. `MM. d' +MM MM MM MM MM MM MM' MM `MMd MM YMMMMb `MMd +MM MM MM MM MMMMMMMM MM MM d'`MM. MM `Mb dMM. +YM M9 MM MM MM MM MM d' `MM. MM MM d'`MM. + 8b d8 MM. ,M9 YM d9 MM MM d' `MM. MM / L ,M9 d' `MM. + YMMMM9 MMYMMM9 YMMMM9 _MM_ _MM_M(_ _)MM_ _MMMMMMM MYMMMM9 _M(_ _)MM_ + MM + MM + _MM_ + + Copyright (c) 2018, Kenneth Troldal Balslev + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + - Neither the name of the author nor the + names of any contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + +// ===== External Includes ===== // +#include +#include + +// ===== OpenXLSX Includes ===== // +#include "XLImage.hpp" + +using namespace OpenXLSX; + +// ========== XLImageUtils Static Data ========== // + +// These contain the binary data from the tiny image files in the images directory + +// 1x1 red PNG (minimal) +const std::vector XLImageUtils::red1x1PNGData = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53, 0xDE, 0x00, 0x00, 0x00, + 0x0C, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, 0xF8, 0x0F, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, + 0x42, 0x60, 0x82 +}; + +// tiny_png.png - Small PNG image +const std::vector XLImageUtils::png31x15Data = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x0f, + 0x08, 0x02, 0x00, 0x00, 0x00, 0x93, 0x1a, 0x83, 0xf5, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00, + 0x00, 0x01, 0xa8, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4f, 0x63, 0x7c, 0xff, + 0x64, 0x03, 0x03, 0xcd, 0x00, 0x13, 0x94, 0xa6, 0x0d, 0x00, 0xbb, 0xfd, + 0xf1, 0xfe, 0x7f, 0xa1, 0x5b, 0xa1, 0x02, 0x50, 0x20, 0xc5, 0xb8, 0xba, + 0x88, 0x51, 0x96, 0x81, 0xe1, 0xfb, 0x85, 0x98, 0x6b, 0x67, 0x18, 0xa4, + 0x42, 0x96, 0x48, 0x0a, 0x40, 0x65, 0x40, 0x22, 0xef, 0xd3, 0x8c, 0x1d, + 0xed, 0xa0, 0x7c, 0xce, 0x89, 0x29, 0xc2, 0xfb, 0xa1, 0x6c, 0x06, 0xc5, + 0xe0, 0x17, 0xfd, 0x9e, 0x7f, 0xa0, 0x1c, 0x06, 0x06, 0x16, 0x28, 0xcd, + 0x60, 0xcc, 0x78, 0x22, 0x92, 0x11, 0xca, 0x66, 0x60, 0x38, 0xb2, 0xfc, + 0x5f, 0x68, 0x1f, 0x03, 0xd0, 0x02, 0x11, 0x10, 0xef, 0xfa, 0xb3, 0xf3, + 0x87, 0x24, 0xe1, 0xc6, 0x21, 0x81, 0x17, 0x7c, 0x85, 0x35, 0x7c, 0x0a, + 0xd9, 0x4f, 0x36, 0x18, 0x42, 0xf8, 0x40, 0x9b, 0x24, 0x0a, 0x19, 0x10, + 0x16, 0xe0, 0x08, 0x19, 0x1b, 0x17, 0x46, 0x8d, 0x67, 0x0c, 0x0f, 0x21, + 0x1c, 0xe5, 0x7c, 0xa9, 0xf7, 0xb3, 0x9e, 0x7f, 0x80, 0x70, 0x90, 0x00, + 0xcb, 0xba, 0x99, 0x7c, 0x0c, 0xc1, 0x2f, 0xf2, 0xa1, 0x46, 0x03, 0xc1, + 0xf7, 0xfc, 0x96, 0x4f, 0x0c, 0x6b, 0xf9, 0x4e, 0x41, 0xb9, 0xc4, 0x85, + 0xbb, 0x82, 0xa4, 0x93, 0xdb, 0xfb, 0xf3, 0x87, 0xa0, 0x3c, 0x18, 0x78, + 0xc1, 0x75, 0xf8, 0xf1, 0xb7, 0x48, 0xa4, 0x70, 0x00, 0x01, 0x89, 0x4f, + 0xfd, 0x73, 0xde, 0x99, 0x41, 0x39, 0xb8, 0x4c, 0x3f, 0xb2, 0xe7, 0xff, + 0x0d, 0x63, 0x06, 0x1b, 0x28, 0x8f, 0x81, 0x41, 0x20, 0x4e, 0x8a, 0xa1, + 0xf0, 0x1e, 0xd4, 0x2f, 0x50, 0xf0, 0x9c, 0xe5, 0xbe, 0xec, 0x1f, 0x19, + 0x28, 0x07, 0x3b, 0x80, 0x87, 0xfb, 0xd9, 0xff, 0x16, 0x67, 0xff, 0x43, + 0xd9, 0x40, 0x00, 0x8e, 0x55, 0x28, 0x1b, 0x0c, 0x04, 0x0c, 0xf3, 0x9f, + 0xed, 0x5b, 0xf4, 0x5d, 0x3e, 0x0e, 0xca, 0x47, 0x07, 0xa7, 0xe6, 0xc8, + 0xb4, 0x9d, 0x80, 0x30, 0xff, 0xc4, 0xb5, 0xbc, 0x08, 0x92, 0x00, 0xb1, + 0x70, 0xc4, 0x2a, 0x16, 0x20, 0x10, 0xa7, 0xa8, 0x14, 0xf3, 0xfc, 0x61, + 0x9c, 0x24, 0x94, 0xcf, 0x20, 0xf9, 0x47, 0xf1, 0x31, 0xcb, 0x13, 0xa0, + 0x43, 0xc0, 0x3c, 0xb3, 0x94, 0x27, 0x1b, 0x52, 0x80, 0x34, 0x30, 0x62, + 0xf9, 0xc1, 0x02, 0x20, 0x40, 0x4a, 0x7a, 0xe7, 0x34, 0x48, 0x63, 0xd8, + 0xdd, 0x00, 0x8f, 0x5d, 0x89, 0x6f, 0xb6, 0xb2, 0x5c, 0xcb, 0xb7, 0xc3, + 0xdd, 0x87, 0x05, 0x90, 0x96, 0x9b, 0xec, 0x24, 0x4d, 0xee, 0x3c, 0x3b, + 0x73, 0x1d, 0xca, 0xfb, 0x13, 0x94, 0x0e, 0x4c, 0x21, 0x12, 0x85, 0x08, + 0x0b, 0x58, 0xd6, 0x35, 0x22, 0xd2, 0x3e, 0x10, 0xe0, 0xb3, 0x19, 0x0b, + 0xe0, 0x34, 0x68, 0x95, 0xba, 0x17, 0xf4, 0x0c, 0xca, 0x03, 0xa7, 0x90, + 0x6f, 0xeb, 0x1a, 0x25, 0x02, 0xd6, 0x42, 0x05, 0x18, 0x2c, 0xde, 0x6e, + 0xa8, 0xff, 0x0e, 0x65, 0x43, 0xf3, 0x2a, 0xcd, 0x00, 0x69, 0x21, 0x43, + 0x2a, 0xa0, 0xa5, 0xe9, 0x0c, 0x0c, 0x00, 0xe4, 0x3f, 0x87, 0xe1, 0xb6, + 0x42, 0x8a, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, + 0x42, 0x60, 0x82 + }; + + // tiny_jpeg.jpg - Small JPEG image (actual file data) + const std::vector XLImageUtils::jpeg32x18Data = { + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0xff, 0xe1, 0x00, 0x22, + 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x01, 0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, + 0x00, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x03, 0x05, 0x03, 0x03, 0x03, 0x03, 0x03, 0x06, 0x04, + 0x04, 0x03, 0x05, 0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x08, + 0x09, 0x0b, 0x09, 0x08, 0x08, 0x0a, 0x08, 0x07, 0x07, 0x0a, 0x0d, 0x0a, + 0x0a, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x07, 0x09, 0x0e, 0x0f, 0x0d, 0x0c, + 0x0e, 0x0b, 0x0c, 0x0c, 0x0c, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x02, 0x02, + 0x02, 0x03, 0x03, 0x03, 0x06, 0x03, 0x03, 0x06, 0x0c, 0x08, 0x07, 0x08, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x12, 0x00, 0x20, 0x03, + 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, + 0x1f, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, + 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, + 0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, + 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, + 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, + 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, + 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, + 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, + 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, + 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, + 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, + 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xc4, 0x00, + 0x1f, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x11, 0x00, + 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, + 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, + 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, + 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, + 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, + 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, + 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, + 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, + 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, + 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xfd, + 0x5e, 0xf8, 0xcf, 0xf1, 0x17, 0xfe, 0x15, 0x17, 0xc2, 0x5f, 0x12, 0x78, + 0xa7, 0xec, 0x7f, 0xda, 0x1f, 0xf0, 0x8f, 0xe9, 0xd3, 0xea, 0x1f, 0x65, + 0xf3, 0x7c, 0x9f, 0xb4, 0x79, 0x68, 0x5b, 0x66, 0xfd, 0xad, 0xb7, 0x38, + 0xc6, 0x70, 0x71, 0xe8, 0x6b, 0x26, 0x4f, 0x88, 0xfe, 0x29, 0xd6, 0x3c, + 0x59, 0xe2, 0x0d, 0x3f, 0x43, 0xf0, 0xee, 0x83, 0x79, 0x6d, 0xa0, 0x5c, + 0xa5, 0xab, 0xcd, 0x7b, 0xae, 0x4b, 0x6b, 0x24, 0xce, 0xd0, 0x47, 0x37, + 0x08, 0xb6, 0xb2, 0x00, 0x31, 0x20, 0x1c, 0xbf, 0x51, 0xda, 0xb9, 0xdf, + 0xda, 0x7b, 0x4d, 0xf1, 0xaf, 0x8e, 0xbc, 0x3d, 0xe2, 0x3f, 0x06, 0xe9, + 0x3e, 0x1b, 0x5d, 0x4f, 0x45, 0xf1, 0x76, 0x86, 0x34, 0xdb, 0x6d, 0x4e, + 0x2b, 0xa8, 0x22, 0xfe, 0xc9, 0xba, 0x95, 0xe5, 0x8e, 0x79, 0x2e, 0x84, + 0x92, 0xab, 0xb4, 0x2b, 0x13, 0x42, 0xeb, 0xe4, 0x47, 0x23, 0xe5, 0x25, + 0x04, 0x72, 0x95, 0xa9, 0xa6, 0xfc, 0x0f, 0x8b, 0x5b, 0xf1, 0xe7, 0x8c, + 0x35, 0x1d, 0x5c, 0xeb, 0xd6, 0xd0, 0xea, 0x7a, 0x84, 0x52, 0x5a, 0x7d, + 0x8b, 0x5f, 0xbb, 0xb2, 0x8e, 0x78, 0x85, 0xac, 0x08, 0x49, 0x8e, 0xde, + 0x65, 0x5c, 0xef, 0x57, 0x19, 0x61, 0xb8, 0x81, 0xe9, 0x8a, 0xfc, 0x3f, + 0x56, 0xfe, 0x4f, 0xef, 0xba, 0xff, 0x00, 0x83, 0xf9, 0x9f, 0x53, 0xc4, + 0x58, 0x4a, 0xd4, 0xb0, 0x18, 0x47, 0x82, 0x92, 0xf6, 0xb5, 0x2a, 0x4f, + 0x9f, 0x96, 0x49, 0xb5, 0x4f, 0xd9, 0xa7, 0x1e, 0x6f, 0x76, 0x7c, 0x9e, + 0xfd, 0xd6, 0xb1, 0x4d, 0xbd, 0x2f, 0x6b, 0x15, 0x57, 0xf6, 0x8a, 0xbf, + 0xf1, 0x8c, 0xda, 0x3e, 0x9f, 0xe0, 0xdf, 0x0d, 0xc5, 0xab, 0xeb, 0xba, + 0x86, 0x9c, 0xba, 0xb5, 0xe4, 0x1a, 0x9e, 0xa3, 0xfd, 0x9f, 0x69, 0xa4, + 0xdb, 0x33, 0xbc, 0x6b, 0xe7, 0x4f, 0x1c, 0x53, 0x93, 0x23, 0xc9, 0x1c, + 0x8a, 0x88, 0x91, 0xb0, 0x61, 0x14, 0x8c, 0x59, 0x40, 0x1b, 0xba, 0xff, + 0x00, 0x86, 0x3e, 0x34, 0xd5, 0x3c, 0x61, 0xa5, 0xde, 0xae, 0xb7, 0xe1, + 0xfb, 0x8f, 0x0e, 0xea, 0xda, 0x5d, 0xe3, 0xd9, 0xdc, 0xdb, 0x99, 0x0c, + 0xf6, 0xd3, 0x10, 0x15, 0x96, 0x6b, 0x69, 0xca, 0x27, 0x9d, 0x0b, 0xab, + 0xa9, 0x0d, 0xb1, 0x48, 0x3b, 0x95, 0x95, 0x59, 0x58, 0x0e, 0x23, 0xc4, + 0x7e, 0x00, 0xd5, 0x3e, 0x14, 0x7c, 0x46, 0x9b, 0x5c, 0xf0, 0xcf, 0x86, + 0xae, 0x35, 0xff, 0x00, 0x0f, 0xeb, 0x1a, 0x1d, 0xb6, 0x83, 0x7d, 0xa4, + 0xe9, 0x57, 0x70, 0xd9, 0x5f, 0xd8, 0x0b, 0x66, 0x9d, 0xa0, 0x96, 0xdd, + 0xa6, 0x92, 0x28, 0xca, 0x15, 0xb8, 0x74, 0x71, 0xe6, 0xa3, 0xa9, 0x58, + 0xd9, 0x77, 0x1d, 0xc0, 0x5a, 0xfd, 0x98, 0xfc, 0x11, 0xe2, 0x0f, 0x09, + 0x5b, 0x78, 0x9a, 0xe3, 0x5a, 0x87, 0x5c, 0xd3, 0xb4, 0xfd, 0x5b, 0x52, + 0x59, 0xf4, 0x7d, 0x2b, 0x58, 0xf1, 0x0c, 0xda, 0xe5, 0xf6, 0x99, 0x6c, + 0xb0, 0xc7, 0x11, 0x59, 0x66, 0x92, 0x49, 0x42, 0xb3, 0xc8, 0x8f, 0x2e, + 0xc8, 0xe5, 0x91, 0x54, 0x48, 0x06, 0xec, 0x82, 0x07, 0xd7, 0x63, 0x30, + 0x79, 0x7f, 0xf6, 0x73, 0xab, 0x47, 0x92, 0xe9, 0x41, 0xa7, 0xcd, 0xef, + 0xca, 0x4d, 0xfb, 0xf1, 0x71, 0xf6, 0x8e, 0xdc, 0xad, 0xbb, 0x7e, 0xee, + 0xce, 0x31, 0x4f, 0x9e, 0xef, 0xde, 0xf2, 0xf0, 0xaf, 0x13, 0x1e, 0x48, + 0x57, 0x77, 0x95, 0x92, 0x93, 0x4b, 0x46, 0xf9, 0x55, 0xda, 0x76, 0x5b, + 0xca, 0xfd, 0xad, 0xb5, 0x8f, 0x52, 0xa2, 0x8a, 0x2b, 0xe4, 0x4f, 0x4c, + 0x28, 0xa2, 0x8a, 0x00, 0xff, 0xd9 + }; + + // tiny_bmp.bmp - Small BMP image (actual file data) + const std::vector XLImageUtils::bmp33x16Data = { + 0x42, 0x4d, 0x76, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x6f, 0xc7, 0xef, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, + 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x57, 0x1c, 0xed, 0xb0, 0xe4, 0xee, + 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0xb0, 0xe4, 0xef, 0x6f, 0xd4, 0xef, 0x5e, 0xb1, 0x22, 0xb0, 0xdb, 0x91, + 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, + 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x3e, 0x89, + 0xef, 0x6f, 0x44, 0xed, 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0xb0, 0xe4, 0xef, 0x4c, 0xc2, 0xb1, 0x4c, 0xb1, + 0x22, 0xa0, 0xcb, 0x4b, 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, + 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, + 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0x85, 0xe4, 0xef, 0x3e, 0x1c, 0xee, 0xb0, 0xc7, 0xee, 0x80, + 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, 0x91, 0xe4, 0xef, 0x5e, + 0xb1, 0x6f, 0x80, 0xd4, 0x91, 0x80, 0xba, 0x4b, 0xb0, 0xe4, 0xd0, 0x80, + 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, + 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x85, 0xe4, 0xef, 0x24, 0x1c, 0xee, + 0xb0, 0xa8, 0xee, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0x80, 0xdb, 0xef, 0x80, 0xba, 0x4b, 0x91, 0xe4, 0xd0, 0x6f, 0xb1, 0x6f, + 0xb0, 0xe4, 0xb1, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, + 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, + 0xef, 0x3e, 0x1c, 0xed, 0xb0, 0xc7, 0xee, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0x5e, 0xcb, 0xd0, 0xa0, 0xcb, 0x4b, 0xb0, 0xe4, + 0xef, 0x4c, 0xc2, 0xb1, 0xb0, 0xd4, 0x6f, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xcc, 0x48, 0x60, 0xcc, 0x48, + 0x3f, 0xcc, 0x48, 0x3f, 0xcc, 0x48, 0x3f, 0xcc, 0x48, 0x3f, 0xc8, 0x48, + 0x3f, 0xb0, 0xcc, 0x9d, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x24, + 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, + 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x9b, 0x89, 0xed, 0xb0, 0xe4, 0xef, 0x80, + 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xa0, 0xe4, 0xb1, 0x5e, 0xba, 0x91, 0xb0, + 0xdb, 0x91, 0xb0, 0xe4, 0xef, 0x6f, 0xd4, 0xef, 0x91, 0xc2, 0x22, 0x80, + 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, + 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xc2, 0xb3, 0xef, 0xc2, 0x48, 0x3f, 0xb0, 0xe4, 0xb9, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x57, 0xa8, 0xef, 0x85, 0x67, 0xed, + 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x6f, 0xb1, 0x4b, 0x80, 0xdb, 0xb1, + 0x80, 0xba, 0x4b, 0xb0, 0xe4, 0xd0, 0xb0, 0xe4, 0xef, 0x91, 0xe4, 0xef, + 0x6f, 0xb1, 0x6f, 0x80, 0xdb, 0xb1, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, + 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb4, 0xe4, 0xef, 0xcc, 0x65, 0x9d, + 0xb0, 0xb3, 0x80, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, + 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x85, 0xe4, + 0xef, 0x3e, 0x1c, 0xee, 0xb0, 0xc7, 0xee, 0x80, 0xdb, 0xef, 0x6f, 0xb1, + 0x4b, 0x5e, 0xcb, 0x91, 0xa0, 0xcb, 0x4b, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0x4c, 0xc2, 0xb1, 0x80, 0xcb, 0x6f, 0x6f, 0xb1, + 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, 0x48, 0x60, 0xb0, 0xe4, + 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb4, 0xe4, + 0xef, 0xcc, 0x65, 0x9d, 0xb0, 0xb3, 0x80, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xc7, 0xef, 0x57, + 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0x57, 0xa8, 0xef, 0x57, 0x1c, 0xed, 0xb0, 0xe4, 0xee, 0x80, + 0xdb, 0xef, 0x5e, 0xb1, 0x4b, 0x4c, 0xba, 0x4b, 0xb0, 0xd4, 0x6f, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x6f, 0xd4, 0xef, 0x5e, + 0xba, 0x22, 0x6f, 0xb1, 0x4b, 0xb0, 0xe4, 0xb1, 0xbe, 0xcc, 0xef, 0xc2, + 0x48, 0x60, 0xb0, 0xe4, 0xb9, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xc2, 0xb3, 0xef, 0xc8, 0x48, 0x3f, 0xb0, 0xcc, 0x9d, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x6f, 0xc7, 0xef, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, + 0x24, 0x1c, 0xed, 0x24, 0x1c, 0xed, 0x3e, 0x1c, 0xed, 0xb0, 0xc7, 0xee, + 0xb0, 0xe4, 0xef, 0x80, 0xdb, 0xef, 0x4c, 0xb1, 0x4b, 0x6f, 0xb1, 0x22, + 0xb0, 0xe4, 0xb1, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0x91, 0xe4, 0xef, 0x4c, 0xb1, 0x6f, 0x6f, 0xb1, 0x22, 0xb0, 0xe4, 0xb1, + 0xbe, 0xcc, 0xef, 0xcc, 0x48, 0x60, 0xcc, 0x48, 0x3f, 0xcc, 0x48, 0x3f, + 0xcc, 0x48, 0x3f, 0xcc, 0x48, 0x3f, 0xc8, 0x48, 0x3f, 0xb0, 0xcc, 0x9d, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, + 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, + 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, + 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0xb0, 0xe4, 0xef, 0x00 + }; + + // tiny_gif.gif - Small GIF image (actual file data) + const std::vector XLImageUtils::gif26x16Data = { + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x1a, 0x00, 0x10, 0x00, 0x70, 0x00, + 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x2c, 0x00, 0x00, + 0x00, 0x00, 0x1a, 0x00, 0x10, 0x00, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x00, 0x00, 0x66, 0x00, 0x00, 0x99, 0x00, 0x00, 0xcc, 0x00, 0x00, + 0xff, 0x00, 0x2b, 0x00, 0x00, 0x2b, 0x33, 0x00, 0x2b, 0x66, 0x00, 0x2b, + 0x99, 0x00, 0x2b, 0xcc, 0x00, 0x2b, 0xff, 0x00, 0x55, 0x00, 0x00, 0x55, + 0x33, 0x00, 0x55, 0x66, 0x00, 0x55, 0x99, 0x00, 0x55, 0xcc, 0x00, 0x55, + 0xff, 0x00, 0x80, 0x00, 0x00, 0x80, 0x33, 0x00, 0x80, 0x66, 0x00, 0x80, + 0x99, 0x00, 0x80, 0xcc, 0x00, 0x80, 0xff, 0x00, 0xaa, 0x00, 0x00, 0xaa, + 0x33, 0x00, 0xaa, 0x66, 0x00, 0xaa, 0x99, 0x00, 0xaa, 0xcc, 0x00, 0xaa, + 0xff, 0x00, 0xd5, 0x00, 0x00, 0xd5, 0x33, 0x00, 0xd5, 0x66, 0x00, 0xd5, + 0x99, 0x00, 0xd5, 0xcc, 0x00, 0xd5, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, + 0x33, 0x00, 0xff, 0x66, 0x00, 0xff, 0x99, 0x00, 0xff, 0xcc, 0x00, 0xff, + 0xff, 0x33, 0x00, 0x00, 0x33, 0x00, 0x33, 0x33, 0x00, 0x66, 0x33, 0x00, + 0x99, 0x33, 0x00, 0xcc, 0x33, 0x00, 0xff, 0x33, 0x2b, 0x00, 0x33, 0x2b, + 0x33, 0x33, 0x2b, 0x66, 0x33, 0x2b, 0x99, 0x33, 0x2b, 0xcc, 0x33, 0x2b, + 0xff, 0x33, 0x55, 0x00, 0x33, 0x55, 0x33, 0x33, 0x55, 0x66, 0x33, 0x55, + 0x99, 0x33, 0x55, 0xcc, 0x33, 0x55, 0xff, 0x33, 0x80, 0x00, 0x33, 0x80, + 0x33, 0x33, 0x80, 0x66, 0x33, 0x80, 0x99, 0x33, 0x80, 0xcc, 0x33, 0x80, + 0xff, 0x33, 0xaa, 0x00, 0x33, 0xaa, 0x33, 0x33, 0xaa, 0x66, 0x33, 0xaa, + 0x99, 0x33, 0xaa, 0xcc, 0x33, 0xaa, 0xff, 0x33, 0xd5, 0x00, 0x33, 0xd5, + 0x33, 0x33, 0xd5, 0x66, 0x33, 0xd5, 0x99, 0x33, 0xd5, 0xcc, 0x33, 0xd5, + 0xff, 0x33, 0xff, 0x00, 0x33, 0xff, 0x33, 0x33, 0xff, 0x66, 0x33, 0xff, + 0x99, 0x33, 0xff, 0xcc, 0x33, 0xff, 0xff, 0x66, 0x00, 0x00, 0x66, 0x00, + 0x33, 0x66, 0x00, 0x66, 0x66, 0x00, 0x99, 0x66, 0x00, 0xcc, 0x66, 0x00, + 0xff, 0x66, 0x2b, 0x00, 0x66, 0x2b, 0x33, 0x66, 0x2b, 0x66, 0x66, 0x2b, + 0x99, 0x66, 0x2b, 0xcc, 0x66, 0x2b, 0xff, 0x66, 0x55, 0x00, 0x66, 0x55, + 0x33, 0x66, 0x55, 0x66, 0x66, 0x55, 0x99, 0x66, 0x55, 0xcc, 0x66, 0x55, + 0xff, 0x66, 0x80, 0x00, 0x66, 0x80, 0x33, 0x66, 0x80, 0x66, 0x66, 0x80, + 0x99, 0x66, 0x80, 0xcc, 0x66, 0x80, 0xff, 0x66, 0xaa, 0x00, 0x66, 0xaa, + 0x33, 0x66, 0xaa, 0x66, 0x66, 0xaa, 0x99, 0x66, 0xaa, 0xcc, 0x66, 0xaa, + 0xff, 0x66, 0xd5, 0x00, 0x66, 0xd5, 0x33, 0x66, 0xd5, 0x66, 0x66, 0xd5, + 0x99, 0x66, 0xd5, 0xcc, 0x66, 0xd5, 0xff, 0x66, 0xff, 0x00, 0x66, 0xff, + 0x33, 0x66, 0xff, 0x66, 0x66, 0xff, 0x99, 0x66, 0xff, 0xcc, 0x66, 0xff, + 0xff, 0x99, 0x00, 0x00, 0x99, 0x00, 0x33, 0x99, 0x00, 0x66, 0x99, 0x00, + 0x99, 0x99, 0x00, 0xcc, 0x99, 0x00, 0xff, 0x99, 0x2b, 0x00, 0x99, 0x2b, + 0x33, 0x99, 0x2b, 0x66, 0x99, 0x2b, 0x99, 0x99, 0x2b, 0xcc, 0x99, 0x2b, + 0xff, 0x99, 0x55, 0x00, 0x99, 0x55, 0x33, 0x99, 0x55, 0x66, 0x99, 0x55, + 0x99, 0x99, 0x55, 0xcc, 0x99, 0x55, 0xff, 0x99, 0x80, 0x00, 0x99, 0x80, + 0x33, 0x99, 0x80, 0x66, 0x99, 0x80, 0x99, 0x99, 0x80, 0xcc, 0x99, 0x80, + 0xff, 0x99, 0xaa, 0x00, 0x99, 0xaa, 0x33, 0x99, 0xaa, 0x66, 0x99, 0xaa, + 0x99, 0x99, 0xaa, 0xcc, 0x99, 0xaa, 0xff, 0x99, 0xd5, 0x00, 0x99, 0xd5, + 0x33, 0x99, 0xd5, 0x66, 0x99, 0xd5, 0x99, 0x99, 0xd5, 0xcc, 0x99, 0xd5, + 0xff, 0x99, 0xff, 0x00, 0x99, 0xff, 0x33, 0x99, 0xff, 0x66, 0x99, 0xff, + 0x99, 0x99, 0xff, 0xcc, 0x99, 0xff, 0xff, 0xcc, 0x00, 0x00, 0xcc, 0x00, + 0x33, 0xcc, 0x00, 0x66, 0xcc, 0x00, 0x99, 0xcc, 0x00, 0xcc, 0xcc, 0x00, + 0xff, 0xcc, 0x2b, 0x00, 0xcc, 0x2b, 0x33, 0xcc, 0x2b, 0x66, 0xcc, 0x2b, + 0x99, 0xcc, 0x2b, 0xcc, 0xcc, 0x2b, 0xff, 0xcc, 0x55, 0x00, 0xcc, 0x55, + 0x33, 0xcc, 0x55, 0x66, 0xcc, 0x55, 0x99, 0xcc, 0x55, 0xcc, 0xcc, 0x55, + 0xff, 0xcc, 0x80, 0x00, 0xcc, 0x80, 0x33, 0xcc, 0x80, 0x66, 0xcc, 0x80, + 0x99, 0xcc, 0x80, 0xcc, 0xcc, 0x80, 0xff, 0xcc, 0xaa, 0x00, 0xcc, 0xaa, + 0x33, 0xcc, 0xaa, 0x66, 0xcc, 0xaa, 0x99, 0xcc, 0xaa, 0xcc, 0xcc, 0xaa, + 0xff, 0xcc, 0xd5, 0x00, 0xcc, 0xd5, 0x33, 0xcc, 0xd5, 0x66, 0xcc, 0xd5, + 0x99, 0xcc, 0xd5, 0xcc, 0xcc, 0xd5, 0xff, 0xcc, 0xff, 0x00, 0xcc, 0xff, + 0x33, 0xcc, 0xff, 0x66, 0xcc, 0xff, 0x99, 0xcc, 0xff, 0xcc, 0xcc, 0xff, + 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x33, 0xff, 0x00, 0x66, 0xff, 0x00, + 0x99, 0xff, 0x00, 0xcc, 0xff, 0x00, 0xff, 0xff, 0x2b, 0x00, 0xff, 0x2b, + 0x33, 0xff, 0x2b, 0x66, 0xff, 0x2b, 0x99, 0xff, 0x2b, 0xcc, 0xff, 0x2b, + 0xff, 0xff, 0x55, 0x00, 0xff, 0x55, 0x33, 0xff, 0x55, 0x66, 0xff, 0x55, + 0x99, 0xff, 0x55, 0xcc, 0xff, 0x55, 0xff, 0xff, 0x80, 0x00, 0xff, 0x80, + 0x33, 0xff, 0x80, 0x66, 0xff, 0x80, 0x99, 0xff, 0x80, 0xcc, 0xff, 0x80, + 0xff, 0xff, 0xaa, 0x00, 0xff, 0xaa, 0x33, 0xff, 0xaa, 0x66, 0xff, 0xaa, + 0x99, 0xff, 0xaa, 0xcc, 0xff, 0xaa, 0xff, 0xff, 0xd5, 0x00, 0xff, 0xd5, + 0x33, 0xff, 0xd5, 0x66, 0xff, 0xd5, 0x99, 0xff, 0xd5, 0xcc, 0xff, 0xd5, + 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x33, 0xff, 0xff, 0x66, 0xff, 0xff, + 0x99, 0xff, 0xff, 0xcc, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xff, 0x00, 0xe7, 0xe9, + 0x13, 0x48, 0x70, 0xa0, 0xc1, 0x82, 0x08, 0x0f, 0xea, 0xd3, 0x97, 0x8c, + 0x5e, 0x32, 0x86, 0x0e, 0x21, 0x3e, 0x6c, 0x38, 0x31, 0x22, 0xc5, 0x7c, + 0xca, 0x04, 0x26, 0xc3, 0x38, 0x8f, 0x23, 0xc6, 0x7c, 0x0d, 0x41, 0xd2, + 0x03, 0xc9, 0x50, 0xe3, 0x48, 0x88, 0x1d, 0x51, 0x4d, 0xc3, 0x96, 0xea, + 0x5c, 0x3e, 0x7a, 0xf2, 0x88, 0x98, 0x72, 0x47, 0x86, 0x86, 0x0e, 0x9b, + 0x3a, 0xde, 0x0c, 0xcc, 0x38, 0x0f, 0x9a, 0x3c, 0x54, 0xee, 0xf2, 0x41, + 0xcb, 0x37, 0xad, 0x17, 0x48, 0x37, 0x9f, 0xe6, 0xe9, 0x78, 0x36, 0xb2, + 0x69, 0xc8, 0x92, 0xdf, 0xce, 0x41, 0x9b, 0x37, 0x2f, 0x99, 0x3c, 0x55, + 0xfa, 0x62, 0x42, 0xa3, 0xf9, 0xc9, 0xe1, 0x4b, 0x93, 0x0f, 0xe5, 0x51, + 0x6b, 0xc8, 0x54, 0x9f, 0xb2, 0x7c, 0x54, 0x89, 0x80, 0x9a, 0x47, 0x06, + 0xd4, 0xd7, 0xa9, 0x4c, 0xbf, 0x0a, 0x03, 0xf7, 0x75, 0xe3, 0x3c, 0x65, + 0xfa, 0x90, 0xb9, 0x99, 0x59, 0x53, 0xc7, 0x4d, 0x20, 0xf3, 0xe8, 0x31, + 0x9d, 0xf7, 0x8c, 0x1d, 0xac, 0x88, 0xdf, 0x52, 0x65, 0x4b, 0x45, 0x15, + 0xa9, 0xd2, 0x67, 0x81, 0xd1, 0x42, 0xcc, 0x37, 0xf0, 0x2a, 0xe5, 0x8d, + 0x59, 0xa7, 0x29, 0xd3, 0xca, 0x15, 0x62, 0x5c, 0x8a, 0x0c, 0xa3, 0x26, + 0x0b, 0xbc, 0xf1, 0x55, 0xbb, 0x7c, 0x44, 0x3e, 0x29, 0x23, 0xd3, 0xb0, + 0x27, 0xe9, 0x93, 0x54, 0x91, 0x4d, 0x6b, 0x37, 0x35, 0xdf, 0xab, 0x69, + 0xf4, 0xf4, 0x6e, 0x1d, 0xe3, 0xf6, 0xac, 0x6b, 0x86, 0x64, 0x47, 0xd6, + 0x92, 0x96, 0x6a, 0x1a, 0xb8, 0xa1, 0xf3, 0x64, 0xce, 0xf3, 0x62, 0xca, + 0x24, 0xc7, 0x85, 0x81, 0x21, 0x8f, 0x1c, 0x9a, 0x51, 0x30, 0xbd, 0xbb, + 0x68, 0x23, 0x56, 0x1d, 0x38, 0xfa, 0x21, 0xf6, 0x8c, 0x03, 0xa7, 0xba, + 0x11, 0x7e, 0xf9, 0x79, 0x24, 0xf8, 0x9d, 0x5f, 0x4b, 0x86, 0x7f, 0x3d, + 0xf8, 0xac, 0x76, 0x65, 0x01, 0x01, 0x00, 0x3b + }; + + + + +// ========== XLImageUtils Member Functions ========== // + +/** + * @brief Detect MIME type from binary image data and return as enum + * @param data Binary image data + * @return Corresponding XLMimeType enum value + */ +XLMimeType XLImageUtils::detectMimeType(const std::vector& data) +{ + if (data.size() >= 8 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47) { + return XLMimeType::PNG; + } + if (data.size() >= 2 && data[0] == 0xFF && data[1] == 0xD8) { + return XLMimeType::JPEG; + } + if (data.size() >= 2 && data[0] == 0x42 && data[1] == 0x4D) { + return XLMimeType::BMP; + } + if (data.size() >= 3 && data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46) { + return XLMimeType::GIF; + } + return XLMimeType::Unknown; +} + +/** + * @brief Convert XLMimeType enum to MIME type string + * @param mimeType XLMimeType enum value + * @return Corresponding MIME type string (e.g., "image/png") + */ +std::string XLImageUtils::mimeTypeToString(XLMimeType mimeType) +{ + switch (mimeType) { + case XLMimeType::PNG: return "image/png"; + case XLMimeType::JPEG: return "image/jpeg"; + case XLMimeType::BMP: return "image/bmp"; + case XLMimeType::GIF: return "image/gif"; + case XLMimeType::Unknown: + default: return ""; + } +} + +/** + * @brief Convert MIME type string to XLMimeType enum + * @param mimeType MIME type string (e.g., "image/png") + * @return Corresponding XLMimeType enum value + */ +XLMimeType XLImageUtils::stringToMimeType(const std::string& mimeTypeStr) +{ + if (mimeTypeStr == "image/png") return XLMimeType::PNG; + if (mimeTypeStr == "image/jpeg") return XLMimeType::JPEG; + if (mimeTypeStr == "image/bmp") return XLMimeType::BMP; + if (mimeTypeStr == "image/gif") return XLMimeType::GIF; + return XLMimeType::Unknown; +} + +/** + * @brief Determine file extension from MIME type + * @param mimeType MIME type string + * @return File extension string + */ +std::string XLImageUtils::mimeTypeToExtension(XLMimeType mimeType) +{ + switch (mimeType) { + case XLMimeType::PNG: return ".png"; + case XLMimeType::JPEG: return ".jpg"; + case XLMimeType::BMP: return ".bmp"; + case XLMimeType::GIF: return ".gif"; + case XLMimeType::Unknown: + default: return ""; + } +} + +/** + * @brief Determine MIME type from file extension + * @param extension File extension + * @return MIME type + */ +XLMimeType XLImageUtils::extensionToMimeType(const std::string& extension) +{ + if (extension.find("png") != std::string::npos ) return XLMimeType::PNG; + if ((extension.find("jpg") != std::string::npos ) || + (extension.find("jpeg") != std::string::npos )) return XLMimeType::JPEG; + if (extension.find("bmp") != std::string::npos ) return XLMimeType::BMP; + if (extension.find("gif") != std::string::npos ) return XLMimeType::GIF; + return XLMimeType::Unknown; +} + +/** + * @brief Convert MIME type to XLContentType enum + * @param mimeType MIME type enum + * @return Corresponding XLContentType enum value + */ +XLContentType XLImageUtils::mimeTypeToContentType(const XLMimeType& mimeType) +{ + if (mimeType == XLMimeType::PNG) return XLContentType::ImagePNG; + if (mimeType == XLMimeType::JPEG) return XLContentType::ImageJPEG; + if (mimeType == XLMimeType::BMP) return XLContentType::ImageBMP; + if (mimeType == XLMimeType::GIF) return XLContentType::ImageGIF; + return XLContentType::Unknown; +} + +/** + * @brief Convert XLContentType enum to MIME type + * @param contentType XLContentType enum value + * @return Corresponding MIME type + */ +XLMimeType XLImageUtils::contentTypeToMimeType(XLContentType contentType) +{ + switch (contentType) { + case XLContentType::ImagePNG: return XLMimeType::PNG; + case XLContentType::ImageJPEG: return XLMimeType::JPEG; + case XLContentType::ImageBMP: return XLMimeType::BMP; + case XLContentType::ImageGIF: return XLMimeType::GIF; + default: return XLMimeType::Unknown; + } +} + +/** + * @brief Convert pixels to EMUs (Excel units) + * @param pixels Number of pixels + * @return Equivalent EMUs + */ +uint32_t XLImageUtils::pixelsToEmus(uint32_t pixels) +{ + return pixels * EMU_TO_PIXEL_RATIO; +} + +/** + * @brief Convert EMUs to pixels + * @param emus Number of EMUs + * @return Equivalent pixels + */ +uint32_t XLImageUtils::emusToPixels(uint32_t emus) +{ + return emus / EMU_TO_PIXEL_RATIO; +} + +/** + * @brief Convert points to EMUs + * @param points Point value + * @return EMU value + */ +uint32_t XLImageUtils::pointsToEmus(double points) +{ + // Convert points to EMUs (1 point = 12700 EMUs) + return static_cast(points * 12700); +} + +/** + * @brief Calculate aspect ratio from dimensions + * @param width Width in pixels + * @param height Height in pixels + * @return Aspect ratio (width/height) + */ +double XLImageUtils::calculateAspectRatio(uint32_t width, uint32_t height) +{ + if (height == 0) return 0.0; + return static_cast(width) / static_cast(height); +} + +/** + * @brief Get image dimensions from binary data + * @param data Binary image data + * @param mimeType MIME type of the image + * @return Pair of (width, height) in pixels + */ +std::pair XLImageUtils::getImageDimensions(const std::vector& data, XLMimeType mimeType) +{ + if (data.size() < 8) return {0, 0}; + + // PNG signature check and dimension extraction + if (mimeType == XLMimeType::PNG) { + if (data.size() >= 24 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47) { + // PNG IHDR chunk contains width and height at bytes 16-23 + uint32_t width = (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19]; + uint32_t height = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23]; + return {width, height}; + } + } + // JPEG signature check and dimension extraction + else if (mimeType == XLMimeType::JPEG) { + if (data.size() >= 2 && data[0] == 0xFF && data[1] == 0xD8) { + // For JPEG, we need to find the SOF0 marker (0xFFC0) and extract dimensions + for (size_t i = 2; i < data.size() - 8; ++i) { + if (data[i] == 0xFF && data[i + 1] == 0xC0) { + uint32_t height = (data[i + 5] << 8) | data[i + 6]; + uint32_t width = (data[i + 7] << 8) | data[i + 8]; + return {width, height}; + } + } + } + } + // BMP signature check and dimension extraction + else if (mimeType == XLMimeType::BMP) { + if (data.size() >= 26 && data[0] == 0x42 && data[1] == 0x4D) { + // BMP header contains width and height at bytes 18-25 + uint32_t width = data[18] | (data[19] << 8) | (data[20] << 16) | (data[21] << 24); + uint32_t height = data[22] | (data[23] << 8) | (data[24] << 16) | (data[25] << 24); + return {width, height}; + } + } + // GIF signature check and dimension extraction + else if (mimeType == XLMimeType::GIF) { + if (data.size() >= 10 && data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46) { + // GIF header contains width and height at bytes 6-9 + uint32_t width = data[6] | (data[7] << 8); + uint32_t height = data[8] | (data[9] << 8); + return {width, height}; + } + } + + // Default fallback - assume square image + return {100, 100}; +} + +/** + * @brief Get image dimensions from binary data (auto-detect MIME type) + * @param data Binary image data + * @return Pair of (width, height) in pixels, or {0,0} if detection fails + */ +std::pair XLImageUtils::getImageDimensions(const std::vector& data) +{ + // Auto-detect MIME type first + const XLMimeType mimeType = detectMimeType(data); + if (mimeType == XLMimeType::Unknown) { + return {0, 0}; + } + + // Use the existing function with detected MIME type + return getImageDimensions(data, mimeType); +} + +/** + * @brief Check if image dimensions are reasonable + * @param width Width in pixels + * @param height Height in pixels + * @return True if dimensions are within reasonable bounds + */ +bool XLImageUtils::isValidImageSize(uint32_t width, uint32_t height) +{ + // Reasonable bounds: 1x1 to 10000x10000 pixels + return width > 0 && height > 0 && width <= 10000 && height <= 10000; +} + +/** + * @brief Validate image data integrity + * @param data Binary image data + * @param mimeType MIME type of the image + * @return True if image data is valid + */ +bool XLImageUtils::validateImageData(const std::vector& data, const XLMimeType& mimeType) +{ + if (data.empty()) return false; + + // Check basic signature matches MIME type + if (XLMimeType::PNG == mimeType) { + return data.size() >= 8 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47; + } + if (XLMimeType::JPEG == mimeType) { + return data.size() >= 2 && data[0] == 0xFF && data[1] == 0xD8; + } + if (XLMimeType::BMP == mimeType) { + return data.size() >= 2 && data[0] == 0x42 && data[1] == 0x4D; + } + if (XLMimeType::GIF == mimeType) { + return data.size() >= 3 && data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46; + } + + return false; +} + +/** + * @brief Calculate hash value for binary image data + * @param imageData Binary image data as vector + * @return Hash value for the image data + */ +size_t XLImageUtils::imageDataHash(const std::vector& imageData) +{ + std::string imageDataStr; + imageDataStr.reserve(imageData.size()); + imageDataStr.insert(imageDataStr.end(), imageData.begin(), imageData.end()); + const size_t h = imageDataStrHash(imageDataStr); + return h; +} + +/** + * @brief Calculate hash value for image data stored as string + * @param imageDataStr Binary image data as string + * @return Hash value for the image data (same as imageDataHash for equivalent data) + */ +size_t XLImageUtils::imageDataStrHash(const std::string& imageDataStr) +{ + size_t h = 0; + if (!imageDataStr.empty()) { + std::hash hasher; + h = hasher(imageDataStr); + } + return h; +} diff --git a/OpenXLSX/sources/XLProperties.cpp b/OpenXLSX/sources/XLProperties.cpp index 2ca4dd89..07ef6892 100644 --- a/OpenXLSX/sources/XLProperties.cpp +++ b/OpenXLSX/sources/XLProperties.cpp @@ -234,8 +234,8 @@ void XLProperties::createFromTemplate() props.append_attribute("xmlns:dcmitype") = "http://purl.org/dc/dcmitype/"; props.append_attribute("xmlns:xsi") = "http://www.w3.org/2001/XMLSchema-instance"; - props.append_child("dc:creator").text().set("Kenneth Balslev"); - props.append_child("cp:lastModifiedBy").text().set("Kenneth Balslev"); + props.append_child("dc:creator").text().set("OpenXLSX"); + props.append_child("cp:lastModifiedBy").text().set("OpenXLSX"); XMLNode prop {}; prop = props.append_child("dcterms:created"); diff --git a/OpenXLSX/sources/XLRelationships.cpp b/OpenXLSX/sources/XLRelationships.cpp index a0f18c41..8fa2f3d1 100644 --- a/OpenXLSX/sources/XLRelationships.cpp +++ b/OpenXLSX/sources/XLRelationships.cpp @@ -99,7 +99,7 @@ namespace OpenXLSX { // anonymous namespace: do not export these symbols std::random_device rd; rdSeed = rd(); } - Rand32.seed(rdSeed); + Rand32.seed(static_cast(rdSeed)); RandomizerInitialized = true; } } // namespace OpenXLSX @@ -466,3 +466,13 @@ bool XLRelationships::idExists(const std::string& id) const * @details Print the underlying XML using pugixml::xml_node::print */ void XLRelationships::print(std::basic_ostream& ostr) const { xmlDocument().document_element().print(ostr); } + +int XLRelationships::verifyData(std::string* dbgMsg) const +{ + int errorCount = 0; + + // Call base class verifyData() which calls m_xmlData->verifyData() + errorCount += XLXmlFile::verifyData(dbgMsg); + + return errorCount; +} diff --git a/OpenXLSX/sources/XLRow.cpp b/OpenXLSX/sources/XLRow.cpp index 4f47e3b4..ded0bd2a 100644 --- a/OpenXLSX/sources/XLRow.cpp +++ b/OpenXLSX/sources/XLRow.cpp @@ -486,7 +486,7 @@ namespace OpenXLSX XMLNode rowNode = m_hintRow.next_sibling_of_type(pugi::node_element); uint32_t rowNo = 0; while (not rowNode.empty()) { - rowNo = rowNode.attribute("r").as_ullong(); + rowNo = static_cast(rowNode.attribute("r").as_ullong()); if (rowNo >= m_currentRowNumber) break; // if desired row was reached / passed, break before incrementing rowNode rowNode = rowNode.next_sibling_of_type(pugi::node_element); } diff --git a/OpenXLSX/sources/XLRowData.cpp b/OpenXLSX/sources/XLRowData.cpp index 0787eee6..37d9affe 100644 --- a/OpenXLSX/sources/XLRowData.cpp +++ b/OpenXLSX/sources/XLRowData.cpp @@ -383,7 +383,7 @@ namespace OpenXLSX // ===== prepend new cell nodes to current row node XMLNode curNode{}; - uint16_t colNo = values.size(); + uint16_t colNo = static_cast(values.size()); for (auto value = values.rbegin(); value != values.rend(); ++value) { // NOLINT curNode = m_rowNode->prepend_child("c"); setDefaultCellAttributes(curNode, XLCellReference(static_cast(m_row->rowNumber()), colNo).address(), *m_rowNode, colNo); diff --git a/OpenXLSX/sources/XLSheet.cpp b/OpenXLSX/sources/XLSheet.cpp index 7eab5dbb..b3d5c7fc 100644 --- a/OpenXLSX/sources/XLSheet.cpp +++ b/OpenXLSX/sources/XLSheet.cpp @@ -46,8 +46,10 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM. // ===== External Includes ===== // #include // std::max #include // std::isdigit (issue #330) +#include // std::cout #include // std::numeric_limits #include // std::multimap +#include // std::set #include // ===== OpenXLSX Includes ===== // @@ -992,8 +994,10 @@ XLWorksheet::XLWorksheet(const XLWorksheet& other) : XLSheetBase(ot m_relationships = other.m_relationships; // invoke XLRelationships copy assignment operator m_merges = other.m_merges; // " XLMergeCells " m_vmlDrawing = other.m_vmlDrawing; // " XLVmlDrawing + m_drawingML = other.m_drawingML; // " XLDrawingML m_comments = other.m_comments; // " XLComments " m_tables = other.m_tables; // " XLTables " + m_embImages = other.m_embImages; // " XLEmbImgVecShPtr" } /** @@ -1004,8 +1008,10 @@ XLWorksheet::XLWorksheet(XLWorksheet&& other) : XLSheetBase< XLWorksheet >(other m_relationships = std::move(other.m_relationships); // invoke XLRelationships move assignment operator m_merges = std::move(other.m_merges); // " XLMergeCells " m_vmlDrawing = std::move(other.m_vmlDrawing); // " XLVmlDrawing + m_drawingML = std::move(other.m_drawingML); // " XLDrawingML m_comments = std::move(other.m_comments); // " XLComments " m_tables = std::move(other.m_tables); // " XLTables " + m_embImages = std::move(other.m_embImages); // " XLEmbImgVecShPtr" } /** @@ -1017,8 +1023,10 @@ XLWorksheet& XLWorksheet::operator=(const XLWorksheet& other) m_relationships = other.m_relationships; m_merges = other.m_merges; m_vmlDrawing = other.m_vmlDrawing; + m_drawingML = other.m_drawingML; m_comments = other.m_comments; m_tables = other.m_tables; + m_embImages = other.m_embImages; return *this; } @@ -1031,8 +1039,10 @@ XLWorksheet& XLWorksheet::operator=(XLWorksheet&& other) m_relationships = std::move(other.m_relationships); m_merges = std::move(other.m_merges); m_vmlDrawing = std::move(other.m_vmlDrawing); + m_drawingML = std::move(other.m_drawingML); m_comments = std::move(other.m_comments); m_tables = std::move(other.m_tables); + m_embImages = std::move(other.m_embImages); return *this; } @@ -1685,6 +1695,173 @@ XLVmlDrawing& XLWorksheet::vmlDrawing() return m_vmlDrawing; } +/** + * @details Get the DrawingML object for this worksheet + */ +XLDrawingML& XLWorksheet::drawingML() +{ + if (!m_drawingML.valid()) { + // Only initialize DrawingML if the worksheet actually has images + // This prevents unnecessary initialization for worksheets without images + if (!hasImagesInXML()) { + // For worksheets without images, return an invalid DrawingML + // DrawingML will be created when addImage() is called + static XLDrawingML invalidDrawingML(nullptr); + return invalidDrawingML; + } + + // ===== Append xdr namespace attribute to worksheet if not present + XMLNode docElement = xmlDocument().document_element(); + XMLAttribute xdrNamespace = appendAndGetAttribute(docElement, "xmlns:xdr", ""); + xdrNamespace = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"; + + std::ignore = relationships(); // create sheet relationships if not existing + + // ===== Check if DrawingML XML file already exists + uint16_t sheetXmlNo = sheetXmlNumber(); + std::string drawingFilename = getDrawingFilename(); + + // Try to load existing DrawingML data first + try { + XLXmlData* xmlData = parentDoc().getDrawingXmlData(drawingFilename); + if (xmlData) { + m_drawingML = XLDrawingML(xmlData); + if (m_drawingML.valid()) { + return m_drawingML; // Successfully loaded existing data + } + } + } catch (...) { + // DrawingML data doesn't exist or failed to load, will create new one below + } + + // If no existing data or loading failed, create new DrawingML XML file + std::string drawingXml = "\n" + "\n" + ""; + + // Add to archive using public method + if (!parentDoc().addDrawingFile(drawingFilename, drawingXml)) { + throw XLException("XLWorksheet::drawingML(): could not create drawing file"); + } + + // Create DrawingML object + XLXmlData* xmlData = parentDoc().getDrawingXmlData(drawingFilename); + if (!xmlData) { + throw XLException("XLWorksheet::drawingML(): could not get XML data for drawing"); + } + m_drawingML = XLDrawingML(xmlData); + + // Ensure the XML document is loaded + if (!m_drawingML.valid()) { + throw XLException("XLWorksheet::drawingML(): could not initialize DrawingML object"); + } + + // Add relationship + std::string drawingRelativePath = getPathARelativeToPathB(drawingFilename, getXmlPath()); + XLRelationshipItem drawingRelationship; + if (!m_relationships.targetExists(drawingRelativePath)) + drawingRelationship = m_relationships.addRelationship(XLRelationshipType::Drawing, drawingRelativePath); + else + drawingRelationship = m_relationships.relationshipByTarget(drawingRelativePath); + + if (drawingRelationship.empty()) + throw XLException("XLWorksheet::drawingML(): could not add determine sheet relationship for Drawing"); + + // Add element to worksheet + XMLNode drawing = appendAndGetNode(docElement, "drawing", m_nodeOrder); + if (drawing.empty()) + throw XLException("XLWorksheet::drawingML(): could not add element to worksheet XML"); + appendAndSetAttribute(drawing, "r:id", drawingRelationship.id()); + + // Create drawing relationships file only if it doesn't exist + std::string drawingRelsFilename = getDrawingRelsFilename(); + + // Only create relationships file if it doesn't exist + if (!parentDoc().hasRelationshipsFile(drawingRelsFilename)) { + createDrawingRelationshipsFile(drawingFilename); + } + } + + return m_drawingML; +} + +const XLDrawingML& XLWorksheet::drawingML() const{ +XLDrawingML& result = const_cast(this)->drawingML(); +return const_cast(result); +} + +/** + * @details Create an empty DrawingML object for worksheets without images + * This is needed when addImage() is called on a worksheet that doesn't have images yet + */ +XLDrawingML& XLWorksheet::createEmptyDrawingML() +{ + // ===== Append xdr namespace attribute to worksheet if not present + XMLNode docElement = xmlDocument().document_element(); + XMLAttribute xdrNamespace = appendAndGetAttribute(docElement, "xmlns:xdr", ""); + xdrNamespace = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"; + + std::ignore = relationships(); // create sheet relationships if not existing + + // Create new DrawingML XML file + std::string drawingXml = "\n" + "\n" + ""; + + // Add to archive using public method + uint16_t sheetXmlNo = sheetXmlNumber(); + std::string drawingFilename = getDrawingFilename(); + + if (!parentDoc().addDrawingFile(drawingFilename, drawingXml)) { + throw XLException("XLWorksheet::createEmptyDrawingML(): could not create drawing file"); + } + + // Create DrawingML object + XLXmlData* xmlData = parentDoc().getDrawingXmlData(drawingFilename); + if (!xmlData) { + throw XLException("XLWorksheet::createEmptyDrawingML(): could not get XML data for drawing"); + } + m_drawingML = XLDrawingML(xmlData); + + // Ensure the XML document is loaded + if (!m_drawingML.valid()) { + throw XLException("XLWorksheet::createEmptyDrawingML(): could not initialize DrawingML object"); + } + + // Add relationship + std::string drawingRelativePath = getPathARelativeToPathB(drawingFilename, getXmlPath()); + XLRelationshipItem drawingRelationship; + if (!m_relationships.targetExists(drawingRelativePath)) + drawingRelationship = m_relationships.addRelationship(XLRelationshipType::Drawing, drawingRelativePath); + else + drawingRelationship = m_relationships.relationshipByTarget(drawingRelativePath); + + if (drawingRelationship.empty()) + throw XLException("XLWorksheet::createEmptyDrawingML(): could not add determine sheet relationship for Drawing"); + + // Add element to worksheet + XMLNode drawing = appendAndGetNode(docElement, "drawing", m_nodeOrder); + if (drawing.empty()) + throw XLException("XLWorksheet::createEmptyDrawingML(): could not add element to worksheet XML"); + appendAndSetAttribute(drawing, "r:id", drawingRelationship.id()); + + // Create drawing relationships file + std::string drawingRelsFilename = getDrawingRelsFilename(); + if (!parentDoc().hasRelationshipsFile(drawingRelsFilename)) { + createDrawingRelationshipsFile(drawingFilename); + } + + return m_drawingML; +} + /** * @details fetches XLComments for the sheet - creates & assigns the class if empty */ @@ -1734,6 +1911,481 @@ XLTables& XLWorksheet::tables() return m_tables; } +//---------------------------------------------------------------------------------------------------------------------- +// Image Methods +//---------------------------------------------------------------------------------------------------------------------- + +/** + * @brief Embed an image from image data at the specified cell location + * @details Constructs a oneCellAnchor XLImageAnchor from cellRef and creates XLImage from imageData and mimeType + * @param cellRef Cell reference where the image should be anchored + * @param imageData Vector containing the image binary data + * @param mimeType MIME type of the image (PNG, JPEG, etc.) + * @return Image ID string + */ +std::string XLWorksheet::embedImageFromImageData(const XLCellReference& cellRef, + const std::vector& imageData, + XLMimeType mimeType) +{ + XLImageAnchor imageAnchor; + imageAnchor.initOneCell(cellRef, 0, 0); + const std::string imageId = embedImageFromImageData(imageAnchor, imageData, mimeType); + return imageId; +} + +/** + * @brief Embed an image from image data using the specified anchor + * @details Creates XLImage from imageData and mimeType using the provided anchor configuration + * @param imageAnchor Anchor configuration specifying position and dimensions + * @param imageData Vector containing the image binary data + * @param mimeType MIME type of the image (PNG, JPEG, etc.) + * @return Image ID string + */ +std::string XLWorksheet::embedImageFromImageData(const XLImageAnchor& imageAnchor, + const std::vector& imageData, + XLMimeType mimeType) +{ + // Auto-detect MIME type if unknown + XLMimeType detectedMimeType = mimeType; + if (mimeType == XLMimeType::Unknown) { + detectedMimeType = XLImageUtils::detectMimeType(imageData); + } + + XLImageShPtr image = parentDoc().getImageManager().findOrAddImage("", + imageData, + XLImageUtils::mimeTypeToContentType(detectedMimeType)); + const std::string imageId = embedImage(imageAnchor, image); + return imageId; +} + +/** + * @brief Embed an image from file at the specified cell location + * @details Constructs a oneCellAnchor XLImageAnchor from cellRef and creates XLImage from file and mimeType + * @param cellRef Cell reference where the image should be anchored + * @param imageFileName Path to the image file on disk + * @param mimeType MIME type of the image (PNG, JPEG, etc.) + * @return Image ID string + */ +std::string XLWorksheet::embedImageFromFile(const XLCellReference& cellRef, + const std::string& imageFileName, + XLMimeType mimeType) +{ + XLImageAnchor imageAnchor; + imageAnchor.initOneCell(cellRef, 0, 0); + const std::string imageId = embedImageFromFile(imageAnchor, imageFileName, mimeType); + return imageId; +} + +/** + * @brief Embed an image from file using the specified anchor + * @details Creates XLImage from file and mimeType using the provided anchor configuration + * @param imageAnchor Anchor configuration specifying position and dimensions + * @param imageFileName Path to the image file on disk + * @param mimeType MIME type of the image (PNG, JPEG, etc.) + * @return Image ID string + */ +std::string XLWorksheet::embedImageFromFile(const XLImageAnchor& imageAnchor, + const std::string& imageFileName, + XLMimeType mimeType) +{ + std::string imageId; + + std::ifstream file(imageFileName, std::ios::binary); + if (file.is_open()) { + // Read the entire file into a vector + file.seekg(0, std::ios::end); + size_t fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector imageData(fileSize); + file.read(reinterpret_cast(imageData.data()), fileSize); + file.close(); + + // Auto-detect MIME type if unknown + XLMimeType detectedMimeType = mimeType; + if (mimeType == XLMimeType::Unknown) { + detectedMimeType = XLImageUtils::detectMimeType(imageData); + } + + XLImageShPtr image = parentDoc().getImageManager().findOrAddImage("", + imageData, XLImageUtils::mimeTypeToContentType(detectedMimeType)); + + imageId = embedImage(imageAnchor, image); + } + return imageId; +} + +/** + * @details Get the number of images in the worksheet + */ +size_t XLWorksheet::imageCount() const +{ + if (getEmbImages().empty()) { + // Check if DrawingML is valid (was initialized because images exist) + if (m_drawingML.valid()) { + size_t count = m_drawingML.imageCount(); + return count; + } else { + // DrawingML not initialized = no images in XML + // Debug assertion: if DrawingML is invalid, XML should not have element + #if (defined(_DEBUG) || !defined(NDEBUG)) + bool hasDrawingInXML = hasImagesInXML(); + if (hasDrawingInXML) { + std::cerr << "WARNING: Worksheet '" << name() + << "' has element in XML but DrawingML is invalid!" << std::endl; + std::cerr << " This indicates DrawingML initialization failed despite XML having images." << std::endl; + } + #endif + + return 0; + } + } else { + // Use the vector count for newly added images + return getEmbImages().size(); + } +} + +/** + * @details Check if the worksheet has any images + */ +bool XLWorksheet::hasImages() const +{ + return !getEmbImages().empty(); +} + +/** + * @details Check if the worksheet has images by examining the XML directly + * This method does not modify the document and can be called safely on temporary objects + */ +bool XLWorksheet::hasImagesInXML() const +{ + // Check if worksheet XML has a element + XMLNode docElement = xmlDocument().document_element(); + XMLNode drawingNode = docElement.child("drawing"); + return !drawingNode.empty(); +} + +/** + * @details Generate the next available image ID for this worksheet + * Ensures uniqueness within the worksheet scope + */ +std::string XLWorksheet::generateNextImageId() const +{ + // Get all existing image IDs (sorted and unique) + std::vector existingIds = getImageIDs(); + + // Microsoft Excel uses numeric IDs (e.g., "1", "2", "3") in DrawingML XML + int counter = 1; + std::string candidateId; + do { + candidateId = std::to_string(counter++); + } while (std::binary_search(existingIds.begin(), existingIds.end(), candidateId)); + + return candidateId; +} + +/** + * @details Generate a unique relationship ID + */ +std::string XLWorksheet::getNextUnusedRelationshipId() const +{ + // Collect all existing relationship IDs + std::set existingRelIds; + + // Get relationship IDs from getEmbImages() + for (const auto& embImage : getEmbImages()) { + if (!embImage.relationshipId().empty()) { + existingRelIds.insert(embImage.relationshipId()); + } + } + + // Get relationship IDs from DrawingML XML (for existing files) + // TODO: this is not needed - we should only be using the m_embImages vector for relationship IDs + if (m_drawingML.valid()) { + try { + XMLNode rootNode = m_drawingML.getRootNode(); + if (!rootNode.empty()) { + for (auto anchor : rootNode.children()) { + // Check anchor type using enum conversion + XLImageAnchorType anchorType = XLImageAnchorUtils::stringToAnchorType(anchor.name()); + if (anchorType != XLImageAnchorType::Unknown) { + XMLNode pic = anchor.child("xdr:pic"); + if (!pic.empty()) { + XMLNode blipFill = pic.child("xdr:blipFill"); + if (!blipFill.empty()) { + XMLNode blip = blipFill.child("a:blip"); + if (!blip.empty()) { + XMLAttribute embedAttr = blip.attribute("r:embed"); + if (!embedAttr.empty()) { + // TODO: add check that the relationship ID is not already in existingRelIds + existingRelIds.insert(embedAttr.value()); + } + } + } + } + } + } + } + } catch (...) { + // Fall through to default + } + } + + // Find the next available relationship ID + int counter = 1; + std::string candidateId; + do { + candidateId = "rId" + std::to_string(counter++); + } while (existingRelIds.find(candidateId) != existingRelIds.end()); + + return candidateId; +} + +/** + * @details Create the drawing relationships file + */ +void XLWorksheet::createDrawingRelationshipsFile(const std::string& drawingFilename) +{ + // Create the relationships filename + std::string drawingRelsFilename = getDrawingRelsFilename(); + + // Create the relationships XML content + std::string relsXml = "\n" + "\n"; + + // Add relationships for each image + int relationshipId = 1; + for (const auto& embImage : getEmbImages()) { + // Use the actual package filename from the XLImage object, not constructed from ID/extension + std::string packageFilename = embImage.getImage()->filePackagePath(); + std::string imageRelativePath = "../media/" + packageFilename.substr(packageFilename.find_last_of('/') + 1); + + relsXml += "\n"; + relationshipId++; + } + + relsXml += ""; + + // Add the relationships file to the archive + if (!parentDoc().addRelationshipsFile(drawingRelsFilename, relsXml)) { + throw XLException("XLWorksheet::createDrawingRelationshipsFile(): could not create drawing relationships file"); + } +} + +/** + * @details Helper function to create new relationships XML with a single relationship + */ +std::string XLWorksheet::createNewRelationshipsXml(const std::string& relationshipId, const std::string& imageRelativePath) +{ + return "\n" + "\n" + "\n" + ""; +} + +/** + * @details Add a relationship for a new image to the existing relationships file + */ +void XLWorksheet::addImageRelationship(const XLEmbeddedImage& embImage) +{ + // Get the drawing relationships filename + std::string drawingRelsFilename = getDrawingRelsFilename(); + + // Get the image's package path and convert to relative path for relationships + std::string packageFilename = embImage.getImage()->filePackagePath(); + std::string imageRelativePath = "../media/" + packageFilename.substr(packageFilename.find_last_of('/') + 1); + + /* get relationship ID */ + const std::string& relationshipId = embImage.relationshipId(); + + std::string relsXml; + + // Try to read existing relationships file + if (this->parentDoc().hasRelationshipsFile(drawingRelsFilename)) { + // Read existing content and add new relationship + try { + // Get existing content from in-memory XML data + std::string relsFilename = getDrawingRelsFilename(); + + // Try to get the relationships XML data from in-memory cache + XLXmlData* relsXmlData = const_cast(this->parentDoc()).getRelationshipsXmlData(relsFilename); + if (!relsXmlData || !relsXmlData->valid()) { + // No existing relationships data, create new file + relsXml = createNewRelationshipsXml(relationshipId, imageRelativePath); + } else { + // Parse existing XML from in-memory data + XMLDocument* relsDoc = relsXmlData->getXmlDocument(); + if (!relsDoc || !relsDoc->document_element()) { + // Invalid XML data, create new file + relsXml = createNewRelationshipsXml(relationshipId, imageRelativePath); + } else { + XMLNode relationshipsNode = relsDoc->document_element(); + + // Start building new XML + relsXml = "\n" + "\n"; + + // Find the highest existing relationship ID to avoid duplicates + int maxId = 0; + std::set existingIds; + + for (auto relNode : relationshipsNode.children("Relationship")) { + std::string relId = relNode.attribute("Id").value(); + std::string relType = relNode.attribute("Type").value(); + std::string relTarget = relNode.attribute("Target").value(); + + // Copy existing relationships + relsXml += "\n"; + + // Track existing IDs for uniqueness check + // TODO: this should not be needed and could cause a problem if different than relationhipId in embImage + existingIds.insert(relId); + + // Extract numeric part for max ID calculation + if (relId.substr(0, 3) == "rId") { + try { + int idNum = std::stoi(relId.substr(3)); + maxId = std::max(maxId, idNum); + } catch (...) { + // Ignore non-numeric IDs + } + } + } + + // Generate unique relationship ID + // TODO: this should not be needed and could cause a problem if different than relationhipId in embImage + std::string uniqueRelId = relationshipId; + int idNum = maxId + 1; + while (existingIds.find(uniqueRelId) != existingIds.end()) { + uniqueRelId = "rId" + std::to_string(idNum); + idNum++; + } + + // Add new relationship with unique ID + relsXml += "\n"; + + relsXml += ""; + } + } + } catch (const std::exception&) { + // If parsing fails, fall back to creating new file + relsXml = createNewRelationshipsXml(relationshipId, imageRelativePath); + } + } else { + // Create new relationships file + relsXml = createNewRelationshipsXml(relationshipId, imageRelativePath); + } + + // Update the relationships file + if (!const_cast(parentDoc()).updateRelationshipsXmlData(drawingRelsFilename, relsXml)) { + throw XLException("XLWorksheet::addImageRelationship(): could not update drawing relationships file"); + } +} + +/** + * @details Add a relationship for a new image to the existing relationships file + */ +void XLWorksheet::addImageRelationship(const XLEmbeddedImage& embImage, const std::string& relationshipId) +{ + // Get the drawing relationships filename + std::string drawingRelsFilename = getDrawingRelsFilename(); + + // Get the image's package path and convert to relative path for relationships + std::string packageFilename = embImage.getImage()->filePackagePath(); + std::string imageRelativePath = "../media/" + packageFilename.substr(packageFilename.find_last_of('/') + 1); + + std::string relsXml; + + // Try to read existing relationships file + if (this->parentDoc().hasRelationshipsFile(drawingRelsFilename)) { + // Read existing content and add new relationship + try { + // Get existing content from in-memory XML data + std::string relsFilename = getDrawingRelsFilename(); + + // Try to get the relationships XML data from in-memory cache + XLXmlData* relsXmlData = const_cast(this->parentDoc()).getRelationshipsXmlData(relsFilename); + if (!relsXmlData || !relsXmlData->valid()) { + // No existing relationships data, create new file + relsXml = createNewRelationshipsXml(relationshipId, imageRelativePath); + } else { + // Parse existing XML from in-memory data + XMLDocument* relsDoc = relsXmlData->getXmlDocument(); + if (!relsDoc || !relsDoc->document_element()) { + // Invalid XML data, create new file + relsXml = createNewRelationshipsXml(relationshipId, imageRelativePath); + } else { + XMLNode relationshipsNode = relsDoc->document_element(); + + // Start building new XML + relsXml = "\n" + "\n"; + + // Find the highest existing relationship ID to avoid duplicates + int maxId = 0; + std::set existingIds; + + for (auto relNode : relationshipsNode.children("Relationship")) { + std::string relId = relNode.attribute("Id").value(); + std::string relType = relNode.attribute("Type").value(); + std::string relTarget = relNode.attribute("Target").value(); + + // Copy existing relationships + relsXml += "\n"; + + // Track existing IDs for uniqueness check + existingIds.insert(relId); + + // Extract numeric part for max ID calculation + if (relId.substr(0, 3) == "rId") { + try { + int idNum = std::stoi(relId.substr(3)); + maxId = std::max(maxId, idNum); + } catch (...) { + // Ignore non-numeric IDs + } + } + } + + // Generate unique relationship ID + std::string uniqueRelId = relationshipId; + int idNum = maxId + 1; + while (existingIds.find(uniqueRelId) != existingIds.end()) { + uniqueRelId = "rId" + std::to_string(idNum); + idNum++; + } + + // Add new relationship with unique ID + relsXml += "\n"; + + relsXml += ""; + } + } + } catch (const std::exception&) { + // If parsing fails, fall back to creating new file + relsXml = createNewRelationshipsXml(relationshipId, imageRelativePath); + } + } else { + // Create new relationships file + relsXml = createNewRelationshipsXml(relationshipId, imageRelativePath); + } + + // Update the relationships file + if (!const_cast(parentDoc()).updateRelationshipsXmlData(drawingRelsFilename, relsXml)) { + throw XLException("XLWorksheet::addImageRelationship(): could not update drawing relationships file"); + } +} + /** * @details perform a pattern matching on getXmlPath for (regex) .*xl/worksheets/sheet([0-9]*)\.xml$ and extract the numeric part \1 */ @@ -1798,3 +2450,815 @@ bool XLChartsheet::isSelected_impl() const { return tabIsSelected(xmlDocument()) * @details Calls the setTabSelected() free function. */ void XLChartsheet::setSelected_impl(bool selected) { setTabSelected(xmlDocument(), selected); } + +/** + * @details Compare two worksheets for debugging + */ +int XLWorksheet::compare(const XLWorksheet& other, std::string* diffMsg) const +{ + // Compare worksheet names first (most important for human readability) + std::string thisName = name(); + std::string otherName = other.name(); + int nameCompare = thisName.compare(otherName); + if (nameCompare != 0) { + appendDbgMsg(diffMsg, "worksheet name differs: '" + thisName + "' vs '" + otherName + "'"); + return nameCompare; + } + + // Compare DrawingML validity (crucial for image detection) + // DrawingML is now automatically initialized in XLWorkbook::worksheet() if images exist + bool thisDrawingMLValid = m_drawingML.valid(); + bool otherDrawingMLValid = other.m_drawingML.valid(); + if (thisDrawingMLValid != otherDrawingMLValid) { + appendDbgMsg(diffMsg, "DrawingML validity differs: " + std::string(thisDrawingMLValid ? "valid" : "invalid") + + " vs " + std::string(otherDrawingMLValid ? "valid" : "invalid")); + return thisDrawingMLValid ? 1 : -1; + } + + // Compare image count + size_t thisImageCount = imageCount(); + size_t otherImageCount = other.imageCount(); + if (thisImageCount != otherImageCount) { + appendDbgMsg(diffMsg, "image count differs: " + std::to_string(thisImageCount) + + " vs " + std::to_string(otherImageCount)); + return thisImageCount < otherImageCount ? -1 : 1; + } + + // Compare embedded images (parsed image metadata) + size_t thisEmbImagesSize = getEmbImages().size(); + size_t otherEmbImagesSize = other.getEmbImages().size(); + if (thisEmbImagesSize != otherEmbImagesSize) { + appendDbgMsg(diffMsg, "embedded images size differs: " + std::to_string(thisEmbImagesSize) + + " vs " + std::to_string(otherEmbImagesSize)); + return thisEmbImagesSize < otherEmbImagesSize ? -1 : 1; + } + + // Compare images if both worksheets have images in memory + size_t thisImagesInMemory = getEmbImages().size(); + size_t otherImagesInMemory = other.getEmbImages().size(); + if (thisImagesInMemory > 0 || otherImagesInMemory > 0) { + if (thisImagesInMemory != otherImagesInMemory) { + appendDbgMsg(diffMsg, "images in memory count differs: " + std::to_string(thisImagesInMemory) + + " vs " + std::to_string(otherImagesInMemory)); + return thisImagesInMemory < otherImagesInMemory ? -1 : 1; + } + + // Compare each image in memory + for (size_t i = 0; i < thisImagesInMemory && i < otherImagesInMemory; ++i) { + int imageCompare = getEmbImages()[i].compare(other.getEmbImages()[i], diffMsg); + if (imageCompare != 0) { + appendDbgMsg(diffMsg, "image " + std::to_string(i) + " differs"); + return imageCompare; + } + } + } + + // Compare embedded images entries if both have embedded images entries + if (thisEmbImagesSize > 0) { + for (size_t i = 0; i < thisEmbImagesSize && i < otherEmbImagesSize; ++i) { + int embImagesCompare = getEmbImages()[i].compare(other.getEmbImages()[i], diffMsg); + if (embImagesCompare != 0) { + appendDbgMsg(diffMsg, "embedded images entry " + std::to_string(i) + " differs"); + return embImagesCompare; + } + } + } + + // Compare relationships (crucial for image detection) - simplified + try { + // Just check if both have valid relationships objects + bool thisRelValid = m_relationships.valid(); + bool otherRelValid = other.m_relationships.valid(); + if (thisRelValid != otherRelValid) { + appendDbgMsg(diffMsg, "relationships validity differs: " + std::string(thisRelValid ? "valid" : "invalid") + + " vs " + std::string(otherRelValid ? "valid" : "invalid")); + return thisRelValid ? 1 : -1; + } + + // Skip DrawingML relationship check for now to avoid path issues + // TODO: Implement safer DrawingML relationship comparison + } catch (const std::exception&) { + appendDbgMsg(diffMsg, "error comparing relationships"); + return -1; + } + + // TODO: Compare other worksheet properties + // - Cell content (would require extensive implementation) + // - Merge cells + // - Comments + // - Tables + // - DrawingML XML content (would require XML comparison) + // - VML drawing content + + // Worksheets are identical + return 0; +} + +int XLWorksheet::verifyData(std::string* dbgMsg) const +{ + int errorCount = 0; + std::string worksheetName = name(); + + // TODO: Verify sub-objects when verifyData() is implemented for them + // errorCount += m_relationships.verifyData(dbgMsg); + // errorCount += m_merges.verifyData(dbgMsg); + // errorCount += m_vmlDrawing.verifyData(dbgMsg); + + // Only verify DrawingML if the worksheet has images + // For worksheets without images, DrawingML can be invalid + if (!getEmbImages().empty() || hasImagesInXML()) { + errorCount += m_drawingML.verifyData(dbgMsg); + } + // errorCount += m_comments.verifyData(dbgMsg); + // errorCount += m_tables.verifyData(dbgMsg); + + // Verify images vector + for (size_t i = 0; i < getEmbImages().size(); ++i) { + int imageErrors = getEmbImages()[i].verifyData(dbgMsg); + if (imageErrors > 0) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image " + std::to_string(i) + " has " + std::to_string(imageErrors) + " errors"); + errorCount += imageErrors; + } + } + + // Verify embedded images consistency + if (m_drawingML.valid()) { + try { + // Check that embedded images vector is populated + if (getEmbImages().empty()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] embedded images vector is empty but drawing is valid"); + errorCount++; + } + } catch (const std::exception&) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] error accessing embedded images"); + errorCount++; + } + } + + // Verify unique image IDs + errorCount += verifyUniqueImageIDs(dbgMsg); + + // Verify DrawingML XML consistency with registry + errorCount += verifyDrawingMLConsistency(dbgMsg); + + // Verify m_embImages vector consistency with XML + errorCount += verifyImagesVectorConsistency(dbgMsg); + + // Verify binary image data exists in archive + errorCount += verifyBinaryImageData(dbgMsg); + + return errorCount; +} + +int XLWorksheet::verifyUniqueImageIDs(std::string* dbgMsg) const +{ + int duplicateCount = 0; + std::string worksheetName = name(); + + // Check 1: Embedded images for duplicate IDs + if (m_drawingML.valid()) { + try { + std::set registryIds; + + for (const auto& embImage : getEmbImages()) { + if (registryIds.find(embImage.id()) != registryIds.end()) { + // Duplicate found in embedded images + appendDbgMsg(dbgMsg, "[" + worksheetName + "] duplicate image ID in embedded images: " + embImage.id()); + duplicateCount++; + } else { + registryIds.insert(embImage.id()); + } + } + } catch (const std::exception&) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] error accessing image registry for ID verification"); + duplicateCount++; + } + } + + // Check 2: m_embImages vector for duplicate IDs + std::set imageIds; + for (size_t i = 0; i < getEmbImages().size(); ++i) { + const std::string& imageId = getEmbImages()[i].id(); + if (imageIds.find(imageId) != imageIds.end()) { + // Duplicate found in m_embImages vector + appendDbgMsg(dbgMsg, "[" + worksheetName + "] duplicate image ID in m_embImages vector: " + imageId); + duplicateCount++; + } else { + imageIds.insert(imageId); + } + } + + return duplicateCount; +} + +int XLWorksheet::verifyDrawingMLConsistency(std::string* dbgMsg) const +{ + int inconsistencyCount = 0; + std::string worksheetName = name(); + + // Check if DrawingML is valid + if (!m_drawingML.valid()) { + // If DrawingML is invalid but we have embedded images, that's an inconsistency + if (!getEmbImages().empty()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] DrawingML is invalid but has " + + std::to_string(getEmbImages().size()) + " embedded images"); + inconsistencyCount++; + } + return inconsistencyCount; // No further checks possible + } + + try { + // Get embedded images data + size_t embeddedCount = getEmbImages().size(); + + // Get DrawingML XML data + XMLNode rootNode = m_drawingML.getRootNode(); + if (rootNode.empty()) { + // Empty root node is OK if there are no images + if (!getEmbImages().empty()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] DrawingML root node is empty but has " + + std::to_string(getEmbImages().size()) + " embedded images"); + inconsistencyCount++; + } + return inconsistencyCount; + } + + // Count anchors in XML + size_t xmlAnchorCount = 0; + std::vector xmlImageIds; + std::vector xmlRelationshipIds; + + for (auto anchor : rootNode.children()) { + // Check anchor type using enum conversion + XLImageAnchorType anchorType = XLImageAnchorUtils::stringToAnchorType(anchor.name()); + if (anchorType != XLImageAnchorType::Unknown) { + xmlAnchorCount++; + + // Extract image ID from XML + XMLNode pic = anchor.child("xdr:pic"); + if (!pic.empty()) { + XMLNode nvPicPr = pic.child("xdr:nvPicPr"); + if (!nvPicPr.empty()) { + XMLNode cNvPr = nvPicPr.child("xdr:cNvPr"); + if (!cNvPr.empty()) { + XMLAttribute idAttr = cNvPr.attribute("id"); + if (!idAttr.empty()) { + xmlImageIds.push_back(idAttr.value()); + } + } + } + } + + // Extract relationship ID from XML + XMLNode blipFill = pic.child("xdr:blipFill"); + if (!blipFill.empty()) { + XMLNode blip = blipFill.child("a:blip"); + if (!blip.empty()) { + XMLAttribute embedAttr = blip.attribute("r:embed"); + if (!embedAttr.empty()) { + xmlRelationshipIds.push_back(embedAttr.value()); + } + } + } + } + } + + // Check 1: Anchor count consistency + if (xmlAnchorCount != embeddedCount) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] XML anchor count (" + std::to_string(xmlAnchorCount) + + ") does not match embedded images count (" + std::to_string(embeddedCount) + ")"); + inconsistencyCount++; + } + + // Check 2: Image ID consistency + std::set embeddedImageIds; + for (const auto& embImage : getEmbImages()) { + embeddedImageIds.insert(embImage.id()); + } + + std::set xmlImageIdSet(xmlImageIds.begin(), xmlImageIds.end()); + + // Check for IDs in registry but not in XML + // Check for IDs in embedded images but not in XML + for (const auto& embeddedId : embeddedImageIds) { + if (xmlImageIdSet.find(embeddedId) == xmlImageIdSet.end()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image ID '" + embeddedId + "' in embedded images but not in XML"); + inconsistencyCount++; + } + } + + // Check for IDs in XML but not in embedded images + for (const auto& xmlId : xmlImageIdSet) { + if (embeddedImageIds.find(xmlId) == embeddedImageIds.end()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image ID '" + xmlId + "' in XML but not in embedded images"); + inconsistencyCount++; + } + } + + // Check 3: Relationship ID consistency + std::set embeddedRelIds; + for (const auto& embImage : getEmbImages()) { + embeddedRelIds.insert(embImage.relationshipId()); + } + + std::set xmlRelIdSet(xmlRelationshipIds.begin(), xmlRelationshipIds.end()); + + // Check for relationship IDs in embedded images but not in XML + for (const auto& embeddedRelId : embeddedRelIds) { + if (xmlRelIdSet.find(embeddedRelId) == xmlRelIdSet.end()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] relationship ID '" + embeddedRelId + "' in embedded images but not in XML"); + inconsistencyCount++; + } + } + + // Check for relationship IDs in XML but not in embedded images + for (const auto& xmlRelId : xmlRelIdSet) { + if (embeddedRelIds.find(xmlRelId) == embeddedRelIds.end()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] relationship ID '" + xmlRelId + "' in XML but not in embedded images"); + inconsistencyCount++; + } + } + + } catch (const std::exception& e) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] error during DrawingML consistency check: " + std::string(e.what())); + inconsistencyCount++; + } + + return inconsistencyCount; +} + +int XLWorksheet::verifyImagesVectorConsistency(std::string* dbgMsg) const +{ + int inconsistencyCount = 0; + std::string worksheetName = name(); + + // Check if DrawingML is valid + if (!m_drawingML.valid()) { + // If DrawingML is invalid but we have images in m_embImages, that's an inconsistency + if (!getEmbImages().empty()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] DrawingML is invalid but m_embImages has " + + std::to_string(getEmbImages().size()) + " entries"); + inconsistencyCount++; + } + return inconsistencyCount; // No further checks possible + } + + try { + // Get DrawingML XML data + XMLNode rootNode = m_drawingML.getRootNode(); + if (rootNode.empty()) { + // Empty root node is OK if there are no images + if (!getEmbImages().empty()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] DrawingML root node is empty but m_embImages has " + + std::to_string(getEmbImages().size()) + " entries"); + inconsistencyCount++; + } + return inconsistencyCount; + } + + // Count anchors in XML and extract detailed information + size_t xmlAnchorCount = 0; + std::vector xmlImageIds; + std::vector xmlRelationshipIds; + std::vector xmlAnchorTypes; + + for (auto anchor : rootNode.children()) { + // Check anchor type using enum conversion + XLImageAnchorType anchorType = XLImageAnchorUtils::stringToAnchorType(anchor.name()); + if (anchorType != XLImageAnchorType::Unknown) { + xmlAnchorCount++; + + // Extract anchor type + xmlAnchorTypes.push_back(anchorType); + + // Extract image ID from XML + std::string xmlImageId; + XMLNode pic = anchor.child("xdr:pic"); + if (!pic.empty()) { + XMLNode nvPicPr = pic.child("xdr:nvPicPr"); + if (!nvPicPr.empty()) { + XMLNode cNvPr = nvPicPr.child("xdr:cNvPr"); + if (!cNvPr.empty()) { + XMLAttribute idAttr = cNvPr.attribute("id"); + if (!idAttr.empty()) { + xmlImageId = idAttr.value(); + xmlImageIds.push_back(xmlImageId); + } + } + } + } + + // Extract relationship ID from XML + std::string xmlRelId; + if (!pic.empty()) { + XMLNode blipFill = pic.child("xdr:blipFill"); + if (!blipFill.empty()) { + XMLNode blip = blipFill.child("a:blip"); + if (!blip.empty()) { + XMLAttribute embedAttr = blip.attribute("r:embed"); + if (!embedAttr.empty()) { + xmlRelId = embedAttr.value(); + xmlRelationshipIds.push_back(xmlRelId); + } + } + } + } + } + } + + // Check 1: Anchor count consistency + if (xmlAnchorCount != getEmbImages().size()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] XML anchor count (" + std::to_string(xmlAnchorCount) + + ") does not match m_embImages count (" + std::to_string(getEmbImages().size()) + ")"); + inconsistencyCount++; + } + + // Check 2: Image ID consistency + std::set mImagesIds; + for (const auto& embImage : getEmbImages()) { + if (!embImage.id().empty()) { + mImagesIds.insert(embImage.id()); + } + } + + std::set xmlImageIdSet(xmlImageIds.begin(), xmlImageIds.end()); + + // Check for IDs in m_embImages but not in XML + for (const auto& mImageId : mImagesIds) { + if (xmlImageIdSet.find(mImageId) == xmlImageIdSet.end()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image ID '" + mImageId + "' in m_embImages but not in XML"); + inconsistencyCount++; + } + } + + // Check for IDs in XML but not in m_embImages + for (const auto& xmlId : xmlImageIdSet) { + if (mImagesIds.find(xmlId) == mImagesIds.end()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image ID '" + xmlId + "' in XML but not in m_embImages"); + inconsistencyCount++; + } + } + + // Check 3: Detailed cross-reference validation for each embedded image + for (size_t i = 0; i < getEmbImages().size() && i < xmlImageIds.size(); ++i) { + const auto& embImage = getEmbImages()[i]; + const std::string& xmlImageId = xmlImageIds[i]; + const std::string& xmlRelId = (i < xmlRelationshipIds.size()) ? xmlRelationshipIds[i] : ""; + XLImageAnchorType xmlAnchorType = (i < xmlAnchorTypes.size()) ? xmlAnchorTypes[i] : XLImageAnchorType::Unknown; + + // Check image ID match + if (embImage.id() != xmlImageId) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image " + std::to_string(i) + + " ID mismatch: m_embImages='" + embImage.id() + "', XML='" + xmlImageId + "'"); + inconsistencyCount++; + } + + // Check relationship ID match + if (!xmlRelId.empty() && embImage.relationshipId() != xmlRelId) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image " + std::to_string(i) + + " relationship ID mismatch: m_embImages='" + embImage.relationshipId() + "', XML='" + xmlRelId + "'"); + inconsistencyCount++; + } + + // Check anchor type match + if (xmlAnchorType != XLImageAnchorType::Unknown && embImage.getImageAnchor().anchorType != xmlAnchorType) { + std::string embAnchorTypeStr = XLImageAnchorUtils::anchorTypeToString(embImage.getImageAnchor().anchorType); + std::string xmlAnchorTypeStr = XLImageAnchorUtils::anchorTypeToString(xmlAnchorType); + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image " + std::to_string(i) + + " anchor type mismatch: m_embImages='" + embAnchorTypeStr + "', XML='" + xmlAnchorTypeStr + "'"); + inconsistencyCount++; + } + + // Check if embedded image has valid XLImage object + if (!embImage.getImage()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image " + std::to_string(i) + + " has no associated XLImage object"); + inconsistencyCount++; + } else { + // Check package path format + std::string packagePath = embImage.getImage()->filePackagePath(); + if (!packagePath.empty() && packagePath.find("xl/media/") != 0) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image " + std::to_string(i) + + " package path '" + packagePath + "' does not start with 'xl/media/'"); + inconsistencyCount++; + } + } + } + + // Check 4: Relationship ID uniqueness + std::set uniqueRelIds; + for (const auto& relId : xmlRelationshipIds) { + if (!relId.empty()) { + if (uniqueRelIds.find(relId) != uniqueRelIds.end()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] duplicate relationship ID '" + relId + "' in XML"); + inconsistencyCount++; + } else { + uniqueRelIds.insert(relId); + } + } + } + + // Check 5: Image ID uniqueness + std::set uniqueImageIds; + for (const auto& imgId : xmlImageIds) { + if (!imgId.empty()) { + if (uniqueImageIds.find(imgId) != uniqueImageIds.end()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] duplicate image ID '" + imgId + "' in XML"); + inconsistencyCount++; + } else { + uniqueImageIds.insert(imgId); + } + } + } + + // Check 6: Package path consistency with relationship XML + try { + // Get the drawing relationships filename + std::string relsFilename = getDrawingRelsFilename(); + + // Check if relationships file exists + if (parentDoc().hasRelationshipsFile(relsFilename)) { + // Use in-memory XML data instead of parsing file content + XLXmlData* relsXmlData = const_cast(parentDoc()).getRelationshipsXmlData(relsFilename); + if (relsXmlData && relsXmlData->valid()) { + // Parse the relationships XML from in-memory data + XMLDocument* relsDoc = relsXmlData->getXmlDocument(); + if (relsDoc && relsDoc->document_element()) { + // Create a map of relationship ID to target path + std::map relIdToTarget; + for (auto rel : relsDoc->document_element().children("Relationship")) { + std::string relId = rel.attribute("Id").value(); + std::string target = rel.attribute("Target").value(); + std::string type = rel.attribute("Type").value(); + + // Only process image relationships + if (type == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image") { + if (!relId.empty() && !target.empty()) { + relIdToTarget[relId] = target; + } + } + } + + // Check each embedded image's package path against XML target + for (size_t i = 0; i < getEmbImages().size() && i < xmlRelationshipIds.size(); ++i) { + const auto& embImage = getEmbImages()[i]; + const std::string& xmlRelId = xmlRelationshipIds[i]; + + if (!xmlRelId.empty() && embImage.getImage()) { + auto targetIt = relIdToTarget.find(xmlRelId); + if (targetIt != relIdToTarget.end()) { + std::string xmlTarget = targetIt->second; + + // Convert XML target path to expected package path + // XML target is like "../media/image1.png", package path should be "xl/media/image1.png" + std::string expectedPackagePath; + if (xmlTarget.find("../media/") == 0) { + expectedPackagePath = "xl/media/" + xmlTarget.substr(9); // Remove "../media/" + } else if (xmlTarget.find("media/") == 0) { + expectedPackagePath = "xl/media/" + xmlTarget.substr(6); // Remove "media/" + } else { + // Handle other possible formats + size_t lastSlash = xmlTarget.find_last_of('/'); + if (lastSlash != std::string::npos) { + expectedPackagePath = "xl/media/" + xmlTarget.substr(lastSlash + 1); + } else { + expectedPackagePath = "xl/media/" + xmlTarget; + } + } + + std::string actualPackagePath = embImage.getImage()->filePackagePath(); + + if (actualPackagePath != expectedPackagePath) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image " + std::to_string(i) + + " package path mismatch: XLImage='" + actualPackagePath + + "', XML target='" + xmlTarget + "' (expected='" + expectedPackagePath + "')"); + inconsistencyCount++; + } + } else { + if (relIdToTarget.empty()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image " + std::to_string(i) + + " relationship ID '" + xmlRelId + "' not found in relationships XML (file is empty)"); + } else { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image " + std::to_string(i) + + " relationship ID '" + xmlRelId + "' not found in relationships XML"); + } + inconsistencyCount++; + } + } + } + } else { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] relationships XML document is invalid"); + inconsistencyCount++; + } + } else { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] relationships XML data is not available"); + inconsistencyCount++; + } + } else { + // No relationships file - this might be OK if there are no images + if (!getEmbImages().empty()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] has " + std::to_string(getEmbImages().size()) + + " images but no relationships file"); + inconsistencyCount++; + } + } + } catch (const std::exception& e) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] error during package path consistency check: " + std::string(e.what())); + inconsistencyCount++; + } + + } catch (const std::exception& e) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] error during m_embImages consistency check: " + std::string(e.what())); + inconsistencyCount++; + } + + return inconsistencyCount; +} + +int XLWorksheet::verifyBinaryImageData(std::string* dbgMsg) const +{ + int issueCount = 0; + std::string worksheetName = name(); + + // Only validate if we have images to check + if (getEmbImages().empty()) { + return issueCount; // No images to validate + } + + try { + // Get the drawing relationships filename + std::string relsFilename = getDrawingRelsFilename(); + + // Check if relationships file exists in archive + if (!parentDoc().hasRelationshipsFile(relsFilename)) { + // Check if we have images but no relationships file - this is a problem + if (getEmbImages().size() > 0) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] has " + std::to_string(getEmbImages().size()) + " images but no relationships file"); + issueCount++; + } + // No relationships file means no binary data to validate + return issueCount; + } + + // Read the relationships file + std::string relsContent = parentDoc().readRelationshipsFile(relsFilename); + if (relsContent.empty()) { + // Check if we have images but empty relationships file - this is a problem + if (getEmbImages().size() > 0) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] has " + std::to_string(getEmbImages().size()) + " images but relationships file is empty"); + issueCount++; + } + return issueCount; // Empty relationships file means no binary data to validate + } + + // Parse the relationships XML + pugi::xml_document relsDoc; + if (!relsDoc.load_string(relsContent.c_str())) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] failed to parse relationships XML"); + issueCount++; + return issueCount; + } + + // Collect unique image paths and verify binary files exist + std::set referencedImagePaths; + + for (auto rel : relsDoc.document_element().children("Relationship")) { + std::string target = rel.attribute("Target").value(); + std::string type = rel.attribute("Type").value(); + + // Only process image relationships + if (type == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image") { + referencedImagePaths.insert(target); + } + } + + // Verify that each referenced binary file actually exists in the archive + for (const auto& imagePath : referencedImagePaths) { + // Convert relative path to absolute path for archive check + std::string absolutePath = getImageMediaPathFromFilename(imagePath); + + try { + std::string imageContent = parentDoc().readFile(absolutePath); + if (imageContent.empty()) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] image file '" + absolutePath + "' referenced in relationships but binary file missing from archive"); + issueCount++; + } + } catch (const std::exception&) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] error checking image file '" + absolutePath + "' during relationships integrity verification"); + issueCount++; + } + } + + } catch (const std::exception& e) { + appendDbgMsg(dbgMsg, "[" + worksheetName + "] error during relationships integrity verification: " + std::string(e.what())); + issueCount++; + } + + return issueCount; +} + +std::vector XLWorksheet::getImageIDs() const +{ + std::vector imageIds; + + // Get image IDs from m_embImages vector + for (const auto& embImage : getEmbImages()) { + if (!embImage.id().empty()) { + imageIds.push_back(embImage.id()); + } + } + + // Get image IDs from embedded images + if (m_drawingML.valid()) { + try { + for (const auto& embImage : getEmbImages()) { + if (!embImage.id().empty()) { + imageIds.push_back(embImage.id()); + } + } + } catch (const std::exception&) { + // Embedded images access failed, continue with empty list + } + } + + // Sort the vector + std::sort(imageIds.begin(), imageIds.end()); + + // Remove duplicates using std::unique() and vector::erase() + auto it = std::unique(imageIds.begin(), imageIds.end()); + imageIds.erase(it, imageIds.end()); + + return imageIds; +} + +/** + * @brief Get the drawing relationships filename for this worksheet + * @return The relationships filename (e.g., "xl/drawings/_rels/drawing1.xml.rels") + */ +std::string XLWorksheet::getDrawingRelsFilename() const +{ + return "xl/drawings/_rels/drawing" + std::to_string(sheetXmlNumber()) + ".xml.rels"; +} + +/** + * @brief Get the drawing filename for this worksheet + * @return The drawing filename (e.g., "xl/drawings/drawing1.xml") + */ +std::string XLWorksheet::getDrawingFilename() const +{ + return "xl/drawings/drawing" + std::to_string(sheetXmlNumber()) + ".xml"; +} + +/** + * @brief Get the image media filename for an embedded image + * @param imageId The image ID + * @param extension The image file extension (e.g., ".png", ".jpg") + * @return The image media filename (e.g., "xl/media/image_1.png") + */ +std::string XLWorksheet::getImageMediaFilename(const std::string& imageId, const std::string& extension) +{ + return "xl/media/image_" + imageId + extension; +} + +/** + * @brief Convert a relative image path to absolute archive path + * @param relativePath The relative path (e.g., "../media/image_1.png" or "media/image_1.png") + * @return The absolute archive path (e.g., "xl/media/image_1.png") + */ +std::string XLWorksheet::getAbsoluteImagePath(const std::string& relativePath) +{ + if (relativePath.substr(0, 3) == "../") { + return "xl/" + relativePath.substr(3); // Convert "../media/image_1.png" to "xl/media/image_1.png" + } else if (relativePath.substr(0, 4) != "xl/") { + return "xl/" + relativePath; // Add xl/ prefix if missing + } + return relativePath; // Already absolute +} + +/** + * @brief Extract filename from a full image path + * @param imagePath The full image path + * @return The absolute archive path with extracted filename (e.g., "xl/media/image_1.png") + */ +std::string XLWorksheet::getImageMediaPathFromFilename(const std::string& imagePath) +{ + return "xl/media/" + imagePath.substr(imagePath.find_last_of('/') + 1); +} + +std::vector& XLWorksheet::getEmbImages() +{ + if (!m_embImages) { + m_embImages = parentDoc().getImageManager().getEmbImgsForSheetName(name()); + if(!m_embImages){ + throw XLInternalError( std::string("failed to initialize m_embImages for") + name() ); + } + } + return *m_embImages; +} + +const std::vector& XLWorksheet::getEmbImages() const +{ + const std::vector& embImages = const_cast(this)->getEmbImages(); + return embImages; +} + diff --git a/OpenXLSX/sources/XLWorkbook.cpp b/OpenXLSX/sources/XLWorkbook.cpp index 34df1c79..7f8fb198 100644 --- a/OpenXLSX/sources/XLWorkbook.cpp +++ b/OpenXLSX/sources/XLWorkbook.cpp @@ -130,12 +130,48 @@ XLSheet XLWorkbook::sheet(uint16_t index) // 2024-04-30: whitespace support /** * @details */ -XLWorksheet XLWorkbook::worksheet(const std::string& sheetName) { return sheet(sheetName).get(); } +XLWorksheet XLWorkbook::worksheet(const std::string& sheetName) +{ + XLWorksheet worksheet = sheet(sheetName).get(); + + // Only initialize DrawingML if the worksheet actually has images + // This avoids unnecessary XML modifications for worksheets without images + if (worksheet.hasImagesInXML()) { + try { + std::ignore = worksheet.drawingML(); // Initialize DrawingML + // Populate m_images vector from existing XML data + worksheet.populateImagesFromXML(); + } catch (...) { + // DrawingML initialization failed, but worksheet is still usable + } + } + // If no images, leave DrawingML invalid (fast path) + + return worksheet; +} /** * @details */ -XLWorksheet XLWorkbook::worksheet(uint16_t index) { return sheet(index).get(); } +XLWorksheet XLWorkbook::worksheet(uint16_t index) +{ + XLWorksheet worksheet = sheet(index).get(); + + // Only initialize DrawingML if the worksheet actually has images + // This avoids unnecessary XML modifications for worksheets without images + if (worksheet.hasImagesInXML()) { + try { + std::ignore = worksheet.drawingML(); // Initialize DrawingML + // Populate m_images vector from existing XML data + worksheet.populateImagesFromXML(); + } catch (...) { + // DrawingML initialization failed, but worksheet is still usable + } + } + // If no images, leave DrawingML invalid (fast path) + + return worksheet; +} /** * @details diff --git a/OpenXLSX/sources/XLWorksheetImageQuery.cpp b/OpenXLSX/sources/XLWorksheetImageQuery.cpp new file mode 100644 index 00000000..cee212ac --- /dev/null +++ b/OpenXLSX/sources/XLWorksheetImageQuery.cpp @@ -0,0 +1,840 @@ +/* + + ____ ____ ___ ____ ____ ____ ___ + 6MMMMb `MM( )M' `MM' 6MMMMb\`MM( )M' + 8P Y8 `MM. d' MM 6M' ` `MM. d' +6M Mb __ ____ ____ ___ __ `MM. d' MM MM `MM. d' +MM MM `M6MMMMb 6MMMMb `MM 6MMb `MM. d' MM YM. `MM. d' +MM MM MM' `Mb 6M' `Mb MMM9 `Mb `MMd MM YMMMMb `MMd +MM MM MM MM MM MM MM' MM dMM. MM `Mb dMM. +MM MM MM MM MMMMMMMM MM MM d'`MM. MM MM d'`MM. +YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM. + 8b d8 MM. ,M9 YM d9 MM MM d' `MM. MM / L ,M9 d' `MM. + YMMMM9 MMYMMM9 YMMMM9 _MM_ _MM_M(_ _)MM_ _MMMMMMM MYMMMM9 _M(_ _)MM_ + MM + MM + _MM_ + + Copyright (c) 2018, Kenneth Troldal Balslev + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + - Neither the name of the author nor the + names of any contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + */ + +// ===== External Includes ===== // +#include +#include +#include +#include + +// ===== OpenXLSX Includes ===== // +#include "XLImage.hpp" +#include "XLSheet.hpp" +#include "XLCellReference.hpp" +#include "XLDocument.hpp" +#include "XLDrawingML.hpp" + +namespace OpenXLSX +{ + //---------------------------------------------------------------------------------------------------------------------- + // Image Query Methods Implementation + //---------------------------------------------------------------------------------------------------------------------- + + /** + * @brief Get specific embedded image by its image ID + */ + const XLEmbeddedImage& XLWorksheet::getEmbImageByImageID(const std::string& imageId) const + { + const std::vector& embImages = getEmbImages(); + const XLEmbeddedImage *result = nullptr; + std::vector::const_iterator embImgItr = embImages.begin(); + for( ; (embImages.end() != embImgItr) && (nullptr == result); ++embImgItr ){ + const XLEmbeddedImage& embImage = *embImgItr; + if( embImage.id() == imageId ){ + result = &embImage; + } + } + +#if (defined(_DEBUG) || !defined(NDEBUG)) + /* check for duplicate ID */ + for( ; embImages.end() != embImgItr; ++embImgItr ){ + const XLEmbeddedImage& embImage = *embImgItr; + if( embImage.id() == imageId ){ + std::cerr << "WARNING: Worksheet '" << name() + << "' has duplicate imageId = '" << imageId << std::endl; + } + } +#endif + // Return empty embedded image if not found + static const XLEmbeddedImage emptyEmbImage{}; + if( nullptr == result ){ + result = &emptyEmbImage; + } + + return *result; + } + + /** + * @brief Get specific embedded image by its relationship ID + */ + const XLEmbeddedImage& XLWorksheet::getEmbImageByRelationshipId(const std::string& relationshipId) const + { + const std::vector& embImages = getEmbImages(); + const XLEmbeddedImage *result = nullptr; + std::vector::const_iterator embImgItr = embImages.begin(); + for( ; (embImages.end() != embImgItr) && (nullptr == result); ++embImgItr ){ + const XLEmbeddedImage& embImage = *embImgItr; + if( embImage.relationshipId() == relationshipId ){ + result = &embImage; + } + } + +#if (defined(_DEBUG) || !defined(NDEBUG)) + /* check for duplicate ID */ + for( ; embImages.end() != embImgItr; ++embImgItr ){ + const XLEmbeddedImage& embImage = *embImgItr; + if( embImage.relationshipId() == relationshipId ){ + std::cerr << "WARNING: Worksheet '" << name() + << "' has duplicate relationshipId = '" << relationshipId << std::endl; + } + } +#endif + // Return empty embedded image if not found + static const XLEmbeddedImage emptyEmbImage{}; + if( nullptr == result ){ + result = &emptyEmbImage; + } + + return *result; + } + + /** + * @brief Get all embedded images at a specific cell + */ + std::vector XLWorksheet::getEmbImagesAtCell(const std::string& cellRef) const + { + std::vector result; + + // Filter embedded images by cell reference + for (const auto& embImage : getEmbImages()) { + if (embImage.anchorCell() == cellRef) { + result.push_back(embImage); + } + } + + return result; + } + + /** + * @brief Get all embedded images in a specific range + */ + std::vector XLWorksheet::getEmbImagesInRange(const std::string& cellRange) const + { + std::vector result; + + try { + // Parse the range + size_t colonPos = cellRange.find(':'); + if (colonPos == std::string::npos) { + // Single cell range + return getEmbImagesAtCell(cellRange); + } else { + // Range format + std::string topLeftStr = cellRange.substr(0, colonPos); + std::string bottomRightStr = cellRange.substr(colonPos + 1); + + XLCellReference topLeftRef(topLeftStr); + XLCellReference bottomRightRef(bottomRightStr); + + // Filter embedded images by range + for (const auto& embImage : getEmbImages()) { + XLCellReference imgRef(embImage.anchorCell()); + + // Check if image is within range + if (imgRef.row() >= topLeftRef.row() && imgRef.row() <= bottomRightRef.row() && + imgRef.column() >= topLeftRef.column() && imgRef.column() <= bottomRightRef.column()) { + result.push_back(embImage); + } + } + } + } catch (const std::exception&) { + // Invalid range format, return empty vector + } + + return result; + } + + + //---------------------------------------------------------------------------------------------------------------------- + // Image Modification Methods Implementation + //---------------------------------------------------------------------------------------------------------------------- + + /** + * @brief Remove an image by its ID + * @param imageId The unique image identifier to remove + * @return True if the image was found and removed, false otherwise + */ + bool XLWorksheet::removeImageByImageID(const std::string& imageId) + { + // Find the image in embedded images + auto it = std::find_if(getEmbImages().begin(), getEmbImages().end(), + [&imageId](const XLEmbeddedImage& img) { return img.id() == imageId; }); + + if (it == getEmbImages().end()) { + return false; // Image not found + } + + // Store the relationship ID before removing + std::string relationshipId = it->relationshipId(); + + // Remove from DrawingML XML (in-memory XML document) + bool xmlRemoved = removeImageFromDrawingXML(relationshipId); + + // Remove from relationships file (in-memory XML document) + bool relsRemoved = removeImageFromRelationships(relationshipId); + + + // Remove from m_embImages vector (in-memory cache) + getEmbImages().erase(it); + + // If this was the last image, remove the element from worksheet XML + if (getEmbImages().empty()) { + removeDrawingElementIfEmpty(); + } + + // Note: XLDocument.getImageManager()->prune() will delete binary image data files from archive as needed. + + // Return true if all critical operations succeeded + return xmlRemoved && relsRemoved; + } + + /** + * @brief Remove an image by its relationship ID + * @param relationshipId The relationship ID to remove + * @return True if the image was found and removed, false otherwise + */ + bool XLWorksheet::removeImageByRelationshipId(const std::string& relationshipId) + { + // Find the image in embedded images + auto it = std::find_if(getEmbImages().begin(), getEmbImages().end(), + [&relationshipId](const XLEmbeddedImage& img) { return img.relationshipId() == relationshipId; }); + + if (it == getEmbImages().end()) { + return false; // Image not found + } + + // Remove from DrawingML XML (in-memory XML document) + bool xmlRemoved = removeImageFromDrawingXML(relationshipId); + + // Remove from relationships file (in-memory XML document) + bool relsRemoved = removeImageFromRelationships(relationshipId); + + + // Remove from m_embImages vector (in-memory cache) + getEmbImages().erase(it); + + // If this was the last image, remove the element from worksheet XML + if (getEmbImages().empty()) { + removeDrawingElementIfEmpty(); + } + + // Note: XLDocument.getImageManager()->prune() will delete binary image data files from archive as needed. + + // Return true if all critical operations succeeded + return xmlRemoved && relsRemoved; + } + + /** + * @brief Remove all images from the worksheet + * @return Number of images removed + */ + void XLWorksheet::clearImages() + { + const std::vector embImgs = getEmbImages(); + std::vector::const_iterator embImgItr = embImgs.begin(); + for( ; embImgs.end() != embImgItr; ++embImgItr ){ + const XLEmbeddedImage& embImg = *embImgItr; + const std::string& relationshipId = embImg.relationshipId(); + + // Remove from DrawingML XML (in-memory XML document) + removeImageFromDrawingXML(relationshipId); + + // Remove from relationships file (in-memory XML document) + removeImageFromRelationships(relationshipId); + } + + getEmbImages().clear(); + + // After clearing all images, remove the element from worksheet XML + removeDrawingElementIfEmpty(); + } + + /** + * @brief Remove the element from worksheet XML if there are no more images + * @note This is a helper function called after removing images to clean up the XML + */ + void XLWorksheet::removeDrawingElementIfEmpty() + { + // Only remove if there are no images left and the drawing element exists + if (getEmbImages().empty() && hasImagesInXML()) { + XMLNode docElement = xmlDocument().document_element(); + XMLNode drawingNode = docElement.child("drawing"); + if (!drawingNode.empty()) { + // Get the relationship ID from the drawing element + XMLAttribute rIdAttr = drawingNode.attribute("r:id"); + if (!rIdAttr.empty()) { + std::string drawingRelId = rIdAttr.value(); + // Remove the relationship from worksheet relationships + try { + relationships().deleteRelationship(drawingRelId); + } catch (...) { + // Relationship might not exist, ignore + } + } + // Remove the element from worksheet XML + docElement.remove_child(drawingNode); + + // Invalidate m_drawingML since the drawing element no longer exists + m_drawingML = XLDrawingML(nullptr); + } + } + } + + //---------------------------------------------------------------------------------------------------------------------- + // Image Registry Management Implementation + //---------------------------------------------------------------------------------------------------------------------- + + /** + * @brief Read the drawing relationships file to map relationship IDs to image files + * @param relationshipMap Output map of relationship ID to image file path + */ + void XLWorksheet::readDrawingRelationships(std::map& relationshipMap) const + { + try { + // Get the drawing filename + std::string drawingFilename = getDrawingFilename(); + std::string relsFilename = getDrawingRelsFilename(); + + // Check if relationships file exists + if (!parentDoc().hasRelationshipsFile(relsFilename)) { + return; // No relationships file + } + + // Alternate implementation using XLXmlData access + // Get relationships XML data from in-memory cache + XLXmlData* relsXmlData = const_cast(parentDoc()).getRelationshipsXmlData(relsFilename); + if (!relsXmlData || !relsXmlData->valid()) { + return; // No relationships data available + } + + // Parse the relationships XML from in-memory data + XMLDocument* relsDoc = relsXmlData->getXmlDocument(); + if (!relsDoc || !relsDoc->document_element()) { + return; // Invalid XML data + } + + // Extract relationship mappings + int relationshipCount = 0; + for (auto rel : relsDoc->document_element().children("Relationship")) { + std::string id = rel.attribute("Id").value(); + std::string target = rel.attribute("Target").value(); + std::string type = rel.attribute("Type").value(); + + // Only process image relationships + if (type == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image") { + relationshipMap[id] = target; + relationshipCount++; + } + } + } + catch (const std::exception& ) { + // If reading relationships fails, leave map empty + } + } + + /** + * @brief Populate m_embImages vector from existing XML data + */ + void XLWorksheet::populateImagesFromXML() + { + // Only populate if m_embImages is empty to avoid duplicates + if (!getEmbImages().empty()) { + return; + } + + // Check if DrawingML is valid (was initialized because images exist) + if (!m_drawingML.valid()) { + return; // No images to populate + } + + try { + // Use standard sequential numbering approach (consistent with rest of codebase) + std::string drawingFilename = getDrawingFilename(); + // Get the existing XLXmlData object for the drawing file + // This is the proper way - use the existing XML data structure rather than bypassing it + XLXmlData* xmlData = const_cast(parentDoc()).getDrawingXmlData(drawingFilename); + if (!xmlData) { + return; + } + + XMLNode wsDr; + std::string rootName; + // Get the underlying XMLDocument (pugi::xml_document) + XMLDocument& xmlDoc = *xmlData->getXmlDocument(); + if (!xmlDoc.document_element()) { + return; + } + + // Get the root element (xdr:wsDr) + wsDr = xmlDoc.document_element(); + rootName = wsDr.name(); + + // Check for both "wsDr" and "xdr:wsDr" (with namespace prefix) + if (rootName != "wsDr" && rootName != "xdr:wsDr") { + return; // Invalid root element + } + + // Read the relationships file to get image mappings + std::map relationshipMap; + readDrawingRelationships(relationshipMap); + + // Process each anchor element directly + for (auto anchor : wsDr.children()) { + std::string anchorName = anchor.name(); + // Check anchor type using enum conversion + XLImageAnchorType anchorType = XLImageAnchorUtils::stringToAnchorType(anchorName); + if (anchorType != XLImageAnchorType::Unknown) { + // Parse anchor using XLDrawingML utility function + XLImageAnchor anchorInfo; + if (XLDrawingML::parseXMLNode(anchor, &anchorInfo)) { + // Process this anchor directly into XLEmbeddedImage + processAnchorToEmbeddedImage(anchorInfo, relationshipMap); + } + } + } + } + catch (const std::exception&) { + // If any error occurs, just return without populating + return; + } + } + + /** + * @brief Process a single XLImageAnchor into an XLEmbeddedImage and add to m_embImages + * @param anchorInfo The parsed anchor information + * @param relationshipMap Map of relationship IDs to image files + */ + void XLWorksheet::processAnchorToEmbeddedImage(const XLImageAnchor& anchorInfo, const std::map& relationshipMap) + { + try { + // Find the image file path using the relationship ID + auto it = relationshipMap.find(anchorInfo.relationshipId); + if (it == relationshipMap.end()) { + return; // Skip if relationship not found + } + + std::string packagePath = it->second; + + // Convert relative path to absolute path if needed + packagePath = getAbsoluteImagePath(packagePath); + + // Load binary data from archive + std::string binaryData = parentDoc().readFile(packagePath); + if (binaryData.empty()) { + return; // Skip if binary data not found + } + + // Convert string to vector + std::vector imageData(binaryData.begin(), binaryData.end()); + + // Determine MIME type from file extension ot imageData + std::string extension = packagePath.substr(packagePath.find_last_of('.')); + XLMimeType mimeType = XLImageUtils::extensionToMimeType(extension); + if (XLMimeType::Unknown == mimeType) { + mimeType = XLImageUtils::detectMimeType(imageData); + } + + // Convert enum back to XLContentType for findOrAddImage + XLContentType contentType = XLImageUtils::mimeTypeToContentType(mimeType); + + // Use XLImageManager to find or add the image with existing package path + XLImageShPtr image = parentDoc().getImageManager().findOrAddImage(packagePath, + imageData, contentType); + + if (!image.get()) { + return; + } + + // Add to embedded image register + XLEmbeddedImage embImage; + embImage.setImage(image); + embImage.setImageAnchor( anchorInfo ); + getEmbImages().push_back(embImage); + } + catch (const std::exception&) { + // If creating XLEmbeddedImage fails, skip this entry + // This ensures we don't crash on malformed data + } + } + + /** + * @brief Extract image ID from XML pic element + * @param pic The pic XML node containing the image information + * @return The image ID from the cNvPr id attribute + */ + std::string XLWorksheet::extractImageIdFromXML(const pugi::xml_node& pic) const + { + try { + // Navigate to cNvPr element: pic -> nvPicPr -> cNvPr + auto nvPicPr = pic.child("xdr:nvPicPr"); + if (nvPicPr.empty()) { + nvPicPr = pic.child("nvPicPr"); // Try without namespace + } + + if (!nvPicPr.empty()) { + auto cNvPr = nvPicPr.child("xdr:cNvPr"); + if (cNvPr.empty()) { + cNvPr = nvPicPr.child("cNvPr"); // Try without namespace + } + + if (!cNvPr.empty()) { + auto idAttr = cNvPr.attribute("id"); + if (!idAttr.empty()) { + std::string imageId = idAttr.value(); + return imageId; + } + } + } + } catch (const std::exception&) { + // If XML parsing fails, fall back to empty string + } + + return ""; // Fallback to empty string + } + + /** + * @brief Convert EMUs to pixels (approximate conversion) + * @param emus EMUs value + * @return Approximate pixel value + */ + uint32_t XLWorksheet::emusToPixels(uint32_t emus) const + { + // Standard conversion: 1 inch = 914400 EMUs, 1 inch = 96 pixels (at 96 DPI) + // So: 1 pixel = 914400 / 96 = 9525 EMUs + return static_cast(emus / 9525); + } + + /** + * @brief Validate image registry for data integrity + * @return True if registry is valid (no duplicate IDs), false otherwise + */ + bool XLWorksheet::validateImageRegistry() const + { + std::set imageIds; + std::set relationshipIds; + + for (const auto& embImage : getEmbImages()) { + // Check for duplicate image IDs + if (!embImage.id().empty()) { + if (imageIds.find(embImage.id()) != imageIds.end()) { + return false; // Duplicate image ID found + } + imageIds.insert(embImage.id()); + } + + // Check for duplicate relationship IDs + if (!embImage.relationshipId().empty()) { + if (relationshipIds.find(embImage.relationshipId()) != relationshipIds.end()) { + return false; // Duplicate relationship ID found + } + relationshipIds.insert(embImage.relationshipId()); + } + } + + return true; // No duplicates found + } + + /** + * @brief Check if an image ID already exists + * @param imageId The image ID to check + * @return True if the ID exists, false otherwise + */ + bool XLWorksheet::hasEmbImageWithId(const std::string& imageId) const + { + return std::any_of(getEmbImages().begin(), getEmbImages().end(), + [&imageId](const XLEmbeddedImage& img) { return img.id() == imageId; }); + } + + /** + * @brief Check if a relationship ID already exists + * @param relationshipId The relationship ID to check + * @return True if the ID exists, false otherwise + */ + bool XLWorksheet::hasImageWithRelationshipId(const std::string& relationshipId) const + { + return std::any_of(getEmbImages().begin(), getEmbImages().end(), + [&relationshipId](const XLEmbeddedImage& img) { return img.relationshipId() == relationshipId; }); + } + + /** + * @brief Get image path from relationship ID + * @param relationshipId The relationship ID to look up + * @return The image path, or empty string if not found + */ + std::string XLWorksheet::getImagePathFromRelationship(const std::string& relationshipId) const + { + try { + std::string relsFilename = getDrawingRelsFilename(); + + if (!parentDoc().hasRelationshipsFile(relsFilename)) { + return ""; + } + // Alternate implementation using XLXmlData access + // Get relationships XML data from in-memory cache + XLXmlData* relsXmlData = const_cast(parentDoc()).getRelationshipsXmlData(relsFilename); + if (!relsXmlData || !relsXmlData->valid()) { + return ""; + } + + // Parse the relationships XML from in-memory data + XMLDocument* relsDoc = relsXmlData->getXmlDocument(); + if (!relsDoc || !relsDoc->document_element()) { + return ""; + } + + // Find the relationship with the specified ID + for (auto rel : relsDoc->document_element().children("Relationship")) { + if (rel.attribute("Id").value() == relationshipId) { + return rel.attribute("Target").value(); + } + } + + return ""; + } + catch (const std::exception&) { + return ""; + } + } + +/** + * @brief Add an embedded image, assuming image is fully created and imageAnchor has all user-specified data + * @details This function generates unique IDs, completes the anchor initialization, and adds the image to the worksheet + * @param imageAnchor Anchor configuration (may be partially initialized) + * @param image Shared pointer to the XLImage object + * @return Image ID string + */ +std::string XLWorksheet::embedImage(const XLImageAnchor& imageAnchor, XLImageShPtr image) +{ + std::string imageId; + if (image.get()) { + // Get unique IDs + imageId = generateNextImageId(); + const std::string relationshipId = getNextUnusedRelationshipId(); + + // Fully initialize image anchor + XLImageAnchor anchor = imageAnchor; + anchor.imageId = imageId; + anchor.relationshipId = relationshipId; + if (XLImageAnchorType::Unknown == anchor.anchorType) { + anchor.anchorType = ((0 == anchor.toCol) && (0 == anchor.toRow)) ? + XLImageAnchorType::OneCellAnchor : XLImageAnchorType::TwoCellAnchor; + } + if ((0 == anchor.displayWidthEMUs) || (0 == anchor.displayHeightEMUs)) { + const uint32_t widthPixels = image->widthPixels(); + const uint32_t heightPixels = image->heightPixels(); + anchor.displayWidthEMUs = XLImageUtils::pixelsToEmus(widthPixels); + anchor.displayHeightEMUs = XLImageUtils::pixelsToEmus(heightPixels); + } + if ((0 == anchor.actualWidthEMUs) || (0 == anchor.actualHeightEMUs)) { + anchor.actualWidthEMUs = anchor.displayWidthEMUs; + anchor.actualHeightEMUs = anchor.displayHeightEMUs; + } + + // Construct embedded image + XLEmbeddedImage embImage; + embImage.setImageAnchor(anchor); + embImage.setImage(image); + + // Add anchor to drawing file XML (drawing3.xml) + if (!m_drawingML.valid()) { + createEmptyDrawingML(); + } + m_drawingML.addImage(embImage); + + // Add relationship to relationship file XML (drawing3.xml.rels) + addImageRelationship(embImage); + + // Add to register + getEmbImages().push_back(embImage); + } + return imageId; +} + + /** + * @brief Remove image node from DrawingML XML (in-memory document) + * @param relationshipId The relationship ID of the image to remove + * @return True if the image node was found and removed + * + * @note This function modifies the existing XLXmlData object for the drawing file. + * The changes are automatically persisted when the document is saved. + */ + bool XLWorksheet::removeImageFromDrawingXML(const std::string& relationshipId) const + { + try { + // Use standard sequential numbering approach (consistent with rest of codebase) + std::string drawingFilename = getDrawingFilename(); + // Get the existing XLXmlData object for the drawing file + // This is the proper way - use the existing XML data structure rather than bypassing it + XLXmlData* xmlData = const_cast(parentDoc()).getDrawingXmlData(drawingFilename); + if (!xmlData) { + return false; + } + + // Get the underlying XMLDocument (pugi::xml_document) + XMLDocument& xmlDoc = *xmlData->getXmlDocument(); + if (!xmlDoc.document_element()) { + return false; + } + + // Get the root element (xdr:wsDr) + auto wsDr = xmlDoc.document_element(); + std::string rootName = wsDr.name(); + + // Check for both "wsDr" and "xdr:wsDr" (with namespace prefix) + if (rootName != "wsDr" && rootName != "xdr:wsDr") { + return false; + } + + // Delegate to XLDrawingML utility function + return XLDrawingML::deleteXMLNode(wsDr, relationshipId); + } + catch (const std::exception&) { + return false; + } + } + + /** + * @brief Remove image relationship from relationships file (in-memory document) + * @param relationshipId The relationship ID to remove + * @return True if the relationship was found and removed + */ + bool XLWorksheet::removeImageFromRelationships(const std::string& relationshipId) const + { + try { + // Use standard sequential numbering approach (consistent with rest of codebase) + std::string relsFilename = getDrawingRelsFilename(); + + // Check if relationships file exists + if (!parentDoc().hasRelationshipsFile(relsFilename)) { + return false; + } + + // Alternate implementation using XLXmlData access + // Get relationships XML data from in-memory cache + XLXmlData* relsXmlData = const_cast(parentDoc()).getRelationshipsXmlData(relsFilename); + if (!relsXmlData || !relsXmlData->valid()) { + return false; + } + + // Parse the relationships XML from in-memory data + XMLDocument* relsDoc = relsXmlData->getXmlDocument(); + if (!relsDoc || !relsDoc->document_element()) { + return false; + } + + // Find and remove the relationship + bool found = false; + for (auto rel : relsDoc->document_element().children("Relationship")) { + std::string relId = rel.attribute("Id").value(); + if (relId == relationshipId) { + relsDoc->document_element().remove_child(rel); + found = true; + break; + } + } + + if (found) { + // Persist the changes to XLXmlData using setRawData() + // This avoids archive access and lets XLXmlData handle persistence + std::ostringstream oss; + relsDoc->save(oss); + relsXmlData->setRawData(oss.str()); + return true; + } + + return false; // Relationship not found + } + catch (const std::exception&) { + return false; + } + } + + /** + * @brief Check if a binary file is referenced by any relationships in this worksheet + * @param imagePath The relative image path (e.g., "../media/image_3.gif") + * @return True if the file is referenced by at least one relationship in this worksheet +* TODO: imagePath should be imagePackagePath + */ + bool XLWorksheet::isBinaryFileReferenced(const std::string& imagePath) const + { + try { + // Get the drawing relationships filename + std::string relsFilename = getDrawingRelsFilename(); + + // Check if relationships file exists in archive + if (!parentDoc().hasRelationshipsFile(relsFilename)) { + return false; // No relationships file + } + + // Alternate implementation using XLXmlData access + // Get relationships XML data from in-memory cache + XLXmlData* relsXmlData = const_cast(parentDoc()).getRelationshipsXmlData(relsFilename); + if (!relsXmlData || !relsXmlData->valid()) { + return false; // No relationships data available + } + + // Parse the relationships XML from in-memory data + XMLDocument* relsDoc = relsXmlData->getXmlDocument(); + if (!relsDoc || !relsDoc->document_element()) { + return false; // Invalid XML data + } + + // Check if any relationship references the image path + for (auto rel : relsDoc->document_element().children("Relationship")) { + std::string target = rel.attribute("Target").value(); + if (target == imagePath) { + return true; // Found at least one reference + } + } + + return false; // No references found + + } catch (const std::exception&) { + // If any error occurs, assume no references to be safe + return false; + } + } + +} // namespace OpenXLSX diff --git a/OpenXLSX/sources/XLXmlData.cpp b/OpenXLSX/sources/XLXmlData.cpp index 11dcf704..f40440dc 100644 --- a/OpenXLSX/sources/XLXmlData.cpp +++ b/OpenXLSX/sources/XLXmlData.cpp @@ -46,6 +46,9 @@ YM M9 MM MM MM MM MM d' `MM. MM MM d' `MM. // ===== External Includes ===== // #include #include +#include +#include +#include // ===== OpenXLSX Includes ===== // #include "XLDocument.hpp" @@ -181,3 +184,232 @@ const XMLDocument* XLXmlData::getXmlDocument() const return m_xmlDoc.get(); } + +int XLXmlData::verifyData(std::string* dbgMsg) const +{ + int errorCount = 0; + + // Call verifyUniqueXMLRecords to check for duplicates + errorCount += verifyUniqueXMLRecords(dbgMsg); + + return errorCount; +} + +int XLXmlData::verifyUniqueXMLRecords(std::string* dbgMsg) const +{ + int errorCount = 0; + + // Get the XML document and call recursive verification on root node + const XMLDocument* xmlDoc = getXmlDocument(); + if (xmlDoc != nullptr) { + errorCount += verifyUniqueXMLRecordsRecursive(xmlDoc->document_element(), dbgMsg); + } + + return errorCount; +} + +/* +pugi::xml_node::operator<() only compares pointers. This function compares data. +*/ +int XLXmlData::compareXMLNode(const pugi::xml_node& x, const pugi::xml_node& y) +{ + // Handle empty nodes + if (x.empty() && !y.empty()) return -1; + if (y.empty() && !x.empty()) return 1; + if (x.empty() && y.empty()) return 0; + + // Compare node types + if (x.type() != y.type()) { + return (x.type() < y.type()) ? -1 : 1; + } + + // Compare names (handle null pointers) + const char* xName = x.name(); + const char* yName = y.name(); + + if (xName == nullptr && yName != nullptr) return -1; + if (yName == nullptr && xName != nullptr) return 1; + if (xName == nullptr && yName == nullptr) { + // Both null, continue to value comparison + } else { + int nameCompare = std::strcmp(xName, yName); + if (nameCompare != 0) return nameCompare; + } + + // Compare values + const char* xValue = x.value(); + const char* yValue = y.value(); + + if (xValue == nullptr && yValue != nullptr) return -1; + if (yValue == nullptr && xValue != nullptr) return 1; + if (xValue == nullptr && yValue == nullptr) { + // Both null, continue to attribute comparison + } else { + int valueCompare = std::strcmp(xValue, yValue); + if (valueCompare != 0) return valueCompare; + } + + // Compare attributes (this is crucial for distinguishing nodes) + auto xAttr = x.attributes_begin(); + auto yAttr = y.attributes_begin(); + auto xAttrEnd = x.attributes_end(); + auto yAttrEnd = y.attributes_end(); + + while (xAttr != xAttrEnd && yAttr != yAttrEnd) { + // Compare attribute names + int attrNameCompare = std::strcmp(xAttr->name(), yAttr->name()); + if (attrNameCompare != 0) return attrNameCompare; + + // Compare attribute values + int attrValueCompare = std::strcmp(xAttr->value(), yAttr->value()); + if (attrValueCompare != 0) return attrValueCompare; + + ++xAttr; + ++yAttr; + } + + // If one has more attributes than the other + if (xAttr != xAttrEnd) return 1; // x has more attributes + if (yAttr != yAttrEnd) return -1; // y has more attributes + + return 0; // All comparisons equal +} + +bool XLXmlData::lessXMLNode(const pugi::xml_node& x, const pugi::xml_node& y) +{ + const int compareResult = compareXMLNode(x, y); + return (compareResult < 0); +} + + +int XLXmlData::verifyUniqueXMLRecordsRecursive(const pugi::xml_node& rootNode, std::string* dbgMsg) +{ + int errorCount = 0; + + std::stack nodeStack; + if( !rootNode.empty() ){ + nodeStack.push(rootNode); + } + std::vector children; // Declare outside the loop + + while (!nodeStack.empty()) { + const pugi::xml_node currentNode = nodeStack.top(); + nodeStack.pop(); + + // Clear and collect children using first_child() and next_sibling() + // Only check element nodes for duplicate detection - ignore formatting nodes + children.resize(0); + for (pugi::xml_node child = currentNode.first_child(); !child.empty(); child = child.next_sibling()) { + // Only process element nodes (node_element) - these contain actual data + // Only process leaf nodes + if ((child.type() == pugi::node_element) && (child.first_child().empty()) ) { + children.push_back(child); + } + } + + // Sort children using lessXMLNode comparison function + std::sort(children.begin(), children.end(), &XLXmlData::lessXMLNode); + + // Iterate sorted children and detect duplicates + for (size_t i = 1; i < children.size(); ++i) { + if (compareXMLNode(children[i-1], children[i]) == 0) { + // Found duplicate nodes + errorCount++; + + // Count how many adjacent children are the same + size_t duplicateCount = 1; + while (i + duplicateCount < children.size() && + compareXMLNode(children[i-1], children[i + duplicateCount]) == 0) { + ++duplicateCount; + } + + if (dbgMsg != nullptr) { + // Create deluxe debug message with comprehensive node information + *dbgMsg += "=== DUPLICATE XML NODES DETECTED ===\n"; + + // Show root node information + *dbgMsg += "Root Node: "; + if (rootNode.empty()) { + *dbgMsg += "[EMPTY ROOT]\n"; + } else { + *dbgMsg += "Type=" + std::to_string(static_cast(rootNode.type())) + + ", Name='" + (rootNode.name() ? rootNode.name() : "[NULL]") + "'" + + ", Value='" + (rootNode.value() ? rootNode.value() : "[NULL]") + "'"; + + // Show root node attributes if any + if (!rootNode.attributes().empty()) { + *dbgMsg += ", Attributes: "; + bool firstAttr = true; + for (const pugi::xml_attribute& attr : rootNode.attributes()) { + std::string attrName = (attr.name()) ? attr.name() : ""; + std::string attrValue = (attr.value()) ? attr.value() : ""; + if (!firstAttr) *dbgMsg += ", "; + *dbgMsg += attrName + "='" + attrValue + "'"; + firstAttr = false; + } + } + *dbgMsg += "\n"; + } + + *dbgMsg += "Parent Node: "; + if (currentNode.empty()) { + *dbgMsg += "[ROOT/DOCUMENT]\n"; + } else { + *dbgMsg += "Type=" + std::to_string(static_cast(currentNode.type())) + + ", Name='" + (currentNode.name() ? currentNode.name() : "[NULL]") + "'" + + ", Value='" + (currentNode.value() ? currentNode.value() : "[NULL]") + "'"; + + // Show parent node attributes if any + if (!currentNode.attributes().empty()) { + *dbgMsg += ", Attributes: "; + bool firstAttr = true; + for (const pugi::xml_attribute& attr : currentNode.attributes()) { + std::string attrName = (attr.name()) ? attr.name() : ""; + std::string attrValue = (attr.value()) ? attr.value() : ""; + if (!firstAttr) *dbgMsg += ", "; + *dbgMsg += attrName + "='" + attrValue + "'"; + firstAttr = false; + } + } + *dbgMsg += "\n"; + } + + *dbgMsg += "Duplicate Count: " + std::to_string(duplicateCount + 1) + " identical nodes\n"; + *dbgMsg += "Duplicate Node Details:\n"; + + // Show details of the first duplicate node + const pugi::xml_node& duplicateNode = children[i-1]; + *dbgMsg += " - Type=" + std::to_string(static_cast(duplicateNode.type())) + + ", Name='" + (duplicateNode.name() ? duplicateNode.name() : "[NULL]") + "'" + + ", Value='" + (duplicateNode.value() ? duplicateNode.value() : "[NULL]") + "'\n"; + + // Show attributes if any + if (!duplicateNode.attributes().empty()) { + *dbgMsg += " - Attributes: "; + bool firstAttr = true; + for (const pugi::xml_attribute& attr : duplicateNode.attributes()) { + std::string attrName = (attr.name()) ? attr.name() : ""; + std::string attrValue = (attr.value()) ? attr.value() : ""; + if (!firstAttr) *dbgMsg += ", "; + *dbgMsg += attrName + "='" + attrValue + "'"; + firstAttr = false; + } + *dbgMsg += "\n"; + } + + *dbgMsg += "=====================================\n"; + } + + // Skip past all the duplicates + i += duplicateCount - 1; + } + } + + // Push children onto the stack for further processing + for (const pugi::xml_node& child : children) { + nodeStack.push(child); + } + } + + return errorCount; +} diff --git a/OpenXLSX/sources/XLXmlFile.cpp b/OpenXLSX/sources/XLXmlFile.cpp index 5b2d6575..cf279c6b 100644 --- a/OpenXLSX/sources/XLXmlFile.cpp +++ b/OpenXLSX/sources/XLXmlFile.cpp @@ -130,3 +130,15 @@ std::string XLXmlFile::getXmlPath() const { return m_xmlData == nullptr ? "" : m_xmlData->getXmlPath(); } + +int XLXmlFile::verifyData(std::string* dbgMsg) const +{ + int errorCount = 0; + + // Call m_xmlData->verifyData() if m_xmlData is not null + if (m_xmlData != nullptr) { + errorCount += m_xmlData->verifyData(dbgMsg); + } + + return errorCount; +} diff --git a/OpenXLSX/sources/utilities/XLUtilities.hpp b/OpenXLSX/sources/utilities/XLUtilities.hpp index 8c4a134d..7a54c821 100644 --- a/OpenXLSX/sources/utilities/XLUtilities.hpp +++ b/OpenXLSX/sources/utilities/XLUtilities.hpp @@ -102,6 +102,10 @@ namespace OpenXLSX case XLContentType::VMLDrawing: return "VMLDrawing"; case XLContentType::Comments: return "Comments"; case XLContentType::Table: return "Table"; + case XLContentType::ImagePNG: return "ImagePNG"; + case XLContentType::ImageJPEG: return "ImageJPEG"; + case XLContentType::ImageBMP: return "ImageBMP"; + case XLContentType::ImageGIF: return "ImageGIF"; case XLContentType::Unknown: return "Unknown"; } return "invalid"; diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index b49767fe..2666810f 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -26,6 +26,7 @@ target_sources(OpenXLSXTests testXLColor.cpp testXLDateTime.cpp testXLFormula.cpp + testXLImage.cpp testXLRow.cpp testXLSheet.cpp ) diff --git a/Tests/testXLCellValue.cpp b/Tests/testXLCellValue.cpp index 6ecc839f..f0014001 100644 --- a/Tests/testXLCellValue.cpp +++ b/Tests/testXLCellValue.cpp @@ -185,7 +185,7 @@ TEST_CASE("XLCellValue Tests", "[XLCellValue]") XLCellValue value; XLDocument doc; doc.create("./testXLCellValue.xlsx"); - XLWorksheet wks = doc.workbook().sheet(1); + XLWorksheet wks = doc.workbook().worksheet(1); wks.cell("A1").value() = "Hello OpenXLSX!"; value = wks.cell("A1").value(); diff --git a/Tests/testXLImage.cpp b/Tests/testXLImage.cpp new file mode 100644 index 00000000..10cd17d7 --- /dev/null +++ b/Tests/testXLImage.cpp @@ -0,0 +1,908 @@ +// +// Created by OpenXLSX Image Test Suite +// + +#include +#include +#include +#include +#include +#include +#include + +using namespace OpenXLSX; + +TEST_CASE("XLEmbeddedImage", "[XLEmbeddedImage]") +{ + SECTION("Basic Image Embedding Test") { + + // Create a new workbook + XLDocument doc; + doc.create("./testXLEmbeddedImage.xlsx"); + + // Get the first worksheet and rename it + XLWorksheet worksheet1 = doc.workbook().worksheet(1); + worksheet1.setName("Images_Sheet1"); + REQUIRE(worksheet1.name() == "Images_Sheet1"); + + // Add a second worksheet + doc.workbook().addWorksheet("Images_Sheet2"); + XLWorksheet worksheet2 = doc.workbook().worksheet("Images_Sheet2"); + REQUIRE(worksheet2.name() == "Images_Sheet2"); + + // Add some images to the first worksheet + try { + // Use shared tiny PNG image data from XLImageUtils + const std::string imageId1 = worksheet1.embedImageFromImageData( + XLCellReference(2,2), XLImageUtils::red1x1PNGData, XLMimeType::PNG ); + const std::string imageId2 = worksheet1.embedImageFromImageData( + XLCellReference(5,3), XLImageUtils::red1x1PNGData, XLMimeType::PNG ); + const std::string imageId3 = worksheet2.embedImageFromImageData( + XLCellReference(1,1), XLImageUtils::red1x1PNGData, XLMimeType::PNG ); + REQUIRE(!imageId1.empty()); + REQUIRE(!imageId3.empty()); + REQUIRE(!imageId3.empty()); + + // Verify the document data integrity + std::string dbgMsg; + int result = doc.verifyData(&dbgMsg); + REQUIRE(result == EXIT_SUCCESS); + + // Basic verification that images were added + REQUIRE(worksheet1.imageCount() > 0); + REQUIRE(worksheet2.imageCount() > 0); + + } catch (const std::exception& e) { + FAIL("Exception during image embedding: " << e.what()); + } + + // Save and close the document + doc.save(); + doc.close(); + + // Clean up + std::remove("./testXLEmbeddedImage.xlsx"); + } + + // ----------------------------------------------------------------- + // Additional sections consolidated under the XLEmbeddedImage test + // ----------------------------------------------------------------- + + SECTION("XLImageUtils - brief test") { + // Detect and validate + const XLMimeType mt = XLImageUtils::detectMimeType(XLImageUtils::red1x1PNGData); + REQUIRE(mt == XLMimeType::PNG); + REQUIRE(XLImageUtils::validateImageData(XLImageUtils::red1x1PNGData, mt)); + + // MIME/content-type round-trip + const XLContentType ct = XLImageUtils::mimeTypeToContentType(mt); + const XLMimeType mt2 = XLImageUtils::contentTypeToMimeType(ct); + REQUIRE(mt2 == XLMimeType::PNG); + + // Extension mapping + REQUIRE(XLImageUtils::extensionToMimeType(".png") == XLMimeType::PNG); + REQUIRE(XLImageUtils::mimeTypeToExtension(XLMimeType::PNG) == ".png"); + + // EMU conversion sanity + const uint32_t px = 31; + const uint32_t emu = XLImageUtils::pixelsToEmus(px); + REQUIRE(emu > 0u); + REQUIRE(XLImageUtils::emusToPixels(emu) >= 1u); + } + + SECTION("XLImageUtils - full test") { + // Test data - using pointers to avoid vector of references + struct TestImage { + const std::vector* data; + XLMimeType mimeType; + }; + const std::vector testImages = { + {&XLImageUtils::red1x1PNGData, XLMimeType::PNG}, + {&XLImageUtils::png31x15Data, XLMimeType::PNG}, + {&XLImageUtils::jpeg32x18Data, XLMimeType::JPEG}, + {&XLImageUtils::bmp33x16Data, XLMimeType::BMP}, + {&XLImageUtils::gif26x16Data, XLMimeType::GIF} + }; + + // Test detectMimeType() - Convert each test image data vector to mimetype + REQUIRE(XLImageUtils::detectMimeType(XLImageUtils::red1x1PNGData) == XLMimeType::PNG); + REQUIRE(XLImageUtils::detectMimeType(XLImageUtils::png31x15Data) == XLMimeType::PNG); + REQUIRE(XLImageUtils::detectMimeType(XLImageUtils::jpeg32x18Data) == XLMimeType::JPEG); + REQUIRE(XLImageUtils::detectMimeType(XLImageUtils::bmp33x16Data) == XLMimeType::BMP); + REQUIRE(XLImageUtils::detectMimeType(XLImageUtils::gif26x16Data) == XLMimeType::GIF); + + // Test with invalid data + const std::vector invalidData = {0x00, 0x01, 0x02, 0x03}; + REQUIRE(XLImageUtils::detectMimeType(invalidData) == XLMimeType::Unknown); + + // Test mimeTypeToString() and stringToMimeType() - Convert each to and from MIME type string + REQUIRE(XLImageUtils::mimeTypeToString(XLMimeType::PNG) == "image/png"); + REQUIRE(XLImageUtils::mimeTypeToString(XLMimeType::JPEG) == "image/jpeg"); + REQUIRE(XLImageUtils::mimeTypeToString(XLMimeType::BMP) == "image/bmp"); + REQUIRE(XLImageUtils::mimeTypeToString(XLMimeType::GIF) == "image/gif"); + REQUIRE(XLImageUtils::mimeTypeToString(XLMimeType::Unknown) == ""); + + REQUIRE(XLImageUtils::stringToMimeType("image/png") == XLMimeType::PNG); + REQUIRE(XLImageUtils::stringToMimeType("image/jpeg") == XLMimeType::JPEG); + REQUIRE(XLImageUtils::stringToMimeType("image/bmp") == XLMimeType::BMP); + REQUIRE(XLImageUtils::stringToMimeType("image/gif") == XLMimeType::GIF); + + // Round-trip tests + for (const auto& img : testImages) { + const std::string mimeStr = XLImageUtils::mimeTypeToString(img.mimeType); + REQUIRE(!mimeStr.empty()); + REQUIRE(XLImageUtils::stringToMimeType(mimeStr) == img.mimeType); + } + + // Convert random garbage MIME type string to mime type + REQUIRE(XLImageUtils::stringToMimeType("garbage/string") == XLMimeType::Unknown); + REQUIRE(XLImageUtils::stringToMimeType("") == XLMimeType::Unknown); + REQUIRE(XLImageUtils::stringToMimeType("image/unknown") == XLMimeType::Unknown); + REQUIRE(XLImageUtils::stringToMimeType("text/plain") == XLMimeType::Unknown); + + // Test mimeTypeToExtension() and extensionToMimeType() - Convert each to and from file extension + REQUIRE(XLImageUtils::mimeTypeToExtension(XLMimeType::PNG) == ".png"); + REQUIRE(XLImageUtils::mimeTypeToExtension(XLMimeType::JPEG) == ".jpg"); + REQUIRE(XLImageUtils::mimeTypeToExtension(XLMimeType::BMP) == ".bmp"); + REQUIRE(XLImageUtils::mimeTypeToExtension(XLMimeType::GIF) == ".gif"); + REQUIRE(XLImageUtils::mimeTypeToExtension(XLMimeType::Unknown) == ""); + + REQUIRE(XLImageUtils::extensionToMimeType(".png") == XLMimeType::PNG); + REQUIRE(XLImageUtils::extensionToMimeType(".jpg") == XLMimeType::JPEG); + REQUIRE(XLImageUtils::extensionToMimeType(".jpeg") == XLMimeType::JPEG); + REQUIRE(XLImageUtils::extensionToMimeType(".bmp") == XLMimeType::BMP); + REQUIRE(XLImageUtils::extensionToMimeType(".gif") == XLMimeType::GIF); + + // Round-trip tests + for (const auto& img : testImages) { + const std::string ext = XLImageUtils::mimeTypeToExtension(img.mimeType); + REQUIRE(!ext.empty()); + REQUIRE(XLImageUtils::extensionToMimeType(ext) == img.mimeType); + } + + // Convert random garbage extension to mime type + REQUIRE(XLImageUtils::extensionToMimeType(".xyz") == XLMimeType::Unknown); + REQUIRE(XLImageUtils::extensionToMimeType(".txt") == XLMimeType::Unknown); + REQUIRE(XLImageUtils::extensionToMimeType("") == XLMimeType::Unknown); + REQUIRE(XLImageUtils::extensionToMimeType("noextension") == XLMimeType::Unknown); + REQUIRE(XLImageUtils::extensionToMimeType("png") == XLMimeType::PNG); // without dot should still work + + // Test mimeTypeToContentType() and contentTypeToMimeType() - Convert each to and from XLContentType + REQUIRE(XLImageUtils::mimeTypeToContentType(XLMimeType::PNG) == XLContentType::ImagePNG); + REQUIRE(XLImageUtils::mimeTypeToContentType(XLMimeType::JPEG) == XLContentType::ImageJPEG); + REQUIRE(XLImageUtils::mimeTypeToContentType(XLMimeType::BMP) == XLContentType::ImageBMP); + REQUIRE(XLImageUtils::mimeTypeToContentType(XLMimeType::GIF) == XLContentType::ImageGIF); + REQUIRE(XLImageUtils::mimeTypeToContentType(XLMimeType::Unknown) == XLContentType::Unknown); + + REQUIRE(XLImageUtils::contentTypeToMimeType(XLContentType::ImagePNG) == XLMimeType::PNG); + REQUIRE(XLImageUtils::contentTypeToMimeType(XLContentType::ImageJPEG) == XLMimeType::JPEG); + REQUIRE(XLImageUtils::contentTypeToMimeType(XLContentType::ImageBMP) == XLMimeType::BMP); + REQUIRE(XLImageUtils::contentTypeToMimeType(XLContentType::ImageGIF) == XLMimeType::GIF); + REQUIRE(XLImageUtils::contentTypeToMimeType(XLContentType::Unknown) == XLMimeType::Unknown); + + // Round-trip tests + for (const auto& img : testImages) { + const XLContentType ct = XLImageUtils::mimeTypeToContentType(img.mimeType); + REQUIRE(ct != XLContentType::Unknown); + REQUIRE(XLImageUtils::contentTypeToMimeType(ct) == img.mimeType); + } + + // Test pixelsToEmus() and emusToPixels() - Use Catch RNG to pick random pixel value, convert back and forth + auto& rng = Catch::rng(); + std::uniform_int_distribution pixelDist(1, 1000); + + for (int i = 0; i < 10; ++i) { + // Generate random pixel value between 1 and 1000 + const uint32_t pixels = pixelDist(rng); + + const uint32_t emus = XLImageUtils::pixelsToEmus(pixels); + REQUIRE(emus > 0u); + + const uint32_t pixelsBack = XLImageUtils::emusToPixels(emus); + // Allow for some rounding error (within 1 pixel) + REQUIRE((pixelsBack == pixels || pixelsBack == pixels - 1 || pixelsBack == pixels + 1)); + } + + // Use Catch RNG to pick random EMU value, convert back and forth + std::uniform_int_distribution emuDist(9525, 9525000); // 1-1000 pixels + + for (int i = 0; i < 10; ++i) { + // Generate random EMU value between 9525 and 9525000 (1-1000 pixels) + const uint32_t emus = emuDist(rng); + + const uint32_t pixels = XLImageUtils::emusToPixels(emus); + REQUIRE(pixels >= 1u); + + const uint32_t emusBack = XLImageUtils::pixelsToEmus(pixels); + // Allow for some rounding error + const uint32_t diff = (emusBack > emus) ? (emusBack - emus) : (emus - emusBack); + REQUIRE(diff <= 9525u); // Within 1 pixel + } + + // Test pointsToEmus() + REQUIRE(XLImageUtils::pointsToEmus(1.0) == 12700u); + REQUIRE(XLImageUtils::pointsToEmus(0.0) == 0u); + REQUIRE(XLImageUtils::pointsToEmus(12.0) == 152400u); // 12 * 12700 + + // Use Catch RNG to pick random point values + std::uniform_real_distribution pointDist(0.0, 100.0); + + for (int i = 0; i < 10; ++i) { + const double points = pointDist(rng); // 0 to 100 points + const uint32_t emus = XLImageUtils::pointsToEmus(points); + REQUIRE(emus == static_cast(points * 12700)); + } + + // Test calculateAspectRatio() - Use Catch RNG to pick random width and height values + std::uniform_int_distribution dimensionDist(1, 999); + + for (int i = 0; i < 10; ++i) { + const uint32_t width = dimensionDist(rng); + const uint32_t height = dimensionDist(rng); + + const double aspectRatio = XLImageUtils::calculateAspectRatio(width, height); + REQUIRE(aspectRatio > 0.0); + REQUIRE(aspectRatio == static_cast(width) / static_cast(height)); + } + + // Test edge cases for calculateAspectRatio() + REQUIRE(XLImageUtils::calculateAspectRatio(100, 100) == 1.0); + REQUIRE(XLImageUtils::calculateAspectRatio(200, 100) == 2.0); + REQUIRE(XLImageUtils::calculateAspectRatio(100, 200) == 0.5); + REQUIRE(XLImageUtils::calculateAspectRatio(0, 100) == 0.0); // height == 0 case + + // Test getImageDimensions() - Get image dimensions from each of the test image data vectors + const auto dims1x1 = XLImageUtils::getImageDimensions(XLImageUtils::red1x1PNGData, XLMimeType::PNG); + REQUIRE(dims1x1.first == 1u); + REQUIRE(dims1x1.second == 1u); + + const auto dims31x15 = XLImageUtils::getImageDimensions(XLImageUtils::png31x15Data, XLMimeType::PNG); + REQUIRE(dims31x15.first == 31u); + REQUIRE(dims31x15.second == 15u); + + const auto dims32x18 = XLImageUtils::getImageDimensions(XLImageUtils::jpeg32x18Data, XLMimeType::JPEG); + REQUIRE(dims32x18.first == 32u); + REQUIRE(dims32x18.second == 18u); + + const auto dims33x16 = XLImageUtils::getImageDimensions(XLImageUtils::bmp33x16Data, XLMimeType::BMP); + REQUIRE(dims33x16.first == 33u); + REQUIRE(dims33x16.second == 16u); + + const auto dims26x16 = XLImageUtils::getImageDimensions(XLImageUtils::gif26x16Data, XLMimeType::GIF); + REQUIRE(dims26x16.first == 26u); + REQUIRE(dims26x16.second == 16u); + + // Test getImageDimensions() with auto-detection + const auto dims1x1Auto = XLImageUtils::getImageDimensions(XLImageUtils::red1x1PNGData); + REQUIRE(dims1x1Auto.first == 1u); + REQUIRE(dims1x1Auto.second == 1u); + + const auto dims31x15Auto = XLImageUtils::getImageDimensions(XLImageUtils::png31x15Data); + REQUIRE(dims31x15Auto.first == 31u); + REQUIRE(dims31x15Auto.second == 15u); + + const auto dims32x18Auto = XLImageUtils::getImageDimensions(XLImageUtils::jpeg32x18Data); + REQUIRE(dims32x18Auto.first == 32u); + REQUIRE(dims32x18Auto.second == 18u); + + // Test with invalid data + const auto dimsInvalid = XLImageUtils::getImageDimensions(invalidData); + REQUIRE(dimsInvalid.first == 0u); + REQUIRE(dimsInvalid.second == 0u); + + // Test isValidImageSize() + REQUIRE(XLImageUtils::isValidImageSize(1, 1) == true); + REQUIRE(XLImageUtils::isValidImageSize(100, 100) == true); + REQUIRE(XLImageUtils::isValidImageSize(10000, 10000) == true); + REQUIRE(XLImageUtils::isValidImageSize(10001, 10000) == false); + REQUIRE(XLImageUtils::isValidImageSize(10000, 10001) == false); + REQUIRE(XLImageUtils::isValidImageSize(0, 100) == false); + REQUIRE(XLImageUtils::isValidImageSize(100, 0) == false); + REQUIRE(XLImageUtils::isValidImageSize(0, 0) == false); + + // Test validateImageData() + for (const auto& img : testImages) { + REQUIRE(XLImageUtils::validateImageData(*img.data, img.mimeType) == true); + // Test with wrong MIME type + if (img.mimeType != XLMimeType::PNG) { + REQUIRE(XLImageUtils::validateImageData(*img.data, XLMimeType::PNG) == false); + } + } + + // Test with invalid data + REQUIRE(XLImageUtils::validateImageData(invalidData, XLMimeType::PNG) == false); + REQUIRE(XLImageUtils::validateImageData(invalidData, XLMimeType::JPEG) == false); + REQUIRE(XLImageUtils::validateImageData(std::vector(), XLMimeType::PNG) == false); + + // Test imageDataHash() + const size_t hash1 = XLImageUtils::imageDataHash(XLImageUtils::red1x1PNGData); + const size_t hash2 = XLImageUtils::imageDataHash(XLImageUtils::red1x1PNGData); + REQUIRE(hash1 == hash2); // Same data should produce same hash + + const size_t hash3 = XLImageUtils::imageDataHash(XLImageUtils::png31x15Data); + REQUIRE(hash1 != hash3); // Different data should produce different hash + + // Test imageDataStrHash() + std::string imageDataStr; + imageDataStr.insert(imageDataStr.end(), XLImageUtils::red1x1PNGData.begin(), XLImageUtils::red1x1PNGData.end()); + const size_t hashStr = XLImageUtils::imageDataStrHash(imageDataStr); + REQUIRE(hash1 == hashStr); // Same data in different formats should produce same hash + + // Test with empty data + REQUIRE(XLImageUtils::imageDataHash(std::vector()) == XLImageUtils::imageDataStrHash("")); + + // Test matchesElementName() + REQUIRE(XLImageUtils::matchesElementName("xdr:oneCellAnchor", "oneCellAnchor") == true); + REQUIRE(XLImageUtils::matchesElementName("oneCellAnchor", "oneCellAnchor") == true); + REQUIRE(XLImageUtils::matchesElementName("xdr:twoCellAnchor", "twoCellAnchor") == true); + REQUIRE(XLImageUtils::matchesElementName("twoCellAnchor", "twoCellAnchor") == true); + REQUIRE(XLImageUtils::matchesElementName("xdr:oneCellAnchor", "twoCellAnchor") == false); + REQUIRE(XLImageUtils::matchesElementName("oneCellAnchor", "twoCellAnchor") == false); + REQUIRE(XLImageUtils::matchesElementName("", "") == true); + REQUIRE(XLImageUtils::matchesElementName("prefix:name", "name") == true); + REQUIRE(XLImageUtils::matchesElementName("prefix:name", "prefix") == false); + } + + SECTION("XLImageManager - brief test") { + XLDocument doc; doc.create("./ut_mgr.xlsx"); + auto &mgr = doc.getImageManager(); + + const size_t before = mgr.getImageCount(); + auto img1 = mgr.findOrAddImage("", + XLImageUtils::red1x1PNGData, + XLImageUtils::mimeTypeToContentType(XLMimeType::PNG)); + auto img2 = mgr.findOrAddImage("", + XLImageUtils::red1x1PNGData, + XLImageUtils::mimeTypeToContentType(XLMimeType::PNG)); + REQUIRE(img1.get() != nullptr); + REQUIRE(img1.get() == img2.get()); + REQUIRE(mgr.getImageCount() == before + 1); + + doc.save(); doc.close(); std::remove("./ut_mgr.xlsx"); + } + + SECTION("XLImageManager/XLImage - full test") { + // Create empty document + XLDocument doc; + doc.create("./ut_mgr_full.xlsx"); + auto &mgr = doc.getImageManager(); + + // Test initial state + REQUIRE(mgr.getImageCount() == 0); + REQUIRE(mgr.begin() == mgr.end()); + std::string dbgMsg; + int verifyDataResult = mgr.verifyData(&dbgMsg); + REQUIRE(verifyDataResult== EXIT_SUCCESS); + + if(verifyDataResult== EXIT_SUCCESS){ + // Add two worksheets "SheetA", "SheetB" + doc.workbook().worksheet(1).setName("SheetA"); + doc.workbook().addWorksheet("SheetB"); + XLWorksheet sheetA = doc.workbook().worksheet("SheetA"); + XLWorksheet sheetB = doc.workbook().worksheet("SheetB"); + + // Map to track image hash -> image data from XLImageUtils + std::map*> hashToImageData; + + // Check vectors for each worksheet + XLEmbImgVec sheetAImagesCheck; + XLEmbImgVec sheetBImagesCheck; + + // Add two images to Worksheet "SheetA" + // Image 1: red1x1PNGData (will be shared with SheetB) + const std::string imageIdA1 = sheetA.embedImageFromImageData( + XLCellReference(1, 1), XLImageUtils::red1x1PNGData, XLMimeType::PNG); + REQUIRE(!imageIdA1.empty()); + const auto& embA1 = sheetA.getEmbImageByImageID(imageIdA1); + sheetAImagesCheck.push_back(embA1); + size_t hashA1 = XLImageUtils::imageDataHash(XLImageUtils::red1x1PNGData); + hashToImageData[hashA1] = &XLImageUtils::red1x1PNGData; + + // Image 2: png31x15Data (unique to SheetA) + const std::string imageIdA2 = sheetA.embedImageFromImageData( + XLCellReference(2, 2), XLImageUtils::png31x15Data, XLMimeType::PNG); + REQUIRE(!imageIdA2.empty()); + const auto& embA2 = sheetA.getEmbImageByImageID(imageIdA2); + sheetAImagesCheck.push_back(embA2); + size_t hashA2 = XLImageUtils::imageDataHash(XLImageUtils::png31x15Data); + hashToImageData[hashA2] = &XLImageUtils::png31x15Data; + + // Add three images to Worksheet "SheetB" + // Image 1: red1x1PNGData (shared with SheetA) + const std::string imageIdB1 = sheetB.embedImageFromImageData( + XLCellReference(1, 1), XLImageUtils::red1x1PNGData, XLMimeType::PNG); + REQUIRE(!imageIdB1.empty()); + const auto& embB1 = sheetB.getEmbImageByImageID(imageIdB1); + sheetBImagesCheck.push_back(embB1); + + // Image 2: jpeg32x18Data (unique to SheetB) + const std::string imageIdB2 = sheetB.embedImageFromImageData( + XLCellReference(3, 3), XLImageUtils::jpeg32x18Data, XLMimeType::JPEG); + REQUIRE(!imageIdB2.empty()); + const auto& embB2 = sheetB.getEmbImageByImageID(imageIdB2); + sheetBImagesCheck.push_back(embB2); + size_t hashB2 = XLImageUtils::imageDataHash(XLImageUtils::jpeg32x18Data); + hashToImageData[hashB2] = &XLImageUtils::jpeg32x18Data; + + // Image 3: bmp33x16Data (unique to SheetB) + const std::string imageIdB3 = sheetB.embedImageFromImageData( + XLCellReference(4, 4), XLImageUtils::bmp33x16Data, XLMimeType::BMP); + REQUIRE(!imageIdB3.empty()); + const auto& embB3 = sheetB.getEmbImageByImageID(imageIdB3); + sheetBImagesCheck.push_back(embB3); + size_t hashB3 = XLImageUtils::imageDataHash(XLImageUtils::bmp33x16Data); + hashToImageData[hashB3] = &XLImageUtils::bmp33x16Data; + + // Test: mgr.getImageCount() == 4 (four unique images) + REQUIRE(mgr.getImageCount() == 4); + REQUIRE(mgr.begin() != mgr.end()); + + // Get embedded images for each sheet + auto sheetAImagesPtr = mgr.getEmbImgsForSheetName("SheetA"); + auto sheetBImagesPtr = mgr.getEmbImgsForSheetName("SheetB"); + REQUIRE(sheetAImagesPtr != nullptr); + REQUIRE(sheetBImagesPtr != nullptr); + + XLEmbImgVec sheetAImages = *sheetAImagesPtr; + XLEmbImgVec sheetBImages = *sheetBImagesPtr; + + // Sort vectors by image ID for comparison + auto sortByImageId = [](const XLEmbeddedImage& a, const XLEmbeddedImage& b) { + return a.id() < b.id(); + }; + std::sort(sheetAImages.begin(), sheetAImages.end(), sortByImageId); + std::sort(sheetBImages.begin(), sheetBImages.end(), sortByImageId); + std::sort(sheetAImagesCheck.begin(), sheetAImagesCheck.end(), sortByImageId); + std::sort(sheetBImagesCheck.begin(), sheetBImagesCheck.end(), sortByImageId); + + // Test: sheetAImages == sheetAImagesCheck + REQUIRE(sheetAImages.size() == sheetAImagesCheck.size()); + for (size_t i = 0; i < sheetAImages.size(); ++i) { + REQUIRE(sheetAImages[i].id() == sheetAImagesCheck[i].id()); + } + + // Test: sheetBImages == sheetBImagesCheck + REQUIRE(sheetBImages.size() == sheetBImagesCheck.size()); + for (size_t i = 0; i < sheetBImages.size(); ++i) { + REQUIRE(sheetBImages[i].id() == sheetBImagesCheck[i].id()); + } + + // Test: mgr.verifyData() == EXIT_SUCCESS + dbgMsg.clear(); + REQUIRE(mgr.verifyData(&dbgMsg) == EXIT_SUCCESS); + + // Iterate through all images and test properties + for (auto imageItr = mgr.begin(); imageItr != mgr.end(); ++imageItr) { + XLImageShPtr image = *imageItr; + REQUIRE(image != nullptr); + + // Test: mgr.findImageByImageData(image->getImageData()) + std::vector imageData = image->getImageData(); + XLImageShPtr foundByData = mgr.findImageByImageData(imageData); + REQUIRE(foundByData != nullptr); + REQUIRE(foundByData.get() == image.get()); + + // Test: mgr.findImageByFilePackagePath(image->filePackagePath()) + XLImageShPtr foundByPath = mgr.findImageByFilePackagePath(image->filePackagePath()); + REQUIRE(foundByPath != nullptr); + REQUIRE(foundByPath.get() == image.get()); + + // Test: image->parentDoc() == doc + REQUIRE(image->parentDoc() == &doc); + + // Get hash and look up image data from map + size_t hash = image->imageDataHash(); + auto it = hashToImageData.find(hash); + REQUIRE(it != hashToImageData.end()); + const std::vector* expectedData = it->second; + + // Test: image->imageDataHash() + REQUIRE(image->imageDataHash() == hash); + REQUIRE(image->imageDataHash() == XLImageUtils::imageDataHash(*expectedData)); + + // Test: image->mimeType() + XLMimeType expectedMimeType = XLImageUtils::detectMimeType(*expectedData); + REQUIRE(image->mimeType() == expectedMimeType); + + // Test: image->extension() + std::string expectedExt = XLImageUtils::mimeTypeToExtension(expectedMimeType); + REQUIRE(image->extension() == expectedExt); + + // Test: image->filePackagePath() + REQUIRE(!image->filePackagePath().empty()); + REQUIRE(image->filePackagePath().find("xl/media/") == 0); + + // Test: image->widthPixels() and image->heightPixels() + auto expectedDims = XLImageUtils::getImageDimensions(*expectedData, expectedMimeType); + REQUIRE(image->widthPixels() == expectedDims.first); + REQUIRE(image->heightPixels() == expectedDims.second); + + // Test: image->verifyData() == EXIT_SUCCESS + dbgMsg.clear(); + REQUIRE(image->verifyData(&dbgMsg) == EXIT_SUCCESS); + } + + // Remove sheetB + doc.workbook().deleteSheet("SheetB"); + + // Test: mgr.getImageCount() == 4 (images still exist until prune) + REQUIRE(mgr.getImageCount() == 4); + } + + // Save document, triggering prune() + doc.save(); + + // Test: mgr.getImageCount() == 2 after prune + REQUIRE(mgr.getImageCount() == 2); // red1x1PNGData and png31x15Data + + doc.close(); + std::remove("./ut_mgr_full.xlsx"); + } + + + SECTION("XLWorksheet - embed image -- brief test") { + XLDocument doc; doc.create("./ut_ws.xlsx"); + auto ws = doc.workbook().worksheet(1); + + const size_t before = ws.imageCount(); + const std::string id = ws.embedImageFromImageData(XLCellReference(2, 2), + XLImageUtils::red1x1PNGData, + XLMimeType::PNG); + REQUIRE(!id.empty()); + REQUIRE(ws.imageCount() == before + 1); + const auto &emb = ws.getEmbImageByImageID(id); + REQUIRE(!emb.isEmpty()); + // XLCellReference(2,2) -> row 2, col 2 => "B2" + REQUIRE(emb.anchorCell() == std::string("B2")); + + // Remove and verify count restored + REQUIRE(ws.removeImageByImageID(id)); + REQUIRE(ws.imageCount() == before); + + doc.save(); doc.close(); std::remove("./ut_ws.xlsx"); + } + + + SECTION("XLImageAnchor -- embed image -- full test") { +/* + XLImageAnchor::XLImageAnchor() + XLImageAnchor::reset(); + XLImageAnchor::initOneCell(); + XLImageAnchor::initTwoCell(); + XLImageAnchor::getFromCellReference() const; + XLImageAnchor::setFromCellReference( const XLCellReference& fromCellRef ); + XLImageAnchor::getToCellReference() const; + XLImageAnchor::setToCellReference(); + XLImageAnchor::setDisplaySizeWithAspectRatio(); + XLImageAnchor::setDisplaySizeWithAspectRatio(); + XLImageAnchor::setDisplaySizeWithAspectRatio(); + XLImageAnchor::isTwoCell() + XLImageAnchor::getDisplayDimensions() + XLImageAnchor::getActualDimensions() + XLImageAnchor::setActualDimensions() + XLImageAnchor::getDisplayDimensionsPixels() + XLImageAnchor::getActualDimensionsPixels() +*/ + auto& rng = Catch::rng(); + std::uniform_int_distribution rowDist(1, 200); + std::uniform_int_distribution colDist(1, 20); + std::uniform_int_distribution rowOffsetDist(1, 10000); + std::uniform_int_distribution colOffsetDist(1, 10000); + + const uint32_t fromRowA = rowDist(rng); + const uint16_t fromColA = colDist(rng); + const int32_t rowOffsetA = rowOffsetDist(rng); + const int32_t colOffsetA = colOffsetDist(rng); + + XLCellReference fromCellRefA( fromRowA, fromColA ); + + XLImageAnchor anchorA; + anchorA.initOneCell( fromCellRefA, rowOffsetA, colOffsetA ); + + REQUIRE(anchorA.fromRow == fromRowA - 1); + REQUIRE(anchorA.fromCol == fromColA - 1); + REQUIRE(anchorA.fromRowOffset == rowOffsetA); + REQUIRE(anchorA.fromColOffset == colOffsetA); + REQUIRE(anchorA.getFromCellReference() == fromCellRefA); + REQUIRE(!anchorA.isTwoCell()); + + const uint32_t fromRowB = rowDist(rng); + const uint16_t fromColB = colDist(rng); + const uint32_t toRowB = fromRowB + rowDist(rng); + const uint16_t toColB = fromColB + colDist(rng); + + XLCellReference fromCellRefB( fromRowB, fromColB ); + XLCellReference toCellRefB( toRowB, toColB ); + + XLImageAnchor anchorB; + anchorB.initTwoCell( fromCellRefB, toCellRefB ); + + REQUIRE(anchorB.fromRow == fromRowB - 1); + REQUIRE(anchorB.fromCol == fromColB - 1); + REQUIRE(anchorB.fromRowOffset == 0); + REQUIRE(anchorB.fromColOffset == 0); + REQUIRE(anchorB.getFromCellReference() == fromCellRefB); + REQUIRE(anchorB.toRow == toRowB - 1); + REQUIRE(anchorB.toCol == toColB - 1); + REQUIRE(anchorB.toRowOffset == 0); + REQUIRE(anchorB.toColOffset == 0); + REQUIRE(anchorB.getToCellReference() == toCellRefB); + REQUIRE(anchorB.isTwoCell()); + + const uint32_t fromRowC = rowDist(rng); + const uint16_t fromColC = colDist(rng); + const int32_t fromRowOffsetC = rowOffsetDist(rng); + const int32_t fromColOffsetC = colOffsetDist(rng); + const uint32_t toRowC = fromRowC + rowDist(rng); + const uint16_t toColC = fromColC + colDist(rng); + const int32_t toRowOffsetC = rowOffsetDist(rng); + const int32_t toColOffsetC = colOffsetDist(rng); + + XLCellReference fromCellRefC( fromRowC, fromColC ); + XLCellReference toCellRefC( toRowC, toColC ); + + XLImageAnchor anchorC; + anchorC.initTwoCell( fromCellRefC, toCellRefC, + fromRowOffsetC, fromColOffsetC, toRowOffsetC, toColOffsetC ); + + REQUIRE(anchorC.fromRow == fromRowC - 1); + REQUIRE(anchorC.fromCol == fromColC - 1); + REQUIRE(anchorC.fromRowOffset == fromRowOffsetC); + REQUIRE(anchorC.fromColOffset == fromColOffsetC); + REQUIRE(anchorC.getFromCellReference() == fromCellRefC); + REQUIRE(anchorC.toRow == toRowC - 1); + REQUIRE(anchorC.toCol == toColC - 1); + REQUIRE(anchorC.toRowOffset == toRowOffsetC); + REQUIRE(anchorC.toColOffset == toColOffsetC); + REQUIRE(anchorC.getToCellReference() == toCellRefC); + REQUIRE(anchorC.isTwoCell()); + + // Test setFromCellReference() and getFromCellReference() standalone + anchorC.setFromCellReference(fromCellRefB); + REQUIRE(anchorC.fromRow == fromRowB-1); + REQUIRE(anchorC.fromCol == fromColB-1); + REQUIRE(anchorC.getFromCellReference() == fromCellRefB); + anchorC.setFromCellReference(fromCellRefC); + REQUIRE(anchorC.fromRow == fromRowC-1); + REQUIRE(anchorC.fromCol == fromColC-1); + REQUIRE(anchorC.getFromCellReference() == fromCellRefC); + + // Test setToCellReference() and getToCellReference() standalone + anchorC.setToCellReference(toCellRefB); + REQUIRE(anchorC.toRow == toRowB-1); + REQUIRE(anchorC.toCol == toColB-1); + REQUIRE(anchorC.getToCellReference() == toCellRefB); + anchorC.setToCellReference(toCellRefC); + REQUIRE(anchorC.toRow == toRowC-1); + REQUIRE(anchorC.toCol == toColC-1); + REQUIRE(anchorC.getToCellReference() == toCellRefC); + + // Test setDisplaySizeWithAspectRatio() with pixel dimensions + const uint32_t testWidthPx = 100 + rowDist(rng) % 400; // 100-500 pixels + const uint32_t testHeightPx = 100 + rowDist(rng) % 400; // 100-500 pixels + const uint32_t maxWidthEmus = XLImageUtils::pixelsToEmus(300); + const uint32_t maxHeightEmus = XLImageUtils::pixelsToEmus(200); + anchorA.setDisplaySizeWithAspectRatio(testWidthPx, testHeightPx, maxWidthEmus, maxHeightEmus); + auto displayDims = anchorA.getDisplayDimensions(); + REQUIRE(displayDims.first > 0); + REQUIRE(displayDims.second > 0); + REQUIRE(displayDims.first <= maxWidthEmus); + REQUIRE(displayDims.second <= maxHeightEmus); + // Verify aspect ratio is maintained (within tolerance) + double expectedAspectRatio = XLImageUtils::calculateAspectRatio(testWidthPx, testHeightPx); + double actualAspectRatio = static_cast(displayDims.first) / static_cast(displayDims.second); + REQUIRE(std::abs(expectedAspectRatio - actualAspectRatio) < 0.01); + + // Test setDisplaySizeWithAspectRatio() with image data + const uint32_t maxWidthEmusH = XLImageUtils::pixelsToEmus(500); + const uint32_t maxHeightEmusH = XLImageUtils::pixelsToEmus(300); + anchorA.setDisplaySizeWithAspectRatio(XLImageUtils::png31x15Data, XLMimeType::PNG, + maxWidthEmusH, maxHeightEmusH); + auto displayDimsH = anchorA.getDisplayDimensions(); + REQUIRE(displayDimsH.first > 0); + REQUIRE(displayDimsH.second > 0); + REQUIRE(displayDimsH.first <= maxWidthEmusH); + REQUIRE(displayDimsH.second <= maxHeightEmusH); + // Verify aspect ratio matches image dimensions (31x15) + double expectedAspectRatioH = XLImageUtils::calculateAspectRatio(31, 15); + double actualAspectRatioH = static_cast(displayDimsH.first) / static_cast(displayDimsH.second); + REQUIRE(std::abs(expectedAspectRatioH - actualAspectRatioH) < 0.01); + + // Test getDisplayDimensions() + const uint32_t testDisplayWidth = 100000; // EMUs + const uint32_t testDisplayHeight = 50000; // EMUs + anchorA.displayWidthEMUs = testDisplayWidth; + anchorA.displayHeightEMUs = testDisplayHeight; + auto displayDimsI = anchorA.getDisplayDimensions(); + REQUIRE(displayDimsI.first == testDisplayWidth); + REQUIRE(displayDimsI.second == testDisplayHeight); + + // Test getActualDimensions() and setActualDimensions() + const uint32_t testActualWidth = 200000; // EMUs + const uint32_t testActualHeight = 100000; // EMUs + anchorA.setActualDimensions(testActualWidth, testActualHeight); + auto actualDimsJ = anchorA.getActualDimensions(); + REQUIRE(actualDimsJ.first == testActualWidth); + REQUIRE(actualDimsJ.second == testActualHeight); + + // Test getDisplayDimensionsPixels() + const uint32_t testDisplayWidthK = XLImageUtils::pixelsToEmus(150); + const uint32_t testDisplayHeightK = XLImageUtils::pixelsToEmus(75); + anchorA.displayWidthEMUs = testDisplayWidthK; + anchorA.displayHeightEMUs = testDisplayHeightK; + auto displayDimsPxK = anchorA.getDisplayDimensionsPixels(); + REQUIRE(displayDimsPxK.first >= 149); // Allow for rounding + REQUIRE(displayDimsPxK.first <= 151); + REQUIRE(displayDimsPxK.second >= 74); + REQUIRE(displayDimsPxK.second <= 76); + + // Test getActualDimensionsPixels() + const uint32_t testActualWidthL = XLImageUtils::pixelsToEmus(200); + const uint32_t testActualHeightL = XLImageUtils::pixelsToEmus(100); + anchorA.setActualDimensions(testActualWidthL, testActualHeightL); + auto actualDimsPxL = anchorA.getActualDimensionsPixels(); + REQUIRE(actualDimsPxL.first >= 199); // Allow for rounding + REQUIRE(actualDimsPxL.first <= 201); + REQUIRE(actualDimsPxL.second >= 99); + REQUIRE(actualDimsPxL.second <= 101); + + // Test setDisplaySizeWithAspectRatio() with various aspect ratios using Catch RNG + for (int i = 0; i < 5; ++i) { + const uint32_t widthPx = 50 + rowDist(rng) % 450; // 50-500 pixels + const uint32_t heightPx = 50 + rowDist(rng) % 450; // 50-500 pixels + const uint32_t maxWidthEmusM = XLImageUtils::pixelsToEmus(300); + const uint32_t maxHeightEmusM = XLImageUtils::pixelsToEmus(200); + anchorA.setDisplaySizeWithAspectRatio(widthPx, heightPx, maxWidthEmusM, maxHeightEmusM); + auto displayDimsM = anchorA.getDisplayDimensions(); + REQUIRE(displayDimsM.first > 0); + REQUIRE(displayDimsM.second > 0); + REQUIRE(displayDimsM.first <= maxWidthEmusM); + REQUIRE(displayDimsM.second <= maxHeightEmusM); + } + + // Test reset() + anchorC.reset(); + REQUIRE(anchorC.anchorType == XLImageAnchorType::Unknown); + REQUIRE(anchorC.fromRow == 0); + REQUIRE(anchorC.fromCol == 0); + REQUIRE(anchorC.displayWidthEMUs == 0); + REQUIRE(anchorC.displayHeightEMUs == 0); + } + + + SECTION("XLWorksheet - full test") { + + XLDocument doc; doc.create("./ut_ws.xlsx"); + auto ws = doc.workbook().worksheet(1); + + REQUIRE(ws.imageCount() == 0); + REQUIRE(!ws.hasImages()); + REQUIRE(!ws.hasImagesInXML()); + + std::string errMsg; + auto& rng = Catch::rng(); + std::uniform_int_distribution rowDist(1, 20); + std::uniform_int_distribution colDist(1, 5); + + const uint32_t fromRowA = rowDist(rng); + const uint16_t fromColA = colDist(rng); + XLCellReference fromCellRefA( fromRowA, fromColA ); + + const uint32_t fromRowB = rowDist(rng) + fromRowA; + const uint16_t fromColB = colDist(rng); + XLCellReference fromCellRefB( fromRowB, fromColB ); + XLImageAnchor anchorB; + anchorB.initOneCell( fromCellRefB ); + + const uint32_t fromRowC = fromRowB + rowDist(rng); + const uint16_t fromColC = colDist(rng); + XLCellReference fromCellRefC( fromRowC, fromColC ); + + const uint32_t fromRowD = fromRowC + rowDist(rng); + const uint16_t fromColD = colDist(rng); + const uint32_t toRowD = fromRowD + rowDist(rng); + const uint16_t toColD = fromColD + colDist(rng); + XLCellReference fromCellRefD( fromRowD, fromColD ); + XLCellReference toCellRefD( toRowD, toColD ); + XLImageAnchor anchorD; + const uint32_t maxWidthEmusD = XLImageUtils::pixelsToEmus(500); + const uint32_t maxHeightEmusD = XLImageUtils::pixelsToEmus(300); + anchorD.setDisplaySizeWithAspectRatio(XLImageUtils::gif26x16Data, + XLMimeType::GIF, maxWidthEmusD, maxHeightEmusD); + anchorD.initTwoCell( fromCellRefD, toCellRefD ); + + const std::string embImgIdA = ws.embedImageFromImageData( + fromCellRefA, XLImageUtils::png31x15Data, XLMimeType::PNG ); + const XLEmbeddedImage embImgA = ws.getEmbImageByImageID(embImgIdA); + const XLEmbeddedImage embImgAByRelId = + ws.getEmbImageByRelationshipId(embImgA.relationshipId()); + REQUIRE( !embImgIdA.empty() ); + REQUIRE( embImgA.id() == embImgIdA ); + REQUIRE( embImgA == embImgAByRelId ); + REQUIRE(embImgA.anchorCell() == fromCellRefA.address()); + REQUIRE(embImgA.verifyData(&errMsg) == EXIT_SUCCESS); + + const std::string embImgIdB = ws.embedImageFromImageData( + anchorB, XLImageUtils::jpeg32x18Data ); + const XLEmbeddedImage embImgB = ws.getEmbImageByImageID(embImgIdB); + const XLEmbeddedImage embImgBByRelId = + ws.getEmbImageByRelationshipId(embImgB.relationshipId()); + REQUIRE( !embImgIdB.empty() ); + REQUIRE( embImgB.id() == embImgIdB ); + REQUIRE( embImgB == embImgBByRelId ); + REQUIRE(embImgB.anchorCell() == fromCellRefB.address()); + REQUIRE(embImgB.verifyData(&errMsg) == EXIT_SUCCESS); + + const std::string bmp33x16fileName = "bmp33x16.bmp"; + { + std::ofstream file(bmp33x16fileName, std::ios::binary); + file.write(reinterpret_cast(XLImageUtils::bmp33x16Data.data()), + XLImageUtils::bmp33x16Data.size()); + } + const std::string embImgIdC = ws.embedImageFromFile( + fromCellRefC, bmp33x16fileName, XLMimeType::BMP ); + std::remove(bmp33x16fileName.c_str()); + const XLEmbeddedImage embImgC = ws.getEmbImageByImageID(embImgIdC); + const XLEmbeddedImage embImgCByRelId = + ws.getEmbImageByRelationshipId(embImgC.relationshipId()); + REQUIRE( !embImgIdC.empty() ); + REQUIRE( embImgC.id() == embImgIdC ); + REQUIRE( embImgC == embImgCByRelId ); + REQUIRE(embImgC.anchorCell() == fromCellRefC.address()); + REQUIRE(embImgC.verifyData(&errMsg) == EXIT_SUCCESS); + + const std::string gif26x16fileName = "gif26x16.gif"; + { + std::ofstream file(gif26x16fileName, std::ios::binary); + file.write(reinterpret_cast(XLImageUtils::gif26x16Data.data()), + XLImageUtils::gif26x16Data.size()); + } + const std::string embImgIdD = ws.embedImageFromFile( + anchorD, gif26x16fileName ); + std::remove(gif26x16fileName.c_str()); + const XLEmbeddedImage embImgD = ws.getEmbImageByImageID(embImgIdD); + const XLEmbeddedImage embImgDByRelId = + ws.getEmbImageByRelationshipId(embImgD.relationshipId()); + REQUIRE( !embImgIdD.empty() ); + REQUIRE( embImgD.id() == embImgIdD ); + REQUIRE( embImgD == embImgDByRelId ); + REQUIRE(embImgD.anchorCell() == fromCellRefD.address()); + REQUIRE(embImgD.verifyData(&errMsg) == EXIT_SUCCESS); + + REQUIRE(ws.imageCount() == 4); + REQUIRE(ws.hasImages()); + REQUIRE(ws.hasImagesInXML()); + REQUIRE(ws.verifyData(&errMsg) == EXIT_SUCCESS); + REQUIRE(doc.verifyData(&errMsg) == EXIT_SUCCESS); + + std::vector embImagesA = + ws.getEmbImagesAtCell(fromCellRefA.address()); + REQUIRE( !embImagesA.empty() ); + const std::string cellRangeAD = fromCellRefA.address() + ":" + + toCellRefD.address(); + std::vector embImagesAD = + ws.getEmbImagesInRange( cellRangeAD ); + REQUIRE( !embImagesAD.empty() ); + std::vector embImages = ws.getEmbImages(); + REQUIRE( !embImages.empty() ); + + doc.save(); + + ws.removeImageByImageID(embImgIdB); + REQUIRE(ws.imageCount() == 3); + ws.removeImageByRelationshipId(embImgD.relationshipId()); + REQUIRE(ws.imageCount() == 2); + REQUIRE(ws.verifyData(&errMsg) == EXIT_SUCCESS); + REQUIRE(doc.verifyData(&errMsg) == EXIT_SUCCESS); + ws.clearImages(); + REQUIRE(ws.imageCount() == 0); + REQUIRE(!ws.hasImages()); + REQUIRE(!ws.hasImagesInXML()); + REQUIRE(ws.verifyData(&errMsg) == EXIT_SUCCESS); + REQUIRE(doc.verifyData(&errMsg) == EXIT_SUCCESS); + + doc.close(); + std::remove("./ut_ws.xlsx"); + } +} + diff --git a/Tests/testXLRow.cpp b/Tests/testXLRow.cpp index c8876b20..513b03f2 100644 --- a/Tests/testXLRow.cpp +++ b/Tests/testXLRow.cpp @@ -32,9 +32,9 @@ TEST_CASE("XLRow Tests", "[XLRow]") XLRow copy3; copy3 = copy2; - copy3.setHeight(height * 3); + copy3.setHeight(static_cast(height * 3)); REQUIRE(copy3.height() == height * 3); - copy3.setHeight(height * 4); + copy3.setHeight(static_cast(height * 4)); REQUIRE(copy3.height() == height * 4); XLRow copy4; @@ -162,7 +162,7 @@ TEST_CASE("XLRowData Tests", "[XLRowData]") const auto row1c = row1; - auto val1sum = 0; + std::string::size_type val1sum = 0; const auto val1results1 = static_cast>(row1c.values()); for (const auto v : val1results1) val1sum += v.size(); REQUIRE(val1sum == 12); @@ -294,7 +294,7 @@ TEST_CASE("XLRowData Tests", "[XLRowData]") const auto row1c = row1; - auto val1sum = 0; + std::string::size_type val1sum = 0; const auto val1results1 = static_cast>(row1c.values()); for (const auto v : val1results1) val1sum += v.size(); REQUIRE(val1sum == 12); @@ -426,7 +426,7 @@ TEST_CASE("XLRowData Tests", "[XLRowData]") const auto row1c = row1; - auto val1sum = 0; + std::string::size_type val1sum = 0; const auto val1results1 = static_cast>(row1c.values()); for (const auto v : val1results1) val1sum += v.size(); REQUIRE(val1sum == 12);