From 86e4c9dca4ae0eb0e5854d4c0d4c4596253eeacb Mon Sep 17 00:00:00 2001 From: Simon D Fink Date: Sun, 27 Oct 2024 08:50:11 +0100 Subject: [PATCH 1/4] support reading and writing paths to external images (instead of embedding their data) --- src/include/ipebitmap.h | 27 +++++++++--- src/ipelib/ipebitmap.cpp | 78 ++++++++++++++++++++++++++++++++--- src/ipelib/ipebitmap_unix.cpp | 43 +++++++------------ src/ipelib/ipebitmap_win.cpp | 15 +++---- src/ipelib/ipeiml.cpp | 31 ++++++++++---- src/ipelib/ipepdfwriter.cpp | 2 +- 6 files changed, 140 insertions(+), 56 deletions(-) diff --git a/src/include/ipebitmap.h b/src/include/ipebitmap.h index 0578de4..d88bda7 100644 --- a/src/include/ipebitmap.h +++ b/src/include/ipebitmap.h @@ -43,11 +43,12 @@ namespace ipe { class Bitmap { public: enum Flags { - ERGB = 0x01, // not grayscale - EAlpha = 0x02, // has alpha channel - EDCT = 0x04, // DCT encoded jpg image - EInflate = 0x08, // data needs to be inflated - ENative = 0x10, // data is already in native-endian ARGB32 + ERGB = 0x01, // not grayscale + EAlpha = 0x02, // has alpha channel + EDCT = 0x04, // DCT encoded jpg image + EInflate = 0x08, // data needs to be inflated + ENative = 0x10, // data is already in native-endian ARGB32 + EExternal = 0x20, // image is stored externally }; Bitmap(); @@ -69,6 +70,8 @@ class Bitmap { inline bool isJpeg() const; inline bool isGray() const; + inline bool isExternal() const; + inline String externalPath() const; inline bool hasAlpha() const; inline int colorKey() const; @@ -77,7 +80,7 @@ class Bitmap { inline int objNum() const; inline void setObjNum(int objNum) const; - std::pair embed() const; + std::pair getEmbedData() const; inline bool operator==(const Bitmap & rhs) const; inline bool operator!=(const Bitmap & rhs) const; @@ -87,7 +90,12 @@ class Bitmap { Vector & dotsPerInch, uint32_t & flags); static Bitmap readJpeg(const char * fname, Vector & dotsPerInch, const char *& errmsg); + static const char * readPNGData(const char * fname, int & width, int & height, + uint32_t & flags, Buffer & pixels, + Vector & dotsPerInch); static Bitmap readPNG(const char * fname, Vector & dotsPerInch, const char *& errmsg); + static Bitmap readExternal(String path, const XmlAttributes & attr, + const char *& errmsg); void savePixels(const char * fname); @@ -109,6 +117,7 @@ class Bitmap { bool iPixelsComputed; uint32_t iChecksum; mutable int iObjNum; // Object number (e.g. in PDF file) + String iPath; }; Imp * iImp; @@ -131,6 +140,12 @@ inline bool Bitmap::isJpeg() const { return (iImp->iFlags & EDCT) != 0; } //! Is the bitmap grayscale? inline bool Bitmap::isGray() const { return (iImp->iFlags & ERGB) == 0; } +//! Is the bitmap stored externally or embedded? +inline bool Bitmap::isExternal() const { return (iImp->iFlags & EExternal) != 0; } + +//! The path to the external bitmap file +inline String Bitmap::externalPath() const { return iImp->iPath; } + //! Does the bitmap have transparency? /*! Bitmaps with color key will return false here. */ inline bool Bitmap::hasAlpha() const { return (iImp->iFlags & EAlpha) != 0; } diff --git a/src/ipelib/ipebitmap.cpp b/src/ipelib/ipebitmap.cpp index 2462f19..f9fd1ca 100644 --- a/src/ipelib/ipebitmap.cpp +++ b/src/ipelib/ipebitmap.cpp @@ -212,7 +212,8 @@ void Bitmap::unpack(Buffer alphaChannel) { iImp->iData = pixels; } -//! Determine if bitmap has alpha channel, colorkey, rgb values (does nothing for JPG). +//! Determine if bitmap has alpha channel, colorkey, rgb values (does nothing for +//! JPG). void Bitmap::analyze() { iImp->iColorKey = -1; iImp->iFlags &= EDCT | ERGB; // clear all other flags, we recompute them @@ -310,10 +311,14 @@ void Bitmap::saveAsXml(Stream & stream, int id, int pdfObjNum) const { if (pdfObjNum >= 0) { stream << " pdfObject=\"" << pdfObjNum; if (hasAlpha()) stream << " " << pdfObjNum - 1; - stream << "\"/>\n"; - } else { + stream << "\""; + } + + if (isExternal()) { + stream << " path=\"" << externalPath() << "\"/>\n"; + } else if (pdfObjNum < 0) { // save data - auto data = embed(); + auto data = getEmbedData(); stream << " length=\"" << data.first.size() << "\""; if (hasAlpha()) stream << " alphaLength=\"" << data.second.size() << "\""; stream << " encoding=\"base64\">\n"; @@ -325,6 +330,8 @@ void Bitmap::saveAsXml(Stream & stream, int id, int pdfObjNum) const { } b64.close(); stream << "\n"; + } else { + stream << "/>"; } } @@ -352,7 +359,7 @@ void Bitmap::computeChecksum() { iImp->iChecksum = iImp->iData.checksum(); } /*! For Jpeg images, this is simply the bitmap data. For other images, rgb/grayscale data and alpha channel are split and deflated separately. */ -std::pair Bitmap::embed() const { +std::pair Bitmap::getEmbedData() const { if (isJpeg()) return std::make_pair(iImp->iData, Buffer()); int npixels = width() * height(); uint32_t * src = (uint32_t *)iImp->iData.data(); @@ -667,4 +674,65 @@ Bitmap Bitmap::readJpeg(const char * fname, Vector & dotsPerInch, const char *& return Bitmap(width, height, flags, Buffer(a.data(), a.size())); } +//! Read PNG image from file. +/*! Returns the image as a Bitmap. + Sets \a dotsPerInch if the image file contains a resolution, + otherwise sets it to (0,0). + If reading the file fails, returns a null Bitmap, + and sets the error message \a errmsg. +*/ +Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& errmsg) { + int width; + int height; + uint32_t flags; + Buffer pixels; + errmsg = readPNGData(fname, width, height, flags, pixels, dotsPerInch); + + if (!errmsg) { + return Bitmap(width, height, flags, pixels); + } else { + return Bitmap(); + } +} + +// -------------------------------------------------------------------- + +Bitmap Bitmap::readExternal(String path, const XmlAttributes & attr, + const char *& errmsg) { + Bitmap ret; + ret.init(attr); + ret.iImp->iPath = Lex(path).nextToken(); + Vector dotsPerInch; + + errmsg = readPNGData(ret.iImp->iPath.z(), ret.iImp->iWidth, ret.iImp->iHeight, + ret.iImp->iFlags, ret.iImp->iData, dotsPerInch); + if (errmsg) { + FILE * file = Platform::fopen(ret.iImp->iPath.z(), "rb"); + if (!file) { + errmsg = "Error opening file"; + } else { + errmsg = readJpegInfo(file, ret.iImp->iWidth, ret.iImp->iHeight, dotsPerInch, + ret.iImp->iFlags); + fclose(file); + if (!errmsg) { + String a = Platform::readFile(ret.iImp->iPath); + ret.iImp->iData = Buffer(a.data(), a.size()); + } + } + } + ret.iImp->iFlags |= Bitmap::EExternal; + + if (!errmsg) { + assert(ret.iImp->iWidth > 0 && ret.iImp->iHeight > 0); + assert(ret.iImp->iData.size() > 0); + ret.unpack(Buffer()); + ret.computeChecksum(); + ret.analyze(); + return ret; + } else { + ipeDebug("Could not load linked image at '%s%': %s", ret.iImp->iPath.z(), errmsg); + return Bitmap(); + } +} + // -------------------------------------------------------------------- diff --git a/src/ipelib/ipebitmap_unix.cpp b/src/ipelib/ipebitmap_unix.cpp index ee97802..a09aaeb 100644 --- a/src/ipelib/ipebitmap_unix.cpp +++ b/src/ipelib/ipebitmap_unix.cpp @@ -166,47 +166,35 @@ bool dctDecode(Buffer dctData, Buffer pixelData) { // -------------------------------------------------------------------- -//! Read PNG image from file. -/*! Returns the image as a Bitmap. - It will be compressed if \a deflate is set. - Sets \a dotsPerInch if the image file contains a resolution, - otherwise sets it to (0,0). - If reading the file fails, returns a null Bitmap, - and sets the error message \a errmsg. -*/ -Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& errmsg) { +const char * Bitmap::readPNGData(const char * fname, int & width, int & height, + uint32_t & flags, Buffer & pixels, + Vector & dotsPerInch) { FILE * fp = Platform::fopen(fname, "rb"); - if (!fp) { - errmsg = "Error opening file"; - return Bitmap(); - } + if (!fp) { return "Error opening file"; } static const char pngerr[] = "PNG library error"; uint8_t header[8]; if (fread(header, 1, 8, fp) != 8 || png_sig_cmp(header, 0, 8)) { - errmsg = "The file does not appear to be a PNG image"; fclose(fp); - return Bitmap(); + return "The file does not appear to be a PNG image"; } png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp) nullptr, nullptr, nullptr); if (!png_ptr) { - errmsg = pngerr; fclose(fp); - return Bitmap(); + return pngerr; } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp) nullptr, (png_infopp) nullptr); - errmsg = pngerr; - return Bitmap(); + fclose(fp); + return pngerr; } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); - errmsg = pngerr; fclose(fp); - return Bitmap(); + return pngerr; } #if PNG_LIBPNG_VER >= 10504 @@ -217,8 +205,9 @@ Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& e png_set_sig_bytes(png_ptr, 8); png_read_info(png_ptr, info_ptr); - int width = png_get_image_width(png_ptr, info_ptr); - int height = png_get_image_height(png_ptr, info_ptr); + width = png_get_image_width(png_ptr, info_ptr); + height = png_get_image_height(png_ptr, info_ptr); + flags = Bitmap::ERGB | Bitmap::EAlpha; int color_type = png_get_color_type(png_ptr, info_ptr); if (color_type == PNG_COLOR_TYPE_PALETTE) { @@ -251,16 +240,15 @@ Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& e png_read_update_info(png_ptr, info_ptr); if (png_get_bit_depth(png_ptr, info_ptr) != 8) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); - errmsg = "Depth of PNG image is not eight bits."; fclose(fp); - return Bitmap(); + return "Depth of PNG image is not eight bits."; } const double mpi = 25.4 / 1000.0; dotsPerInch = Vector(mpi * png_get_x_pixels_per_meter(png_ptr, info_ptr), mpi * png_get_y_pixels_per_meter(png_ptr, info_ptr)); - Buffer pixels(4 * width * height); + pixels = Buffer(4 * width * height); std::vector row(height); for (int y = 0; y < height; ++y) row[y] = (png_bytep)pixels.data() + 4 * width * y; png_read_image(png_ptr, row.data()); @@ -269,8 +257,7 @@ Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& e png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); fclose(fp); - Bitmap bm(width, height, Bitmap::ERGB | Bitmap::EAlpha, pixels); - return bm; + return nullptr; } // -------------------------------------------------------------------- diff --git a/src/ipelib/ipebitmap_win.cpp b/src/ipelib/ipebitmap_win.cpp index 676116c..59ca21d 100644 --- a/src/ipelib/ipebitmap_win.cpp +++ b/src/ipelib/ipebitmap_win.cpp @@ -87,19 +87,20 @@ bool dctDecode(Buffer dctData, Buffer pixelData) { // -------------------------------------------------------------------- // The graphics file formats supported by GDI+ are // BMP, GIF, JPEG, PNG, TIFF. -Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& errmsg) { +const char * Bitmap::readPNGInfo(const char * fname, int & w, int & h, uint32_t & flags, + Buffer & pixels, Vector & dotsPerInch) { // load without color correction Gdiplus::Bitmap * bitmap = Gdiplus::Bitmap::FromFile(String(fname).w().data(), FALSE); if (bitmap->GetLastStatus() != Gdiplus::Ok) { delete bitmap; - return Bitmap(); + return "GDI+ Status not OK"; } dotsPerInch = Vector(bitmap->GetHorizontalResolution(), bitmap->GetVerticalResolution()); - - int w = bitmap->GetWidth(); - int h = bitmap->GetHeight(); + w = bitmap->GetWidth(); + h = bitmap->GetHeight(); + flags = Bitmap::ENative; Buffer pixelData(4 * w * h); Gdiplus::BitmapData * bitmapData = new Gdiplus::BitmapData; @@ -114,13 +115,13 @@ Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& e Gdiplus::ImageLockModeRead | Gdiplus::ImageLockModeUserInputBuf, PixelFormat32bppARGB, bitmapData); - Bitmap bm(w, h, Bitmap::ENative, pixelData); + // TODO check whether we need to do the processing of analyze() here bitmap->UnlockBits(bitmapData); delete bitmapData; delete bitmap; - return bm; + return nullptr; } // -------------------------------------------------------------------- diff --git a/src/ipelib/ipeiml.cpp b/src/ipelib/ipeiml.cpp index 0f0bb87..4821663 100644 --- a/src/ipelib/ipeiml.cpp +++ b/src/ipelib/ipeiml.cpp @@ -158,15 +158,28 @@ int ImlParser::parseDocument(Document & doc) { bool ImlParser::parseBitmap() { XmlAttributes att; if (!parseAttributes(att)) return false; - String objNumStr; - if (att.slash() && att.has("pdfObject", objNumStr)) { - Lex lex(objNumStr); - Buffer data = pdfStream(lex.getInt()); - Buffer alpha; - lex.skipWhitespace(); - if (!lex.eos()) alpha = pdfStream(lex.getInt()); - Bitmap bitmap(att, data, alpha); - iBitmaps.push_back(bitmap); + String objRefStr; + // only load the pdfObject data if the XML contains no base64 data / external path + if (att.slash()) { + if (att.has("pdfObject", objRefStr)) { + Lex lex(objRefStr); + Buffer data = pdfStream(lex.getInt()); + Buffer alpha; + lex.skipWhitespace(); + if (!lex.eos()) alpha = pdfStream(lex.getInt()); + Bitmap bitmap(att, data, alpha); + iBitmaps.push_back(bitmap); + } else if (att.has("path", objRefStr)) { + const char * errmsg = nullptr; + Bitmap bitmap = Bitmap::readExternal(objRefStr, att, errmsg); + if (!errmsg) { + iBitmaps.push_back(bitmap); + } else { + return false; + } + } else { + return false; + } } else { String bits; if (!parsePCDATA("bitmap", bits)) return false; diff --git a/src/ipelib/ipepdfwriter.cpp b/src/ipelib/ipepdfwriter.cpp index 658894f..dc821fe 100644 --- a/src/ipelib/ipepdfwriter.cpp +++ b/src/ipelib/ipepdfwriter.cpp @@ -562,7 +562,7 @@ void PdfWriter::createStream(const char * data, int size, bool preCompressed) { void PdfWriter::embedBitmap(Bitmap bitmap) { int smaskNum = -1; - auto embed = bitmap.embed(); + auto embed = bitmap.getEmbedData(); if (bitmap.hasAlpha() && embed.second.size() > 0) { smaskNum = startObject(); iStream << "<<\n"; From 4c59f94057b899af5820ce6d4674dddc4292617e Mon Sep 17 00:00:00 2001 From: Simon D Fink Date: Sun, 10 Nov 2024 15:36:55 +0100 Subject: [PATCH 2/4] improve handling of unresolvable bitmaps --- src/include/ipebitmap.h | 6 ++++++ src/ipelib/ipebitmap.cpp | 36 ++++++++++++++++++++++++------------ src/ipelib/ipeiml.cpp | 34 ++++++++++++---------------------- src/ipelib/ipepainter.cpp | 30 +++++++++++++++++++++++++++++- src/ipelib/ipepdfwriter.cpp | 1 + src/ipelib/ipeplatform.cpp | 6 ++++-- src/ipelua/ipeluaobj.cpp | 8 ++++++++ 7 files changed, 84 insertions(+), 37 deletions(-) diff --git a/src/include/ipebitmap.h b/src/include/ipebitmap.h index d88bda7..9501026 100644 --- a/src/include/ipebitmap.h +++ b/src/include/ipebitmap.h @@ -63,6 +63,7 @@ class Bitmap { void saveAsXml(Stream & stream, int id, int pdfObjNum = -1) const; inline bool isNull() const; + inline bool isLoaded() const; bool equal(Bitmap rhs) const; inline int width() const; @@ -128,6 +129,11 @@ class Bitmap { //! Is this a null bitmap? inline bool Bitmap::isNull() const { return (iImp == nullptr); } +//! Could the pixel data for this bitmap be loaded? +inline bool Bitmap::isLoaded() const { + return !isNull() && (iImp->iData.size() > 0 || iImp->iPixelData.size() > 0); +} + //! Return width of pixel array. inline int Bitmap::width() const { return iImp->iWidth; } diff --git a/src/ipelib/ipebitmap.cpp b/src/ipelib/ipebitmap.cpp index f9fd1ca..6812857 100644 --- a/src/ipelib/ipebitmap.cpp +++ b/src/ipelib/ipebitmap.cpp @@ -697,30 +697,34 @@ Bitmap Bitmap::readPNG(const char * fname, Vector & dotsPerInch, const char *& e // -------------------------------------------------------------------- -Bitmap Bitmap::readExternal(String path, const XmlAttributes & attr, +Bitmap Bitmap::readExternal(String pathAttr, const XmlAttributes & attr, const char *& errmsg) { Bitmap ret; ret.init(attr); - ret.iImp->iPath = Lex(path).nextToken(); + ret.iImp->iPath = Lex(pathAttr).nextToken(); + String path(Platform::realPath(ret.iImp->iPath)); Vector dotsPerInch; - errmsg = readPNGData(ret.iImp->iPath.z(), ret.iImp->iWidth, ret.iImp->iHeight, - ret.iImp->iFlags, ret.iImp->iData, dotsPerInch); - if (errmsg) { - FILE * file = Platform::fopen(ret.iImp->iPath.z(), "rb"); + errmsg = nullptr; + const char * errjpg = nullptr; + const char * errpng = readPNGData(path.z(), ret.iImp->iWidth, ret.iImp->iHeight, + ret.iImp->iFlags, ret.iImp->iData, dotsPerInch); + if (errpng) { + FILE * file = Platform::fopen(path.z(), "rb"); if (!file) { errmsg = "Error opening file"; } else { - errmsg = readJpegInfo(file, ret.iImp->iWidth, ret.iImp->iHeight, dotsPerInch, + errjpg = readJpegInfo(file, ret.iImp->iWidth, ret.iImp->iHeight, dotsPerInch, ret.iImp->iFlags); fclose(file); - if (!errmsg) { - String a = Platform::readFile(ret.iImp->iPath); + if (errjpg) { + errmsg = "Could not parse image file as PNG or JPEG"; + } else { + String a = Platform::readFile(path); ret.iImp->iData = Buffer(a.data(), a.size()); } } } - ret.iImp->iFlags |= Bitmap::EExternal; if (!errmsg) { assert(ret.iImp->iWidth > 0 && ret.iImp->iHeight > 0); @@ -728,10 +732,18 @@ Bitmap Bitmap::readExternal(String path, const XmlAttributes & attr, ret.unpack(Buffer()); ret.computeChecksum(); ret.analyze(); + ret.iImp->iFlags |= Bitmap::EExternal; return ret; } else { - ipeDebug("Could not load linked image at '%s%': %s", ret.iImp->iPath.z(), errmsg); - return Bitmap(); + ipeDebug( + "Could not load linked image at '%s' in '%s', resolved to '%s':\n%s\n%s\n%s", + pathAttr.z(), Platform::currentDirectory().z(), path.z(), errmsg, errpng, + errjpg); + Bitmap eret; + eret.init(attr); + eret.iImp->iPath = ret.iImp->iPath; + eret.iImp->iFlags = Bitmap::EExternal; + return eret; } } diff --git a/src/ipelib/ipeiml.cpp b/src/ipelib/ipeiml.cpp index 4821663..0bbde2a 100644 --- a/src/ipelib/ipeiml.cpp +++ b/src/ipelib/ipeiml.cpp @@ -159,30 +159,20 @@ bool ImlParser::parseBitmap() { XmlAttributes att; if (!parseAttributes(att)) return false; String objRefStr; - // only load the pdfObject data if the XML contains no base64 data / external path - if (att.slash()) { - if (att.has("pdfObject", objRefStr)) { - Lex lex(objRefStr); - Buffer data = pdfStream(lex.getInt()); - Buffer alpha; - lex.skipWhitespace(); - if (!lex.eos()) alpha = pdfStream(lex.getInt()); - Bitmap bitmap(att, data, alpha); - iBitmaps.push_back(bitmap); - } else if (att.has("path", objRefStr)) { - const char * errmsg = nullptr; - Bitmap bitmap = Bitmap::readExternal(objRefStr, att, errmsg); - if (!errmsg) { - iBitmaps.push_back(bitmap); - } else { - return false; - } - } else { - return false; - } + if (att.has("path", objRefStr)) { + const char * errmsg = nullptr; + iBitmaps.push_back(Bitmap::readExternal(objRefStr, att, errmsg)); + } else if (att.has("pdfObject", objRefStr)) { + Lex lex(objRefStr); + Buffer data = pdfStream(lex.getInt()); + Buffer alpha; + lex.skipWhitespace(); + if (!lex.eos()) alpha = pdfStream(lex.getInt()); + Bitmap bitmap(att, data, alpha); + iBitmaps.push_back(bitmap); } else { String bits; - if (!parsePCDATA("bitmap", bits)) return false; + if (att.slash() || !parsePCDATA("bitmap", bits)) return false; Bitmap bitmap(att, bits); iBitmaps.push_back(bitmap); } diff --git a/src/ipelib/ipepainter.cpp b/src/ipelib/ipepainter.cpp index d2c81a4..100bed9 100644 --- a/src/ipelib/ipepainter.cpp +++ b/src/ipelib/ipepainter.cpp @@ -233,7 +233,35 @@ void Painter::drawPath(TPathMode mode) { */ void Painter::drawBitmap(Bitmap bitmap) { assert(!iInPath); - doDrawBitmap(bitmap); + assert(!bitmap.isNull()); + if (!bitmap.isLoaded()) { + push(); + // setState(State()); // reset state + setOpacity(Attribute(Fixed(1000))); + setStrokeOpacity(Attribute(Fixed(1000))); + setFill(Attribute(Color(1000, 1000, 1000))); + setStroke(Attribute(Color(1000, 0, 0))); + setLineJoin(EMiterJoin); + setTiling(Attribute::normal(ETiling)); + setGradient(Attribute::normal(EGradient)); + + setPen(Attribute(Fixed(2))); + newPath(); + rect(Rect{Vector{0, 0}, Vector{1, 1}}); + drawPath(EStrokedAndFilled); + + setPen(Attribute(Fixed(1))); + newPath(); + moveTo(Vector{0, 0}); + lineTo(Vector{1, 1}); + moveTo(Vector{1, 0}); + lineTo(Vector{0, 1}); + drawPath(EStrokedOnly); + + pop(); + } else { + doDrawBitmap(bitmap); + } } //! Render a text object. diff --git a/src/ipelib/ipepdfwriter.cpp b/src/ipelib/ipepdfwriter.cpp index dc821fe..a6962f8 100644 --- a/src/ipelib/ipepdfwriter.cpp +++ b/src/ipelib/ipepdfwriter.cpp @@ -610,6 +610,7 @@ void PdfWriter::embedBitmap(Bitmap bitmap) { void PdfWriter::embedBitmaps(const BitmapFinder & bm) { for (BmIter it = bm.iBitmaps.begin(); it != bm.iBitmaps.end(); ++it) { + if (!it->isLoaded()) continue; BmIter it1 = std::find(iBitmaps.begin(), iBitmaps.end(), *it); if (it1 == iBitmaps.end()) { // look again, more carefully diff --git a/src/ipelib/ipeplatform.cpp b/src/ipelib/ipeplatform.cpp index 7fe56aa..50d55a0 100644 --- a/src/ipelib/ipeplatform.cpp +++ b/src/ipelib/ipeplatform.cpp @@ -406,8 +406,9 @@ String Platform::currentDirectory() { wchar_t * buffer = _wgetcwd(nullptr, 0); return String(buffer); #else - char buffer[1024]; - if (getcwd(buffer, 1024) != buffer) return String(); + char buffer[PATH_MAX]; + buffer[0] = 0; + if (getcwd(buffer, PATH_MAX) != buffer) return String(); return String(buffer); #endif } @@ -456,6 +457,7 @@ String Platform::realPath(String fname) { return String(wresult); #else char rpath[PATH_MAX]; + rpath[0] = 0; if (realpath(fname.z(), rpath)) return String(rpath); if (errno != ENOENT || fname.left(1) == "/") return fname; // not much we can do if (realpath(".", rpath) == nullptr) return fname; // nothing we can do diff --git a/src/ipelua/ipeluaobj.cpp b/src/ipelua/ipeluaobj.cpp index 522352c..7af0050 100644 --- a/src/ipelua/ipeluaobj.cpp +++ b/src/ipelua/ipeluaobj.cpp @@ -854,6 +854,14 @@ static int object_info(lua_State * L) { } push_string(L, format); lua_setfield(L, -2, "format"); + lua_pushboolean(L, bm.isExternal()); + lua_setfield(L, -2, "external"); + lua_pushboolean(L, bm.isLoaded()); + lua_setfield(L, -2, "loaded"); + if (bm.isExternal()) { + push_string(L, bm.externalPath()); + lua_setfield(L, -2, "path"); + } return 1; } From f0d1c9785aab7d136b79ac44a1d3bae6f6e9cfa2 Mon Sep 17 00:00:00 2001 From: Simon D Fink Date: Sun, 10 Nov 2024 16:03:16 +0100 Subject: [PATCH 3/4] change into directory of loaded file --- src/include/ipebase.h | 2 ++ src/ipelib/ipedoc.cpp | 1 + src/ipelib/ipeplatform.cpp | 22 ++++++++++++---------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/include/ipebase.h b/src/include/ipebase.h index f9314b5..c24a842 100644 --- a/src/include/ipebase.h +++ b/src/include/ipebase.h @@ -391,6 +391,8 @@ class Platform { static void initLib(int version); static void setDebug(bool debug); static String currentDirectory(); + static void changeDirectory(String dir); + static String parentDirectory(String dir); static String latexPath(); static bool fileExists(String fname); static bool listDirectory(String path, std::vector & files); diff --git a/src/ipelib/ipedoc.cpp b/src/ipelib/ipedoc.cpp index 1d813bb..e65ed8f 100644 --- a/src/ipelib/ipedoc.cpp +++ b/src/ipelib/ipedoc.cpp @@ -217,6 +217,7 @@ Document * Document::load(const char * fname, int & reason) { reason = EFileOpenError; std::FILE * fd = Platform::fopen(fname, "rb"); if (!fd) return nullptr; + Platform::changeDirectory(Platform::parentDirectory(fname)); FileSource source(fd); FileFormat format = fileFormat(source); std::rewind(fd); diff --git a/src/ipelib/ipeplatform.cpp b/src/ipelib/ipeplatform.cpp index 50d55a0..7610e14 100644 --- a/src/ipelib/ipeplatform.cpp +++ b/src/ipelib/ipeplatform.cpp @@ -30,6 +30,7 @@ #include "ipeattributes.h" #include "ipebase.h" +#include #ifdef WIN32 #define NTDDI_VERSION 0x06000000 @@ -400,17 +401,18 @@ void ipeDebugBuffer(Buffer data, int maxsize) { // -------------------------------------------------------------------- //! Returns current working directory. -/*! Returns empty string if something fails. */ String Platform::currentDirectory() { -#ifdef WIN32 - wchar_t * buffer = _wgetcwd(nullptr, 0); - return String(buffer); -#else - char buffer[PATH_MAX]; - buffer[0] = 0; - if (getcwd(buffer, PATH_MAX) != buffer) return String(); - return String(buffer); -#endif + return std::filesystem::current_path().generic_string(); +} + +//! Changes the current working directory. +void Platform::changeDirectory(String dir) { + if (!dir.empty()) std::filesystem::current_path(dir.z()); +} + +//! Get the directory containg the given file. +String Platform::parentDirectory(String file) { + return std::filesystem::path(file.z()).parent_path().generic_string(); } //! Returns drive on which Ipe executable exists. From fa59c35b0b43602c0c0b4038f518b42c8a1eaadf Mon Sep 17 00:00:00 2001 From: Simon D Fink Date: Sun, 10 Nov 2024 16:33:13 +0100 Subject: [PATCH 4/4] update external path when saving in different directory --- src/include/ipebitmap.h | 10 ++++++++++ src/ipelib/ipebitmap.cpp | 9 +++++++++ src/ipelib/ipedoc.cpp | 8 ++++++++ 3 files changed, 27 insertions(+) diff --git a/src/include/ipebitmap.h b/src/include/ipebitmap.h index 9501026..14484b5 100644 --- a/src/include/ipebitmap.h +++ b/src/include/ipebitmap.h @@ -76,6 +76,9 @@ class Bitmap { inline bool hasAlpha() const; inline int colorKey() const; + inline void setExternalPath(String path); + void changeExternalPathRelativeBase(String new_base); + Buffer pixelData(); inline int objNum() const; @@ -152,6 +155,13 @@ inline bool Bitmap::isExternal() const { return (iImp->iFlags & EExternal) != 0; //! The path to the external bitmap file inline String Bitmap::externalPath() const { return iImp->iPath; } +//! Set the path to the external bitmap file +inline void Bitmap::setExternalPath(String path) { + assert(isExternal()); + assert(!path.empty()); + iImp->iPath = path; +} + //! Does the bitmap have transparency? /*! Bitmaps with color key will return false here. */ inline bool Bitmap::hasAlpha() const { return (iImp->iFlags & EAlpha) != 0; } diff --git a/src/ipelib/ipebitmap.cpp b/src/ipelib/ipebitmap.cpp index 6812857..914b48c 100644 --- a/src/ipelib/ipebitmap.cpp +++ b/src/ipelib/ipebitmap.cpp @@ -30,6 +30,8 @@ #include "ipebitmap.h" #include "ipeutils.h" + +#include #include using namespace ipe; @@ -747,4 +749,11 @@ Bitmap Bitmap::readExternal(String pathAttr, const XmlAttributes & attr, } } +void Bitmap::changeExternalPathRelativeBase(String new_base) { + new_base = Platform::realPath(new_base); + String old_path = Platform::realPath(externalPath()); + setExternalPath( + std::filesystem::proximate(old_path.s(), new_base.s()).generic_string()); +} + // -------------------------------------------------------------------- diff --git a/src/ipelib/ipedoc.cpp b/src/ipelib/ipedoc.cpp index e65ed8f..613227e 100644 --- a/src/ipelib/ipedoc.cpp +++ b/src/ipelib/ipedoc.cpp @@ -297,6 +297,14 @@ bool Document::save(TellStream & stream, FileFormat format, uint32_t flags) cons bool Document::save(const char * fname, FileFormat format, uint32_t flags) const { std::FILE * fd = Platform::fopen(fname, "wb"); if (!fd) return false; + + String new_base = Platform::parentDirectory(Platform::realPath(fname)); + BitmapFinder bmf; + findBitmaps(bmf); + for (auto & bm : bmf.iBitmaps) { + if (bm.isExternal()) bm.changeExternalPathRelativeBase(new_base); + } + FileStream stream(fd); bool result = save(stream, format, flags); std::fclose(fd);