diff --git a/.travis.yml b/.travis.yml index 158967d..0c180f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,32 +1,101 @@ language: cpp + +#sudo: true + +os: + - linux +# - osx + compiler: gcc + +env: + - POPPLER_NAME="poppler-0.63.0" POPPLER_SOURCE="https://ftp.osuosl.org/pub/blfs/conglomeration/poppler/poppler-0.63.0.tar.xz" FONTFORGE_SOURCE="https://github.com/fontforge/fontforge.git" PDF2HTMLEX_SOURCE="https://github.com/Rockstar04/pdf2htmlEX.git" + addons: + apt: + sources: + - sourceline: 'ppa:fontforge/fontforge' + - sourceline: 'ppa:coolwanglu/pdf2htmlex' + update: true + packages: + - ttfautohint + - build-essential + - libtool + - autoconf + - python-pip + - libgetopt++-dev + - pkg-config + - git + - default-jre + - libnss3-dev + - libopenjpeg-dev + - libjpeg-turbo8-dev + - libfontconfig1-dev + - poppler-data +# - poppler-utils +# - poppler-dbg + - packaging-dev + - libglib2.0-dev + - libxml2-dev + - giflib-dbg + - libjpeg-dev + - libtiff-dev + - uthash-dev + - libcairo-dev +# - libpoppler-dev + - libspiro-dev + - libcairo-dev + - libpango1.0-dev + - libfreetype6-dev + - libltdl-dev + - libfontforge-dev + - python-dev + - python-imaging + - python-pip + - firefox + - xvfb + - cmake + homebrew: + packages: + - fontforge + - poppler + - cairo sauce_connect: true + before_install: - - sudo add-apt-repository ppa:fontforge/fontforge --yes - - sudo add-apt-repository ppa:coolwanglu/pdf2htmlex --yes - - sudo apt-get update -qq - - sudo apt-get install -qq libpoppler-dev libpoppler-private-dev libspiro-dev libcairo-dev libpango1.0-dev libfreetype6-dev libltdl-dev libfontforge-dev python-imaging python-pip firefox xvfb - - sudo pip install selenium sauceclient - - export DISPLAY=:99.0 - - test/start_xvfb.sh - - pushd / - - python -m SimpleHTTPServer 8000 >/dev/null 2>&1 & - - popd - - sleep 5 + # Install poppler + - wget "${POPPLER_SOURCE}" + - tar -xvf "${POPPLER_NAME}.tar.xz" + - cd "${POPPLER_NAME}/" + - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=`pwd`/../usr -DENABLE_XPDF_HEADERS=ON -DENABLE_LIBOPENJPEG=none + - make && make install + - cd .. + # Install fontforge libuninameslist via source ... + - git clone https://github.com/fontforge/libuninameslist.git + - cd libuninameslist + - autoreconf -i && automake + - ./configure --prefix=`pwd`/../usr + - make && make install + # Install fontforge via source + - git clone --depth 1 --single-branch --branch 20170731 "$FONTFORGE_SOURCE" + - cd fontforge/ && git checkout tags/20170731 + - ./bootstrap + - ./configure --prefix=`pwd`/../usr + - make && make install && ldconfig +# - pip install --user selenium sauceclient +# - export DISPLAY=:99.0 +# - test/start_xvfb.sh +# - pushd / +# - python -m SimpleHTTPServer 8000 >/dev/null 2>&1 & +# - popd +# - sleep 5 + before_script: - - cmake -DENABLE_SVG=ON . + - cmake -DENABLE_SVG=ON -DCMAKE_INSTALL_PREFIX=`pwd`/usr . + - export LD_LIBRARY_PATH=`pwd`/usr/lib:$LD_LIBRARY_PATH + script: - make - - P2H_TEST_REMOTE=1 ctest --output-on-failure --verbose - - sudo make install - - /usr/local/bin/pdf2htmlEX -v -branches: - only: - - master - - incoming - - wl -env: - global: - - secure: V0yGXROTAsRc3ExcECj7X/CrJLbodUeqZyfQGkA6x0iLV7Lh8/hgTjSsvuj7ef/DIWMqJ5cAIzZuXiF0KIxiVllF1v0I3w+LScxynT7B1NsyH16hvGIc7EvrsRmGVeTv8n9I+cCIwQxjtliNKfeZjV4Rk2+u6LioUzTszmW2etc= - - secure: Q5ZSrdFEgN0JvUp90nY5Wh58iukmGZQ2EW7crOibWH2yuUsxAnMELxpY+9yV3+eA7kbjJf/I0NCa5ZY1gkxK60ugUj+zuUDTL+BV1XCbO37e0uwh3ae99iyQWpXc8e8wBp10sthoX7U6Hvypa5tD9r1JJib8jxJV/MzIFpb7H9s= +# - P2H_TEST_REMOTE=1 ctest --output-on-failure --verbose + - make install + - /usr/local/bin/pdf2htmlEX -v \ No newline at end of file diff --git a/3rdparty/poppler/git/CairoFontEngine.cc b/3rdparty/poppler/git/CairoFontEngine.cc index 4571b7f..fb50687 100644 --- a/3rdparty/poppler/git/CairoFontEngine.cc +++ b/3rdparty/poppler/git/CairoFontEngine.cc @@ -17,16 +17,17 @@ // Copyright (C) 2005-2007 Jeff Muizelaar // Copyright (C) 2005, 2006 Kristian Høgsberg // Copyright (C) 2005 Martin Kretzschmar -// Copyright (C) 2005, 2009, 2012, 2013 Albert Astals Cid +// Copyright (C) 2005, 2009, 2012, 2013, 2015, 2017 Albert Astals Cid // Copyright (C) 2006, 2007, 2010, 2011 Carlos Garcia Campos // Copyright (C) 2007 Koji Otani // Copyright (C) 2008, 2009 Chris Wilson -// Copyright (C) 2008, 2012 Adrian Johnson +// Copyright (C) 2008, 2012, 2014, 2016, 2017 Adrian Johnson // Copyright (C) 2009 Darren Kenny // Copyright (C) 2010 Suzuki Toshiya // Copyright (C) 2010 Jan Kümmel // Copyright (C) 2012 Hib Eris // Copyright (C) 2013 Thomas Freitag +// Copyright (C) 2015, 2016 Jason Crain // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git @@ -47,7 +48,7 @@ #include "Gfx.h" #include "Page.h" -#if HAVE_FCNTL_H && HAVE_SYS_MMAN_H && HAVE_SYS_STAT_H +#if defined(HAVE_FCNTL_H) && defined(HAVE_SYS_MMAN_H) && defined(HAVE_SYS_STAT_H) #include #include #include @@ -58,16 +59,11 @@ #pragma implementation #endif -/* - * pdf2htmlEX: disabled multi thread -#if MULTITHREADED +#ifdef MULTITHREADED # define fontEngineLocker() MutexLocker locker(&mutex) #else -*/ # define fontEngineLocker() -/* #endif -*/ //------------------------------------------------------------------------ // CairoFont @@ -116,7 +112,7 @@ CairoFont::getGlyph(CharCode code, double CairoFont::getSubstitutionCorrection(GfxFont *gfxFont) { - double w1, w2; + double w1, w2, w3; CharCode code; char *name; @@ -146,7 +142,8 @@ CairoFont::getSubstitutionCorrection(GfxFont *gfxFont) cairo_font_options_destroy(options); w2 = extents.x_advance; } - if (!gfxFont->isSymbolic()) { + w3 = ((Gfx8BitFont *)gfxFont)->getWidth(0); + if (!gfxFont->isSymbolic() && w2 > 0 && w1 > w3) { // if real font is substantially narrower than substituted // font, reduce the font size accordingly if (w1 > 0.01 && w1 < 0.9 * w2) { @@ -183,7 +180,7 @@ _ft_new_face_uncached (FT_Library lib, FT_Face face; cairo_font_face_t *font_face; - if (font_data == NULL) { + if (font_data == nullptr) { if (FT_New_Face (lib, filename, 0, &face)) return gFalse; } else { @@ -209,7 +206,7 @@ _ft_new_face_uncached (FT_Library lib, return gTrue; } -#if CAN_CHECK_OPEN_FACES +#ifdef CAN_CHECK_OPEN_FACES static struct _ft_face_data { struct _ft_face_data *prev, *next, **head; @@ -260,12 +257,16 @@ _ft_done_face (void *closure) else _ft_open_faces = data->next; + if (data->fd != -1) { #if defined(__SUNPRO_CC) && defined(__sun) && defined(__SVR4) - munmap ((char*)data->bytes, data->size); + munmap ((char*)data->bytes, data->size); #else - munmap (data->bytes, data->size); + munmap (data->bytes, data->size); #endif - close (data->fd); + close (data->fd); + } else { + gfree (data->bytes); + } FT_Done_Face (data->face); gfree (data); @@ -285,7 +286,7 @@ _ft_new_face (FT_Library lib, tmpl.fd = -1; - if (font_data == NULL) { + if (font_data == nullptr) { /* if we fail to mmap the file, just pass it to FreeType instead */ tmpl.fd = open (filename, O_RDONLY); if (tmpl.fd == -1) @@ -296,7 +297,7 @@ _ft_new_face (FT_Library lib, return _ft_new_face_uncached (lib, filename, font_data, font_data_len, face_out, font_face_out); } - tmpl.bytes = (unsigned char *) mmap (NULL, st.st_size, + tmpl.bytes = (unsigned char *) mmap (nullptr, st.st_size, PROT_READ, MAP_PRIVATE, tmpl.fd, 0); if (tmpl.bytes == MAP_FAILED) { @@ -322,6 +323,8 @@ _ft_new_face (FT_Library lib, munmap (tmpl.bytes, tmpl.size); #endif close (tmpl.fd); + } else { + gfree (tmpl.bytes); } *face_out = l->face; *font_face_out = cairo_font_face_reference (l->font_face); @@ -348,7 +351,7 @@ _ft_new_face (FT_Library lib, l = (struct _ft_face_data *) gmallocn (1, sizeof (struct _ft_face_data)); *l = tmpl; - l->prev = NULL; + l->prev = nullptr; l->next = _ft_open_faces; if (_ft_open_faces) _ft_open_faces->prev = l; @@ -399,7 +402,7 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, GfxFontType fontType; GfxFontLoc *fontLoc; char **enc; - char *name; + const char *name; FoFiTrueType *ff; FoFiType1C *ff1c; Ref ref; @@ -408,20 +411,19 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, int *codeToGID; Guint codeToGIDLen; - - codeToGID = NULL; + + codeToGID = nullptr; codeToGIDLen = 0; - font_data = NULL; + font_data = nullptr; font_data_len = 0; - fileName = NULL; - fileNameC = NULL; + fileName = nullptr; + fileNameC = nullptr; GBool substitute = gFalse; - + ref = *gfxFont->getID(); fontType = gfxFont->getType(); - // pdf2htmlEX: changed gFlase to nullptr if (!(fontLoc = gfxFont->locateFont(xref, nullptr))) { error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() @@ -432,7 +434,7 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, // embedded font if (fontLoc->locType == gfxFontLocEmbedded) { font_data = gfxFont->readEmbFontFile(xref, &font_data_len); - if (NULL == font_data) + if (nullptr == font_data) goto err2; // external font @@ -442,7 +444,7 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, substitute = gTrue; } - if (fileName != NULL) { + if (fileName != nullptr) { fileNameC = fileName->getCString(); } @@ -454,21 +456,32 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, error(errSyntaxError, -1, "could not create type1 face"); goto err2; } - + enc = ((Gfx8BitFont *)gfxFont)->getEncoding(); - + codeToGID = (int *)gmallocn(256, sizeof(int)); codeToGIDLen = 256; for (i = 0; i < 256; ++i) { codeToGID[i] = 0; if ((name = enc[i])) { - codeToGID[i] = FT_Get_Name_Index(face, name); + codeToGID[i] = FT_Get_Name_Index(face, (char*)name); + if (codeToGID[i] == 0) { + Unicode u; + u = globalParams->mapNameToUnicodeText (name); + codeToGID[i] = FT_Get_Char_Index (face, u); + } + if (codeToGID[i] == 0) { + name = GfxFont::getAlternateName(name); + if (name) { + codeToGID[i] = FT_Get_Name_Index(face, (char*)name); + } + } } } break; case fontCIDType2: case fontCIDType2OT: - codeToGID = NULL; + codeToGID = nullptr; n = 0; if (((GfxCIDFont *)gfxFont)->getCIDToGID()) { n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); @@ -478,7 +491,7 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, n * sizeof(int)); } } else { - if (font_data != NULL) { + if (font_data != nullptr) { ff = FoFiTrueType::make(font_data, font_data_len); } else { ff = FoFiTrueType::load(fileNameC); @@ -491,7 +504,8 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, codeToGIDLen = n; /* Fall through */ case fontTrueType: - if (font_data != NULL) { + case fontTrueTypeOT: + if (font_data != nullptr) { ff = FoFiTrueType::make(font_data, font_data_len); } else { ff = FoFiTrueType::load(fileNameC); @@ -501,7 +515,7 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, goto err2; } /* This might be set already for the CIDType2 case */ - if (fontType == fontTrueType) { + if (fontType == fontTrueType || fontType == fontTrueTypeOT) { codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff); codeToGIDLen = 256; } @@ -511,16 +525,16 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, goto err2; } break; - + case fontCIDType0: case fontCIDType0C: - codeToGID = NULL; + codeToGID = nullptr; codeToGIDLen = 0; if (!useCIDs) { - if (font_data != NULL) { + if (font_data != nullptr) { ff1c = FoFiType1C::make(font_data, font_data_len); } else { ff1c = FoFiType1C::load(fileNameC); @@ -532,13 +546,45 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, } if (! _ft_new_face (lib, fileNameC, font_data, font_data_len, &face, &font_face)) { - gfree(codeToGID); - codeToGID = NULL; error(errSyntaxError, -1, "could not create cid face\n"); goto err2; } break; - + + case fontCIDType0COT: + codeToGID = nullptr; + n = 0; + if (((GfxCIDFont *)gfxFont)->getCIDToGID()) { + n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen(); + if (n) { + codeToGID = (int *)gmallocn(n, sizeof(int)); + memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), + n * sizeof(int)); + } + } + codeToGIDLen = n; + + if (!codeToGID) { + if (!useCIDs) { + if (font_data != nullptr) { + ff = FoFiTrueType::make(font_data, font_data_len); + } else { + ff = FoFiTrueType::load(fileNameC); + } + if (ff) { + if (ff->isOpenTypeCFF()) { + codeToGID = ff->getCIDToGIDMap((int *)&codeToGIDLen); + } + delete ff; + } + } + } + if (! _ft_new_face (lib, fileNameC, font_data, font_data_len, &face, &font_face)) { + error(errSyntaxError, -1, "could not create cid (OT) face\n"); + goto err2; + } + break; + default: fprintf (stderr, "font type %d not handled\n", (int)fontType); goto err2; @@ -554,8 +600,10 @@ CairoFreeTypeFont *CairoFreeTypeFont::create(GfxFont *gfxFont, XRef *xref, err2: /* hmm? */ delete fontLoc; + gfree (codeToGID); + gfree (font_data); fprintf (stderr, "some font thing failed\n"); - return NULL; + return nullptr; } //------------------------------------------------------------------------ @@ -655,11 +703,12 @@ _render_type3_glyph (cairo_scaled_font_t *scaled_font, box.y1 = mat[1]; box.x2 = mat[2]; box.y2 = mat[3]; - gfx = new Gfx(info->doc, output_dev, resDict, &box, NULL); + gfx = new Gfx(info->doc, output_dev, resDict, &box, nullptr); output_dev->startDoc(info->doc, info->fontEngine); output_dev->startPage (1, gfx->getState(), gfx->getXRef()); output_dev->setInType3Char(gTrue); - gfx->display(charProcs->getVal(glyph, &charProc)); + charProc = charProcs->getVal(glyph); + gfx->display(&charProc); output_dev->getType3GlyphWidth (&wx, &wy); cairo_matrix_transform_distance (&matrix, &wx, &wy); @@ -678,7 +727,6 @@ _render_type3_glyph (cairo_scaled_font_t *scaled_font, delete gfx; delete output_dev; - charProc.free(); return CAIRO_STATUS_SUCCESS; } @@ -760,35 +808,29 @@ CairoFontEngine::CairoFontEngine(FT_Library libA) { lib = libA; for (i = 0; i < cairoFontCacheSize; ++i) { - fontCache[i] = NULL; + fontCache[i] = nullptr; } - + FT_Int major, minor, patch; // as of FT 2.1.8, CID fonts are indexed by CID instead of GID FT_Library_Version(lib, &major, &minor, &patch); useCIDs = major > 2 || (major == 2 && (minor > 1 || (minor == 1 && patch > 7))); -/* - * pdf2htmlEX: disabled multi thread -#if MULTITHREADED +#ifdef MULTITHREADED gInitMutex(&mutex); #endif -*/ } CairoFontEngine::~CairoFontEngine() { int i; - + for (i = 0; i < cairoFontCacheSize; ++i) { if (fontCache[i]) delete fontCache[i]; } -/* - * pdf2htmlEX: disabled multi thread -#if MULTITHREADED +#ifdef MULTITHREADED gDestroyMutex(&mutex); #endif -*/ } CairoFont * @@ -797,7 +839,7 @@ CairoFontEngine::getFont(GfxFont *gfxFont, PDFDoc *doc, GBool printing, XRef *xr Ref ref; CairoFont *font; GfxFontType fontType; - + fontEngineLocker(); ref = *gfxFont->getID(); @@ -811,7 +853,7 @@ CairoFontEngine::getFont(GfxFont *gfxFont, PDFDoc *doc, GBool printing, XRef *xr return font; } } - + fontType = gfxFont->getType(); if (fontType == fontType3) font = CairoType3Font::create (gfxFont, doc, this, printing, xref); diff --git a/3rdparty/poppler/git/CairoFontEngine.h b/3rdparty/poppler/git/CairoFontEngine.h index 96cc4c1..601fbae 100644 --- a/3rdparty/poppler/git/CairoFontEngine.h +++ b/3rdparty/poppler/git/CairoFontEngine.h @@ -15,10 +15,10 @@ // under GPL version 2 or later // // Copyright (C) 2005, 2006 Kristian Høgsberg -// Copyright (C) 2005 Albert Astals Cid +// Copyright (C) 2005, 2018 Albert Astals Cid // Copyright (C) 2006, 2007 Jeff Muizelaar // Copyright (C) 2006, 2010 Carlos Garcia Campos -// Copyright (C) 2008 Adrian Johnson +// Copyright (C) 2008, 2017 Adrian Johnson // Copyright (C) 2013 Thomas Freitag // // To see a description of the changes please see the Changelog file that @@ -51,6 +51,8 @@ public: GBool substitute, GBool printing); virtual ~CairoFont(); + CairoFont(const CairoFont &) = delete; + CairoFont& operator=(const CairoFont &other) = delete; virtual GBool matches(Ref &other, GBool printing); cairo_font_face_t *getFontFace(void); @@ -74,7 +76,7 @@ protected: class CairoFreeTypeFont : public CairoFont { public: static CairoFreeTypeFont *create(GfxFont *gfxFont, XRef *xref, FT_Library lib, GBool useCIDs); - virtual ~CairoFreeTypeFont(); + ~CairoFreeTypeFont(); private: CairoFreeTypeFont(Ref ref, cairo_font_face_t *cairo_font_face, @@ -88,9 +90,9 @@ public: static CairoType3Font *create(GfxFont *gfxFont, PDFDoc *doc, CairoFontEngine *fontEngine, GBool printing, XRef *xref); - virtual ~CairoType3Font(); + ~CairoType3Font(); - virtual GBool matches(Ref &other, GBool printing); + GBool matches(Ref &other, GBool printing) override; private: CairoType3Font(Ref ref, PDFDoc *doc, @@ -114,6 +116,8 @@ public: // Create a font engine. CairoFontEngine(FT_Library libA); ~CairoFontEngine(); + CairoFontEngine(const CairoFontEngine &) = delete; + CairoFontEngine& operator=(const CairoFontEngine &other) = delete; CairoFont *getFont(GfxFont *gfxFont, PDFDoc *doc, GBool printing, XRef *xref); @@ -121,12 +125,9 @@ private: CairoFont *fontCache[cairoFontCacheSize]; FT_Library lib; GBool useCIDs; -/* - * pdf2htmlEX: disabled multi thread -#if MULTITHREADED +#ifdef MULTITHREADED GooMutex mutex; #endif -*/ }; #endif diff --git a/3rdparty/poppler/git/CairoOutputDev.cc b/3rdparty/poppler/git/CairoOutputDev.cc index ff52824..67ec7c5 100644 --- a/3rdparty/poppler/git/CairoOutputDev.cc +++ b/3rdparty/poppler/git/CairoOutputDev.cc @@ -16,18 +16,19 @@ // // Copyright (C) 2005-2008 Jeff Muizelaar // Copyright (C) 2005, 2006 Kristian Høgsberg -// Copyright (C) 2005, 2009, 2012 Albert Astals Cid +// Copyright (C) 2005, 2009, 2012, 2017 Albert Astals Cid // Copyright (C) 2005 Nickolay V. Shmyrev -// Copyright (C) 2006-2011, 2013 Carlos Garcia Campos +// Copyright (C) 2006-2011, 2013, 2014, 2017 Carlos Garcia Campos // Copyright (C) 2008 Carl Worth -// Copyright (C) 2008-2013 Adrian Johnson +// Copyright (C) 2008-2017 Adrian Johnson // Copyright (C) 2008 Michael Vrable // Copyright (C) 2008, 2009 Chris Wilson // Copyright (C) 2008, 2012 Hib Eris // Copyright (C) 2009, 2010 David Benjamin -// Copyright (C) 2011-2013 Thomas Freitag +// Copyright (C) 2011-2014 Thomas Freitag // Copyright (C) 2012 Patrick Pfeifer -// Copyright (C) 2012 Jason Crain +// Copyright (C) 2012, 2015, 2016 Jason Crain +// Copyright (C) 2015 Suzuki Toshiya // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git @@ -40,11 +41,11 @@ #pragma implementation #endif +#include #include #include #include #include -#include #include "goo/gfile.h" #include "GlobalParams.h" @@ -63,10 +64,16 @@ #include "CairoFontEngine.h" #include "CairoRescaleBox.h" #include "UnicodeMap.h" +#include "JBIG2Stream.h" //------------------------------------------------------------------------ // #define LOG_CAIRO +// To limit memory usage and improve performance when printing, limit +// cairo images to this size. 8192 is sufficient for an A2 sized +// 300ppi image. +#define MAX_PRINT_IMAGE_SIZE 8192 + #ifdef LOG_CAIRO #define LOG(x) (x) #else @@ -89,7 +96,7 @@ static inline void printMatrix(cairo_matrix_t *matrix){ //------------------------------------------------------------------------ CairoImage::CairoImage (double x1, double y1, double x2, double y2) { - this->image = NULL; + this->image = nullptr; this->x1 = x1; this->y1 = y1; this->x2 = x2; @@ -121,50 +128,56 @@ FT_Library CairoOutputDev::ft_lib; GBool CairoOutputDev::ft_lib_initialized = gFalse; CairoOutputDev::CairoOutputDev() { - doc = NULL; + doc = nullptr; if (!ft_lib_initialized) { FT_Init_FreeType(&ft_lib); ft_lib_initialized = gTrue; } - fontEngine = NULL; + fontEngine = nullptr; fontEngine_owner = gFalse; - glyphs = NULL; - fill_pattern = NULL; + glyphs = nullptr; + fill_pattern = nullptr; fill_color.r = fill_color.g = fill_color.b = 0; - stroke_pattern = NULL; + stroke_pattern = nullptr; stroke_color.r = stroke_color.g = stroke_color.b = 0; stroke_opacity = 1.0; fill_opacity = 1.0; - textClipPath = NULL; - strokePathClip = NULL; - cairo = NULL; - currentFont = NULL; + textClipPath = nullptr; + strokePathClip = nullptr; + cairo = nullptr; + currentFont = nullptr; +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) + prescaleImages = gFalse; +#else prescaleImages = gTrue; +#endif printing = gTrue; use_show_text_glyphs = gFalse; inUncoloredPattern = gFalse; inType3Char = gFalse; t3_glyph_has_bbox = gFalse; + text_matrix_valid = gTrue; + antialias = CAIRO_ANTIALIAS_DEFAULT; - groupColorSpaceStack = NULL; - maskStack = NULL; - group = NULL; - mask = NULL; - shape = NULL; - cairo_shape = NULL; + groupColorSpaceStack = nullptr; + maskStack = nullptr; + group = nullptr; + mask = nullptr; + shape = nullptr; + cairo_shape = nullptr; knockoutCount = 0; - text = NULL; - actualText = NULL; + text = nullptr; + actualText = nullptr; // the SA parameter supposedly defaults to false, but Acrobat // apparently hardwires it to true - stroke_adjust = globalParams->getStrokeAdjust(); + stroke_adjust = gTrue; align_stroke_coords = gFalse; adjusted_stroke_width = gFalse; - xref = NULL; + xref = nullptr; } CairoOutputDev::~CairoOutputDev() { @@ -182,15 +195,15 @@ CairoOutputDev::~CairoOutputDev() { cairo_pattern_destroy (mask); if (shape) cairo_pattern_destroy (shape); - if (text) + if (text) text->decRefCnt(); if (actualText) - delete actualText; + delete actualText; } void CairoOutputDev::setCairo(cairo_t *cairo) { - if (this->cairo != NULL) { + if (this->cairo != nullptr) { cairo_status_t status = cairo_status (this->cairo); if (status) { error(errInternal, -1, "cairo context error: {0:s}\n", cairo_status_to_string(status)); @@ -198,20 +211,21 @@ void CairoOutputDev::setCairo(cairo_t *cairo) cairo_destroy (this->cairo); assert(!cairo_shape); } - if (cairo != NULL) { + if (cairo != nullptr) { this->cairo = cairo_reference (cairo); /* save the initial matrix so that we can use it for type3 fonts. */ //XXX: is this sufficient? could we miss changes to the matrix somehow? cairo_get_matrix(cairo, &orig_matrix); + setContextAntialias(cairo, antialias); } else { - this->cairo = NULL; - this->cairo_shape = NULL; + this->cairo = nullptr; + this->cairo_shape = nullptr; } } void CairoOutputDev::setTextPage(TextPage *text) { - if (this->text) + if (this->text) this->text->decRefCnt(); if (actualText) delete actualText; @@ -220,11 +234,31 @@ void CairoOutputDev::setTextPage(TextPage *text) this->text->incRefCnt(); actualText = new ActualText(text); } else { - this->text = NULL; - actualText = NULL; + this->text = nullptr; + actualText = nullptr; } } +void CairoOutputDev::setAntialias(cairo_antialias_t antialias) +{ + this->antialias = antialias; + if (cairo) + setContextAntialias (cairo, antialias); + if (cairo_shape) + setContextAntialias (cairo_shape, antialias); +} + +void CairoOutputDev::setContextAntialias(cairo_t *cr, cairo_antialias_t antialias) +{ + cairo_font_options_t *font_options; + cairo_set_antialias (cr, antialias); + font_options = cairo_font_options_create (); + cairo_get_font_options (cr, font_options); + cairo_font_options_set_antialias (font_options, antialias); + cairo_set_font_options (cr, font_options); + cairo_font_options_destroy (font_options); +} + void CairoOutputDev::startDoc(PDFDoc *docA, CairoFontEngine *parentFontEngine) { doc = docA; @@ -252,7 +286,7 @@ void CairoOutputDev::startPage(int pageNum, GfxState *state, XRef *xrefA) { if (text) text->startPage(state); - if (xrefA != NULL) { + if (xrefA != nullptr) { xref = xrefA; } } @@ -275,6 +309,9 @@ void CairoOutputDev::saveState(GfxState *state) { ms->mask_matrix = mask_matrix; ms->next = maskStack; maskStack = ms; + + if (strokePathClip) + strokePathClip->ref_count++; } void CairoOutputDev::restoreState(GfxState *state) { @@ -283,6 +320,8 @@ void CairoOutputDev::restoreState(GfxState *state) { if (cairo_shape) cairo_restore (cairo_shape); + text_matrix_valid = gTrue; + /* These aren't restored by cairo_restore() since we keep them in * the output device. */ updateFillColor(state); @@ -300,6 +339,14 @@ void CairoOutputDev::restoreState(GfxState *state) { maskStack = ms->next; delete ms; } + + if (strokePathClip && --strokePathClip->ref_count == 0) { + delete strokePathClip->path; + if (strokePathClip->dashes) + gfree (strokePathClip->dashes); + gfree (strokePathClip); + strokePathClip = nullptr; + } } void CairoOutputDev::updateAll(GfxState *state) { @@ -622,7 +669,7 @@ void CairoOutputDev::updateFont(GfxState *state) { //FIXME: use cairo font engine? if (text) text->updateFont(state); - + currentFont = fontEngine->getFont (state->getFont(), doc, printing, xref); if (!currentFont) @@ -633,7 +680,7 @@ void CairoOutputDev::updateFont(GfxState *state) { use_show_text_glyphs = state->getFont()->hasToUnicodeCMap() && cairo_surface_has_show_text_glyphs (cairo_get_target (cairo)); - + double fontSize = state->getFontSize(); double *m = state->getTextMat(); /* NOTE: adjusting by a constant is hack. The correct solution @@ -657,11 +704,13 @@ void CairoOutputDev::updateFont(GfxState *state) { */ invert_matrix = matrix; if (cairo_matrix_invert(&invert_matrix)) { - error(errSyntaxWarning, -1, "font matrix not invertible\n"); + error(errSyntaxWarning, -1, "font matrix not invertible"); + text_matrix_valid = gFalse; return; } cairo_set_font_matrix (cairo, &matrix); + text_matrix_valid = gTrue; } /* Tolerance in pixels for checking if strokes are horizontal or vertical @@ -773,7 +822,14 @@ void CairoOutputDev::stroke(GfxState *state) { align_stroke_coords = gFalse; cairo_set_source (cairo, stroke_pattern); LOG(printf ("stroke\n")); - cairo_stroke (cairo); + if (strokePathClip) { + cairo_push_group (cairo); + cairo_stroke (cairo); + cairo_pop_group_to_source (cairo); + fillToStrokePathClip (state); + } else { + cairo_stroke (cairo); + } if (cairo_shape) { doPath (cairo_shape, state, state->getPath()); cairo_stroke (cairo_shape); @@ -796,6 +852,11 @@ void CairoOutputDev::fill(GfxState *state) { if (mask) { cairo_save (cairo); cairo_clip (cairo); + if (strokePathClip) { + cairo_push_group (cairo); + fillToStrokePathClip (state); + cairo_pop_group_to_source (cairo); + } cairo_set_matrix (cairo, &mask_matrix); cairo_mask (cairo, mask); cairo_restore (cairo); @@ -816,8 +877,16 @@ void CairoOutputDev::eoFill(GfxState *state) { cairo_set_fill_rule (cairo, CAIRO_FILL_RULE_EVEN_ODD); cairo_set_source (cairo, fill_pattern); LOG(printf ("fill-eo\n")); - cairo_fill (cairo); + if (mask) { + cairo_save (cairo); + cairo_clip (cairo); + cairo_set_matrix (cairo, &mask_matrix); + cairo_mask (cairo, mask); + cairo_restore (cairo); + } else { + cairo_fill (cairo); + } if (cairo_shape) { cairo_set_fill_rule (cairo_shape, CAIRO_FILL_RULE_EVEN_ODD); doPath (cairo_shape, state, state->getPath()); @@ -837,11 +906,15 @@ GBool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat cairo_pattern_t *pattern; cairo_surface_t *surface; cairo_matrix_t matrix; + cairo_matrix_t pattern_matrix; cairo_t *old_cairo; double xMin, yMin, xMax, yMax; double width, height; + double scaleX, scaleY; int surface_width, surface_height; StrokePathClip *strokePathTmp; + GBool adjusted_stroke_width_tmp; + cairo_pattern_t *maskTmp; width = bbox[2] - bbox[0]; height = bbox[3] - bbox[1]; @@ -850,8 +923,20 @@ GBool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat return gFalse; /* TODO: implement the other cases here too */ - surface_width = (int) ceil (width); - surface_height = (int) ceil (height); + // Find the width and height of the transformed pattern + cairo_get_matrix (cairo, &matrix); + cairo_matrix_init (&pattern_matrix, mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); + cairo_matrix_multiply (&matrix, &matrix, &pattern_matrix); + + double widthX = width, widthY = 0; + cairo_matrix_transform_distance (&matrix, &widthX, &widthY); + surface_width = ceil (sqrt (widthX * widthX + widthY * widthY)); + + double heightX = 0, heightY = height; + cairo_matrix_transform_distance (&matrix, &heightX, &heightY); + surface_height = ceil (sqrt (heightX * heightX + heightY * heightY)); + scaleX = surface_width / width; + scaleY = surface_height / height; surface = cairo_surface_create_similar (cairo_get_target (cairo), CAIRO_CONTENT_COLOR_ALPHA, @@ -862,12 +947,19 @@ GBool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat old_cairo = cairo; cairo = cairo_create (surface); cairo_surface_destroy (surface); + setContextAntialias(cairo, antialias); box.x1 = bbox[0]; box.y1 = bbox[1]; box.x2 = bbox[2]; box.y2 = bbox[3]; + cairo_scale (cairo, scaleX, scaleY); + cairo_translate (cairo, -box.x1, -box.y1); + strokePathTmp = strokePathClip; - strokePathClip = NULL; - gfx = new Gfx(doc, this, resDict, &box, NULL, NULL, NULL, gfxA->getXRef()); + strokePathClip = nullptr; + adjusted_stroke_width_tmp = adjusted_stroke_width; + maskTmp = mask; + mask = nullptr; + gfx = new Gfx(doc, this, resDict, &box, nullptr, nullptr, nullptr, gfxA->getXRef()); if (paintType == 2) inUncoloredPattern = gTrue; gfx->display(str); @@ -875,6 +967,8 @@ GBool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat inUncoloredPattern = gFalse; delete gfx; strokePathClip = strokePathTmp; + adjusted_stroke_width = adjusted_stroke_width_tmp; + mask = maskTmp; pattern = cairo_pattern_create_for_surface (cairo_get_target (cairo)); cairo_destroy (cairo); @@ -885,11 +979,11 @@ GBool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); cairo_rectangle (cairo, xMin, yMin, xMax - xMin, yMax - yMin); - cairo_matrix_init_scale (&matrix, surface_width / width, surface_height / height); + cairo_matrix_init_scale (&matrix, scaleX, scaleY); + cairo_matrix_translate (&matrix, -box.x1, -box.y1); cairo_pattern_set_matrix (pattern, &matrix); - cairo_matrix_init (&matrix, mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); - cairo_transform (cairo, &matrix); + cairo_transform (cairo, &pattern_matrix); cairo_set_source (cairo, pattern); cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT); if (strokePathClip) { @@ -903,6 +997,107 @@ GBool CairoOutputDev::tilingPatternFill(GfxState *state, Gfx *gfxA, Catalog *cat return gTrue; } +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) +GBool CairoOutputDev::functionShadedFill(GfxState *state, GfxFunctionShading *shading) +{ + // Function shaded fills are subdivided to rectangles that are the + // following size in device space. Note when printing this size is + // in points. + const int subdivide_pixels = 10; + + double x_begin, x_end, x1, x2; + double y_begin, y_end, y1, y2; + double x_step; + double y_step; + GfxColor color; + GfxRGB rgb; + double *matrix; + cairo_matrix_t mat; + + matrix = shading->getMatrix(); + mat.xx = matrix[0]; + mat.yx = matrix[1]; + mat.xy = matrix[2]; + mat.yy = matrix[3]; + mat.x0 = matrix[4]; + mat.y0 = matrix[5]; + if (cairo_matrix_invert(&mat)) { + error(errSyntaxWarning, -1, "matrix not invertible\n"); + return gFalse; + } + + // get cell size in pattern space + x_step = y_step = subdivide_pixels; + cairo_matrix_transform_distance (&mat, &x_step, &y_step); + + cairo_pattern_destroy(fill_pattern); + fill_pattern = cairo_pattern_create_mesh (); + cairo_pattern_set_matrix(fill_pattern, &mat); + shading->getDomain(&x_begin, &y_begin, &x_end, &y_end); + + for (x1 = x_begin; x1 < x_end; x1 += x_step) { + x2 = x1 + x_step; + if (x2 > x_end) + x2 = x_end; + + for (y1 = y_begin; y1 < y_end; y1 += y_step) { + y2 = y1 + y_step; + if (y2 > y_end) + y2 = y_end; + + cairo_mesh_pattern_begin_patch (fill_pattern); + cairo_mesh_pattern_move_to (fill_pattern, x1, y1); + cairo_mesh_pattern_line_to (fill_pattern, x2, y1); + cairo_mesh_pattern_line_to (fill_pattern, x2, y2); + cairo_mesh_pattern_line_to (fill_pattern, x1, y2); + + shading->getColor(x1, y1, &color); + shading->getColorSpace()->getRGB(&color, &rgb); + cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, 0, + colToDbl(rgb.r), + colToDbl(rgb.g), + colToDbl(rgb.b)); + + shading->getColor(x2, y1, &color); + shading->getColorSpace()->getRGB(&color, &rgb); + cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, 1, + colToDbl(rgb.r), + colToDbl(rgb.g), + colToDbl(rgb.b)); + + shading->getColor(x2, y2, &color); + shading->getColorSpace()->getRGB(&color, &rgb); + cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, 2, + colToDbl(rgb.r), + colToDbl(rgb.g), + colToDbl(rgb.b)); + + shading->getColor(x1, y2, &color); + shading->getColorSpace()->getRGB(&color, &rgb); + cairo_mesh_pattern_set_corner_color_rgb (fill_pattern, 3, + colToDbl(rgb.r), + colToDbl(rgb.g), + colToDbl(rgb.b)); + + cairo_mesh_pattern_end_patch (fill_pattern); + } + } + + double xMin, yMin, xMax, yMax; + // get the clip region bbox + state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); + state->moveTo(xMin, yMin); + state->lineTo(xMin, yMax); + state->lineTo(xMax, yMax); + state->lineTo(xMax, yMin); + state->closePath(); + fill(state); + state->clearPath(); + + return gTrue; +} +#endif /* CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) */ + GBool CairoOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax) { double x0, y0, x1, y1; double dx, dy; @@ -934,18 +1129,32 @@ GBool CairoOutputDev::axialShadedSupportExtend(GfxState *state, GfxAxialShading GBool CairoOutputDev::radialShadedFill(GfxState *state, GfxRadialShading *shading, double sMin, double sMax) { double x0, y0, r0, x1, y1, r1; double dx, dy, dr; + cairo_matrix_t matrix; + double scale; shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1); dx = x1 - x0; dy = y1 - y0; dr = r1 - r0; + + // Cairo/pixman do not work well with a very large or small scaled + // matrix. See cairo bug #81657. + // + // As a workaround, scale the pattern by the average of the vertical + // and horizontal scaling of the current transformation matrix. + cairo_get_matrix(cairo, &matrix); + scale = (sqrt(matrix.xx * matrix.xx + matrix.yx * matrix.yx) + + sqrt(matrix.xy * matrix.xy + matrix.yy * matrix.yy)) / 2; + cairo_matrix_init_scale(&matrix, scale, scale); + cairo_pattern_destroy(fill_pattern); - fill_pattern = cairo_pattern_create_radial (x0 + sMin * dx, - y0 + sMin * dy, - r0 + sMin * dr, - x0 + sMax * dx, - y0 + sMax * dy, - r0 + sMax * dr); + fill_pattern = cairo_pattern_create_radial ((x0 + sMin * dx) * scale, + (y0 + sMin * dy) * scale, + (r0 + sMin * dr) * scale, + (x0 + sMax * dx) * scale, + (y0 + sMax * dy) * scale, + (r0 + sMax * dr) * scale); + cairo_pattern_set_matrix(fill_pattern, &matrix); if (shading->getExtend0() && shading->getExtend1()) cairo_pattern_set_extend (fill_pattern, CAIRO_EXTEND_PAD); else @@ -1147,11 +1356,12 @@ void CairoOutputDev::clipToStrokePath(GfxState *state) { strokePathClip->dashes = (double*) gmallocn (sizeof(double), strokePathClip->dash_count); cairo_get_dash (cairo, strokePathClip->dashes, &strokePathClip->dash_offset); } else { - strokePathClip->dashes = NULL; + strokePathClip->dashes = nullptr; } strokePathClip->cap = cairo_get_line_cap (cairo); strokePathClip->join = cairo_get_line_join (cairo); strokePathClip->miter = cairo_get_miter_limit (cairo); + strokePathClip->ref_count = 1; } void CairoOutputDev::fillToStrokePathClip(GfxState *state) { @@ -1159,7 +1369,6 @@ void CairoOutputDev::fillToStrokePathClip(GfxState *state) { cairo_set_matrix (cairo, &strokePathClip->ctm); cairo_set_line_width (cairo, strokePathClip->line_width); - strokePathClip->dash_count = cairo_get_dash_count (cairo); cairo_set_dash (cairo, strokePathClip->dashes, strokePathClip->dash_count, strokePathClip->dash_offset); cairo_set_line_cap (cairo, strokePathClip->cap); cairo_set_line_join (cairo, strokePathClip->join); @@ -1168,12 +1377,6 @@ void CairoOutputDev::fillToStrokePathClip(GfxState *state) { cairo_stroke (cairo); cairo_restore (cairo); - - delete strokePathClip->path; - if (strokePathClip->dashes) - gfree (strokePathClip->dashes); - gfree (strokePathClip); - strokePathClip = NULL; } void CairoOutputDev::beginString(GfxState *state, GooString *s) @@ -1250,10 +1453,8 @@ void CairoOutputDev::endString(GfxState *state) // ignore empty strings and invisible text -- this is used by // Acrobat Capture render = state->getRender(); - if (render == 3 || glyphCount == 0) { - gfree(glyphs); - glyphs = NULL; - return; + if (render == 3 || glyphCount == 0 || !text_matrix_valid) { + goto finish; } if (!(render & 1)) { @@ -1292,11 +1493,11 @@ void CairoOutputDev::endString(GfxState *state) } cairo_path_destroy (textClipPath); } - + // append the glyph path cairo_glyph_path (cairo, glyphs, glyphCount); - - // move the path back into textClipPath + + // move the path back into textClipPath // and clear the current path textClipPath = cairo_copy_path (cairo); cairo_new_path (cairo); @@ -1305,13 +1506,14 @@ void CairoOutputDev::endString(GfxState *state) } } +finish: gfree (glyphs); - glyphs = NULL; + glyphs = nullptr; if (use_show_text_glyphs) { gfree (clusters); - clusters = NULL; + clusters = nullptr; gfree (utf8); - utf8 = NULL; + utf8 = nullptr; } } @@ -1319,6 +1521,7 @@ void CairoOutputDev::endString(GfxState *state) GBool CairoOutputDev::beginType3Char(GfxState *state, double x, double y, double dx, double dy, CharCode code, Unicode *u, int uLen) { + cairo_save (cairo); double *ctm; cairo_matrix_t matrix; @@ -1381,7 +1584,7 @@ void CairoOutputDev::endTextObject(GfxState *state) { cairo_clip (cairo_shape); } cairo_path_destroy (textClipPath); - textClipPath = NULL; + textClipPath = nullptr; } } @@ -1412,29 +1615,15 @@ static inline int splashFloor(SplashCoord x) { static cairo_surface_t *cairo_surface_create_similar_clip (cairo_t *cairo, cairo_content_t content) { - double x1, y1, x2, y2; - int width, height; - cairo_clip_extents (cairo, &x1, &y1, &x2, &y2); - cairo_matrix_t matrix; - cairo_get_matrix (cairo, &matrix); - //cairo_matrix_transform_point(&matrix, &x1, &y1); - //cairo_matrix_transform_point(&matrix, &x2, &y2);*/ - cairo_user_to_device(cairo, &x1, &y1); - cairo_user_to_device(cairo, &x2, &y2); - width = splashCeil(x2) - splashFloor(x1); - //XXX: negative matrix - ////height = splashCeil(y2) - splashFloor(y1); - height = splashFloor(y1) - splashCeil(y2); - cairo_surface_t *target = cairo_get_target (cairo); - cairo_surface_t *result; + cairo_pattern_t *pattern; + cairo_surface_t *surface = nullptr; - result = cairo_surface_create_similar (target, content, width, height); - double x_offset, y_offset; - cairo_surface_get_device_offset(target, &x_offset, &y_offset); - cairo_surface_set_device_offset(result, x_offset, y_offset); - - - return result; + cairo_push_group_with_content (cairo, content); + pattern = cairo_pop_group (cairo); + cairo_pattern_get_surface (pattern, &surface); + cairo_surface_reference (surface); + cairo_pattern_destroy (pattern); + return surface; } @@ -1460,6 +1649,7 @@ void CairoOutputDev::beginTransparencyGroup(GfxState * /*state*/, double * /*bbo cairo_surface_t *cairo_shape_surface = cairo_surface_create_similar_clip (cairo, CAIRO_CONTENT_ALPHA); cairo_shape = cairo_create (cairo_shape_surface); cairo_surface_destroy (cairo_shape_surface); + setContextAntialias(cairo_shape, antialias); /* the color doesn't matter as long as it is opaque */ cairo_set_source_rgb (cairo_shape, 0, 0, 0); @@ -1467,8 +1657,6 @@ void CairoOutputDev::beginTransparencyGroup(GfxState * /*state*/, double * /*bbo cairo_get_matrix (cairo, &matrix); //printMatrix(&matrix); cairo_set_matrix (cairo_shape, &matrix); - } else { - cairo_reference (cairo_shape); } } if (groupColorSpaceStack->next && groupColorSpaceStack->next->knockout) { @@ -1508,33 +1696,22 @@ void CairoOutputDev::paintTransparencyGroup(GfxState * /*state*/, double * /*bbo cairo_save (cairo); cairo_set_matrix (cairo, &groupColorSpaceStack->group_matrix); + + if (shape) { + /* OPERATOR_SOURCE w/ a mask is defined as (src IN mask) ADD (dest OUT mask) + * however our source has already been clipped to mask so we only need to + * do ADD and OUT */ + + /* clear the shape mask */ + cairo_set_source (cairo, shape); + cairo_set_operator (cairo, CAIRO_OPERATOR_DEST_OUT); + cairo_paint (cairo); + cairo_set_operator (cairo, CAIRO_OPERATOR_ADD); + } cairo_set_source (cairo, group); if (!mask) { - //XXX: deal with mask && shape case - if (shape) { - cairo_save (cairo); - - /* OPERATOR_SOURCE w/ a mask is defined as (src IN mask) ADD (dest OUT mask) - * however our source has already been clipped to mask so we only need to - * do ADD and OUT */ - - /* clear the shape mask */ - cairo_set_source (cairo, shape); - cairo_set_operator (cairo, CAIRO_OPERATOR_DEST_OUT); - cairo_paint (cairo); - - cairo_set_operator (cairo, CAIRO_OPERATOR_ADD); - cairo_set_source (cairo, group); - cairo_paint (cairo); - - cairo_restore (cairo); - - cairo_pattern_destroy (shape); - shape = NULL; - } else { - cairo_paint_with_alpha (cairo, fill_opacity); - } + cairo_paint_with_alpha (cairo, fill_opacity); cairo_status_t status = cairo_status(cairo); if (status) printf("BAD status: %s\n", cairo_status_to_string(status)); @@ -1551,7 +1728,17 @@ void CairoOutputDev::paintTransparencyGroup(GfxState * /*state*/, double * /*bbo cairo_paint_with_alpha (cairo, fill_opacity); } cairo_pattern_destroy(mask); - mask = NULL; + mask = nullptr; + } + + if (shape) { + if (cairo_shape) { + cairo_set_source (cairo_shape, shape); + cairo_paint (cairo_shape); + cairo_set_source_rgb (cairo_shape, 0, 0, 0); + } + cairo_pattern_destroy (shape); + shape = nullptr; } popTransparencyGroup(); @@ -1617,9 +1804,10 @@ void CairoOutputDev::setSoftMask(GfxState * state, double * bbox, GBool alpha, cairo_surface_t *source = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); cairo_t *maskCtx = cairo_create(source); + setContextAntialias(maskCtx, antialias); //XXX: hopefully this uses the correct color space */ - if (!alpha) { + if (!alpha && groupColorSpaceStack->cs) { GfxRGB backdropColorRGB; groupColorSpaceStack->cs->getRGB(backdropColor, &backdropColorRGB); /* paint the backdrop */ @@ -1651,7 +1839,7 @@ void CairoOutputDev::setSoftMask(GfxState * state, double * bbox, GBool alpha, /* convert to a luminocity map */ uint32_t *source_data = (uint32_t*)cairo_image_surface_get_data(source); /* get stride in units of 32 bits */ - int stride = cairo_image_surface_get_stride(source)/4; + ptrdiff_t stride = cairo_image_surface_get_stride(source)/4; for (int y=0; ynext; @@ -1709,7 +1897,7 @@ void CairoOutputDev::popTransparencyGroup() { void CairoOutputDev::clearSoftMask(GfxState * /*state*/) { if (mask) cairo_pattern_destroy(mask); - mask = NULL; + mask = nullptr; } /* Taken from cairo/doc/tutorial/src/singular.c */ @@ -1782,7 +1970,7 @@ CairoOutputDev::getFilterForSurface(cairo_surface_t *image, GBool interpolate) { if (interpolate) - return CAIRO_FILTER_BILINEAR; + return CAIRO_FILTER_BEST; int orig_width = cairo_image_surface_get_width (image); int orig_height = cairo_image_surface_get_height (image); @@ -1802,7 +1990,7 @@ CairoOutputDev::getFilterForSurface(cairo_surface_t *image, if (scaled_width / orig_width >= 4 || scaled_height / orig_height >= 4) return CAIRO_FILTER_NEAREST; - return CAIRO_FILTER_BILINEAR; + return CAIRO_FILTER_BEST; } void CairoOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, @@ -1845,7 +2033,7 @@ void CairoOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, cairo_matrix_t matrix; cairo_get_matrix (cairo, &matrix); - //XXX: it is possible that we should only do sub pixel positioning if + //XXX: it is possible that we should only do sub pixel positioning if // we are rendering fonts */ if (!printing && prescaleImages /* not rotated */ @@ -1879,45 +2067,43 @@ void CairoOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stre delete imgStr; invert_bit = invert ? 1 : 0; - if (pix ^ invert_bit) - return; - - cairo_save (cairo); - cairo_rectangle (cairo, 0., 0., width, height); - cairo_fill (cairo); - cairo_restore (cairo); - if (cairo_shape) { - cairo_save (cairo_shape); - cairo_rectangle (cairo_shape, 0., 0., width, height); - cairo_fill (cairo_shape); - cairo_restore (cairo_shape); + if (!(pix ^ invert_bit)) { + cairo_save (cairo); + cairo_rectangle (cairo, 0., 0., width, height); + cairo_fill (cairo); + cairo_restore (cairo); + if (cairo_shape) { + cairo_save (cairo_shape); + cairo_rectangle (cairo_shape, 0., 0., width, height); + cairo_fill (cairo_shape); + cairo_restore (cairo_shape); + } } - return; - } - - cairo_push_group_with_content (cairo, CAIRO_CONTENT_ALPHA); - - /* shape is 1.0 for painted areas, 0.0 for unpainted ones */ - - cairo_matrix_t matrix; - cairo_get_matrix (cairo, &matrix); - //XXX: it is possible that we should only do sub pixel positioning if - // we are rendering fonts */ - if (!printing && prescaleImages && matrix.xy == 0.0 && matrix.yx == 0.0) { - drawImageMaskPrescaled(state, ref, str, width, height, invert, gFalse, inlineImg); } else { - drawImageMaskRegular(state, ref, str, width, height, invert, gFalse, inlineImg); - } + cairo_push_group_with_content (cairo, CAIRO_CONTENT_ALPHA); - if (state->getFillColorSpace()->getMode() == csPattern) { - cairo_set_source_rgb (cairo, 1, 1, 1); - cairo_set_matrix (cairo, &mask_matrix); - cairo_mask (cairo, mask); - } + /* shape is 1.0 for painted areas, 0.0 for unpainted ones */ - if (mask) - cairo_pattern_destroy (mask); - mask = cairo_pop_group (cairo); + cairo_matrix_t matrix; + cairo_get_matrix (cairo, &matrix); + //XXX: it is possible that we should only do sub pixel positioning if + // we are rendering fonts */ + if (!printing && prescaleImages && matrix.xy == 0.0 && matrix.yx == 0.0) { + drawImageMaskPrescaled(state, ref, str, width, height, invert, gFalse, inlineImg); + } else { + drawImageMaskRegular(state, ref, str, width, height, invert, gFalse, inlineImg); + } + + if (state->getFillColorSpace()->getMode() == csPattern) { + cairo_set_source_rgb (cairo, 1, 1, 1); + cairo_set_matrix (cairo, &mask_matrix); + cairo_mask (cairo, mask); + } + + if (mask) + cairo_pattern_destroy (mask); + mask = cairo_pop_group (cairo); + } saveState(state); double bbox[4] = {0,0,1,1}; // dummy @@ -1946,7 +2132,7 @@ void CairoOutputDev::drawImageMaskRegular(GfxState *state, Object *ref, Stream * Guchar *pix; cairo_matrix_t matrix; int invert_bit; - int row_stride; + ptrdiff_t row_stride; cairo_filter_t filter; /* TODO: Do we want to cache these? */ @@ -1997,9 +2183,6 @@ void CairoOutputDev::drawImageMaskRegular(GfxState *state, Object *ref, Stream * cairo_pattern_set_filter (pattern, filter); - if (!printing) - cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD); - cairo_matrix_init_translate (&matrix, 0, height); cairo_matrix_scale (&matrix, width, -height); cairo_pattern_set_matrix (pattern, &matrix); @@ -2015,6 +2198,11 @@ void CairoOutputDev::drawImageMaskRegular(GfxState *state, Object *ref, Stream * cairo_save (cairo); cairo_rectangle (cairo, 0., 0., 1., 1.); cairo_clip (cairo); + if (strokePathClip) { + cairo_push_group (cairo); + fillToStrokePathClip (state); + cairo_pop_group_to_source (cairo); + } cairo_mask (cairo, pattern); cairo_restore (cairo); } else { @@ -2051,7 +2239,7 @@ void CairoOutputDev::drawImageMaskPrescaled(GfxState *state, Object *ref, Stream Guchar *pix; cairo_matrix_t matrix; int invert_bit; - int row_stride; + ptrdiff_t row_stride; /* cairo does a very poor job of scaling down images so we scale them ourselves */ @@ -2271,7 +2459,6 @@ void CairoOutputDev::drawImageMaskPrescaled(GfxState *state, Object *ref, Stream * images with CAIRO_FILTER_NEAREST to look really bad */ cairo_pattern_set_filter (pattern, interpolate ? CAIRO_FILTER_BEST : CAIRO_FILTER_FAST); - cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD); if (state->getFillColorSpace()->getMode() == csPattern) { cairo_matrix_init_translate (&matrix, 0, scaledHeight); @@ -2300,6 +2487,11 @@ void CairoOutputDev::drawImageMaskPrescaled(GfxState *state, Object *ref, Stream cairo_rectangle (cairo, 0., 0., scaledWidth, scaledHeight); cairo_clip (cairo); + if (strokePathClip) { + cairo_push_group (cairo); + fillToStrokePathClip (state); + cairo_pop_group_to_source (cairo); + } cairo_mask (cairo, pattern); //cairo_get_matrix(cairo, &matrix); @@ -2340,7 +2532,7 @@ void CairoOutputDev::drawMaskedImage(GfxState *state, Object *ref, GBool maskInterpolate) { ImageStream *maskImgStr, *imgStr; - int row_stride; + ptrdiff_t row_stride; unsigned char *maskBuffer, *buffer; unsigned char *maskDest; unsigned int *dest; @@ -2396,7 +2588,7 @@ void CairoOutputDev::drawMaskedImage(GfxState *state, Object *ref, * so check its underlying color space as well */ int is_identity_transform; is_identity_transform = colorMap->getColorSpace()->getMode() == csDeviceRGB || - (colorMap->getColorSpace()->getMode() == csICCBased && + (colorMap->getColorSpace()->getMode() == csICCBased && ((GfxICCBasedColorSpace*)colorMap->getColorSpace())->getAlt()->getMode() == csDeviceRGB); #endif @@ -2498,7 +2690,7 @@ void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *s GBool maskInterpolate) { ImageStream *maskImgStr, *imgStr; - int row_stride; + ptrdiff_t row_stride; unsigned char *maskBuffer, *buffer; unsigned char *maskDest; unsigned int *dest; @@ -2527,7 +2719,9 @@ void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *s for (y = 0; y < maskHeight; y++) { maskDest = (unsigned char *) (maskBuffer + y * row_stride); pix = maskImgStr->getLine(); - maskColorMap->getGrayLine (pix, maskDest, maskWidth); + if (likely(pix != nullptr)) { + maskColorMap->getGrayLine (pix, maskDest, maskWidth); + } } maskImgStr->close(); @@ -2572,7 +2766,7 @@ void CairoOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *s cairo_surface_mark_dirty (image); - setMimeData(str, ref, image); + setMimeData(state, str, ref, colorMap, image, height); pattern = cairo_pattern_create_for_surface (image); cairo_surface_destroy (image); @@ -2675,49 +2869,200 @@ GBool CairoOutputDev::getStreamData (Stream *str, char **buffer, int *length) return gTrue; } -void CairoOutputDev::setMimeData(Stream *str, Object *ref, cairo_surface_t *image) +static GBool colorMapHasIdentityDecodeMap(GfxImageColorMap *colorMap) +{ + for (int i = 0; i < colorMap->getNumPixelComps(); i++) { + if (colorMap->getDecodeLow(i) != 0.0 || colorMap->getDecodeHigh(i) != 1.0) + return gFalse; + } + return gTrue; +} + +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2) +static cairo_status_t setMimeIdFromRef(cairo_surface_t *surface, + const char *mime_type, + const char *mime_id_prefix, + Ref ref) +{ + GooString *mime_id; + char *idBuffer; + cairo_status_t status; + + mime_id = new GooString; + + if (mime_id_prefix) + mime_id->append(mime_id_prefix); + + mime_id->appendf("{0:d}-{1:d}", ref.gen, ref.num); + + idBuffer = copyString(mime_id->getCString()); + status = cairo_surface_set_mime_data (surface, mime_type, + (const unsigned char *)idBuffer, + mime_id->getLength(), + gfree, idBuffer); + delete mime_id; + if (status) + gfree (idBuffer); + return status; +} +#endif + +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) +GBool CairoOutputDev::setMimeDataForJBIG2Globals(Stream *str, + cairo_surface_t *image) +{ + JBIG2Stream *jb2Str = static_cast(str); + Object* globalsStr = jb2Str->getGlobalsStream(); + char *globalsBuffer; + int globalsLength; + + // nothing to do for JBIG2 stream without Globals + if (!globalsStr->isStream()) + return gTrue; + + if (setMimeIdFromRef(image, CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID, nullptr, + jb2Str->getGlobalsStreamRef())) + return gFalse; + + if (!getStreamData(globalsStr->getStream(), &globalsBuffer, &globalsLength)) + return gFalse; + + if (cairo_surface_set_mime_data (image, CAIRO_MIME_TYPE_JBIG2_GLOBAL, + (const unsigned char*)globalsBuffer, + globalsLength, + gfree, (void*)globalsBuffer)) + { + gfree (globalsBuffer); + return gFalse; + } + + return gTrue; +} +#endif + +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10) +GBool CairoOutputDev::setMimeDataForCCITTParams(Stream *str, + cairo_surface_t *image, int height) +{ + CCITTFaxStream *ccittStr = static_cast(str); + + GooString params; + params.appendf("Columns={0:d}", ccittStr->getColumns()); + params.appendf(" Rows={0:d}", height); + params.appendf(" K={0:d}", ccittStr->getEncoding()); + params.appendf(" EndOfLine={0:d}", ccittStr->getEndOfLine() ? 1 : 0); + params.appendf(" EncodedByteAlign={0:d}", ccittStr->getEncodedByteAlign() ? 1 : 0); + params.appendf(" EndOfBlock={0:d}", ccittStr->getEndOfBlock() ? 1 : 0); + params.appendf(" BlackIs1={0:d}", ccittStr->getBlackIs1() ? 1 : 0); + params.appendf(" DamagedRowsBeforeError={0:d}", ccittStr->getDamagedRowsBeforeError()); + + char *p = strdup(params.getCString()); + if (cairo_surface_set_mime_data (image, CAIRO_MIME_TYPE_CCITT_FAX_PARAMS, + (const unsigned char*)p, + params.getLength(), + gfree, (void*)p)) + { + gfree (p); + return gFalse; + } + + return gTrue; +} +#endif + +void CairoOutputDev::setMimeData(GfxState *state, Stream *str, Object *ref, + GfxImageColorMap *colorMap, cairo_surface_t *image, int height) { char *strBuffer; int len; Object obj; + GfxColorSpace *colorSpace; + StreamKind strKind = str->getKind(); + const char *mime_type; - if (!printing || !(str->getKind() == strDCT || str->getKind() == strJPX)) + if (!printing) return; - // colorspace in stream dict may be different from colorspace in jpx - // data - if (str->getKind() == strJPX) { - GBool hasColorSpace = !str->getDict()->lookup("ColorSpace", &obj)->isNull(); - obj.free(); - if (hasColorSpace) + switch (strKind) { + case strDCT: + mime_type = CAIRO_MIME_TYPE_JPEG; + break; + case strJPX: + mime_type = CAIRO_MIME_TYPE_JP2; + break; +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) + case strJBIG2: + mime_type = CAIRO_MIME_TYPE_JBIG2; + break; +#endif +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10) + case strCCITTFax: + mime_type = CAIRO_MIME_TYPE_CCITT_FAX; + break; +#endif + default: return; } + obj = str->getDict()->lookup("ColorSpace"); + colorSpace = GfxColorSpace::parse(nullptr, &obj, this, state); + + // colorspace in stream dict may be different from colorspace in jpx + // data + if (strKind == strJPX && colorSpace) + return; + + // only embed mime data for gray, rgb, and cmyk colorspaces. + if (colorSpace) { + GfxColorSpaceMode mode = colorSpace->getMode(); + delete colorSpace; + switch (mode) { + case csDeviceGray: + case csCalGray: + case csDeviceRGB: + case csCalRGB: + case csDeviceCMYK: + case csICCBased: + break; + + case csLab: + case csIndexed: + case csSeparation: + case csDeviceN: + case csPattern: + return; + } + } + + if (!colorMapHasIdentityDecodeMap(colorMap)) + return; + +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) + if (strKind == strJBIG2 && !setMimeDataForJBIG2Globals(str, image)) + return; +#endif + +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10) + if (strKind == strCCITTFax && !setMimeDataForCCITTParams(str, image, height)) + return; +#endif + if (getStreamData (str->getNextStream(), &strBuffer, &len)) { - cairo_status_t st; + cairo_status_t status = CAIRO_STATUS_SUCCESS; #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2) if (ref && ref->isRef()) { - Ref imgRef = ref->getRef(); - GooString *surfaceId = new GooString("poppler-surface-"); - surfaceId->appendf("{0:d}-{1:d}", imgRef.gen, imgRef.num); - char *idBuffer = copyString(surfaceId->getCString()); - st = cairo_surface_set_mime_data (image, CAIRO_MIME_TYPE_UNIQUE_ID, - (const unsigned char *)idBuffer, - surfaceId->getLength(), - gfree, idBuffer); - if (st) - gfree(idBuffer); - delete surfaceId; + status = setMimeIdFromRef(image, CAIRO_MIME_TYPE_UNIQUE_ID, + "poppler-surface-", ref->getRef()); } #endif + if (!status) { + status = cairo_surface_set_mime_data (image, mime_type, + (const unsigned char *)strBuffer, len, + gfree, strBuffer); + } - st = cairo_surface_set_mime_data (image, - str->getKind() == strDCT ? - CAIRO_MIME_TYPE_JPEG : CAIRO_MIME_TYPE_JP2, - (const unsigned char *)strBuffer, len, - gfree, strBuffer); - if (st) + if (status) gfree (strBuffer); } } @@ -2730,6 +3075,7 @@ private: GfxImageColorMap *colorMap; int *maskColors; int current_row; + GBool imageError; public: cairo_surface_t *getSourceImage(Stream *str, @@ -2738,14 +3084,15 @@ public: GBool printing, GfxImageColorMap *colorMapA, int *maskColorsA) { - cairo_surface_t *image = NULL; + cairo_surface_t *image = nullptr; int i; - lookup = NULL; + lookup = nullptr; colorMap = colorMapA; maskColors = maskColorsA; width = widthA; current_row = -1; + imageError = gFalse; /* TODO: Do we want to cache these? */ imgStr = new ImageStream(str, width, @@ -2777,10 +3124,31 @@ public: } } - if (printing || scaledWidth >= width || scaledHeight >= height) { +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) + bool needsCustomDownscaling = false; +#else + bool needsCustomDownscaling = true; +#endif + + if (printing) { + if (width > MAX_PRINT_IMAGE_SIZE || height > MAX_PRINT_IMAGE_SIZE) { + if (width > height) { + scaledWidth = MAX_PRINT_IMAGE_SIZE; + scaledHeight = MAX_PRINT_IMAGE_SIZE * (double)height/width; + } else { + scaledHeight = MAX_PRINT_IMAGE_SIZE; + scaledWidth = MAX_PRINT_IMAGE_SIZE * (double)width/height; + } + needsCustomDownscaling = true; + } else { + needsCustomDownscaling = false; + } + } + + if (!needsCustomDownscaling || scaledWidth >= width || scaledHeight >= height) { // No downscaling. Create cairo image containing the source image data. unsigned char *buffer; - int stride; + ptrdiff_t stride; image = cairo_image_surface_create (maskColors ? CAIRO_FORMAT_ARGB32 : @@ -2824,7 +3192,7 @@ public: return image; } - void getRow(int row_num, uint32_t *row_data) { + void getRow(int row_num, uint32_t *row_data) override { int i; Guchar *pix; @@ -2836,7 +3204,13 @@ public: current_row++; } - if (lookup) { + if (unlikely(pix == nullptr)) { + memset(row_data, 0, width*4); + if (!imageError) { + error(errInternal, -1, "Bad image stream"); + imageError = gTrue; + } + } else if (lookup) { Guchar *p = pix; GfxRGB rgb; @@ -2885,7 +3259,7 @@ void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, cairo_matrix_t matrix; int width, height; int scaledWidth, scaledHeight; - cairo_filter_t filter = CAIRO_FILTER_BILINEAR; + cairo_filter_t filter = CAIRO_FILTER_BEST; RescaleDrawImage rescale; LOG (printf ("drawImage %dx%d\n", widthA, heightA)); @@ -2901,8 +3275,17 @@ void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, if (width == widthA && height == heightA) filter = getFilterForSurface (image, interpolate); - if (!inlineImg) /* don't read stream twice if it is an inline image */ - setMimeData(str, ref, image); + if (!inlineImg) { /* don't read stream twice if it is an inline image */ + // cairo 1.15.10 allows mime image data to have different size to cairo image + // mime image size will be scaled to same size as cairo image +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10) + bool requireSameSize = false; +#else + bool requireSameSize = true; +#endif + if (!requireSameSize || (width == widthA && height == heightA)) + setMimeData(state, str, ref, colorMap, image, heightA); + } pattern = cairo_pattern_create_for_surface (image); cairo_surface_destroy (image); @@ -2927,7 +3310,7 @@ void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, } else if (mask) { maskPattern = cairo_pattern_reference (mask); } else { - maskPattern = NULL; + maskPattern = nullptr; } cairo_save (cairo); @@ -2937,7 +3320,8 @@ void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, if (maskPattern) { if (!printing) cairo_clip (cairo); - cairo_set_matrix (cairo, &mask_matrix); + if (mask) + cairo_set_matrix (cairo, &mask_matrix); cairo_mask (cairo, maskPattern); } else { if (printing) @@ -2971,11 +3355,11 @@ void CairoOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, CairoImageOutputDev::CairoImageOutputDev() { - images = NULL; + images = nullptr; numImages = 0; size = 0; - imgDrawCbk = NULL; - imgDrawCbkData = NULL; + imgDrawCbk = nullptr; + imgDrawCbkData = nullptr; } CairoImageOutputDev::~CairoImageOutputDev() @@ -2988,7 +3372,7 @@ CairoImageOutputDev::~CairoImageOutputDev() } void CairoImageOutputDev::saveImage(CairoImage *image) -{ +{ if (numImages >= size) { size += 16; images = (CairoImage **) greallocn (images, size, sizeof (CairoImage *)); @@ -3048,7 +3432,7 @@ void CairoImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *st CairoOutputDev::drawImageMask(state, ref, str, width, height, invert, interpolate, inlineImg); image->setImage (surface); - setCairo (NULL); + setCairo (nullptr); cairo_surface_destroy (surface); cairo_destroy (cr); } @@ -3081,7 +3465,7 @@ void CairoImageOutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, } image->setImage (surface); - setCairo (NULL); + setCairo (nullptr); cairo_surface_destroy (surface); cairo_destroy (cr); } @@ -3107,11 +3491,11 @@ void CairoImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, setCairo (cr); cairo_translate (cr, 0, height); cairo_scale (cr, width, -height); - + CairoOutputDev::drawImage(state, ref, str, width, height, colorMap, interpolate, maskColors, inlineImg); image->setImage (surface); - - setCairo (NULL); + + setCairo (nullptr); cairo_surface_destroy (surface); cairo_destroy (cr); } @@ -3142,12 +3526,12 @@ void CairoImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stre setCairo (cr); cairo_translate (cr, 0, height); cairo_scale (cr, width, -height); - + CairoOutputDev::drawSoftMaskedImage(state, ref, str, width, height, colorMap, interpolate, maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate); image->setImage (surface); - - setCairo (NULL); + + setCairo (nullptr); cairo_surface_destroy (surface); cairo_destroy (cr); } @@ -3177,12 +3561,12 @@ void CairoImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream * setCairo (cr); cairo_translate (cr, 0, height); cairo_scale (cr, width, -height); - + CairoOutputDev::drawMaskedImage(state, ref, str, width, height, colorMap, interpolate, maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate); image->setImage (surface); - - setCairo (NULL); + + setCairo (nullptr); cairo_surface_destroy (surface); cairo_destroy (cr); } diff --git a/3rdparty/poppler/git/CairoOutputDev.h b/3rdparty/poppler/git/CairoOutputDev.h index f345e39..451fa78 100644 --- a/3rdparty/poppler/git/CairoOutputDev.h +++ b/3rdparty/poppler/git/CairoOutputDev.h @@ -18,9 +18,12 @@ // Copyright (C) 2005, 2006 Kristian Høgsberg // Copyright (C) 2005 Nickolay V. Shmyrev // Copyright (C) 2006-2011, 2013 Carlos Garcia Campos -// Copyright (C) 2008, 2009, 2011-2013 Adrian Johnson +// Copyright (C) 2008, 2009, 2011-2016 Adrian Johnson // Copyright (C) 2008 Michael Vrable // Copyright (C) 2010-2013 Thomas Freitag +// Copyright (C) 2015 Suzuki Toshiya +// Copyright (C) 2016 Jason Crain +// Copyright (C) 2018 Albert Astals Cid // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git @@ -61,6 +64,9 @@ public: // Destructor. ~CairoImage (); + CairoImage(const CairoImage &) = delete; + CairoImage& operator=(const CairoImage &) = delete; + // Set the image cairo surface void setImage (cairo_surface_t *image); @@ -95,114 +101,121 @@ public: // Does this device use upside-down coordinates? // (Upside-down means (0,0) is the top left corner of the page.) - virtual GBool upsideDown() { return gTrue; } + GBool upsideDown() override { return gTrue; } // Does this device use drawChar() or drawString()? - virtual GBool useDrawChar() { return gTrue; } + GBool useDrawChar() override { return gTrue; } // Does this device use tilingPatternFill()? If this returns false, // tiling pattern fills will be reduced to a series of other drawing // operations. - virtual GBool useTilingPatternFill() { return gTrue; } + GBool useTilingPatternFill() override { return gTrue; } // Does this device use functionShadedFill(), axialShadedFill(), and // radialShadedFill()? If this returns false, these shaded fills // will be reduced to a series of other drawing operations. #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) - virtual GBool useShadedFills(int type) { return type <= 7; } + GBool useShadedFills(int type) override { return type <= 7; } #else - virtual GBool useShadedFills(int type) { return type < 4; } + GBool useShadedFills(int type) override { return type > 1 && type < 4; } #endif // Does this device use FillColorStop()? - virtual GBool useFillColorStop() { return gTrue; } + GBool useFillColorStop() override { return gTrue; } // Does this device use beginType3Char/endType3Char? Otherwise, // text in Type 3 fonts will be drawn with drawChar/drawString. - virtual GBool interpretType3Chars() { return gFalse; } + GBool interpretType3Chars() override { return gFalse; } + + // Does this device need to clip pages to the crop box even when the + // box is the crop box? + GBool needClipToCropBox() override { return gTrue; } //----- initialization and control // Start a page. - virtual void startPage(int pageNum, GfxState *state, XRef *xref); + void startPage(int pageNum, GfxState *state, XRef *xref) override; // End a page. - virtual void endPage(); + void endPage() override; //----- save/restore graphics state - virtual void saveState(GfxState *state); - virtual void restoreState(GfxState *state); + void saveState(GfxState *state) override; + void restoreState(GfxState *state) override; //----- update graphics state - virtual void updateAll(GfxState *state); - virtual void setDefaultCTM(double *ctm); - virtual void updateCTM(GfxState *state, double m11, double m12, - double m21, double m22, double m31, double m32); - virtual void updateLineDash(GfxState *state); - virtual void updateFlatness(GfxState *state); - virtual void updateLineJoin(GfxState *state); - virtual void updateLineCap(GfxState *state); - virtual void updateMiterLimit(GfxState *state); - virtual void updateLineWidth(GfxState *state); - virtual void updateFillColor(GfxState *state); - virtual void updateStrokeColor(GfxState *state); - virtual void updateFillOpacity(GfxState *state); - virtual void updateStrokeOpacity(GfxState *state); - virtual void updateFillColorStop(GfxState *state, double offset); - virtual void updateBlendMode(GfxState *state); + void updateAll(GfxState *state) override; + void setDefaultCTM(double *ctm) override; + void updateCTM(GfxState *state, double m11, double m12, + double m21, double m22, double m31, double m32) override; + void updateLineDash(GfxState *state) override; + void updateFlatness(GfxState *state) override; + void updateLineJoin(GfxState *state) override; + void updateLineCap(GfxState *state) override; + void updateMiterLimit(GfxState *state) override; + void updateLineWidth(GfxState *state) override; + void updateFillColor(GfxState *state) override; + void updateStrokeColor(GfxState *state) override; + void updateFillOpacity(GfxState *state) override; + void updateStrokeOpacity(GfxState *state) override; + void updateFillColorStop(GfxState *state, double offset) override; + void updateBlendMode(GfxState *state) override; //----- update text state - virtual void updateFont(GfxState *state); + void updateFont(GfxState *state) override; //----- path painting - virtual void stroke(GfxState *state); - virtual void fill(GfxState *state); - virtual void eoFill(GfxState *state); - virtual void clipToStrokePath(GfxState *state); - virtual GBool tilingPatternFill(GfxState *state, Gfx *gfx, Catalog *cat, Object *str, - double *pmat, int paintType, int tilingType, Dict *resDict, - double *mat, double *bbox, - int x0, int y0, int x1, int y1, - double xStep, double yStep); - virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax); - virtual GBool axialShadedSupportExtend(GfxState *state, GfxAxialShading *shading); - virtual GBool radialShadedFill(GfxState *state, GfxRadialShading *shading, double sMin, double sMax); - virtual GBool radialShadedSupportExtend(GfxState *state, GfxRadialShading *shading); + void stroke(GfxState *state) override; + void fill(GfxState *state) override; + void eoFill(GfxState *state) override; + void clipToStrokePath(GfxState *state) override; + GBool tilingPatternFill(GfxState *state, Gfx *gfx, Catalog *cat, Object *str, + double *pmat, int paintType, int tilingType, Dict *resDict, + double *mat, double *bbox, + int x0, int y0, int x1, int y1, + double xStep, double yStep) override; #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) - virtual GBool gouraudTriangleShadedFill(GfxState *state, GfxGouraudTriangleShading *shading); - virtual GBool patchMeshShadedFill(GfxState *state, GfxPatchMeshShading *shading); + GBool functionShadedFill(GfxState *state, GfxFunctionShading *shading) override; +#endif + GBool axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax) override; + GBool axialShadedSupportExtend(GfxState *state, GfxAxialShading *shading) override; + GBool radialShadedFill(GfxState *state, GfxRadialShading *shading, double sMin, double sMax) override; + GBool radialShadedSupportExtend(GfxState *state, GfxRadialShading *shading) override; +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) + GBool gouraudTriangleShadedFill(GfxState *state, GfxGouraudTriangleShading *shading) override; + GBool patchMeshShadedFill(GfxState *state, GfxPatchMeshShading *shading) override; #endif //----- path clipping - virtual void clip(GfxState *state); - virtual void eoClip(GfxState *state); + void clip(GfxState *state) override; + void eoClip(GfxState *state) override; //----- text drawing - void beginString(GfxState *state, GooString *s); - void endString(GfxState *state); + void beginString(GfxState *state, GooString *s) override; + void endString(GfxState *state) override; void drawChar(GfxState *state, double x, double y, double dx, double dy, double originX, double originY, - CharCode code, int nBytes, Unicode *u, int uLen); - void beginActualText(GfxState *state, GooString *text); - void endActualText(GfxState *state); + CharCode code, int nBytes, Unicode *u, int uLen) override; + void beginActualText(GfxState *state, GooString *text) override; + void endActualText(GfxState *state) override; - virtual GBool beginType3Char(GfxState *state, double x, double y, - double dx, double dy, - CharCode code, Unicode *u, int uLen); - virtual void endType3Char(GfxState *state); - virtual void beginTextObject(GfxState *state); - virtual void endTextObject(GfxState *state); + GBool beginType3Char(GfxState *state, double x, double y, + double dx, double dy, + CharCode code, Unicode *u, int uLen) override; + void endType3Char(GfxState *state) override; + void beginTextObject(GfxState *state) override; + void endTextObject(GfxState *state) override; //----- image drawing - virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, - int width, int height, GBool invert, GBool interpolate, - GBool inlineImg); - virtual void setSoftMaskFromImageMask(GfxState *state, - Object *ref, Stream *str, - int width, int height, GBool invert, - GBool inlineImg, double *baseMatrix); - virtual void unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix); + void drawImageMask(GfxState *state, Object *ref, Stream *str, + int width, int height, GBool invert, GBool interpolate, + GBool inlineImg) override; + void setSoftMaskFromImageMask(GfxState *state, + Object *ref, Stream *str, + int width, int height, GBool invert, + GBool inlineImg, double *baseMatrix) override; + void unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix) override; void drawImageMaskPrescaled(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, GBool interpolate, GBool inlineImg); @@ -210,42 +223,42 @@ public: int width, int height, GBool invert, GBool interpolate, GBool inlineImg); - virtual void drawImage(GfxState *state, Object *ref, Stream *str, - int width, int height, GfxImageColorMap *colorMap, - GBool interpolate, int *maskColors, GBool inlineImg); - virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, - int width, int height, - GfxImageColorMap *colorMap, - GBool interpolate, - Stream *maskStr, - int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap, - GBool maskInterpolate); + void drawImage(GfxState *state, Object *ref, Stream *str, + int width, int height, GfxImageColorMap *colorMap, + GBool interpolate, int *maskColors, GBool inlineImg) override; + void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + GBool interpolate, + Stream *maskStr, + int maskWidth, int maskHeight, + GfxImageColorMap *maskColorMap, + GBool maskInterpolate) override; - virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, - int width, int height, - GfxImageColorMap *colorMap, - GBool interpolate, - Stream *maskStr, - int maskWidth, int maskHeight, - GBool maskInvert, GBool maskInterpolate); + void drawMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + GBool interpolate, + Stream *maskStr, + int maskWidth, int maskHeight, + GBool maskInvert, GBool maskInterpolate) override; //----- transparency groups and soft masks - virtual void beginTransparencyGroup(GfxState * /*state*/, double * /*bbox*/, + void beginTransparencyGroup(GfxState * /*state*/, double * /*bbox*/, GfxColorSpace * /*blendingColorSpace*/, GBool /*isolated*/, GBool /*knockout*/, - GBool /*forSoftMask*/); - virtual void endTransparencyGroup(GfxState * /*state*/); + GBool /*forSoftMask*/) override; + void endTransparencyGroup(GfxState * /*state*/) override; void popTransparencyGroup(); - virtual void paintTransparencyGroup(GfxState * /*state*/, double * /*bbox*/); - virtual void setSoftMask(GfxState * /*state*/, double * /*bbox*/, GBool /*alpha*/, - Function * /*transferFunc*/, GfxColor * /*backdropColor*/); - virtual void clearSoftMask(GfxState * /*state*/); + void paintTransparencyGroup(GfxState * /*state*/, double * /*bbox*/) override; + void setSoftMask(GfxState * /*state*/, double * /*bbox*/, GBool /*alpha*/, + Function * /*transferFunc*/, GfxColor * /*backdropColor*/) override; + void clearSoftMask(GfxState * /*state*/) override; //----- Type 3 font operators - virtual void type3D0(GfxState *state, double wx, double wy); - virtual void type3D1(GfxState *state, double wx, double wy, - double llx, double lly, double urx, double ury); + void type3D0(GfxState *state, double wx, double wy) override; + void type3D1(GfxState *state, double wx, double wy, + double llx, double lly, double urx, double ury) override; //----- special access @@ -257,6 +270,7 @@ public: void setCairo (cairo_t *cr); void setTextPage (TextPage *text); void setPrinting (GBool printing) { this->printing = printing; needFontUpdate = gTrue; } + void setAntialias(cairo_antialias_t antialias); void setInType3Char(GBool inType3Char) { this->inType3Char = inType3Char; } void getType3GlyphWidth (double *wx, double *wy) { *wx = t3_glyph_wx; *wy = t3_glyph_wy; } @@ -272,11 +286,17 @@ protected: cairo_filter_t getFilterForSurface(cairo_surface_t *image, GBool interpolate); GBool getStreamData (Stream *str, char **buffer, int *length); - // pdf2htmlEX: make setMimeData virtual, we need to override it - virtual - void setMimeData(Stream *str, Object *ref, cairo_surface_t *image); + void setMimeData(GfxState *state, Stream *str, Object *ref, + GfxImageColorMap *colorMap, cairo_surface_t *image, int height); void fillToStrokePathClip(GfxState *state); void alignStrokeCoords(GfxSubpath *subpath, int i, double *x, double *y); +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 14, 0) + GBool setMimeDataForJBIG2Globals (Stream *str, cairo_surface_t *image); +#endif +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 10) + GBool setMimeDataForCCITTParams(Stream *str, cairo_surface_t *image, int height); +#endif + static void setContextAntialias(cairo_t *cr, cairo_antialias_t antialias); GfxRGB fill_color, stroke_color; cairo_pattern_t *fill_pattern, *stroke_pattern; @@ -298,6 +318,7 @@ protected: cairo_line_cap_t cap; cairo_line_join_t join; double miter; + int ref_count; } *strokePathClip; PDFDoc *doc; // the current document @@ -313,6 +334,7 @@ protected: GBool needFontUpdate; // set when the font needs to be updated GBool printing; GBool use_show_text_glyphs; + GBool text_matrix_valid; cairo_surface_t *surface; cairo_glyph_t *glyphs; int glyphCount; @@ -327,7 +349,7 @@ protected: double t3_glyph_wx, t3_glyph_wy; GBool t3_glyph_has_bbox; double t3_glyph_bbox[4]; - + cairo_antialias_t antialias; GBool prescaleImages; TextPage *text; // text for the current page @@ -373,118 +395,118 @@ public: // Does this device use upside-down coordinates? // (Upside-down means (0,0) is the top left corner of the page.) - virtual GBool upsideDown() { return gTrue; } + GBool upsideDown() override { return gTrue; } // Does this device use drawChar() or drawString()? - virtual GBool useDrawChar() { return gFalse; } + GBool useDrawChar() override { return gFalse; } // Does this device use tilingPatternFill()? If this returns false, // tiling pattern fills will be reduced to a series of other drawing // operations. - virtual GBool useTilingPatternFill() { return gTrue; } + GBool useTilingPatternFill() override { return gTrue; } // Does this device use functionShadedFill(), axialShadedFill(), and // radialShadedFill()? If this returns false, these shaded fills // will be reduced to a series of other drawing operations. #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2) - virtual GBool useShadedFills(int type) { return type <= 7; } + GBool useShadedFills(int type) override { return type <= 7; } #else - virtual GBool useShadedFills(int type) { return type < 4; } + GBool useShadedFills(int type) override { return type < 4; } #endif // Does this device use FillColorStop()? - virtual GBool useFillColorStop() { return gFalse; } + GBool useFillColorStop() override { return gFalse; } // Does this device use beginType3Char/endType3Char? Otherwise, // text in Type 3 fonts will be drawn with drawChar/drawString. - virtual GBool interpretType3Chars() { return gFalse; } + GBool interpretType3Chars() override { return gFalse; } // Does this device need non-text content? - virtual GBool needNonText() { return gTrue; } + GBool needNonText() override { return gTrue; } //----- save/restore graphics state - virtual void saveState(GfxState *state) { } - virtual void restoreState(GfxState *state) { } + void saveState(GfxState *state) override { } + void restoreState(GfxState *state) override { } //----- update graphics state - virtual void updateAll(GfxState *state) { } - virtual void setDefaultCTM(double *ctm) { } - virtual void updateCTM(GfxState *state, double m11, double m12, - double m21, double m22, double m31, double m32) { } - virtual void updateLineDash(GfxState *state) { } - virtual void updateFlatness(GfxState *state) { } - virtual void updateLineJoin(GfxState *state) { } - virtual void updateLineCap(GfxState *state) { } - virtual void updateMiterLimit(GfxState *state) { } - virtual void updateLineWidth(GfxState *state) { } - virtual void updateFillColor(GfxState *state) { } - virtual void updateStrokeColor(GfxState *state) { } - virtual void updateFillOpacity(GfxState *state) { } - virtual void updateStrokeOpacity(GfxState *state) { } - virtual void updateBlendMode(GfxState *state) { } + void updateAll(GfxState *state) override { } + void setDefaultCTM(double *ctm) override { } + void updateCTM(GfxState *state, double m11, double m12, + double m21, double m22, double m31, double m32) override { } + void updateLineDash(GfxState *state) override { } + void updateFlatness(GfxState *state) override { } + void updateLineJoin(GfxState *state) override { } + void updateLineCap(GfxState *state) override { } + void updateMiterLimit(GfxState *state) override { } + void updateLineWidth(GfxState *state) override { } + void updateFillColor(GfxState *state) override { } + void updateStrokeColor(GfxState *state) override { } + void updateFillOpacity(GfxState *state) override { } + void updateStrokeOpacity(GfxState *state) override { } + void updateBlendMode(GfxState *state) override { } //----- update text state - virtual void updateFont(GfxState *state) { } + void updateFont(GfxState *state) override { } //----- path painting - virtual void stroke(GfxState *state) { } - virtual void fill(GfxState *state) { } - virtual void eoFill(GfxState *state) { } - virtual void clipToStrokePath(GfxState *state) { } - virtual GBool tilingPatternFill(GfxState *state, Gfx *gfx, Catalog *cat, Object *str, - double *pmat, int paintType, int tilingType, Dict *resDict, - double *mat, double *bbox, - int x0, int y0, int x1, int y1, - double xStep, double yStep) { return gTrue; } - virtual GBool axialShadedFill(GfxState *state, - GfxAxialShading *shading, - double tMin, double tMax) { return gTrue; } - virtual GBool radialShadedFill(GfxState *state, - GfxRadialShading *shading, - double sMin, double sMax) { return gTrue; } + void stroke(GfxState *state) override { } + void fill(GfxState *state) override { } + void eoFill(GfxState *state) override { } + void clipToStrokePath(GfxState *state) override { } + GBool tilingPatternFill(GfxState *state, Gfx *gfx, Catalog *cat, Object *str, + double *pmat, int paintType, int tilingType, Dict *resDict, + double *mat, double *bbox, + int x0, int y0, int x1, int y1, + double xStep, double yStep) override { return gTrue; } + GBool axialShadedFill(GfxState *state, + GfxAxialShading *shading, + double tMin, double tMax) override { return gTrue; } + GBool radialShadedFill(GfxState *state, + GfxRadialShading *shading, + double sMin, double sMax) override { return gTrue; } //----- path clipping - virtual void clip(GfxState *state) { } - virtual void eoClip(GfxState *state) { } + void clip(GfxState *state) override { } + void eoClip(GfxState *state) override { } //----- image drawing - virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, - int width, int height, GBool invert, - GBool interpolate, GBool inlineImg); - virtual void drawImage(GfxState *state, Object *ref, Stream *str, - int width, int height, GfxImageColorMap *colorMap, - GBool interpolate, int *maskColors, GBool inlineImg); - virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, - int width, int height, - GfxImageColorMap *colorMap, - GBool interpolate, - Stream *maskStr, - int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap, - GBool maskInterpolate); - virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, - int width, int height, - GfxImageColorMap *colorMap, - GBool interpolate, - Stream *maskStr, - int maskWidth, int maskHeight, - GBool maskInvert, GBool maskInterpolate); - virtual void setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, - int width, int height, GBool invert, - GBool inlineImg, double *baseMatrix); - virtual void unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix) {} + void drawImageMask(GfxState *state, Object *ref, Stream *str, + int width, int height, GBool invert, + GBool interpolate, GBool inlineImg) override; + void drawImage(GfxState *state, Object *ref, Stream *str, + int width, int height, GfxImageColorMap *colorMap, + GBool interpolate, int *maskColors, GBool inlineImg) override; + void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + GBool interpolate, + Stream *maskStr, + int maskWidth, int maskHeight, + GfxImageColorMap *maskColorMap, + GBool maskInterpolate) override; + void drawMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + GBool interpolate, + Stream *maskStr, + int maskWidth, int maskHeight, + GBool maskInvert, GBool maskInterpolate) override; + void setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, + int width, int height, GBool invert, + GBool inlineImg, double *baseMatrix) override; + void unsetSoftMaskFromImageMask(GfxState *state, double *baseMatrix) override {} //----- transparency groups and soft masks - virtual void beginTransparencyGroup(GfxState * /*state*/, double * /*bbox*/, - GfxColorSpace * /*blendingColorSpace*/, - GBool /*isolated*/, GBool /*knockout*/, - GBool /*forSoftMask*/) {} - virtual void endTransparencyGroup(GfxState * /*state*/) {} - virtual void paintTransparencyGroup(GfxState * /*state*/, double * /*bbox*/) {} - virtual void setSoftMask(GfxState * /*state*/, double * /*bbox*/, GBool /*alpha*/, - Function * /*transferFunc*/, GfxColor * /*backdropColor*/) {} - virtual void clearSoftMask(GfxState * /*state*/) {} + void beginTransparencyGroup(GfxState * /*state*/, double * /*bbox*/, + GfxColorSpace * /*blendingColorSpace*/, + GBool /*isolated*/, GBool /*knockout*/, + GBool /*forSoftMask*/) override {} + void endTransparencyGroup(GfxState * /*state*/) override {} + void paintTransparencyGroup(GfxState * /*state*/, double * /*bbox*/) override {} + void setSoftMask(GfxState * /*state*/, double * /*bbox*/, GBool /*alpha*/, + Function * /*transferFunc*/, GfxColor * /*backdropColor*/) override {} + void clearSoftMask(GfxState * /*state*/) override {} //----- Image list // By default images are not rendred diff --git a/3rdparty/poppler/git/CairoRescaleBox.cc b/3rdparty/poppler/git/CairoRescaleBox.cc index 3d19689..251e8e6 100644 --- a/3rdparty/poppler/git/CairoRescaleBox.cc +++ b/3rdparty/poppler/git/CairoRescaleBox.cc @@ -31,7 +31,7 @@ // under GPL version 2 or later // // Copyright (C) 2012 Hib Eris -// Copyright (C) 2012 Adrian Johnson +// Copyright (C) 2012, 2017 Adrian Johnson // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git @@ -41,10 +41,9 @@ /* This implements a box filter that supports non-integer box sizes */ -#ifdef HAVE_CONFIG_H -#include -#endif +#include +#include #include #include #include @@ -272,9 +271,9 @@ GBool CairoRescaleBox::downScaleImage(unsigned orig_width, unsigned orig_height, int dest_y; int src_y = 0; uint32_t *scanline; - int *x_coverage = NULL; - int *y_coverage = NULL; - uint32_t *temp_buf = NULL; + int *x_coverage = nullptr; + int *y_coverage = nullptr; + uint32_t *temp_buf = nullptr; GBool retval = gFalse; unsigned int *dest; int dst_stride; diff --git a/3rdparty/poppler/git/CairoRescaleBox.h b/3rdparty/poppler/git/CairoRescaleBox.h index 072e8a9..ca307cb 100644 --- a/3rdparty/poppler/git/CairoRescaleBox.h +++ b/3rdparty/poppler/git/CairoRescaleBox.h @@ -30,6 +30,7 @@ // under GPL version 2 or later // // Copyright (C) 2012 Adrian Johnson +// Copyright (C) 2018 Albert Astals Cid // // To see a description of the changes please see the Changelog file that // came with your tarball or type make ChangeLog if you are building from git @@ -48,6 +49,9 @@ public: CairoRescaleBox() {}; virtual ~CairoRescaleBox() {}; + CairoRescaleBox(const CairoRescaleBox &) = delete; + CairoRescaleBox& operator=(const CairoRescaleBox &) = delete; + virtual GBool downScaleImage(unsigned orig_width, unsigned orig_height, signed scaled_width, signed scaled_height, unsigned short int start_column, unsigned short int start_row, diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cad847..135e02f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ option(ENABLE_SVG "Enable SVG support, for generating SVG background images and include_directories(${CMAKE_SOURCE_DIR}/src) -set(PDF2HTMLEX_VERSION "0.14.6") +set(PDF2HTMLEX_VERSION "0.15.0") set(ARCHIVE_NAME pdf2htmlex-${PDF2HTMLEX_VERSION}) add_custom_target(dist COMMAND git archive --prefix=${ARCHIVE_NAME}/ HEAD @@ -81,7 +81,7 @@ endif() if(CYGWIN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++0x") else() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -pthread") endif() # check the C++11 features we need diff --git a/README.md b/README.md index 850187f..cb07bac 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,28 @@ -#![](https://pdf2htmlEX.github.io/pdf2htmlEX/images/pdf2htmlEX-64x64.png) pdf2htmlEX +# ![](https://pdf2htmlEX.github.io/pdf2htmlEX/images/pdf2htmlEX-64x64.png) pdf2htmlEX + +[![Build Status](https://travis-ci.org/jgoldfar/pdf2htmlEX.svg?branch=add-build-status)](https://travis-ci.org/jgoldfar/pdf2htmlEX) + +# Differences from upstream pdf2htmlEX: + +This is my branch of pdf2htmlEX which I maintain for my own purposes. I have made a number of changes and improvements over the original code: + +* Lots of bugs fixes, mostly of edge cases +* Integration of latest Cairo code +* Out of source building +* Rewritten handling of obscured/partially obscured text - now much more accurate +* Some support for transparent text +* Improvement of DPI settings - clamping of DPI to ensure output graphic isn't too big + +`--correct-text-visibility` tracks the visibility of 4 sample points for each character (currently the 4 corners of the character's bounding box, inset slightly) to determine visibility. +It now has two modes. 1 = Fully occluded text handled (i.e. doesn't get put into the HTML layer). 2 = Partially occluded text handled. + +The default is now "1", so fully occluded text should no longer show through. If "2" is selected then if the character is partially occluded it will be drawn in the background layer. In this case, the rendered DPI of the page will be automatically increased to `--covered-text-dpi` (default: 300) to reduce the impact of rasterized text. + +For maximum accuracy I strongly recommend using the output options: `--font-size-multiplier 1 --zoom 25`. This will circumvent rounding errors inside web browsers. You will then have to scale down the resulting HTML page using an appropriate "scale" transform. + +If you are concerned about file size of the resulting HTML, then I recommend patching fontforge to prevent it writing the current time into the dumped fonts, and then post-process the pdf2htmlEX data to remove duplicate files - there will usually be many duplicate background images and fonts. + - >一图胜千言
A beautiful demo is worth a thousand words - **Bible de Genève, 1564** (fonts and typography): [HTML](https://pdf2htmlEX.github.io/pdf2htmlEX/demo/geneve.html) / [PDF](https://github.com/raphink/geneve_1564/releases/download/2015-07-08_01/geneve_1564.pdf) diff --git a/dobuild b/dobuild new file mode 100755 index 0000000..5b55a1c --- /dev/null +++ b/dobuild @@ -0,0 +1,4 @@ +mkdir build +cd build +cmake .. +make install diff --git a/doclean b/doclean new file mode 100755 index 0000000..97f8946 --- /dev/null +++ b/doclean @@ -0,0 +1 @@ +rm -rf build pdf2htmlEX.1 share/*.css share/*.js share/*.min.* src/pdf2htmlEX-config.h src/util/css_const.h diff --git a/pdf2htmlEX.1.in b/pdf2htmlEX.1.in index 7df4614..d3cd881 100644 --- a/pdf2htmlEX.1.in +++ b/pdf2htmlEX.1.in @@ -247,9 +247,10 @@ If set to 0, pdf2htmlEX would try its best to balance the two methods above. If set to 1, pdf2htmlEX will try to reduce the number of HTML elements used for text. Turn it off if anything goes wrong. .TP -.B --correct-text-visibility <0|1> (Default: 0) -If set to 1, pdf2htmlEX will try to detect texts covered by other graphics and properly arrange them, -i.e. covered texts are made transparent in text layer, and are drawn on background layer. +.B --correct-text-visibility <0|1|2> (Default: 1) +0 : Do not do visibility calculations for text +1 : Text fully occluded will be drawn in the background layer +2 : Text partially occluded will be drawn in the background layer (more false positives than option "1") .SS Background Image diff --git a/share/pdf2htmlEX.js.in b/share/pdf2htmlEX.js.in index 518eb5e..a678759 100644 --- a/share/pdf2htmlEX.js.in +++ b/share/pdf2htmlEX.js.in @@ -901,7 +901,7 @@ Viewer.prototype = { var self = this; /** - * page should have type Page + * page should have type Page * @param{Page} page */ var transform_and_scroll = function(page) { diff --git a/src/BackgroundRenderer/CairoBackgroundRenderer.cc b/src/BackgroundRenderer/CairoBackgroundRenderer.cc index 1ce6eac..2c19a94 100644 --- a/src/BackgroundRenderer/CairoBackgroundRenderer.cc +++ b/src/BackgroundRenderer/CairoBackgroundRenderer.cc @@ -134,9 +134,9 @@ bool CairoBackgroundRenderer::render_page(PDFDoc * doc, int pageno) if(param.embed_image) html_renderer->tmp_files.add(fn); - surface = cairo_svg_surface_create(fn.c_str(), page_width * param.h_dpi / DEFAULT_DPI, page_height * param.v_dpi / DEFAULT_DPI); + surface = cairo_svg_surface_create(fn.c_str(), page_width * param.actual_dpi / DEFAULT_DPI, page_height * param.actual_dpi / DEFAULT_DPI); cairo_svg_surface_restrict_to_version(surface, CAIRO_SVG_VERSION_1_2); - cairo_surface_set_fallback_resolution(surface, param.h_dpi, param.v_dpi); + cairo_surface_set_fallback_resolution(surface, param.actual_dpi, param.actual_dpi); cairo_t * cr = cairo_create(surface); setCairo(cr); @@ -144,15 +144,15 @@ bool CairoBackgroundRenderer::render_page(PDFDoc * doc, int pageno) bitmaps_in_current_page.clear(); bool process_annotation = param.process_annotation; - doc->displayPage(this, pageno, param.h_dpi, param.v_dpi, - 0, + doc->displayPage(this, pageno, param.actual_dpi, param.actual_dpi, + 0, (!(param.use_cropbox)), - false, + false, false, nullptr, nullptr, &annot_cb, &process_annotation); setCairo(nullptr); - + { auto status = cairo_status(cr); cairo_destroy(cr); @@ -198,7 +198,7 @@ bool CairoBackgroundRenderer::render_page(PDFDoc * doc, int pageno) void CairoBackgroundRenderer::embed_image(int pageno) { auto & f_page = *(html_renderer->f_curpage); - + // SVGs introduced by or background-image can't have external resources; // SVGs introduced by and can, but they are more expensive for browsers. // So we use if the SVG contains no external bitmaps, and use otherwise. @@ -235,11 +235,11 @@ string CairoBackgroundRenderer::build_bitmap_path(int id) return string(html_renderer->str_fmt("%s/o%d.jpg", param.dest_dir.c_str(), id)); } // Override CairoOutputDev::setMimeData() and dump bitmaps in SVG to external files. -void CairoBackgroundRenderer::setMimeData(Stream *str, Object *ref, cairo_surface_t *image) +void CairoBackgroundRenderer::setMimeData(GfxState *state, Stream *str, Object *ref, GfxImageColorMap *colorMap, cairo_surface_t *image) { if (param.svg_embed_bitmap) { - CairoOutputDev::setMimeData(str, ref, image); + CairoOutputDev::setMimeData(state, str, ref, colorMap, image, cairo_image_surface_get_height (image)); return; } @@ -263,21 +263,20 @@ void CairoBackgroundRenderer::setMimeData(Stream *str, Object *ref, cairo_surfac // // In PDF, jpeg stream objects can also specify other color spaces like DeviceN and Separation, // It is also not safe to dump them directly. - Object obj; - str->getDict()->lookup("ColorSpace", &obj); + Object obj = str->getDict()->lookup("ColorSpace"); if (!obj.isName() || (strcmp(obj.getName(), "DeviceRGB") && strcmp(obj.getName(), "DeviceGray")) ) { - obj.free(); + //obj.free(); return; } - obj.free(); - str->getDict()->lookup("Decode", &obj); + //obj.free(); + obj = str->getDict()->lookup("Decode"); if (obj.isArray()) { - obj.free(); + //obj.free(); return; } - obj.free(); + //obj.free(); int imgId = ref->getRef().num; auto uri = strdup((char*) html_renderer->str_fmt("o%d.jpg", imgId)); diff --git a/src/BackgroundRenderer/CairoBackgroundRenderer.h b/src/BackgroundRenderer/CairoBackgroundRenderer.h index 4ed9c86..e4e92d2 100644 --- a/src/BackgroundRenderer/CairoBackgroundRenderer.h +++ b/src/BackgroundRenderer/CairoBackgroundRenderer.h @@ -51,7 +51,7 @@ public: void updateRender(GfxState *state); protected: - virtual void setMimeData(Stream *str, Object *ref, cairo_surface_t *image); + virtual void setMimeData(GfxState *state, Stream *str, Object *ref, GfxImageColorMap *colorMap, cairo_surface_t *image); protected: HTMLRenderer * html_renderer; diff --git a/src/BackgroundRenderer/SplashBackgroundRenderer.cc b/src/BackgroundRenderer/SplashBackgroundRenderer.cc index 55b5322..5ae289d 100644 --- a/src/BackgroundRenderer/SplashBackgroundRenderer.cc +++ b/src/BackgroundRenderer/SplashBackgroundRenderer.cc @@ -29,7 +29,7 @@ using std::unique_ptr; const SplashColor SplashBackgroundRenderer::white = {255,255,255}; SplashBackgroundRenderer::SplashBackgroundRenderer(const string & imgFormat, HTMLRenderer * html_renderer, const Param & param) - : SplashOutputDev(splashModeRGB8, 4, gFalse, (SplashColorPtr)(&white)) + : SplashOutputDev(splashModeRGB8, 4, gFalse, (SplashColorPtr)(&white), gTrue, splashThinLineSolid) // DCRH: Make thin line mode = solid , html_renderer(html_renderer) , param(param) , format(imgFormat) @@ -67,30 +67,10 @@ void SplashBackgroundRenderer::drawChar(GfxState *state, double x, double y, double originX, double originY, CharCode code, int nBytes, Unicode *u, int uLen) { - // draw characters as image when - // - in fallback mode - // - OR there is special filling method - // - OR using a writing mode font - // - OR using a Type 3 font while param.process_type3 is not enabled - // - OR the text is used as path - if((param.fallback || param.proof) - || ( (state->getFont()) - && ( (state->getFont()->getWMode()) - || ((state->getFont()->getType() == fontType3) && (!param.process_type3)) - || (state->getRender() >= 4) - ) - ) - ) - { + if (param.proof || html_renderer->is_char_covered(drawn_char_count)) { SplashOutputDev::drawChar(state,x,y,dx,dy,originX,originY,code,nBytes,u,uLen); } - // If a char is treated as image, it is not subject to cover test - // (see HTMLRenderer::drawString), so don't increase drawn_char_count. - else if (param.correct_text_visibility) { - if (html_renderer->is_char_covered(drawn_char_count)) - SplashOutputDev::drawChar(state,x,y,dx,dy,originX,originY,code,nBytes,u,uLen); - drawn_char_count++; - } + drawn_char_count++; } void SplashBackgroundRenderer::beginTextObject(GfxState *state) @@ -134,7 +114,8 @@ bool SplashBackgroundRenderer::render_page(PDFDoc * doc, int pageno) { drawn_char_count = 0; bool process_annotation = param.process_annotation; - doc->displayPage(this, pageno, param.h_dpi, param.v_dpi, + + doc->displayPage(this, pageno, param.actual_dpi, param.actual_dpi, 0, (!(param.use_cropbox)), false, false, @@ -159,8 +140,8 @@ void SplashBackgroundRenderer::embed_image(int pageno) dump_image((char*)fn, xmin, ymin, xmax, ymax); } - double h_scale = html_renderer->text_zoom_factor() * DEFAULT_DPI / param.h_dpi; - double v_scale = html_renderer->text_zoom_factor() * DEFAULT_DPI / param.v_dpi; + double h_scale = html_renderer->text_zoom_factor() * DEFAULT_DPI / param.actual_dpi; + double v_scale = html_renderer->text_zoom_factor() * DEFAULT_DPI / param.actual_dpi; auto & f_page = *(html_renderer->f_curpage); auto & all_manager = html_renderer->all_manager; @@ -227,7 +208,7 @@ void SplashBackgroundRenderer::dump_image(const char * filename, int x1, int y1, throw string("Image format not supported: ") + format; } - if(!writer->init(f, width, height, param.h_dpi, param.v_dpi)) + if(!writer->init(f, width, height, param.actual_dpi, param.actual_dpi)) throw "Cannot initialize image writer"; auto * bitmap = getBitmap(); diff --git a/src/CoveredTextDetector.cc b/src/CoveredTextDetector.cc index e109b3f..0792c52 100644 --- a/src/CoveredTextDetector.cc +++ b/src/CoveredTextDetector.cc @@ -7,43 +7,110 @@ #include "CoveredTextDetector.h" +#include #include "util/math.h" +//#define DEBUG + namespace pdf2htmlEX { +CoveredTextDetector::CoveredTextDetector(Param & param): param(param) +{ +} + void CoveredTextDetector::reset() { char_bboxes.clear(); chars_covered.clear(); + char_pts_visible.clear(); } -void CoveredTextDetector::add_char_bbox(double * bbox) +void CoveredTextDetector::add_char_bbox(cairo_t *cairo, double * bbox) { char_bboxes.insert(char_bboxes.end(), bbox, bbox + 4); chars_covered.push_back(false); + char_pts_visible.push_back(1|2|4|8); } -void CoveredTextDetector::add_char_bbox_clipped(double * bbox, bool patially) +void CoveredTextDetector::add_char_bbox_clipped(cairo_t *cairo, double * bbox, int pts_visible) { +#ifdef DEBUG + printf("add_char_bbox_clipped: pts_visible:%x: [%f,%f,%f,%f]\n", pts_visible, bbox[0], bbox[1], bbox[2], bbox[3]); +#endif char_bboxes.insert(char_bboxes.end(), bbox, bbox + 4); - chars_covered.push_back(true); - if (patially) - add_non_char_bbox(bbox, chars_covered.size() - 1); + char_pts_visible.push_back(pts_visible); + + // DCRH: Hide if no points are visible, or if some points are visible and correct_text_visibility == 2 + if (pts_visible == 0 || param.correct_text_visibility == 2) { + chars_covered.push_back(true); + if (pts_visible > 0 && param.correct_text_visibility == 2) { + param.actual_dpi = std::min(param.text_dpi, param.max_dpi); // Char partially covered so increase background resolution + } + } else { + chars_covered.push_back(false); + } } -void CoveredTextDetector::add_non_char_bbox(double * bbox, int index) +// We now track the visibility of each corner of the char bbox. Potentially we could track +// more sample points but this should be sufficient for most cases. +// We check to see if each point is covered by any stroke or fill operation +// and mark it as invisible if so +void CoveredTextDetector::add_non_char_bbox(cairo_t *cairo, double * bbox, int what) { - if (index < 0) - index = chars_covered.size(); - for (int i = 0; i < index; i++) - { + int index = chars_covered.size(); + for (int i = 0; i < index; i++) { if (chars_covered[i]) continue; double * cbbox = &char_bboxes[i * 4]; - if (bbox_intersect(cbbox, bbox)) - { - chars_covered[i] = true; - add_non_char_bbox(cbbox, i); +#ifdef DEBUG +printf("add_non_char_bbox: what=%d, cbbox:[%f,%f,%f,%f], bbox:[%f,%f,%f,%f]\n", what, cbbox[0], cbbox[1], cbbox[2], cbbox[3], bbox[0], bbox[1], bbox[2], bbox[3]); +#endif + if (bbox_intersect(cbbox, bbox)) { + int pts_visible = char_pts_visible[i]; +#ifdef DEBUG +printf("pts_visible=%x\n", pts_visible); +#endif + if ((pts_visible & 1) && cairo_in_clip(cairo, cbbox[0], cbbox[1]) && + (what == 0 || + (what == 1 && cairo_in_fill(cairo, cbbox[0], cbbox[1])) || + (what == 2 && cairo_in_stroke(cairo, cbbox[0], cbbox[1])))) { + pts_visible &= ~1; + } + if ((pts_visible & 2) && cairo_in_clip(cairo, cbbox[2], cbbox[1]) && + (what == 0 || + (what == 1 && cairo_in_fill(cairo, cbbox[2], cbbox[1])) || + (what == 2 && cairo_in_stroke(cairo, cbbox[2], cbbox[1])))) { + pts_visible &= ~2; + } + if ((pts_visible & 4) && cairo_in_clip(cairo, cbbox[2], cbbox[3]) && + (what == 0 || + (what == 1 && cairo_in_fill(cairo, cbbox[2], cbbox[3])) || + (what == 2 && cairo_in_stroke(cairo, cbbox[2], cbbox[3])))) { + pts_visible &= ~4; + } + if ((pts_visible & 8) && cairo_in_clip(cairo, cbbox[0], cbbox[3]) && + (what == 0 || + (what == 1 && cairo_in_fill(cairo, cbbox[0], cbbox[3])) || + (what == 2 && cairo_in_stroke(cairo, cbbox[0], cbbox[3])))) { + pts_visible &= ~8; + } +#ifdef DEBUG +printf("pts_visible=%x\n", pts_visible); +#endif + char_pts_visible[i] = pts_visible; + if (pts_visible == 0 || (pts_visible != (1|2|4|8) && param.correct_text_visibility == 2)) { +#ifdef DEBUG +printf("Char covered\n"); +#endif + chars_covered[i] = true; + if (pts_visible > 0 && param.correct_text_visibility == 2) { // Partially visible text => increase rendering DPI + param.actual_dpi = std::min(param.text_dpi, param.max_dpi); + } + } + } else { +#ifdef DEBUG +printf("Not covered\n"); +#endif } } } diff --git a/src/CoveredTextDetector.h b/src/CoveredTextDetector.h index bee6c17..0f0506f 100644 --- a/src/CoveredTextDetector.h +++ b/src/CoveredTextDetector.h @@ -9,6 +9,9 @@ #define COVEREDTEXTDETECTOR_H__ #include +#include "Param.h" + +#include namespace pdf2htmlEX { @@ -19,6 +22,8 @@ class CoveredTextDetector { public: + CoveredTextDetector(Param & param); + /** * Reset to initial state. Should be called when start drawing a page. */ @@ -28,9 +33,9 @@ public: * Add a drawn character's bounding box. * @param bbox (x0, y0, x1, y1) */ - void add_char_bbox(double * bbox); + void add_char_bbox(cairo_t *, double * bbox); - void add_char_bbox_clipped(double * bbox, bool patially); + void add_char_bbox_clipped(cairo_t *,double * bbox, int pts_covered); /** * Add a drawn non-char graphics' bounding box. @@ -40,7 +45,7 @@ public: * @param index this graphics' drawing order: assume it is drawn after (index-1)th * char. -1 means after the last char. */ - void add_non_char_bbox(double * bbox, int index = -1); + void add_non_char_bbox(cairo_t *cairo, double * bbox, int what); /** * An array of flags indicating whether a char is covered by any non-char graphics. @@ -54,6 +59,8 @@ private: std::vector chars_covered; // x00, y00, x01, y01; x10, y10, x11, y11;... std::vector char_bboxes; + std::vector char_pts_visible; + Param & param; }; } diff --git a/src/DrawingTracer.cc b/src/DrawingTracer.cc index ffabad0..7086565 100644 --- a/src/DrawingTracer.cc +++ b/src/DrawingTracer.cc @@ -11,18 +11,15 @@ #include "DrawingTracer.h" #if !ENABLE_SVG -#warning "Cairo is disabled because ENABLE_SVG is off, --correct-text-visibility has limited functionality." +#error "ENABLE_SVG must be enabled" #endif -static constexpr bool DT_DEBUG = false; +//#define DEBUG namespace pdf2htmlEX { -DrawingTracer::DrawingTracer(const Param & param): param(param) -#if ENABLE_SVG -, cairo(nullptr) -#endif +DrawingTracer::DrawingTracer(const Param & param): param(param), cairo(nullptr) { } @@ -33,11 +30,8 @@ DrawingTracer::~DrawingTracer() void DrawingTracer::reset(GfxState *state) { - if (!param.correct_text_visibility) - return; finish(); -#if ENABLE_SVG // pbox is defined in device space, which is affected by zooming; // We want to trace in page space which is stable, so invert pbox by ctm. double pbox[] { 0, 0, state->getPageWidth(), state->getPageHeight() }; @@ -48,20 +42,24 @@ void DrawingTracer::reset(GfxState *state) cairo_rectangle_t page_box { pbox[0], pbox[1], pbox[2] - pbox[0], pbox[3] - pbox[1] }; cairo_surface_t * surface = cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, &page_box); cairo = cairo_create(surface); - if (DT_DEBUG) - printf("DrawingTracer::reset:page bbox:[%f,%f,%f,%f]\n",pbox[0], pbox[1], pbox[2], pbox[3]); + + ctm_stack.clear(); + double *identity = new double[6]; + tm_init(identity); + ctm_stack.push_back(identity); + +#ifdef DEBUG + printf("DrawingTracer::reset:page bbox:[%f,%f,%f,%f]\n",pbox[0], pbox[1], pbox[2], pbox[3]); #endif } void DrawingTracer::finish() { -#if ENABLE_SVG if (cairo) { cairo_destroy(cairo); cairo = nullptr; } -#endif } // Poppler won't inform us its initial CTM, and the initial CTM is affected by zoom level. @@ -72,22 +70,17 @@ void DrawingTracer::update_ctm(GfxState *state, double m11, double m12, double m if (!param.correct_text_visibility) return; -#if ENABLE_SVG - cairo_matrix_t matrix; - matrix.xx = m11; - matrix.yx = m12; - matrix.xy = m21; - matrix.yy = m22; - matrix.x0 = m31; - matrix.y0 = m32; - cairo_transform(cairo, &matrix); - - if (DT_DEBUG) - { - cairo_matrix_t mat; - cairo_get_matrix(cairo, &mat); - printf("DrawingTracer::update_ctm:ctm:[%f,%f,%f,%f,%f,%f]\n", mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0); - } + double *tmp = new double[6]; + tmp[0] = m11; + tmp[1] = m12; + tmp[2] = m21; + tmp[3] = m22; + tmp[4] = m31; + tmp[5] = m32; + double *ctm = ctm_stack.back(); + tm_multiply(ctm, tmp); +#ifdef DEBUG + printf("DrawingTracer::before update_ctm:ctm:[%f,%f,%f,%f,%f,%f] => [%f,%f,%f,%f,%f,%f]\n", m11, m12, m21, m22, m31, m32, ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]); #endif } @@ -95,16 +88,15 @@ void DrawingTracer::clip(GfxState * state, bool even_odd) { if (!param.correct_text_visibility) return; -#if ENABLE_SVG do_path(state, state->getPath()); cairo_set_fill_rule(cairo, even_odd? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); cairo_clip (cairo); - if (DT_DEBUG) +#ifdef DEBUG { double cbox[4]; cairo_clip_extents(cairo, cbox, cbox + 1, cbox + 2, cbox + 3); - printf("DrawingTracer::clip:extents:[%f,%f,%f,%f]\n", cbox[0],cbox[1],cbox[2],cbox[3]); + printf("DrawingTracer::clip:extents:even_odd=%d,[%f,%f,%f,%f]\n", even_odd, cbox[0],cbox[1],cbox[2],cbox[3]); } #endif } @@ -113,6 +105,8 @@ void DrawingTracer::clip_to_stroke_path(GfxState * state) { if (!param.correct_text_visibility) return; + + printf("TODO:clip_to_stroke_path\n"); // TODO cairo_stroke_to_path() ? } @@ -120,92 +114,112 @@ void DrawingTracer::save() { if (!param.correct_text_visibility) return; -#if ENABLE_SVG cairo_save(cairo); - if (DT_DEBUG) - printf("DrawingTracer::save\n"); + double *e = new double[6]; + memcpy(e, ctm_stack.back(), sizeof(double) * 6); + ctm_stack.push_back(e); + +#ifdef DEBUG + printf("DrawingTracer::saved: [%f,%f,%f,%f,%f,%f]\n", e[0], e[1], e[2], e[3], e[4], e[5]); #endif } void DrawingTracer::restore() { if (!param.correct_text_visibility) return; -#if ENABLE_SVG cairo_restore(cairo); - if (DT_DEBUG) - printf("DrawingTracer::restore\n"); + ctm_stack.pop_back(); + +#ifdef DEBUG + double *ctm = ctm_stack.back(); + printf("DrawingTracer::restored: [%f,%f,%f,%f,%f,%f]\n", ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]); #endif } void DrawingTracer::do_path(GfxState * state, GfxPath * path) { -#if ENABLE_SVG //copy from CairoOutputDev::doPath GfxSubpath *subpath; int i, j; double x, y; cairo_new_path(cairo); - if (DT_DEBUG) - printf("DrawingTracer::do_path:new_path\n"); +#ifdef DEBUG + printf("DrawingTracer::do_path:new_path (%d subpaths)\n", path->getNumSubpaths()); +#endif + for (i = 0; i < path->getNumSubpaths(); ++i) { subpath = path->getSubpath(i); if (subpath->getNumPoints() > 0) { x = subpath->getX(0); y = subpath->getY(0); + xform_pt(x, y); cairo_move_to(cairo, x, y); - if (DT_DEBUG) - printf("DrawingTracer::do_path:move_to[%f,%f]\n",x,y); j = 1; while (j < subpath->getNumPoints()) { if (subpath->getCurve(j)) { x = subpath->getX(j+2); y = subpath->getY(j+2); + double x1 = subpath->getX(j); + double y1 = subpath->getY(j); + double x2 = subpath->getX(j+1); + double y2 = subpath->getY(j+1); + xform_pt(x, y); + xform_pt(x1, y1); + xform_pt(x2, y2); cairo_curve_to(cairo, - subpath->getX(j), subpath->getY(j), - subpath->getX(j+1), subpath->getY(j+1), + x1, y1, + x2, y2, x, y); - if (DT_DEBUG) - printf("DrawingTracer::do_path:curve_to[%f,%f]\n",x,y); j += 3; } else { x = subpath->getX(j); y = subpath->getY(j); + xform_pt(x, y); cairo_line_to(cairo, x, y); - if (DT_DEBUG) - printf("DrawingTracer::do_path:line_to[%f,%f]\n",x,y); ++j; } } if (subpath->isClosed()) { cairo_close_path (cairo); - if (DT_DEBUG) - printf("DrawingTracer::do_path:close\n"); } } } -#endif } void DrawingTracer::stroke(GfxState * state) { -#if ENABLE_SVG if (!param.correct_text_visibility) return; - if (DT_DEBUG) - printf("DrawingTracer::stroke\n"); + if (state->getStrokeOpacity() < 0.5) { + // Ignore partially transparent fills for occlusion purposes + return; + } - cairo_set_line_width(cairo, state->getLineWidth()); + // Transform the line width by the ctm. This isn't 100% - we should really do this path segment by path segment, + // this is a reasonable approximation providing the CTM has uniform scaling X/Y + double lwx, lwy; + lwx = lwy = sqrt(0.5); + tm_transform(ctm_stack.back(), lwx, lwy, true); + double lineWidthScale = sqrt(lwx * lwx + lwy * lwy); +#ifdef DEBUG + printf("DrawingTracer::stroke. line width = %f*%f, line cap = %d\n", lineWidthScale, state->getLineWidth(), state->getLineCap()); +#endif + cairo_set_line_width(cairo, lineWidthScale * state->getLineWidth()); + + // Line cap is important - some PDF line widths are very large + switch (state->getLineCap()) { + case 0: + cairo_set_line_cap (cairo, CAIRO_LINE_CAP_BUTT); + break; + case 1: + cairo_set_line_cap (cairo, CAIRO_LINE_CAP_ROUND); + break; + case 2: + cairo_set_line_cap (cairo, CAIRO_LINE_CAP_SQUARE); + break; + } - // GfxPath is broken into steps, each step makes up a cairo path and its bbox is used for covering test. - // TODO - // 1. path steps that are not vertical or horizontal lines may still falsely "cover" many chars, - // can we slice those steps further? - // 2. if the line width is small, can we just ignore the path? - // 3. line join feature can't be retained. We use line-cap-square to minimize the problem that - // some chars actually covered by a line join are missed. However chars covered by a acute angle - // with line-join-miter may be still recognized as not covered. - cairo_set_line_cap(cairo, CAIRO_LINE_CAP_SQUARE); GfxPath * path = state->getPath(); for (int i = 0; i < path->getNumSubpaths(); ++i) { GfxSubpath * subpath = path->getSubpath(i); @@ -213,48 +227,54 @@ void DrawingTracer::stroke(GfxState * state) continue; double x = subpath->getX(0); double y = subpath->getY(0); + xform_pt(x, y); //p: loop cursor; j: next point index - int p =1, j = 1; + int p =1; int n = subpath->getNumPoints(); - while (p <= n) { + while (p < n) { cairo_new_path(cairo); +#ifdef DEBUG + printf("move_to: [%f,%f]\n", x, y); +#endif cairo_move_to(cairo, x, y); - if (subpath->getCurve(j)) { - x = subpath->getX(j+2); - y = subpath->getY(j+2); + if (subpath->getCurve(p)) { + x = subpath->getX(p+2); + y = subpath->getY(p+2); + double x1 = subpath->getX(p); + double y1 = subpath->getY(p); + double x2 = subpath->getX(p+1); + double y2 = subpath->getY(p+1); + xform_pt(x, y); + xform_pt(x1, y1); + xform_pt(x2, y2); +#ifdef DEBUG + printf("curve_to: [%f,%f], [%f,%f], [%f,%f]\n", x1, y1, x2, y2, x, y); +#endif cairo_curve_to(cairo, - subpath->getX(j), subpath->getY(j), - subpath->getX(j+1), subpath->getY(j+1), + x1, y1, + x2, y2, x, y); p += 3; } else { - x = subpath->getX(j); - y = subpath->getY(j); + x = subpath->getX(p); + y = subpath->getY(p); + xform_pt(x, y); +#ifdef DEBUG + printf("line_to: [%f,%f]\n", x, y); +#endif cairo_line_to(cairo, x, y); ++p; } - if (DT_DEBUG) - printf("DrawingTracer::stroke:new box:\n"); double sbox[4]; cairo_stroke_extents(cairo, sbox, sbox + 1, sbox + 2, sbox + 3); +#ifdef DEBUG + printf("DrawingTracer::stroke:new box:[%f,%f,%f,%f]\n", sbox[0], sbox[1], sbox[2], sbox[3]); +#endif if (sbox[0] != sbox[2] && sbox[1] != sbox[3]) - draw_non_char_bbox(state, sbox); - else if (DT_DEBUG) - printf("DrawingTracer::stroke:zero box!\n"); - - if (p == n) - { - if (subpath->isClosed()) - j = 0; // if sub path is closed, go back to starting point - else - break; - } - else - j = p; + draw_non_char_bbox(state, sbox, 2); } } -#endif } void DrawingTracer::fill(GfxState * state, bool even_odd) @@ -262,139 +282,166 @@ void DrawingTracer::fill(GfxState * state, bool even_odd) if (!param.correct_text_visibility) return; -#if ENABLE_SVG + if (state->getFillOpacity() < 0.5) { + // Ignore partially transparent fills for occlusion purposes + return; + } + do_path(state, state->getPath()); //cairo_fill_extents don't take fill rule into account. //cairo_set_fill_rule (cairo, even_odd? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); double fbox[4]; cairo_fill_extents(cairo, fbox, fbox + 1, fbox + 2, fbox + 3); - draw_non_char_bbox(state, fbox); + +#ifdef DEBUG + printf("DrawingTracer::fill:[%f,%f,%f,%f]\n", fbox[0],fbox[1],fbox[2],fbox[3]); #endif + + draw_non_char_bbox(state, fbox, 1); } -void DrawingTracer::draw_non_char_bbox(GfxState * state, double * bbox) +void DrawingTracer::draw_non_char_bbox(GfxState * state, double * bbox, int what) { -#if ENABLE_SVG +// what == 0 => just do bbox text +// what == 1 => stroke test +// what == 2 => fill test double cbox[4]; cairo_clip_extents(cairo, cbox, cbox + 1, cbox + 2, cbox + 3); - if(bbox_intersect(cbox, bbox, bbox)) -#endif + if(bbox_intersect(cbox, bbox)) { - transform_bbox_by_ctm(bbox, state); - if (DT_DEBUG) - printf("DrawingTracer::draw_non_char_bbox:[%f,%f,%f,%f]\n", bbox[0],bbox[1],bbox[2],bbox[3]); +#ifdef DEBUG + printf("DrawingTracer::draw_non_char_bbox:what=%d,[%f,%f,%f,%f]\n", what, bbox[0],bbox[1],bbox[2],bbox[3]); +#endif if (on_non_char_drawn) - on_non_char_drawn(bbox); + on_non_char_drawn(cairo, bbox, what); } } -void DrawingTracer::draw_char_bbox(GfxState * state, double * bbox) +void DrawingTracer::draw_char_bbox(GfxState * state, double * bbox, int inTransparencyGroup) { -#if ENABLE_SVG - // Note: even if 4 corners of the char are all in or all out of the clip area, - // it could still be partially clipped. - // TODO better solution? - int pt_in = 0; - if (cairo_in_clip(cairo, bbox[0], bbox[1])) - ++pt_in; - if (cairo_in_clip(cairo, bbox[2], bbox[3])) - ++pt_in; - if (cairo_in_clip(cairo, bbox[2], bbox[1])) - ++pt_in; - if (cairo_in_clip(cairo, bbox[0], bbox[3])) - ++pt_in; + if (inTransparencyGroup || state->getFillOpacity() < 1.0 || state->getStrokeOpacity() < 1.0) { + on_char_clipped(cairo, bbox, 0); + return; + } + if (!param.correct_text_visibility) { + double bbox[4] = { 0, 0, 0, 0 }; // bbox not relevant if not correcting text visibility + on_char_drawn(cairo, bbox); + return; + } - if (pt_in == 0) - { - transform_bbox_by_ctm(bbox); - if(on_char_clipped) - on_char_clipped(bbox, false); - } - else - { - if (pt_in < 4) - { - double cbox[4]; - cairo_clip_extents(cairo, cbox, cbox + 1, cbox + 2, cbox + 3); - bbox_intersect(cbox, bbox, bbox); - } - transform_bbox_by_ctm(bbox); - if (pt_in < 4) - { - if(on_char_clipped) - on_char_clipped(bbox, true); - } - else - { - if (on_char_drawn) - on_char_drawn(bbox); - } - } -#else - transform_bbox_by_ctm(bbox, state); - if (on_char_drawn) - on_char_drawn(bbox); + double cbox[4]; + cairo_clip_extents(cairo, cbox, cbox + 1, cbox + 2, cbox + 3); +#ifdef DEBUG + printf("DrawingTracer::draw_char_bbox::char bbox[%f,%f,%f,%f],clip extents:[%f,%f,%f,%f]\n", bbox[0], bbox[1], bbox[2], bbox[3], cbox[0],cbox[1],cbox[2],cbox[3]); #endif - if (DT_DEBUG) - printf("DrawingTracer::draw_char_bbox:[%f,%f,%f,%f]\n",bbox[0],bbox[1],bbox[2],bbox[3]); + + if (bbox_intersect(bbox, cbox)) { +#ifdef DEBUG + printf("char intersects clip\n"); +#endif + int pts_visible = 0; + // See which points are inside the current clip + if (cairo_in_clip(cairo, bbox[0], bbox[1])) + pts_visible |= 1; + if (cairo_in_clip(cairo, bbox[2], bbox[1])) + pts_visible |= 2; + if (cairo_in_clip(cairo, bbox[2], bbox[3])) + pts_visible |= 4; + if (cairo_in_clip(cairo, bbox[0], bbox[3])) + pts_visible |= 8; + + if (pts_visible == (1|2|4|8)) { +#ifdef DEBUG + printf("char inside clip\n"); +#endif + on_char_drawn(cairo, bbox); + } else { +#ifdef DEBUG + printf("char partial clip (%x)\n", pts_visible); +#endif + on_char_clipped(cairo, bbox, pts_visible); + } + } else { +#ifdef DEBUG + printf("char outside clip\n"); +#endif + on_char_clipped(cairo, bbox, 0); + } } void DrawingTracer::draw_image(GfxState *state) { if (!param.correct_text_visibility) return; + + double x1, y1, x2, y2, x3, y3, x4, y4; + x1 = x4 = y3 = y4 = 0; + x2 = y2 = x3 = y1 = 1; + + xform_pt(x1, y1); + xform_pt(x2, y2); + xform_pt(x3, y3); + xform_pt(x4, y4); + + cairo_new_path(cairo); + cairo_move_to(cairo, x1, y1); + cairo_line_to(cairo, x2, y2); + cairo_line_to(cairo, x3, y3); + cairo_line_to(cairo, x4, y4); + cairo_close_path (cairo); +#ifdef DEBUG + printf("draw_image: [%f,%f], [%f,%f], [%f,%f], [%f,%f]\n", x1, y1, x2, y2, x3, y3, x4, y4); +#endif + double bbox[4] {0, 0, 1, 1}; - draw_non_char_bbox(state, bbox); + tm_transform_bbox(ctm_stack.back(), bbox); + + draw_non_char_bbox(state, bbox, 1); } -void DrawingTracer::draw_char(GfxState *state, double x, double y, double ax, double ay) +void DrawingTracer::draw_char(GfxState *state, double x, double y, double width, double height, int inTransparencyGroup) { - if (!param.correct_text_visibility) - return; - +//printf("x=%f,y=%f,width=%f,height=%f\n", x, y, width, height); Matrix tm, itm; memcpy(tm.m, state->getTextMat(), sizeof(tm.m)); +//printf("tm = %f,%f,%f,%f,%f,%f\n", tm.m[0], tm.m[1], tm.m[2], tm.m[3], tm.m[4], tm.m[5]); double cx = state->getCurX(), cy = state->getCurY(), fs = state->getFontSize(), - ry = state->getRise(), h = state->getHorizScaling(); + ry = state->getRise(), h = state->getHorizScaling(); + +//printf("cx=%f,cy=%f,fs=%f,ry=%f,h=%f\n", cx,cy,fs,ry,h); //cx and cy has been transformed by text matrix, we need to reverse them. tm.invertTo(&itm); double char_cx, char_cy; itm.transform(cx, cy, &char_cx, &char_cy); +//printf("char_cx = %f, char_cy = %f\n", char_cx, char_cy); + //TODO Vertical? Currently vertical/type3 chars are treated as non-chars. double char_m[6] {fs * h, 0, 0, fs, char_cx + x, char_cy + y + ry}; +//printf("char_m = %f,%f,%f,%f,%f,%f\n", char_m[0], char_m[1], char_m[2], char_m[3], char_m[4], char_m[5]); + double final_m[6]; tm_multiply(final_m, tm.m, char_m); - auto font = state->getFont(); - double bbox[4] {0, 0, ax, ay}; - double desc = font->getDescent(), asc = font->getAscent(); - if (font->getWMode() == 0) - { - bbox[1] += desc; - bbox[3] += asc; - } - else - {//TODO Vertical? - } - tm_transform_bbox(final_m, bbox); - draw_char_bbox(state, bbox); +//printf("final_m = %f,%f,%f,%f,%f,%f\n", final_m[0], final_m[1], final_m[2], final_m[3], final_m[4], final_m[5]); + double final_after_ctm[6]; + tm_multiply(final_after_ctm, ctm_stack.back(), final_m); +//printf("final_after_ctm= %f,%f,%f,%f,%f,%f\n", final_after_ctm[0], final_after_ctm[1], final_after_ctm[2], final_after_ctm[3], final_after_ctm[4], final_after_ctm[5]); + double inset = 0.1; + double bbox[4] {inset*width, inset*height, (1-inset)*width, (1-inset)*height}; + +//printf("bbox before: [%f,%f,%f,%f]\n", bbox[0],bbox[1],bbox[2],bbox[3]); +//printf("bbox after: [%f,%f,%f,%f]\n", bbox[0],bbox[1],bbox[2],bbox[3]); + tm_transform_bbox(final_after_ctm, bbox); +//printf("bbox after: [%f,%f,%f,%f]\n", bbox[0],bbox[1],bbox[2],bbox[3]); + draw_char_bbox(state, bbox, inTransparencyGroup); } - -void DrawingTracer::transform_bbox_by_ctm(double * bbox, GfxState * state) -{ -#if ENABLE_SVG - cairo_matrix_t mat; - cairo_get_matrix(cairo, &mat); - double mat_a[6] {mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0}; - tm_transform_bbox(mat_a, bbox); -#else - tm_transform_bbox(state->getCTM(), bbox); -#endif +void DrawingTracer::xform_pt(double & x, double & y) { + tm_transform(ctm_stack.back(), x, y); } } /* namespace pdf2htmlEX */ diff --git a/src/DrawingTracer.h b/src/DrawingTracer.h index 2e3159d..3a0bc88 100644 --- a/src/DrawingTracer.h +++ b/src/DrawingTracer.h @@ -12,6 +12,9 @@ #include +#include +#include + #include "pdf2htmlEX-config.h" #if ENABLE_SVG @@ -31,11 +34,11 @@ public: * bbox in device space. */ // a non-char graphics is drawn - std::function on_non_char_drawn; + std::function on_non_char_drawn; // a char is drawn in the clip area - std::function on_char_drawn; + std::function on_char_drawn; // a char is drawn out of/partially in the clip area - std::function on_char_clipped; + std::function on_char_clipped; DrawingTracer(const Param & param); virtual ~DrawingTracer(); @@ -44,9 +47,9 @@ public: /* * A character is drawing * x, y: glyph-drawing position, in PDF text object space. - * ax, ay: glyph advance, in glyph space. + * width, height: glyph width/height */ - void draw_char(GfxState * state, double x, double y, double ax, double ay); + void draw_char(GfxState * state, double x, double y, double width, double height, int inTransparencyGroup); /* * An image is drawing */ @@ -63,13 +66,15 @@ private: void finish(); // Following methods operate in user space (just before CTM is applied) void do_path(GfxState * state, GfxPath * path); - void draw_non_char_bbox(GfxState * state, double * bbox); - void draw_char_bbox(GfxState * state, double * bbox); + void draw_non_char_bbox(GfxState * state, double * bbox, int what); + void draw_char_bbox(GfxState * state, double * bbox, int inTransparencyGroup); // If cairo is available, parameter state is ignored - void transform_bbox_by_ctm(double * bbox, GfxState * state = nullptr); + void xform_pt(double & x, double & y); const Param & param; + std::vector ctm_stack; + #if ENABLE_SVG cairo_t * cairo; #endif diff --git a/src/HTMLRenderer/HTMLRenderer.h b/src/HTMLRenderer/HTMLRenderer.h index 18e395d..24c76cd 100644 --- a/src/HTMLRenderer/HTMLRenderer.h +++ b/src/HTMLRenderer/HTMLRenderer.h @@ -47,7 +47,7 @@ namespace pdf2htmlEX { struct HTMLRenderer : OutputDev { - HTMLRenderer(const Param & param); + HTMLRenderer(Param & param); virtual ~HTMLRenderer(); void process(PDFDoc * doc); @@ -144,6 +144,13 @@ struct HTMLRenderer : OutputDev virtual void eoFill(GfxState *state); virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax); + virtual void beginTransparencyGroup(GfxState * /*state*/, double * /*bbox*/, + GfxColorSpace * /*blendingColorSpace*/, + GBool /*isolated*/, GBool /*knockout*/, + GBool /*forSoftMask*/); + virtual void endTransparencyGroup(GfxState * /*state*/); + + virtual void processLink(AnnotLink * al); /* @@ -245,11 +252,12 @@ protected: double print_scale (void) const { return 96.0 / DEFAULT_DPI / text_zoom_factor(); } - const Param & param; + Param & param; //////////////////////////////////////////////////// // PDF states //////////////////////////////////////////////////// + int inTransparencyGroup; // track the original (unscaled) values to determine scaling and merge lines // current position double cur_tx, cur_ty; // real text position, in text coords diff --git a/src/HTMLRenderer/draw.cc b/src/HTMLRenderer/draw.cc index 6529418..61460df 100644 --- a/src/HTMLRenderer/draw.cc +++ b/src/HTMLRenderer/draw.cc @@ -62,4 +62,14 @@ GBool HTMLRenderer::axialShadedFill(GfxState *state, GfxAxialShading *shading, d return true; } +void HTMLRenderer::beginTransparencyGroup(GfxState *state, double *bbox, + GfxColorSpace *blendingColorSpace, + GBool isolated, GBool knockout, + GBool forSoftMask) { + inTransparencyGroup++; +} +void HTMLRenderer::endTransparencyGroup(GfxState *state) { + inTransparencyGroup--; +} + } // namespace pdf2htmlEX diff --git a/src/HTMLRenderer/font.cc b/src/HTMLRenderer/font.cc index 0f4680a..cf98db4 100644 --- a/src/HTMLRenderer/font.cc +++ b/src/HTMLRenderer/font.cc @@ -66,10 +66,10 @@ string HTMLRenderer::dump_embedded_font (GfxFont * font, FontInfo & info) auto * id = font->getID(); - Object ref_obj; - ref_obj.initRef(id->num, id->gen); - ref_obj.fetch(xref, &font_obj); - ref_obj.free(); + Object ref_obj(id->num, id->gen); + //ref_obj.initRef(id->num, id->gen); + font_obj = ref_obj.fetch(xref); + //ref_obj.free(); if(!font_obj.isDict()) { @@ -78,7 +78,8 @@ string HTMLRenderer::dump_embedded_font (GfxFont * font, FontInfo & info) } Dict * dict = font_obj.getDict(); - if(dict->lookup("DescendantFonts", &font_obj2)->isArray()) + font_obj2 = dict->lookup("DescendantFonts"); + if(font_obj2.isArray()) { if(font_obj2.arrayGetLength() == 0) { @@ -86,27 +87,31 @@ string HTMLRenderer::dump_embedded_font (GfxFont * font, FontInfo & info) } else { - if(font_obj2.arrayGetLength() > 1) + if(font_obj2.arrayGetLength() > 1) { cerr << "TODO: multiple entries in DescendantFonts array" << endl; - - if(font_obj2.arrayGet(0, &obj2)->isDict()) + } + + obj2 = font_obj2.arrayGet(0); + if(obj2.isDict()) { dict = obj2.getDict(); } } } - if(!dict->lookup("FontDescriptor", &fontdesc_obj)->isDict()) + fontdesc_obj = dict->lookup("FontDescriptor"); + if(!fontdesc_obj.isDict()) { cerr << "Cannot find FontDescriptor " << endl; throw 0; } dict = fontdesc_obj.getDict(); - - if(dict->lookup("FontFile3", &obj)->isStream()) + obj = dict->lookup("FontFile3"); + if(obj.isStream()) { - if(obj.streamGetDict()->lookup("Subtype", &obj1)->isName()) + obj1 = obj.streamGetDict()->lookup("Subtype"); + if(obj1.isName()) { subtype = obj1.getName(); if(subtype == "Type1C") @@ -132,19 +137,19 @@ string HTMLRenderer::dump_embedded_font (GfxFont * font, FontInfo & info) cerr << "Invalid subtype in font descriptor" << endl; throw 0; } - } - else if (dict->lookup("FontFile2", &obj)->isStream()) - { - suffix = ".ttf"; - } - else if (dict->lookup("FontFile", &obj)->isStream()) - { - suffix = ".pfa"; - } - else - { - cerr << "Cannot find FontFile for dump" << endl; - throw 0; + } else { + obj = dict->lookup("FontFile2"); + if (obj.isStream()) { + suffix = ".ttf"; + } else { + obj = dict->lookup("FontFile"); + if (obj.isStream()) { + suffix = ".pfa"; + } else { + cerr << "Cannot find FontFile for dump" << endl; + throw 0; + } + } } if(suffix == "") @@ -175,13 +180,13 @@ string HTMLRenderer::dump_embedded_font (GfxFont * font, FontInfo & info) cerr << "Something wrong when trying to dump font " << hex << fn_id << dec << endl; } - obj2.free(); - obj1.free(); - obj.free(); + //obj2.free(); + //obj1.free(); + //obj.free(); - fontdesc_obj.free(); - font_obj2.free(); - font_obj.free(); + //fontdesc_obj.free(); + //font_obj2.free(); + //font_obj.free(); return filepath; } @@ -237,7 +242,7 @@ string HTMLRenderer::dump_type3_font (GfxFont * font, FontInfo & info) surface = cairo_svg_surface_create(glyph_filename.c_str(), transformed_bbox_width * scale, transformed_bbox_height * scale); cairo_svg_surface_restrict_to_version(surface, CAIRO_SVG_VERSION_1_2); - cairo_surface_set_fallback_resolution(surface, param.h_dpi, param.v_dpi); + cairo_surface_set_fallback_resolution(surface, param.actual_dpi, param.actual_dpi); cairo_t * cr = cairo_create(surface); // track the position of the origin @@ -373,6 +378,14 @@ string HTMLRenderer::dump_type3_font (GfxFont * font, FontInfo & info) #endif } +namespace { + +void output_map_file_header(std::ostream& out) { + out << "glyph_code mapped_code unicode" << std::endl; +} + +} // namespace + void HTMLRenderer::embed_font(const string & filepath, GfxFont * font, FontInfo & info, bool get_metric_only) { if(param.debug) @@ -528,6 +541,7 @@ void HTMLRenderer::embed_font(const string & filepath, GfxFont * font, FontInfo ffw_reencode_glyph_order(); GfxCIDFont * _font = dynamic_cast(font); + assert(_font != nullptr); // To locate CID2GID for the font // as in CairoFontEngine.cc @@ -574,6 +588,7 @@ void HTMLRenderer::embed_font(const string & filepath, GfxFont * font, FontInfo map_filename = (char*)str_fmt("%s/f%llx.map", param.tmp_dir.c_str(), info.id); tmp_files.add(map_filename); map_outf.open(map_filename); + output_map_file_header(map_outf); } unordered_set codeset; @@ -650,6 +665,7 @@ void HTMLRenderer::embed_font(const string & filepath, GfxFont * font, FontInfo { map_outf.close(); map_outf.open(map_filename); + output_map_file_header(map_outf); } continue; } diff --git a/src/HTMLRenderer/general.cc b/src/HTMLRenderer/general.cc index 6a54194..c1e8825 100644 --- a/src/HTMLRenderer/general.cc +++ b/src/HTMLRenderer/general.cc @@ -41,12 +41,13 @@ using std::abs; using std::cerr; using std::endl; -HTMLRenderer::HTMLRenderer(const Param & param) +HTMLRenderer::HTMLRenderer(Param & param) :OutputDev() ,param(param) ,html_text_page(param, all_manager) ,preprocessor(param) ,tmp_files(param) + ,covered_text_detector(param) ,tracer(param) { if(!(param.debug)) @@ -81,11 +82,11 @@ HTMLRenderer::HTMLRenderer(const Param & param) all_manager.bottom .set_eps(EPS); tracer.on_char_drawn = - [this](double * box) { covered_text_detector.add_char_bbox(box); }; + [this](cairo_t *cairo, double * box) { covered_text_detector.add_char_bbox(cairo, box); }; tracer.on_char_clipped = - [this](double * box, bool partial) { covered_text_detector.add_char_bbox_clipped(box, partial); }; + [this](cairo_t *cairo, double * box, int partial) { covered_text_detector.add_char_bbox_clipped(cairo, box, partial); }; tracer.on_non_char_drawn = - [this](double * box) { covered_text_detector.add_non_char_bbox(box); }; + [this](cairo_t *cairo, double * box, int what) { covered_text_detector.add_non_char_bbox(cairo, box, what); }; } HTMLRenderer::~HTMLRenderer() @@ -93,6 +94,8 @@ HTMLRenderer::~HTMLRenderer() ffw_finalize(); } +#define MAX_DIMEN 9000 + void HTMLRenderer::process(PDFDoc *doc) { cur_doc = doc; @@ -119,12 +122,22 @@ void HTMLRenderer::process(PDFDoc *doc) int page_count = (param.last_page - param.first_page + 1); for(int i = param.first_page; i <= param.last_page ; ++i) { + param.actual_dpi = param.desired_dpi; + param.max_dpi = 72 * MAX_DIMEN / max(doc->getPageCropWidth(i), doc->getPageCropHeight(i)); + + if (param.actual_dpi > param.max_dpi) { + param.actual_dpi = param.max_dpi; + printf("Warning:Page %d clamped to %f DPI\n", i, param.actual_dpi); + } + if (param.tmp_file_size_limit != -1 && tmp_files.get_total_size() > param.tmp_file_size_limit * 1024) { - cerr << "Stop processing, reach max size\n"; + if(param.quiet == 0) + cerr << "Stop processing, reach max size\n"; break; } - cerr << "Working: " << (i-param.first_page) << "/" << page_count << '\r' << flush; + if (param.quiet == 0) + cerr << "Working: " << (i-param.first_page) << "/" << page_count << '\r' << flush; if(param.split_pages) { @@ -147,15 +160,21 @@ void HTMLRenderer::process(PDFDoc *doc) false, // printing nullptr, nullptr, nullptr, nullptr); + if (param.desired_dpi != param.actual_dpi) { + printf("Page %d DPI change %.1f => %.1f\n", i, param.desired_dpi, param.actual_dpi); + } + if(param.split_pages) { delete f_curpage; f_curpage = nullptr; } } - if(page_count >= 0) + if(page_count >= 0 && param.quiet == 0) cerr << "Working: " << page_count << "/" << page_count; - cerr << endl; + + if(param.quiet == 0) + cerr << endl; //////////////////////// // Process Outline @@ -167,7 +186,8 @@ void HTMLRenderer::process(PDFDoc *doc) bg_renderer = nullptr; fallback_bg_renderer = nullptr; - cerr << endl; + if(param.quiet == 0) + cerr << endl; } void HTMLRenderer::setDefaultCTM(double *ctm) diff --git a/src/HTMLRenderer/link.cc b/src/HTMLRenderer/link.cc index 3c90ab5..b8b4129 100644 --- a/src/HTMLRenderer/link.cc +++ b/src/HTMLRenderer/link.cc @@ -56,73 +56,70 @@ static string get_linkdest_detail_str(LinkDest * dest, Catalog * catalog, int & // dec sout << "[" << pageno; - if(dest) + switch(dest->getKind()) { - switch(dest->getKind()) - { - case destXYZ: - { - sout << ",\"XYZ\","; - if(dest->getChangeLeft()) - sout << (dest->getLeft()); - else - sout << "null"; - sout << ","; - if(dest->getChangeTop()) - sout << (dest->getTop()); - else - sout << "null"; - sout << ","; - if(dest->getChangeZoom()) - sout << (dest->getZoom()); - else - sout << "null"; - } - break; - case destFit: - sout << ",\"Fit\""; - break; - case destFitH: - sout << ",\"FitH\","; - if(dest->getChangeTop()) - sout << (dest->getTop()); - else - sout << "null"; - break; - case destFitV: - sout << ",\"FitV\","; + case destXYZ: + { + sout << ",\"XYZ\","; if(dest->getChangeLeft()) sout << (dest->getLeft()); else sout << "null"; - break; - case destFitR: - sout << ",\"FitR\"," - << (dest->getLeft()) << "," - << (dest->getBottom()) << "," - << (dest->getRight()) << "," - << (dest->getTop()); - break; - case destFitB: - sout << ",\"FitB\""; - break; - case destFitBH: - sout << ",\"FitBH\","; + sout << ","; if(dest->getChangeTop()) sout << (dest->getTop()); else sout << "null"; - break; - case destFitBV: - sout << ",\"FitBV\","; - if(dest->getChangeLeft()) - sout << (dest->getLeft()); + sout << ","; + if(dest->getChangeZoom()) + sout << (dest->getZoom()); else sout << "null"; - break; - default: - break; - } + } + break; + case destFit: + sout << ",\"Fit\""; + break; + case destFitH: + sout << ",\"FitH\","; + if(dest->getChangeTop()) + sout << (dest->getTop()); + else + sout << "null"; + break; + case destFitV: + sout << ",\"FitV\","; + if(dest->getChangeLeft()) + sout << (dest->getLeft()); + else + sout << "null"; + break; + case destFitR: + sout << ",\"FitR\"," + << (dest->getLeft()) << "," + << (dest->getBottom()) << "," + << (dest->getRight()) << "," + << (dest->getTop()); + break; + case destFitB: + sout << ",\"FitB\""; + break; + case destFitBH: + sout << ",\"FitBH\","; + if(dest->getChangeTop()) + sout << (dest->getTop()); + else + sout << "null"; + break; + case destFitBV: + sout << ",\"FitBV\","; + if(dest->getChangeLeft()) + sout << (dest->getLeft()); + else + sout << "null"; + break; + default: + break; } sout << "]"; @@ -166,6 +163,7 @@ string HTMLRenderer::get_linkaction_str(LinkAction * action, string & detail) case actionURI: { auto * real_action = dynamic_cast(action); + assert(real_action != nullptr); dest_str = real_action->getURI()->getCString(); } break; diff --git a/src/HTMLRenderer/state.cc b/src/HTMLRenderer/state.cc index f26b17f..e6ae4bb 100644 --- a/src/HTMLRenderer/state.cc +++ b/src/HTMLRenderer/state.cc @@ -104,6 +104,7 @@ void HTMLRenderer::clipToStrokePath(GfxState * state) } void HTMLRenderer::reset_state() { + inTransparencyGroup = 0; draw_text_scale = 1.0; cur_font_size = 0.0; diff --git a/src/HTMLRenderer/text.cc b/src/HTMLRenderer/text.cc index e58a17a..754c75b 100644 --- a/src/HTMLRenderer/text.cc +++ b/src/HTMLRenderer/text.cc @@ -33,16 +33,23 @@ void HTMLRenderer::drawString(GfxState * state, GooString * s) double cur_word_space = state->getWordSpace(); double cur_horiz_scaling = state->getHorizScaling(); + bool drawChars = true; // Writing mode fonts and Type 3 fonts are rendered as images // I don't find a way to display writing mode fonts in HTML except for one div for each character, which is too costly // For type 3 fonts, due to the font matrix, still it's hard to show it on HTML - if( (font == nullptr) - || (font->getWMode()) - || ((font->getType() == fontType3) && (!param.process_type3)) + + + if(state->getFont() + && ( (state->getFont()->getWMode()) + || ((state->getFont()->getType() == fontType3) && (!param.process_type3)) + || (state->getRender() >= 4) + ) ) { - return; + // We still want to go through the loop to ensure characters are added to the covered_chars array + drawChars = false; +//printf("%d / %d / %d\n", state->getFont()->getWMode(), (state->getFont()->getType() == fontType3), state->getRender()); } // see if the line has to be closed due to state change @@ -74,7 +81,7 @@ void HTMLRenderer::drawString(GfxState * state, GooString * s) while (len > 0) { auto n = font->getNextChar(p, len, &code, &u, &uLen, &ax, &ay, &ox, &oy); - HR_DEBUG(printf("HTMLRenderer::drawString:unicode=%lc(%d)\n", (wchar_t)u[0], u[0])); + HR_DEBUG(printf("HTMLRenderer::drawString:unicode=%lc(%d)\n", u ? (wchar_t)u[0] : ' ', u ? u[0] : -1)); if(!(equal(ox, 0) && equal(oy, 0))) { @@ -82,7 +89,34 @@ void HTMLRenderer::drawString(GfxState * state, GooString * s) } ddx = ax * cur_font_size + cur_letter_space; ddy = ay * cur_font_size; - tracer.draw_char(state, dx, dy, ax, ay); + + double width = 0, height = font->getAscent(); + if (font->isCIDFont()) { + char buf[2]; + buf[0] = (code >> 8) & 0xff; + buf[1] = (code & 0xff); + width = ((GfxCIDFont *)font)->getWidth(buf, 2); + } else { + width = ((Gfx8BitFont *)font)->getWidth(code); + } + + if (width == 0 || height == 0) { + //cerr << "CID: " << font->isCIDFont() << ", char:" << code << ", width:" << width << ", ax:" << ax << ", height:" << height << ", ay:" << ay << endl; + } + if (width == 0) { + width = ax; + if (width == 0) { + width = 0.001; + } + } + if (height == 0) { + height = ay; + if (height == 0) { + height = 0.001; + } + } + + tracer.draw_char(state, dx, dy, width, height, !drawChars || inTransparencyGroup); bool is_space = false; if (n == 1 && *p == ' ') @@ -99,6 +133,7 @@ void HTMLRenderer::drawString(GfxState * state, GooString * s) is_space = true; } + if(is_space && (param.space_as_offset)) { html_text_page.get_cur_line()->append_padding_char(); @@ -158,7 +193,7 @@ bool HTMLRenderer::is_char_covered(int index) { std::cerr << "Warning: HTMLRenderer::is_char_covered: index out of bound: " << index << ", size: " << covered.size() <