From 4f9de1dfebdc66d367d730252d6f224d37e96ee6 Mon Sep 17 00:00:00 2001 From: Akira Kakuto Date: Mon, 1 Feb 2021 06:11:18 +0000 Subject: xpdf 4.03 git-svn-id: svn://tug.org/texlive/trunk@57588 c570f23f-e606-0410-a88d-b1316a301751 --- Build/source/libs/xpdf/xpdf-src/ANNOUNCE | 20 +- Build/source/libs/xpdf/xpdf-src/CHANGES | 96 +- Build/source/libs/xpdf/xpdf-src/INSTALL | 24 +- Build/source/libs/xpdf/xpdf-src/README | 12 +- Build/source/libs/xpdf/xpdf-src/cmake-config.txt | 32 +- Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.1 | 8 +- Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.cat | 6 +- Build/source/libs/xpdf/xpdf-src/doc/pdffonts.1 | 8 +- Build/source/libs/xpdf/xpdf-src/doc/pdffonts.cat | 6 +- Build/source/libs/xpdf/xpdf-src/doc/pdfimages.1 | 8 +- Build/source/libs/xpdf/xpdf-src/doc/pdfimages.cat | 6 +- Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.1 | 8 +- Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.cat | 6 +- Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.1 | 13 +- Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.cat | 11 +- Build/source/libs/xpdf/xpdf-src/doc/pdftopng.1 | 11 +- Build/source/libs/xpdf/xpdf-src/doc/pdftopng.cat | 9 +- Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.1 | 11 +- Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.cat | 9 +- Build/source/libs/xpdf/xpdf-src/doc/pdftops.1 | 14 +- Build/source/libs/xpdf/xpdf-src/doc/pdftops.cat | 12 +- Build/source/libs/xpdf/xpdf-src/doc/pdftotext.1 | 21 +- Build/source/libs/xpdf/xpdf-src/doc/pdftotext.cat | 21 +- Build/source/libs/xpdf/xpdf-src/doc/xpdf.1 | 56 +- Build/source/libs/xpdf/xpdf-src/doc/xpdf.cat | 148 +- Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.5 | 55 +- Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.cat | 106 +- .../libs/xpdf/xpdf-src/fofi/FoFiIdentifier.cc | 19 +- .../source/libs/xpdf/xpdf-src/fofi/FoFiTrueType.cc | 26 +- Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1.cc | 2 +- Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.cc | 84 +- Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.h | 2 +- Build/source/libs/xpdf/xpdf-src/goo/CMakeLists.txt | 1 + Build/source/libs/xpdf/xpdf-src/goo/FixedPoint.h | 15 + Build/source/libs/xpdf/xpdf-src/goo/Trace.cc | 114 + Build/source/libs/xpdf/xpdf-src/goo/Trace.h | 43 + Build/source/libs/xpdf/xpdf-src/goo/gfile.cc | 141 +- Build/source/libs/xpdf/xpdf-src/goo/gfile.h | 13 +- Build/source/libs/xpdf/xpdf-src/goo/gmem.h | 8 - Build/source/libs/xpdf/xpdf-src/splash/Splash.cc | 8155 ++++++++++---------- Build/source/libs/xpdf/xpdf-src/splash/Splash.h | 225 +- .../libs/xpdf/xpdf-src/splash/SplashBitmap.cc | 20 +- .../libs/xpdf/xpdf-src/splash/SplashBitmap.h | 4 + .../source/libs/xpdf/xpdf-src/splash/SplashFont.cc | 50 +- .../libs/xpdf/xpdf-src/splash/SplashFontEngine.cc | 36 + .../libs/xpdf/xpdf-src/splash/SplashFontEngine.h | 6 + .../libs/xpdf/xpdf-src/splash/SplashXPath.cc | 3 +- Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.cc | 1825 ++++- Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.h | 66 +- Build/source/libs/xpdf/xpdf-src/xpdf/Annot.cc | 2 +- .../source/libs/xpdf/xpdf-src/xpdf/CMakeLists.txt | 8 +- Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.cc | 10 +- Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.h | 6 +- .../libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.cc | 59 + .../libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.h | 1 + Build/source/libs/xpdf/xpdf-src/xpdf/Form.cc | 99 - Build/source/libs/xpdf/xpdf-src/xpdf/Form.h | 77 - Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.cc | 288 +- Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.h | 11 +- Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.cc | 37 +- Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.h | 2 + Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.cc | 104 +- Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.h | 8 + .../source/libs/xpdf/xpdf-src/xpdf/GlobalParams.cc | 596 +- .../source/libs/xpdf/xpdf-src/xpdf/GlobalParams.h | 120 +- Build/source/libs/xpdf/xpdf-src/xpdf/HTMLGen.cc | 5 +- .../libs/xpdf/xpdf-src/xpdf/ImageOutputDev.cc | 85 +- .../libs/xpdf/xpdf-src/xpdf/ImageOutputDev.h | 9 +- .../source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.cc | 57 +- Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.h | 1 + Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.cc | 615 +- Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.h | 16 +- Build/source/libs/xpdf/xpdf-src/xpdf/Lexer.cc | 15 +- Build/source/libs/xpdf/xpdf-src/xpdf/Object.cc | 9 +- .../libs/xpdf/xpdf-src/xpdf/OptionalContent.cc | 47 +- Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.cc | 4 +- Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.h | 25 +- Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.cc | 164 +- Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.h | 21 +- Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.cc | 52 +- Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.h | 1 + .../source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.cc | 84 +- Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.h | 25 +- Build/source/libs/xpdf/xpdf-src/xpdf/Page.cc | 11 +- Build/source/libs/xpdf/xpdf-src/xpdf/Page.h | 1 + .../libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.cc | 35 +- .../libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.h | 15 +- .../source/libs/xpdf/xpdf-src/xpdf/ShadingImage.cc | 1327 ++++ .../source/libs/xpdf/xpdf-src/xpdf/ShadingImage.h | 106 + .../libs/xpdf/xpdf-src/xpdf/SplashOutputDev.cc | 1040 +-- .../libs/xpdf/xpdf-src/xpdf/SplashOutputDev.h | 27 +- Build/source/libs/xpdf/xpdf-src/xpdf/Stream.cc | 132 +- Build/source/libs/xpdf/xpdf-src/xpdf/Stream.h | 4 + .../libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc | 1226 ++- .../source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.h | 68 +- Build/source/libs/xpdf/xpdf-src/xpdf/TextString.cc | 10 + Build/source/libs/xpdf/xpdf-src/xpdf/TextString.h | 3 + Build/source/libs/xpdf/xpdf-src/xpdf/WebFont.cc | 37 +- Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.cc | 3245 -------- Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.h | 181 - Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.cc | 675 ++ Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.h | 166 + Build/source/libs/xpdf/xpdf-src/xpdf/XRef.cc | 365 +- Build/source/libs/xpdf/xpdf-src/xpdf/XRef.h | 10 +- Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.cc | 8 +- Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.h | 2 +- Build/source/libs/xpdf/xpdf-src/xpdf/config.h | 24 +- Build/source/libs/xpdf/xpdf-src/xpdf/pdfdetach.cc | 52 +- Build/source/libs/xpdf/xpdf-src/xpdf/pdffonts.cc | 6 +- Build/source/libs/xpdf/xpdf-src/xpdf/pdfimages.cc | 2 +- Build/source/libs/xpdf/xpdf-src/xpdf/pdfinfo.cc | 27 +- Build/source/libs/xpdf/xpdf-src/xpdf/pdftohtml.cc | 19 +- Build/source/libs/xpdf/xpdf-src/xpdf/pdftopng.cc | 14 +- Build/source/libs/xpdf/xpdf-src/xpdf/pdftoppm.cc | 7 +- Build/source/libs/xpdf/xpdf-src/xpdf/pdftops.cc | 4 +- Build/source/libs/xpdf/xpdf-src/xpdf/pdftotext.cc | 24 +- 116 files changed, 12895 insertions(+), 10236 deletions(-) create mode 100644 Build/source/libs/xpdf/xpdf-src/goo/Trace.cc create mode 100644 Build/source/libs/xpdf/xpdf-src/goo/Trace.h delete mode 100644 Build/source/libs/xpdf/xpdf-src/xpdf/Form.cc delete mode 100644 Build/source/libs/xpdf/xpdf-src/xpdf/Form.h create mode 100644 Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.cc create mode 100644 Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.h delete mode 100644 Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.cc delete mode 100644 Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.h create mode 100644 Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.cc create mode 100644 Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.h (limited to 'Build/source/libs/xpdf/xpdf-src') diff --git a/Build/source/libs/xpdf/xpdf-src/ANNOUNCE b/Build/source/libs/xpdf/xpdf-src/ANNOUNCE index 025303c44de..e070ad95d5b 100644 --- a/Build/source/libs/xpdf/xpdf-src/ANNOUNCE +++ b/Build/source/libs/xpdf/xpdf-src/ANNOUNCE @@ -1,4 +1,4 @@ -Subject: ANNOUNCE: Xpdf 4.02 - a PDF viewer and related tools +Subject: ANNOUNCE: Xpdf 4.03 - a PDF viewer and related tools Glyph & Cog, LLC is pleased to announce a new version of Xpdf, the open source Portable Document Format (PDF) viewer. The Xpdf project @@ -10,18 +10,16 @@ non-graphical components (pdftops, pdftotext, etc.) run on Linux, Unix, Windows, MacOSX, and pretty much any other system with a decent C++ compiler. -4.02 is primarily a bug fix release. There are some new features: +4.03 is primarily a bug fix release. There are some new features: -* Pdftohtml now extracts embedded fonts. +* XpdfReader improvements: + - Implemented selection extension via shift-click, and word/line + selection via double/triple click. + - Added default bindings for ctrl-mousewheel-up/down to zoom in/out. + - Added a help menu item that shows all of the key bindings. -* Various user interface tweaks in XpdfReader: added a button to - toggle sidebar visibilty, added menu items to toggle the sidebar and - toolbar, added the 'initialDisplayMode', 'initialToolbarState', and - 'initialSelectMode' xpdfrc settings. - -* If XpdfReader is already running, double-clicking on a PDF file (or - dragging-and-dropping it on the XpdfReader icon) opens the file in a - new tab (via the new '-open' command line switch). +* Various new command line options for pdftotext, pdftohtml, pdftoppm, + pdftopng, and xpdf. See the `CHANGES' file for a complete list of changes. diff --git a/Build/source/libs/xpdf/xpdf-src/CHANGES b/Build/source/libs/xpdf/xpdf-src/CHANGES index e707ff01afa..f2dccc095fd 100644 --- a/Build/source/libs/xpdf/xpdf-src/CHANGES +++ b/Build/source/libs/xpdf/xpdf-src/CHANGES @@ -2645,4 +2645,98 @@ Increased the width of the font name column in the pdffonts output. Function objects weren't checking their input/output counts properly. [Thanks to Pangu Lab for the bug report.] TextPage::findGaps() wasn't checking the x/y min/max values for int - overflow. [Thanks to Taolaw for the bugf report.] + overflow. [Thanks to Taolaw for the bug report.] + +4.03 (2021-jan-28) +------------------ +Implemented selection extension via shift-click, and word/line + selection via double/triple click. +Added default bindings for ctrl-mousewheel-up/down to zoom in/out. +Added the "-nofonts" option to pdftohtml. +Added the "simple2" mode to pdftotext. +Added the "-rot" flag to xpdf, pdftoppm, and pdftopng. +Added the "-listencodings" flag to pdftotext. +Added the 'copyLinkTarget' command. +Added the 'selectionColor' xpdfrc setting. +Added the 'initialSidebarWidth' xpdfrc setting. +Added support for @"..." strings in xpdfrc files. This includes using + '%' as an escape character, and also the '${DATADIR}' variable. +Added a help menu item that shows all of the key bindings. +Reorganized the gradient shading rasterization code to avoid + transparency artifacts between triangles/patches. +Use interpolation rather than subdivision to rasterize Gouraud + triangle shadings. +Allow escaping (via the \x01 character) in command args, so that "xpdf + -open foo(123).pdf" works correctly. +Extended the damaged file repair code to handle PDF files that use + xref streams and object streams. +Modified pdfinfo to print the encryption algorithm used (if any). +Handle Windows shortcut (.lnk) files in Xpdf and in the command line + tools. +Display an error dialog when trying to copy text from a protected + file. +Updated all four of the CJK language support packages with the latest + Adobe data. +Modified pdftohtml to draw 'invisible' text over non-horizontal words. +Catalog.pageLabels wasn't being properly initialized to NULL. [Thanks + to Dhiraj for the bug report.] +Added a sanity check for JBIG2 symbol width. [Reported by Marc + Schoenefeld.] +Fixed a bug in the fontconfig calls that was causing Xpdf to crash. +The Type 3 font cache code wasn't correctly handling the case where a + Type 3 char refers to another char in the same T3 font. [Thanks to + Pangu Lab for the bug report.] +Xpdf was crashing if 'view page labels' was enabled without any + document open. [Thanks to TeamSeri0us for the bug report.] +The Type 1C-to-Type 1 font converter wasn't checking for + divide-by-zero with rational numbers. [Thanks to TeamSeri0us for + the bug report.] +Fixed a bug in the TrueType font parser - zero-length loca tables + weren't handled correctly. [Thanks to TeamSeri0us for the bug + report.] +Fixed an integer overflow bug in the transparency group setup code. + [Thanks to TeamSeri0us for the bug report.] +Pdfinfo with the "-box" option was crashing on zero-page PDF files. + [Thanks to TeamSeri0us for the bug report.] +The code that caches scaled images wasn't checking the rendering + intent. [Thanks to Martin Muskens at Ergosoft for the bug report.] +The JBIG2 decoder now checks for gibberish values in various segment + headers, but keeps any data decoded prior to that. +Fixed two bugs in the FoFi code that handles CFF fonts. [Thanks to + dsmic for the bug report.] +Redesigned the image scaling code, for performance. +Cleaned up the code that sanity-checks font sizes to avoid trying to + allocate too much memory for the font cache. +Implemented precincts in the JPEG 2000 decoder. +The page-up/down snapping code was calling TileMap::getPageTopY() with + invalid page numbers. [Thanks to dfandrich for the bug report.] +Added missing error-checking when parsing an annotation appearance + stream's bbox. +Handled UTF-16 sequences in ToUnicode maps. +Rewrote the code that draws static XFA forms: combined it with the + AcroForm code, to match Adobe's behavior. Removed the enableXFA + xpdfrc setting. +Check for indirect references in content streams and report an error. +Doing overprint preview in tiling patterns requires tracking the + overprint mask at each pixel. +Check for "decompression bombs" in Flate and LZW streams. +The nextPageNoScroll and prevPageNoScroll commands weren't working + correctly in continuous mode. +Disable stroke adjustment when drawing Type 3 characters. +The "xpdf -open" flag now constructs an absolute path, so it's not + dependent on the current directory when xpdf was started. +Check for infinite loops in Type 1C charstring subroutines. [Thanks + to blbi for the bug report.] +Fixed an incomplete test for headless CFF files. [Thanks to Chengbin + for the bug report.] +Tweaked the code that computes segment slope in SplashXPath.cc to + avoid problems with extremely small coordinate values. [Thanks to + Chengbin for the bug report.] +Check for an invalid segment length in PSOutputDev's PFB parse. + [Thanks to Chengbin for the bug report.] +Check for JPEG 2000 segments that are only allowed in the first + tile-part of a tile. [Thanks to Chengbin for the bug report.] +Check for invalid AcroForm ListBox field top index. [Thanks to + Chengbin for the bug report.] +Check for invalid mesh shading parameters. [Thanks to Chengbin for + the bug report.] diff --git a/Build/source/libs/xpdf/xpdf-src/INSTALL b/Build/source/libs/xpdf/xpdf-src/INSTALL index b7e184538c8..5a7eb6423f4 100644 --- a/Build/source/libs/xpdf/xpdf-src/INSTALL +++ b/Build/source/libs/xpdf/xpdf-src/INSTALL @@ -1,11 +1,11 @@ Xpdf ==== -version 4.02 -2019-sep-25 +version 4.03 +2021-jan-28 The Xpdf software and documentation are -copyright 1996-2019 Glyph & Cog, LLC. +copyright 1996-2021 Glyph & Cog, LLC. Email: xpdf@xpdfreader.com WWW: http://www.xpdfreader.com/ @@ -115,25 +115,29 @@ different systems. -DSYSTEM_XPDFRC="/etc/xpdfrc" Look for a system-wide xpdfrc config file in this directory. - -DCMAKE_DISABLE_FIND_PACKAGE_Qt4=1 - -DCMAKE_DISABLE_FIND_PACKAGE_Qt5Widgets=1 + -DXPDFRC_DATADIR="/usr/share/xpdf" + The ${DATADIR} variable in xpdfrc config files will expand to + this string. + + -DCMAKE_DISABLE_FIND_PACKAGE_Qt4=1 + -DCMAKE_DISABLE_FIND_PACKAGE_Qt5Widgets=1 Do not search for the Qt4/Qt5 libraries. This will disable building the GUI viewer (xpdf). Cmake will look for a "qmake" binary -- make sure the first qmake binary on your executable search path matches the desired version of Qt. - -DCMAKE_C_FLAGS="..." - -DCMAKE_CXX_FLAGS="..." + -DCMAKE_C_FLAGS="..." + -DCMAKE_CXX_FLAGS="..." Set additional options to pass to the C and/or C++ compilers. - -DCMAKE_EXE_LINKER_FLAGS="..." + -DCMAKE_EXE_LINKER_FLAGS="..." Set additional options to pass to the linker. - -DCMAKE_INSTALL_BINDIR + -DCMAKE_INSTALL_BINDIR Set the bin directory, relative to CMAKE_INSTALL_PREFIX (typically "bin"). - -DCMAKE_INSTALL_MANDIR + -DCMAKE_INSTALL_MANDIR Set the man directory, relative to CMAKE_INSTALL_PREFIX (typically "man" or "share/man"). diff --git a/Build/source/libs/xpdf/xpdf-src/README b/Build/source/libs/xpdf/xpdf-src/README index ebecfe97c3e..e5a114262f0 100644 --- a/Build/source/libs/xpdf/xpdf-src/README +++ b/Build/source/libs/xpdf/xpdf-src/README @@ -1,17 +1,17 @@ Xpdf ==== -version 4.02 -2019-sep-25 +version 4.03 +2021-jan-28 The Xpdf software and documentation are -copyright 1996-2019 Glyph & Cog, LLC. +copyright 1996-2021 Glyph & Cog, LLC. Email: xpdf@xpdfreader.com WWW: http://www.xpdfreader.com/ The PDF data structures, operators, and specification are -documented in ISO 32000-2:2017. +documented in ISO 32000-2:2020. What is Xpdf? @@ -326,8 +326,8 @@ were changes made to the bitstream format between that draft and the published spec.] ISO, International Standard -- Document Management - Portable document -format - Part 2: PDF 2.0. ISO 32000-2:2017. -[The manual for PDF version 2.0.] +format - Part 2: PDF 2.0. ISO 32000-2:2020. +[The specification for PDF version 2.0.] ITU, "Standardization of Group 3 facsimile terminals for document transmission", ITU-T Recommendation T.4, 1999. diff --git a/Build/source/libs/xpdf/xpdf-src/cmake-config.txt b/Build/source/libs/xpdf/xpdf-src/cmake-config.txt index 3fe2faf125c..e4b3650fcb3 100644 --- a/Build/source/libs/xpdf/xpdf-src/cmake-config.txt +++ b/Build/source/libs/xpdf/xpdf-src/cmake-config.txt @@ -8,6 +8,17 @@ # #======================================================================== +if (POLICY CMP0074) + cmake_policy(SET CMP0074 NEW) +endif () + +if (APPLE) + if (POLICY CMP0042) + cmake_policy(SET CMP0042 NEW) + endif () + set(CMAKE_MACOSX_RPATH 1) +endif () + include(CheckFunctionExists) include(CheckCXXSourceCompiles) include(GNUInstallDirs) @@ -41,12 +52,12 @@ if (CMAKE_GENERATOR STREQUAL "Unix Makefiles") CMAKE_CXX_FLAGS_PROFILING CMAKE_EXE_LINKER_FLAGS_PROFILING) set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING - "Choose build mode - options are: None Debug Release RelWithDebInfo MinSiz -eRel Profiling") + "Choose build mode - options are: None Debug Release RelWithDebInfo MinSizeRel Profiling") endif () #--- set default C/C++ compiler flags for Windows if (WIN32) + option(USE_MT_IN_DEBUG "use /MT instead of /MTd in debug builds" OFF) foreach (var CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_MINSIZEREL @@ -54,8 +65,13 @@ if (WIN32) # note: this converts /MD to /MT and /MDd to /MTd string(REGEX REPLACE "/MD" "/MT" ${var} "${${var}}") endforeach () - set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/NODEFAULTLIB:libcmt ${CMAKE_EXE_LINKER_FLAGS_DEBUG}") - set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "/NODEFAULTLIB:libcmt ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}") + if (USE_MT_IN_DEBUG) + string(REPLACE "/MTd" "/MT" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") + string(REPLACE "/MTd" "/MT" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}") + else () + set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/NODEFAULTLIB:libcmt ${CMAKE_EXE_LINKER_FLAGS_DEBUG}") + set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "/NODEFAULTLIB:libcmt ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}") + endif () foreach (var CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL @@ -88,6 +104,12 @@ if (SYSTEM_XPDFRC) else () set(SYSTEM_XPDFRC_DEFINE "/* #undef SYSTEM_XPDFRC */") endif () +option(XPDFRC_DATADIR "directory to use for the DATADIR xpdfrc variable" "") +if (XPDFRC_DATADIR) + set(XPDFRC_DATADIR_DEFINE "#define XPDFRC_DATADIR \"${XPDFRC_DATADIR}\"") +else () + set(XPDFRC_DATADIR_DEFINE "/* #undef SYSTEM_XPDFRC */") +endif () if (WIN32) option(XPDFWIDGET_PRINTING "include printing support in XpdfWidget" OFF) else () @@ -194,7 +216,7 @@ if (Qt5Widgets_FOUND) find_package(Qt5Network) find_package(Qt5PrintSupport) else () - find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED) + find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork) endif () if(Qt5Widgets_FOUND) message(STATUS "Qt5 found") diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.1 b/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.1 index 82497df84fc..7fd3b8a5231 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.1 @@ -1,8 +1,8 @@ -.\" Copyright 2013-2019 Glyph & Cog, LLC -.TH pdfdetach 1 "25 Sep 2019" +.\" Copyright 2013-2021 Glyph & Cog, LLC +.TH pdfdetach 1 "28 Jan 2021" .SH NAME pdfdetach \- Portable Document Format (PDF) document embedded file -extractor (version 4.02) +extractor (version 4.03) .SH SYNOPSIS .B pdfdetach [options] @@ -90,7 +90,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdfinfo software and documentation are copyright 1996-2019 Glyph & +The pdfinfo software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.cat b/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.cat index 3d4b026e701..000eb66e5ae 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.cat @@ -4,7 +4,7 @@ pdfdetach(1) General Commands Manual pdfdetach(1) NAME pdfdetach - Portable Document Format (PDF) document embedded file - extractor (version 4.02) + extractor (version 4.03) SYNOPSIS pdfdetach [options] [PDF-file] @@ -79,7 +79,7 @@ EXIT CODES 99 Other error. AUTHOR - The pdfinfo software and documentation are copyright 1996-2019 Glyph & + The pdfinfo software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO @@ -89,4 +89,4 @@ SEE ALSO - 25 Sep 2019 pdfdetach(1) + 28 Jan 2021 pdfdetach(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.1 b/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.1 index b8ffaa22b2b..0192bd3247e 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.1 @@ -1,8 +1,8 @@ -.\" Copyright 1999-2019 Glyph & Cog, LLC -.TH pdffonts 1 "25 Sep 2019" +.\" Copyright 1999-2021 Glyph & Cog, LLC +.TH pdffonts 1 "28 Jan 2021" .SH NAME pdffonts \- Portable Document Format (PDF) font analyzer (version -4.02) +4.03) .SH SYNOPSIS .B pdffonts [options] @@ -147,7 +147,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdffonts software and documentation are copyright 1996-2019 Glyph +The pdffonts software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.cat b/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.cat index 363699cf1c5..59f4dd90ca9 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.cat @@ -3,7 +3,7 @@ pdffonts(1) General Commands Manual pdffonts(1) NAME - pdffonts - Portable Document Format (PDF) font analyzer (version 4.02) + pdffonts - Portable Document Format (PDF) font analyzer (version 4.03) SYNOPSIS pdffonts [options] [PDF-file] @@ -105,7 +105,7 @@ EXIT CODES 99 Other error. AUTHOR - The pdffonts software and documentation are copyright 1996-2019 Glyph & + The pdffonts software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO @@ -115,4 +115,4 @@ SEE ALSO - 25 Sep 2019 pdffonts(1) + 28 Jan 2021 pdffonts(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.1 b/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.1 index bef93eeaba8..2d95ac79b66 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.1 @@ -1,8 +1,8 @@ -.\" Copyright 1998-2019 Glyph & Cog, LLC -.TH pdfimages 1 "25 Sep 2019" +.\" Copyright 1998-2021 Glyph & Cog, LLC +.TH pdfimages 1 "28 Jan 2021" .SH NAME pdfimages \- Portable Document Format (PDF) image extractor -(version 4.02) +(version 4.03) .SH SYNOPSIS .B pdfimages [options] @@ -102,7 +102,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdfimages software and documentation are copyright 1998-2019 Glyph +The pdfimages software and documentation are copyright 1998-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.cat b/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.cat index 74b7ace12d0..97a0371573b 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.cat @@ -4,7 +4,7 @@ pdfimages(1) General Commands Manual pdfimages(1) NAME pdfimages - Portable Document Format (PDF) image extractor (version - 4.02) + 4.03) SYNOPSIS pdfimages [options] PDF-file image-root @@ -85,7 +85,7 @@ EXIT CODES 99 Other error. AUTHOR - The pdfimages software and documentation are copyright 1998-2019 Glyph + The pdfimages software and documentation are copyright 1998-2021 Glyph & Cog, LLC. SEE ALSO @@ -95,4 +95,4 @@ SEE ALSO - 25 Sep 2019 pdfimages(1) + 28 Jan 2021 pdfimages(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.1 b/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.1 index c095e88bbfa..f84badc9814 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.1 @@ -1,8 +1,8 @@ -.\" Copyright 1999-2019 Glyph & Cog, LLC -.TH pdfinfo 1 "25 Sep 2019" +.\" Copyright 1999-2021 Glyph & Cog, LLC +.TH pdfinfo 1 "28 Jan 2021" .SH NAME pdfinfo \- Portable Document Format (PDF) document information -extractor (version 4.02) +extractor (version 4.03) .SH SYNOPSIS .B pdfinfo [options] @@ -150,7 +150,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdfinfo software and documentation are copyright 1996-2019 Glyph & +The pdfinfo software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.cat b/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.cat index 1f2ed6bd637..0478db677c4 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.cat @@ -4,7 +4,7 @@ pdfinfo(1) General Commands Manual pdfinfo(1) NAME pdfinfo - Portable Document Format (PDF) document information extractor - (version 4.02) + (version 4.03) SYNOPSIS pdfinfo [options] [PDF-file] @@ -104,7 +104,7 @@ EXIT CODES 99 Other error. AUTHOR - The pdfinfo software and documentation are copyright 1996-2019 Glyph & + The pdfinfo software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO @@ -114,4 +114,4 @@ SEE ALSO - 25 Sep 2019 pdfinfo(1) + 28 Jan 2021 pdfinfo(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.1 b/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.1 index f37bf5c4424..4618a7a77a9 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.1 @@ -1,8 +1,8 @@ -.\" Copyright 1997-2019 Glyph & Cog, LLC -.TH pdftohtml 1 "25 Sep 2019" +.\" Copyright 1997-2021 Glyph & Cog, LLC +.TH pdftohtml 1 "28 Jan 2021" .SH NAME pdftohtml \- Portable Document Format (PDF) to HTML converter -(version 4.02) +(version 4.03) .SH SYNOPSIS .B pdftohtml [options] @@ -50,6 +50,11 @@ zoom level is controlled by the \'-z' option. Specifying a larger \'-r' value will allow the viewer to zoom in farther without upscaling artifacts in the background. .TP +.B \-nofonts +Disable extraction of embedded fonts. By default, pdftohtml extracts +TrueType and OpenType fonts. Disabling extraction can work around +problems with buggy fonts. +.TP .B \-skipinvisible Don't draw invisible text. By default, invisible text (commonly used in OCR'ed PDF files) is drawn as transparent (alpha=0) HTML text. @@ -109,7 +114,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdftohtml software and documentation are copyright 1996-2019 Glyph +The pdftohtml software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.cat b/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.cat index 5f3a0bb8fad..3eea2e32ec1 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.cat @@ -4,7 +4,7 @@ pdftohtml(1) General Commands Manual pdftohtml(1) NAME pdftohtml - Portable Document Format (PDF) to HTML converter (version - 4.02) + 4.03) SYNOPSIS pdftohtml [options] PDF-file HTML-dir @@ -48,6 +48,11 @@ OPTIONS larger '-r' value will allow the viewer to zoom in farther with- out upscaling artifacts in the background. + -nofonts + Disable extraction of embedded fonts. By default, pdftohtml + extracts TrueType and OpenType fonts. Disabling extraction can + work around problems with buggy fonts. + -skipinvisible Don't draw invisible text. By default, invisible text (commonly used in OCR'ed PDF files) is drawn as transparent (alpha=0) HTML @@ -97,7 +102,7 @@ EXIT CODES 99 Other error. AUTHOR - The pdftohtml software and documentation are copyright 1996-2019 Glyph + The pdftohtml software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO @@ -107,4 +112,4 @@ SEE ALSO - 25 Sep 2019 pdftohtml(1) + 28 Jan 2021 pdftohtml(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.1 b/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.1 index c0745ced65b..bd8f517358f 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.1 @@ -1,8 +1,8 @@ -.\" Copyright 2017-2019 Glyph & Cog, LLC -.TH pdftopng 1 "25 Sep 2019" +.\" Copyright 2017-2021 Glyph & Cog, LLC +.TH pdftopng 1 "28 Jan 2021" .SH NAME pdftopng \- Portable Document Format (PDF) to Portable Network Graphics -(PNG) converter (version 4.02) +(PNG) converter (version 4.03) .SH SYNOPSIS .B pdftopng [options] @@ -56,6 +56,9 @@ Generate an alpha channel in the PNG file. This is only useful with PDF files that have been constructed with a transparent background. The \-alpha flag cannot be used with \-mono. .TP +.BI \-rot " angle" +Rotate pages by 0 (the default), 90, 180, or 270 degrees. +.TP .BI \-freetype " yes | no" Enable or disable FreeType (a TrueType / Type 1 font rasterizer). This defaults to "yes". @@ -107,7 +110,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdftopng software and documentation are copyright 1996-2019 Glyph +The pdftopng software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.cat b/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.cat index 0241fee5956..7e17c504b4b 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.cat @@ -4,7 +4,7 @@ pdftopng(1) General Commands Manual pdftopng(1) NAME pdftopng - Portable Document Format (PDF) to Portable Network Graphics - (PNG) converter (version 4.02) + (PNG) converter (version 4.03) SYNOPSIS pdftopng [options] PDF-file PNG-root @@ -48,6 +48,9 @@ OPTIONS with PDF files that have been constructed with a transparent background. The -alpha flag cannot be used with -mono. + -rot angle + Rotate pages by 0 (the default), 90, 180, or 270 degrees. + -freetype yes | no Enable or disable FreeType (a TrueType / Type 1 font raster- izer). This defaults to "yes". [config file: enableFreeType] @@ -87,7 +90,7 @@ EXIT CODES 99 Other error. AUTHOR - The pdftopng software and documentation are copyright 1996-2019 Glyph & + The pdftopng software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO @@ -97,4 +100,4 @@ SEE ALSO - 25 Sep 2019 pdftopng(1) + 28 Jan 2021 pdftopng(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.1 b/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.1 index 5ad6541d5bb..23b5b06980c 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.1 @@ -1,8 +1,8 @@ -.\" Copyright 2005-2019 Glyph & Cog, LLC -.TH pdftoppm 1 "25 Sep 2019" +.\" Copyright 2005-2021 Glyph & Cog, LLC +.TH pdftoppm 1 "28 Jan 2021" .SH NAME pdftoppm \- Portable Document Format (PDF) to Portable Pixmap (PPM) -converter (version 4.02) +converter (version 4.03) .SH SYNOPSIS .B pdftoppm [options] @@ -56,6 +56,9 @@ Generate a grayscale PGM file (instead of an RGB PPM file). .B \-cmyk Generate a CMYK PAM file (instead of an RGB PPM file). .TP +.BI \-rot " angle" +Rotate pages by 0 (the default), 90, 180, or 270 degrees. +.TP .BI \-freetype " yes | no" Enable or disable FreeType (a TrueType / Type 1 font rasterizer). This defaults to "yes". @@ -107,7 +110,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdftoppm software and documentation are copyright 1996-2019 Glyph +The pdftoppm software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.cat b/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.cat index 45e1bf1f255..9621be3c3b0 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.cat @@ -4,7 +4,7 @@ pdftoppm(1) General Commands Manual pdftoppm(1) NAME pdftoppm - Portable Document Format (PDF) to Portable Pixmap (PPM) con- - verter (version 4.02) + verter (version 4.03) SYNOPSIS pdftoppm [options] PDF-file PPM-root @@ -47,6 +47,9 @@ OPTIONS -cmyk Generate a CMYK PAM file (instead of an RGB PPM file). + -rot angle + Rotate pages by 0 (the default), 90, 180, or 270 degrees. + -freetype yes | no Enable or disable FreeType (a TrueType / Type 1 font raster- izer). This defaults to "yes". [config file: enableFreeType] @@ -86,7 +89,7 @@ EXIT CODES 99 Other error. AUTHOR - The pdftoppm software and documentation are copyright 1996-2019 Glyph & + The pdftoppm software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO @@ -96,4 +99,4 @@ SEE ALSO - 25 Sep 2019 pdftoppm(1) + 28 Jan 2021 pdftoppm(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftops.1 b/Build/source/libs/xpdf/xpdf-src/doc/pdftops.1 index 360e042121f..e23425d5bad 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftops.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftops.1 @@ -1,8 +1,8 @@ -.\" Copyright 1996-2019 Glyph & Cog, LLC -.TH pdftops 1 "25 Sep 2019" +.\" Copyright 1996-2021 Glyph & Cog, LLC +.TH pdftops 1 "28 Jan 2021" .SH NAME pdftops \- Portable Document Format (PDF) to PostScript converter -(version 4.02) +(version 4.03) .SH SYNOPSIS .B pdftops [options] @@ -51,7 +51,7 @@ Generate Level 1 PostScript. The resulting PostScript files will be significantly larger (if they contain images), but will print on Level 1 printers. This also converts all images to black and white. No more than one of the PostScript level options (\-level1, \-level1sep, -\-level2, \-level2sep, \-level3, \-level3Sep) may be given. +\-level2, \-level2sep, \-level3, \-level3sep) may be given. .RB "[config file: " psLevel ] .TP .B \-level1sep @@ -86,9 +86,9 @@ Generate grayscale Level 3 PostScript. All colors, including images, are converted to grayscale. .RB "[config file: " psLevel ] .TP -.B \-level3Sep +.B \-level3sep Generate Level 3 separable PostScript. The separation handling is the -same as for \-level2Sep. +same as for \-level2sep. .RB "[config file: " psLevel ] .TP .B \-eps @@ -236,7 +236,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdftops software and documentation are copyright 1996-2019 Glyph & +The pdftops software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftops.cat b/Build/source/libs/xpdf/xpdf-src/doc/pdftops.cat index 5b2d9d3e0bb..c985d686fea 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftops.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftops.cat @@ -4,7 +4,7 @@ pdftops(1) General Commands Manual pdftops(1) NAME pdftops - Portable Document Format (PDF) to PostScript converter (ver- - sion 4.02) + sion 4.03) SYNOPSIS pdftops [options] [PDF-file [PS-file]] @@ -42,7 +42,7 @@ OPTIONS print on Level 1 printers. This also converts all images to black and white. No more than one of the PostScript level options (-level1, -level1sep, -level2, -level2sep, -level3, - -level3Sep) may be given. [config file: psLevel] + -level3sep) may be given. [config file: psLevel] -level1sep Generate Level 1 separable PostScript. All colors are converted @@ -72,9 +72,9 @@ OPTIONS Generate grayscale Level 3 PostScript. All colors, including images, are converted to grayscale. [config file: psLevel] - -level3Sep + -level3sep Generate Level 3 separable PostScript. The separation handling - is the same as for -level2Sep. [config file: psLevel] + is the same as for -level2sep. [config file: psLevel] -eps Generate an Encapsulated PostScript (EPS) file. An EPS file contains a single image, so if you use this option with a multi- @@ -201,7 +201,7 @@ EXIT CODES 99 Other error. AUTHOR - The pdftops software and documentation are copyright 1996-2019 Glyph & + The pdftops software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO @@ -211,4 +211,4 @@ SEE ALSO - 25 Sep 2019 pdftops(1) + 28 Jan 2021 pdftops(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.1 b/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.1 index d0190491ac6..d1e81d01f25 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.1 @@ -1,8 +1,8 @@ -.\" Copyright 1997-2019 Glyph & Cog, LLC -.TH pdftotext 1 "25 Sep 2019" +.\" Copyright 1997-2021 Glyph & Cog, LLC +.TH pdftotext 1 "28 Jan 2021" .SH NAME pdftotext \- Portable Document Format (PDF) to text converter -(version 4.02) +(version 4.03) .SH SYNOPSIS .B pdftotext [options] @@ -59,6 +59,12 @@ but optimized for simple one-column pages. This mode will do a better job of maintaining horizontal spacing, but it will only work properly with a single column of text. .TP +.B \-simple2 +Similar to +.BR \-simple , +but handles slightly rotated text (e.g., OCR output) better. Only works +for pages with a single column of text. +.TP .B \-table Table mode is similar to physical layout mode, but optimized for tabular data, with the goal of keeping rows and columns aligned (at @@ -161,11 +167,14 @@ Read .I config-file in place of ~/.xpdfrc or the system-wide config file. .TP +.B \-listencodings +List all available text output encodings, then exit. +.TP .B \-v -Print copyright and version information. +Print copyright and version information, then exit. .TP .B \-h -Print usage information. +Print usage information, then exit. .RB ( \-help and .B \-\-help @@ -192,7 +201,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdftotext software and documentation are copyright 1996-2019 Glyph +The pdftotext software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.cat b/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.cat index ec51212f332..d4a001d7c63 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.cat @@ -4,7 +4,7 @@ pdftotext(1) General Commands Manual pdftotext(1) NAME pdftotext - Portable Document Format (PDF) to text converter (version - 4.02) + 4.03) SYNOPSIS pdftotext [options] [PDF-file [text-file]] @@ -47,6 +47,11 @@ OPTIONS ing, but it will only work properly with a single column of text. + -simple2 + Similar to -simple, but handles slightly rotated text (e.g., OCR + output) better. Only works for pages with a single column of + text. + -table Table mode is similar to physical layout mode, but optimized for tabular data, with the goal of keeping rows and columns aligned (at the expense of inserting extra whitespace). If the -fixed @@ -135,12 +140,16 @@ OPTIONS Read config-file in place of ~/.xpdfrc or the system-wide config file. - -v Print copyright and version information. + -listencodings + List all available text output encodings, then exit. + + -v Print copyright and version information, then exit. - -h Print usage information. (-help and --help are equivalent.) + -h Print usage information, then exit. (-help and --help are + equivalent.) BUGS - Some PDF files contain fonts whose encodings have been mangled beyond + Some PDF files contain fonts whose encodings have been mangled beyond recognition. There is no way (short of OCR) to extract text from these files. @@ -158,7 +167,7 @@ EXIT CODES 99 Other error. AUTHOR - The pdftotext software and documentation are copyright 1996-2019 Glyph + The pdftotext software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO @@ -168,4 +177,4 @@ SEE ALSO - 25 Sep 2019 pdftotext(1) + 28 Jan 2021 pdftotext(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/xpdf.1 b/Build/source/libs/xpdf/xpdf-src/doc/xpdf.1 index c1aedbd80cc..7cf06315f88 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/xpdf.1 +++ b/Build/source/libs/xpdf/xpdf-src/doc/xpdf.1 @@ -1,7 +1,7 @@ -.\" Copyright 1996-2019 Glyph & Cog, LLC -.TH xpdf 1 "25 Sep 2019" +.\" Copyright 1996-2021 Glyph & Cog, LLC +.TH xpdf 1 "28 Jan 2021" .SH NAME -xpdf \- Portable Document Format (PDF) file viewer (version 4.02) +xpdf \- Portable Document Format (PDF) file viewer (version 4.03) .SH SYNOPSIS .B xpdf [options] @@ -118,6 +118,9 @@ to the window size, or \'width', to fit the page width to the window width. .RB "[config file: " initialZoom ] .TP +.BI \-rot " angle" +Rotate pages by 0 (the default), 90, 180, or 270 degrees. +.TP .BI \-aa " yes | no" Enable or disable font anti-aliasing. This defaults to "yes". .RB "[config file: " antialias ] @@ -152,6 +155,10 @@ Set the X display (only available with X11). Print commands as they're executed (useful for debugging). .RB "[config file: " printCommands ] .TP +.BI \-tabstate " tabstate-file" +Sets the file used by the loadTabState and saveTabState commands. +.RB "[config file: " tabStateFile ] +.TP .BI \-cfg " config-file" Read .I config-file @@ -240,10 +247,18 @@ a 'save' button for each attached file. Not all PDF files have attachments. .PP .SS Text selection -Dragging the mouse with the left button held down will highlight an -arbitrary rectangle. Selected text can be copied to the clipboard -(with the edit/copy menu item). On X11, selected text will be -available in the X selection buffer. +In block selection mode, dragging the mouse with the left button held +down will highlight an arbitrary rectangle. Shift-clicking will +extend the selection. +.PP +In linear selection mode, dragging with the left button will highlight +text in reading order. Double-clicking or triple-clicking will select +a word or a line, respectively. Shift-clicking will extend the +selection. +.PP +Selected text can be copied to the clipboard (with the edit/copy menu +item). On X11, selected text will be available in the X selection +buffer. .PP .SS Links When the mouse is over a hyperlink, the link target will be shown in a @@ -400,6 +415,12 @@ and unbind commands in the config file (see The bind command allows you to bind a key or mouse button to a sequence of one or more commands. .PP +In commands that take arguments (inside parentheses), special +characters (namely '(', ')', ',', and '\\x01') can be escaped by +preceding them with a '\\x01' character. This is mostly useful in +things like scripts that need to be able to open arbitrary PDF files, +using the \'openFile' command. +.PP The following commands are supported: .TP .B about @@ -452,6 +473,9 @@ Switch to continuous view mode. .B copy Copy selected text to the clipboard. .TP +.B copyLinkTarget +Copy the target of the link under the mouse cursor to the clipboard. +.TP .B endPan End a pan operation. .TP @@ -626,6 +650,9 @@ Open the \'print' dialog. .B quit Quit from xpdf. .TP +.B raise +Bring the xpdf window to the front. +.TP .B reload Reload the current PDF file. .TP @@ -751,9 +778,18 @@ Scroll up by .I n pixels, moving to the previous page if appropriate. .TP +.B selectLine +Set the selection to the line at the current mouse position. +.TP +.B selectWord +Set the selection to the word at the current mouse position. +.TP .BI setSelection( pg , ulx , uly , lrx , lry ) Set the selection to the specified coordinates on the specified page. .TP +.B showKeyBindings +Open the key bindings dialog. +.TP .B showToolbar Show the toolbar. .TP @@ -772,6 +808,10 @@ Switch to side-by-side two-page view mode. .B singlePageMode Switch to single-page view mode. .TP +.B startExtendedSelection +Extend the selection to the current mouse position, and continue +extending as the mouse moves. +.TP .B startPan Start a pan operation at the current mouse position, which will scroll the document as the mouse moves. @@ -868,7 +908,7 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The xpdf software and documentation are copyright 1996-2019 Glyph & +The xpdf software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR pdftops (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/xpdf.cat b/Build/source/libs/xpdf/xpdf-src/doc/xpdf.cat index 30c841c45ad..3b71667f055 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/xpdf.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/xpdf.cat @@ -3,7 +3,7 @@ xpdf(1) General Commands Manual xpdf(1) NAME - xpdf - Portable Document Format (PDF) file viewer (version 4.02) + xpdf - Portable Document Format (PDF) file viewer (version 4.03) SYNOPSIS xpdf [options] [PDF-file [:page | +dest]] ... @@ -98,6 +98,9 @@ OPTIONS fit the page to the window size, or 'width', to fit the page width to the window width. [config file: initialZoom] + -rot angle + Rotate pages by 0 (the default), 90, 180, or 270 degrees. + -aa yes | no Enable or disable font anti-aliasing. This defaults to "yes". [config file: antialias] @@ -130,6 +133,10 @@ OPTIONS -cmd Print commands as they're executed (useful for debugging). [config file: printCommands] + -tabstate tabstate-file + Sets the file used by the loadTabState and saveTabState com- + mands. [config file: tabStateFile] + -cfg config-file Read config-file in place of ~/.xpdfrc or the system-wide config file. @@ -144,18 +151,18 @@ CONTROLS Toggles (i.e., shows or hides) the sidebar. status indicator - This icon is animated while Xpdf is rendering a page. It turns - red when an error or warning has been issued. Clicking on it + This icon is animated while Xpdf is rendering a page. It turns + red when an error or warning has been issued. Clicking on it opens the error dialog. selection mode - This icon is an "I-beam" in linear selection mode, and an arrow + This icon is an "I-beam" in linear selection mode, and an arrow in block selection mode. Clicking on it toggles between the two selection modes. page number entry box - Move to a specific page number. Click in the box to activate - it, type the page number, then hit return. This will instead + Move to a specific page number. Click in the box to activate + it, type the page number, then hit return. This will instead display and accept page labels, if the "view - page labels" menu item is checked. @@ -166,18 +173,18 @@ CONTROLS Zoom out or in (i.e., change magnification) incrementally. zoom popup menu - Change the zoom factor (see the description of the -z option + Change the zoom factor (see the description of the -z option above). fit width button - Change the zoom factor to fit the page width to the window + Change the zoom factor to fit the page width to the window width. fit page button Change the zoom factor to fit the page to the window size. find entry box - Find a text string. Click in the box to activate it, type a + Find a text string. Click in the box to activate it, type a search string, then hit return. find next button @@ -191,7 +198,7 @@ CONTROLS whole words (on/off). Menu bar - The menu bar is above the toolbar. The menu items should be self- + The menu bar is above the toolbar. The menu items should be self- explanatory. Tab list @@ -199,33 +206,41 @@ CONTROLS tabs. Outline/layers/attachments pane - This pane is on the left, below the tab list. The popup allows you to + This pane is on the left, below the tab list. The popup allows you to select from outline, layers, or attachments. - The outline is a tree-like structure of bookmarks that allows moving + The outline is a tree-like structure of bookmarks that allows moving within the PDF file. Not all PDF files have outlines. - Layers (a.k.a. optional content) allow parts of the PDF content to be + Layers (a.k.a. optional content) allow parts of the PDF content to be shown or hidden. Not all PDF files have layers. - Attachments are other files embedded within the PDF file. There is a - 'save' button for each attached file. Not all PDF files have attach- + Attachments are other files embedded within the PDF file. There is a + 'save' button for each attached file. Not all PDF files have attach- ments. Text selection - Dragging the mouse with the left button held down will highlight an - arbitrary rectangle. Selected text can be copied to the clipboard - (with the edit/copy menu item). On X11, selected text will be avail- - able in the X selection buffer. + In block selection mode, dragging the mouse with the left button held + down will highlight an arbitrary rectangle. Shift-clicking will extend + the selection. + + In linear selection mode, dragging with the left button will highlight + text in reading order. Double-clicking or triple-clicking will select + a word or a line, respectively. Shift-clicking will extend the selec- + tion. + + Selected text can be copied to the clipboard (with the edit/copy menu + item). On X11, selected text will be available in the X selection buf- + fer. Links - When the mouse is over a hyperlink, the link target will be shown in a + When the mouse is over a hyperlink, the link target will be shown in a popup near the bottom of the window. Clicking on a hyperlink will jump to the link's destination. A link to - another PDF document will make xpdf load that document. A 'launch' - link to an executable program will display a dialog, and if you click - 'ok', execute the program. URL links are opened in a system-dependent + another PDF document will make xpdf load that document. A 'launch' + link to an executable program will display a dialog, and if you click + 'ok', execute the program. URL links are opened in a system-dependent way. (On UNIX, Qt uses the $BROWSER environment variable.) Mouse bindings @@ -235,19 +250,19 @@ CONTROLS Dragging the mouse with the middle button held down pans the window. - The right mouse button opens a popup menu (see popupMenuCmd in + The right mouse button opens a popup menu (see popupMenuCmd in xpdfrc(5)). Key bindings - This section lists the default key bindings. Bindings can be changed + This section lists the default key bindings. Bindings can be changed using the config file (see xpdfrc(5)). control-o Open a new PDF file via a file requester. control-r - Reload the current PDF file. Note that Xpdf will reload the - file automatically (on a page change or redraw) if it has + Reload the current PDF file. Note that Xpdf will reload the + file automatically (on a page change or redraw) if it has changed since it was last loaded. control-f @@ -281,8 +296,8 @@ CONTROLS Open a new window. control-w - Close the current tab. Closes the window if this was the last - open tab. Quits the application if this was the last open win- + Close the current tab. Closes the window if this was the last + open tab. Quits the application if this was the last open win- dow. control-l @@ -317,11 +332,11 @@ CONTROLS Go to the last page. or - Scroll down on the current page; if already at bottom, move to + Scroll down on the current page; if already at bottom, move to next page. control- or control- - Go to the next page. If is active, this maintains + Go to the next page. If is active, this maintains the relative position on the page. @@ -329,7 +344,7 @@ CONTROLS ous page. control- or control- - Go to the previous page. If is active, this main- + Go to the previous page. If is active, this main- tains the relative position on the page. Exit full-screen mode. @@ -343,22 +358,28 @@ CONTROLS w Set the zoom factor to 'width' (fit page width to window). Full-screen mode - Xpdf can be placed into full-screen mode via the -fullscreen command - line option, the 'full screen' menu item, or a binding to the + Xpdf can be placed into full-screen mode via the -fullscreen command + line option, the 'full screen' menu item, or a binding to the fullScreenMode or toggleFullScreenMode command. - Entering full-screen mode automatically switches to single-page view + Entering full-screen mode automatically switches to single-page view mode and to the fit-page zoom factor. - Full-screen mode can be exited via the default key binding, or + Full-screen mode can be exited via the default key binding, or via a binding to the windowMode or toggleFullScreenModecommand. COMMANDS Xpdf's key and mouse bindings are user-configurable, using the bind and - unbind commands in the config file (see xpdfrc(5)). The bind command - allows you to bind a key or mouse button to a sequence of one or more + unbind commands in the config file (see xpdfrc(5)). The bind command + allows you to bind a key or mouse button to a sequence of one or more commands. + In commands that take arguments (inside parentheses), special charac- + ters (namely '(', ')', ',', and '\x01') can be escaped by preceding + them with a '\x01' character. This is mostly useful in things like + scripts that need to be able to open arbitrary PDF files, using the + 'openFile' command. + The following commands are supported: about Open the 'about' dialog. @@ -372,11 +393,11 @@ COMMANDS Check that file is open in the current tab, and open it if not. checkOpenFileAtDest(file,dest) - Check that file is open in the current tab, and open it if not. + Check that file is open in the current tab, and open it if not. In either case go to the specified named destination. checkOpenFileAtPage(file,page) - Check that file is open in the current tab, and open it if not. + Check that file is open in the current tab, and open it if not. In either case go to the specified page. closeSidebar @@ -403,6 +424,10 @@ COMMANDS copy Copy selected text to the clipboard. + copyLinkTarget + Copy the target of the link under the mouse cursor to the clip- + board. + endPan End a pan operation. endSelection @@ -571,6 +596,8 @@ COMMANDS quit Quit from xpdf. + raise Bring the xpdf window to the front. + reload Reload the current PDF file. rotateCCW @@ -674,10 +701,19 @@ COMMANDS Scroll up by n pixels, moving to the previous page if appropri- ate. + selectLine + Set the selection to the line at the current mouse position. + + selectWord + Set the selection to the word at the current mouse position. + setSelection(pg,ulx,uly,lrx,lry) Set the selection to the specified coordinates on the specified page. + showKeyBindings + Open the key bindings dialog. + showToolbar Show the toolbar. @@ -694,12 +730,16 @@ COMMANDS singlePageMode Switch to single-page view mode. + startExtendedSelection + Extend the selection to the current mouse position, and continue + extending as the mouse moves. + startPan - Start a pan operation at the current mouse position, which will + Start a pan operation at the current mouse position, which will scroll the document as the mouse moves. startSelection - Start a selection at the current mouse position, which will be + Start a selection at the current mouse position, which will be extended as the mouse moves. toggleContinuousMode @@ -715,19 +755,19 @@ COMMANDS Toggle the sidebar between open and closed. toggleSidebarMoveResizeWin - Toggle the sidebar between open and closed, resizing the window - so that the document size doesn't change, and moving the window + Toggle the sidebar between open and closed, resizing the window + so that the document size doesn't change, and moving the window so that the document stays in the same place on the screen. toggleSidebarResizeWin - Toggle the sidebar between open and closed, resizing the window + Toggle the sidebar between open and closed, resizing the window so that the document size doesn't change. toggleToolbar Toggle the toolbar between shown and hidden. viewPageLabels - Show page labels (if the PDF file has them), rather than page + Show page labels (if the PDF file has them), rather than page numbers. viewPageNumbers @@ -754,19 +794,19 @@ COMMANDS Zoom to the current selection. REMOTE SERVER MODE - Starting xpdf with the "-remote" switch puts it into remote server - mode. All remaining command line options are commands (see the COM- + Starting xpdf with the "-remote" switch puts it into remote server + mode. All remaining command line options are commands (see the COM- MANDS section). Subsequent invocations of "xpdf -remote" with the same - remote server name will send commands to the already-running instance - of xpdf. The "checkOpenFile" commands are useful here for things like + remote server name will send commands to the already-running instance + of xpdf. The "checkOpenFile" commands are useful here for things like changing pages. For example: # Start up xpdf, and open something.pdf. xpdf -remote foo 'openFile(something.pdf)' - # Switch to page 7 in the already-open something.pdf. If + # Switch to page 7 in the already-open something.pdf. If the user - # has closed xpdf in the meantime, this will restart it and + # has closed xpdf in the meantime, this will restart it and reopen # the file. xpdf -remote foo 'checkOpenFileAtPage(something.pdf, 7)' @@ -785,7 +825,7 @@ EXIT CODES 99 Other error. AUTHOR - The xpdf software and documentation are copyright 1996-2019 Glyph & + The xpdf software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO @@ -795,4 +835,4 @@ SEE ALSO - 25 Sep 2019 xpdf(1) + 28 Jan 2021 xpdf(1) diff --git a/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.5 b/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.5 index bde3b5a5973..07639316ec7 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.5 +++ b/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.5 @@ -1,7 +1,7 @@ -.\" Copyright 2002-2019 Glyph & Cog, LLC -.TH xpdfrc 5 "25 Sep 2019" +.\" Copyright 2002-2021 Glyph & Cog, LLC +.TH xpdfrc 5 "28 Jan 2021" .SH NAME -xpdfrc \- configuration file for Xpdf tools (version 4.02) +xpdfrc \- configuration file for Xpdf tools (version 4.03) .SH DESCRIPTION All of the Xpdf tools read a single configuration file. If you have a .I .xpdfrc @@ -17,8 +17,25 @@ The xpdfrc file consists of a series of configuration options, one per line. Blank lines and lines starting with a \'#' (comments) are ignored. .PP -Arguments may be quoted, using "double-quote" characters, e.g., for -file names that contain spaces. +Arguments can be single-quoted or double-quoted, e.g., for file names +that contain spaces ("aaa bbb", 'aaa bbb'). This quoting does not +provide any escaping, so there's no way to include a double quote in a +double-quoted argument or a single quote in a single-quoted argument. +.PP + +Arguments can also be at-quoted: @"aaa bbb". At-quoted strings allow +use of the DATADIR variable, which is set to the 'data' subdirectory +in the xpdf install directory. The percent sign (%) is an escape +character: a percent sign followed by any other character is replaced +with that character. +.PP +.RS +.nf +@"abc %"def%" ghi" --> abc "def" ghi +@"${DATADIR}/foo" --> ...install-dir.../data/foo +@"%${DATADIR}/foo" --> ${DATADIR}/foo +.fi +.RE .PP The following sections list all of the configuration options, sorted into functional groups. There is an examples section at the end. @@ -549,6 +566,11 @@ If set to "yes", xpdf opens with the sidebar (tabs, outline, etc.) visible. If set to "no", xpdf opens with the sidebar collapsed. The default is "yes". .TP +.BI initialSidebarWidth " width" +Sets the initial sidebar width, in pixels. This is only relevant if +initialSidebarState is "yes". The default value is zero, which tells +xpdf to use an internal default size. +.TP .BR initialSelectMode " block | linear" Sets the initial selection mode. The default setting is "linear". .TP @@ -567,6 +589,10 @@ color. Set the matte color for full-screen mode. The color can be #RRGGBB (hexadecimal) or a named color. .TP +.BI selectionColor " color" +Set the selection color. The color can be #RRGGBB (hexadecimal) or a +named color. +.TP .BI reverseVideoInvertImages " yes | no" If set to "no", xpdf's reverse-video mode inverts text and vector graphic content, but not images. If set to "yes", xpdf inverts images @@ -641,11 +667,13 @@ can be a regular ASCII character, or any one of: end pgup pgdn - left / right / up / down (arrow keys) - f1 .. f35 (function keys) - mousePress1 .. mousePress7 (mouse buttons) - mouseRelease1 .. mouseRelease7 (mouse buttons) - mouseClick1 .. mouseClick7 (mouse buttons) + left / right / up / down (arrow keys) + f1 .. f35 (function keys) + mousePress1 .. mousePress7 (mouse buttons) + mouseRelease1 .. mouseRelease7 (mouse buttons) + mouseClick1 .. mouseClick7 (mouse buttons) + mouseDoubleClick1 .. mouseDoubleClick7 (mouse buttons) + mouseTripleClick1 .. mouseTripleClick7 (mouse buttons) .fi .I Context @@ -708,11 +736,6 @@ value is "yes". If set to "no", form fields will not be drawn or printed. The default value is "yes". .TP -.BI enableXFA " yes | no" -If set to "yes", an XFA form (if present) will be rendered in place of -an AcroForm. If "no", an XFA form will never be rendered. This -defaults to "yes". -.TP .BI printCommands " yes | no" If set to "yes", drawing commands are printed as they're executed (useful for debugging). This defaults to "no". @@ -783,7 +806,7 @@ Depending on build options, it may be placed elsewhere. This is the user's configuration file. If it exists, it will be read in place of the system-wide file. .SH AUTHOR -The Xpdf software and documentation are copyright 1996-2019 Glyph & +The Xpdf software and documentation are copyright 1996-2021 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), diff --git a/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.cat b/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.cat index f2412bd3420..c43e8005e2b 100644 --- a/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.cat +++ b/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.cat @@ -3,7 +3,7 @@ xpdfrc(5) File Formats Manual xpdfrc(5) NAME - xpdfrc - configuration file for Xpdf tools (version 4.02) + xpdfrc - configuration file for Xpdf tools (version 4.03) DESCRIPTION All of the Xpdf tools read a single configuration file. If you have a @@ -17,8 +17,20 @@ DESCRIPTION line. Blank lines and lines starting with a '#' (comments) are ignored. - Arguments may be quoted, using "double-quote" characters, e.g., for - file names that contain spaces. + Arguments can be single-quoted or double-quoted, e.g., for file names + that contain spaces ("aaa bbb", 'aaa bbb'). This quoting does not pro- + vide any escaping, so there's no way to include a double quote in a + double-quoted argument or a single quote in a single-quoted argument. + + Arguments can also be at-quoted: @"aaa bbb". At-quoted strings allow + use of the DATADIR variable, which is set to the 'data' subdirectory in + the xpdf install directory. The percent sign (%) is an escape charac- + ter: a percent sign followed by any other character is replaced with + that character. + + @"abc %"def%" ghi" --> abc "def" ghi + @"${DATADIR}/foo" --> ...install-dir.../data/foo + @"%${DATADIR}/foo" --> ${DATADIR}/foo The following sections list all of the configuration options, sorted into functional groups. There is an examples section at the end. @@ -507,6 +519,11 @@ VIEWER SETTINGS etc.) visible. If set to "no", xpdf opens with the sidebar collapsed. The default is "yes". + initialSidebarWidth width + Sets the initial sidebar width, in pixels. This is only rele- + vant if initialSidebarState is "yes". The default value is + zero, which tells xpdf to use an internal default size. + initialSelectMode block | linear Sets the initial selection mode. The default setting is "lin- ear". @@ -526,15 +543,19 @@ VIEWER SETTINGS Set the matte color for full-screen mode. The color can be #RRGGBB (hexadecimal) or a named color. + selectionColor color + Set the selection color. The color can be #RRGGBB (hexadecimal) + or a named color. + reverseVideoInvertImages yes | no - If set to "no", xpdf's reverse-video mode inverts text and vec- - tor graphic content, but not images. If set to "yes", xpdf + If set to "no", xpdf's reverse-video mode inverts text and vec- + tor graphic content, but not images. If set to "yes", xpdf inverts images as well. The default is "no". popupMenuCmd title command ... - Add a command to the popup menu. Title is the text to be dis- - played in the menu. Command is an Xpdf command (see the COM- - MANDS section of the xpdf(1) man page for details). Multiple + Add a command to the popup menu. Title is the text to be dis- + played in the menu. Command is an Xpdf command (see the COM- + MANDS section of the xpdf(1) man page for details). Multiple commands are separated by whitespace. maxTileWidth pixels @@ -542,11 +563,11 @@ VIEWER SETTINGS ing pages. This defaults to 1500. maxTileHeight pixels - Set the maximum height of tiles to be used by xpdf when raster- + Set the maximum height of tiles to be used by xpdf when raster- izing pages. This defaults to 1500. tileCacheSize tiles - Set the maximum number of tiles to be cached by xpdf when ras- + Set the maximum number of tiles to be cached by xpdf when ras- terizing pages. This defaults to 10. workerThreads numThreads @@ -554,16 +575,16 @@ VIEWER SETTINGS izing pages. This defaults to 1. launchCommand command - Sets the command executed when you click on a "launch"-type - link. The intent is for the command to be a program/script - which determines the file type and runs the appropriate viewer. - The command line will consist of the file to be launched, fol- - lowed by any parameters specified with the link. Do not use - "%s" in "command". By default, this is unset, and Xpdf will + Sets the command executed when you click on a "launch"-type + link. The intent is for the command to be a program/script + which determines the file type and runs the appropriate viewer. + The command line will consist of the file to be launched, fol- + lowed by any parameters specified with the link. Do not use + "%s" in "command". By default, this is unset, and Xpdf will simply try to execute the file (after prompting the user). movieCommand command - Sets the command executed when you click on a movie annotation. + Sets the command executed when you click on a movie annotation. The string "%s" will be replaced with the movie file name. This has no default value. @@ -571,7 +592,7 @@ VIEWER SETTINGS Sets the default printer used in the viewer's print dialog. bind modifiers-key context command ... - Add a key or mouse button binding. Modifiers can be zero or + Add a key or mouse button binding. Modifiers can be zero or more of: shift- @@ -592,11 +613,13 @@ VIEWER SETTINGS end pgup pgdn - left / right / up / down (arrow keys) - f1 .. f35 (function keys) - mousePress1 .. mousePress7 (mouse buttons) - mouseRelease1 .. mouseRelease7 (mouse buttons) - mouseClick1 .. mouseClick7 (mouse buttons) + left / right / up / down (arrow keys) + f1 .. f35 (function keys) + mousePress1 .. mousePress7 (mouse buttons) + mouseRelease1 .. mouseRelease7 (mouse buttons) + mouseClick1 .. mouseClick7 (mouse buttons) + mouseDoubleClick1 .. mouseDoubleClick7 (mouse buttons) + mouseTripleClick1 .. mouseTripleClick7 (mouse buttons) Context is either "any" or a comma-separated combination of: @@ -605,14 +628,14 @@ VIEWER SETTINGS overLink / offLink (mouse over link or not) scrLockOn / scrLockOff (scroll lock on/off) - The context string can include only one of each pair in the + The context string can include only one of each pair in the above list. - Command is an Xpdf command (see the COMMANDS section of the - xpdf(1) man page for details). Multiple commands are separated + Command is an Xpdf command (see the COMMANDS section of the + xpdf(1) man page for details). Multiple commands are separated by whitespace. - The bind command replaces any existing binding, but only if it + The bind command replaces any existing binding, but only if it was defined for the exact same modifiers, key, and context. All tokens (modifiers, key, context, commands) are case-sensitive. @@ -630,10 +653,10 @@ VIEWER SETTINGS See the xpdf(1) man page for more examples. unbind modifiers-key context - Removes a key binding established with the bind command. This - is most useful to remove default key bindings before establish- - ing new ones (e.g., if the default key binding is given for - "any" context, and you want to create new key bindings for mul- + Removes a key binding established with the bind command. This + is most useful to remove default key bindings before establish- + ing new ones (e.g., if the default key binding is given for + "any" context, and you want to create new key bindings for mul- tiple contexts). tabStateFile path @@ -642,24 +665,19 @@ VIEWER SETTINGS MISCELLANEOUS SETTINGS drawAnnotations yes | no - If set to "no", annotations will not be drawn or printed. The + If set to "no", annotations will not be drawn or printed. The default value is "yes". drawFormFields yes | no - If set to "no", form fields will not be drawn or printed. The + If set to "no", form fields will not be drawn or printed. The default value is "yes". - enableXFA yes | no - If set to "yes", an XFA form (if present) will be rendered in - place of an AcroForm. If "no", an XFA form will never be ren- - dered. This defaults to "yes". - printCommands yes | no - If set to "yes", drawing commands are printed as they're exe- + If set to "yes", drawing commands are printed as they're exe- cuted (useful for debugging). This defaults to "no". errQuiet yes | no - If set to "yes", this suppresses all error and warning messages + If set to "yes", this suppresses all error and warning messages from all of the Xpdf tools. This defaults to "no". EXAMPLES @@ -716,7 +734,7 @@ EXAMPLES FILES /usr/local/etc/xpdfrc - This is the default location for the system-wide configuration + This is the default location for the system-wide configuration file. Depending on build options, it may be placed elsewhere. $HOME/.xpdfrc @@ -724,14 +742,14 @@ FILES read in place of the system-wide file. AUTHOR - The Xpdf software and documentation are copyright 1996-2019 Glyph & + The Xpdf software and documentation are copyright 1996-2021 Glyph & Cog, LLC. SEE ALSO - xpdf(1), pdftops(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdf- + xpdf(1), pdftops(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdf- fonts(1), pdfdetach(1), pdftoppm(1), pdftopng(1), pdfimages(1) http://www.xpdfreader.com/ - 25 Sep 2019 xpdfrc(5) + 28 Jan 2021 xpdfrc(5) diff --git a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiIdentifier.cc b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiIdentifier.cc index 333fe0763b3..4c6797ba9d6 100644 --- a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiIdentifier.cc +++ b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiIdentifier.cc @@ -561,7 +561,7 @@ static FoFiIdentifierType identifyOpenType(Reader *reader) { static FoFiIdentifierType identifyCFF(Reader *reader, int start) { Guint offset0, offset1; - int hdrSize, offSize0, offSize1, pos, endPos, b0, n, i; + int hdrSize, offSize0, offSize1, pos, endPos, b0, n; //----- read the header if (reader->getByte(start) != 0x01 || @@ -622,19 +622,18 @@ static FoFiIdentifierType identifyCFF(Reader *reader, int start) { //----- parse the top dict, look for ROS as first entry // for a CID font, the top dict starts with: // ROS - for (i = 0; i < 3; ++i) { - b0 = reader->getByte(pos++); + while (pos >= 0 && pos < endPos) { + b0 = reader->getByte(pos); if (b0 == 0x1c) { - pos += 2; + pos += 3; } else if (b0 == 0x1d) { - pos += 4; + pos += 5; } else if (b0 >= 0xf7 && b0 <= 0xfe) { + pos += 2; + } else if (b0 >= 0x20 && b0 <= 0xf6) { pos += 1; - } else if (b0 < 0x20 || b0 > 0xf6) { - return fofiIdCFF8Bit; - } - if (pos >= endPos || pos < 0) { - return fofiIdCFF8Bit; + } else { + break; } } if (pos + 1 < endPos && diff --git a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiTrueType.cc b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiTrueType.cc index 0f5b5bc7230..57b12b14996 100644 --- a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiTrueType.cc +++ b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiTrueType.cc @@ -1730,7 +1730,7 @@ void FoFiTrueType::cvtCharStrings(char **encoding, if (encoding) { name = encoding[i]; } else { - sprintf(buf2, "c%02x", i); + snprintf(buf2, sizeof(buf2), "c%02x", i); name = buf2; } if (name && strcmp(name, ".notdef")) { @@ -2143,7 +2143,7 @@ Guint FoFiTrueType::computeTableChecksum(Guchar *data, int length) { void FoFiTrueType::parse(int fontNum, GBool allowHeadlessCFF) { Guint topTag; - int offset, pos, ver, i, j; + int offset, pos, ver, i, j, k; parsedOk = gTrue; @@ -2206,7 +2206,7 @@ void FoFiTrueType::parse(int fontNum, GBool allowHeadlessCFF) { // check for the head table; allow for a head-less OpenType CFF font headlessCFF = gFalse; if (seekTable("head") < 0) { - if (openTypeCFF && allowHeadlessCFF) { + if (openTypeCFF && allowHeadlessCFF && seekTable("CFF ") >= 0) { headlessCFF = gTrue; nGlyphs = 0; bbox[0] = bbox[1] = bbox[2] = bbox[3] = 0; @@ -2238,14 +2238,22 @@ void FoFiTrueType::parse(int fontNum, GBool allowHeadlessCFF) { return; } cmaps = (TrueTypeCmap *)gmallocn(nCmaps, sizeof(TrueTypeCmap)); + k = 0; for (j = 0; j < nCmaps; ++j) { - cmaps[j].platform = getU16BE(pos, &parsedOk); - cmaps[j].encoding = getU16BE(pos + 2, &parsedOk); - cmaps[j].offset = tables[i].offset + getU32BE(pos + 4, &parsedOk); + cmaps[k].platform = getU16BE(pos, &parsedOk); + cmaps[k].encoding = getU16BE(pos + 2, &parsedOk); + cmaps[k].offset = getU32BE(pos + 4, &parsedOk); pos += 8; - cmaps[j].fmt = getU16BE(cmaps[j].offset, &parsedOk); - cmaps[j].len = getU16BE(cmaps[j].offset + 2, &parsedOk); + if (cmaps[k].offset >= tables[i].len) { + // skip any invalid subtables + continue; + } + cmaps[k].offset += tables[i].offset; + cmaps[k].fmt = getU16BE(cmaps[k].offset, &parsedOk); + cmaps[k].len = getU16BE(cmaps[k].offset + 2, &parsedOk); + ++k; } + nCmaps = k; if (!parsedOk) { return; } @@ -2273,7 +2281,7 @@ void FoFiTrueType::parse(int fontNum, GBool allowHeadlessCFF) { // NB: out-of-bounds entries are handled in writeTTF() if (!openTypeCFF) { i = seekTable("loca"); - if (tables[i].len < 0) { + if (tables[i].len < (locaFmt ? 4 : 2)) { parsedOk = gFalse; return; } diff --git a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1.cc b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1.cc index 0d067b5c5a9..83ec53ef5e8 100644 --- a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1.cc +++ b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1.cc @@ -114,7 +114,7 @@ void FoFiType1::writeEncoded(const char **newEncoding, "0 1 255 {1 index exch /.notdef put} for\n", 40); for (i = 0; i < 256; ++i) { if (newEncoding[i]) { - sprintf(buf, "dup %d /%s put\n", i, newEncoding[i]); + snprintf(buf, sizeof(buf), "dup %d /%s put\n", i, newEncoding[i]); (*outputFunc)(outputStream, buf, (int)strlen(buf)); } } diff --git a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.cc b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.cc index 13c045f8b97..b4f0a061be9 100644 --- a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.cc +++ b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.cc @@ -26,6 +26,8 @@ static char hexChars[17] = "0123456789ABCDEF"; +#define type1cSubrRecursionLimit 20 + //------------------------------------------------------------------------ GBool Type1COp::isZero() { @@ -39,28 +41,52 @@ GBool Type1COp::isZero() { GBool Type1COp::isNegative() { switch (kind) { - case type1COpInteger: return intgr < 0; - case type1COpFloat: return flt < 0; - case type1COpRational: return (rat.num < 0) != (rat.den < 0); - default: return gFalse; // shouldn't happen + case type1COpInteger: + return intgr < 0; + case type1COpFloat: + return flt < 0; + case type1COpRational: + return (rat.num < 0) != (rat.den < 0); + default: + // shouldn't happen + return gFalse; } } int Type1COp::toInt() { switch (kind) { - case type1COpInteger: return intgr; - case type1COpFloat: return (int)flt; - case type1COpRational: return rat.num / rat.den; - default: return 0; // shouldn't happen + case type1COpInteger: + return intgr; + case type1COpFloat: + if (flt < -2e9 || flt > 2e9) { + return 0; + } + return (int)flt; + case type1COpRational: + if (rat.den == 0) { + return 0; + } + return rat.num / rat.den; + default: + // shouldn't happen + return 0; } } double Type1COp::toFloat() { switch (kind) { - case type1COpInteger: return (double)intgr; - case type1COpFloat: return flt; - case type1COpRational: return (double)rat.num / (double)rat.den; - default: return 0.0; // shouldn't happen + case type1COpInteger: + return (double)intgr; + case type1COpFloat: + return flt; + case type1COpRational: + if (rat.den == 0) { + return 0; + } + return (double)rat.num / (double)rat.den; + default: + // shouldn't happen + return 0.0; } } @@ -607,7 +633,8 @@ void FoFiType1C::convertToCIDType0(char *psName, int *codeMap, int nCodes, subrIdx.pos = -1; } cvtGlyph(val.pos, val.len, charStrings, - &subrIdx, &privateDicts[fdSelect ? fdSelect[gid] : 0], gTrue); + &subrIdx, &privateDicts[fdSelect ? fdSelect[gid] : 0], + gTrue, 0); } } } @@ -969,6 +996,10 @@ void FoFiType1C::convertToType0(char *psName, int *codeMap, int nCodes, } } } + if (fd < 0 || fd >= nFDs) { + // this will only happen in a broken/damaged font + fd = 0; + } // font dictionary (unencrypted section) (*outputFunc)(outputStream, "16 dict begin\n", 14); @@ -1269,7 +1300,7 @@ void FoFiType1C::eexecCvtGlyph(Type1CEexecBuf *eb, const char *glyphName, // generate the charstring charBuf = new GString(); - cvtGlyph(offset, nBytes, charBuf, subrIdx, pDict, gTrue); + cvtGlyph(offset, nBytes, charBuf, subrIdx, pDict, gTrue, 0); buf = GString::format("/{0:s} {1:d} RD ", glyphName, charBuf->getLength()); eexecWrite(eb, buf->getCString()); @@ -1283,7 +1314,7 @@ void FoFiType1C::eexecCvtGlyph(Type1CEexecBuf *eb, const char *glyphName, void FoFiType1C::cvtGlyph(int offset, int nBytes, GString *charBuf, Type1CIndex *subrIdx, Type1CPrivateDict *pDict, - GBool top) { + GBool top, int recursion) { Type1CIndexVal val; Type1COp zero, tmp; GBool ok, dInt; @@ -1292,6 +1323,11 @@ void FoFiType1C::cvtGlyph(int offset, int nBytes, GString *charBuf, Guchar byte; int pos, subrBias, start, num, den, i, k; + if (recursion > type1cSubrRecursionLimit) { + //~ error(-1, "Recursive loop in Type1C glyph"); + return; + } + start = charBuf->getLength(); if (top) { charBuf->append((char)73); @@ -1504,7 +1540,8 @@ void FoFiType1C::cvtGlyph(int offset, int nBytes, GString *charBuf, ok = gTrue; getIndexVal(subrIdx, k, &val, &ok); if (ok) { - cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse); + cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse, + recursion + 1); } } else { //~ error(-1, "Too few args to Type 2 callsubr"); @@ -1739,7 +1776,8 @@ void FoFiType1C::cvtGlyph(int offset, int nBytes, GString *charBuf, ok = gTrue; getIndexVal(&gsubrIdx, k, &val, &ok); if (ok) { - cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse); + cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse, + recursion + 1); } } else { //~ error(-1, "Too few args to Type 2 callgsubr"); @@ -2945,11 +2983,10 @@ void FoFiType1C::readFDSelect() { int fdSelectFmt, pos, nRanges, gid0, gid1, fd, i, j; fdSelect = (Guchar *)gmalloc(nGlyphs); - if (topDict.fdSelectOffset == 0) { - for (i = 0; i < nGlyphs; ++i) { - fdSelect[i] = 0; - } - } else { + for (i = 0; i < nGlyphs; ++i) { + fdSelect[i] = 0; + } + if (topDict.fdSelectOffset != 0) { pos = topDict.fdSelectOffset; fdSelectFmt = getU8(pos++, &parsedOk); if (!parsedOk) { @@ -2992,9 +3029,6 @@ void FoFiType1C::readFDSelect() { } } else { //~ error(-1, "Unknown FDSelect table format in CID font"); - for (i = 0; i < nGlyphs; ++i) { - fdSelect[i] = 0; - } } } } diff --git a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.h b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.h index eb75dce330d..15a823cffe6 100644 --- a/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.h +++ b/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.h @@ -229,7 +229,7 @@ private: Type1CPrivateDict *pDict); void cvtGlyph(int offset, int nBytes, GString *charBuf, Type1CIndex *subrIdx, Type1CPrivateDict *pDict, - GBool top); + GBool top, int recursion); void cvtGlyphWidth(GBool useOp, GString *charBuf, Type1CPrivateDict *pDict); void cvtNum(Type1COp op, GString *charBuf); diff --git a/Build/source/libs/xpdf/xpdf-src/goo/CMakeLists.txt b/Build/source/libs/xpdf/xpdf-src/goo/CMakeLists.txt index 7302dbbe08c..0a1c56b0ebd 100644 --- a/Build/source/libs/xpdf/xpdf-src/goo/CMakeLists.txt +++ b/Build/source/libs/xpdf/xpdf-src/goo/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(goo_objs OBJECT gmem.cc gmempp.cc parseargs.c + Trace.cc ) add_library(goo diff --git a/Build/source/libs/xpdf/xpdf-src/goo/FixedPoint.h b/Build/source/libs/xpdf/xpdf-src/goo/FixedPoint.h index a0baa70b61f..9f0c3240627 100644 --- a/Build/source/libs/xpdf/xpdf-src/goo/FixedPoint.h +++ b/Build/source/libs/xpdf/xpdf-src/goo/FixedPoint.h @@ -36,6 +36,7 @@ public: FixedPoint(const FixedPoint &x) { val = x.val; } FixedPoint(double x) { val = (int)(x * (1 << fixptShift) + 0.5); } FixedPoint(int x) { val = x << fixptShift; } + FixedPoint(Guint x) { val = x << fixptShift; } FixedPoint(long x) { val = (int)x << fixptShift; } operator float() @@ -52,31 +53,37 @@ public: bool operator ==(FixedPoint x) const { return val == x.val; } bool operator ==(double x) const { return *this == (FixedPoint)x; } bool operator ==(int x) const { return *this == (FixedPoint)x; } + bool operator ==(Guint x) const { return *this == (FixedPoint)x; } bool operator ==(long x) const { return *this == (FixedPoint)x; } bool operator !=(FixedPoint x) const { return val != x.val; } bool operator !=(double x) const { return *this != (FixedPoint)x; } bool operator !=(int x) const { return *this != (FixedPoint)x; } + bool operator !=(Guint x) const { return *this != (FixedPoint)x; } bool operator !=(long x) const { return *this != (FixedPoint)x; } bool operator <(FixedPoint x) const { return val < x.val; } bool operator <(double x) const { return *this < (FixedPoint)x; } bool operator <(int x) const { return *this < (FixedPoint)x; } + bool operator <(Guint x) const { return *this < (FixedPoint)x; } bool operator <(long x) const { return *this < (FixedPoint)x; } bool operator <=(FixedPoint x) const { return val <= x.val; } bool operator <=(double x) const { return *this <= (FixedPoint)x; } bool operator <=(int x) const { return *this <= (FixedPoint)x; } + bool operator <=(Guint x) const { return *this <= (FixedPoint)x; } bool operator <=(long x) const { return *this <= (FixedPoint)x; } bool operator >(FixedPoint x) const { return val > x.val; } bool operator >(double x) const { return *this > (FixedPoint)x; } bool operator >(int x) const { return *this > (FixedPoint)x; } + bool operator >(Guint x) const { return *this > (FixedPoint)x; } bool operator >(long x) const { return *this > (FixedPoint)x; } bool operator >=(FixedPoint x) const { return val >= x.val; } bool operator >=(double x) const { return *this >= (FixedPoint)x; } bool operator >=(int x) const { return *this >= (FixedPoint)x; } + bool operator >=(Guint x) const { return *this >= (FixedPoint)x; } bool operator >=(long x) const { return *this >= (FixedPoint)x; } FixedPoint operator -() { return make(-val); } @@ -84,41 +91,49 @@ public: FixedPoint operator +(FixedPoint x) { return make(val + x.val); } FixedPoint operator +(double x) { return *this + (FixedPoint)x; } FixedPoint operator +(int x) { return *this + (FixedPoint)x; } + FixedPoint operator +(Guint x) { return *this + (FixedPoint)x; } FixedPoint operator +(long x) { return *this + (FixedPoint)x; } FixedPoint operator +=(FixedPoint x) { val = val + x.val; return *this; } FixedPoint operator +=(double x) { return *this += (FixedPoint)x; } FixedPoint operator +=(int x) { return *this += (FixedPoint)x; } + FixedPoint operator +=(Guint x) { return *this += (FixedPoint)x; } FixedPoint operator +=(long x) { return *this += (FixedPoint)x; } FixedPoint operator -(FixedPoint x) { return make(val - x.val); } FixedPoint operator -(double x) { return *this - (FixedPoint)x; } FixedPoint operator -(int x) { return *this - (FixedPoint)x; } + FixedPoint operator -(Guint x) { return *this - (FixedPoint)x; } FixedPoint operator -(long x) { return *this - (FixedPoint)x; } FixedPoint operator -=(FixedPoint x) { val = val - x.val; return *this; } FixedPoint operator -=(double x) { return *this -= (FixedPoint)x; } FixedPoint operator -=(int x) { return *this -= (FixedPoint)x; } + FixedPoint operator -=(Guint x) { return *this -= (FixedPoint)x; } FixedPoint operator -=(long x) { return *this -= (FixedPoint)x; } FixedPoint operator *(FixedPoint x) { return make(mul(val, x.val)); } FixedPoint operator *(double x) { return *this * (FixedPoint)x; } FixedPoint operator *(int x) { return *this * (FixedPoint)x; } + FixedPoint operator *(Guint x) { return *this * (FixedPoint)x; } FixedPoint operator *(long x) { return *this * (FixedPoint)x; } FixedPoint operator *=(FixedPoint x) { val = mul(val, x.val); return *this; } FixedPoint operator *=(double x) { return *this *= (FixedPoint)x; } FixedPoint operator *=(int x) { return *this *= (FixedPoint)x; } + FixedPoint operator *=(Guint x) { return *this *= (FixedPoint)x; } FixedPoint operator *=(long x) { return *this *= (FixedPoint)x; } FixedPoint operator /(FixedPoint x) { return make(div(val, x.val)); } FixedPoint operator /(double x) { return *this / (FixedPoint)x; } FixedPoint operator /(int x) { return *this / (FixedPoint)x; } + FixedPoint operator /(Guint x) { return *this / (FixedPoint)x; } FixedPoint operator /(long x) { return *this / (FixedPoint)x; } FixedPoint operator /=(FixedPoint x) { val = div(val, x.val); return *this; } FixedPoint operator /=(double x) { return *this /= (FixedPoint)x; } FixedPoint operator /=(int x) { return *this /= (FixedPoint)x; } + FixedPoint operator /=(Guint x) { return *this /= (FixedPoint)x; } FixedPoint operator /=(long x) { return *this /= (FixedPoint)x; } static FixedPoint abs(FixedPoint x) { return make(::abs(x.val)); } diff --git a/Build/source/libs/xpdf/xpdf-src/goo/Trace.cc b/Build/source/libs/xpdf/xpdf-src/goo/Trace.cc new file mode 100644 index 00000000000..4b730e5b97d --- /dev/null +++ b/Build/source/libs/xpdf/xpdf-src/goo/Trace.cc @@ -0,0 +1,114 @@ +//======================================================================== +// +// Trace.cc +// +// Nested tracing. +// +// Copyright 2020 Glyph & Cog, LLC +// +//======================================================================== + +#include + +#if ENABLE_TRACING + +#include +#include +#include +#include +#include +#include "GString.h" +#include "Trace.h" + +// NB: This module is NOT thread-safe. + +static bool traceInitialized = false; +static FILE *traceOut = NULL; + +static void traceInit() { + if (traceInitialized) { + return; + } + //~ this could read an env var to set up an output file + GString *fileName = GString::format("/tmp/trace.{0:d}", (int)getpid()); + traceOut = fopen(fileName->getCString(), "w"); + delete fileName; + traceInitialized = true; +} + +static void traceHeader(char flag, void *handle) { + timeval tv; + gettimeofday(&tv, NULL); + if (handle) { + fprintf(traceOut, "%c %ld %06ld %p ", flag, tv.tv_sec, tv.tv_usec, handle); + } else { + fprintf(traceOut, "%c %ld %06ld 0x0 ", flag, tv.tv_sec, tv.tv_usec); + } +} + +void traceBegin(void *nestHandle, const char *fmt, ...) { + traceInit(); + if (!traceOut) { + return; + } + traceHeader('B', nestHandle); + va_list args; + va_start(args, fmt); + vfprintf(traceOut, fmt, args); + va_end(args); + fprintf(traceOut, "\n"); +} + +void traceEnd(void *nestHandle, const char *fmt, ...) { + traceInit(); + if (!traceOut) { + return; + } + traceHeader('E', nestHandle); + va_list args; + va_start(args, fmt); + vfprintf(traceOut, fmt, args); + va_end(args); + fprintf(traceOut, "\n"); +} + +void traceAlloc(void *resourceHandle, const char *fmt, ...) { + traceInit(); + if (!traceOut) { + return; + } + traceHeader('A', resourceHandle); + va_list args; + va_start(args, fmt); + vfprintf(traceOut, fmt, args); + va_end(args); + fprintf(traceOut, "\n"); +} + +void traceFree(void *resourceHandle, const char *fmt, ...) { + traceInit(); + if (!traceOut) { + return; + } + traceHeader('F', resourceHandle); + va_list args; + va_start(args, fmt); + vfprintf(traceOut, fmt, args); + va_end(args); + fprintf(traceOut, "\n"); +} + +void traceMessage(const char *fmt, ...) { + traceInit(); + if (!traceOut) { + return; + } + traceHeader('M', NULL); + va_list args; + va_start(args, fmt); + vfprintf(traceOut, fmt, args); + va_end(args); + fprintf(traceOut, "\n"); +} + +#endif // ENABLE_TRACING diff --git a/Build/source/libs/xpdf/xpdf-src/goo/Trace.h b/Build/source/libs/xpdf/xpdf-src/goo/Trace.h new file mode 100644 index 00000000000..9f53ac5ef68 --- /dev/null +++ b/Build/source/libs/xpdf/xpdf-src/goo/Trace.h @@ -0,0 +1,43 @@ +//======================================================================== +// +// Trace.h +// +// Nested tracing. +// +// Copyright 2020 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef TRACING_H +#define TRACING_H + +#include + +#if ENABLE_TRACING + +// Enter a nesting level. +extern void traceBegin(void *nestHandle, const char *fmt, ...); + +// Exit a nesting level. +extern void traceEnd(void *nestHandle, const char *fmt, ...); + +// Mark a resource as allocated. +extern void traceAlloc(void *resourceHandle, const char *fmt, ...); + +// Mark a resource as freed. +extern void traceFree(void *resourceHandle, const char *fmt, ...); + +// Misc message. +extern void traceMessage(const char *fmt, ...); + +#else // ENABLE_TRACING + +static inline void traceBegin(void *nestHandle, const char *fmt, ...) {} +static inline void traceEnd(void *nestHandle, const char *fmt, ...) {} +static inline void traceAlloc(void *resourceHandle, const char *fmt, ...) {} +static inline void traceFree(void *nestHandle, const char *fmt, ...) {} +static inline void traceMessage(const char *fmt, ...) {} + +#endif // ENABLE_TRACING + +#endif // TRACING_H diff --git a/Build/source/libs/xpdf/xpdf-src/goo/gfile.cc b/Build/source/libs/xpdf/xpdf-src/goo/gfile.cc index e7112b2039d..570f4e7ab13 100644 --- a/Build/source/libs/xpdf/xpdf-src/goo/gfile.cc +++ b/Build/source/libs/xpdf/xpdf-src/goo/gfile.cc @@ -18,6 +18,8 @@ # include # include # include +# include +# include #else # if !defined(ACORN) # include @@ -579,68 +581,49 @@ GString *fileNameToUTF8(wchar_t *path) { } return s; } + +wchar_t *fileNameToUCS2(const char *path, wchar_t *out, size_t outSize) { + const char *p; + size_t i; + + for (p = path, i = 0; *p && i < outSize - 1; ++i) { + if ((p[0] & 0xe0) == 0xc0 && + p[1] && (p[1] & 0xc0) == 0x80) { + out[i] = (wchar_t)(((p[0] & 0x1f) << 6) | + (p[1] & 0x3f)); + p += 2; + } else if ((p[0] & 0xf0) == 0xe0 && + (p[1] & 0xc0) == 0x80 && + (p[2] & 0xc0) == 0x80) { + out[i] = (wchar_t)(((p[0] & 0x0f) << 12) | + ((p[1] & 0x3f) << 6) | + (p[2] & 0x3f)); + p += 3; + } else { + out[i] = (wchar_t)(p[0] & 0xff); + p += 1; + } + } + out[i] = (wchar_t)0; + return out; +} #endif FILE *openFile(const char *path, const char *mode) { #if defined(_WIN32) return fopen(path, mode); #if 0 - OSVERSIONINFO version; wchar_t wPath[_MAX_PATH + 1]; - char nPath[_MAX_PATH + 1]; wchar_t wMode[8]; - const char *p; int i; - // NB: _wfopen is only available in NT - version.dwOSVersionInfoSize = sizeof(version); - GetVersionEx(&version); - if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) { - for (p = path, i = 0; *p && i < _MAX_PATH; ++i) { - if ((p[0] & 0xe0) == 0xc0 && - p[1] && (p[1] & 0xc0) == 0x80) { - wPath[i] = (wchar_t)(((p[0] & 0x1f) << 6) | - (p[1] & 0x3f)); - p += 2; - } else if ((p[0] & 0xf0) == 0xe0 && - p[1] && (p[1] & 0xc0) == 0x80 && - p[2] && (p[2] & 0xc0) == 0x80) { - wPath[i] = (wchar_t)(((p[0] & 0x0f) << 12) | - ((p[1] & 0x3f) << 6) | - (p[2] & 0x3f)); - p += 3; - } else { - wPath[i] = (wchar_t)(p[0] & 0xff); - p += 1; - } - } - wPath[i] = (wchar_t)0; - for (i = 0; mode[i] && i < sizeof(wMode)/sizeof(wchar_t) - 1; ++i) { - wMode[i] = (wchar_t)(mode[i] & 0xff); - } - wMode[i] = (wchar_t)0; - return _wfopen(wPath, wMode); - } else { - for (p = path, i = 0; *p && i < _MAX_PATH; ++i) { - if ((p[0] & 0xe0) == 0xc0 && - p[1] && (p[1] & 0xc0) == 0x80) { - nPath[i] = (char)(((p[0] & 0x1f) << 6) | - (p[1] & 0x3f)); - p += 2; - } else if ((p[0] & 0xf0) == 0xe0 && - p[1] && (p[1] & 0xc0) == 0x80 && - p[2] && (p[2] & 0xc0) == 0x80) { - nPath[i] = (char)(((p[1] & 0x3f) << 6) | - (p[2] & 0x3f)); - p += 3; - } else { - nPath[i] = p[0]; - p += 1; - } - } - nPath[i] = '\0'; - return fopen(nPath, mode); + fileNameToUCS2(path, wPath, sizeof(wPath) / sizeof(wchar_t)); + for (i = 0; mode[i] && i < sizeof(wMode)/sizeof(wchar_t) - 1; ++i) { + wMode[i] = (wchar_t)(mode[i] & 0xff); } + wMode[i] = (wchar_t)0; + readWindowsShortcut(wPath, _MAX_PATH + 1); + return _wfopen(wPath, wMode); #endif /* 0 */ #elif defined(VMS) return fopen(path, mode, "ctx=stm"); @@ -649,6 +632,62 @@ FILE *openFile(const char *path, const char *mode) { #endif } +#if 0 +#ifdef _WIN32 +void readWindowsShortcut(wchar_t *wPath, size_t wPathSize) { + size_t n = wcslen(wPath); + if (n < 4 || wcscmp(wPath + n - 4, L".lnk")) { + return; + } + IShellLinkW *shellLink; + HRESULT hres; + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_IShellLinkW, (LPVOID *)&shellLink); + bool needCoUninit = false; + if (hres == CO_E_NOTINITIALIZED) { + CoInitialize(NULL); + needCoUninit = true; + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_IShellLinkW, (LPVOID *)&shellLink); + } + if (FAILED(hres)) { + return; + } + IPersistFile *persistFile; + hres = shellLink->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); + if (FAILED(hres)) { + return; + } + hres = persistFile->Load(wPath, STGM_READ); + if (FAILED(hres)) { + fprintf(stderr, "IPersistFile.Load failed: 0x%08x\n", hres); + exit(1); + } + wchar_t target[_MAX_PATH + 1]; + hres = shellLink->GetPath(target, _MAX_PATH + 1, NULL, 0); + if (FAILED(hres)) { + return; + } + shellLink->Release(); + if (needCoUninit) { + CoUninitialize(); + } + if (wcslen(target) > wPathSize - 1) { + return; + } + wcscpy(wPath, target); +} +#endif +#endif /* 0 */ + +int makeDir(const char *path, int mode) { +#ifdef _WIN32 + return _mkdir(path); +#else + return mkdir(path, (mode_t)mode); +#endif +} + char *getLine(char *buf, int size, FILE *f) { int c, i; diff --git a/Build/source/libs/xpdf/xpdf-src/goo/gfile.h b/Build/source/libs/xpdf/xpdf-src/goo/gfile.h index c0e75073ed0..2075d731a6c 100644 --- a/Build/source/libs/xpdf/xpdf-src/goo/gfile.h +++ b/Build/source/libs/xpdf/xpdf-src/goo/gfile.h @@ -80,13 +80,22 @@ extern GString *fileNameToUTF8(char *path); // Convert a file name from UCS-2 to UTF-8. extern GString *fileNameToUTF8(wchar_t *path); + +// Convert a file name from UTF-8 to UCS-2. [out] has space for +// [outSize] wchar_t elements (including the trailing zero). Returns +// [out]. +extern wchar_t *fileNameToUCS2(const char *path, wchar_t *out, size_t outSize); #endif // Open a file. On Windows, this converts the path from UTF-8 to -// UCS-2 and calls _wfopen (if available). On other OSes, this simply -// calls fopen. +// UCS-2 and calls _wfopen(). On other OSes, this simply calls fopen(). extern FILE *openFile(const char *path, const char *mode); +// Create a directory. On Windows, this converts the path from UTF-8 +// to UCS-2 and calls _wmkdir(), ignoring the mode argument. On other +// OSes, this simply calls mkdir(). +extern int makeDir(const char *path, int mode); + // Just like fgets, but handles Unix, Mac, and/or DOS end-of-line // conventions. extern char *getLine(char *buf, int size, FILE *f); diff --git a/Build/source/libs/xpdf/xpdf-src/goo/gmem.h b/Build/source/libs/xpdf/xpdf-src/goo/gmem.h index 87a32f0324c..f131c176f8b 100644 --- a/Build/source/libs/xpdf/xpdf-src/goo/gmem.h +++ b/Build/source/libs/xpdf/xpdf-src/goo/gmem.h @@ -32,10 +32,6 @@ public: #endif // USE_EXCEPTIONS -#ifdef __cplusplus -extern "C" { -#endif - /* * Same as malloc, but prints error message and exits if malloc() * returns NULL. @@ -96,8 +92,4 @@ extern void gMemReport(FILE *f); */ extern char *copyString(const char *s); -#ifdef __cplusplus -} -#endif - #endif diff --git a/Build/source/libs/xpdf/xpdf-src/splash/Splash.cc b/Build/source/libs/xpdf/xpdf-src/splash/Splash.cc index 44f87a9dcd2..343db796fb2 100644 --- a/Build/source/libs/xpdf/xpdf-src/splash/Splash.cc +++ b/Build/source/libs/xpdf/xpdf-src/splash/Splash.cc @@ -2,7 +2,7 @@ // // Splash.cc // -// Copyright 2003-2013 Glyph & Cog, LLC +// Copyright 2003-2020 Glyph & Cog, LLC // //======================================================================== @@ -80,6 +80,12 @@ struct SplashPipe { Guchar aInput; SplashColor cSrcVal; + // source overprint mask + //~ this is a kludge - this pointer should be passed as an arg to the + //~ pipeRun function, but that would require passing in a lot of + //~ null pointers, since it's rarely used + Guint *srcOverprintMaskPtr; + // special cases and result color GBool noTransparency; GBool shapeOnly; @@ -163,7 +169,7 @@ inline void Splash::updateModY(int y) { inline void Splash::pipeInit(SplashPipe *pipe, SplashPattern *pattern, Guchar aInput, GBool usesShape, - GBool nonIsolatedGroup) { + GBool nonIsolatedGroup, GBool usesSrcOverprint) { SplashColorMode mode; mode = bitmap->mode; @@ -181,6 +187,9 @@ inline void Splash::pipeInit(SplashPipe *pipe, SplashPattern *pattern, // source alpha pipe->aInput = aInput; + // source overprint mask + pipe->srcOverprintMaskPtr = NULL; + // special cases pipe->noTransparency = aInput == 255 && !state->softMask && @@ -212,7 +221,9 @@ inline void Splash::pipeInit(SplashPipe *pipe, SplashPattern *pattern, // select the 'run' function pipe->run = &Splash::pipeRun; - if (!pipe->pattern && pipe->noTransparency && !state->blendFunc) { + if (overprintMaskBitmap || usesSrcOverprint) { + // use Splash::pipeRun + } else if (!pipe->pattern && pipe->noTransparency && !state->blendFunc) { if (mode == splashModeMono1 && !bitmap->alpha) { pipe->run = &Splash::pipeRunSimpleMono1; } else if (mode == splashModeMono8 && bitmap->alpha) { @@ -239,6 +250,9 @@ inline void Splash::pipeInit(SplashPipe *pipe, SplashPattern *pattern, } else if (mode == splashModeCMYK8 && bitmap->alpha) { pipe->run = &Splash::pipeRunShapeCMYK8; #endif + } else if (mode == splashModeMono8 && !bitmap->alpha) { + // this is used when drawing soft-masked images + pipe->run = &Splash::pipeRunShapeNoAlphaMono8; } } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask && usesShape && @@ -256,6 +270,26 @@ inline void Splash::pipeInit(SplashPipe *pipe, SplashPattern *pattern, #if SPLASH_CMYK } else if (mode == splashModeCMYK8 && bitmap->alpha) { pipe->run = &Splash::pipeRunAACMYK8; +#endif + } + } else if (!pipe->pattern && + aInput == 255 && + state->softMask && + usesShape && + !state->inNonIsolatedGroup && + !state->inKnockoutGroup && + !nonIsolatedGroup && + state->overprintMask == 0xffffffff && + !state->blendFunc) { + if (mode == splashModeMono8 && bitmap->alpha) { + pipe->run = &Splash::pipeRunSoftMaskMono8; + } else if (mode == splashModeRGB8 && bitmap->alpha) { + pipe->run = &Splash::pipeRunSoftMaskRGB8; + } else if (mode == splashModeBGR8 && bitmap->alpha) { + pipe->run = &Splash::pipeRunSoftMaskBGR8; +#if SPLASH_CMYK + } else if (mode == splashModeCMYK8 && bitmap->alpha) { + pipe->run = &Splash::pipeRunSoftMaskCMYK8; #endif } } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask && @@ -292,6 +326,8 @@ void Splash::pipeRun(SplashPipe *pipe, int x0, int x1, int y, Guchar color0Mask; Guchar *alpha0Ptr; SplashColorPtr softMaskPtr; + Guint overprintMask; + Guint *overprintMaskPtr; #if SPLASH_CMYK Guchar aPrev; SplashColor cSrc2, cDest2; @@ -313,6 +349,9 @@ void Splash::pipeRun(SplashPipe *pipe, int x0, int x1, int y, } cSrcPtr += cSrcStride; ++shapePtr2; + if (pipe->srcOverprintMaskPtr) { + ++pipe->srcOverprintMaskPtr; + } } } else { shapeVal = 0xff; @@ -326,6 +365,8 @@ void Splash::pipeRun(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + if (bitmap->mode == splashModeMono1) { destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)]; destColorMask = (Guchar)(0x80 >> (x0 & 7)); @@ -367,6 +408,11 @@ void Splash::pipeRun(SplashPipe *pipe, int x0, int x1, int y, } else { alpha0Ptr = NULL; } + if (overprintMaskBitmap) { + overprintMaskPtr = overprintMaskBitmap + y * bitmap->width + x0; + } else { + overprintMaskPtr = NULL; + } for (x = x0; x <= x1; ++x) { @@ -399,6 +445,12 @@ void Splash::pipeRun(SplashPipe *pipe, int x0, int x1, int y, } cSrcPtr += cSrcStride; shapePtr2 += shapeStride; + if (pipe->srcOverprintMaskPtr) { + ++pipe->srcOverprintMaskPtr; + } + if (overprintMaskPtr) { + ++overprintMaskPtr; + } continue; } lastX = x; @@ -519,6 +571,15 @@ void Splash::pipeRun(SplashPipe *pipe, int x0, int x1, int y, //----- read source color; handle overprint + if (pipe->srcOverprintMaskPtr) { + overprintMask = *pipe->srcOverprintMaskPtr++; + } else { + overprintMask = state->overprintMask; + } + if (overprintMaskPtr) { + *overprintMaskPtr++ |= overprintMask; + } + switch (bitmap->mode) { case splashModeMono1: case splashModeMono8: @@ -545,22 +606,22 @@ void Splash::pipeRun(SplashPipe *pipe, int x0, int x1, int y, aPrev = aDest; } } - if (state->overprintMask & 0x01) { + if (overprintMask & 0x01) { cSrc[0] = state->cmykTransferC[cSrcPtr[0]]; } else { cSrc[0] = div255(aPrev * cDest[0]); } - if (state->overprintMask & 0x02) { + if (overprintMask & 0x02) { cSrc[1] = state->cmykTransferM[cSrcPtr[1]]; } else { cSrc[1] = div255(aPrev * cDest[1]); } - if (state->overprintMask & 0x04) { + if (overprintMask & 0x04) { cSrc[2] = state->cmykTransferY[cSrcPtr[2]]; } else { cSrc[2] = div255(aPrev * cDest[2]); } - if (state->overprintMask & 0x08) { + if (overprintMask & 0x08) { cSrc[3] = state->cmykTransferK[cSrcPtr[3]]; } else { cSrc[3] = div255(aPrev * cDest[3]); @@ -862,6 +923,8 @@ void Splash::pipeRunSimpleMono1(SplashPipe *pipe, int x0, int x1, int y, updateModX(x1); updateModY(y); + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)]; destColorMask = (Guchar)(0x80 >> (x0 & 7)); @@ -905,6 +968,8 @@ void Splash::pipeRunSimpleMono8(SplashPipe *pipe, int x0, int x1, int y, updateModX(x1); updateModY(y); + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -940,6 +1005,8 @@ void Splash::pipeRunSimpleRGB8(SplashPipe *pipe, int x0, int x1, int y, updateModX(x1); updateModY(y); + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -978,6 +1045,8 @@ void Splash::pipeRunSimpleBGR8(SplashPipe *pipe, int x0, int x1, int y, updateModX(x1); updateModY(y); + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -1017,6 +1086,8 @@ void Splash::pipeRunSimpleCMYK8(SplashPipe *pipe, int x0, int x1, int y, updateModX(x1); updateModY(y); + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -1067,6 +1138,8 @@ void Splash::pipeRunShapeMono1(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)]; destColorMask = (Guchar)(0x80 >> (x0 & 7)); @@ -1150,6 +1223,8 @@ void Splash::pipeRunShapeMono8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -1243,6 +1318,8 @@ void Splash::pipeRunShapeRGB8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -1349,6 +1426,8 @@ void Splash::pipeRunShapeBGR8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -1456,6 +1535,8 @@ void Splash::pipeRunShapeCMYK8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -1480,26 +1561,10 @@ void Splash::pipeRunShapeCMYK8(SplashPipe *pipe, int x0, int x1, int y, aDest = *destAlphaPtr; //----- overprint - if (state->overprintMask & 1) { - cSrc0 = state->cmykTransferC[cSrcPtr[0]]; - } else { - cSrc0 = div255(aDest * cDest0); - } - if (state->overprintMask & 2) { - cSrc1 = state->cmykTransferM[cSrcPtr[1]]; - } else { - cSrc1 = div255(aDest * cDest1); - } - if (state->overprintMask & 4) { - cSrc2 = state->cmykTransferY[cSrcPtr[2]]; - } else { - cSrc2 = div255(aDest * cDest2); - } - if (state->overprintMask & 8) { - cSrc3 = state->cmykTransferK[cSrcPtr[3]]; - } else { - cSrc3 = div255(aDest * cDest3); - } + cSrc0 = state->cmykTransferC[cSrcPtr[0]]; + cSrc1 = state->cmykTransferM[cSrcPtr[1]]; + cSrc2 = state->cmykTransferY[cSrcPtr[2]]; + cSrc3 = state->cmykTransferK[cSrcPtr[3]]; //----- source alpha aSrc = shape; @@ -1551,6 +1616,80 @@ void Splash::pipeRunShapeCMYK8(SplashPipe *pipe, int x0, int x1, int y, #endif +// special case: +// !pipe->pattern && pipe->shapeOnly && !state->blendFunc && +// bitmap->mode == splashModeMono8 && !bitmap->alpha +void Splash::pipeRunShapeNoAlphaMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, + SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, cSrc0, cDest0, cResult0; + SplashColorPtr destColorPtr; + int cSrcStride, x, lastX; + + if (cSrcPtr) { + cSrcStride = 1; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } + for (; x0 <= x1; ++x0) { + if (*shapePtr) { + break; + } + cSrcPtr += cSrcStride; + ++shapePtr; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModY(y); + lastX = x0; + + useDestRow(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0]; + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + ++destColorPtr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- source color + cSrc0 = state->grayTransfer[cSrcPtr[0]]; + + //----- source alpha + aSrc = shape; + + //----- special case for aSrc = 255 + if (aSrc == 255) { + cResult0 = cSrc0; + } else { + + //----- read destination pixel + cDest0 = *destColorPtr; + + //----- result color + cResult0 = div255((255 - aSrc) * cDest0 + aSrc * cSrc0); + } + + //----- write destination pixel + *destColorPtr++ = cResult0; + + cSrcPtr += cSrcStride; + ++shapePtr; + } + + updateModX(lastX); +} + // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && @@ -1584,6 +1723,8 @@ void Splash::pipeRunAAMono1(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)]; destColorMask = (Guchar)(0x80 >> (x0 & 7)); @@ -1663,6 +1804,8 @@ void Splash::pipeRunAAMono8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -1746,6 +1889,8 @@ void Splash::pipeRunAARGB8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -1840,6 +1985,8 @@ void Splash::pipeRunAABGR8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -1935,6 +2082,8 @@ void Splash::pipeRunAACMYK8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; @@ -2017,13 +2166,19 @@ void Splash::pipeRunAACMYK8(SplashPipe *pipe, int x0, int x1, int y, #endif -void Splash::pipeRunNonIsoMono8(SplashPipe *pipe, int x0, int x1, int y, - Guchar *shapePtr, SplashColorPtr cSrcPtr) { - Guchar shape, aSrc, aDest, alphaI, alpha0, aResult; +// special case: +// !pipe->pattern && aInput == 255 && state->softMask && usesShape && +// !state->inNonIsolatedGroup && !state->inKnockoutGroup && +// !nonIsolatedGroup && state->overprintMask == 0xffffffff && +// !state->blendFunc && +// bitmap->mode == splashModeMono8 && bitmap->alpha +void Splash::pipeRunSoftMaskMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult; Guchar cSrc0, cDest0, cResult0; SplashColorPtr destColorPtr; Guchar *destAlphaPtr; - Guchar *alpha0Ptr; + SplashColorPtr softMaskPtr; int cSrcStride, x, lastX; if (cSrcPtr) { @@ -2046,68 +2201,86 @@ void Splash::pipeRunNonIsoMono8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; - alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y) - * groupBackBitmap->alphaRowSize + - (groupBackX + x0)]; + softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0]; for (x = x0; x <= x1; ++x) { //----- shape shape = *shapePtr; if (!shape) { - destColorPtr += 1; + ++destColorPtr; ++destAlphaPtr; - ++alpha0Ptr; + ++softMaskPtr; cSrcPtr += cSrcStride; ++shapePtr; continue; } lastX = x; - //----- read destination pixel - cDest0 = destColorPtr[0]; - aDest = *destAlphaPtr; - - //----- source color + //----- read source color cSrc0 = state->grayTransfer[cSrcPtr[0]]; //----- source alpha - aSrc = div255(pipe->aInput * shape); - - //----- result alpha and non-isolated group element correction - aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); - alpha0 = *alpha0Ptr++; - alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0)); + aSrc = div255(*softMaskPtr++ * shape); - //----- result color - if (alphaI == 0) { - cResult0 = 0; + //----- special case for aSrc = 255 + if (aSrc == 255) { + aResult = 255; + cResult0 = cSrc0; } else { - cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); + + //----- read destination alpha + aDest = *destAlphaPtr; + + //----- special case for aDest = 0 + if (aDest == 0) { + aResult = aSrc; + cResult0 = cSrc0; + } else { + + //----- read destination pixel + cDest0 = destColorPtr[0]; + + //----- result alpha and non-isolated group element correction + aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); + alphaI = aResult; + + //----- result color + cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); + } } //----- write destination pixel - *destColorPtr++ = cResult0; + destColorPtr[0] = cResult0; + ++destColorPtr; *destAlphaPtr++ = aResult; cSrcPtr += cSrcStride; ++shapePtr; - } // for (x ...) + } updateModX(lastX); } -void Splash::pipeRunNonIsoRGB8(SplashPipe *pipe, int x0, int x1, int y, - Guchar *shapePtr, SplashColorPtr cSrcPtr) { - Guchar shape, aSrc, aDest, alphaI, alpha0, aResult; +// special case: +// !pipe->pattern && aInput == 255 && state->softMask && usesShape && +// !state->inNonIsolatedGroup && !state->inKnockoutGroup && +// !nonIsolatedGroup && state->overprintMask == 0xffffffff && +// !state->blendFunc && +// bitmap->mode == splashModeRGB8 && bitmap->alpha +void Splash::pipeRunSoftMaskRGB8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult; Guchar cSrc0, cSrc1, cSrc2; Guchar cDest0, cDest1, cDest2; Guchar cResult0, cResult1, cResult2; SplashColorPtr destColorPtr; Guchar *destAlphaPtr; - Guchar *alpha0Ptr; + SplashColorPtr softMaskPtr; int cSrcStride, x, lastX; if (cSrcPtr) { @@ -2130,11 +2303,11 @@ void Splash::pipeRunNonIsoRGB8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; - alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y) - * groupBackBitmap->alphaRowSize + - (groupBackX + x0)]; + softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0]; for (x = x0; x <= x1; ++x) { @@ -2143,41 +2316,54 @@ void Splash::pipeRunNonIsoRGB8(SplashPipe *pipe, int x0, int x1, int y, if (!shape) { destColorPtr += 3; ++destAlphaPtr; - ++alpha0Ptr; + ++softMaskPtr; cSrcPtr += cSrcStride; ++shapePtr; continue; } lastX = x; - //----- read destination pixel - cDest0 = destColorPtr[0]; - cDest1 = destColorPtr[1]; - cDest2 = destColorPtr[2]; - aDest = *destAlphaPtr; - - //----- source color + //----- read source color cSrc0 = state->rgbTransferR[cSrcPtr[0]]; cSrc1 = state->rgbTransferG[cSrcPtr[1]]; cSrc2 = state->rgbTransferB[cSrcPtr[2]]; //----- source alpha - aSrc = div255(pipe->aInput * shape); - - //----- result alpha and non-isolated group element correction - aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); - alpha0 = *alpha0Ptr++; - alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0)); + aSrc = div255(*softMaskPtr++ * shape); - //----- result color - if (alphaI == 0) { - cResult0 = 0; - cResult1 = 0; - cResult2 = 0; + //----- special case for aSrc = 255 + if (aSrc == 255) { + aResult = 255; + cResult0 = cSrc0; + cResult1 = cSrc1; + cResult2 = cSrc2; } else { - cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); - cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI); - cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI); + + //----- read destination alpha + aDest = *destAlphaPtr; + + //----- special case for aDest = 0 + if (aDest == 0) { + aResult = aSrc; + cResult0 = cSrc0; + cResult1 = cSrc1; + cResult2 = cSrc2; + } else { + + //----- read destination pixel + cDest0 = destColorPtr[0]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[2]; + + //----- result alpha and non-isolated group element correction + aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); + alphaI = aResult; + + //----- result color + cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); + cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI); + cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI); + } } //----- write destination pixel @@ -2189,20 +2375,26 @@ void Splash::pipeRunNonIsoRGB8(SplashPipe *pipe, int x0, int x1, int y, cSrcPtr += cSrcStride; ++shapePtr; - } // for (x ...) + } updateModX(lastX); } -void Splash::pipeRunNonIsoBGR8(SplashPipe *pipe, int x0, int x1, int y, - Guchar *shapePtr, SplashColorPtr cSrcPtr) { - Guchar shape, aSrc, aDest, alphaI, alpha0, aResult; +// special case: +// !pipe->pattern && aInput == 255 && state->softMask && usesShape && +// !state->inNonIsolatedGroup && !state->inKnockoutGroup && +// !nonIsolatedGroup && state->overprintMask == 0xffffffff && +// !state->blendFunc && +// bitmap->mode == splashModeBGR8 && bitmap->alpha +void Splash::pipeRunSoftMaskBGR8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult; Guchar cSrc0, cSrc1, cSrc2; Guchar cDest0, cDest1, cDest2; Guchar cResult0, cResult1, cResult2; SplashColorPtr destColorPtr; Guchar *destAlphaPtr; - Guchar *alpha0Ptr; + SplashColorPtr softMaskPtr; int cSrcStride, x, lastX; if (cSrcPtr) { @@ -2225,11 +2417,11 @@ void Splash::pipeRunNonIsoBGR8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; - alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y) - * groupBackBitmap->alphaRowSize + - (groupBackX + x0)]; + softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0]; for (x = x0; x <= x1; ++x) { @@ -2238,67 +2430,86 @@ void Splash::pipeRunNonIsoBGR8(SplashPipe *pipe, int x0, int x1, int y, if (!shape) { destColorPtr += 3; ++destAlphaPtr; - ++alpha0Ptr; + ++softMaskPtr; cSrcPtr += cSrcStride; ++shapePtr; continue; } lastX = x; - //----- read destination pixel - cDest0 = destColorPtr[2]; - cDest1 = destColorPtr[1]; - cDest2 = destColorPtr[0]; - aDest = *destAlphaPtr; - - //----- source color + //----- read source color cSrc0 = state->rgbTransferR[cSrcPtr[0]]; cSrc1 = state->rgbTransferG[cSrcPtr[1]]; cSrc2 = state->rgbTransferB[cSrcPtr[2]]; //----- source alpha - aSrc = div255(pipe->aInput * shape); + aSrc = div255(*softMaskPtr++ * shape); - //----- result alpha and non-isolated group element correction - aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); - alpha0 = *alpha0Ptr++; - alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0)); + //----- special case for aSrc = 255 + if (aSrc == 255) { + aResult = 255; + cResult0 = cSrc0; + cResult1 = cSrc1; + cResult2 = cSrc2; + } else { - //----- result color - if (alphaI == 0) { - cResult0 = 0; - cResult1 = 0; - cResult2 = 0; - } else { - cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); - cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI); - cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI); + //----- read destination alpha + aDest = *destAlphaPtr; + + //----- special case for aDest = 0 + if (aDest == 0) { + aResult = aSrc; + cResult0 = cSrc0; + cResult1 = cSrc1; + cResult2 = cSrc2; + } else { + + //----- read destination pixel + cDest0 = destColorPtr[2]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[0]; + + //----- result alpha and non-isolated group element correction + aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); + alphaI = aResult; + + //----- result color + cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); + cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI); + cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI); + } } //----- write destination pixel - destColorPtr[0] = cResult2; - destColorPtr[1] = cResult1; destColorPtr[2] = cResult0; + destColorPtr[1] = cResult1; + destColorPtr[0] = cResult2; destColorPtr += 3; *destAlphaPtr++ = aResult; cSrcPtr += cSrcStride; ++shapePtr; - } // for (x ...) + } updateModX(lastX); } #if SPLASH_CMYK -void Splash::pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y, - Guchar *shapePtr, SplashColorPtr cSrcPtr) { - Guchar shape, aSrc, aDest, alphaI, alpha0, aResult, aPrev; +// special case: +// !pipe->pattern && aInput == 255 && state->softMask && usesShape && +// !state->inNonIsolatedGroup && !state->inKnockoutGroup && +// !nonIsolatedGroup && state->overprintMask == 0xffffffff && +// !state->blendFunc && +// bitmap->mode == splashModeCMYK8 && bitmap->alpha +void Splash::pipeRunSoftMaskCMYK8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult; Guchar cSrc0, cSrc1, cSrc2, cSrc3; Guchar cDest0, cDest1, cDest2, cDest3; Guchar cResult0, cResult1, cResult2, cResult3; SplashColorPtr destColorPtr; Guchar *destAlphaPtr; - Guchar *alpha0Ptr; + SplashColorPtr softMaskPtr; int cSrcStride, x, lastX; if (cSrcPtr) { @@ -2321,11 +2532,11 @@ void Splash::pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y, updateModY(y); lastX = x0; + useDestRow(y); + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 4]; destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; - alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y) - * groupBackBitmap->alphaRowSize + - (groupBackX + x0)]; + softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0]; for (x = x0; x <= x1; ++x) { @@ -2334,7 +2545,7 @@ void Splash::pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y, if (!shape) { destColorPtr += 4; ++destAlphaPtr; - ++alpha0Ptr; + ++softMaskPtr; cSrcPtr += cSrcStride; ++shapePtr; continue; @@ -2346,30 +2557,213 @@ void Splash::pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y, cDest1 = destColorPtr[1]; cDest2 = destColorPtr[2]; cDest3 = destColorPtr[3]; + + //----- read destination alpha aDest = *destAlphaPtr; //----- overprint - aPrev = (Guchar)(*alpha0Ptr + aDest - div255(*alpha0Ptr * aDest)); - if (state->overprintMask & 0x01) { - cSrc0 = state->cmykTransferC[cSrcPtr[0]]; + cSrc0 = state->cmykTransferC[cSrcPtr[0]]; + cSrc1 = state->cmykTransferM[cSrcPtr[1]]; + cSrc2 = state->cmykTransferY[cSrcPtr[2]]; + cSrc3 = state->cmykTransferK[cSrcPtr[3]]; + + //----- source alpha + aSrc = div255(*softMaskPtr++ * shape); + + //----- special case for aSrc = 255 + if (aSrc == 255) { + aResult = 255; + cResult0 = cSrc0; + cResult1 = cSrc1; + cResult2 = cSrc2; + cResult3 = cSrc3; } else { - cSrc0 = div255(aPrev * cDest0); + + //----- special case for aDest = 0 + if (aDest == 0) { + aResult = aSrc; + cResult0 = cSrc0; + cResult1 = cSrc1; + cResult2 = cSrc2; + cResult3 = cSrc3; + } else { + + //----- result alpha and non-isolated group element correction + aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); + alphaI = aResult; + + //----- result color + cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); + cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI); + cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI); + cResult3 = (Guchar)(((alphaI - aSrc) * cDest3 + aSrc * cSrc3) / alphaI); + } } - if (state->overprintMask & 0x02) { - cSrc1 = state->cmykTransferM[cSrcPtr[1]]; - } else { - cSrc1 = div255(aPrev * cDest1); + + //----- write destination pixel + destColorPtr[0] = cResult0; + destColorPtr[1] = cResult1; + destColorPtr[2] = cResult2; + destColorPtr[3] = cResult3; + destColorPtr += 4; + *destAlphaPtr++ = aResult; + + cSrcPtr += cSrcStride; + ++shapePtr; + } + + updateModX(lastX); +} +#endif + + +void Splash::pipeRunNonIsoMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, alpha0, aResult; + Guchar cSrc0, cDest0, cResult0; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + Guchar *alpha0Ptr; + int cSrcStride, x, lastX; + + if (cSrcPtr) { + cSrcStride = 1; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } + for (; x0 <= x1; ++x0) { + if (*shapePtr) { + break; } - if (state->overprintMask & 0x04) { - cSrc2 = state->cmykTransferY[cSrcPtr[2]]; - } else { - cSrc2 = div255(aPrev * cDest2); + cSrcPtr += cSrcStride; + ++shapePtr; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModY(y); + lastX = x0; + + useDestRow(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; + alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y) + * groupBackBitmap->alphaRowSize + + (groupBackX + x0)]; + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 1; + ++destAlphaPtr; + ++alpha0Ptr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; } - if (state->overprintMask & 0x08) { - cSrc3 = state->cmykTransferK[cSrcPtr[3]]; + lastX = x; + + //----- read destination pixel + cDest0 = destColorPtr[0]; + aDest = *destAlphaPtr; + + //----- source color + cSrc0 = state->grayTransfer[cSrcPtr[0]]; + + //----- source alpha + aSrc = div255(pipe->aInput * shape); + + //----- result alpha and non-isolated group element correction + aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); + alpha0 = *alpha0Ptr++; + alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0)); + + //----- result color + if (alphaI == 0) { + cResult0 = 0; } else { - cSrc3 = div255(aPrev * cDest3); + cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); + } + + //----- write destination pixel + *destColorPtr++ = cResult0; + *destAlphaPtr++ = aResult; + + cSrcPtr += cSrcStride; + ++shapePtr; + } // for (x ...) + + updateModX(lastX); +} + +void Splash::pipeRunNonIsoRGB8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, alpha0, aResult; + Guchar cSrc0, cSrc1, cSrc2; + Guchar cDest0, cDest1, cDest2; + Guchar cResult0, cResult1, cResult2; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + Guchar *alpha0Ptr; + int cSrcStride, x, lastX; + + if (cSrcPtr) { + cSrcStride = 3; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } + for (; x0 <= x1; ++x0) { + if (*shapePtr) { + break; + } + cSrcPtr += cSrcStride; + ++shapePtr; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModY(y); + lastX = x0; + + useDestRow(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3]; + destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; + alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y) + * groupBackBitmap->alphaRowSize + + (groupBackX + x0)]; + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 3; + ++destAlphaPtr; + ++alpha0Ptr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; } + lastX = x; + + //----- read destination pixel + cDest0 = destColorPtr[0]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[2]; + aDest = *destAlphaPtr; + + //----- source color + cSrc0 = state->rgbTransferR[cSrcPtr[0]]; + cSrc1 = state->rgbTransferG[cSrcPtr[1]]; + cSrc2 = state->rgbTransferB[cSrcPtr[2]]; //----- source alpha aSrc = div255(pipe->aInput * shape); @@ -2384,20 +2778,17 @@ void Splash::pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y, cResult0 = 0; cResult1 = 0; cResult2 = 0; - cResult3 = 0; } else { cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI); cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI); - cResult3 = (Guchar)(((alphaI - aSrc) * cDest3 + aSrc * cSrc3) / alphaI); } //----- write destination pixel destColorPtr[0] = cResult0; destColorPtr[1] = cResult1; destColorPtr[2] = cResult2; - destColorPtr[3] = cResult3; - destColorPtr += 4; + destColorPtr += 3; *destAlphaPtr++ = aResult; cSrcPtr += cSrcStride; @@ -2406,20 +2797,315 @@ void Splash::pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y, updateModX(lastX); } -#endif - -//------------------------------------------------------------------------ +void Splash::pipeRunNonIsoBGR8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, alpha0, aResult; + Guchar cSrc0, cSrc1, cSrc2; + Guchar cDest0, cDest1, cDest2; + Guchar cResult0, cResult1, cResult2; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + Guchar *alpha0Ptr; + int cSrcStride, x, lastX; -// Transform a point from user space to device space. -inline void Splash::transform(SplashCoord *matrix, - SplashCoord xi, SplashCoord yi, - SplashCoord *xo, SplashCoord *yo) { - // [ m[0] m[1] 0 ] - // [xo yo 1] = [xi yi 1] * [ m[2] m[3] 0 ] - // [ m[4] m[5] 1 ] - *xo = xi * matrix[0] + yi * matrix[2] + matrix[4]; - *yo = xi * matrix[1] + yi * matrix[3] + matrix[5]; + if (cSrcPtr) { + cSrcStride = 3; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } + for (; x0 <= x1; ++x0) { + if (*shapePtr) { + break; + } + cSrcPtr += cSrcStride; + ++shapePtr; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModY(y); + lastX = x0; + + useDestRow(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3]; + destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; + alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y) + * groupBackBitmap->alphaRowSize + + (groupBackX + x0)]; + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 3; + ++destAlphaPtr; + ++alpha0Ptr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- read destination pixel + cDest0 = destColorPtr[2]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[0]; + aDest = *destAlphaPtr; + + //----- source color + cSrc0 = state->rgbTransferR[cSrcPtr[0]]; + cSrc1 = state->rgbTransferG[cSrcPtr[1]]; + cSrc2 = state->rgbTransferB[cSrcPtr[2]]; + + //----- source alpha + aSrc = div255(pipe->aInput * shape); + + //----- result alpha and non-isolated group element correction + aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); + alpha0 = *alpha0Ptr++; + alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0)); + + //----- result color + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + } else { + cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); + cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI); + cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI); + } + + //----- write destination pixel + destColorPtr[0] = cResult2; + destColorPtr[1] = cResult1; + destColorPtr[2] = cResult0; + destColorPtr += 3; + *destAlphaPtr++ = aResult; + + cSrcPtr += cSrcStride; + ++shapePtr; + } // for (x ...) + + updateModX(lastX); +} + +#if SPLASH_CMYK +void Splash::pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, alpha0, aResult, aPrev; + Guchar cSrc0, cSrc1, cSrc2, cSrc3; + Guchar cDest0, cDest1, cDest2, cDest3; + Guchar cResult0, cResult1, cResult2, cResult3; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + Guchar *alpha0Ptr; + int cSrcStride, x, lastX; + + if (cSrcPtr) { + cSrcStride = 4; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } + for (; x0 <= x1; ++x0) { + if (*shapePtr) { + break; + } + cSrcPtr += cSrcStride; + ++shapePtr; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModY(y); + lastX = x0; + + useDestRow(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 4]; + destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0]; + alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y) + * groupBackBitmap->alphaRowSize + + (groupBackX + x0)]; + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 4; + ++destAlphaPtr; + ++alpha0Ptr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- read destination pixel + cDest0 = destColorPtr[0]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[2]; + cDest3 = destColorPtr[3]; + aDest = *destAlphaPtr; + + //----- overprint + aPrev = (Guchar)(*alpha0Ptr + aDest - div255(*alpha0Ptr * aDest)); + if (state->overprintMask & 0x01) { + cSrc0 = state->cmykTransferC[cSrcPtr[0]]; + } else { + cSrc0 = div255(aPrev * cDest0); + } + if (state->overprintMask & 0x02) { + cSrc1 = state->cmykTransferM[cSrcPtr[1]]; + } else { + cSrc1 = div255(aPrev * cDest1); + } + if (state->overprintMask & 0x04) { + cSrc2 = state->cmykTransferY[cSrcPtr[2]]; + } else { + cSrc2 = div255(aPrev * cDest2); + } + if (state->overprintMask & 0x08) { + cSrc3 = state->cmykTransferK[cSrcPtr[3]]; + } else { + cSrc3 = div255(aPrev * cDest3); + } + + //----- source alpha + aSrc = div255(pipe->aInput * shape); + + //----- result alpha and non-isolated group element correction + aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest)); + alpha0 = *alpha0Ptr++; + alphaI = (Guchar)(aResult + alpha0 - div255(aResult * alpha0)); + + //----- result color + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + cResult3 = 0; + } else { + cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI); + cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI); + cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI); + cResult3 = (Guchar)(((alphaI - aSrc) * cDest3 + aSrc * cSrc3) / alphaI); + } + + //----- write destination pixel + destColorPtr[0] = cResult0; + destColorPtr[1] = cResult1; + destColorPtr[2] = cResult2; + destColorPtr[3] = cResult3; + destColorPtr += 4; + *destAlphaPtr++ = aResult; + + cSrcPtr += cSrcStride; + ++shapePtr; + } // for (x ...) + + updateModX(lastX); +} +#endif + + +void Splash::useDestRow(int y) { + int y0, y1, yy; + + if (groupDestInitMode == splashGroupDestPreInit) { + return; + } + if (groupDestInitYMin > groupDestInitYMax) { + y0 = y1 = y; + groupDestInitYMin = groupDestInitYMax = y; + } else if (y < groupDestInitYMin) { + y0 = y; + y1 = groupDestInitYMin - 1; + groupDestInitYMin = y; + } else if (y > groupDestInitYMax) { + y0 = groupDestInitYMax + 1; + y1 = y; + groupDestInitYMax = y; + } else { + return; + } + for (yy = y0; yy <= y1; ++yy) { + if (groupDestInitMode == splashGroupDestInitZero) { + // same as clear(color=0, alpha=0) + memset(bitmap->data + bitmap->rowSize * yy, 0, + bitmap->rowSize < 0 ? -bitmap->rowSize : bitmap->rowSize); + if (bitmap->alpha) { + memset(bitmap->alpha + bitmap->alphaRowSize * yy, 0, + bitmap->alphaRowSize); + } + } else { // (groupDestInitMode == splashGroupDestInitCopy) + // same as blitTransparent + copyGroupBackdropRow(yy); + } + } +} + +void Splash::copyGroupBackdropRow(int y) { + SplashColorPtr p, q; + Guchar mask, srcMask; + int x; + + if (groupBackBitmap->mode != bitmap->mode) { + return; + } + + if (bitmap->mode == splashModeMono1) { + p = &bitmap->data[y * bitmap->rowSize]; + mask = (Guchar)0x80; + q = &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize + + (groupBackX >> 3)]; + srcMask = (Guchar)(0x80 >> (groupBackX & 7)); + for (x = 0; x < bitmap->width; ++x) { + if (*q & srcMask) { + *p |= mask; + } else { + *p &= (Guchar)~mask; + } + if (!(mask = (Guchar)(mask >> 1))) { + mask = 0x80; + ++p; + } + if (!(srcMask = (Guchar)(srcMask >> 1))) { + srcMask = 0x80; + ++q; + } + } + } else { + p = &bitmap->data[y * bitmap->rowSize]; + q = &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize + + bitmapComps * groupBackX]; + memcpy(p, q, bitmapComps * bitmap->width); + } + + if (bitmap->alpha) { + memset(&bitmap->alpha[y * bitmap->alphaRowSize], 0, bitmap->width); + } +} + +//------------------------------------------------------------------------ + +// Transform a point from user space to device space. +inline void Splash::transform(SplashCoord *matrix, + SplashCoord xi, SplashCoord yi, + SplashCoord *xo, SplashCoord *yo) { + // [ m[0] m[1] 0 ] + // [xo yo 1] = [xi yi 1] * [ m[2] m[3] 0 ] + // [ m[4] m[5] 1 ] + *xo = xi * matrix[0] + yi * matrix[2] + matrix[4]; + *yo = xi * matrix[1] + yi * matrix[3] + matrix[5]; } //------------------------------------------------------------------------ @@ -2428,7 +3114,13 @@ inline void Splash::transform(SplashCoord *matrix, SplashImageCache::SplashImageCache() { tag = NULL; - image = NULL; + width = 0; + height = 0; + mode = splashModeRGB8; + alpha = gFalse; + interpolate = gFalse; + colorData = NULL; + alphaData = NULL; refCount = 1; } @@ -2436,9 +3128,39 @@ SplashImageCache::~SplashImageCache() { if (tag) { delete tag; } - if (image) { - delete image; + gfree(colorData); + gfree(alphaData); +} + +GBool SplashImageCache::match(GString *aTag, int aWidth, int aHeight, + SplashColorMode aMode, GBool aAlpha, + GBool aInterpolate) { + return aTag && tag && !aTag->cmp(tag) && + aWidth == width && aHeight == height && + aMode == mode && aAlpha == alpha && + aInterpolate == interpolate; +} + +void SplashImageCache::reset(GString *aTag, int aWidth, int aHeight, + SplashColorMode aMode, GBool aAlpha, + GBool aInterpolate) { + if (tag) { + delete tag; } + if (aTag) { + tag = aTag->copy(); + } else { + tag = NULL; + } + width = aWidth; + height = aHeight; + mode = aMode; + alpha = aAlpha; + interpolate = aInterpolate; + gfree(colorData); + colorData = NULL; + gfree(alphaData); + alphaData = NULL; } void SplashImageCache::incRefCount() { @@ -2453,4210 +3175,3804 @@ void SplashImageCache::decRefCount() { } //------------------------------------------------------------------------ -// Splash +// ImageScaler //------------------------------------------------------------------------ -Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA, - SplashImageCache *imageCacheA, - SplashScreenParams *screenParams) { - bitmap = bitmapA; - bitmapComps = splashColorModeNComps[bitmap->mode]; - vectorAntialias = vectorAntialiasA; - inShading = gFalse; - state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, - screenParams); - scanBuf = (Guchar *)gmalloc(bitmap->width); - if (bitmap->mode == splashModeMono1) { - scanBuf2 = (Guchar *)gmalloc(bitmap->width); - } else { - scanBuf2 = NULL; - } - groupBackBitmap = NULL; - minLineWidth = 0; - clearModRegion(); - debugMode = gFalse; +// Abstract base class. +class ImageScaler { +public: - if (imageCacheA) { - imageCache = imageCacheA; - imageCache->incRefCount(); - } else { - imageCache = new SplashImageCache(); - } -} + ImageScaler() {} + virtual ~ImageScaler() {} -Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA, - SplashImageCache *imageCacheA, SplashScreen *screenA) { - bitmap = bitmapA; - bitmapComps = splashColorModeNComps[bitmap->mode]; - vectorAntialias = vectorAntialiasA; - inShading = gFalse; - state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, - screenA); - scanBuf = (Guchar *)gmalloc(bitmap->width); - if (bitmap->mode == splashModeMono1) { - scanBuf2 = (Guchar *)gmalloc(bitmap->width); + // Compute the next line of the scaled image. This can be called up + // to [scaledHeight] times. + virtual void nextLine() = 0; + + // Retrieve the color and alpha data generated by the most recent + // call to nextLine(). + virtual Guchar *colorData() = 0; + virtual Guchar *alphaData() = 0; +}; + +//------------------------------------------------------------------------ +// BasicImageScaler +//------------------------------------------------------------------------ + +// The actual image scaler. +class BasicImageScaler: public ImageScaler { +public: + + BasicImageScaler(SplashImageSource aSrc, void *aSrcData, + int aSrcWidth, int aSrcHeight, int aNComps, GBool aHasAlpha, + int aScaledWidth, int aScaledHeight, GBool aInterpolate); + virtual ~BasicImageScaler(); + virtual void nextLine(); + virtual Guchar *colorData() { return colorLine; } + virtual Guchar *alphaData() { return alphaLine; } + +protected: + + void vertDownscaleHorizDownscale(); + void vertDownscaleHorizUpscaleNoInterp(); + void vertDownscaleHorizUpscaleInterp(); + void vertUpscaleHorizDownscaleNoInterp(); + void vertUpscaleHorizDownscaleInterp(); + void vertUpscaleHorizUpscaleNoInterp(); + void vertUpscaleHorizUpscaleInterp(); + + // source image data function + SplashImageSource src; + void *srcData; + + // source image size + int srcWidth; + int srcHeight; + + // scaled image size + int scaledWidth; + int scaledHeight; + + // number of color and alpha components + int nComps; + GBool hasAlpha; + + // params/state for vertical scaling + int yp, yq; + int yt, yn; + int ySrcCur, yScaledCur; + SplashCoord yInvScale; + + // params for horizontal scaling + int xp, xq; + SplashCoord xInvScale; + + // scaling function + void (BasicImageScaler::*scalingFunc)(); + + // temporary buffers for vertical scaling + Guchar *colorTmpBuf0; + Guchar *colorTmpBuf1; + Guchar *colorTmpBuf2; + Guchar *alphaTmpBuf0; + Guchar *alphaTmpBuf1; + Guchar *alphaTmpBuf2; + Guint *colorAccBuf; + Guint *alphaAccBuf; + + // output of horizontal scaling + Guchar *colorLine; + Guchar *alphaLine; +}; + +BasicImageScaler::BasicImageScaler(SplashImageSource aSrc, void *aSrcData, + int aSrcWidth, int aSrcHeight, + int aNComps, GBool aHasAlpha, + int aScaledWidth, int aScaledHeight, + GBool aInterpolate) { + colorTmpBuf0 = NULL; + colorTmpBuf1 = NULL; + colorTmpBuf2 = NULL; + alphaTmpBuf0 = NULL; + alphaTmpBuf1 = NULL; + alphaTmpBuf2 = NULL; + colorAccBuf = NULL; + alphaAccBuf = NULL; + colorLine = NULL; + alphaLine = NULL; + + src = aSrc; + srcData = aSrcData; + srcWidth = aSrcWidth; + srcHeight = aSrcHeight; + scaledWidth = aScaledWidth; + scaledHeight = aScaledHeight; + nComps = aNComps; + hasAlpha = aHasAlpha; + + // select scaling function; allocate buffers + if (scaledHeight <= srcHeight) { + // vertical downscaling + yp = srcHeight / scaledHeight; + yq = srcHeight % scaledHeight; + yt = 0; + colorTmpBuf0 = (Guchar *)gmallocn(srcWidth, nComps); + colorAccBuf = (Guint *)gmallocn(srcWidth, nComps * (int)sizeof(Guint)); + if (hasAlpha) { + alphaTmpBuf0 = (Guchar *)gmalloc(srcWidth); + alphaAccBuf = (Guint *)gmallocn(srcWidth, sizeof(Guint)); + } + if (scaledWidth <= srcWidth) { + scalingFunc = &BasicImageScaler::vertDownscaleHorizDownscale; + } else { + if (aInterpolate) { + scalingFunc = &BasicImageScaler::vertDownscaleHorizUpscaleInterp; + } else { + scalingFunc = &BasicImageScaler::vertDownscaleHorizUpscaleNoInterp; + } + } } else { - scanBuf2 = NULL; + // vertical upscaling + yp = scaledHeight / srcHeight; + yq = scaledHeight % srcHeight; + yt = 0; + yn = 0; + if (aInterpolate) { + yInvScale = (SplashCoord)srcHeight / (SplashCoord)scaledHeight; + colorTmpBuf0 = (Guchar *)gmallocn(srcWidth, nComps); + colorTmpBuf1 = (Guchar *)gmallocn(srcWidth, nComps); + if (hasAlpha) { + alphaTmpBuf0 = (Guchar *)gmalloc(srcWidth); + alphaTmpBuf1 = (Guchar *)gmalloc(srcWidth); + } + ySrcCur = 0; + yScaledCur = 0; + if (scaledWidth <= srcWidth) { + scalingFunc = &BasicImageScaler::vertUpscaleHorizDownscaleInterp; + } else { + colorTmpBuf2 = (Guchar *)gmallocn(srcWidth, nComps); + if (hasAlpha) { + alphaTmpBuf2 = (Guchar *)gmalloc(srcWidth); + } + scalingFunc = &BasicImageScaler::vertUpscaleHorizUpscaleInterp; + } + } else { + colorTmpBuf0 = (Guchar *)gmallocn(srcWidth, nComps); + if (hasAlpha) { + alphaTmpBuf0 = (Guchar *)gmalloc(srcWidth); + } + if (scaledWidth <= srcWidth) { + scalingFunc = &BasicImageScaler::vertUpscaleHorizDownscaleNoInterp; + } else { + scalingFunc = &BasicImageScaler::vertUpscaleHorizUpscaleNoInterp; + } + } } - groupBackBitmap = NULL; - minLineWidth = 0; - clearModRegion(); - debugMode = gFalse; - - if (imageCacheA) { - imageCache = imageCacheA; - imageCache->incRefCount(); + if (scaledWidth <= srcWidth) { + xp = srcWidth / scaledWidth; + xq = srcWidth % scaledWidth; } else { - imageCache = new SplashImageCache(); + xp = scaledWidth / srcWidth; + xq = scaledWidth % srcWidth; + if (aInterpolate) { + xInvScale = (SplashCoord)srcWidth / (SplashCoord)scaledWidth; + } + } + colorLine = (Guchar *)gmallocn(scaledWidth, nComps); + if (hasAlpha) { + alphaLine = (Guchar *)gmalloc(scaledWidth); } } -Splash::~Splash() { - imageCache->decRefCount(); +BasicImageScaler::~BasicImageScaler() { + gfree(colorTmpBuf0); + gfree(colorTmpBuf1); + gfree(colorTmpBuf2); + gfree(alphaTmpBuf0); + gfree(alphaTmpBuf1); + gfree(alphaTmpBuf2); + gfree(colorAccBuf); + gfree(alphaAccBuf); + gfree(colorLine); + gfree(alphaLine); +} - while (state->next) { - restoreState(); - } - delete state; - gfree(scanBuf); - gfree(scanBuf2); +void BasicImageScaler::nextLine() { + (this->*scalingFunc)(); } -//------------------------------------------------------------------------ -// state read -//------------------------------------------------------------------------ +void BasicImageScaler::vertDownscaleHorizDownscale() { + //--- vert downscale + int yStep = yp; + yt += yq; + if (yt >= scaledHeight) { + yt -= scaledHeight; + ++yStep; + } + memset(colorAccBuf, 0, (srcWidth * nComps) * sizeof(Guint)); + if (hasAlpha) { + memset(alphaAccBuf, 0, srcWidth * sizeof(Guint)); + } + int nRowComps = srcWidth * nComps; + for (int i = 0; i < yStep; ++i) { + (*src)(srcData, colorTmpBuf0, alphaTmpBuf0); + for (int j = 0; j < nRowComps; ++j) { + colorAccBuf[j] += colorTmpBuf0[j]; + } + if (hasAlpha) { + for (int j = 0; j < srcWidth; ++j) { + alphaAccBuf[j] += alphaTmpBuf0[j]; + } + } + } -SplashCoord *Splash::getMatrix() { - return state->matrix; -} + //--- horiz downscale + int colorAcc[splashMaxColorComps]; + int xt = 0; + int unscaledColorIdx = 0; + int unscaledAlphaIdx = 0; + int scaledColorIdx = 0; -SplashPattern *Splash::getStrokePattern() { - return state->strokePattern; -} + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + int xStep = xp; + xt += xq; + if (xt >= scaledWidth) { + xt -= scaledWidth; + ++xStep; + } -SplashPattern *Splash::getFillPattern() { - return state->fillPattern; -} + for (int j = 0; j < nComps; ++j) { + colorAcc[j] = 0; + } + for (int i = 0; i < xStep; ++i) { + for (int j = 0; j < nComps; ++j) { + colorAcc[j] += colorAccBuf[unscaledColorIdx + j]; + } + unscaledColorIdx += nComps; + } + int nPixels = yStep * xStep; + for (int j = 0; j < nComps; ++j) { + colorLine[scaledColorIdx + j] = (Guchar)(colorAcc[j] / nPixels); + } + scaledColorIdx += nComps; -SplashScreen *Splash::getScreen() { - return state->screen; + if (hasAlpha) { + int alphaAcc = 0; + for (int i = 0; i < xStep; ++i) { + alphaAcc += alphaAccBuf[unscaledAlphaIdx]; + ++unscaledAlphaIdx; + } + alphaLine[scaledIdx] = (Guchar)(alphaAcc / nPixels); + } + } } -SplashBlendFunc Splash::getBlendFunc() { - return state->blendFunc; -} +void BasicImageScaler::vertDownscaleHorizUpscaleNoInterp() { + //--- vert downscale + int yStep = yp; + yt += yq; + if (yt >= scaledHeight) { + yt -= scaledHeight; + ++yStep; + } + memset(colorAccBuf, 0, (srcWidth * nComps) * sizeof(Guint)); + if (hasAlpha) { + memset(alphaAccBuf, 0, srcWidth * sizeof(Guint)); + } + int nRowComps = srcWidth * nComps; + for (int i = 0; i < yStep; ++i) { + (*src)(srcData, colorTmpBuf0, alphaTmpBuf0); + for (int j = 0; j < nRowComps; ++j) { + colorAccBuf[j] += colorTmpBuf0[j]; + } + if (hasAlpha) { + for (int j = 0; j < srcWidth; ++j) { + alphaAccBuf[j] += alphaTmpBuf0[j]; + } + } + } -SplashCoord Splash::getStrokeAlpha() { - return state->strokeAlpha; + //--- horiz upscale + Guchar colorBuf[splashMaxColorComps]; + int xt = 0; + int scaledColorIdx = 0; + int srcColorIdx = 0; + int scaledAlphaIdx = 0; + for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) { + int xStep = xp; + xt += xq; + if (xt >= srcWidth) { + xt -= srcWidth; + ++xStep; + } + for (int j = 0; j < nComps; ++j) { + colorBuf[j] = (Guchar)(colorAccBuf[srcColorIdx + j] / yStep); + } + srcColorIdx += nComps; + for (int i = 0; i < xStep; ++i) { + for (int j = 0; j < nComps; ++j) { + colorLine[scaledColorIdx + j] = colorBuf[j]; + } + scaledColorIdx += nComps; + } + if (hasAlpha) { + Guchar alphaBuf = (Guchar)(alphaAccBuf[srcIdx] / yStep); + for (int i = 0; i < xStep; ++i) { + alphaLine[scaledAlphaIdx] = alphaBuf; + ++scaledAlphaIdx; + } + } + } } -SplashCoord Splash::getFillAlpha() { - return state->fillAlpha; -} +void BasicImageScaler::vertDownscaleHorizUpscaleInterp() { + //--- vert downscale + int yStep = yp; + yt += yq; + if (yt >= scaledHeight) { + yt -= scaledHeight; + ++yStep; + } + memset(colorAccBuf, 0, (srcWidth * nComps) * sizeof(Guint)); + if (hasAlpha) { + memset(alphaAccBuf, 0, srcWidth * sizeof(Guint)); + } + int nRowComps = srcWidth * nComps; + for (int i = 0; i < yStep; ++i) { + (*src)(srcData, colorTmpBuf0, alphaTmpBuf0); + for (int j = 0; j < nRowComps; ++j) { + colorAccBuf[j] += colorTmpBuf0[j]; + } + if (hasAlpha) { + for (int j = 0; j < srcWidth; ++j) { + alphaAccBuf[j] += alphaTmpBuf0[j]; + } + } + } + for (int j = 0; j < srcWidth * nComps; ++j) { + colorAccBuf[j] /= yStep; + } + if (hasAlpha) { + for (int j = 0; j < srcWidth; ++j) { + alphaAccBuf[j] /= yStep; + } + } -SplashCoord Splash::getLineWidth() { - return state->lineWidth; + //--- horiz upscale + int scaledColorIdx = 0; + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale; + int x0 = splashFloor(xs - 0.5); + int x1 = x0 + 1; + SplashCoord s0 = (SplashCoord)x1 + 0.5 - xs; + SplashCoord s1 = (SplashCoord)1 - s0; + if (x0 < 0) { + x0 = 0; + } + if (x1 >= srcWidth) { + x1 = srcWidth - 1; + } + for (int j = 0; j < nComps; ++j) { + colorLine[scaledColorIdx + j] = + (Guchar)(int)(s0 * colorAccBuf[x0 * nComps + j] + + s1 * colorAccBuf[x1 * nComps + j]); + } + scaledColorIdx += nComps; + if (hasAlpha) { + alphaLine[scaledIdx] = (Guchar)(int)(s0 * alphaAccBuf[x0] + + s1 * alphaAccBuf[x1]); + } + } } -int Splash::getLineCap() { - return state->lineCap; -} +void BasicImageScaler::vertUpscaleHorizDownscaleNoInterp() { + //--- vert upscale + if (yn == 0) { + yn = yp; + yt += yq; + if (yt >= srcHeight) { + yt -= srcHeight; + ++yn; + } + (*src)(srcData, colorTmpBuf0, alphaTmpBuf0); + } + --yn; -int Splash::getLineJoin() { - return state->lineJoin; -} + //--- horiz downscale + int colorAcc[splashMaxColorComps]; + int xt = 0; + int unscaledColorIdx = 0; + int unscaledAlphaIdx = 0; + int scaledColorIdx = 0; + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + int xStep = xp; + xt += xq; + if (xt >= scaledWidth) { + xt -= scaledWidth; + ++xStep; + } -SplashCoord Splash::getMiterLimit() { - return state->miterLimit; -} + for (int j = 0; j < nComps; ++j) { + colorAcc[j] = 0; + } + for (int i = 0; i < xStep; ++i) { + for (int j = 0; j < nComps; ++j) { + colorAcc[j] += colorTmpBuf0[unscaledColorIdx + j]; + } + unscaledColorIdx += nComps; + } + for (int j = 0; j < nComps; ++j) { + colorLine[scaledColorIdx + j] = (Guchar)(colorAcc[j] / xStep); + } + scaledColorIdx += nComps; -SplashCoord Splash::getFlatness() { - return state->flatness; + if (hasAlpha) { + int alphaAcc = 0; + for (int i = 0; i < xStep; ++i) { + alphaAcc += alphaTmpBuf0[unscaledAlphaIdx]; + ++unscaledAlphaIdx; + } + alphaLine[scaledIdx] = (Guchar)(alphaAcc / xStep); + } + } } -SplashCoord *Splash::getLineDash() { - return state->lineDash; -} +void BasicImageScaler::vertUpscaleHorizDownscaleInterp() { + //--- vert upscale + if (ySrcCur == 0) { + (*src)(srcData, colorTmpBuf0, alphaTmpBuf0); + (*src)(srcData, colorTmpBuf1, alphaTmpBuf1); + ySrcCur = 1; + } + SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale; + int y0 = splashFloor(ys - 0.5); + int y1 = y0 + 1; + SplashCoord vs0 = (SplashCoord)y1 + 0.5 - ys; + SplashCoord vs1 = (SplashCoord)1 - vs0; + if (y1 > ySrcCur && ySrcCur < srcHeight - 1) { + Guchar *t = colorTmpBuf0; + colorTmpBuf0 = colorTmpBuf1; + colorTmpBuf1 = t; + if (hasAlpha) { + t = alphaTmpBuf0; + alphaTmpBuf0 = alphaTmpBuf1; + alphaTmpBuf1 = t; + } + (*src)(srcData, colorTmpBuf1, alphaTmpBuf1); + ++ySrcCur; + } + Guchar *color0 = colorTmpBuf0; + Guchar *color1 = colorTmpBuf1; + Guchar *alpha0 = alphaTmpBuf0; + Guchar *alpha1 = alphaTmpBuf1; + if (y0 < 0) { + y0 = 0; + color1 = color0; + alpha1 = alpha0; + } + if (y1 >= srcHeight) { + y1 = srcHeight - 1; + color0 = color1; + alpha0 = alpha1; + } + ++yScaledCur; -int Splash::getLineDashLength() { - return state->lineDashLength; -} + //--- horiz downscale + int colorAcc[splashMaxColorComps]; + int xt = 0; + int unscaledColorIdx = 0; + int unscaledAlphaIdx = 0; + int scaledColorIdx = 0; + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + int xStep = xp; + xt += xq; + if (xt >= scaledWidth) { + xt -= scaledWidth; + ++xStep; + } -SplashCoord Splash::getLineDashPhase() { - return state->lineDashPhase; -} + for (int j = 0; j < nComps; ++j) { + colorAcc[j] = 0; + } + for (int i = 0; i < xStep; ++i) { + for (int j = 0; j < nComps; ++j) { + colorAcc[j] += (int)(vs0 * (int)color0[unscaledColorIdx + j] + + vs1 * (int)color1[unscaledColorIdx + j]); + } + unscaledColorIdx += nComps; + } + for (int j = 0; j < nComps; ++j) { + colorLine[scaledColorIdx + j] = (Guchar)(colorAcc[j] / xStep); + } + scaledColorIdx += nComps; -SplashStrokeAdjustMode Splash::getStrokeAdjust() { - return state->strokeAdjust; + if (hasAlpha) { + int alphaAcc = 0; + for (int i = 0; i < xStep; ++i) { + alphaAcc += (int)(vs0 * (int)alpha0[unscaledAlphaIdx] + + vs1 * (int)alpha1[unscaledAlphaIdx]); + ++unscaledAlphaIdx; + } + alphaLine[scaledIdx] = (Guchar)(alphaAcc / xStep); + } + } } -SplashClip *Splash::getClip() { - return state->clip; -} +void BasicImageScaler::vertUpscaleHorizUpscaleNoInterp() { + //--- vert upscale + if (yn == 0) { + yn = yp; + yt += yq; + if (yt >= srcHeight) { + yt -= srcHeight; + ++yn; + } + (*src)(srcData, colorTmpBuf0, alphaTmpBuf0); + } + --yn; -SplashBitmap *Splash::getSoftMask() { - return state->softMask; + //--- horiz upscale + int xt = 0; + int scaledColorIdx = 0; + int srcColorIdx = 0; + int scaledAlphaIdx = 0; + for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) { + int xStep = xp; + xt += xq; + if (xt >= srcWidth) { + xt -= srcWidth; + ++xStep; + } + for (int i = 0; i < xStep; ++i) { + for (int j = 0; j < nComps; ++j) { + colorLine[scaledColorIdx + j] = colorTmpBuf0[srcColorIdx + j]; + } + scaledColorIdx += nComps; + } + srcColorIdx += nComps; + if (hasAlpha) { + Guchar alphaBuf = alphaTmpBuf0[srcIdx]; + for (int i = 0; i < xStep; ++i) { + alphaLine[scaledAlphaIdx] = alphaBuf; + ++scaledAlphaIdx; + } + } + } } -GBool Splash::getInNonIsolatedGroup() { - return state->inNonIsolatedGroup; -} +void BasicImageScaler::vertUpscaleHorizUpscaleInterp() { + //--- vert upscale + if (ySrcCur == 0) { + (*src)(srcData, colorTmpBuf0, alphaTmpBuf0); + (*src)(srcData, colorTmpBuf1, alphaTmpBuf1); + ySrcCur = 1; + } + SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale; + int y0 = splashFloor(ys - 0.5); + int y1 = y0 + 1; + SplashCoord vs0 = (SplashCoord)y1 + 0.5 - ys; + SplashCoord vs1 = (SplashCoord)1 - vs0; + if (y1 > ySrcCur && ySrcCur < srcHeight - 1) { + Guchar *t = colorTmpBuf0; + colorTmpBuf0 = colorTmpBuf1; + colorTmpBuf1 = t; + if (hasAlpha) { + t = alphaTmpBuf0; + alphaTmpBuf0 = alphaTmpBuf1; + alphaTmpBuf1 = t; + } + (*src)(srcData, colorTmpBuf1, alphaTmpBuf1); + ++ySrcCur; + } + Guchar *color0 = colorTmpBuf0; + Guchar *color1 = colorTmpBuf1; + Guchar *alpha0 = alphaTmpBuf0; + Guchar *alpha1 = alphaTmpBuf1; + if (y0 < 0) { + y0 = 0; + color1 = color0; + alpha1 = alpha0; + } + if (y1 >= srcHeight) { + y1 = srcHeight - 1; + color0 = color1; + alpha0 = alpha1; + } + ++yScaledCur; + for (int srcIdx = 0; srcIdx < srcWidth * nComps; ++srcIdx) { + colorTmpBuf2[srcIdx] = (Guchar)(int)(vs0 * (int)color0[srcIdx] + + vs1 * (int)color1[srcIdx]); + } + if (hasAlpha) { + for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) { + alphaTmpBuf2[srcIdx] = (Guchar)(int)(vs0 * (int)alpha0[srcIdx] + + vs1 * (int)alpha1[srcIdx]); + } + } -GBool Splash::getInKnockoutGroup() { - return state->inKnockoutGroup; + //--- horiz upscale + int scaledColorIdx = 0; + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale; + int x0 = splashFloor(xs - 0.5); + int x1 = x0 + 1; + SplashCoord hs0 = (SplashCoord)x1 + 0.5 - xs; + SplashCoord hs1 = (SplashCoord)1 - hs0; + if (x0 < 0) { + x0 = 0; + } + if (x1 >= srcWidth) { + x1 = srcWidth - 1; + } + for (int j = 0; j < nComps; ++j) { + colorLine[scaledColorIdx + j] = + (Guchar)(int)(hs0 * (int)colorTmpBuf2[x0 * nComps + j] + + hs1 * (int)colorTmpBuf2[x1 * nComps + j]); + } + scaledColorIdx += nComps; + if (hasAlpha) { + alphaLine[scaledIdx] = (Guchar)(int)(hs0 * (int)alphaTmpBuf2[x0] + + hs1 * (int)alphaTmpBuf2[x1]); + } + } } //------------------------------------------------------------------------ -// state write +// SavingImageScaler //------------------------------------------------------------------------ -void Splash::setMatrix(SplashCoord *matrix) { - memcpy(state->matrix, matrix, 6 * sizeof(SplashCoord)); -} +// Wrapper around BasicImageScaler that saves the scaled image for use +// by ReplayImageScaler. +class SavingImageScaler: public BasicImageScaler { +public: -void Splash::setStrokePattern(SplashPattern *strokePattern) { - state->setStrokePattern(strokePattern); -} + SavingImageScaler(SplashImageSource aSrc, void *aSrcData, + int aSrcWidth, int aSrcHeight, int aNComps, GBool aHasAlpha, + int aScaledWidth, int aScaledHeight, GBool aInterpolate, + Guchar *aColorCache, Guchar *aAlphaCache); + virtual void nextLine(); -void Splash::setFillPattern(SplashPattern *fillPattern) { - state->setFillPattern(fillPattern); -} +private: -void Splash::setScreen(SplashScreen *screen) { - state->setScreen(screen); -} + Guchar *colorPtr; + Guchar *alphaPtr; +}; -void Splash::setBlendFunc(SplashBlendFunc func) { - state->blendFunc = func; +SavingImageScaler::SavingImageScaler(SplashImageSource aSrc, void *aSrcData, + int aSrcWidth, int aSrcHeight, + int aNComps, GBool aHasAlpha, + int aScaledWidth, int aScaledHeight, + GBool aInterpolate, + Guchar *aColorCache, Guchar *aAlphaCache): + BasicImageScaler(aSrc, aSrcData, aSrcWidth, aSrcHeight, aNComps, aHasAlpha, + aScaledWidth, aScaledHeight, aInterpolate) +{ + colorPtr = aColorCache; + alphaPtr = aAlphaCache; } -void Splash::setStrokeAlpha(SplashCoord alpha) { - state->strokeAlpha = alpha; +void SavingImageScaler::nextLine() { + BasicImageScaler::nextLine(); + memcpy(colorPtr, colorData(), scaledWidth * nComps); + colorPtr += scaledWidth * nComps; + if (hasAlpha) { + memcpy(alphaPtr, alphaData(), scaledWidth); + alphaPtr += scaledWidth; + } } -void Splash::setFillAlpha(SplashCoord alpha) { - state->fillAlpha = alpha; -} +//------------------------------------------------------------------------ +// ReplayImageScaler +//------------------------------------------------------------------------ -void Splash::setLineWidth(SplashCoord lineWidth) { - state->lineWidth = lineWidth; +// "Replay" a scaled image saved by SavingImageScaler. +class ReplayImageScaler: public ImageScaler { +public: + + ReplayImageScaler(int aNComps, GBool aHasAlpha, + int aScaledWidth, + Guchar *aColorCache, Guchar *aAlphaCache); + virtual void nextLine(); + virtual Guchar *colorData() { return colorLine; } + virtual Guchar *alphaData() { return alphaLine; } + +private: + + int nComps; + GBool hasAlpha; + int scaledWidth; + Guchar *colorPtr; + Guchar *alphaPtr; + Guchar *colorLine; + Guchar *alphaLine; +}; + +ReplayImageScaler::ReplayImageScaler(int aNComps, GBool aHasAlpha, + int aScaledWidth, + Guchar *aColorCache, Guchar *aAlphaCache) { + nComps = aNComps; + hasAlpha = aHasAlpha; + scaledWidth = aScaledWidth; + colorPtr = aColorCache; + alphaPtr = aAlphaCache; + colorLine = NULL; + alphaLine = NULL; } -void Splash::setLineCap(int lineCap) { - if (lineCap >= 0 && lineCap <= 2) { - state->lineCap = lineCap; - } else { - state->lineCap = 0; +void ReplayImageScaler::nextLine() { + colorLine = colorPtr; + alphaLine = alphaPtr; + colorPtr += scaledWidth * nComps; + if (hasAlpha) { + alphaPtr += scaledWidth; } } -void Splash::setLineJoin(int lineJoin) { - if (lineJoin >= 0 && lineJoin <= 2) { - state->lineJoin = lineJoin; - } else { - state->lineJoin = 0; - } -} - -void Splash::setMiterLimit(SplashCoord miterLimit) { - state->miterLimit = miterLimit; -} - -void Splash::setFlatness(SplashCoord flatness) { - if (flatness < 1) { - state->flatness = 1; - } else { - state->flatness = flatness; - } -} - -void Splash::setLineDash(SplashCoord *lineDash, int lineDashLength, - SplashCoord lineDashPhase) { - state->setLineDash(lineDash, lineDashLength, lineDashPhase); -} - -void Splash::setStrokeAdjust(SplashStrokeAdjustMode strokeAdjust) { - state->strokeAdjust = strokeAdjust; -} - -void Splash::clipResetToRect(SplashCoord x0, SplashCoord y0, - SplashCoord x1, SplashCoord y1) { - state->clipResetToRect(x0, y0, x1, y1); -} +//------------------------------------------------------------------------ +// ImageMaskScaler +//------------------------------------------------------------------------ -SplashError Splash::clipToRect(SplashCoord x0, SplashCoord y0, - SplashCoord x1, SplashCoord y1) { - return state->clipToRect(x0, y0, x1, y1); -} +class ImageMaskScaler { +public: -SplashError Splash::clipToPath(SplashPath *path, GBool eo) { - return state->clipToPath(path, eo); -} + // Set up a MaskScaler to scale from [srcWidth]x[srcHeight] to + // [scaledWidth]x[scaledHeight]. + ImageMaskScaler(SplashImageMaskSource aSrc, void *aSrcData, + int aSrcWidth, int aSrcHeight, + int aScaledWidth, int aScaledHeight, GBool aInterpolate); -void Splash::setSoftMask(SplashBitmap *softMask) { - state->setSoftMask(softMask); -} + ~ImageMaskScaler(); -void Splash::setInTransparencyGroup(SplashBitmap *groupBackBitmapA, - int groupBackXA, int groupBackYA, - GBool nonIsolated, GBool knockout) { - groupBackBitmap = groupBackBitmapA; - groupBackX = groupBackXA; - groupBackY = groupBackYA; - state->inNonIsolatedGroup = nonIsolated; - state->inKnockoutGroup = knockout; -} + // Compute the next line of the scaled image mask. This can be + // called up to [scaledHeight] times. + void nextLine(); -void Splash::setTransfer(Guchar *red, Guchar *green, Guchar *blue, - Guchar *gray) { - state->setTransfer(red, green, blue, gray); -} + // Retrieve the data generated by the most recent call to + // nextLine(). + Guchar *data() { return line; } -void Splash::setOverprintMask(Guint overprintMask) { - state->overprintMask = overprintMask; -} +private: + void vertDownscaleHorizDownscale(); + void vertDownscaleHorizUpscaleNoInterp(); + void vertDownscaleHorizUpscaleInterp(); + void vertUpscaleHorizDownscaleNoInterp(); + void vertUpscaleHorizDownscaleInterp(); + void vertUpscaleHorizUpscaleNoInterp(); + void vertUpscaleHorizUpscaleInterp(); -void Splash::setEnablePathSimplification(GBool en) { - state->enablePathSimplification = en; -} + // source image data function + SplashImageMaskSource src; + void *srcData; -//------------------------------------------------------------------------ -// state save/restore -//------------------------------------------------------------------------ + // source image size + int srcWidth; + int srcHeight; -void Splash::saveState() { - SplashState *newState; + // scaled image size + int scaledWidth; + int scaledHeight; - newState = state->copy(); - newState->next = state; - state = newState; -} + // params/state for vertical scaling + int yp, yq; + int yt, yn; + int ySrcCur, yScaledCur; + SplashCoord yInvScale; -SplashError Splash::restoreState() { - SplashState *oldState; + // params for horizontal scaling + int xp, xq; + SplashCoord xInvScale; - if (!state->next) { - return splashErrNoSave; - } - oldState = state; - state = state->next; - delete oldState; - return splashOk; -} + // vertical and horizontal scaling functions + void (ImageMaskScaler::*scalingFunc)(); -//------------------------------------------------------------------------ -// drawing operations -//------------------------------------------------------------------------ + // temporary buffers for vertical scaling + Guchar *tmpBuf0; + Guchar *tmpBuf1; + Guchar *tmpBuf2; + Guint *accBuf; -void Splash::clear(SplashColorPtr color, Guchar alpha) { - SplashColorPtr row, p; - Guchar mono; - int x, y; + // output of horizontal scaling + Guchar *line; +}; - switch (bitmap->mode) { - case splashModeMono1: - mono = (color[0] & 0x80) ? 0xff : 0x00; - if (bitmap->rowSize < 0) { - memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), - mono, -bitmap->rowSize * bitmap->height); - } else { - memset(bitmap->data, mono, bitmap->rowSize * bitmap->height); - } - break; - case splashModeMono8: - if (bitmap->rowSize < 0) { - memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), - color[0], -bitmap->rowSize * bitmap->height); +ImageMaskScaler::ImageMaskScaler(SplashImageMaskSource aSrc, void *aSrcData, + int aSrcWidth, int aSrcHeight, + int aScaledWidth, int aScaledHeight, + GBool aInterpolate) { + tmpBuf0 = NULL; + tmpBuf1 = NULL; + tmpBuf2 = NULL; + accBuf = NULL; + line = NULL; + + src = aSrc; + srcData = aSrcData; + srcWidth = aSrcWidth; + srcHeight = aSrcHeight; + scaledWidth = aScaledWidth; + scaledHeight = aScaledHeight; + + // select scaling function; allocate buffers + if (scaledHeight <= srcHeight) { + // vertical downscaling + yp = srcHeight / scaledHeight; + yq = srcHeight % scaledHeight; + yt = 0; + tmpBuf0 = (Guchar *)gmalloc(srcWidth); + accBuf = (Guint *)gmallocn(srcWidth, sizeof(Guint)); + if (scaledWidth <= srcWidth) { + scalingFunc = &ImageMaskScaler::vertDownscaleHorizDownscale; } else { - memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); - } - break; - case splashModeRGB8: - if (color[0] == color[1] && color[1] == color[2]) { - if (bitmap->rowSize < 0) { - memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), - color[0], -bitmap->rowSize * bitmap->height); + if (aInterpolate) { + scalingFunc = &ImageMaskScaler::vertDownscaleHorizUpscaleInterp; } else { - memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); - } - } else { - row = bitmap->data; - for (y = 0; y < bitmap->height; ++y) { - p = row; - for (x = 0; x < bitmap->width; ++x) { - *p++ = color[0]; - *p++ = color[1]; - *p++ = color[2]; - } - row += bitmap->rowSize; + scalingFunc = &ImageMaskScaler::vertDownscaleHorizUpscaleNoInterp; } } - break; - case splashModeBGR8: - if (color[0] == color[1] && color[1] == color[2]) { - if (bitmap->rowSize < 0) { - memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), - color[0], -bitmap->rowSize * bitmap->height); + } else { + // vertical upscaling + yp = scaledHeight / srcHeight; + yq = scaledHeight % srcHeight; + yt = 0; + yn = 0; + if (aInterpolate) { + yInvScale = (SplashCoord)srcHeight / (SplashCoord)scaledHeight; + tmpBuf0 = (Guchar *)gmalloc(srcWidth); + tmpBuf1 = (Guchar *)gmalloc(srcWidth); + ySrcCur = 0; + yScaledCur = 0; + if (scaledWidth <= srcWidth) { + scalingFunc = &ImageMaskScaler::vertUpscaleHorizDownscaleInterp; } else { - memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); + tmpBuf2 = (Guchar *)gmalloc(srcWidth); + scalingFunc = &ImageMaskScaler::vertUpscaleHorizUpscaleInterp; } } else { - row = bitmap->data; - for (y = 0; y < bitmap->height; ++y) { - p = row; - for (x = 0; x < bitmap->width; ++x) { - *p++ = color[2]; - *p++ = color[1]; - *p++ = color[0]; - } - row += bitmap->rowSize; - } - } - break; -#if SPLASH_CMYK - case splashModeCMYK8: - if (color[0] == color[1] && color[1] == color[2] && color[2] == color[3]) { - if (bitmap->rowSize < 0) { - memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), - color[0], -bitmap->rowSize * bitmap->height); + tmpBuf0 = (Guchar *)gmalloc(srcWidth); + if (scaledWidth <= srcWidth) { + scalingFunc = &ImageMaskScaler::vertUpscaleHorizDownscaleNoInterp; } else { - memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); - } - } else { - row = bitmap->data; - for (y = 0; y < bitmap->height; ++y) { - p = row; - for (x = 0; x < bitmap->width; ++x) { - *p++ = color[0]; - *p++ = color[1]; - *p++ = color[2]; - *p++ = color[3]; - } - row += bitmap->rowSize; + scalingFunc = &ImageMaskScaler::vertUpscaleHorizUpscaleNoInterp; } } - break; -#endif } - - if (bitmap->alpha) { - memset(bitmap->alpha, alpha, bitmap->alphaRowSize * bitmap->height); + if (scaledWidth <= srcWidth) { + xp = srcWidth / scaledWidth; + xq = srcWidth % scaledWidth; + } else { + xp = scaledWidth / srcWidth; + xq = scaledWidth % srcWidth; + if (aInterpolate) { + xInvScale = (SplashCoord)srcWidth / (SplashCoord)scaledWidth; + } } + line = (Guchar *)gmalloc(scaledWidth); +} - updateModX(0); - updateModY(0); - updateModX(bitmap->width - 1); - updateModY(bitmap->height - 1); +ImageMaskScaler::~ImageMaskScaler() { + gfree(tmpBuf0); + gfree(tmpBuf1); + gfree(tmpBuf2); + gfree(accBuf); + gfree(line); } -SplashError Splash::stroke(SplashPath *path) { - SplashPath *path2, *dPath; - SplashCoord t0, t1, t2, t3, w, w2, lineDashMax, lineDashTotal; - int lineCap, lineJoin, i; +void ImageMaskScaler::nextLine() { + (this->*scalingFunc)(); +} - if (debugMode) { - printf("stroke [dash:%d] [width:%.2f]:\n", - state->lineDashLength, (double)state->lineWidth); - dumpPath(path); - } - opClipRes = splashClipAllOutside; - if (path->length == 0) { - return splashErrEmptyPath; +void ImageMaskScaler::vertDownscaleHorizDownscale() { + //--- vert downscale + int yStep = yp; + yt += yq; + if (yt >= scaledHeight) { + yt -= scaledHeight; + ++yStep; } - path2 = flattenPath(path, state->matrix, state->flatness); - - // Compute an approximation of the transformed line width. - // Given a CTM of [m0 m1], - // [m2 m3] - // if |m0|*|m3| >= |m1|*|m2| then use min{|m0|,|m3|}, else - // use min{|m1|,|m2|}. - // This handles the common cases -- [s 0 ] and [0 s] -- - // [0 +/-s] [+/-s 0] - // well, and still does something reasonable for the uncommon - // case transforms. - t0 = splashAbs(state->matrix[0]); - t1 = splashAbs(state->matrix[1]); - t2 = splashAbs(state->matrix[2]); - t3 = splashAbs(state->matrix[3]); - if (t0 * t3 >= t1 * t2) { - w = (t0 < t3) ? t0 : t3; - } else { - w = (t1 < t2) ? t1 : t2; + memset(accBuf, 0, srcWidth * sizeof(Guint)); + for (int i = 0; i < yStep; ++i) { + (*src)(srcData, tmpBuf0); + for (int j = 0; j < srcWidth; ++j) { + accBuf[j] += tmpBuf0[j]; + } } - w2 = w * state->lineWidth; - - // construct the dashed path - if (state->lineDashLength > 0) { - // check the maximum transformed dash element length (using the - // same approximation as for line width) -- if it's less than 0.1 - // pixel, don't apply the dash pattern; this avoids a huge - // performance/memory hit with PDF files that use absurd dash - // patterns like [0.0007 0.0003] - lineDashTotal = 0; - lineDashMax = 0; - for (i = 0; i < state->lineDashLength; ++i) { - lineDashTotal += state->lineDash[i]; - if (state->lineDash[i] > lineDashMax) { - lineDashMax = state->lineDash[i]; - } + //--- horiz downscale + int acc; + int xt = 0; + int unscaledIdx = 0; + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + int xStep = xp; + xt += xq; + if (xt >= scaledWidth) { + xt -= scaledWidth; + ++xStep; } - // Acrobat simply draws nothing if the dash array is [0] - if (lineDashTotal == 0) { - delete path2; - return splashOk; + + acc = 0; + for (int i = 0; i < xStep; ++i) { + acc += accBuf[unscaledIdx]; + ++unscaledIdx; } - if (w * lineDashMax > 0.1) { + line[scaledIdx] = (Guchar)((255 * acc) / (xStep * yStep)); + } +} - dPath = makeDashedPath(path2); - delete path2; - path2 = dPath; - if (path2->length == 0) { - delete path2; - return splashErrEmptyPath; - } +void ImageMaskScaler::vertDownscaleHorizUpscaleNoInterp() { + //--- vert downscale + int yStep = yp; + yt += yq; + if (yt >= scaledHeight) { + yt -= scaledHeight; + ++yStep; + } + memset(accBuf, 0, srcWidth * sizeof(Guint)); + for (int i = 0; i < yStep; ++i) { + (*src)(srcData, tmpBuf0); + for (int j = 0; j < srcWidth; ++j) { + accBuf[j] += tmpBuf0[j]; } } - // round caps on narrow lines look bad, and can't be - // stroke-adjusted, so use projecting caps instead (but we can't do - // this if there are zero-length dashes or segments, because those - // turn into round dots) - lineCap = state->lineCap; - lineJoin = state->lineJoin; - if (state->strokeAdjust == splashStrokeAdjustCAD && - w2 < 3.5) { - if (lineCap == splashLineCapRound && - !state->lineDashContainsZeroLengthDashes() && - !path->containsZeroLengthSubpaths()) { - lineCap = splashLineCapProjecting; + //--- horiz upscale + int xt = 0; + int scaledIdx = 0; + for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) { + int xStep = xp; + xt += xq; + if (xt >= srcWidth) { + xt -= srcWidth; + ++xStep; } - if (lineJoin == splashLineJoinRound) { - lineJoin = splashLineJoinBevel; + Guchar buf = (Guchar)((255 * accBuf[srcIdx]) / yStep); + for (int i = 0; i < xStep; ++i) { + line[scaledIdx] = buf; + ++scaledIdx; } } +} - // if there is a min line width set, and the transformed line width - // is smaller, use the min line width - if (w > 0 && w2 < minLineWidth) { - strokeWide(path2, minLineWidth / w, splashLineCapButt, splashLineJoinBevel); - } else if (bitmap->mode == splashModeMono1 || !vectorAntialias) { - // in monochrome mode or if antialiasing is disabled, use 0-width - // lines for any transformed line width <= 1 -- lines less than 1 - // pixel wide look too fat without antialiasing - if (w2 < 1.001) { - strokeNarrow(path2); - } else { - strokeWide(path2, state->lineWidth, lineCap, lineJoin); - } - } else { - // in gray and color modes, only use 0-width lines if the line - // width is explicitly set to 0 - if (state->lineWidth == 0) { - strokeNarrow(path2); - } else { - strokeWide(path2, state->lineWidth, lineCap, lineJoin); +void ImageMaskScaler::vertDownscaleHorizUpscaleInterp() { + //--- vert downscale + int yStep = yp; + yt += yq; + if (yt >= scaledHeight) { + yt -= scaledHeight; + ++yStep; + } + memset(accBuf, 0, srcWidth * sizeof(Guint)); + for (int i = 0; i < yStep; ++i) { + (*src)(srcData, tmpBuf0); + for (int j = 0; j < srcWidth; ++j) { + accBuf[j] += tmpBuf0[j]; } } + for (int j = 0; j < srcWidth; ++j) { + accBuf[j] = (255 * accBuf[j]) / yStep; + } - delete path2; - return splashOk; + //--- horiz upscale + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale; + int x0 = splashFloor(xs - 0.5); + int x1 = x0 + 1; + SplashCoord s0 = (SplashCoord)x1 + 0.5 - xs; + SplashCoord s1 = (SplashCoord)1 - s0; + if (x0 < 0) { + x0 = 0; + } + if (x1 >= srcWidth) { + x1 = srcWidth - 1; + } + line[scaledIdx] = (Guchar)(int)(s0 * accBuf[x0] + s1 * accBuf[x1]); + } } -void Splash::strokeNarrow(SplashPath *path) { - SplashPipe pipe; - SplashXPath *xPath; - SplashXPathSeg *seg; - int x0, x1, y0, y1, xa, xb, y; - SplashCoord dxdy; - SplashClipResult clipRes; - int nClipRes[3]; - int i; +void ImageMaskScaler::vertUpscaleHorizDownscaleNoInterp() { + //--- vert upscale + if (yn == 0) { + yn = yp; + yt += yq; + if (yt >= srcHeight) { + yt -= srcHeight; + ++yn; + } + (*src)(srcData, tmpBuf0); + } + --yn; - nClipRes[0] = nClipRes[1] = nClipRes[2] = 0; + //--- horiz downscale + int acc; + int xt = 0; + int unscaledIdx = 0; + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + int xStep = xp; + xt += xq; + if (xt >= scaledWidth) { + xt -= scaledWidth; + ++xStep; + } - xPath = new SplashXPath(path, state->matrix, state->flatness, gFalse, - state->enablePathSimplification, - state->strokeAdjust); + acc = 0; + for (int i = 0; i < xStep; ++i) { + acc += tmpBuf0[unscaledIdx]; + ++unscaledIdx; + } + line[scaledIdx] = (Guchar)((255 * acc) / xStep); + } +} - pipeInit(&pipe, state->strokePattern, - (Guchar)splashRound(state->strokeAlpha * 255), - gTrue, gFalse); +void ImageMaskScaler::vertUpscaleHorizDownscaleInterp() { + //--- vert upscale + if (ySrcCur == 0) { + (*src)(srcData, tmpBuf0); + (*src)(srcData, tmpBuf1); + ySrcCur = 1; + } + SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale; + int y0 = splashFloor(ys - 0.5); + int y1 = y0 + 1; + SplashCoord vs0 = (SplashCoord)y1 + 0.5 - ys; + SplashCoord vs1 = (SplashCoord)1 - vs0; + if (y1 > ySrcCur && ySrcCur < srcHeight - 1) { + Guchar *t = tmpBuf0; + tmpBuf0 = tmpBuf1; + tmpBuf1 = t; + (*src)(srcData, tmpBuf1); + ++ySrcCur; + } + Guchar *mask0 = tmpBuf0; + Guchar *mask1 = tmpBuf1; + if (y0 < 0) { + y0 = 0; + mask1 = mask0; + } + if (y1 >= srcHeight) { + y1 = srcHeight - 1; + mask0 = mask1; + } + ++yScaledCur; - for (i = 0, seg = xPath->segs; i < xPath->length; ++i, ++seg) { - if (seg->y0 <= seg->y1) { - y0 = splashFloor(seg->y0); - y1 = splashFloor(seg->y1); - x0 = splashFloor(seg->x0); - x1 = splashFloor(seg->x1); - } else { - y0 = splashFloor(seg->y1); - y1 = splashFloor(seg->y0); - x0 = splashFloor(seg->x1); - x1 = splashFloor(seg->x0); + //--- horiz downscale + int acc; + int xt = 0; + int unscaledIdx = 0; + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + int xStep = xp; + xt += xq; + if (xt >= scaledWidth) { + xt -= scaledWidth; + ++xStep; } - if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0, - x0 <= x1 ? x1 : x0, y1, - state->strokeAdjust)) - != splashClipAllOutside) { - if (y0 == y1) { - if (x0 <= x1) { - drawStrokeSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside); - } else { - drawStrokeSpan(&pipe, x1, x0, y0, clipRes == splashClipAllInside); - } - } else { - dxdy = seg->dxdy; - y = state->clip->getYMinI(state->strokeAdjust); - if (y0 < y) { - y0 = y; - x0 = splashFloor(seg->x0 + ((SplashCoord)y0 - seg->y0) * dxdy); - } - y = state->clip->getYMaxI(state->strokeAdjust); - if (y1 > y) { - y1 = y; - x1 = splashFloor(seg->x0 + ((SplashCoord)y1 - seg->y0) * dxdy); - } - if (x0 <= x1) { - xa = x0; - for (y = y0; y <= y1; ++y) { - if (y < y1) { - xb = splashFloor(seg->x0 + - ((SplashCoord)y + 1 - seg->y0) * dxdy); - } else { - xb = x1 + 1; - } - if (xa == xb) { - drawStrokeSpan(&pipe, xa, xa, y, clipRes == splashClipAllInside); - } else { - drawStrokeSpan(&pipe, xa, xb - 1, y, - clipRes == splashClipAllInside); - } - xa = xb; - } - } else { - xa = x0; - for (y = y0; y <= y1; ++y) { - if (y < y1) { - xb = splashFloor(seg->x0 + - ((SplashCoord)y + 1 - seg->y0) * dxdy); - } else { - xb = x1 - 1; - } - if (xa == xb) { - drawStrokeSpan(&pipe, xa, xa, y, clipRes == splashClipAllInside); - } else { - drawStrokeSpan(&pipe, xb + 1, xa, y, - clipRes == splashClipAllInside); - } - xa = xb; - } - } - } + + acc = 0; + for (int i = 0; i < xStep; ++i) { + acc += (int)(vs0 * (int)mask0[unscaledIdx] + + vs1 * (int)mask1[unscaledIdx]); + ++unscaledIdx; } - ++nClipRes[clipRes]; - } - if (nClipRes[splashClipPartial] || - (nClipRes[splashClipAllInside] && nClipRes[splashClipAllOutside])) { - opClipRes = splashClipPartial; - } else if (nClipRes[splashClipAllInside]) { - opClipRes = splashClipAllInside; - } else { - opClipRes = splashClipAllOutside; + line[scaledIdx] = (Guchar)((255 * acc) / xStep); } - - delete xPath; } -void Splash::drawStrokeSpan(SplashPipe *pipe, int x0, int x1, int y, - GBool noClip) { - int x; +void ImageMaskScaler::vertUpscaleHorizUpscaleNoInterp() { + //--- vert upscale + if (yn == 0) { + yn = yp; + yt += yq; + if (yt >= srcHeight) { + yt -= srcHeight; + ++yn; + } + (*src)(srcData, tmpBuf0); + } + --yn; - x = state->clip->getXMinI(state->strokeAdjust); - if (x > x0) { - x0 = x; + //--- horiz upscale + int xt = 0; + int scaledIdx = 0; + for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) { + int xStep = xp; + xt += xq; + if (xt >= srcWidth) { + xt -= srcWidth; + ++xStep; + } + Guchar buf = (Guchar)(255 * tmpBuf0[srcIdx]); + for (int i = 0; i < xStep; ++i) { + line[scaledIdx] = buf; + ++scaledIdx; + } } - x = state->clip->getXMaxI(state->strokeAdjust); - if (x < x1) { - x1 = x; +} + +void ImageMaskScaler::vertUpscaleHorizUpscaleInterp() { + //--- vert upscale + if (ySrcCur == 0) { + (*src)(srcData, tmpBuf0); + (*src)(srcData, tmpBuf1); + ySrcCur = 1; + } + SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale; + int y0 = splashFloor(ys - 0.5); + int y1 = y0 + 1; + SplashCoord vs0 = (SplashCoord)255 * ((SplashCoord)y1 + 0.5 - ys); + SplashCoord vs1 = (SplashCoord)255 - vs0; + if (y1 > ySrcCur && ySrcCur < srcHeight - 1) { + Guchar *t = tmpBuf0; + tmpBuf0 = tmpBuf1; + tmpBuf1 = t; + (*src)(srcData, tmpBuf1); + ++ySrcCur; + } + Guchar *mask0 = tmpBuf0; + Guchar *mask1 = tmpBuf1; + if (y0 < 0) { + y0 = 0; + mask1 = mask0; } - if (x0 > x1) { - return; + if (y1 >= srcHeight) { + y1 = srcHeight - 1; + mask0 = mask1; } - for (x = x0; x <= x1; ++x) { - scanBuf[x] = 0xff; + ++yScaledCur; + for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) { + tmpBuf2[srcIdx] = (Guchar)(int)(vs0 * (int)mask0[srcIdx] + + vs1 * (int)mask1[srcIdx]); } - if (!noClip) { - if (!state->clip->clipSpanBinary(scanBuf, y, x0, x1, state->strokeAdjust)) { - return; + + //--- horiz upscale + for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) { + SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale; + int x0 = splashFloor(xs - 0.5); + int x1 = x0 + 1; + SplashCoord hs0 = (SplashCoord)x1 + 0.5 - xs; + SplashCoord hs1 = (SplashCoord)1 - hs0; + if (x0 < 0) { + x0 = 0; + } + if (x1 >= srcWidth) { + x1 = srcWidth - 1; } + line[scaledIdx] = (Guchar)(int)(hs0 * (int)tmpBuf2[x0] + + hs1 * (int)tmpBuf2[x1]); } - (this->*pipe->run)(pipe, x0, x1, y, scanBuf + x0, NULL); } -void Splash::strokeWide(SplashPath *path, SplashCoord w, - int lineCap, int lineJoin) { - SplashPath *path2; +//------------------------------------------------------------------------ +// Splash +//------------------------------------------------------------------------ - path2 = makeStrokePath(path, w, lineCap, lineJoin, gFalse); - fillWithPattern(path2, gFalse, state->strokePattern, state->strokeAlpha); - delete path2; +Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA, + SplashImageCache *imageCacheA, + SplashScreenParams *screenParams) { + bitmap = bitmapA; + bitmapComps = splashColorModeNComps[bitmap->mode]; + vectorAntialias = vectorAntialiasA; + inShading = gFalse; + state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, + screenParams); + scanBuf = (Guchar *)gmalloc(bitmap->width); + if (bitmap->mode == splashModeMono1) { + scanBuf2 = (Guchar *)gmalloc(bitmap->width); + } else { + scanBuf2 = NULL; + } + groupBackBitmap = NULL; + groupDestInitMode = splashGroupDestPreInit; + overprintMaskBitmap = NULL; + minLineWidth = 0; + clearModRegion(); + debugMode = gFalse; + + if (imageCacheA) { + imageCache = imageCacheA; + imageCache->incRefCount(); + } else { + imageCache = new SplashImageCache(); + } } -SplashPath *Splash::flattenPath(SplashPath *path, SplashCoord *matrix, - SplashCoord flatness) { - SplashPath *fPath; - SplashCoord flatness2; - Guchar flag; - int i; +Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA, + SplashImageCache *imageCacheA, SplashScreen *screenA) { + bitmap = bitmapA; + bitmapComps = splashColorModeNComps[bitmap->mode]; + vectorAntialias = vectorAntialiasA; + inShading = gFalse; + state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, + screenA); + scanBuf = (Guchar *)gmalloc(bitmap->width); + if (bitmap->mode == splashModeMono1) { + scanBuf2 = (Guchar *)gmalloc(bitmap->width); + } else { + scanBuf2 = NULL; + } + groupBackBitmap = NULL; + groupDestInitMode = splashGroupDestPreInit; + overprintMaskBitmap = NULL; + minLineWidth = 0; + clearModRegion(); + debugMode = gFalse; - fPath = new SplashPath(); -#if USE_FIXEDPOINT - flatness2 = flatness; -#else - flatness2 = flatness * flatness; -#endif - i = 0; - while (i < path->length) { - flag = path->flags[i]; - if (flag & splashPathFirst) { - fPath->moveTo(path->pts[i].x, path->pts[i].y); - ++i; - } else { - if (flag & splashPathCurve) { - flattenCurve(path->pts[i-1].x, path->pts[i-1].y, - path->pts[i ].x, path->pts[i ].y, - path->pts[i+1].x, path->pts[i+1].y, - path->pts[i+2].x, path->pts[i+2].y, - matrix, flatness2, fPath); - i += 3; - } else { - fPath->lineTo(path->pts[i].x, path->pts[i].y); - ++i; - } - if (path->flags[i-1] & splashPathClosed) { - fPath->close(); - } - } + if (imageCacheA) { + imageCache = imageCacheA; + imageCache->incRefCount(); + } else { + imageCache = new SplashImageCache(); } - return fPath; } -void Splash::flattenCurve(SplashCoord x0, SplashCoord y0, - SplashCoord x1, SplashCoord y1, - SplashCoord x2, SplashCoord y2, - SplashCoord x3, SplashCoord y3, - SplashCoord *matrix, SplashCoord flatness2, - SplashPath *fPath) { - SplashCoord cx[splashMaxCurveSplits + 1][3]; - SplashCoord cy[splashMaxCurveSplits + 1][3]; - int cNext[splashMaxCurveSplits + 1]; - SplashCoord xl0, xl1, xl2, xr0, xr1, xr2, xr3, xx1, xx2, xh; - SplashCoord yl0, yl1, yl2, yr0, yr1, yr2, yr3, yy1, yy2, yh; - SplashCoord dx, dy, mx, my, tx, ty, d1, d2; - int p1, p2, p3; - - // initial segment - p1 = 0; - p2 = splashMaxCurveSplits; - cx[p1][0] = x0; cy[p1][0] = y0; - cx[p1][1] = x1; cy[p1][1] = y1; - cx[p1][2] = x2; cy[p1][2] = y2; - cx[p2][0] = x3; cy[p2][0] = y3; - cNext[p1] = p2; +Splash::~Splash() { + imageCache->decRefCount(); - while (p1 < splashMaxCurveSplits) { + while (state->next) { + restoreState(); + } + delete state; + gfree(scanBuf); + gfree(scanBuf2); +} - // get the next segment - xl0 = cx[p1][0]; yl0 = cy[p1][0]; - xx1 = cx[p1][1]; yy1 = cy[p1][1]; - xx2 = cx[p1][2]; yy2 = cy[p1][2]; - p2 = cNext[p1]; - xr3 = cx[p2][0]; yr3 = cy[p2][0]; +//------------------------------------------------------------------------ +// state read +//------------------------------------------------------------------------ - // compute the distances (in device space) from the control points - // to the midpoint of the straight line (this is a bit of a hack, - // but it's much faster than computing the actual distances to the - // line) - transform(matrix, (xl0 + xr3) * 0.5, (yl0 + yr3) * 0.5, &mx, &my); - transform(matrix, xx1, yy1, &tx, &ty); -#if USE_FIXEDPOINT - d1 = splashDist(tx, ty, mx, my); -#else - dx = tx - mx; - dy = ty - my; - d1 = dx*dx + dy*dy; -#endif - transform(matrix, xx2, yy2, &tx, &ty); -#if USE_FIXEDPOINT - d2 = splashDist(tx, ty, mx, my); -#else - dx = tx - mx; - dy = ty - my; - d2 = dx*dx + dy*dy; -#endif +SplashCoord *Splash::getMatrix() { + return state->matrix; +} - // if the curve is flat enough, or no more subdivisions are - // allowed, add the straight line segment - if (p2 - p1 == 1 || (d1 <= flatness2 && d2 <= flatness2)) { - fPath->lineTo(xr3, yr3); - p1 = p2; +SplashPattern *Splash::getStrokePattern() { + return state->strokePattern; +} - // otherwise, subdivide the curve - } else { - xl1 = splashAvg(xl0, xx1); - yl1 = splashAvg(yl0, yy1); - xh = splashAvg(xx1, xx2); - yh = splashAvg(yy1, yy2); - xl2 = splashAvg(xl1, xh); - yl2 = splashAvg(yl1, yh); - xr2 = splashAvg(xx2, xr3); - yr2 = splashAvg(yy2, yr3); - xr1 = splashAvg(xh, xr2); - yr1 = splashAvg(yh, yr2); - xr0 = splashAvg(xl2, xr1); - yr0 = splashAvg(yl2, yr1); - // add the new subdivision points - p3 = (p1 + p2) / 2; - cx[p1][1] = xl1; cy[p1][1] = yl1; - cx[p1][2] = xl2; cy[p1][2] = yl2; - cNext[p1] = p3; - cx[p3][0] = xr0; cy[p3][0] = yr0; - cx[p3][1] = xr1; cy[p3][1] = yr1; - cx[p3][2] = xr2; cy[p3][2] = yr2; - cNext[p3] = p2; - } - } +SplashPattern *Splash::getFillPattern() { + return state->fillPattern; } -SplashPath *Splash::makeDashedPath(SplashPath *path) { - SplashPath *dPath; - SplashCoord lineDashTotal; - SplashCoord lineDashStartPhase, lineDashDist, segLen; - SplashCoord x0, y0, x1, y1, xa, ya; - GBool lineDashStartOn, lineDashEndOn, lineDashOn, newPath; - int lineDashStartIdx, lineDashIdx, subpathStart, nDashes; - int i, j, k; +SplashScreen *Splash::getScreen() { + return state->screen; +} - lineDashTotal = 0; - for (i = 0; i < state->lineDashLength; ++i) { - lineDashTotal += state->lineDash[i]; - } - // Acrobat simply draws nothing if the dash array is [0] - if (lineDashTotal == 0) { - return new SplashPath(); - } - lineDashStartPhase = state->lineDashPhase; - if (lineDashStartPhase > lineDashTotal * 2) { - i = splashFloor(lineDashStartPhase / (lineDashTotal * 2)); - lineDashStartPhase -= lineDashTotal * i * 2; - } else if (lineDashStartPhase < 0) { - i = splashCeil(-lineDashStartPhase / (lineDashTotal * 2)); - lineDashStartPhase += lineDashTotal * i * 2; - } - i = splashFloor(lineDashStartPhase / lineDashTotal); - lineDashStartPhase -= (SplashCoord)i * lineDashTotal; - lineDashStartOn = gTrue; - lineDashStartIdx = 0; - if (lineDashStartPhase > 0) { - while (lineDashStartPhase >= state->lineDash[lineDashStartIdx]) { - lineDashStartOn = !lineDashStartOn; - lineDashStartPhase -= state->lineDash[lineDashStartIdx]; - if (++lineDashStartIdx == state->lineDashLength) { - lineDashStartIdx = 0; - } - } - } +SplashBlendFunc Splash::getBlendFunc() { + return state->blendFunc; +} - dPath = new SplashPath(); +SplashCoord Splash::getStrokeAlpha() { + return state->strokeAlpha; +} - // process each subpath - i = 0; - while (i < path->length) { +SplashCoord Splash::getFillAlpha() { + return state->fillAlpha; +} - // find the end of the subpath - for (j = i; - j < path->length - 1 && !(path->flags[j] & splashPathLast); - ++j) ; +SplashCoord Splash::getLineWidth() { + return state->lineWidth; +} - // initialize the dash parameters - lineDashOn = lineDashStartOn; - lineDashEndOn = lineDashStartOn; - lineDashIdx = lineDashStartIdx; - lineDashDist = state->lineDash[lineDashIdx] - lineDashStartPhase; - subpathStart = dPath->length; - nDashes = 0; +int Splash::getLineCap() { + return state->lineCap; +} - // process each segment of the subpath - newPath = gTrue; - for (k = i; k < j; ++k) { +int Splash::getLineJoin() { + return state->lineJoin; +} - // grab the segment - x0 = path->pts[k].x; - y0 = path->pts[k].y; - x1 = path->pts[k+1].x; - y1 = path->pts[k+1].y; - segLen = splashDist(x0, y0, x1, y1); +SplashCoord Splash::getMiterLimit() { + return state->miterLimit; +} - // process the segment - while (segLen > 0) { +SplashCoord Splash::getFlatness() { + return state->flatness; +} - // Special case for zero-length dash segments: draw a very - // short -- but not zero-length -- segment. This ensures that - // we get the correct behavior with butt and projecting line - // caps. The PS/PDF specs imply that zero-length segments are - // not drawn unless the line cap is round, but Acrobat and - // Ghostscript both draw very short segments (for butt caps) - // and squares (for projecting caps). - if (lineDashDist == 0) { - if (lineDashOn) { - if (newPath) { - dPath->moveTo(x0, y0); - newPath = gFalse; - ++nDashes; - } - xa = x0 + ((SplashCoord)0.001 / segLen) * (x1 - x0); - ya = y0 + ((SplashCoord)0.001 / segLen) * (y1 - y0); - dPath->lineTo(xa, ya); - } +SplashCoord *Splash::getLineDash() { + return state->lineDash; +} - } else if (lineDashDist >= segLen) { - if (lineDashOn) { - if (newPath) { - dPath->moveTo(x0, y0); - newPath = gFalse; - ++nDashes; - } - dPath->lineTo(x1, y1); - } - lineDashDist -= segLen; - segLen = 0; +int Splash::getLineDashLength() { + return state->lineDashLength; +} - } else { - xa = x0 + (lineDashDist / segLen) * (x1 - x0); - ya = y0 + (lineDashDist / segLen) * (y1 - y0); - if (lineDashOn) { - if (newPath) { - dPath->moveTo(x0, y0); - newPath = gFalse; - ++nDashes; - } - dPath->lineTo(xa, ya); - } - x0 = xa; - y0 = ya; - segLen -= lineDashDist; - lineDashDist = 0; - } +SplashCoord Splash::getLineDashPhase() { + return state->lineDashPhase; +} - lineDashEndOn = lineDashOn; +SplashStrokeAdjustMode Splash::getStrokeAdjust() { + return state->strokeAdjust; +} - // get the next entry in the dash array - if (lineDashDist <= 0) { - lineDashOn = !lineDashOn; - if (++lineDashIdx == state->lineDashLength) { - lineDashIdx = 0; - } - lineDashDist = state->lineDash[lineDashIdx]; - newPath = gTrue; - } - } - } +SplashClip *Splash::getClip() { + return state->clip; +} - // in a closed subpath, where the dash pattern is "on" at both the - // start and end of the subpath, we need to merge the start and - // end to get a proper line join - if ((path->flags[j] & splashPathClosed) && - lineDashStartOn && - lineDashEndOn) { - if (nDashes == 1) { - dPath->close(); - } else if (nDashes > 1) { - k = subpathStart; - do { - ++k; - dPath->lineTo(dPath->pts[k].x, dPath->pts[k].y); - } while (!(dPath->flags[k] & splashPathLast)); - ++k; - memmove(&dPath->pts[subpathStart], &dPath->pts[k], - (dPath->length - k) * sizeof(SplashPathPoint)); - memmove(&dPath->flags[subpathStart], &dPath->flags[k], - (dPath->length - k) * sizeof(Guchar)); - dPath->length -= k - subpathStart; - dPath->curSubpath -= k - subpathStart; - } - } +SplashBitmap *Splash::getSoftMask() { + return state->softMask; +} - i = j + 1; - } +GBool Splash::getInNonIsolatedGroup() { + return state->inNonIsolatedGroup; +} - return dPath; +GBool Splash::getInKnockoutGroup() { + return state->inKnockoutGroup; } -SplashError Splash::fill(SplashPath *path, GBool eo) { - if (debugMode) { - printf("fill [eo:%d]:\n", eo); - dumpPath(path); - } - return fillWithPattern(path, eo, state->fillPattern, state->fillAlpha); +//------------------------------------------------------------------------ +// state write +//------------------------------------------------------------------------ + +void Splash::setMatrix(SplashCoord *matrix) { + memcpy(state->matrix, matrix, 6 * sizeof(SplashCoord)); } -SplashError Splash::fillWithPattern(SplashPath *path, GBool eo, - SplashPattern *pattern, - SplashCoord alpha) { - SplashPipe pipe; - SplashPath *path2; - SplashXPath *xPath; - SplashXPathScanner *scanner; - int xMin, yMin, xMax, xMin2, xMax2, yMax, y, t; - SplashClipResult clipRes; +void Splash::setStrokePattern(SplashPattern *strokePattern) { + state->setStrokePattern(strokePattern); +} - if (path->length == 0) { - return splashErrEmptyPath; - } - if (pathAllOutside(path)) { - opClipRes = splashClipAllOutside; - return splashOk; - } +void Splash::setFillPattern(SplashPattern *fillPattern) { + state->setFillPattern(fillPattern); +} - path2 = tweakFillPath(path); +void Splash::setScreen(SplashScreen *screen) { + state->setScreen(screen); +} - xPath = new SplashXPath(path2, state->matrix, state->flatness, gTrue, - state->enablePathSimplification, - state->strokeAdjust); - if (path2 != path) { - delete path2; - } - xMin = xPath->getXMin(); - yMin = xPath->getYMin(); - xMax = xPath->getXMax(); - yMax = xPath->getYMax(); - if (xMin > xMax || yMin > yMax) { - delete xPath; - return splashOk; - } - scanner = new SplashXPathScanner(xPath, eo, yMin, yMax); +void Splash::setBlendFunc(SplashBlendFunc func) { + state->blendFunc = func; +} - // check clipping - if ((clipRes = state->clip->testRect(xMin, yMin, xMax, yMax, - state->strokeAdjust)) - != splashClipAllOutside) { +void Splash::setStrokeAlpha(SplashCoord alpha) { + state->strokeAlpha = alpha; +} - if ((t = state->clip->getXMinI(state->strokeAdjust)) > xMin) { - xMin = t; - } - if ((t = state->clip->getXMaxI(state->strokeAdjust)) < xMax) { - xMax = t; - } - if ((t = state->clip->getYMinI(state->strokeAdjust)) > yMin) { - yMin = t; - } - if ((t = state->clip->getYMaxI(state->strokeAdjust)) < yMax) { - yMax = t; - } - if (xMin > xMax || yMin > yMax) { - delete scanner; - delete xPath; - return splashOk; - } +void Splash::setFillAlpha(SplashCoord alpha) { + state->fillAlpha = alpha; +} - pipeInit(&pipe, pattern, (Guchar)splashRound(alpha * 255), - gTrue, gFalse); +void Splash::setLineWidth(SplashCoord lineWidth) { + state->lineWidth = lineWidth; +} - // draw the spans - if (vectorAntialias && !inShading) { - for (y = yMin; y <= yMax; ++y) { - scanner->getSpan(scanBuf, y, xMin, xMax, &xMin2, &xMax2); - if (xMin2 <= xMax2) { - if (clipRes != splashClipAllInside) { - state->clip->clipSpan(scanBuf, y, xMin2, xMax2, - state->strokeAdjust); - } - (this->*pipe.run)(&pipe, xMin2, xMax2, y, scanBuf + xMin2, NULL); - } - } - } else { - for (y = yMin; y <= yMax; ++y) { - scanner->getSpanBinary(scanBuf, y, xMin, xMax, &xMin2, &xMax2); - if (xMin2 <= xMax2) { - if (clipRes != splashClipAllInside) { - state->clip->clipSpanBinary(scanBuf, y, xMin2, xMax2, - state->strokeAdjust); - } - (this->*pipe.run)(&pipe, xMin2, xMax2, y, scanBuf + xMin2, NULL); - } - } - } +void Splash::setLineCap(int lineCap) { + if (lineCap >= 0 && lineCap <= 2) { + state->lineCap = lineCap; + } else { + state->lineCap = 0; } - opClipRes = clipRes; +} - delete scanner; - delete xPath; - return splashOk; +void Splash::setLineJoin(int lineJoin) { + if (lineJoin >= 0 && lineJoin <= 2) { + state->lineJoin = lineJoin; + } else { + state->lineJoin = 0; + } } -// Applies various tweaks to a fill path: -// (1) add stroke adjust hints to a filled rectangle -// (2) applies a minimum width to a zero-width filled rectangle (so -// stroke adjustment works correctly -// (3) convert a degenerate fill ('moveto lineto fill' and 'moveto -// lineto closepath fill') to a minimum-width filled rectangle -// -// These tweaks only apply to paths with a single subpath. -// -// Returns either the unchanged input path or a new path (in which -// case the returned path must be deleted by the caller). -SplashPath *Splash::tweakFillPath(SplashPath *path) { - SplashPath *path2; - SplashCoord xx0, yy0, xx1, yy1, dx, dy, d, wx, wy, w; - int n; +void Splash::setMiterLimit(SplashCoord miterLimit) { + state->miterLimit = miterLimit; +} - if (state->strokeAdjust == splashStrokeAdjustOff || path->hints) { - return path; +void Splash::setFlatness(SplashCoord flatness) { + if (flatness < 1) { + state->flatness = 1; + } else { + state->flatness = flatness; } +} - n = path->getLength(); - if (!((n == 2) || - (n == 3 && - path->flags[1] == 0) || - (n == 4 && - path->flags[1] == 0 && - path->flags[2] == 0) || - (n == 5 && - path->flags[1] == 0 && - path->flags[2] == 0 && - path->flags[3] == 0))) { - return path; - } +void Splash::setLineDash(SplashCoord *lineDash, int lineDashLength, + SplashCoord lineDashPhase) { + state->setLineDash(lineDash, lineDashLength, lineDashPhase); +} - path2 = path; +void Splash::setStrokeAdjust(SplashStrokeAdjustMode strokeAdjust) { + state->strokeAdjust = strokeAdjust; +} - // degenerate fill (2 or 3 points) or rectangle of (nearly) zero - // width --> replace with a min-width rectangle and hint - if (n == 2 || - (n == 3 && (path->flags[0] & splashPathClosed)) || - (n == 3 && (splashAbs(path->pts[0].x - path->pts[2].x) < 0.001 && - splashAbs(path->pts[0].y - path->pts[2].y) < 0.001)) || - ((n == 4 || - (n == 5 && (path->flags[0] & splashPathClosed))) && - ((splashAbs(path->pts[0].x - path->pts[1].x) < 0.001 && - splashAbs(path->pts[0].y - path->pts[1].y) < 0.001 && - splashAbs(path->pts[2].x - path->pts[3].x) < 0.001 && - splashAbs(path->pts[2].y - path->pts[3].y) < 0.001) || - (splashAbs(path->pts[0].x - path->pts[3].x) < 0.001 && - splashAbs(path->pts[0].y - path->pts[3].y) < 0.001 && - splashAbs(path->pts[1].x - path->pts[2].x) < 0.001 && - splashAbs(path->pts[1].y - path->pts[2].y) < 0.001)))) { - wx = state->matrix[0] + state->matrix[2]; - wy = state->matrix[1] + state->matrix[3]; - w = splashSqrt(wx*wx + wy*wy); - if (w < 0.001) { - w = 0; - } else { - // min width is 0.1 -- this constant is minWidth * sqrt(2) - w = (SplashCoord)0.1414 / w; - } - xx0 = path->pts[0].x; - yy0 = path->pts[0].y; - if (n <= 3) { - xx1 = path->pts[1].x; - yy1 = path->pts[1].y; - } else { - xx1 = path->pts[2].x; - yy1 = path->pts[2].y; - } - dx = xx1 - xx0; - dy = yy1 - yy0; - d = splashSqrt(dx * dx + dy * dy); - if (d < 0.001) { - d = 0; - } else { - d = w / d; - } - dx *= d; - dy *= d; - path2 = new SplashPath(); - path2->moveTo(xx0 + dy, yy0 - dx); - path2->lineTo(xx1 + dy, yy1 - dx); - path2->lineTo(xx1 - dy, yy1 + dx); - path2->lineTo(xx0 - dy, yy0 + dx); - path2->close(gTrue); - path2->addStrokeAdjustHint(0, 2, 0, 4); - path2->addStrokeAdjustHint(1, 3, 0, 4); +void Splash::clipResetToRect(SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1) { + state->clipResetToRect(x0, y0, x1, y1); +} - // unclosed rectangle --> close and hint - } else if (n == 4 && !(path->flags[0] & splashPathClosed)) { - path2->close(gTrue); - path2->addStrokeAdjustHint(0, 2, 0, 4); - path2->addStrokeAdjustHint(1, 3, 0, 4); +SplashError Splash::clipToRect(SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1) { + return state->clipToRect(x0, y0, x1, y1); +} - // closed rectangle --> hint - } else if (n == 5 && (path->flags[0] & splashPathClosed)) { - path2->addStrokeAdjustHint(0, 2, 0, 4); - path2->addStrokeAdjustHint(1, 3, 0, 4); - } +SplashError Splash::clipToPath(SplashPath *path, GBool eo) { + return state->clipToPath(path, eo); +} - return path2; +void Splash::setSoftMask(SplashBitmap *softMask) { + state->setSoftMask(softMask); } -GBool Splash::pathAllOutside(SplashPath *path) { - SplashCoord xMin1, yMin1, xMax1, yMax1; - SplashCoord xMin2, yMin2, xMax2, yMax2; - SplashCoord x, y; - int xMinI, yMinI, xMaxI, yMaxI; - int i; +void Splash::setInTransparencyGroup(SplashBitmap *groupBackBitmapA, + int groupBackXA, int groupBackYA, + SplashGroupDestInitMode groupDestInitModeA, + GBool nonIsolated, GBool knockout) { + groupBackBitmap = groupBackBitmapA; + groupBackX = groupBackXA; + groupBackY = groupBackYA; + groupDestInitMode = groupDestInitModeA; + groupDestInitYMin = 1; + groupDestInitYMax = 0; + state->inNonIsolatedGroup = nonIsolated; + state->inKnockoutGroup = knockout; +} - xMin1 = xMax1 = path->pts[0].x; - yMin1 = yMax1 = path->pts[0].y; - for (i = 1; i < path->length; ++i) { - if (path->pts[i].x < xMin1) { - xMin1 = path->pts[i].x; - } else if (path->pts[i].x > xMax1) { - xMax1 = path->pts[i].x; - } - if (path->pts[i].y < yMin1) { - yMin1 = path->pts[i].y; - } else if (path->pts[i].y > yMax1) { - yMax1 = path->pts[i].y; - } - } +void Splash::forceDeferredInit(int y, int h) { + useDestRow(y); + useDestRow(y + h - 1); +} - transform(state->matrix, xMin1, yMin1, &x, &y); - xMin2 = xMax2 = x; - yMin2 = yMax2 = y; - transform(state->matrix, xMin1, yMax1, &x, &y); - if (x < xMin2) { - xMin2 = x; - } else if (x > xMax2) { - xMax2 = x; - } - if (y < yMin2) { - yMin2 = y; - } else if (y > yMax2) { - yMax2 = y; - } - transform(state->matrix, xMax1, yMin1, &x, &y); - if (x < xMin2) { - xMin2 = x; - } else if (x > xMax2) { - xMax2 = x; - } - if (y < yMin2) { - yMin2 = y; - } else if (y > yMax2) { - yMax2 = y; - } - transform(state->matrix, xMax1, yMax1, &x, &y); - if (x < xMin2) { - xMin2 = x; - } else if (x > xMax2) { - xMax2 = x; - } - if (y < yMin2) { - yMin2 = y; - } else if (y > yMax2) { - yMax2 = y; - } - // sanity-check the coordinates - xMinI/yMinI/xMaxI/yMaxI are - // 32-bit integers, so coords need to be < 2^31 - SplashXPath::clampCoords(&xMin2, &yMin2); - SplashXPath::clampCoords(&xMax2, &yMax2); - xMinI = splashFloor(xMin2); - yMinI = splashFloor(yMin2); - xMaxI = splashFloor(xMax2); - yMaxI = splashFloor(yMax2); +void Splash::setTransfer(Guchar *red, Guchar *green, Guchar *blue, + Guchar *gray) { + state->setTransfer(red, green, blue, gray); +} - return state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI, - state->strokeAdjust) == - splashClipAllOutside; +void Splash::setOverprintMask(Guint overprintMask) { + state->overprintMask = overprintMask; } -SplashError Splash::fillChar(SplashCoord x, SplashCoord y, - int c, SplashFont *font) { - SplashGlyphBitmap glyph; - SplashCoord xt, yt; - int x0, y0, xFrac, yFrac; - SplashError err; - if (debugMode) { - printf("fillChar: x=%.2f y=%.2f c=%3d=0x%02x='%c'\n", - (double)x, (double)y, c, c, c); - } - transform(state->matrix, x, y, &xt, &yt); - x0 = splashFloor(xt); - xFrac = splashFloor((xt - x0) * splashFontFraction); - y0 = splashFloor(yt); - yFrac = splashFloor((yt - y0) * splashFontFraction); - if (!font->getGlyph(c, xFrac, yFrac, &glyph)) { - return splashErrNoGlyph; - } - err = fillGlyph2(x0, y0, &glyph); - if (glyph.freeData) { - gfree(glyph.data); - } - return err; +void Splash::setEnablePathSimplification(GBool en) { + state->enablePathSimplification = en; } -SplashError Splash::fillGlyph(SplashCoord x, SplashCoord y, - SplashGlyphBitmap *glyph) { - SplashCoord xt, yt; - int x0, y0; +//------------------------------------------------------------------------ +// state save/restore +//------------------------------------------------------------------------ - transform(state->matrix, x, y, &xt, &yt); - x0 = splashFloor(xt); - y0 = splashFloor(yt); - return fillGlyph2(x0, y0, glyph); +void Splash::saveState() { + SplashState *newState; + + newState = state->copy(); + newState->next = state; + state = newState; } -SplashError Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph) { - SplashPipe pipe; - SplashClipResult clipRes; - Guchar alpha; - Guchar *p; - int xMin, yMin, xMax, yMax; - int x, y, xg, yg, xx, t; +SplashError Splash::restoreState() { + SplashState *oldState; + + if (!state->next) { + return splashErrNoSave; + } + oldState = state; + state = state->next; + delete oldState; + return splashOk; +} + +//------------------------------------------------------------------------ +// drawing operations +//------------------------------------------------------------------------ + +void Splash::clear(SplashColorPtr color, Guchar alpha) { + SplashColorPtr row, p; + Guchar mono; + int x, y; - xg = x0 - glyph->x; - yg = y0 - glyph->y; - xMin = xg; - xMax = xg + glyph->w - 1; - yMin = yg; - yMax = yg + glyph->h - 1; - if ((clipRes = state->clip->testRect(xMin, yMin, xMax, yMax, - state->strokeAdjust)) - != splashClipAllOutside) { - pipeInit(&pipe, state->fillPattern, - (Guchar)splashRound(state->fillAlpha * 255), - gTrue, gFalse); - if (clipRes == splashClipAllInside) { - if (glyph->aa) { - p = glyph->data; - for (y = yMin; y <= yMax; ++y) { - (this->*pipe.run)(&pipe, xMin, xMax, y, - glyph->data + (y - yMin) * glyph->w, NULL); - } + switch (bitmap->mode) { + case splashModeMono1: + mono = (color[0] & 0x80) ? 0xff : 0x00; + if (bitmap->rowSize < 0) { + memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), + mono, -bitmap->rowSize * bitmap->height); + } else { + memset(bitmap->data, mono, bitmap->rowSize * bitmap->height); + } + break; + case splashModeMono8: + if (bitmap->rowSize < 0) { + memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), + color[0], -bitmap->rowSize * bitmap->height); + } else { + memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); + } + break; + case splashModeRGB8: + if (color[0] == color[1] && color[1] == color[2]) { + if (bitmap->rowSize < 0) { + memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), + color[0], -bitmap->rowSize * bitmap->height); } else { - p = glyph->data; - for (y = yMin; y <= yMax; ++y) { - for (x = xMin; x <= xMax; x += 8) { - alpha = *p++; - for (xx = 0; xx < 8 && x + xx <= xMax; ++xx) { - scanBuf[x + xx] = (alpha & 0x80) ? 0xff : 0x00; - alpha = (Guchar)(alpha << 1); - } - } - (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); - } + memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); } } else { - if ((t = state->clip->getXMinI(state->strokeAdjust)) > xMin) { - xMin = t; + row = bitmap->data; + for (y = 0; y < bitmap->height; ++y) { + p = row; + for (x = 0; x < bitmap->width; ++x) { + *p++ = color[0]; + *p++ = color[1]; + *p++ = color[2]; + } + row += bitmap->rowSize; } - if ((t = state->clip->getXMaxI(state->strokeAdjust)) < xMax) { - xMax = t; + } + break; + case splashModeBGR8: + if (color[0] == color[1] && color[1] == color[2]) { + if (bitmap->rowSize < 0) { + memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), + color[0], -bitmap->rowSize * bitmap->height); + } else { + memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); } - if ((t = state->clip->getYMinI(state->strokeAdjust)) > yMin) { - yMin = t; + } else { + row = bitmap->data; + for (y = 0; y < bitmap->height; ++y) { + p = row; + for (x = 0; x < bitmap->width; ++x) { + *p++ = color[2]; + *p++ = color[1]; + *p++ = color[0]; + } + row += bitmap->rowSize; } - if ((t = state->clip->getYMaxI(state->strokeAdjust)) < yMax) { - yMax = t; + } + break; +#if SPLASH_CMYK + case splashModeCMYK8: + if (color[0] == color[1] && color[1] == color[2] && color[2] == color[3]) { + if (bitmap->rowSize < 0) { + memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1), + color[0], -bitmap->rowSize * bitmap->height); + } else { + memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height); } - if (xMin <= xMax && yMin <= yMax) { - if (glyph->aa) { - for (y = yMin; y <= yMax; ++y) { - p = glyph->data + (y - yg) * glyph->w + (xMin - xg); - memcpy(scanBuf + xMin, p, xMax - xMin + 1); - state->clip->clipSpan(scanBuf, y, xMin, xMax, - state->strokeAdjust); - (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); - } - } else { - for (y = yMin; y <= yMax; ++y) { - p = glyph->data + (y - yg) * ((glyph->w + 7) >> 3) - + ((xMin - xg) >> 3); - alpha = *p++; - xx = (xMin - xg) & 7; - alpha = (Guchar)(alpha << xx); - for (x = xMin; xx < 8 && x <= xMax; ++x, ++xx) { - scanBuf[x] = (alpha & 0x80) ? 255 : 0; - alpha = (Guchar)(alpha << 1); - } - for (; x <= xMax; x += 8) { - alpha = *p++; - for (xx = 0; xx < 8 && x + xx <= xMax; ++xx) { - scanBuf[x + xx] = (alpha & 0x80) ? 255 : 0; - alpha = (Guchar)(alpha << 1); - } - } - state->clip->clipSpanBinary(scanBuf, y, xMin, xMax, - state->strokeAdjust); - (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); - } + } else { + row = bitmap->data; + for (y = 0; y < bitmap->height; ++y) { + p = row; + for (x = 0; x < bitmap->width; ++x) { + *p++ = color[0]; + *p++ = color[1]; + *p++ = color[2]; + *p++ = color[3]; } + row += bitmap->rowSize; } } + break; +#endif } - opClipRes = clipRes; - - return splashOk; -} -void Splash::getImageBounds(SplashCoord xyMin, SplashCoord xyMax, - int *xyMinI, int *xyMaxI) { - if (state->strokeAdjust == splashStrokeAdjustOff) { - // make sure the coords fit in 32-bit ints -#if USE_FIXEDPOINT - if (xyMin < -32767) { - xyMin = -32767; - } else if (xyMin > 32767) { - xyMin = 32767; - } - if (xyMax < -32767) { - xyMax = -32767; - } else if (xyMax > 32767) { - xyMax = 32767; - } -#else - if (xyMin < -1e9) { - xyMin = -1e9; - } else if (xyMin > 1e9) { - xyMin = 1e9; - } - if (xyMax < -1e9) { - xyMax = -1e9; - } else if (xyMax > 1e9) { - xyMax = 1e9; - } -#endif - *xyMinI = splashFloor(xyMin); - *xyMaxI = splashFloor(xyMax); - if (*xyMaxI <= *xyMinI) { - *xyMaxI = *xyMinI + 1; - } - } else { - splashStrokeAdjust(xyMin, xyMax, xyMinI, xyMaxI, state->strokeAdjust); + if (bitmap->alpha) { + memset(bitmap->alpha, alpha, bitmap->alphaRowSize * bitmap->height); } + + updateModX(0); + updateModY(0); + updateModX(bitmap->width - 1); + updateModY(bitmap->height - 1); } -// The glyphMode flag is not currently used, but may be useful if the -// stroke adjustment behavior is changed. -SplashError Splash::fillImageMask(GString *imageTag, - SplashImageMaskSource src, void *srcData, - int w, int h, SplashCoord *mat, - GBool glyphMode, GBool interpolate) { - SplashBitmap *scaledMask; - SplashClipResult clipRes; - GBool minorAxisZero; - SplashCoord wSize, hSize, t0, t1; - int x0, y0, x1, y1, scaledWidth, scaledHeight; +SplashError Splash::stroke(SplashPath *path) { + SplashPath *path2, *dPath; + SplashCoord t0, t1, t2, t3, w, w2, lineDashMax, lineDashTotal; + int lineCap, lineJoin, i; if (debugMode) { - printf("fillImageMask: w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n", - w, h, (double)mat[0], (double)mat[1], (double)mat[2], - (double)mat[3], (double)mat[4], (double)mat[5]); + printf("stroke [dash:%d] [width:%.2f]:\n", + state->lineDashLength, (double)state->lineWidth); + dumpPath(path); } - - // check for singular matrix - if (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) { - return splashErrSingularMatrix; + opClipRes = splashClipAllOutside; + if (path->length == 0) { + return splashErrEmptyPath; } + path2 = flattenPath(path, state->matrix, state->flatness); - minorAxisZero = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001; - - // rough estimate of size of scaled mask - t0 = splashAbs(mat[0]); - t1 = splashAbs(mat[1]); - wSize = t0 > t1 ? t0 : t1; - t0 = splashAbs(mat[2]); - t1 = splashAbs(mat[3]); - hSize = t0 > t1 ? t0 : t1; + // Compute an approximation of the transformed line width. + // Given a CTM of [m0 m1], + // [m2 m3] + // if |m0|*|m3| >= |m1|*|m2| then use min{|m0|,|m3|}, else + // use min{|m1|,|m2|}. + // This handles the common cases -- [s 0 ] and [0 s] -- + // [0 +/-s] [+/-s 0] + // well, and still does something reasonable for the uncommon + // case transforms. + t0 = splashAbs(state->matrix[0]); + t1 = splashAbs(state->matrix[1]); + t2 = splashAbs(state->matrix[2]); + t3 = splashAbs(state->matrix[3]); + if (t0 * t3 >= t1 * t2) { + w = (t0 < t3) ? t0 : t3; + } else { + w = (t1 < t2) ? t1 : t2; + } + w2 = w * state->lineWidth; - // stream-mode upscaling -- this is slower, so we only use it if the - // upscaled mask is large (in which case clipping should remove many - // pixels) -#if USE_FIXEDPOINT - if ((wSize > 2 * w && hSize > 2 * h && (int)wSize > 1000000 / (int)hSize) || - (wSize > w && hSize > h && (int)wSize > 10000000 / (int)hSize) || - ((wSize > w || hSize > h) && (int)wSize > 25000000 / (int)hSize)) { -#else - if ((wSize > 2 * w && hSize > 2 * h && wSize * hSize > 1000000) || - (wSize > w && hSize > h && wSize * hSize > 10000000) || - ((wSize > w || hSize > h) && wSize * hSize > 25000000)) { - upscaleMask(src, srcData, w, h, mat, glyphMode, interpolate); -#endif + // construct the dashed path + if (state->lineDashLength > 0) { - // scaling only - } else if (mat[0] > 0 && minorAxisZero && mat[3] > 0) { - getImageBounds(mat[4], mat[0] + mat[4], &x0, &x1); - getImageBounds(mat[5], mat[3] + mat[5], &y0, &y1); - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes != splashClipAllOutside) { - scaledWidth = x1 - x0; - scaledHeight = y1 - y0; - scaledMask = scaleMask(imageTag, src, srcData, w, h, - scaledWidth, scaledHeight, interpolate); - if (imageCache->vertFlip) { - vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->vertFlip = gFalse; - } - if (imageCache->horizFlip) { - horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->horizFlip = gFalse; - } - blitMask(scaledMask, x0, y0, clipRes); - } - - // scaling plus vertical flip - } else if (mat[0] > 0 && minorAxisZero && mat[3] < 0) { - getImageBounds(mat[4], mat[0] + mat[4], &x0, &x1); - getImageBounds(mat[3] + mat[5], mat[5], &y0, &y1); - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes != splashClipAllOutside) { - scaledWidth = x1 - x0; - scaledHeight = y1 - y0; - scaledMask = scaleMask(imageTag, src, srcData, w, h, - scaledWidth, scaledHeight, interpolate); - if (!imageCache->vertFlip) { - vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->vertFlip = gTrue; - } - if (imageCache->horizFlip) { - horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->horizFlip = gFalse; + // check the maximum transformed dash element length (using the + // same approximation as for line width) -- if it's less than 0.1 + // pixel, don't apply the dash pattern; this avoids a huge + // performance/memory hit with PDF files that use absurd dash + // patterns like [0.0007 0.0003] + lineDashTotal = 0; + lineDashMax = 0; + for (i = 0; i < state->lineDashLength; ++i) { + lineDashTotal += state->lineDash[i]; + if (state->lineDash[i] > lineDashMax) { + lineDashMax = state->lineDash[i]; } - blitMask(scaledMask, x0, y0, clipRes); } + // Acrobat simply draws nothing if the dash array is [0] + if (lineDashTotal == 0) { + delete path2; + return splashOk; + } + if (w * lineDashMax > 0.1) { - // scaling plus horizontal flip - } else if (mat[0] < 0 && minorAxisZero && mat[3] > 0) { - getImageBounds(mat[0] + mat[4], mat[4], &x0, &x1); - getImageBounds(mat[5], mat[3] + mat[5], &y0, &y1); - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes != splashClipAllOutside) { - scaledWidth = x1 - x0; - scaledHeight = y1 - y0; - scaledMask = scaleMask(imageTag, src, srcData, w, h, - scaledWidth, scaledHeight, interpolate); - if (imageCache->vertFlip) { - vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->vertFlip = gFalse; - } - if (!imageCache->horizFlip) { - horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->horizFlip = gTrue; + dPath = makeDashedPath(path2); + delete path2; + path2 = dPath; + if (path2->length == 0) { + delete path2; + return splashErrEmptyPath; } - blitMask(scaledMask, x0, y0, clipRes); } + } - // scaling plus horizontal and vertical flips - } else if (mat[0] < 0 && minorAxisZero && mat[3] < 0) { - getImageBounds(mat[0] + mat[4], mat[4], &x0, &x1); - getImageBounds(mat[3] + mat[5], mat[5], &y0, &y1); - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes != splashClipAllOutside) { - scaledWidth = x1 - x0; - scaledHeight = y1 - y0; - scaledMask = scaleMask(imageTag, src, srcData, w, h, - scaledWidth, scaledHeight, interpolate); - if (!imageCache->vertFlip) { - vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->vertFlip = gTrue; - } - if (!imageCache->horizFlip) { - horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->horizFlip = gTrue; - } - blitMask(scaledMask, x0, y0, clipRes); + // round caps on narrow lines look bad, and can't be + // stroke-adjusted, so use projecting caps instead (but we can't do + // this if there are zero-length dashes or segments, because those + // turn into round dots) + lineCap = state->lineCap; + lineJoin = state->lineJoin; + if (state->strokeAdjust == splashStrokeAdjustCAD && + w2 < 3.5) { + if (lineCap == splashLineCapRound && + !state->lineDashContainsZeroLengthDashes() && + !path->containsZeroLengthSubpaths()) { + lineCap = splashLineCapProjecting; + } + if (lineJoin == splashLineJoinRound) { + lineJoin = splashLineJoinBevel; } + } - // all other cases + // if there is a min line width set, and the transformed line width + // is smaller, use the min line width + if (w > 0 && w2 < minLineWidth) { + strokeWide(path2, minLineWidth / w, splashLineCapButt, splashLineJoinBevel); + } else if (bitmap->mode == splashModeMono1 || !vectorAntialias) { + // in monochrome mode or if antialiasing is disabled, use 0-width + // lines for any transformed line width <= 1 -- lines less than 1 + // pixel wide look too fat without antialiasing + if (w2 < 1.001) { + strokeNarrow(path2); + } else { + strokeWide(path2, state->lineWidth, lineCap, lineJoin); + } } else { - arbitraryTransformMask(imageTag, src, srcData, w, h, - mat, glyphMode, interpolate); + // in gray and color modes, only use 0-width lines if the line + // width is explicitly set to 0 + if (state->lineWidth == 0) { + strokeNarrow(path2); + } else { + strokeWide(path2, state->lineWidth, lineCap, lineJoin); + } } + delete path2; return splashOk; } -// The glyphMode flag is not currently used, but may be useful if the -// stroke adjustment behavior is changed. -void Splash::upscaleMask(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - SplashCoord *mat, GBool glyphMode, - GBool interpolate) { - SplashClipResult clipRes; +void Splash::strokeNarrow(SplashPath *path) { SplashPipe pipe; - Guchar *unscaledImage, *p; - SplashCoord xMin, yMin, xMax, yMax, t; - SplashCoord mi0, mi1, mi2, mi3, mi4, mi5, det; - SplashCoord ix, iy, sx, sy, pix0, pix1; - int xMinI, yMinI, xMaxI, yMaxI, x, y, x0, y0, x1, y1, tt; - - // compute the bbox of the target quadrilateral - xMin = xMax = mat[4]; - t = mat[2] + mat[4]; - if (t < xMin) { - xMin = t; - } else if (t > xMax) { - xMax = t; - } - t = mat[0] + mat[2] + mat[4]; - if (t < xMin) { - xMin = t; - } else if (t > xMax) { - xMax = t; - } - t = mat[0] + mat[4]; - if (t < xMin) { - xMin = t; - } else if (t > xMax) { - xMax = t; - } - getImageBounds(xMin, xMax, &xMinI, &xMaxI); - yMin = yMax = mat[5]; - t = mat[3] + mat[5]; - if (t < yMin) { - yMin = t; - } else if (t > yMax) { - yMax = t; - } - t = mat[1] + mat[3] + mat[5]; - if (t < yMin) { - yMin = t; - } else if (t > yMax) { - yMax = t; - } - t = mat[1] + mat[5]; - if (t < yMin) { - yMin = t; - } else if (t > yMax) { - yMax = t; - } - getImageBounds(yMin, yMax, &yMinI, &yMaxI); - - // clipping - clipRes = state->clip->testRect(xMinI, yMinI, xMaxI - 1, yMaxI - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes == splashClipAllOutside) { - return; - } - if (clipRes != splashClipAllInside) { - if ((tt = state->clip->getXMinI(state->strokeAdjust)) > xMinI) { - xMinI = tt; - } - if ((tt = state->clip->getXMaxI(state->strokeAdjust) + 1) < xMaxI) { - xMaxI = tt; - } - if ((tt = state->clip->getYMinI(state->strokeAdjust)) > yMinI) { - yMinI = tt; - } - if ((tt = state->clip->getYMaxI(state->strokeAdjust) + 1) < yMaxI) { - yMaxI = tt; - } - } + SplashXPath *xPath; + SplashXPathSeg *seg; + int x0, x1, y0, y1, xa, xb, y; + SplashCoord dxdy; + SplashClipResult clipRes; + int nClipRes[3]; + int i; - // invert the matrix - det = mat[0] * mat[3] - mat[1] * mat[2]; - if (splashAbs(det) < 1e-6) { - // this should be caught by the singular matrix check in fillImageMask - return; - } - det = (SplashCoord)1 / det; - mi0 = det * mat[3] * srcWidth; - mi1 = -det * mat[1] * srcHeight; - mi2 = -det * mat[2] * srcWidth; - mi3 = det * mat[0] * srcHeight; - mi4 = det * (mat[2] * mat[5] - mat[3] * mat[4]) * srcWidth; - mi5 = -det * (mat[0] * mat[5] - mat[1] * mat[4]) * srcHeight; + nClipRes[0] = nClipRes[1] = nClipRes[2] = 0; - // grab the image - unscaledImage = (Guchar *)gmallocn(srcWidth, srcHeight); - for (y = 0, p = unscaledImage; y < srcHeight; ++y, p += srcWidth) { - (*src)(srcData, p); - for (x = 0; x < srcWidth; ++x) { - p[x] = (Guchar)(p[x] * 255); - } - } + xPath = new SplashXPath(path, state->matrix, state->flatness, gFalse, + state->enablePathSimplification, + state->strokeAdjust); - // draw it - pipeInit(&pipe, state->fillPattern, - (Guchar)splashRound(state->fillAlpha * 255), + pipeInit(&pipe, state->strokePattern, + (Guchar)splashRound(state->strokeAlpha * 255), gTrue, gFalse); - for (y = yMinI; y < yMaxI; ++y) { - for (x = xMinI; x < xMaxI; ++x) { - ix = ((SplashCoord)x + 0.5) * mi0 + ((SplashCoord)y + 0.5) * mi2 + mi4; - iy = ((SplashCoord)x + 0.5) * mi1 + ((SplashCoord)y + 0.5) * mi3 + mi5; - if (interpolate) { - if (ix >= 0 && ix < srcWidth && iy >= 0 && iy < srcHeight) { - x0 = splashFloor(ix - 0.5); - x1 = x0 + 1; - sx = (ix - 0.5) - x0; - y0 = splashFloor(iy - 0.5); - y1 = y0 + 1; - sy = (iy - 0.5) - y0; - if (x0 < 0) { - x0 = 0; - } - if (x1 >= srcWidth) { - x1 = srcWidth - 1; - } - if (y0 < 0) { - y0 = 0; - } - if (y1 >= srcHeight) { - y1 = srcHeight - 1; - } - pix0 = ((SplashCoord)1 - sx) - * (SplashCoord)unscaledImage[y0 * srcWidth + x0] - + sx * (SplashCoord)unscaledImage[y0 * srcWidth + x1]; - pix1 = ((SplashCoord)1 - sx) - * (SplashCoord)unscaledImage[y1 * srcWidth + x0] - + sx * (SplashCoord)unscaledImage[y1 * srcWidth + x1]; - scanBuf[x] = (Guchar)splashRound(((SplashCoord)1 - sy) * pix0 - + sy * pix1); + + for (i = 0, seg = xPath->segs; i < xPath->length; ++i, ++seg) { + if (seg->y0 <= seg->y1) { + y0 = splashFloor(seg->y0); + y1 = splashFloor(seg->y1); + x0 = splashFloor(seg->x0); + x1 = splashFloor(seg->x1); + } else { + y0 = splashFloor(seg->y1); + y1 = splashFloor(seg->y0); + x0 = splashFloor(seg->x1); + x1 = splashFloor(seg->x0); + } + if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0, + x0 <= x1 ? x1 : x0, y1, + state->strokeAdjust)) + != splashClipAllOutside) { + if (y0 == y1) { + if (x0 <= x1) { + drawStrokeSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside); } else { - scanBuf[x] = 0; + drawStrokeSpan(&pipe, x1, x0, y0, clipRes == splashClipAllInside); } } else { - x0 = splashFloor(ix); - y0 = splashFloor(iy); - if (x0 >= 0 && x0 < srcWidth && y0 >= 0 && y0 < srcHeight) { - scanBuf[x] = unscaledImage[y0 * srcWidth + x0]; + dxdy = seg->dxdy; + y = state->clip->getYMinI(state->strokeAdjust); + if (y0 < y) { + y0 = y; + x0 = splashFloor(seg->x0 + ((SplashCoord)y0 - seg->y0) * dxdy); + } + y = state->clip->getYMaxI(state->strokeAdjust); + if (y1 > y) { + y1 = y; + x1 = splashFloor(seg->x0 + ((SplashCoord)y1 - seg->y0) * dxdy); + } + if (x0 <= x1) { + xa = x0; + for (y = y0; y <= y1; ++y) { + if (y < y1) { + xb = splashFloor(seg->x0 + + ((SplashCoord)y + 1 - seg->y0) * dxdy); + } else { + xb = x1 + 1; + } + if (xa == xb) { + drawStrokeSpan(&pipe, xa, xa, y, clipRes == splashClipAllInside); + } else { + drawStrokeSpan(&pipe, xa, xb - 1, y, + clipRes == splashClipAllInside); + } + xa = xb; + } } else { - scanBuf[x] = 0; + xa = x0; + for (y = y0; y <= y1; ++y) { + if (y < y1) { + xb = splashFloor(seg->x0 + + ((SplashCoord)y + 1 - seg->y0) * dxdy); + } else { + xb = x1 - 1; + } + if (xa == xb) { + drawStrokeSpan(&pipe, xa, xa, y, clipRes == splashClipAllInside); + } else { + drawStrokeSpan(&pipe, xb + 1, xa, y, + clipRes == splashClipAllInside); + } + xa = xb; + } } } } - if (clipRes != splashClipAllInside) { - if (vectorAntialias) { - state->clip->clipSpan(scanBuf, y, xMinI, xMaxI - 1, - state->strokeAdjust); - } else { - state->clip->clipSpanBinary(scanBuf, y, xMinI, xMaxI - 1, - state->strokeAdjust); - } - } - (this->*pipe.run)(&pipe, xMinI, xMaxI - 1, y, scanBuf + xMinI, NULL); + ++nClipRes[clipRes]; + } + if (nClipRes[splashClipPartial] || + (nClipRes[splashClipAllInside] && nClipRes[splashClipAllOutside])) { + opClipRes = splashClipPartial; + } else if (nClipRes[splashClipAllInside]) { + opClipRes = splashClipAllInside; + } else { + opClipRes = splashClipAllOutside; } - gfree(unscaledImage); + delete xPath; } -// The glyphMode flag is not currently used, but may be useful if the -// stroke adjustment behavior is changed. -void Splash::arbitraryTransformMask(GString *imageTag, - SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - SplashCoord *mat, GBool glyphMode, - GBool interpolate) { - SplashBitmap *scaledMask; - SplashClipResult clipRes; - SplashPipe pipe; - int scaledWidth, scaledHeight, t0, t1; - SplashCoord r00, r01, r10, r11, det, ir00, ir01, ir10, ir11; - SplashCoord vx[4], vy[4]; - int xMin, yMin, xMax, yMax; - ImageSection section[3]; - int nSections; - int bw, y, xa, xb, x, i, xx, yy; - - // compute the four vertices of the target quadrilateral - vx[0] = mat[4]; vy[0] = mat[5]; - vx[1] = mat[2] + mat[4]; vy[1] = mat[3] + mat[5]; - vx[2] = mat[0] + mat[2] + mat[4]; vy[2] = mat[1] + mat[3] + mat[5]; - vx[3] = mat[0] + mat[4]; vy[3] = mat[1] + mat[5]; - - // clipping - xMin = splashRound(vx[0]); - xMax = splashRound(vx[0]); - yMin = splashRound(vy[0]); - yMax = splashRound(vy[0]); - for (i = 1; i < 4; ++i) { - t0 = splashRound(vx[i]); - if (t0 < xMin) { - xMin = t0; - } else if (t0 > xMax) { - xMax = t0; - } - t1 = splashRound(vy[i]); - if (t1 < yMin) { - yMin = t1; - } else if (t1 > yMax) { - yMax = t1; - } - } - clipRes = state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes == splashClipAllOutside) { - return; - } +void Splash::drawStrokeSpan(SplashPipe *pipe, int x0, int x1, int y, + GBool noClip) { + int x; - // compute the scale factors - if (mat[0] >= 0) { - t0 = splashRound(mat[0] + mat[4]) - splashRound(mat[4]); - } else { - t0 = splashRound(mat[4]) - splashRound(mat[0] + mat[4]); - } - if (mat[1] >= 0) { - t1 = splashRound(mat[1] + mat[5]) - splashRound(mat[5]); - } else { - t1 = splashRound(mat[5]) - splashRound(mat[1] + mat[5]); - } - scaledWidth = t0 > t1 ? t0 : t1; - if (mat[2] >= 0) { - t0 = splashRound(mat[2] + mat[4]) - splashRound(mat[4]); - } else { - t0 = splashRound(mat[4]) - splashRound(mat[2] + mat[4]); - } - if (mat[3] >= 0) { - t1 = splashRound(mat[3] + mat[5]) - splashRound(mat[5]); - } else { - t1 = splashRound(mat[5]) - splashRound(mat[3] + mat[5]); - } - scaledHeight = t0 > t1 ? t0 : t1; - if (scaledWidth == 0) { - scaledWidth = 1; + x = state->clip->getXMinI(state->strokeAdjust); + if (x > x0) { + x0 = x; } - if (scaledHeight == 0) { - scaledHeight = 1; + x = state->clip->getXMaxI(state->strokeAdjust); + if (x < x1) { + x1 = x; } - - // compute the inverse transform (after scaling) matrix - r00 = mat[0] / scaledWidth; - r01 = mat[1] / scaledWidth; - r10 = mat[2] / scaledHeight; - r11 = mat[3] / scaledHeight; - det = r00 * r11 - r01 * r10; - if (splashAbs(det) < 1e-6) { - // this should be caught by the singular matrix check in fillImageMask + if (x0 > x1) { return; } - ir00 = r11 / det; - ir01 = -r01 / det; - ir10 = -r10 / det; - ir11 = r00 / det; - - // scale the input image - scaledMask = scaleMask(imageTag, src, srcData, srcWidth, srcHeight, - scaledWidth, scaledHeight, interpolate); - if (imageCache->vertFlip) { - vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->vertFlip = gFalse; - } - if (imageCache->horizFlip) { - horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1); - imageCache->horizFlip = gFalse; + for (x = x0; x <= x1; ++x) { + scanBuf[x] = 0xff; } - - // construct the three sections - i = 0; - if (vy[1] < vy[i]) { - i = 1; - } - if (vy[2] < vy[i]) { - i = 2; - } - if (vy[3] < vy[i]) { - i = 3; - } - // NB: if using fixed point, 0.000001 will be truncated to zero, - // so these two comparisons must be <=, not < - if (splashAbs(vy[i] - vy[(i-1) & 3]) <= 0.000001 && - vy[(i-1) & 3] < vy[(i+1) & 3]) { - i = (i-1) & 3; - } - if (splashAbs(vy[i] - vy[(i+1) & 3]) <= 0.000001) { - section[0].y0 = splashRound(vy[i]); - section[0].y1 = splashRound(vy[(i+2) & 3]) - 1; - if (vx[i] < vx[(i+1) & 3]) { - section[0].ia0 = i; - section[0].ia1 = (i+3) & 3; - section[0].ib0 = (i+1) & 3; - section[0].ib1 = (i+2) & 3; - } else { - section[0].ia0 = (i+1) & 3; - section[0].ia1 = (i+2) & 3; - section[0].ib0 = i; - section[0].ib1 = (i+3) & 3; - } - nSections = 1; - } else { - section[0].y0 = splashRound(vy[i]); - section[2].y1 = splashRound(vy[(i+2) & 3]) - 1; - section[0].ia0 = section[0].ib0 = i; - section[2].ia1 = section[2].ib1 = (i+2) & 3; - if (vx[(i+1) & 3] < vx[(i+3) & 3]) { - section[0].ia1 = section[2].ia0 = (i+1) & 3; - section[0].ib1 = section[2].ib0 = (i+3) & 3; - } else { - section[0].ia1 = section[2].ia0 = (i+3) & 3; - section[0].ib1 = section[2].ib0 = (i+1) & 3; - } - if (vy[(i+1) & 3] < vy[(i+3) & 3]) { - section[1].y0 = splashRound(vy[(i+1) & 3]); - section[2].y0 = splashRound(vy[(i+3) & 3]); - if (vx[(i+1) & 3] < vx[(i+3) & 3]) { - section[1].ia0 = (i+1) & 3; - section[1].ia1 = (i+2) & 3; - section[1].ib0 = i; - section[1].ib1 = (i+3) & 3; - } else { - section[1].ia0 = i; - section[1].ia1 = (i+3) & 3; - section[1].ib0 = (i+1) & 3; - section[1].ib1 = (i+2) & 3; - } - } else { - section[1].y0 = splashRound(vy[(i+3) & 3]); - section[2].y0 = splashRound(vy[(i+1) & 3]); - if (vx[(i+1) & 3] < vx[(i+3) & 3]) { - section[1].ia0 = i; - section[1].ia1 = (i+1) & 3; - section[1].ib0 = (i+3) & 3; - section[1].ib1 = (i+2) & 3; - } else { - section[1].ia0 = (i+3) & 3; - section[1].ia1 = (i+2) & 3; - section[1].ib0 = i; - section[1].ib1 = (i+1) & 3; - } - } - section[0].y1 = section[1].y0 - 1; - section[1].y1 = section[2].y0 - 1; - nSections = 3; - } - for (i = 0; i < nSections; ++i) { - section[i].xa0 = vx[section[i].ia0]; - section[i].ya0 = vy[section[i].ia0]; - section[i].xa1 = vx[section[i].ia1]; - section[i].ya1 = vy[section[i].ia1]; - section[i].xb0 = vx[section[i].ib0]; - section[i].yb0 = vy[section[i].ib0]; - section[i].xb1 = vx[section[i].ib1]; - section[i].yb1 = vy[section[i].ib1]; - section[i].dxdya = (section[i].xa1 - section[i].xa0) / - (section[i].ya1 - section[i].ya0); - section[i].dxdyb = (section[i].xb1 - section[i].xb0) / - (section[i].yb1 - section[i].yb0); - } - - // initialize the pixel pipe - pipeInit(&pipe, state->fillPattern, - (Guchar)splashRound(state->fillAlpha * 255), - gTrue, gFalse); - - // make sure narrow images cover at least one pixel - if (nSections == 1) { - if (section[0].y0 == section[0].y1) { - ++section[0].y1; - clipRes = opClipRes = splashClipPartial; - } - } else { - if (section[0].y0 == section[2].y1) { - ++section[1].y1; - clipRes = opClipRes = splashClipPartial; - } - } - - // scan all pixels inside the target region - bw = bitmap->width; - for (i = 0; i < nSections; ++i) { - for (y = section[i].y0; y <= section[i].y1; ++y) { - xa = splashRound(section[i].xa0 + - ((SplashCoord)y + 0.5 - section[i].ya0) * - section[i].dxdya); - xb = splashRound(section[i].xb0 + - ((SplashCoord)y + 0.5 - section[i].yb0) * - section[i].dxdyb); - if (xa > xb) { - continue; - } - // make sure narrow images cover at least one pixel - if (xa == xb) { - ++xb; - } - // check the scanBuf bounds - if (xa >= bw || xb < 0) { - continue; - } - if (xa < 0) { - xa = 0; - } - if (xb > bw) { - xb = bw; - } - // get the scan line - for (x = xa; x < xb; ++x) { - // map (x+0.5, y+0.5) back to the scaled image - xx = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir00 + - ((SplashCoord)y + 0.5 - mat[5]) * ir10); - yy = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir01 + - ((SplashCoord)y + 0.5 - mat[5]) * ir11); - // xx should always be within bounds, but floating point - // inaccuracy can cause problems - if (xx < 0) { - xx = 0; - } else if (xx >= scaledWidth) { - xx = scaledWidth - 1; - } - if (yy < 0) { - yy = 0; - } else if (yy >= scaledHeight) { - yy = scaledHeight - 1; - } - scanBuf[x] = scaledMask->data[yy * scaledWidth + xx]; - } - // clip the scan line - if (clipRes != splashClipAllInside) { - if (vectorAntialias) { - state->clip->clipSpan(scanBuf, y, xa, xb - 1, state->strokeAdjust); - } else { - state->clip->clipSpanBinary(scanBuf, y, xa, xb - 1, - state->strokeAdjust); - } - } - // draw the scan line - (this->*pipe.run)(&pipe, xa, xb - 1, y, scanBuf + xa, NULL); - } - } -} - -// Scale an image mask into a SplashBitmap. -SplashBitmap *Splash::scaleMask(GString *imageTag, - SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - GBool interpolate) { - if (imageCache->tag && imageTag && - !imageCache->tag->cmp(imageTag) && - imageCache->isMask && - imageCache->width == scaledWidth && - imageCache->height == scaledHeight && - imageCache->interpolate == interpolate) { - return imageCache->image; - } - if (imageCache->tag) { - delete imageCache->tag; - } - if (imageCache->image) { - delete imageCache->image; - } - imageCache->tag = imageTag ? imageTag->copy() : (GString *)NULL; - imageCache->isMask = gTrue; - imageCache->width = scaledWidth; - imageCache->height = scaledHeight; - imageCache->interpolate = interpolate; - imageCache->vertFlip = gFalse; - imageCache->horizFlip = gFalse; - imageCache->image = new SplashBitmap(scaledWidth, scaledHeight, - 1, splashModeMono8, gFalse, - gTrue, NULL); - if (scaledHeight < srcHeight) { - if (scaledWidth < srcWidth) { - scaleMaskYdXd(src, srcData, srcWidth, srcHeight, - scaledWidth, scaledHeight, imageCache->image); - } else { - scaleMaskYdXu(src, srcData, srcWidth, srcHeight, - scaledWidth, scaledHeight, imageCache->image); - } - } else { - if (scaledWidth < srcWidth) { - scaleMaskYuXd(src, srcData, srcWidth, srcHeight, - scaledWidth, scaledHeight, imageCache->image); - } else { - if (interpolate) { - scaleMaskYuXuI(src, srcData, srcWidth, srcHeight, - scaledWidth, scaledHeight, imageCache->image); - } else { - scaleMaskYuXu(src, srcData, srcWidth, srcHeight, - scaledWidth, scaledHeight, imageCache->image); - } + if (!noClip) { + if (!state->clip->clipSpanBinary(scanBuf, y, x0, x1, state->strokeAdjust)) { + return; } } - return imageCache->image; + (this->*pipe->run)(pipe, x0, x1, y, scanBuf + x0, NULL); } -void Splash::scaleMaskYdXd(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf; - Guint *pixBuf; - Guint pix; - Guchar *destPtr; - int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, d, d0, d1; - int i, j; - - // Bresenham parameters for y scale - yp = srcHeight / scaledHeight; - yq = srcHeight % scaledHeight; - - // Bresenham parameters for x scale - xp = srcWidth / scaledWidth; - xq = srcWidth % scaledWidth; - - // allocate buffers - lineBuf = (Guchar *)gmalloc(srcWidth); - pixBuf = (Guint *)gmallocn(srcWidth, sizeof(int)); +void Splash::strokeWide(SplashPath *path, SplashCoord w, + int lineCap, int lineJoin) { + SplashPath *path2; - // init y scale Bresenham - yt = 0; + path2 = makeStrokePath(path, w, lineCap, lineJoin, gFalse); + fillWithPattern(path2, gFalse, state->strokePattern, state->strokeAlpha); + delete path2; +} - destPtr = dest->data; - for (y = 0; y < scaledHeight; ++y) { +SplashPath *Splash::flattenPath(SplashPath *path, SplashCoord *matrix, + SplashCoord flatness) { + SplashPath *fPath; + SplashCoord flatness2; + Guchar flag; + int i; - // y scale Bresenham - if ((yt += yq) >= scaledHeight) { - yt -= scaledHeight; - yStep = yp + 1; + fPath = new SplashPath(); +#if USE_FIXEDPOINT + flatness2 = flatness; +#else + flatness2 = flatness * flatness; +#endif + i = 0; + while (i < path->length) { + flag = path->flags[i]; + if (flag & splashPathFirst) { + fPath->moveTo(path->pts[i].x, path->pts[i].y); + ++i; } else { - yStep = yp; - } - - // read rows from image - memset(pixBuf, 0, srcWidth * sizeof(int)); - for (i = 0; i < yStep; ++i) { - (*src)(srcData, lineBuf); - for (j = 0; j < srcWidth; ++j) { - pixBuf[j] += lineBuf[j]; - } - } - - // init x scale Bresenham - xt = 0; - d0 = (255 << 23) / (yStep * xp); - d1 = (255 << 23) / (yStep * (xp + 1)); - - xx = 0; - for (x = 0; x < scaledWidth; ++x) { - - // x scale Bresenham - if ((xt += xq) >= scaledWidth) { - xt -= scaledWidth; - xStep = xp + 1; - d = d1; + if (flag & splashPathCurve) { + flattenCurve(path->pts[i-1].x, path->pts[i-1].y, + path->pts[i ].x, path->pts[i ].y, + path->pts[i+1].x, path->pts[i+1].y, + path->pts[i+2].x, path->pts[i+2].y, + matrix, flatness2, fPath); + i += 3; } else { - xStep = xp; - d = d0; + fPath->lineTo(path->pts[i].x, path->pts[i].y); + ++i; } - - // compute the final pixel - pix = 0; - for (i = 0; i < xStep; ++i) { - pix += pixBuf[xx++]; + if (path->flags[i-1] & splashPathClosed) { + fPath->close(); } - // (255 * pix) / xStep * yStep - pix = (pix * d + (1 << 22)) >> 23; - - // store the pixel - *destPtr++ = (Guchar)pix; } } - - gfree(pixBuf); - gfree(lineBuf); + return fPath; } -void Splash::scaleMaskYdXu(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf; - Guint *pixBuf; - Guint pix; - Guchar *destPtr; - int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, d; - int i, j; +void Splash::flattenCurve(SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1, + SplashCoord x2, SplashCoord y2, + SplashCoord x3, SplashCoord y3, + SplashCoord *matrix, SplashCoord flatness2, + SplashPath *fPath) { + SplashCoord cx[splashMaxCurveSplits + 1][3]; + SplashCoord cy[splashMaxCurveSplits + 1][3]; + int cNext[splashMaxCurveSplits + 1]; + SplashCoord xl0, xl1, xl2, xr0, xr1, xr2, xr3, xx1, xx2, xh; + SplashCoord yl0, yl1, yl2, yr0, yr1, yr2, yr3, yy1, yy2, yh; + SplashCoord dx, dy, mx, my, tx, ty, d1, d2; + int p1, p2, p3; - // Bresenham parameters for y scale - yp = srcHeight / scaledHeight; - yq = srcHeight % scaledHeight; + // initial segment + p1 = 0; + p2 = splashMaxCurveSplits; + cx[p1][0] = x0; cy[p1][0] = y0; + cx[p1][1] = x1; cy[p1][1] = y1; + cx[p1][2] = x2; cy[p1][2] = y2; + cx[p2][0] = x3; cy[p2][0] = y3; + cNext[p1] = p2; - // Bresenham parameters for x scale - xp = scaledWidth / srcWidth; - xq = scaledWidth % srcWidth; + while (p1 < splashMaxCurveSplits) { - // allocate buffers - lineBuf = (Guchar *)gmalloc(srcWidth); - pixBuf = (Guint *)gmallocn(srcWidth, sizeof(int)); + // get the next segment + xl0 = cx[p1][0]; yl0 = cy[p1][0]; + xx1 = cx[p1][1]; yy1 = cy[p1][1]; + xx2 = cx[p1][2]; yy2 = cy[p1][2]; + p2 = cNext[p1]; + xr3 = cx[p2][0]; yr3 = cy[p2][0]; - // init y scale Bresenham - yt = 0; + // compute the distances (in device space) from the control points + // to the midpoint of the straight line (this is a bit of a hack, + // but it's much faster than computing the actual distances to the + // line) + transform(matrix, (xl0 + xr3) * 0.5, (yl0 + yr3) * 0.5, &mx, &my); + transform(matrix, xx1, yy1, &tx, &ty); +#if USE_FIXEDPOINT + d1 = splashDist(tx, ty, mx, my); +#else + dx = tx - mx; + dy = ty - my; + d1 = dx*dx + dy*dy; +#endif + transform(matrix, xx2, yy2, &tx, &ty); +#if USE_FIXEDPOINT + d2 = splashDist(tx, ty, mx, my); +#else + dx = tx - mx; + dy = ty - my; + d2 = dx*dx + dy*dy; +#endif - destPtr = dest->data; - for (y = 0; y < scaledHeight; ++y) { + // if the curve is flat enough, or no more subdivisions are + // allowed, add the straight line segment + if (p2 - p1 == 1 || (d1 <= flatness2 && d2 <= flatness2)) { + fPath->lineTo(xr3, yr3); + p1 = p2; - // y scale Bresenham - if ((yt += yq) >= scaledHeight) { - yt -= scaledHeight; - yStep = yp + 1; + // otherwise, subdivide the curve } else { - yStep = yp; - } - - // read rows from image - memset(pixBuf, 0, srcWidth * sizeof(int)); - for (i = 0; i < yStep; ++i) { - (*src)(srcData, lineBuf); - for (j = 0; j < srcWidth; ++j) { - pixBuf[j] += lineBuf[j]; - } + xl1 = splashAvg(xl0, xx1); + yl1 = splashAvg(yl0, yy1); + xh = splashAvg(xx1, xx2); + yh = splashAvg(yy1, yy2); + xl2 = splashAvg(xl1, xh); + yl2 = splashAvg(yl1, yh); + xr2 = splashAvg(xx2, xr3); + yr2 = splashAvg(yy2, yr3); + xr1 = splashAvg(xh, xr2); + yr1 = splashAvg(yh, yr2); + xr0 = splashAvg(xl2, xr1); + yr0 = splashAvg(yl2, yr1); + // add the new subdivision points + p3 = (p1 + p2) / 2; + cx[p1][1] = xl1; cy[p1][1] = yl1; + cx[p1][2] = xl2; cy[p1][2] = yl2; + cNext[p1] = p3; + cx[p3][0] = xr0; cy[p3][0] = yr0; + cx[p3][1] = xr1; cy[p3][1] = yr1; + cx[p3][2] = xr2; cy[p3][2] = yr2; + cNext[p3] = p2; } + } +} - // init x scale Bresenham - xt = 0; - d = (255 << 23) / yStep; - - for (x = 0; x < srcWidth; ++x) { - - // x scale Bresenham - if ((xt += xq) >= srcWidth) { - xt -= srcWidth; - xStep = xp + 1; - } else { - xStep = xp; - } - - // compute the final pixel - pix = pixBuf[x]; - // (255 * pix) / yStep - pix = (pix * d + (1 << 22)) >> 23; +SplashPath *Splash::makeDashedPath(SplashPath *path) { + SplashPath *dPath; + SplashCoord lineDashTotal; + SplashCoord lineDashStartPhase, lineDashDist, segLen; + SplashCoord x0, y0, x1, y1, xa, ya; + GBool lineDashStartOn, lineDashEndOn, lineDashOn, newPath; + int lineDashStartIdx, lineDashIdx, subpathStart, nDashes; + int i, j, k; - // store the pixel - for (i = 0; i < xStep; ++i) { - *destPtr++ = (Guchar)pix; + lineDashTotal = 0; + for (i = 0; i < state->lineDashLength; ++i) { + lineDashTotal += state->lineDash[i]; + } + // Acrobat simply draws nothing if the dash array is [0] + if (lineDashTotal == 0) { + return new SplashPath(); + } + lineDashStartPhase = state->lineDashPhase; + if (lineDashStartPhase > lineDashTotal * 2) { + i = splashFloor(lineDashStartPhase / (lineDashTotal * 2)); + lineDashStartPhase -= lineDashTotal * i * 2; + } else if (lineDashStartPhase < 0) { + i = splashCeil(-lineDashStartPhase / (lineDashTotal * 2)); + lineDashStartPhase += lineDashTotal * i * 2; + } + i = splashFloor(lineDashStartPhase / lineDashTotal); + lineDashStartPhase -= (SplashCoord)i * lineDashTotal; + lineDashStartOn = gTrue; + lineDashStartIdx = 0; + if (lineDashStartPhase > 0) { + while (lineDashStartPhase >= state->lineDash[lineDashStartIdx]) { + lineDashStartOn = !lineDashStartOn; + lineDashStartPhase -= state->lineDash[lineDashStartIdx]; + if (++lineDashStartIdx == state->lineDashLength) { + lineDashStartIdx = 0; } } } - gfree(pixBuf); - gfree(lineBuf); -} - -void Splash::scaleMaskYuXd(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf; - Guint pix; - Guchar *destPtr0, *destPtr; - int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, d, d0, d1; - int i; + dPath = new SplashPath(); - // Bresenham parameters for y scale - yp = scaledHeight / srcHeight; - yq = scaledHeight % srcHeight; + // process each subpath + i = 0; + while (i < path->length) { - // Bresenham parameters for x scale - xp = srcWidth / scaledWidth; - xq = srcWidth % scaledWidth; + // find the end of the subpath + for (j = i; + j < path->length - 1 && !(path->flags[j] & splashPathLast); + ++j) ; - // allocate buffers - lineBuf = (Guchar *)gmalloc(srcWidth); + // initialize the dash parameters + lineDashOn = lineDashStartOn; + lineDashEndOn = lineDashStartOn; + lineDashIdx = lineDashStartIdx; + lineDashDist = state->lineDash[lineDashIdx] - lineDashStartPhase; + subpathStart = dPath->length; + nDashes = 0; - // init y scale Bresenham - yt = 0; + // process each segment of the subpath + newPath = gTrue; + for (k = i; k < j; ++k) { - destPtr0 = dest->data; - for (y = 0; y < srcHeight; ++y) { + // grab the segment + x0 = path->pts[k].x; + y0 = path->pts[k].y; + x1 = path->pts[k+1].x; + y1 = path->pts[k+1].y; + segLen = splashDist(x0, y0, x1, y1); - // y scale Bresenham - if ((yt += yq) >= srcHeight) { - yt -= srcHeight; - yStep = yp + 1; - } else { - yStep = yp; - } + // process the segment + while (segLen > 0) { - // read row from image - (*src)(srcData, lineBuf); + // Special case for zero-length dash segments: draw a very + // short -- but not zero-length -- segment. This ensures that + // we get the correct behavior with butt and projecting line + // caps. The PS/PDF specs imply that zero-length segments are + // not drawn unless the line cap is round, but Acrobat and + // Ghostscript both draw very short segments (for butt caps) + // and squares (for projecting caps). + if (lineDashDist == 0) { + if (lineDashOn) { + if (newPath) { + dPath->moveTo(x0, y0); + newPath = gFalse; + ++nDashes; + } + xa = x0 + ((SplashCoord)0.001 / segLen) * (x1 - x0); + ya = y0 + ((SplashCoord)0.001 / segLen) * (y1 - y0); + dPath->lineTo(xa, ya); + } - // init x scale Bresenham - xt = 0; - d0 = (255 << 23) / xp; - d1 = (255 << 23) / (xp + 1); + } else if (lineDashDist >= segLen) { + if (lineDashOn) { + if (newPath) { + dPath->moveTo(x0, y0); + newPath = gFalse; + ++nDashes; + } + dPath->lineTo(x1, y1); + } + lineDashDist -= segLen; + segLen = 0; - xx = 0; - for (x = 0; x < scaledWidth; ++x) { + } else { + xa = x0 + (lineDashDist / segLen) * (x1 - x0); + ya = y0 + (lineDashDist / segLen) * (y1 - y0); + if (lineDashOn) { + if (newPath) { + dPath->moveTo(x0, y0); + newPath = gFalse; + ++nDashes; + } + dPath->lineTo(xa, ya); + } + x0 = xa; + y0 = ya; + segLen -= lineDashDist; + lineDashDist = 0; + } - // x scale Bresenham - if ((xt += xq) >= scaledWidth) { - xt -= scaledWidth; - xStep = xp + 1; - d = d1; - } else { - xStep = xp; - d = d0; - } + lineDashEndOn = lineDashOn; - // compute the final pixel - pix = 0; - for (i = 0; i < xStep; ++i) { - pix += lineBuf[xx++]; + // get the next entry in the dash array + if (lineDashDist <= 0) { + lineDashOn = !lineDashOn; + if (++lineDashIdx == state->lineDashLength) { + lineDashIdx = 0; + } + lineDashDist = state->lineDash[lineDashIdx]; + newPath = gTrue; + } } - // (255 * pix) / xStep - pix = (pix * d + (1 << 22)) >> 23; + } - // store the pixel - for (i = 0; i < yStep; ++i) { - destPtr = destPtr0 + i * scaledWidth + x; - *destPtr = (Guchar)pix; + // in a closed subpath, where the dash pattern is "on" at both the + // start and end of the subpath, we need to merge the start and + // end to get a proper line join + if ((path->flags[j] & splashPathClosed) && + lineDashStartOn && + lineDashEndOn) { + if (nDashes == 1) { + dPath->close(); + } else if (nDashes > 1) { + k = subpathStart; + do { + ++k; + dPath->lineTo(dPath->pts[k].x, dPath->pts[k].y); + } while (!(dPath->flags[k] & splashPathLast)); + ++k; + memmove(&dPath->pts[subpathStart], &dPath->pts[k], + (dPath->length - k) * sizeof(SplashPathPoint)); + memmove(&dPath->flags[subpathStart], &dPath->flags[k], + (dPath->length - k) * sizeof(Guchar)); + dPath->length -= k - subpathStart; + dPath->curSubpath -= k - subpathStart; } } - destPtr0 += yStep * scaledWidth; + i = j + 1; } - gfree(lineBuf); + return dPath; } -void Splash::scaleMaskYuXu(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf; - Guchar pix; - Guchar *srcPtr, *destPtr; - int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep; - int i; +SplashError Splash::fill(SplashPath *path, GBool eo) { + if (debugMode) { + printf("fill [eo:%d]:\n", eo); + dumpPath(path); + } + return fillWithPattern(path, eo, state->fillPattern, state->fillAlpha); +} - // Bresenham parameters for y scale - yp = scaledHeight / srcHeight; - yq = scaledHeight % srcHeight; +SplashError Splash::fillWithPattern(SplashPath *path, GBool eo, + SplashPattern *pattern, + SplashCoord alpha) { + SplashPipe pipe; + SplashPath *path2; + SplashXPath *xPath; + SplashXPathScanner *scanner; + int xMin, yMin, xMax, xMin2, xMax2, yMax, y, t; + SplashClipResult clipRes; - // Bresenham parameters for x scale - xp = scaledWidth / srcWidth; - xq = scaledWidth % srcWidth; + if (path->length == 0) { + return splashErrEmptyPath; + } + if (pathAllOutside(path)) { + opClipRes = splashClipAllOutside; + return splashOk; + } - // allocate buffers - lineBuf = (Guchar *)gmalloc(srcWidth); + path2 = tweakFillPath(path); - // init y scale Bresenham - yt = 0; + xPath = new SplashXPath(path2, state->matrix, state->flatness, gTrue, + state->enablePathSimplification, + state->strokeAdjust); + if (path2 != path) { + delete path2; + } + xMin = xPath->getXMin(); + yMin = xPath->getYMin(); + xMax = xPath->getXMax(); + yMax = xPath->getYMax(); + if (xMin > xMax || yMin > yMax) { + delete xPath; + return splashOk; + } + scanner = new SplashXPathScanner(xPath, eo, yMin, yMax); - destPtr = dest->data; - for (y = 0; y < srcHeight; ++y) { + // check clipping + if ((clipRes = state->clip->testRect(xMin, yMin, xMax, yMax, + state->strokeAdjust)) + != splashClipAllOutside) { - // y scale Bresenham - if ((yt += yq) >= srcHeight) { - yt -= srcHeight; - yStep = yp + 1; - } else { - yStep = yp; + if ((t = state->clip->getXMinI(state->strokeAdjust)) > xMin) { + xMin = t; } - - // read row from image - (*src)(srcData, lineBuf); - - // init x scale Bresenham - xt = 0; - - // generate one row - srcPtr = lineBuf; - for (x = 0; x < srcWidth; ++x) { - - // x scale Bresenham - if ((xt += xq) >= srcWidth) { - xt -= srcWidth; - xStep = xp + 1; - } else { - xStep = xp; - } - - // compute the final pixel - pix = *srcPtr ? 255 : 0; - ++srcPtr; - - // duplicate the pixel horizontally - for (i = 0; i < xStep; ++i) { - *destPtr++ = pix; - } + if ((t = state->clip->getXMaxI(state->strokeAdjust)) < xMax) { + xMax = t; } - - // duplicate the row vertically - for (i = 1 ; i < yStep; ++i) { - memcpy(destPtr, destPtr - scaledWidth, scaledWidth); - destPtr += scaledWidth; + if ((t = state->clip->getYMinI(state->strokeAdjust)) > yMin) { + yMin = t; + } + if ((t = state->clip->getYMaxI(state->strokeAdjust)) < yMax) { + yMax = t; + } + if (xMin > xMax || yMin > yMax) { + delete scanner; + delete xPath; + return splashOk; } - } - gfree(lineBuf); -} + pipeInit(&pipe, pattern, (Guchar)splashRound(alpha * 255), + gTrue, gFalse); -void Splash::scaleMaskYuXuI(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf0, *lineBuf1, *tBuf; - Guchar pix; - SplashCoord yr, xr, ys, xs, ySrc, xSrc; - int ySrc0, ySrc1, yBuf, xSrc0, xSrc1, y, x; - Guchar *destPtr; - - // ratios - yr = (SplashCoord)srcHeight / (SplashCoord)scaledHeight; - xr = (SplashCoord)srcWidth / (SplashCoord)scaledWidth; - - // allocate buffers - lineBuf0 = (Guchar *)gmalloc(scaledWidth); - lineBuf1 = (Guchar *)gmalloc(scaledWidth); - - // read first two rows - (*src)(srcData, lineBuf0); - if (srcHeight > 1) { - (*src)(srcData, lineBuf1); - yBuf = 1; - } else { - memcpy(lineBuf1, lineBuf0, srcWidth); - yBuf = 0; - } - - // interpolate first two rows - for (x = scaledWidth - 1; x >= 0; --x) { - xSrc = xr * x; - xSrc0 = splashFloor(xSrc + xr * 0.5 - 0.5); - xSrc1 = xSrc0 + 1; - xs = ((SplashCoord)xSrc1 + 0.5) - (xSrc + xr * 0.5); - if (xSrc0 < 0) { - xSrc0 = 0; - } - if (xSrc1 >= srcWidth) { - xSrc1 = srcWidth - 1; - } - lineBuf0[x] = (Guchar)(int) - ((xs * (int)lineBuf0[xSrc0] + - ((SplashCoord)1 - xs) * (int)lineBuf0[xSrc1]) * 255); - lineBuf1[x] = (Guchar)(int) - ((xs * (int)lineBuf1[xSrc0] + - ((SplashCoord)1 - xs) * (int)lineBuf1[xSrc1]) * 255); - } - - destPtr = dest->data; - for (y = 0; y < scaledHeight; ++y) { - - // compute vertical interpolation parameters - ySrc = yr * y; - ySrc0 = splashFloor(ySrc + yr * 0.5 - 0.5); - ySrc1 = ySrc0 + 1; - ys = ((SplashCoord)ySrc1 + 0.5) - (ySrc + yr * 0.5); - if (ySrc0 < 0) { - ySrc0 = 0; - ys = 1; - } - if (ySrc1 >= srcHeight) { - ySrc1 = srcHeight - 1; - ys = 0; - } - - // read another row (if necessary) - if (ySrc1 > yBuf) { - tBuf = lineBuf0; - lineBuf0 = lineBuf1; - lineBuf1 = tBuf; - (*src)(srcData, lineBuf1); - - // interpolate the row - for (x = scaledWidth - 1; x >= 0; --x) { - xSrc = xr * x; - xSrc0 = splashFloor(xSrc + xr * 0.5 - 0.5); - xSrc1 = xSrc0 + 1; - xs = ((SplashCoord)xSrc1 + 0.5) - (xSrc + xr * 0.5); - if (xSrc0 < 0) { - xSrc0 = 0; + // draw the spans + if (vectorAntialias && !inShading) { + for (y = yMin; y <= yMax; ++y) { + scanner->getSpan(scanBuf, y, xMin, xMax, &xMin2, &xMax2); + if (xMin2 <= xMax2) { + if (clipRes != splashClipAllInside) { + state->clip->clipSpan(scanBuf, y, xMin2, xMax2, + state->strokeAdjust); + } + (this->*pipe.run)(&pipe, xMin2, xMax2, y, scanBuf + xMin2, NULL); } - if (xSrc1 >= srcWidth) { - xSrc1 = srcWidth - 1; + } + } else { + for (y = yMin; y <= yMax; ++y) { + scanner->getSpanBinary(scanBuf, y, xMin, xMax, &xMin2, &xMax2); + if (xMin2 <= xMax2) { + if (clipRes != splashClipAllInside) { + state->clip->clipSpanBinary(scanBuf, y, xMin2, xMax2, + state->strokeAdjust); + } + (this->*pipe.run)(&pipe, xMin2, xMax2, y, scanBuf + xMin2, NULL); } - lineBuf1[x] = (Guchar)(int) - ((xs * (int)lineBuf1[xSrc0] + - ((SplashCoord)1 - xs) * (int)lineBuf1[xSrc1]) * 255); } - - ++yBuf; } + } + opClipRes = clipRes; - // do the vertical interpolation - for (x = 0; x < scaledWidth; ++x) { + delete scanner; + delete xPath; + return splashOk; +} - pix = (Guchar)(int)(ys * (int)lineBuf0[x] + - ((SplashCoord)1 - ys) * (int)lineBuf1[x]); +// Applies various tweaks to a fill path: +// (1) add stroke adjust hints to a filled rectangle +// (2) applies a minimum width to a zero-width filled rectangle (so +// stroke adjustment works correctly +// (3) convert a degenerate fill ('moveto lineto fill' and 'moveto +// lineto closepath fill') to a minimum-width filled rectangle +// +// These tweaks only apply to paths with a single subpath. +// +// Returns either the unchanged input path or a new path (in which +// case the returned path must be deleted by the caller). +SplashPath *Splash::tweakFillPath(SplashPath *path) { + SplashPath *path2; + SplashCoord xx0, yy0, xx1, yy1, dx, dy, d, wx, wy, w; + int n; - // store the pixel - *destPtr++ = pix; - } + if (state->strokeAdjust == splashStrokeAdjustOff || path->hints) { + return path; } - gfree(lineBuf1); - gfree(lineBuf0); -} + n = path->getLength(); + if (!((n == 2) || + (n == 3 && + path->flags[1] == 0) || + (n == 4 && + path->flags[1] == 0 && + path->flags[2] == 0) || + (n == 5 && + path->flags[1] == 0 && + path->flags[2] == 0 && + path->flags[3] == 0))) { + return path; + } -void Splash::blitMask(SplashBitmap *src, int xDest, int yDest, - SplashClipResult clipRes) { - SplashPipe pipe; - int w, h, x0, x1, y0, y1, y, t; + path2 = path; - w = src->width; - h = src->height; - pipeInit(&pipe, state->fillPattern, - (Guchar)splashRound(state->fillAlpha * 255), - gTrue, gFalse); - if (clipRes == splashClipAllInside) { - for (y = 0; y < h; ++y) { - (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y, - src->data + y * (size_t)w, NULL); - } - } else { - x0 = xDest; - if ((t = state->clip->getXMinI(state->strokeAdjust)) > x0) { - x0 = t; - } - x1 = xDest + w; - if ((t = state->clip->getXMaxI(state->strokeAdjust) + 1) < x1) { - x1 = t; - } - y0 = yDest; - if ((t = state->clip->getYMinI(state->strokeAdjust)) > y0) { - y0 = t; + // degenerate fill (2 or 3 points) or rectangle of (nearly) zero + // width --> replace with a min-width rectangle and hint + if (n == 2 || + (n == 3 && (path->flags[0] & splashPathClosed)) || + (n == 3 && (splashAbs(path->pts[0].x - path->pts[2].x) < 0.001 && + splashAbs(path->pts[0].y - path->pts[2].y) < 0.001)) || + ((n == 4 || + (n == 5 && (path->flags[0] & splashPathClosed))) && + ((splashAbs(path->pts[0].x - path->pts[1].x) < 0.001 && + splashAbs(path->pts[0].y - path->pts[1].y) < 0.001 && + splashAbs(path->pts[2].x - path->pts[3].x) < 0.001 && + splashAbs(path->pts[2].y - path->pts[3].y) < 0.001) || + (splashAbs(path->pts[0].x - path->pts[3].x) < 0.001 && + splashAbs(path->pts[0].y - path->pts[3].y) < 0.001 && + splashAbs(path->pts[1].x - path->pts[2].x) < 0.001 && + splashAbs(path->pts[1].y - path->pts[2].y) < 0.001)))) { + wx = state->matrix[0] + state->matrix[2]; + wy = state->matrix[1] + state->matrix[3]; + w = splashSqrt(wx*wx + wy*wy); + if (w < 0.001) { + w = 0; + } else { + // min width is 0.1 -- this constant is minWidth * sqrt(2) + w = (SplashCoord)0.1414 / w; } - y1 = yDest + h; - if ((t = state->clip->getYMaxI(state->strokeAdjust) + 1) < y1) { - y1 = t; + xx0 = path->pts[0].x; + yy0 = path->pts[0].y; + if (n <= 3) { + xx1 = path->pts[1].x; + yy1 = path->pts[1].y; + } else { + xx1 = path->pts[2].x; + yy1 = path->pts[2].y; } - if (x0 < x1 && y0 < y1) { - for (y = y0; y < y1; ++y) { - memcpy(scanBuf + x0, - src->data + (y - yDest) * (size_t)w + (x0 - xDest), - x1 - x0); - if (vectorAntialias) { - state->clip->clipSpan(scanBuf, y, x0, x1 - 1, - state->strokeAdjust); - } else { - state->clip->clipSpanBinary(scanBuf, y, x0, x1 - 1, - state->strokeAdjust); - } - (this->*pipe.run)(&pipe, x0, x1 - 1, y, scanBuf + x0, NULL); - } + dx = xx1 - xx0; + dy = yy1 - yy0; + d = splashSqrt(dx * dx + dy * dy); + if (d < 0.001) { + d = 0; + } else { + d = w / d; } - } -} - -SplashError Splash::drawImage(GString *imageTag, - SplashImageSource src, void *srcData, - SplashColorMode srcMode, GBool srcAlpha, - int w, int h, SplashCoord *mat, - GBool interpolate) { - GBool ok; - SplashBitmap *scaledImg; - SplashClipResult clipRes; - GBool minorAxisZero; - SplashCoord wSize, hSize, t0, t1; - int x0, y0, x1, y1, scaledWidth, scaledHeight; - int nComps; - - if (debugMode) { - printf("drawImage: srcMode=%d srcAlpha=%d w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n", - srcMode, srcAlpha, w, h, (double)mat[0], (double)mat[1], (double)mat[2], - (double)mat[3], (double)mat[4], (double)mat[5]); - } + dx *= d; + dy *= d; + path2 = new SplashPath(); + path2->moveTo(xx0 + dy, yy0 - dx); + path2->lineTo(xx1 + dy, yy1 - dx); + path2->lineTo(xx1 - dy, yy1 + dx); + path2->lineTo(xx0 - dy, yy0 + dx); + path2->close(gTrue); + path2->addStrokeAdjustHint(0, 2, 0, 4); + path2->addStrokeAdjustHint(1, 3, 0, 4); - // check color modes - ok = gFalse; // make gcc happy - nComps = 0; // make gcc happy - switch (bitmap->mode) { - case splashModeMono1: - case splashModeMono8: - ok = srcMode == splashModeMono8; - nComps = 1; - break; - case splashModeRGB8: - case splashModeBGR8: - ok = srcMode == splashModeRGB8; - nComps = 3; - break; -#if SPLASH_CMYK - case splashModeCMYK8: - ok = srcMode == splashModeCMYK8; - nComps = 4; - break; -#endif - default: - ok = gFalse; - break; - } - if (!ok) { - return splashErrModeMismatch; - } + // unclosed rectangle --> close and hint + } else if (n == 4 && !(path->flags[0] & splashPathClosed)) { + path2->close(gTrue); + path2->addStrokeAdjustHint(0, 2, 0, 4); + path2->addStrokeAdjustHint(1, 3, 0, 4); - // check for singular matrix - if (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) { - return splashErrSingularMatrix; + // closed rectangle --> hint + } else if (n == 5 && (path->flags[0] & splashPathClosed)) { + path2->addStrokeAdjustHint(0, 2, 0, 4); + path2->addStrokeAdjustHint(1, 3, 0, 4); } - minorAxisZero = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001; + return path2; +} - // rough estimate of size of scaled image - t0 = splashAbs(mat[0]); - t1 = splashAbs(mat[1]); - wSize = t0 > t1 ? t0 : t1; - t0 = splashAbs(mat[2]); - t1 = splashAbs(mat[3]); - hSize = t0 > t1 ? t0 : t1; +GBool Splash::pathAllOutside(SplashPath *path) { + SplashCoord xMin1, yMin1, xMax1, yMax1; + SplashCoord xMin2, yMin2, xMax2, yMax2; + SplashCoord x, y; + int xMinI, yMinI, xMaxI, yMaxI; + int i; - // stream-mode upscaling -- this is slower, so we only use it if the - // upscaled image is large (in which case clipping should remove - // many pixels) -#if USE_FIXEDPOINT - if ((wSize > 2 * w && hSize > 2 * h && (int)wSize > 1000000 / (int)hSize) || - (wSize > w && hSize > h && (int)wSize > 10000000 / (int)hSize) || - ((wSize > w || hSize > h) && (int)wSize > 25000000 / (int)hSize)) { -#else - if ((wSize > 2 * w && hSize > 2 * h && wSize * hSize > 1000000) || - (wSize > w && hSize > h && wSize * hSize > 10000000) || - ((wSize > w || hSize > h) && wSize * hSize > 25000000)) { -#endif - upscaleImage(src, srcData, srcMode, nComps, srcAlpha, - w, h, mat, interpolate); - - // scaling only - } else if (mat[0] > 0 && minorAxisZero && mat[3] > 0) { - getImageBounds(mat[4], mat[0] + mat[4], &x0, &x1); - getImageBounds(mat[5], mat[3] + mat[5], &y0, &y1); - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes != splashClipAllOutside) { - scaledWidth = x1 - x0; - scaledHeight = y1 - y0; - scaledImg = scaleImage(imageTag, - src, srcData, srcMode, nComps, srcAlpha, w, h, - scaledWidth, scaledHeight, interpolate); - if (imageCache->vertFlip) { - vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->vertFlip = gFalse; - } - if (imageCache->horizFlip) { - horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->horizFlip = gFalse; - } - blitImage(scaledImg, srcAlpha, x0, y0, clipRes); - } - - // scaling plus vertical flip - } else if (mat[0] > 0 && minorAxisZero && mat[3] < 0) { - getImageBounds(mat[4], mat[0] + mat[4], &x0, &x1); - getImageBounds(mat[3] + mat[5], mat[5], &y0, &y1); - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes != splashClipAllOutside) { - scaledWidth = x1 - x0; - scaledHeight = y1 - y0; - scaledImg = scaleImage(imageTag, - src, srcData, srcMode, nComps, srcAlpha, w, h, - scaledWidth, scaledHeight, interpolate); - if (!imageCache->vertFlip) { - vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->vertFlip = gTrue; - } - if (imageCache->horizFlip) { - horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->horizFlip = gFalse; - } - blitImage(scaledImg, srcAlpha, x0, y0, clipRes); + xMin1 = xMax1 = path->pts[0].x; + yMin1 = yMax1 = path->pts[0].y; + for (i = 1; i < path->length; ++i) { + if (path->pts[i].x < xMin1) { + xMin1 = path->pts[i].x; + } else if (path->pts[i].x > xMax1) { + xMax1 = path->pts[i].x; } - - // scaling plus horizontal flip - } else if (mat[0] < 0 && minorAxisZero && mat[3] > 0) { - getImageBounds(mat[0] + mat[4], mat[4], &x0, &x1); - getImageBounds(mat[5], mat[3] + mat[5], &y0, &y1); - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes != splashClipAllOutside) { - scaledWidth = x1 - x0; - scaledHeight = y1 - y0; - scaledImg = scaleImage(imageTag, - src, srcData, srcMode, nComps, srcAlpha, w, h, - scaledWidth, scaledHeight, interpolate); - if (imageCache->vertFlip) { - vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->vertFlip = gFalse; - } - if (!imageCache->horizFlip) { - horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->horizFlip = gTrue; - } - blitImage(scaledImg, srcAlpha, x0, y0, clipRes); - } - - // scaling plus horizontal and vertical flips - } else if (mat[0] < 0 && minorAxisZero && mat[3] < 0) { - getImageBounds(mat[0] + mat[4], mat[4], &x0, &x1); - getImageBounds(mat[3] + mat[5], mat[5], &y0, &y1); - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes != splashClipAllOutside) { - scaledWidth = x1 - x0; - scaledHeight = y1 - y0; - scaledImg = scaleImage(imageTag, - src, srcData, srcMode, nComps, srcAlpha, w, h, - scaledWidth, scaledHeight, interpolate); - if (!imageCache->vertFlip) { - vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->vertFlip = gTrue; - } - if (!imageCache->horizFlip) { - horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->horizFlip = gTrue; - } - blitImage(scaledImg, srcAlpha, x0, y0, clipRes); + if (path->pts[i].y < yMin1) { + yMin1 = path->pts[i].y; + } else if (path->pts[i].y > yMax1) { + yMax1 = path->pts[i].y; } + } - // all other cases - } else { - arbitraryTransformImage(imageTag, src, srcData, srcMode, nComps, srcAlpha, - w, h, mat, interpolate); + transform(state->matrix, xMin1, yMin1, &x, &y); + xMin2 = xMax2 = x; + yMin2 = yMax2 = y; + transform(state->matrix, xMin1, yMax1, &x, &y); + if (x < xMin2) { + xMin2 = x; + } else if (x > xMax2) { + xMax2 = x; + } + if (y < yMin2) { + yMin2 = y; + } else if (y > yMax2) { + yMax2 = y; + } + transform(state->matrix, xMax1, yMin1, &x, &y); + if (x < xMin2) { + xMin2 = x; + } else if (x > xMax2) { + xMax2 = x; + } + if (y < yMin2) { + yMin2 = y; + } else if (y > yMax2) { + yMax2 = y; + } + transform(state->matrix, xMax1, yMax1, &x, &y); + if (x < xMin2) { + xMin2 = x; + } else if (x > xMax2) { + xMax2 = x; + } + if (y < yMin2) { + yMin2 = y; + } else if (y > yMax2) { + yMax2 = y; } + // sanity-check the coordinates - xMinI/yMinI/xMaxI/yMaxI are + // 32-bit integers, so coords need to be < 2^31 + SplashXPath::clampCoords(&xMin2, &yMin2); + SplashXPath::clampCoords(&xMax2, &yMax2); + xMinI = splashFloor(xMin2); + yMinI = splashFloor(yMin2); + xMaxI = splashFloor(xMax2); + yMaxI = splashFloor(yMax2); - return splashOk; + return state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI, + state->strokeAdjust) == + splashClipAllOutside; } -void Splash::upscaleImage(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - SplashCoord *mat, GBool interpolate) { - SplashClipResult clipRes; - SplashPipe pipe; - SplashColorPtr unscaledImage, pixelBuf, p, q, q00, q01, q10, q11; - Guchar *unscaledAlpha, *alphaPtr; - SplashCoord xMin, yMin, xMax, yMax, t; - SplashCoord mi0, mi1, mi2, mi3, mi4, mi5, det; - SplashCoord ix, iy, sx, sy, pix0, pix1; - SplashBitmapRowSize rowSize; - int xMinI, yMinI, xMaxI, yMaxI, x, y, x0, y0, x1, y1, tt, i; - - // compute the bbox of the target quadrilateral - xMin = xMax = mat[4]; - t = mat[2] + mat[4]; - if (t < xMin) { - xMin = t; - } else if (t > xMax) { - xMax = t; - } - t = mat[0] + mat[2] + mat[4]; - if (t < xMin) { - xMin = t; - } else if (t > xMax) { - xMax = t; - } - t = mat[0] + mat[4]; - if (t < xMin) { - xMin = t; - } else if (t > xMax) { - xMax = t; - } - getImageBounds(xMin, xMax, &xMinI, &xMaxI); - yMin = yMax = mat[5]; - t = mat[3] + mat[5]; - if (t < yMin) { - yMin = t; - } else if (t > yMax) { - yMax = t; - } - t = mat[1] + mat[3] + mat[5]; - if (t < yMin) { - yMin = t; - } else if (t > yMax) { - yMax = t; - } - t = mat[1] + mat[5]; - if (t < yMin) { - yMin = t; - } else if (t > yMax) { - yMax = t; - } - getImageBounds(yMin, yMax, &yMinI, &yMaxI); - - // clipping - clipRes = state->clip->testRect(xMinI, yMinI, xMaxI - 1, yMaxI - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes == splashClipAllOutside) { - return; - } - if (clipRes != splashClipAllInside) { - if ((tt = state->clip->getXMinI(state->strokeAdjust)) > xMinI) { - xMinI = tt; - } - if ((tt = state->clip->getXMaxI(state->strokeAdjust) + 1) < xMaxI) { - xMaxI = tt; - } - if ((tt = state->clip->getYMinI(state->strokeAdjust)) > yMinI) { - yMinI = tt; - } - if ((tt = state->clip->getYMaxI(state->strokeAdjust) + 1) < yMaxI) { - yMaxI = tt; - } - } +SplashError Splash::fillChar(SplashCoord x, SplashCoord y, + int c, SplashFont *font) { + SplashGlyphBitmap glyph; + SplashCoord xt, yt; + int x0, y0, xFrac, yFrac; + SplashError err; - // invert the matrix - det = mat[0] * mat[3] - mat[1] * mat[2]; - if (splashAbs(det) < 1e-6) { - // this should be caught by the singular matrix check in fillImageMask - return; + if (debugMode) { + printf("fillChar: x=%.2f y=%.2f c=%3d=0x%02x='%c'\n", + (double)x, (double)y, c, c, c); } - det = (SplashCoord)1 / det; - mi0 = det * mat[3] * srcWidth; - mi1 = -det * mat[1] * srcHeight; - mi2 = -det * mat[2] * srcWidth; - mi3 = det * mat[0] * srcHeight; - mi4 = det * (mat[2] * mat[5] - mat[3] * mat[4]) * srcWidth; - mi5 = -det * (mat[0] * mat[5] - mat[1] * mat[4]) * srcHeight; - - // grab the image - if (srcWidth > INT_MAX / nComps) { - rowSize = -1; - } else { - rowSize = srcWidth * nComps; + transform(state->matrix, x, y, &xt, &yt); + x0 = splashFloor(xt); + xFrac = splashFloor((xt - x0) * splashFontFraction); + y0 = splashFloor(yt); + yFrac = splashFloor((yt - y0) * splashFontFraction); + if (!font->getGlyph(c, xFrac, yFrac, &glyph)) { + return splashErrNoGlyph; } - unscaledImage = (SplashColorPtr)gmallocn64(srcHeight, rowSize); - if (srcAlpha) { - unscaledAlpha = (Guchar *)gmallocn(srcHeight, srcWidth); - for (y = 0, p = unscaledImage, alphaPtr = unscaledAlpha; - y < srcHeight; - ++y, p += rowSize, alphaPtr += srcWidth) { - (*src)(srcData, p, alphaPtr); - } - } else { - unscaledAlpha = NULL; - for (y = 0, p = unscaledImage; y < srcHeight; ++y, p += rowSize) { - (*src)(srcData, p, NULL); - } + err = fillGlyph2(x0, y0, &glyph); + if (glyph.freeData) { + gfree(glyph.data); } + return err; +} - // draw it - pixelBuf = (SplashColorPtr)gmallocn(xMaxI - xMinI, nComps); - pipeInit(&pipe, NULL, - (Guchar)splashRound(state->fillAlpha * 255), - gTrue, gFalse); - for (y = yMinI; y < yMaxI; ++y) { - p = pixelBuf; - for (x = xMinI; x < xMaxI; ++x) { - ix = ((SplashCoord)x + 0.5) * mi0 + ((SplashCoord)y + 0.5) * mi2 + mi4; - iy = ((SplashCoord)x + 0.5) * mi1 + ((SplashCoord)y + 0.5) * mi3 + mi5; - if (interpolate) { - if (ix >= 0 && ix < srcWidth && iy >= 0 && iy < srcHeight) { - x0 = splashFloor(ix - 0.5); - x1 = x0 + 1; - sx = (ix - 0.5) - x0; - y0 = splashFloor(iy - 0.5); - y1 = y0 + 1; - sy = (iy - 0.5) - y0; - if (x0 < 0) { - x0 = 0; - } - if (x1 >= srcWidth) { - x1 = srcWidth - 1; - } - if (y0 < 0) { - y0 = 0; - } - if (y1 >= srcHeight) { - y1 = srcHeight - 1; - } - q00 = &unscaledImage[y0 * rowSize + (SplashBitmapRowSize)x0 * nComps]; - q01 = &unscaledImage[y0 * rowSize + (SplashBitmapRowSize)x1 * nComps]; - q10 = &unscaledImage[y1 * rowSize + (SplashBitmapRowSize)x0 * nComps]; - q11 = &unscaledImage[y1 * rowSize + (SplashBitmapRowSize)x1 * nComps]; - for (i = 0; i < nComps; ++i) { - pix0 = ((SplashCoord)1 - sx) * (int)*q00++ + sx * (int)*q01++; - pix1 = ((SplashCoord)1 - sx) * (int)*q10++ + sx * (int)*q11++; - *p++ = (Guchar)splashRound(((SplashCoord)1 - sy) * pix0 - + sy * pix1); - } - if (srcAlpha) { - pix0 = ((SplashCoord)1 - sx) - * (SplashCoord)unscaledAlpha[y0 * srcWidth + x0] - + sx * (SplashCoord)unscaledAlpha[y0 * srcWidth + x1]; - pix1 = ((SplashCoord)1 - sx) - * (SplashCoord)unscaledAlpha[y1 * srcWidth + x0] - + sx * (SplashCoord)unscaledAlpha[y1 * srcWidth + x1]; - scanBuf[x] = (Guchar)splashRound(((SplashCoord)1 - sy) * pix0 - + sy * pix1); - } else { - scanBuf[x] = 0xff; - } - } else { - for (i = 0; i < nComps; ++i) { - *p++ = 0; - } - scanBuf[x] = 0; +SplashError Splash::fillGlyph(SplashCoord x, SplashCoord y, + SplashGlyphBitmap *glyph) { + SplashCoord xt, yt; + int x0, y0; + + transform(state->matrix, x, y, &xt, &yt); + x0 = splashFloor(xt); + y0 = splashFloor(yt); + return fillGlyph2(x0, y0, glyph); +} + +SplashError Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph) { + SplashPipe pipe; + SplashClipResult clipRes; + Guchar alpha; + Guchar *p; + int xMin, yMin, xMax, yMax; + int x, y, xg, yg, xx, t; + + xg = x0 - glyph->x; + yg = y0 - glyph->y; + xMin = xg; + xMax = xg + glyph->w - 1; + yMin = yg; + yMax = yg + glyph->h - 1; + if ((clipRes = state->clip->testRect(xMin, yMin, xMax, yMax, + state->strokeAdjust)) + != splashClipAllOutside) { + pipeInit(&pipe, state->fillPattern, + (Guchar)splashRound(state->fillAlpha * 255), + gTrue, gFalse); + if (clipRes == splashClipAllInside) { + if (glyph->aa) { + p = glyph->data; + for (y = yMin; y <= yMax; ++y) { + (this->*pipe.run)(&pipe, xMin, xMax, y, + glyph->data + (y - yMin) * glyph->w, NULL); } } else { - x0 = splashFloor(ix); - y0 = splashFloor(iy); - if (x0 >= 0 && x0 < srcWidth && y0 >= 0 && y0 < srcHeight) { - q = &unscaledImage[y0 * rowSize + (SplashBitmapRowSize)x0 * nComps]; - for (i = 0; i < nComps; ++i) { - *p++ = *q++; + p = glyph->data; + for (y = yMin; y <= yMax; ++y) { + for (x = xMin; x <= xMax; x += 8) { + alpha = *p++; + for (xx = 0; xx < 8 && x + xx <= xMax; ++xx) { + scanBuf[x + xx] = (alpha & 0x80) ? 0xff : 0x00; + alpha = (Guchar)(alpha << 1); + } } - if (srcAlpha) { - scanBuf[x] = unscaledAlpha[y0 * srcWidth + x0]; - } else { - scanBuf[x] = 0xff; + (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); + } + } + } else { + if ((t = state->clip->getXMinI(state->strokeAdjust)) > xMin) { + xMin = t; + } + if ((t = state->clip->getXMaxI(state->strokeAdjust)) < xMax) { + xMax = t; + } + if ((t = state->clip->getYMinI(state->strokeAdjust)) > yMin) { + yMin = t; + } + if ((t = state->clip->getYMaxI(state->strokeAdjust)) < yMax) { + yMax = t; + } + if (xMin <= xMax && yMin <= yMax) { + if (glyph->aa) { + for (y = yMin; y <= yMax; ++y) { + p = glyph->data + (y - yg) * glyph->w + (xMin - xg); + memcpy(scanBuf + xMin, p, xMax - xMin + 1); + state->clip->clipSpan(scanBuf, y, xMin, xMax, + state->strokeAdjust); + (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); } } else { - for (i = 0; i < nComps; ++i) { - *p++ = 0; + for (y = yMin; y <= yMax; ++y) { + p = glyph->data + (y - yg) * ((glyph->w + 7) >> 3) + + ((xMin - xg) >> 3); + alpha = *p++; + xx = (xMin - xg) & 7; + alpha = (Guchar)(alpha << xx); + for (x = xMin; xx < 8 && x <= xMax; ++x, ++xx) { + scanBuf[x] = (alpha & 0x80) ? 255 : 0; + alpha = (Guchar)(alpha << 1); + } + for (; x <= xMax; x += 8) { + alpha = *p++; + for (xx = 0; xx < 8 && x + xx <= xMax; ++xx) { + scanBuf[x + xx] = (alpha & 0x80) ? 255 : 0; + alpha = (Guchar)(alpha << 1); + } + } + state->clip->clipSpanBinary(scanBuf, y, xMin, xMax, + state->strokeAdjust); + (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); } - scanBuf[x] = 0; } } } - if (clipRes != splashClipAllInside) { - if (vectorAntialias) { - state->clip->clipSpan(scanBuf, y, xMinI, xMaxI - 1, - state->strokeAdjust); - } else { - state->clip->clipSpanBinary(scanBuf, y, xMinI, xMaxI - 1, - state->strokeAdjust); - } + } + opClipRes = clipRes; + + return splashOk; +} + +void Splash::getImageBounds(SplashCoord xyMin, SplashCoord xyMax, + int *xyMinI, int *xyMaxI) { + if (state->strokeAdjust == splashStrokeAdjustOff) { + // make sure the coords fit in 32-bit ints +#if USE_FIXEDPOINT + if (xyMin < -32767) { + xyMin = -32767; + } else if (xyMin > 32767) { + xyMin = 32767; + } + if (xyMax < -32767) { + xyMax = -32767; + } else if (xyMax > 32767) { + xyMax = 32767; + } +#else + if (xyMin < -1e9) { + xyMin = -1e9; + } else if (xyMin > 1e9) { + xyMin = 1e9; + } + if (xyMax < -1e9) { + xyMax = -1e9; + } else if (xyMax > 1e9) { + xyMax = 1e9; + } +#endif + *xyMinI = splashFloor(xyMin); + *xyMaxI = splashFloor(xyMax); + if (*xyMaxI <= *xyMinI) { + *xyMaxI = *xyMinI + 1; } - (this->*pipe.run)(&pipe, xMinI, xMaxI - 1, y, scanBuf + xMinI, pixelBuf); + } else { + splashStrokeAdjust(xyMin, xyMax, xyMinI, xyMaxI, state->strokeAdjust); } - - gfree(pixelBuf); - gfree(unscaledImage); - gfree(unscaledAlpha); } -void Splash::arbitraryTransformImage(GString *imageTag, - SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, - int srcWidth, int srcHeight, - SplashCoord *mat, GBool interpolate) { - SplashBitmap *scaledImg; - SplashClipResult clipRes; +struct SplashDrawImageMaskRowData { SplashPipe pipe; - SplashColorPtr pixelBuf; - int scaledWidth, scaledHeight, t0, t1; - SplashCoord r00, r01, r10, r11, det, ir00, ir01, ir10, ir11; - SplashCoord vx[4], vy[4]; - int xMin, yMin, xMax, yMax; - ImageSection section[3]; - int nSections; - int y, xa, xb, x, i, xx, yy; - - // compute the four vertices of the target quadrilateral - vx[0] = mat[4]; vy[0] = mat[5]; - vx[1] = mat[2] + mat[4]; vy[1] = mat[3] + mat[5]; - vx[2] = mat[0] + mat[2] + mat[4]; vy[2] = mat[1] + mat[3] + mat[5]; - vx[3] = mat[0] + mat[4]; vy[3] = mat[1] + mat[5]; - - // clipping - xMin = splashRound(vx[0]); - xMax = splashRound(vx[0]); - yMin = splashRound(vy[0]); - yMax = splashRound(vy[0]); - for (i = 1; i < 4; ++i) { - t0 = splashRound(vx[i]); - if (t0 < xMin) { - xMin = t0; - } else if (t0 > xMax) { - xMax = t0; - } - t1 = splashRound(vy[i]); - if (t1 < yMin) { - yMin = t1; - } else if (t1 > yMax) { - yMax = t1; - } - } - clipRes = state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1, - state->strokeAdjust); - opClipRes = clipRes; - if (clipRes == splashClipAllOutside) { - return; - } - - // compute the scale factors - if (mat[0] >= 0) { - t0 = splashRound(mat[0] + mat[4]) - splashRound(mat[4]); - } else { - t0 = splashRound(mat[4]) - splashRound(mat[0] + mat[4]); - } - if (mat[1] >= 0) { - t1 = splashRound(mat[1] + mat[5]) - splashRound(mat[5]); - } else { - t1 = splashRound(mat[5]) - splashRound(mat[1] + mat[5]); - } - scaledWidth = t0 > t1 ? t0 : t1; - if (mat[2] >= 0) { - t0 = splashRound(mat[2] + mat[4]) - splashRound(mat[4]); - } else { - t0 = splashRound(mat[4]) - splashRound(mat[2] + mat[4]); - } - if (mat[3] >= 0) { - t1 = splashRound(mat[3] + mat[5]) - splashRound(mat[5]); - } else { - t1 = splashRound(mat[5]) - splashRound(mat[3] + mat[5]); - } - scaledHeight = t0 > t1 ? t0 : t1; - if (scaledWidth == 0) { - scaledWidth = 1; - } - if (scaledHeight == 0) { - scaledHeight = 1; - } +}; - // compute the inverse transform (after scaling) matrix - r00 = mat[0] / scaledWidth; - r01 = mat[1] / scaledWidth; - r10 = mat[2] / scaledHeight; - r11 = mat[3] / scaledHeight; - det = r00 * r11 - r01 * r10; - if (splashAbs(det) < 1e-6) { - // this should be caught by the singular matrix check in drawImage - return; +// The glyphMode flag is not currently used, but may be useful if the +// stroke adjustment behavior is changed. +SplashError Splash::fillImageMask(GString *imageTag, + SplashImageMaskSource src, void *srcData, + int w, int h, SplashCoord *mat, + GBool glyphMode, GBool interpolate) { + if (debugMode) { + printf("fillImageMask: w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n", + w, h, (double)mat[0], (double)mat[1], (double)mat[2], + (double)mat[3], (double)mat[4], (double)mat[5]); } - ir00 = r11 / det; - ir01 = -r01 / det; - ir10 = -r10 / det; - ir11 = r00 / det; - // scale the input image - scaledImg = scaleImage(imageTag, src, srcData, srcMode, nComps, srcAlpha, - srcWidth, srcHeight, scaledWidth, scaledHeight, - interpolate); - if (imageCache->vertFlip) { - vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->vertFlip = gFalse; - } - if (imageCache->horizFlip) { - horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - imageCache->horizFlip = gFalse; + //--- check for singular matrix + if (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) { + return splashErrSingularMatrix; } - // construct the three sections - i = 0; - if (vy[1] < vy[i]) { - i = 1; - } - if (vy[2] < vy[i]) { - i = 2; - } - if (vy[3] < vy[i]) { - i = 3; - } - // NB: if using fixed point, 0.000001 will be truncated to zero, - // so these two comparisons must be <=, not < - if (splashAbs(vy[i] - vy[(i-1) & 3]) <= 0.000001 && - vy[(i-1) & 3] < vy[(i+1) & 3]) { - i = (i-1) & 3; - } - if (splashAbs(vy[i] - vy[(i+1) & 3]) <= 0.000001) { - section[0].y0 = splashRound(vy[i]); - section[0].y1 = splashRound(vy[(i+2) & 3]) - 1; - if (vx[i] < vx[(i+1) & 3]) { - section[0].ia0 = i; - section[0].ia1 = (i+3) & 3; - section[0].ib0 = (i+1) & 3; - section[0].ib1 = (i+2) & 3; + //--- compute image bbox, check clipping + GBool flipsOnly = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001; + GBool horizFlip = gFalse; + GBool vertFlip = gFalse; + int xMin, yMin, xMax, yMax; + if (flipsOnly) { + horizFlip = mat[0] < 0; + vertFlip = mat[3] < 0; + if (vertFlip) { + if (horizFlip) { // bottom-up, mirrored + getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax); + getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax); + } else { // bottom-up + getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax); + getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax); + } } else { - section[0].ia0 = (i+1) & 3; - section[0].ia1 = (i+2) & 3; - section[0].ib0 = i; - section[0].ib1 = (i+3) & 3; + if (horizFlip) { // top-down, mirrored + getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax); + getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax); + } else { // top-down + getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax); + getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax); + } } - nSections = 1; } else { - section[0].y0 = splashRound(vy[i]); - section[2].y1 = splashRound(vy[(i+2) & 3]) - 1; - section[0].ia0 = section[0].ib0 = i; - section[2].ia1 = section[2].ib1 = (i+2) & 3; - if (vx[(i+1) & 3] < vx[(i+3) & 3]) { - section[0].ia1 = section[2].ia0 = (i+1) & 3; - section[0].ib1 = section[2].ib0 = (i+3) & 3; - } else { - section[0].ia1 = section[2].ia0 = (i+3) & 3; - section[0].ib1 = section[2].ib0 = (i+1) & 3; - } - if (vy[(i+1) & 3] < vy[(i+3) & 3]) { - section[1].y0 = splashRound(vy[(i+1) & 3]); - section[2].y0 = splashRound(vy[(i+3) & 3]); - if (vx[(i+1) & 3] < vx[(i+3) & 3]) { - section[1].ia0 = (i+1) & 3; - section[1].ia1 = (i+2) & 3; - section[1].ib0 = i; - section[1].ib1 = (i+3) & 3; - } else { - section[1].ia0 = i; - section[1].ia1 = (i+3) & 3; - section[1].ib0 = (i+1) & 3; - section[1].ib1 = (i+2) & 3; - } - } else { - section[1].y0 = splashRound(vy[(i+3) & 3]); - section[2].y0 = splashRound(vy[(i+1) & 3]); - if (vx[(i+1) & 3] < vx[(i+3) & 3]) { - section[1].ia0 = i; - section[1].ia1 = (i+1) & 3; - section[1].ib0 = (i+3) & 3; - section[1].ib1 = (i+2) & 3; - } else { - section[1].ia0 = (i+3) & 3; - section[1].ia1 = (i+2) & 3; - section[1].ib0 = i; - section[1].ib1 = (i+1) & 3; - } - } - section[0].y1 = section[1].y0 - 1; - section[1].y1 = section[2].y0 - 1; - nSections = 3; - } - for (i = 0; i < nSections; ++i) { - section[i].xa0 = vx[section[i].ia0]; - section[i].ya0 = vy[section[i].ia0]; - section[i].xa1 = vx[section[i].ia1]; - section[i].ya1 = vy[section[i].ia1]; - section[i].xb0 = vx[section[i].ib0]; - section[i].yb0 = vy[section[i].ib0]; - section[i].xb1 = vx[section[i].ib1]; - section[i].yb1 = vy[section[i].ib1]; - section[i].dxdya = (section[i].xa1 - section[i].xa0) / - (section[i].ya1 - section[i].ya0); - section[i].dxdyb = (section[i].xb1 - section[i].xb0) / - (section[i].yb1 - section[i].yb0); - } - - // initialize the pixel pipe - pipeInit(&pipe, NULL, + int xx = splashRound(mat[4]); // (0,0) + int yy = splashRound(mat[5]); + xMin = xMax = xx; + yMin = yMax = yy; + xx = splashRound(mat[0] + mat[4]); // (1,0) + yy = splashRound(mat[1] + mat[5]); + if (xx < xMin) { + xMin = xx; + } else if (xx > xMax) { + xMax = xx; + } + if (yy < yMin) { + yMin = yy; + } else if (yy > yMax) { + yMax = yy; + } + xx = splashRound(mat[2] + mat[4]); // (0,1) + yy = splashRound(mat[3] + mat[5]); + if (xx < xMin) { + xMin = xx; + } else if (xx > xMax) { + xMax = xx; + } + if (yy < yMin) { + yMin = yy; + } else if (yy > yMax) { + yMax = yy; + } + xx = splashRound(mat[0] + mat[2] + mat[4]); // (1,1) + yy = splashRound(mat[1] + mat[3] + mat[5]); + if (xx < xMin) { + xMin = xx; + } else if (xx > xMax) { + xMax = xx; + } + if (yy < yMin) { + yMin = yy; + } else if (yy > yMax) { + yMax = yy; + } + if (xMax <= xMin) { + xMax = xMin + 1; + } + if (yMax <= yMin) { + yMax = yMin + 1; + } + } + SplashClipResult clipRes = + state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1, + state->strokeAdjust); + // If the scaled mask is much wider and/or taller than the clip + // region, we use the "arbitrary" scaling path, to avoid a + // potentially very slow loop in the flips-only path (which scans + // the full width and height of the scaled mask, regardless of the + // clip region). + int clipW = state->clip->getXMaxI(state->strokeAdjust) + - state->clip->getXMinI(state->strokeAdjust); + int clipH = state->clip->getYMaxI(state->strokeAdjust) + - state->clip->getYMinI(state->strokeAdjust); + GBool veryLarge = ((xMax - xMin) / 8 > clipW && xMax - xMin > 1000) || + ((yMax - yMin) / 8 > clipH && yMax - yMin > 1000); + + //--- set up the SplashDrawImageMaskRowData object and the pipes + SplashDrawImageMaskRowData dd; + pipeInit(&dd.pipe, state->fillPattern, (Guchar)splashRound(state->fillAlpha * 255), gTrue, gFalse); - // make sure narrow images cover at least one pixel - if (nSections == 1) { - if (section[0].y0 == section[0].y1) { - ++section[0].y1; - clipRes = opClipRes = splashClipPartial; - } + //--- choose the drawRow function + SplashDrawImageMaskRowFunc drawRowFunc; + if (clipRes == splashClipAllInside) { + drawRowFunc = &Splash::drawImageMaskRowNoClip; } else { - if (section[0].y0 == section[2].y1) { - ++section[1].y1; - clipRes = opClipRes = splashClipPartial; - } - } - - pixelBuf = (SplashColorPtr)gmallocn(xMax - xMin + 1, bitmapComps); - - // scan all pixels inside the target region - for (i = 0; i < nSections; ++i) { - for (y = section[i].y0; y <= section[i].y1; ++y) { - xa = splashRound(section[i].xa0 + - ((SplashCoord)y + 0.5 - section[i].ya0) * - section[i].dxdya); - xb = splashRound(section[i].xb0 + - ((SplashCoord)y + 0.5 - section[i].yb0) * - section[i].dxdyb); - if (xa > xb) { - continue; - } - // make sure narrow images cover at least one pixel - if (xa == xb) { - ++xb; - } - // check the scanBuf bounds - if (xa >= bitmap->width || xb < 0) { - continue; - } - if (xa < 0) { - xa = 0; - } - if (xb > bitmap->width) { - xb = bitmap->width; - } - // clip the scan line - memset(scanBuf + xa, 0xff, xb - xa); - if (clipRes != splashClipAllInside) { - if (vectorAntialias) { - state->clip->clipSpan(scanBuf, y, xa, xb - 1, - state->strokeAdjust); - } else { - state->clip->clipSpanBinary(scanBuf, y, xa, xb - 1, - state->strokeAdjust); - } - } - // draw the scan line - for (x = xa; x < xb; ++x) { - // map (x+0.5, y+0.5) back to the scaled image - xx = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir00 + - ((SplashCoord)y + 0.5 - mat[5]) * ir10); - yy = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir01 + - ((SplashCoord)y + 0.5 - mat[5]) * ir11); - // xx should always be within bounds, but floating point - // inaccuracy can cause problems - if (xx < 0) { - xx = 0; - } else if (xx >= scaledWidth) { - xx = scaledWidth - 1; - } - if (yy < 0) { - yy = 0; - } else if (yy >= scaledHeight) { - yy = scaledHeight - 1; - } - // get the color - scaledImg->getPixel(xx, yy, pixelBuf + (x - xa) * bitmapComps); - // apply alpha - if (srcAlpha) { - scanBuf[x] = div255(scanBuf[x] * - scaledImg->alpha[yy * scaledWidth + xx]); - } - } - (this->*pipe.run)(&pipe, xa, xb - 1, y, scanBuf + xa, pixelBuf); - } - } - - gfree(pixelBuf); -} - -// Scale an image into a SplashBitmap. -SplashBitmap *Splash::scaleImage(GString *imageTag, - SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - GBool interpolate) { - if (imageCache->tag && imageTag && - !imageCache->tag->cmp(imageTag) && - !imageCache->isMask && - imageCache->width == scaledWidth && - imageCache->height == scaledHeight && - imageCache->mode == srcMode && - imageCache->alpha == srcAlpha && - imageCache->interpolate == interpolate) { - return imageCache->image; - } - if (imageCache->tag) { - delete imageCache->tag; - } - if (imageCache->image) { - delete imageCache->image; - } - imageCache->tag = imageTag ? imageTag->copy() : (GString *)NULL; - imageCache->isMask = gFalse; - imageCache->width = scaledWidth; - imageCache->height = scaledHeight; - imageCache->mode = srcMode; - imageCache->alpha = srcAlpha; - imageCache->interpolate = interpolate; - imageCache->vertFlip = gFalse; - imageCache->horizFlip = gFalse; - imageCache->image = new SplashBitmap(scaledWidth, scaledHeight, 1, - srcMode, srcAlpha, gTrue, NULL); - if (scaledHeight < srcHeight) { - if (scaledWidth < srcWidth) { - scaleImageYdXd(src, srcData, srcMode, nComps, srcAlpha, - srcWidth, srcHeight, scaledWidth, scaledHeight, - imageCache->image); + if (vectorAntialias) { + drawRowFunc = &Splash::drawImageMaskRowClipAA; } else { - scaleImageYdXu(src, srcData, srcMode, nComps, srcAlpha, - srcWidth, srcHeight, scaledWidth, scaledHeight, - imageCache->image); + drawRowFunc = &Splash::drawImageMaskRowClipNoAA; } - } else { - if (scaledWidth < srcWidth) { - scaleImageYuXd(src, srcData, srcMode, nComps, srcAlpha, - srcWidth, srcHeight, scaledWidth, scaledHeight, - imageCache->image); - } else { - if (interpolate) { - scaleImageYuXuI(src, srcData, srcMode, nComps, srcAlpha, - srcWidth, srcHeight, scaledWidth, scaledHeight, - imageCache->image); + } + + //--- horizontal/vertical flips only + if (flipsOnly && !veryLarge) { + if (clipRes != splashClipAllOutside) { + int scaledWidth = xMax - xMin; + int scaledHeight = yMax - yMin; + ImageMaskScaler scaler(src, srcData, w, h, + scaledWidth, scaledHeight, interpolate); + Guchar *tmpLine = NULL; + if (horizFlip) { + tmpLine = (Guchar *)gmalloc(scaledWidth); + } + if (vertFlip) { + if (horizFlip) { // bottom-up, mirrored + for (int y = 0; y < scaledHeight; ++y) { + scaler.nextLine(); + mirrorImageMaskRow(scaler.data(), tmpLine, scaledWidth); + (this->*drawRowFunc)(&dd, tmpLine, + xMin, yMax - 1 - y, scaledWidth); + } + } else { // bottom-up + for (int y = 0; y < scaledHeight; ++y) { + scaler.nextLine(); + (this->*drawRowFunc)(&dd, scaler.data(), + xMin, yMax - 1 - y, scaledWidth); + } + } } else { - scaleImageYuXu(src, srcData, srcMode, nComps, srcAlpha, - srcWidth, srcHeight, scaledWidth, scaledHeight, - imageCache->image); + if (horizFlip) { // top-down, mirrored + for (int y = 0; y < scaledHeight; ++y) { + scaler.nextLine(); + mirrorImageMaskRow(scaler.data(), tmpLine, scaledWidth); + (this->*drawRowFunc)(&dd, tmpLine, + xMin, yMin + y, scaledWidth); + } + } else { // top-down + for (int y = 0; y < scaledHeight; ++y) { + scaler.nextLine(); + (this->*drawRowFunc)(&dd, scaler.data(), + xMin, yMin + y, scaledWidth); + } + } } + gfree(tmpLine); } - } - return imageCache->image; -} -void Splash::scaleImageYdXd(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf, *alphaLineBuf; - Guint *pixBuf, *alphaPixBuf; - Guint pix0, pix1, pix2; -#if SPLASH_CMYK - Guint pix3; -#endif - Guint alpha; - Guchar *destPtr, *destAlphaPtr; - int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, xxa, d, d0, d1; - int i, j; - - // Bresenham parameters for y scale - yp = srcHeight / scaledHeight; - yq = srcHeight % scaledHeight; - - // Bresenham parameters for x scale - xp = srcWidth / scaledWidth; - xq = srcWidth % scaledWidth; - - // allocate buffers - lineBuf = (Guchar *)gmallocn(srcWidth, nComps); - pixBuf = (Guint *)gmallocn(srcWidth, (int)(nComps * sizeof(int))); - if (srcAlpha) { - alphaLineBuf = (Guchar *)gmalloc(srcWidth); - alphaPixBuf = (Guint *)gmallocn(srcWidth, sizeof(int)); + //--- arbitrary transform } else { - alphaLineBuf = NULL; - alphaPixBuf = NULL; - } - - // init y scale Bresenham - yt = 0; - - destPtr = dest->data; - destAlphaPtr = dest->alpha; - for (y = 0; y < scaledHeight; ++y) { - - // y scale Bresenham - if ((yt += yq) >= scaledHeight) { - yt -= scaledHeight; - yStep = yp + 1; - } else { - yStep = yp; - } - - // read rows from image - memset(pixBuf, 0, srcWidth * nComps * sizeof(int)); - if (srcAlpha) { - memset(alphaPixBuf, 0, srcWidth * sizeof(int)); - } - for (i = 0; i < yStep; ++i) { - (*src)(srcData, lineBuf, alphaLineBuf); - for (j = 0; j < srcWidth * nComps; ++j) { - pixBuf[j] += lineBuf[j]; + // estimate of size of scaled image + int scaledWidth = splashRound(splashSqrt(mat[0] * mat[0] + + mat[1] * mat[1])); + int scaledHeight = splashRound(splashSqrt(mat[2] * mat[2] + + mat[3] * mat[3])); + if (scaledWidth < 1) { + scaledWidth = 1; + } + if (scaledHeight < 1) { + scaledHeight = 1; + } + GBool downscaling = gTrue; + if (veryLarge || (scaledWidth >= w && scaledHeight >= h)) { + downscaling = gFalse; + scaledWidth = w; + scaledHeight = h; + } + + // compute mapping from device space to scaled image space + SplashCoord mat1[6]; + mat1[0] = mat[0] / scaledWidth; + mat1[1] = mat[1] / scaledWidth; + mat1[2] = mat[2] / scaledHeight; + mat1[3] = mat[3] / scaledHeight; + mat1[4] = mat[4]; + mat1[5] = mat[5]; + SplashCoord det = mat1[0] * mat1[3] - mat1[1] * mat1[2]; + if (splashAbs(det) < 1e-6) { + // this should be caught by the singular matrix check in drawImage + return splashErrSingularMatrix; + } + SplashCoord invMat[6]; + invMat[0] = mat1[3] / det; + invMat[1] = -mat1[1] / det; + invMat[2] = -mat1[2] / det; + invMat[3] = mat1[0] / det; + // the extra "+ 0.5 * (...)" terms are here because the + // drawImageArbitrary(No)Interp functions multiply by pixel + // centers, (x + 0.5, y + 0.5) + invMat[4] = (mat1[2] * mat1[5] - mat1[3] * mat1[4]) / det + + (invMat[0] + invMat[2]) * 0.5; + invMat[5] = (mat1[1] * mat1[4] - mat1[0] * mat1[5]) / det + + (invMat[1] + invMat[3]) * 0.5; + + // if downscaling: store the downscaled image mask + // if upscaling: store the unscaled image mask + Guchar *scaledMask = (Guchar *)gmallocn(scaledHeight, scaledWidth); + if (downscaling) { + ImageMaskScaler scaler(src, srcData, w, h, + scaledWidth, scaledHeight, interpolate); + Guchar *ptr = scaledMask; + for (int y = 0; y < scaledHeight; ++y) { + scaler.nextLine(); + memcpy(ptr, scaler.data(), scaledWidth); + ptr += scaledWidth; } - if (srcAlpha) { - for (j = 0; j < srcWidth; ++j) { - alphaPixBuf[j] += alphaLineBuf[j]; + } else { + Guchar *ptr = scaledMask; + for (int y = 0; y < scaledHeight; ++y) { + (*src)(srcData, ptr); + for (int x = 0; x < scaledWidth; ++x) { + *ptr = (Guchar)(*ptr * 255); + ++ptr; } } } - // init x scale Bresenham - xt = 0; - d0 = (1 << 23) / (yStep * xp); - d1 = (1 << 23) / (yStep * (xp + 1)); - - xx = xxa = 0; - for (x = 0; x < scaledWidth; ++x) { - - // x scale Bresenham - if ((xt += xq) >= scaledWidth) { - xt -= scaledWidth; - xStep = xp + 1; - d = d1; - } else { - xStep = xp; - d = d0; - } - - switch (srcMode) { - - case splashModeMono8: - - // compute the final pixel - pix0 = 0; - for (i = 0; i < xStep; ++i) { - pix0 += pixBuf[xx++]; - } - // pix / xStep * yStep - pix0 = (pix0 * d + (1 << 22)) >> 23; - - // store the pixel - *destPtr++ = (Guchar)pix0; - break; - - case splashModeRGB8: - - // compute the final pixel - pix0 = pix1 = pix2 = 0; - for (i = 0; i < xStep; ++i) { - pix0 += pixBuf[xx]; - pix1 += pixBuf[xx+1]; - pix2 += pixBuf[xx+2]; - xx += 3; - } - // pix / xStep * yStep - pix0 = (pix0 * d + (1 << 22)) >> 23; - pix1 = (pix1 * d + (1 << 22)) >> 23; - pix2 = (pix2 * d + (1 << 22)) >> 23; - - // store the pixel - *destPtr++ = (Guchar)pix0; - *destPtr++ = (Guchar)pix1; - *destPtr++ = (Guchar)pix2; - break; - -#if SPLASH_CMYK - case splashModeCMYK8: + // draw it + if (interpolate) { + drawImageMaskArbitraryInterp(scaledMask, + &dd, drawRowFunc, invMat, + scaledWidth, scaledHeight, + xMin, yMin, xMax, yMax); + } else { + drawImageMaskArbitraryNoInterp(scaledMask, + &dd, drawRowFunc, invMat, + scaledWidth, scaledHeight, + xMin, yMin, xMax, yMax); + } - // compute the final pixel - pix0 = pix1 = pix2 = pix3 = 0; - for (i = 0; i < xStep; ++i) { - pix0 += pixBuf[xx]; - pix1 += pixBuf[xx+1]; - pix2 += pixBuf[xx+2]; - pix3 += pixBuf[xx+3]; - xx += 4; - } - // pix / xStep * yStep - pix0 = (pix0 * d + (1 << 22)) >> 23; - pix1 = (pix1 * d + (1 << 22)) >> 23; - pix2 = (pix2 * d + (1 << 22)) >> 23; - pix3 = (pix3 * d + (1 << 22)) >> 23; - - // store the pixel - *destPtr++ = (Guchar)pix0; - *destPtr++ = (Guchar)pix1; - *destPtr++ = (Guchar)pix2; - *destPtr++ = (Guchar)pix3; - break; -#endif + // free the downscaled/unscaled image + gfree(scaledMask); + } + return splashOk; +} - case splashModeMono1: // mono1 is not allowed - case splashModeBGR8: // bgr8 is not allowed - default: - break; - } +void Splash::drawImageMaskArbitraryNoInterp( + Guchar *scaledMask, + SplashDrawImageMaskRowData *dd, + SplashDrawImageMaskRowFunc drawRowFunc, + SplashCoord *invMat, + int scaledWidth, int scaledHeight, + int xMin, int yMin, int xMax, int yMax) { + int tt = state->clip->getXMinI(state->strokeAdjust); + if (tt > xMin) { + xMin = tt; + } + tt = state->clip->getXMaxI(state->strokeAdjust) + 1; + if (tt < xMax) { + xMax = tt; + } + tt = state->clip->getYMinI(state->strokeAdjust); + if (tt > yMin) { + yMin = tt; + } + tt = state->clip->getYMaxI(state->strokeAdjust) + 1; + if (tt < yMax) { + yMax = tt; + } + if (xMax <= xMin || yMax <= yMin) { + return; + } - // process alpha - if (srcAlpha) { - alpha = 0; - for (i = 0; i < xStep; ++i, ++xxa) { - alpha += alphaPixBuf[xxa]; + Guchar *buf = (Guchar *)gmalloc(xMax - xMin); + + for (int y = yMin; y < yMax; ++y) { + int rowMin = xMax; + int rowMax = 0; + for (int x = xMin; x < xMax; ++x) { + // note: invMat includes a "+0.5" factor so that this is really + // a multiply by (x+0.5, y+0.5) + int xx = splashFloor((SplashCoord)x * invMat[0] + + (SplashCoord)y * invMat[2] + invMat[4]); + int yy = splashFloor((SplashCoord)x * invMat[1] + + (SplashCoord)y * invMat[3] + invMat[5]); + if (xx >= 0 && xx < scaledWidth && + yy >= 0 && yy < scaledHeight) { + Guchar *p = scaledMask + (yy * scaledWidth + xx); + Guchar *q = buf + (x - xMin); + *q = *p; + if (x < rowMin) { + rowMin = x; } - // alpha / xStep * yStep - alpha = (alpha * d + (1 << 22)) >> 23; - *destAlphaPtr++ = (Guchar)alpha; + rowMax = x + 1; } } + if (rowMin < rowMax) { + (this->*drawRowFunc)(dd, buf + (rowMin - xMin), + rowMin, y, rowMax - rowMin); + } } - gfree(alphaPixBuf); - gfree(alphaLineBuf); - gfree(pixBuf); - gfree(lineBuf); + gfree(buf); } -void Splash::scaleImageYdXu(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf, *alphaLineBuf; - Guint *pixBuf, *alphaPixBuf; - Guint pix[splashMaxColorComps]; - Guint alpha; - Guchar *destPtr, *destAlphaPtr; - int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, d; - int i, j; - - // Bresenham parameters for y scale - yp = srcHeight / scaledHeight; - yq = srcHeight % scaledHeight; - - // Bresenham parameters for x scale - xp = scaledWidth / srcWidth; - xq = scaledWidth % srcWidth; - - // allocate buffers - lineBuf = (Guchar *)gmallocn(srcWidth, nComps); - pixBuf = (Guint *)gmallocn(srcWidth, (int)(nComps * sizeof(int))); - if (srcAlpha) { - alphaLineBuf = (Guchar *)gmalloc(srcWidth); - alphaPixBuf = (Guint *)gmallocn(srcWidth, sizeof(int)); - } else { - alphaLineBuf = NULL; - alphaPixBuf = NULL; +void Splash::drawImageMaskArbitraryInterp( + Guchar *scaledMask, + SplashDrawImageMaskRowData *dd, + SplashDrawImageMaskRowFunc drawRowFunc, + SplashCoord *invMat, + int scaledWidth, int scaledHeight, + int xMin, int yMin, int xMax, int yMax) { + int tt = state->clip->getXMinI(state->strokeAdjust); + if (tt > xMin) { + xMin = tt; + } + tt = state->clip->getXMaxI(state->strokeAdjust) + 1; + if (tt < xMax) { + xMax = tt; + } + tt = state->clip->getYMinI(state->strokeAdjust); + if (tt > yMin) { + yMin = tt; + } + tt = state->clip->getYMaxI(state->strokeAdjust) + 1; + if (tt < yMax) { + yMax = tt; + } + if (xMax <= xMin || yMax <= yMin) { + return; } - // make gcc happy - pix[0] = pix[1] = pix[2] = 0; -#if SPLASH_CMYK - pix[3] = 0; -#endif - - // init y scale Bresenham - yt = 0; - - destPtr = dest->data; - destAlphaPtr = dest->alpha; - for (y = 0; y < scaledHeight; ++y) { - - // y scale Bresenham - if ((yt += yq) >= scaledHeight) { - yt -= scaledHeight; - yStep = yp + 1; - } else { - yStep = yp; - } - - // read rows from image - memset(pixBuf, 0, srcWidth * nComps * sizeof(int)); - if (srcAlpha) { - memset(alphaPixBuf, 0, srcWidth * sizeof(int)); - } - for (i = 0; i < yStep; ++i) { - (*src)(srcData, lineBuf, alphaLineBuf); - for (j = 0; j < srcWidth * nComps; ++j) { - pixBuf[j] += lineBuf[j]; - } - if (srcAlpha) { - for (j = 0; j < srcWidth; ++j) { - alphaPixBuf[j] += alphaLineBuf[j]; + Guchar *buf = (Guchar *)gmalloc(xMax - xMin); + + for (int y = yMin; y < yMax; ++y) { + int rowMin = xMax; + int rowMax = 0; + for (int x = xMin; x < xMax; ++x) { + // note: invMat includes a "+0.5" factor so that this is really + // a multiply by (x+0.5, y+0.5) + SplashCoord xs = (SplashCoord)x * invMat[0] + + (SplashCoord)y * invMat[2] + invMat[4]; + SplashCoord ys = (SplashCoord)x * invMat[1] + + (SplashCoord)y * invMat[3] + invMat[5]; + int x0 = splashFloor(xs - 0.5); + int x1 = x0 + 1; + int y0 = splashFloor(ys - 0.5); + int y1 = y0 + 1; + if (x1 >= 0 && x0 < scaledWidth && y1 >= 0 && y0 < scaledHeight) { + SplashCoord sx0 = (SplashCoord)x1 + 0.5 - xs; + SplashCoord sx1 = (SplashCoord)1 - sx0; + SplashCoord sy0 = (SplashCoord)y1 + 0.5 - ys; + SplashCoord sy1 = (SplashCoord)1 - sy0; + if (x0 < 0) { + x0 = 0; + } + if (x1 >= scaledWidth) { + x1 = scaledWidth - 1; + } + if (y0 < 0) { + y0 = 0; } + if (y1 >= scaledHeight) { + y1 = scaledHeight - 1; + } + Guchar *p00 = scaledMask + (y0 * scaledWidth + x0); + Guchar *p10 = scaledMask + (y0 * scaledWidth + x1); + Guchar *p01 = scaledMask + (y1 * scaledWidth + x0); + Guchar *p11 = scaledMask + (y1 * scaledWidth + x1); + Guchar *q = buf + (x - xMin); + *q = (Guchar)(int)(sx0 * (sy0 * (int)*p00 + sy1 * (int)*p01) + + sx1 * (sy0 * (int)*p10 + sy1 * (int)*p11)); + if (x < rowMin) { + rowMin = x; + } + rowMax = x + 1; } } + if (rowMin < rowMax) { + (this->*drawRowFunc)(dd, buf + (rowMin - xMin), + rowMin, y, rowMax - rowMin); + } + } - // init x scale Bresenham - xt = 0; - d = (1 << 23) / yStep; - - for (x = 0; x < srcWidth; ++x) { + gfree(buf); +} - // x scale Bresenham - if ((xt += xq) >= srcWidth) { - xt -= srcWidth; - xStep = xp + 1; - } else { - xStep = xp; - } +void Splash::mirrorImageMaskRow(Guchar *maskIn, Guchar *maskOut, int width) { + Guchar *p, *q; - // compute the final pixel - for (i = 0; i < nComps; ++i) { - // pixBuf[] / yStep - pix[i] = (pixBuf[x * nComps + i] * d + (1 << 22)) >> 23; - } + p = maskIn; + q = maskOut + (width - 1); + for (int i = 0; i < width; ++i) { + *q = *p; + ++p; + --q; + } +} - // store the pixel - switch (srcMode) { - case splashModeMono8: - for (i = 0; i < xStep; ++i) { - *destPtr++ = (Guchar)pix[0]; - } - break; - case splashModeRGB8: - for (i = 0; i < xStep; ++i) { - *destPtr++ = (Guchar)pix[0]; - *destPtr++ = (Guchar)pix[1]; - *destPtr++ = (Guchar)pix[2]; - } - break; -#if SPLASH_CMYK - case splashModeCMYK8: - for (i = 0; i < xStep; ++i) { - *destPtr++ = (Guchar)pix[0]; - *destPtr++ = (Guchar)pix[1]; - *destPtr++ = (Guchar)pix[2]; - *destPtr++ = (Guchar)pix[3]; - } - break; -#endif - case splashModeMono1: // mono1 is not allowed - case splashModeBGR8: // BGR8 is not allowed - default: - break; - } +void Splash::drawImageMaskRowNoClip(SplashDrawImageMaskRowData *data, + Guchar *maskData, + int x, int y, int width) { + (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, maskData, NULL); +} - // process alpha - if (srcAlpha) { - // alphaPixBuf[] / yStep - alpha = (alphaPixBuf[x] * d + (1 << 22)) >> 23; - for (i = 0; i < xStep; ++i) { - *destAlphaPtr++ = (Guchar)alpha; - } - } - } +void Splash::drawImageMaskRowClipNoAA(SplashDrawImageMaskRowData *data, + Guchar *maskData, + int x, int y, int width) { + if (y < 0 || y >= bitmap->height) { + return; + } + if (x < 0) { + maskData -= x; + width += x; + x = 0; + } + if (x + width > bitmap->width) { + width = bitmap->width - x; } + if (width <= 0) { + return; + } + memcpy(scanBuf + x, maskData, width); + state->clip->clipSpanBinary(scanBuf, y, x, x + width - 1, + state->strokeAdjust); + (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, + scanBuf + x, NULL); +} - gfree(alphaPixBuf); - gfree(alphaLineBuf); - gfree(pixBuf); - gfree(lineBuf); +void Splash::drawImageMaskRowClipAA(SplashDrawImageMaskRowData *data, + Guchar *maskData, + int x, int y, int width) { + if (y < 0 || y >= bitmap->height) { + return; + } + if (x < 0) { + maskData -= x; + width += x; + x = 0; + } + if (x + width > bitmap->width) { + width = bitmap->width - x; + } + if (width <= 0) { + return; + } + memcpy(scanBuf + x, maskData, width); + state->clip->clipSpan(scanBuf, y, x, x + width - 1, state->strokeAdjust); + (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, + scanBuf + x, NULL); } -void Splash::scaleImageYuXd(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf, *alphaLineBuf; - Guint pix[splashMaxColorComps]; - Guint alpha; - Guchar *destPtr0, *destPtr, *destAlphaPtr0, *destAlphaPtr; - int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, xxa, d, d0, d1; - int i, j; - - // Bresenham parameters for y scale - yp = scaledHeight / srcHeight; - yq = scaledHeight % srcHeight; - - // Bresenham parameters for x scale - xp = srcWidth / scaledWidth; - xq = srcWidth % scaledWidth; - - // allocate buffers - lineBuf = (Guchar *)gmallocn(srcWidth, nComps); - if (srcAlpha) { - alphaLineBuf = (Guchar *)gmalloc(srcWidth); - } else { - alphaLineBuf = NULL; +struct SplashDrawImageRowData { + int nComps; + GBool srcAlpha; + SplashPipe pipe; +}; + +SplashError Splash::drawImage(GString *imageTag, + SplashImageSource src, void *srcData, + SplashColorMode srcMode, GBool srcAlpha, + int w, int h, SplashCoord *mat, + GBool interpolate) { + if (debugMode) { + printf("drawImage: srcMode=%d srcAlpha=%d w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n", + srcMode, srcAlpha, w, h, (double)mat[0], (double)mat[1], (double)mat[2], + (double)mat[3], (double)mat[4], (double)mat[5]); } - // make gcc happy - pix[0] = pix[1] = pix[2] = 0; + //--- check color modes + GBool ok = gFalse; + int nComps = 0; + switch (bitmap->mode) { + case splashModeMono1: + case splashModeMono8: + ok = srcMode == splashModeMono8; + nComps = 1; + break; + case splashModeRGB8: + case splashModeBGR8: + ok = srcMode == splashModeRGB8; + nComps = 3; + break; #if SPLASH_CMYK - pix[3] = 0; + case splashModeCMYK8: + ok = srcMode == splashModeCMYK8; + nComps = 4; + break; #endif + default: + ok = gFalse; + break; + } + if (!ok) { + return splashErrModeMismatch; + } - // init y scale Bresenham - yt = 0; + //--- check for singular matrix + if (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) { + return splashErrSingularMatrix; + } - destPtr0 = dest->data; - destAlphaPtr0 = dest->alpha; - for (y = 0; y < srcHeight; ++y) { + //--- compute image bbox, check clipping + GBool flipsOnly = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001; + GBool horizFlip = gFalse; + GBool vertFlip = gFalse; + int xMin, yMin, xMax, yMax; + if (flipsOnly) { + horizFlip = mat[0] < 0; + vertFlip = mat[3] < 0; + if (vertFlip) { + if (horizFlip) { // bottom-up, mirrored + getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax); + getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax); + } else { // bottom-up + getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax); + getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax); + } + } else { + if (horizFlip) { // top-down, mirrored + getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax); + getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax); + } else { // top-down + getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax); + getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax); + } + } + } else { + int xx = splashRound(mat[4]); // (0,0) + int yy = splashRound(mat[5]); + xMin = xMax = xx; + yMin = yMax = yy; + xx = splashRound(mat[0] + mat[4]); // (1,0) + yy = splashRound(mat[1] + mat[5]); + if (xx < xMin) { + xMin = xx; + } else if (xx > xMax) { + xMax = xx; + } + if (yy < yMin) { + yMin = yy; + } else if (yy > yMax) { + yMax = yy; + } + xx = splashRound(mat[2] + mat[4]); // (0,1) + yy = splashRound(mat[3] + mat[5]); + if (xx < xMin) { + xMin = xx; + } else if (xx > xMax) { + xMax = xx; + } + if (yy < yMin) { + yMin = yy; + } else if (yy > yMax) { + yMax = yy; + } + xx = splashRound(mat[0] + mat[2] + mat[4]); // (1,1) + yy = splashRound(mat[1] + mat[3] + mat[5]); + if (xx < xMin) { + xMin = xx; + } else if (xx > xMax) { + xMax = xx; + } + if (yy < yMin) { + yMin = yy; + } else if (yy > yMax) { + yMax = yy; + } + if (xMax <= xMin) { + xMax = xMin + 1; + } + if (yMax <= yMin) { + yMax = yMin + 1; + } + } + SplashClipResult clipRes = + state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1, + state->strokeAdjust); + // If the scaled image is much wider and/or taller than the clip + // region, we use the arbitrary transform path, to avoid a + // potentially very slow loop in the flips-only path (which scans + // the full width and height of the scaled image, regardless of the + // clip region). + int clipW = state->clip->getXMaxI(state->strokeAdjust) + - state->clip->getXMinI(state->strokeAdjust); + int clipH = state->clip->getYMaxI(state->strokeAdjust) + - state->clip->getYMinI(state->strokeAdjust); + GBool veryLarge = ((xMax - xMin) / 8 > clipW && xMax - xMin > 1000) || + ((yMax - yMin) / 8 > clipH && yMax - yMin > 1000); + + //--- set up the SplashDrawImageRowData object and the pipes + SplashDrawImageRowData dd; + dd.nComps = nComps; + dd.srcAlpha = srcAlpha; + pipeInit(&dd.pipe, NULL, + (Guchar)splashRound(state->fillAlpha * 255), + clipRes != splashClipAllInside || srcAlpha, + gFalse); - // y scale Bresenham - if ((yt += yq) >= srcHeight) { - yt -= srcHeight; - yStep = yp + 1; + //--- choose the drawRow function + SplashDrawImageRowFunc drawRowFunc; + if (clipRes == splashClipAllInside) { + if (srcAlpha) { + drawRowFunc = &Splash::drawImageRowNoClipAlpha; + } else { + drawRowFunc = &Splash::drawImageRowNoClipNoAlpha; + } + } else { + if (srcAlpha) { + if (vectorAntialias) { + drawRowFunc = &Splash::drawImageRowClipAlphaAA; + } else { + drawRowFunc = &Splash::drawImageRowClipAlphaNoAA; + } } else { - yStep = yp; - } - - // read row from image - (*src)(srcData, lineBuf, alphaLineBuf); - - // init x scale Bresenham - xt = 0; - d0 = (1 << 23) / xp; - d1 = (1 << 23) / (xp + 1); - - xx = xxa = 0; - for (x = 0; x < scaledWidth; ++x) { - - // x scale Bresenham - if ((xt += xq) >= scaledWidth) { - xt -= scaledWidth; - xStep = xp + 1; - d = d1; + if (vectorAntialias) { + drawRowFunc = &Splash::drawImageRowClipNoAlphaAA; } else { - xStep = xp; - d = d0; + drawRowFunc = &Splash::drawImageRowClipNoAlphaNoAA; } + } + } - // compute the final pixel - for (i = 0; i < nComps; ++i) { - pix[i] = 0; - } - for (i = 0; i < xStep; ++i) { - for (j = 0; j < nComps; ++j, ++xx) { - pix[j] += lineBuf[xx]; + //--- horizontal/vertical flips only + if (flipsOnly && !veryLarge) { + if (clipRes != splashClipAllOutside) { + int scaledWidth = xMax - xMin; + int scaledHeight = yMax - yMin; + ImageScaler *scaler = getImageScaler(imageTag, src, srcData, + w, h, nComps, + scaledWidth, scaledHeight, + srcMode, srcAlpha, interpolate); + Guchar *tmpLine = NULL; + Guchar *tmpAlphaLine = NULL; + if (horizFlip) { + tmpLine = (Guchar *)gmallocn(scaledWidth, nComps); + if (srcAlpha) { + tmpAlphaLine = (Guchar *)gmalloc(scaledWidth); } } - for (i = 0; i < nComps; ++i) { - // pix[] / xStep - pix[i] = (pix[i] * d + (1 << 22)) >> 23; - } - - // store the pixel - switch (srcMode) { - case splashModeMono8: - for (i = 0; i < yStep; ++i) { - destPtr = destPtr0 + (i * scaledWidth + x) * nComps; - *destPtr++ = (Guchar)pix[0]; - } - break; - case splashModeRGB8: - for (i = 0; i < yStep; ++i) { - destPtr = destPtr0 + (i * scaledWidth + x) * nComps; - *destPtr++ = (Guchar)pix[0]; - *destPtr++ = (Guchar)pix[1]; - *destPtr++ = (Guchar)pix[2]; + if (vertFlip) { + if (horizFlip) { // bottom-up, mirrored + for (int y = 0; y < scaledHeight; ++y) { + scaler->nextLine(); + mirrorImageRow(scaler->colorData(), scaler->alphaData(), + tmpLine, tmpAlphaLine, + scaledWidth, nComps, srcAlpha); + (this->*drawRowFunc)(&dd, tmpLine, tmpAlphaLine, + xMin, yMax - 1 - y, scaledWidth); + } + } else { // bottom-up + for (int y = 0; y < scaledHeight; ++y) { + scaler->nextLine(); + (this->*drawRowFunc)(&dd, scaler->colorData(), scaler->alphaData(), + xMin, yMax - 1 - y, scaledWidth); + } } - break; -#if SPLASH_CMYK - case splashModeCMYK8: - for (i = 0; i < yStep; ++i) { - destPtr = destPtr0 + (i * scaledWidth + x) * nComps; - *destPtr++ = (Guchar)pix[0]; - *destPtr++ = (Guchar)pix[1]; - *destPtr++ = (Guchar)pix[2]; - *destPtr++ = (Guchar)pix[3]; + } else { + if (horizFlip) { // top-down, mirrored + for (int y = 0; y < scaledHeight; ++y) { + scaler->nextLine(); + mirrorImageRow(scaler->colorData(), scaler->alphaData(), + tmpLine, tmpAlphaLine, + scaledWidth, nComps, srcAlpha); + (this->*drawRowFunc)(&dd, tmpLine, tmpAlphaLine, + xMin, yMin + y, scaledWidth); + } + } else { // top-down + for (int y = 0; y < scaledHeight; ++y) { + scaler->nextLine(); + (this->*drawRowFunc)(&dd, scaler->colorData(), scaler->alphaData(), + xMin, yMin + y, scaledWidth); + } } - break; -#endif - case splashModeMono1: // mono1 is not allowed - case splashModeBGR8: // BGR8 is not allowed - default: - break; } + gfree(tmpLine); + gfree(tmpAlphaLine); + delete scaler; + } - // process alpha - if (srcAlpha) { - alpha = 0; - for (i = 0; i < xStep; ++i, ++xxa) { - alpha += alphaLineBuf[xxa]; - } - // alpha / xStep - alpha = (alpha * d + (1 << 22)) >> 23; - for (i = 0; i < yStep; ++i) { - destAlphaPtr = destAlphaPtr0 + i * scaledWidth + x; - *destAlphaPtr = (Guchar)alpha; - } - } + //--- arbitrary transform + } else { + // estimate of size of scaled image + int scaledWidth = splashRound(splashSqrt(mat[0] * mat[0] + + mat[1] * mat[1])); + int scaledHeight = splashRound(splashSqrt(mat[2] * mat[2] + + mat[3] * mat[3])); + if (scaledWidth < 1) { + scaledWidth = 1; + } + if (scaledHeight < 1) { + scaledHeight = 1; + } + if (veryLarge || (scaledWidth >= w && scaledHeight >= h)) { + scaledWidth = w; + scaledHeight = h; + } + + // compute mapping from device space to scaled image space + SplashCoord mat1[6]; + mat1[0] = mat[0] / scaledWidth; + mat1[1] = mat[1] / scaledWidth; + mat1[2] = mat[2] / scaledHeight; + mat1[3] = mat[3] / scaledHeight; + mat1[4] = mat[4]; + mat1[5] = mat[5]; + SplashCoord det = mat1[0] * mat1[3] - mat1[1] * mat1[2]; + if (splashAbs(det) < 1e-6) { + // this should be caught by the singular matrix check in drawImage + return splashErrSingularMatrix; + } + SplashCoord invMat[6]; + invMat[0] = mat1[3] / det; + invMat[1] = -mat1[1] / det; + invMat[2] = -mat1[2] / det; + invMat[3] = mat1[0] / det; + // the extra "+ 0.5 * (...)" terms are here because the + // drawImageArbitrary(No)Interp functions multiply by pixel + // centers, (x + 0.5, y + 0.5) + invMat[4] = (mat1[2] * mat1[5] - mat1[3] * mat1[4]) / det + + (invMat[0] + invMat[2]) * 0.5; + invMat[5] = (mat1[1] * mat1[4] - mat1[0] * mat1[5]) / det + + (invMat[1] + invMat[3]) * 0.5; + + Guchar *scaledColor, *scaledAlpha; + GBool freeScaledImage; + getScaledImage(imageTag, src, srcData, w, h, nComps, + scaledWidth, scaledHeight, srcMode, srcAlpha, interpolate, + &scaledColor, &scaledAlpha, &freeScaledImage); + + // draw it + if (interpolate) { + drawImageArbitraryInterp(scaledColor, scaledAlpha, + &dd, drawRowFunc, invMat, + scaledWidth, scaledHeight, + xMin, yMin, xMax, yMax, + nComps, srcAlpha); + } else { + drawImageArbitraryNoInterp(scaledColor, scaledAlpha, + &dd, drawRowFunc, invMat, + scaledWidth, scaledHeight, + xMin, yMin, xMax, yMax, + nComps, srcAlpha); } - destPtr0 += yStep * scaledWidth * nComps; - if (srcAlpha) { - destAlphaPtr0 += yStep * scaledWidth; + // free the downscaled/unscaled image + if (freeScaledImage) { + gfree(scaledColor); + gfree(scaledAlpha); } } - gfree(alphaLineBuf); - gfree(lineBuf); + return splashOk; } -void Splash::scaleImageYuXu(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf, *alphaLineBuf; - Guchar pix0, pix1, pix2; -#if SPLASH_CMYK - Guchar pix3; -#endif - Guchar alpha; - Guchar *srcPtr, *srcAlphaPtr; - Guchar *destPtr, *destAlphaPtr; - int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep; - int i; - - // Bresenham parameters for y scale - yp = scaledHeight / srcHeight; - yq = scaledHeight % srcHeight; - - // Bresenham parameters for x scale - xp = scaledWidth / srcWidth; - xq = scaledWidth % srcWidth; - - // allocate buffers - lineBuf = (Guchar *)gmallocn(srcWidth, nComps); - if (srcAlpha) { - alphaLineBuf = (Guchar *)gmalloc(srcWidth); +ImageScaler *Splash::getImageScaler(GString *imageTag, + SplashImageSource src, void *srcData, + int w, int h, int nComps, + int scaledWidth, int scaledHeight, + SplashColorMode srcMode, + GBool srcAlpha, GBool interpolate) { + // Notes: + // + // * If the scaled image width or height is greater than 2000, we + // don't cache it. + // + // * Caching is done on the third consecutive use (second + // consecutive reuse) of an image; this avoids overhead on the + // common case of single-use images. + + if (scaledWidth < 2000 && scaledHeight < 2000 && + imageCache->match(imageTag, scaledWidth, scaledHeight, + srcMode, srcAlpha, interpolate)) { + if (imageCache->colorData) { + return new ReplayImageScaler(nComps, srcAlpha, scaledWidth, + imageCache->colorData, + imageCache->alphaData); + } else { + int lineSize; + if (scaledWidth < INT_MAX / nComps) { + lineSize = scaledWidth * nComps; + } else { + lineSize = -1; + } + imageCache->colorData = (Guchar *)gmallocn(scaledHeight, lineSize); + if (srcAlpha) { + imageCache->alphaData = (Guchar *)gmallocn(scaledHeight, scaledWidth); + } + return new SavingImageScaler(src, srcData, + w, h, nComps, srcAlpha, + scaledWidth, scaledHeight, + interpolate, + imageCache->colorData, + imageCache->alphaData); + } } else { - alphaLineBuf = NULL; + imageCache->reset(imageTag, scaledWidth, scaledHeight, + srcMode, srcAlpha, interpolate); + return new BasicImageScaler(src, srcData, + w, h, nComps, srcAlpha, + scaledWidth, scaledHeight, + interpolate); } +} - // init y scale Bresenham - yt = 0; - - destPtr = dest->data; - destAlphaPtr = dest->alpha; - for (y = 0; y < srcHeight; ++y) { - - // y scale Bresenham - if ((yt += yq) >= srcHeight) { - yt -= srcHeight; - yStep = yp + 1; +void Splash::getScaledImage(GString *imageTag, + SplashImageSource src, void *srcData, + int w, int h, int nComps, + int scaledWidth, int scaledHeight, + SplashColorMode srcMode, + GBool srcAlpha, GBool interpolate, + Guchar **scaledColor, Guchar **scaledAlpha, + GBool *freeScaledImage) { + // Notes: + // + // * If the scaled image width or height is greater than 2000, we + // don't cache it. + // + // * This buffers the whole image anyway, so there's no reason to + // skip caching on the first reuse. + + if (scaledWidth >= 2000 || scaledHeight >= 2000) { + int lineSize; + if (scaledWidth < INT_MAX / nComps) { + lineSize = scaledWidth * nComps; } else { - yStep = yp; + lineSize = -1; } - - // read row from image - (*src)(srcData, lineBuf, alphaLineBuf); - - // init x scale Bresenham - xt = 0; - - // generate one row - srcPtr = lineBuf; - srcAlphaPtr = alphaLineBuf; - for (x = 0; x < srcWidth; ++x) { - - // x scale Bresenham - if ((xt += xq) >= srcWidth) { - xt -= srcWidth; - xStep = xp + 1; - } else { - xStep = xp; - } - - // duplicate the pixel horizontally - switch (srcMode) { - case splashModeMono8: - pix0 = *srcPtr++; - for (i = 0; i < xStep; ++i) { - *destPtr++ = pix0; - } - break; - case splashModeRGB8: - pix0 = *srcPtr++; - pix1 = *srcPtr++; - pix2 = *srcPtr++; - for (i = 0; i < xStep; ++i) { - *destPtr++ = pix0; - *destPtr++ = pix1; - *destPtr++ = pix2; - } - break; -#if SPLASH_CMYK - case splashModeCMYK8: - pix0 = *srcPtr++; - pix1 = *srcPtr++; - pix2 = *srcPtr++; - pix3 = *srcPtr++; - for (i = 0; i < xStep; ++i) { - *destPtr++ = pix0; - *destPtr++ = pix1; - *destPtr++ = pix2; - *destPtr++ = pix3; + *scaledColor = (Guchar *)gmallocn(scaledHeight, lineSize); + if (srcAlpha) { + *scaledAlpha = (Guchar *)gmallocn(scaledHeight, scaledWidth); + } else { + *scaledAlpha = NULL; + } + *freeScaledImage = gTrue; + if (scaledWidth == w && scaledHeight == h) { + Guchar *colorPtr = *scaledColor; + Guchar *alphaPtr = *scaledAlpha; + for (int y = 0; y < scaledHeight; ++y) { + (*src)(srcData, colorPtr, alphaPtr); + colorPtr += scaledWidth * nComps; + if (srcAlpha) { + alphaPtr += scaledWidth; } - break; -#endif - case splashModeMono1: // mono1 is not allowed - case splashModeBGR8: // BGR8 is not allowed - default: - break; } - - // duplicate the alpha value horizontally - if (srcAlpha) { - alpha = *srcAlphaPtr++; - for (i = 0; i < xStep; ++i) { - *destAlphaPtr++ = alpha; + } else { + BasicImageScaler scaler(src, srcData, w, h, nComps, srcAlpha, + scaledWidth, scaledHeight, interpolate); + Guchar *colorPtr = *scaledColor; + Guchar *alphaPtr = *scaledAlpha; + for (int y = 0; y < scaledHeight; ++y) { + scaler.nextLine(); + memcpy(colorPtr, scaler.colorData(), scaledWidth * nComps); + colorPtr += scaledWidth * nComps; + if (srcAlpha) { + memcpy(alphaPtr, scaler.alphaData(), scaledWidth); + alphaPtr += scaledWidth; } } } - - // duplicate the row vertically - for (i = 1; i < yStep; ++i) { - memcpy(destPtr, destPtr - scaledWidth * nComps, - scaledWidth * nComps); - destPtr += scaledWidth * nComps; - } - if (srcAlpha) { - for (i = 1; i < yStep; ++i) { - memcpy(destAlphaPtr, destAlphaPtr - scaledWidth, scaledWidth); - destAlphaPtr += scaledWidth; + } else { + if (!imageCache->match(imageTag, scaledWidth, scaledHeight, + srcMode, srcAlpha, interpolate) || + !imageCache->colorData) { + imageCache->reset(imageTag, scaledWidth, scaledHeight, + srcMode, srcAlpha, interpolate); + int lineSize; + if (scaledWidth < INT_MAX / nComps) { + lineSize = scaledWidth * nComps; + } else { + lineSize = -1; + } + imageCache->colorData = (Guchar *)gmallocn(scaledHeight, lineSize); + if (srcAlpha) { + imageCache->alphaData = (Guchar *)gmallocn(scaledHeight, scaledWidth); + } + if (scaledWidth == w && scaledHeight == h) { + Guchar *colorPtr = imageCache->colorData; + Guchar *alphaPtr = imageCache->alphaData; + for (int y = 0; y < scaledHeight; ++y) { + (*src)(srcData, colorPtr, alphaPtr); + colorPtr += scaledWidth * nComps; + if (srcAlpha) { + alphaPtr += scaledWidth; + } + } + } else { + SavingImageScaler scaler(src, srcData, w, h, nComps, srcAlpha, + scaledWidth, scaledHeight, interpolate, + imageCache->colorData, imageCache->alphaData); + Guchar *colorPtr = imageCache->colorData; + Guchar *alphaPtr = imageCache->alphaData; + for (int y = 0; y < scaledHeight; ++y) { + scaler.nextLine(); + memcpy(colorPtr, scaler.colorData(), scaledWidth * nComps); + colorPtr += scaledWidth * nComps; + if (srcAlpha) { + memcpy(alphaPtr, scaler.alphaData(), scaledWidth); + alphaPtr += scaledWidth; + } + } } } + *scaledColor = imageCache->colorData; + *scaledAlpha = imageCache->alphaData; + *freeScaledImage = gFalse; } - - gfree(alphaLineBuf); - gfree(lineBuf); } -void Splash::scaleImageYuXuI(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest) { - Guchar *lineBuf0, *lineBuf1, *alphaLineBuf0, *alphaLineBuf1, *tBuf; - Guchar pix[splashMaxColorComps]; - SplashCoord yr, xr, ys, xs, ySrc, xSrc; - int ySrc0, ySrc1, yBuf, xSrc0, xSrc1, y, x, i; - Guchar *destPtr, *destAlphaPtr; - - // ratios - yr = (SplashCoord)srcHeight / (SplashCoord)scaledHeight; - xr = (SplashCoord)srcWidth / (SplashCoord)scaledWidth; - - // allocate buffers - lineBuf0 = (Guchar *)gmallocn(scaledWidth, nComps); - lineBuf1 = (Guchar *)gmallocn(scaledWidth, nComps); - if (srcAlpha) { - alphaLineBuf0 = (Guchar *)gmalloc(scaledWidth); - alphaLineBuf1 = (Guchar *)gmalloc(scaledWidth); - } else { - alphaLineBuf0 = NULL; - alphaLineBuf1 = NULL; - } - - // read first two rows - (*src)(srcData, lineBuf0, alphaLineBuf0); - if (srcHeight > 1) { - (*src)(srcData, lineBuf1, alphaLineBuf1); - yBuf = 1; - } else { - memcpy(lineBuf1, lineBuf0, srcWidth * nComps); - if (srcAlpha) { - memcpy(alphaLineBuf1, alphaLineBuf0, srcWidth); - } - yBuf = 0; +void Splash::drawImageArbitraryNoInterp(Guchar *scaledColor, + Guchar *scaledAlpha, + SplashDrawImageRowData *dd, + SplashDrawImageRowFunc drawRowFunc, + SplashCoord *invMat, + int scaledWidth, int scaledHeight, + int xMin, int yMin, int xMax, int yMax, + int nComps, GBool srcAlpha) { + int tt = state->clip->getXMinI(state->strokeAdjust); + if (tt > xMin) { + xMin = tt; + } + tt = state->clip->getXMaxI(state->strokeAdjust) + 1; + if (tt < xMax) { + xMax = tt; + } + tt = state->clip->getYMinI(state->strokeAdjust); + if (tt > yMin) { + yMin = tt; + } + tt = state->clip->getYMaxI(state->strokeAdjust) + 1; + if (tt < yMax) { + yMax = tt; + } + if (xMax <= xMin || yMax <= yMin) { + return; } - // interpolate first two rows - for (x = scaledWidth - 1; x >= 0; --x) { - xSrc = xr * x; - xSrc0 = splashFloor(xSrc + xr * 0.5 - 0.5); - xSrc1 = xSrc0 + 1; - xs = ((SplashCoord)xSrc1 + 0.5) - (xSrc + xr * 0.5); - if (xSrc0 < 0) { - xSrc0 = 0; - } - if (xSrc1 >= srcWidth) { - xSrc1 = srcWidth - 1; - } - for (i = 0; i < nComps; ++i) { - lineBuf0[x*nComps+i] = (Guchar)(int) - (xs * (int)lineBuf0[xSrc0*nComps+i] + - ((SplashCoord)1 - xs) * (int)lineBuf0[xSrc1*nComps+i]); - lineBuf1[x*nComps+i] = (Guchar)(int) - (xs * (int)lineBuf1[xSrc0*nComps+i] + - ((SplashCoord)1 - xs) * (int)lineBuf1[xSrc1*nComps+i]); + Guchar *colorBuf = (Guchar *)gmallocn(xMax - xMin, nComps); + Guchar *alphaBuf = NULL; + if (srcAlpha) { + alphaBuf = (Guchar *)gmalloc(xMax - xMin); + } + + for (int y = yMin; y < yMax; ++y) { + int rowMin = xMax; + int rowMax = 0; + for (int x = xMin; x < xMax; ++x) { + // note: invMat includes a "+0.5" factor so that this is really + // a multiply by (x+0.5, y+0.5) + int xx = splashFloor((SplashCoord)x * invMat[0] + + (SplashCoord)y * invMat[2] + invMat[4]); + int yy = splashFloor((SplashCoord)x * invMat[1] + + (SplashCoord)y * invMat[3] + invMat[5]); + if (xx >= 0 && xx < scaledWidth && + yy >= 0 && yy < scaledHeight) { + Guchar *p = scaledColor + (yy * scaledWidth + xx) * nComps; + Guchar *q = colorBuf + (x - xMin) * nComps; + for (int i = 0; i < nComps; ++i) { + *q++ = *p++; + } + if (srcAlpha) { + alphaBuf[x - xMin] = scaledAlpha[yy * scaledWidth + xx]; + } + if (x < rowMin) { + rowMin = x; + } + rowMax = x + 1; + } } - if (srcAlpha) { - alphaLineBuf0[x] = (Guchar)(int) - (xs * (int)alphaLineBuf0[xSrc0] + - ((SplashCoord)1 - xs) * (int)alphaLineBuf0[xSrc1]); - alphaLineBuf1[x] = (Guchar)(int) - (xs * (int)alphaLineBuf1[xSrc0] + - ((SplashCoord)1 - xs) * (int)alphaLineBuf1[xSrc1]); + if (rowMin < rowMax) { + (this->*drawRowFunc)(dd, + colorBuf + (rowMin - xMin) * nComps, + alphaBuf + (rowMin - xMin), + rowMin, y, rowMax - rowMin); } } - // make gcc happy - pix[0] = pix[1] = pix[2] = 0; -#if SPLASH_CMYK - pix[3] = 0; -#endif + gfree(colorBuf); + gfree(alphaBuf); +} + +void Splash::drawImageArbitraryInterp(Guchar *scaledColor, Guchar *scaledAlpha, + SplashDrawImageRowData *dd, + SplashDrawImageRowFunc drawRowFunc, + SplashCoord *invMat, + int scaledWidth, int scaledHeight, + int xMin, int yMin, int xMax, int yMax, + int nComps, GBool srcAlpha) { + int tt = state->clip->getXMinI(state->strokeAdjust); + if (tt > xMin) { + xMin = tt; + } + tt = state->clip->getXMaxI(state->strokeAdjust) + 1; + if (tt < xMax) { + xMax = tt; + } + tt = state->clip->getYMinI(state->strokeAdjust); + if (tt > yMin) { + yMin = tt; + } + tt = state->clip->getYMaxI(state->strokeAdjust) + 1; + if (tt < yMax) { + yMax = tt; + } + if (xMax <= xMin || yMax <= yMin) { + return; + } - destPtr = dest->data; - destAlphaPtr = dest->alpha; - for (y = 0; y < scaledHeight; ++y) { - - // compute vertical interpolation parameters - ySrc = yr * y; - ySrc0 = splashFloor(ySrc + yr * 0.5 - 0.5); - ySrc1 = ySrc0 + 1; - ys = ((SplashCoord)ySrc1 + 0.5) - (ySrc + yr * 0.5); - if (ySrc0 < 0) { - ySrc0 = 0; - ys = 1; - } - if (ySrc1 >= srcHeight) { - ySrc1 = srcHeight - 1; - ys = 0; - } - - // read another row (if necessary) - if (ySrc1 > yBuf) { - tBuf = lineBuf0; - lineBuf0 = lineBuf1; - lineBuf1 = tBuf; - tBuf = alphaLineBuf0; - alphaLineBuf0 = alphaLineBuf1; - alphaLineBuf1 = tBuf; - (*src)(srcData, lineBuf1, alphaLineBuf1); - - // interpolate the row - for (x = scaledWidth - 1; x >= 0; --x) { - xSrc = xr * x; - xSrc0 = splashFloor(xSrc + xr * 0.5 - 0.5); - xSrc1 = xSrc0 + 1; - xs = ((SplashCoord)xSrc1 + 0.5) - (xSrc + xr * 0.5); - if (xSrc0 < 0) { - xSrc0 = 0; + Guchar *colorBuf = (Guchar *)gmallocn(xMax - xMin, nComps); + Guchar *alphaBuf = NULL; + if (srcAlpha) { + alphaBuf = (Guchar *)gmalloc(xMax - xMin); + } + + for (int y = yMin; y < yMax; ++y) { + int rowMin = xMax; + int rowMax = 0; + for (int x = xMin; x < xMax; ++x) { + // note: invMat includes a "+0.5" factor so that this is really + // a multiply by (x+0.5, y+0.5) + SplashCoord xs = (SplashCoord)x * invMat[0] + + (SplashCoord)y * invMat[2] + invMat[4]; + SplashCoord ys = (SplashCoord)x * invMat[1] + + (SplashCoord)y * invMat[3] + invMat[5]; + int x0 = splashFloor(xs - 0.5); + int x1 = x0 + 1; + int y0 = splashFloor(ys - 0.5); + int y1 = y0 + 1; + if (x1 >= 0 && x0 < scaledWidth && y1 >= 0 && y0 < scaledHeight) { + SplashCoord sx0 = (SplashCoord)x1 + 0.5 - xs; + SplashCoord sx1 = (SplashCoord)1 - sx0; + SplashCoord sy0 = (SplashCoord)y1 + 0.5 - ys; + SplashCoord sy1 = (SplashCoord)1 - sy0; + if (x0 < 0) { + x0 = 0; + } + if (x1 >= scaledWidth) { + x1 = scaledWidth - 1; } - if (xSrc1 >= srcWidth) { - xSrc1 = srcWidth - 1; + if (y0 < 0) { + y0 = 0; } - for (i = 0; i < nComps; ++i) { - lineBuf1[x*nComps+i] = (Guchar)(int) - (xs * (int)lineBuf1[xSrc0*nComps+i] + - ((SplashCoord)1 - xs) * (int)lineBuf1[xSrc1*nComps+i]); + if (y1 >= scaledHeight) { + y1 = scaledHeight - 1; + } + Guchar *p00 = scaledColor + (y0 * scaledWidth + x0) * nComps; + Guchar *p10 = scaledColor + (y0 * scaledWidth + x1) * nComps; + Guchar *p01 = scaledColor + (y1 * scaledWidth + x0) * nComps; + Guchar *p11 = scaledColor + (y1 * scaledWidth + x1) * nComps; + Guchar *q = colorBuf + (x - xMin) * nComps; + for (int i = 0; i < nComps; ++i) { + *q++ = (Guchar)(int)(sx0 * (sy0 * (int)*p00++ + sy1 * (int)*p01++) + + sx1 * (sy0 * (int)*p10++ + sy1 * (int)*p11++)); } if (srcAlpha) { - alphaLineBuf1[x] = (Guchar)(int) - (xs * (int)alphaLineBuf1[xSrc0] + - ((SplashCoord)1 - xs) * (int)alphaLineBuf1[xSrc1]); + p00 = scaledAlpha + (y0 * scaledWidth + x0); + p10 = scaledAlpha + (y0 * scaledWidth + x1); + p01 = scaledAlpha + (y1 * scaledWidth + x0); + p11 = scaledAlpha + (y1 * scaledWidth + x1); + q = alphaBuf + (x - xMin); + *q = (Guchar)(int)(sx0 * (sy0 * (int)*p00 + sy1 * (int)*p01) + + sx1 * (sy0 * (int)*p10 + sy1 * (int)*p11)); + } + if (x < rowMin) { + rowMin = x; } + rowMax = x + 1; } - - ++yBuf; } - - // do the vertical interpolation - for (x = 0; x < scaledWidth; ++x) { - - for (i = 0; i < nComps; ++i) { - pix[i] = (Guchar)(int) - (ys * (int)lineBuf0[x*nComps+i] + - ((SplashCoord)1 - ys) * (int)lineBuf1[x*nComps+i]); - } - - // store the pixel - switch (srcMode) { - case splashModeMono8: - *destPtr++ = pix[0]; - break; - case splashModeRGB8: - *destPtr++ = pix[0]; - *destPtr++ = pix[1]; - *destPtr++ = pix[2]; - break; -#if SPLASH_CMYK - case splashModeCMYK8: - *destPtr++ = pix[0]; - *destPtr++ = pix[1]; - *destPtr++ = pix[2]; - *destPtr++ = pix[3]; - break; -#endif - case splashModeMono1: // mono1 is not allowed - case splashModeBGR8: // BGR8 is not allowed - default: - break; - } - - // process alpha - if (srcAlpha) { - *destAlphaPtr++ = (Guchar)(int) - (ys * (int)alphaLineBuf0[x] + - ((SplashCoord)1 - ys) * (int)alphaLineBuf1[x]); - } + if (rowMin < rowMax) { + (this->*drawRowFunc)(dd, + colorBuf + (rowMin - xMin) * nComps, + alphaBuf + (rowMin - xMin), + rowMin, y, rowMax - rowMin); } } - gfree(alphaLineBuf1); - gfree(alphaLineBuf0); - gfree(lineBuf1); - gfree(lineBuf0); + gfree(colorBuf); + gfree(alphaBuf); } -void Splash::vertFlipImage(SplashBitmap *img, int width, int height, - int nComps) { - Guchar *lineBuf; - Guchar *p0, *p1; - int w; +void Splash::mirrorImageRow(Guchar *colorIn, Guchar *alphaIn, + Guchar *colorOut, Guchar *alphaOut, + int width, int nComps, GBool srcAlpha) { + Guchar *p, *q; - w = width * nComps; - lineBuf = (Guchar *)gmalloc(w); - for (p0 = img->data, p1 = img->data + (height - 1) * (size_t)w; - p0 < p1; - p0 += w, p1 -= w) { - memcpy(lineBuf, p0, w); - memcpy(p0, p1, w); - memcpy(p1, lineBuf, w); - } - if (img->alpha) { - for (p0 = img->alpha, p1 = img->alpha + (height - 1) * (size_t)width; - p0 < p1; - p0 += width, p1 -= width) { - memcpy(lineBuf, p0, width); - memcpy(p0, p1, width); - memcpy(p1, lineBuf, width); + p = colorIn; + q = colorOut + (width - 1) * nComps; + for (int i = 0; i < width; ++i) { + for (int j = 0; j < nComps; ++j) { + q[j] = p[j]; } + p += nComps; + q -= nComps; } - gfree(lineBuf); -} -void Splash::horizFlipImage(SplashBitmap *img, int width, int height, - int nComps) { - Guchar *lineBuf; - SplashColorPtr p0, p1, p2; - int w, x, y, i; - - w = width * nComps; - lineBuf = (Guchar *)gmalloc(w); - for (y = 0, p0 = img->data; y < height; ++y, p0 += img->rowSize) { - memcpy(lineBuf, p0, w); - p1 = p0; - p2 = lineBuf + (w - nComps); - for (x = 0; x < width; ++x) { - for (i = 0; i < nComps; ++i) { - p1[i] = p2[i]; - } - p1 += nComps; - p2 -= nComps; - } - } - if (img->alpha) { - for (y = 0, p0 = img->alpha; y < height; ++y, p0 += width) { - memcpy(lineBuf, p0, width); - p1 = p0; - p2 = lineBuf + (width - 1); - for (x = 0; x < width; ++x) { - *p1++ = *p2--; - } + if (srcAlpha) { + p = alphaIn; + q = alphaOut + (width - 1); + for (int i = 0; i < width; ++i) { + *q = *p; + ++p; + --q; } } - gfree(lineBuf); } -void Splash::blitImage(SplashBitmap *src, GBool srcAlpha, int xDest, int yDest, - SplashClipResult clipRes) { - SplashPipe pipe; - int w, h, x0, y0, x1, y1, y; +void Splash::drawImageRowNoClipNoAlpha(SplashDrawImageRowData *data, + Guchar *colorData, Guchar *alphaData, + int x, int y, int width) { + (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, NULL, colorData); +} - // split the image into clipped and unclipped regions - w = src->width; - h = src->height; - if (clipRes == splashClipAllInside) { - x0 = 0; - y0 = 0; - x1 = w; - y1 = h; - } else { - if (state->clip->getNumPaths()) { - x0 = x1 = w; - y0 = y1 = h; - } else { - if ((x0 = splashCeil(state->clip->getXMin()) - xDest) < 0) { - x0 = 0; - } - if ((y0 = splashCeil(state->clip->getYMin()) - yDest) < 0) { - y0 = 0; - } - if ((x1 = splashFloor(state->clip->getXMax()) - xDest) > w) { - x1 = w; - } - if (x1 < x0) { - x1 = x0; - } - if ((y1 = splashFloor(state->clip->getYMax()) - yDest) > h) { - y1 = h; - } - if (y1 < y0) { - y1 = y0; - } - } - } +void Splash::drawImageRowNoClipAlpha(SplashDrawImageRowData *data, + Guchar *colorData, Guchar *alphaData, + int x, int y, int width) { + (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, + alphaData, colorData); +} - // draw the unclipped region - if (x0 < w && y0 < h && x0 < x1 && y0 < y1) { - pipeInit(&pipe, NULL, - (Guchar)splashRound(state->fillAlpha * 255), - srcAlpha, gFalse); - if (srcAlpha) { - for (y = y0; y < y1; ++y) { - (this->*pipe.run)(&pipe, xDest + x0, xDest + x1 - 1, yDest + y, - src->alpha + y * src->alphaRowSize + x0, - src->data + y * src->rowSize + x0 * bitmapComps); - } - } else { - for (y = y0; y < y1; ++y) { - (this->*pipe.run)(&pipe, xDest + x0, xDest + x1 - 1, yDest + y, - NULL, - src->data + y * src->getRowSize() + - x0 * bitmapComps); - } - } +void Splash::drawImageRowClipNoAlphaNoAA(SplashDrawImageRowData *data, + Guchar *colorData, + Guchar *alphaData, + int x, int y, int width) { + if (y < 0 || y >= bitmap->height) { + return; + } + if (x < 0) { + colorData -= x * data->nComps; + width += x; + x = 0; + } + if (x + width > bitmap->width) { + width = bitmap->width - x; } + if (width <= 0) { + return; + } + memset(scanBuf + x, 0xff, width); + state->clip->clipSpanBinary(scanBuf, y, x, x + width - 1, + state->strokeAdjust); + (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, + scanBuf + x, colorData); +} - // draw the clipped regions - if (y0 > 0) { - blitImageClipped(src, srcAlpha, 0, 0, xDest, yDest, w, y0); +void Splash::drawImageRowClipNoAlphaAA(SplashDrawImageRowData *data, + Guchar *colorData, + Guchar *alphaData, + int x, int y, int width) { + if (y < 0 || y >= bitmap->height) { + return; } - if (y1 < h) { - blitImageClipped(src, srcAlpha, 0, y1, xDest, yDest + y1, w, h - y1); + if (x < 0) { + colorData -= x * data->nComps; + width += x; + x = 0; } - if (x0 > 0 && y0 < y1) { - blitImageClipped(src, srcAlpha, 0, y0, xDest, yDest + y0, x0, y1 - y0); + if (x + width > bitmap->width) { + width = bitmap->width - x; } - if (x1 < w && y0 < y1) { - blitImageClipped(src, srcAlpha, x1, y0, xDest + x1, yDest + y0, - w - x1, y1 - y0); + if (width <= 0) { + return; } + memset(scanBuf + x, 0xff, width); + state->clip->clipSpan(scanBuf, y, x, x + width - 1, state->strokeAdjust); + (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, + scanBuf + x, colorData); } -void Splash::blitImageClipped(SplashBitmap *src, GBool srcAlpha, - int xSrc, int ySrc, int xDest, int yDest, - int w, int h) { - SplashPipe pipe; - int y; - - if (xDest < 0) { - xSrc -= xDest; - w += xDest; - xDest = 0; - } - if (xDest + w > bitmap->width) { - w = bitmap->width - xDest; +void Splash::drawImageRowClipAlphaNoAA(SplashDrawImageRowData *data, + Guchar *colorData, + Guchar *alphaData, + int x, int y, int width) { + if (y < 0 || y >= bitmap->height) { + return; } - if (yDest < 0) { - ySrc -= yDest; - h += yDest; - yDest = 0; + if (x < 0) { + colorData -= x * data->nComps; + alphaData -= x; + width += x; + x = 0; } - if (yDest + h > bitmap->height) { - h = bitmap->height - yDest; + if (x + width > bitmap->width) { + width = bitmap->width - x; } - if (w <= 0 || h <= 0) { + if (width <= 0) { return; } - - pipeInit(&pipe, NULL, - (Guchar)splashRound(state->fillAlpha * 255), - gTrue, gFalse); - if (srcAlpha) { - for (y = 0; y < h; ++y) { - memcpy(scanBuf + xDest, - src->alpha + (ySrc + y) * src->alphaRowSize + xSrc, - w); - if (vectorAntialias) { - state->clip->clipSpan(scanBuf, yDest + y, xDest, xDest + w - 1, - state->strokeAdjust); - } else { - state->clip->clipSpanBinary(scanBuf, yDest + y, xDest, xDest + w - 1, - state->strokeAdjust); - } - (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y, - scanBuf + xDest, - src->data + (ySrc + y) * src->rowSize + - xSrc * bitmapComps); - } - } else { - for (y = 0; y < h; ++y) { - memset(scanBuf + xDest, 0xff, w); - if (vectorAntialias) { - state->clip->clipSpan(scanBuf, yDest + y, xDest, xDest + w - 1, + memcpy(scanBuf + x, alphaData, width); + state->clip->clipSpanBinary(scanBuf, y, x, x + width - 1, state->strokeAdjust); - } else { - state->clip->clipSpanBinary(scanBuf, yDest + y, xDest, xDest + w - 1, - state->strokeAdjust); - } - (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y, - scanBuf + xDest, - src->data + (ySrc + y) * src->rowSize + - xSrc * bitmapComps); - } + (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, + scanBuf + x, colorData); +} + +void Splash::drawImageRowClipAlphaAA(SplashDrawImageRowData *data, + Guchar *colorData, + Guchar *alphaData, + int x, int y, int width) { + if (y < 0 || y >= bitmap->height) { + return; + } + if (x < 0) { + colorData -= x * data->nComps; + alphaData -= x; + width += x; + x = 0; + } + if (x + width > bitmap->width) { + width = bitmap->width - x; } + if (width <= 0) { + return; + } + memcpy(scanBuf + x, alphaData, width); + state->clip->clipSpan(scanBuf, y, x, x + width - 1, state->strokeAdjust); + (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, + scanBuf + x, colorData); } SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc, @@ -6950,6 +7266,101 @@ SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc, return splashOk; } +SplashError Splash::compositeWithOverprint(SplashBitmap *src, + Guint *srcOverprintMaskBitmap, + int xSrc, int ySrc, + int xDest, int yDest, int w, int h, + GBool noClip, GBool nonIsolated) { + SplashPipe pipe; + int x0, x1, y0, y1, y, t; + + if (!(src->mode == bitmap->mode || + (src->mode == splashModeMono8 && bitmap->mode == splashModeMono1) || + (src->mode == splashModeRGB8 && bitmap->mode == splashModeBGR8))) { + return splashErrModeMismatch; + } + + pipeInit(&pipe, NULL, + (Guchar)splashRound(state->fillAlpha * 255), + !noClip || src->alpha != NULL, nonIsolated, gTrue); + + if (noClip) { + if (src->alpha) { + for (y = 0; y < h; ++y) { + pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap + y * w + xSrc; + // this uses shape instead of alpha, which isn't technically + // correct, but works out the same + (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y, + src->alpha + + (ySrc + y) * src->alphaRowSize + xSrc, + src->data + (ySrc + y) * src->rowSize + + xSrc * bitmapComps); + } + } else { + for (y = 0; y < h; ++y) { + pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap + y * w + xSrc; + (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y, + NULL, + src->data + (ySrc + y) * src->rowSize + + xSrc * bitmapComps); + } + } + } else { + x0 = xDest; + if ((t = state->clip->getXMinI(state->strokeAdjust)) > x0) { + x0 = t; + } + x1 = xDest + w; + if ((t = state->clip->getXMaxI(state->strokeAdjust) + 1) < x1) { + x1 = t; + } + y0 = yDest; + if ((t = state->clip->getYMinI(state->strokeAdjust)) > y0) { + y0 = t; + } + y1 = yDest + h; + if ((t = state->clip->getYMaxI(state->strokeAdjust) + 1) < y1) { + y1 = t; + } + if (x0 < x1 && y0 < y1) { + if (src->alpha) { + for (y = y0; y < y1; ++y) { + memcpy(scanBuf + x0, + src->alpha + (ySrc + y - yDest) * src->alphaRowSize + + (xSrc + x0 - xDest), + x1 - x0); + state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust); + pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap + + (ySrc + y - yDest) * w + + (xSrc + x0 - xDest); + // this uses shape instead of alpha, which isn't technically + // correct, but works out the same + (this->*pipe.run)(&pipe, x0, x1 - 1, y, + scanBuf + x0, + src->data + + (ySrc + y - yDest) * src->rowSize + + (xSrc + x0 - xDest) * bitmapComps); + } + } else { + for (y = y0; y < y1; ++y) { + memset(scanBuf + x0, 0xff, x1 - x0); + state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust); + pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap + + (ySrc + y - yDest) * w + + (xSrc + x0 - xDest); + (this->*pipe.run)(&pipe, x0, x1 - 1, y, + scanBuf + x0, + src->data + + (ySrc + y - yDest) * src->rowSize + + (xSrc + x0 - xDest) * bitmapComps); + } + } + } + } + + return splashOk; +} + void Splash::compositeBackground(SplashColorPtr color) { SplashColorPtr p; Guchar *q; diff --git a/Build/source/libs/xpdf/xpdf-src/splash/Splash.h b/Build/source/libs/xpdf/xpdf-src/splash/Splash.h index f35aa7eafd9..d6cc33a5fad 100644 --- a/Build/source/libs/xpdf/xpdf-src/splash/Splash.h +++ b/Build/source/libs/xpdf/xpdf-src/splash/Splash.h @@ -29,6 +29,17 @@ class SplashPath; class SplashXPath; class SplashFont; struct SplashPipe; +struct SplashDrawImageMaskRowData; +class ImageScaler; +typedef void (Splash::*SplashDrawImageMaskRowFunc)( + SplashDrawImageMaskRowData *data, + Guchar *maskData, + int x, int y, int width); +struct SplashDrawImageRowData; +typedef void (Splash::*SplashDrawImageRowFunc)(SplashDrawImageRowData *data, + Guchar *colorData, + Guchar *alphaData, + int x, int y, int width); //------------------------------------------------------------------------ @@ -65,6 +76,15 @@ enum SplashPipeResultColorCtrl { #endif }; +//------------------------------------------------------------------------ + +// Transparency group destination bitmap initialization control. +enum SplashGroupDestInitMode { + splashGroupDestPreInit, // dest is already initialized + splashGroupDestInitZero, // initialize to zero (isolated group) + splashGroupDestInitCopy // copy backdrop (non-isolated group) +}; + //------------------------------------------------------------------------ // SplashImageCache //------------------------------------------------------------------------ @@ -76,19 +96,23 @@ public: SplashImageCache(); ~SplashImageCache(); + GBool match(GString *aTag, int aWidth, int aHeight, + SplashColorMode aMode, GBool aAlpha, + GBool aInterpolate); + void reset(GString *aTag, int aWidth, int aHeight, + SplashColorMode aMode, GBool aAlpha, + GBool aInterpolate); void incRefCount(); void decRefCount(); GString *tag; - GBool isMask; int width; int height; SplashColorMode mode; GBool alpha; GBool interpolate; - GBool vertFlip; - GBool horizFlip; - SplashBitmap *image; + Guchar *colorData; + Guchar *alphaData; int refCount; }; @@ -161,7 +185,9 @@ public: void setSoftMask(SplashBitmap *softMask); void setInTransparencyGroup(SplashBitmap *groupBackBitmapA, int groupBackXA, int groupBackYA, + SplashGroupDestInitMode groupDestInitModeA, GBool nonIsolated, GBool knockout); + void forceDeferredInit(int y, int h); void setTransfer(Guchar *red, Guchar *green, Guchar *blue, Guchar *gray); void setOverprintMask(Guint overprintMask); void setEnablePathSimplification(GBool en); @@ -234,6 +260,15 @@ public: int xDest, int yDest, int w, int h, GBool noClip, GBool nonIsolated); + // Composite a rectangular region from onto this Splash + // object, using as the overprint mask per + // pixel. This is only supported for CMYK and DeviceN bitmaps. + SplashError compositeWithOverprint(SplashBitmap *src, + Guint *srcOverprintMaskBitmap, + int xSrc, int ySrc, + int xDest, int yDest, int w, int h, + GBool noClip, GBool nonIsolated); + // Composite this Splash object onto a background color. The // background alpha is assumed to be 1. void compositeBackground(SplashColorPtr color); @@ -269,6 +304,10 @@ public: // Return the associated bitmap. SplashBitmap *getBitmap() { return bitmap; } + // Enable writing the per-pixel overprint mask to a separate bitmap. + void setOverprintMaskBitmap(Guint *overprintMaskBitmapA) + { overprintMaskBitmap = overprintMaskBitmapA; } + // Set the minimum line width. void setMinLineWidth(SplashCoord w) { minLineWidth = w; } @@ -298,7 +337,7 @@ private: void pipeInit(SplashPipe *pipe, SplashPattern *pattern, Guchar aInput, GBool usesShape, - GBool nonIsolatedGroup); + GBool nonIsolatedGroup, GBool usesSrcOverprint = gFalse); void pipeRun(SplashPipe *pipe, int x0, int x1, int y, Guchar *shapePtr, SplashColorPtr cSrcPtr); void pipeRunSimpleMono1(SplashPipe *pipe, int x0, int x1, int y, @@ -325,6 +364,8 @@ private: void pipeRunShapeCMYK8(SplashPipe *pipe, int x0, int x1, int y, Guchar *shapePtr, SplashColorPtr cSrcPtr); #endif + void pipeRunShapeNoAlphaMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); void pipeRunAAMono1(SplashPipe *pipe, int x0, int x1, int y, Guchar *shapePtr, SplashColorPtr cSrcPtr); void pipeRunAAMono8(SplashPipe *pipe, int x0, int x1, int y, @@ -336,6 +377,16 @@ private: #if SPLASH_CMYK void pipeRunAACMYK8(SplashPipe *pipe, int x0, int x1, int y, Guchar *shapePtr, SplashColorPtr cSrcPtr); +#endif + void pipeRunSoftMaskMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunSoftMaskRGB8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunSoftMaskBGR8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); +#if SPLASH_CMYK + void pipeRunSoftMaskCMYK8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); #endif void pipeRunNonIsoMono8(SplashPipe *pipe, int x0, int x1, int y, Guchar *shapePtr, SplashColorPtr cSrcPtr); @@ -347,6 +398,8 @@ private: void pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y, Guchar *shapePtr, SplashColorPtr cSrcPtr); #endif + void useDestRow(int y); + void copyGroupBackdropRow(int y); void transform(SplashCoord *matrix, SplashCoord xi, SplashCoord yi, SplashCoord *xo, SplashCoord *yo); void updateModX(int x); @@ -371,92 +424,81 @@ private: SplashError fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph); void getImageBounds(SplashCoord xyMin, SplashCoord xyMax, int *xyMinI, int *xyMaxI); - void upscaleMask(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - SplashCoord *mat, GBool glyphMode, - GBool interpolate); - void arbitraryTransformMask(GString *imageTag, - SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - SplashCoord *mat, GBool glyphMode, - GBool interpolate); - SplashBitmap *scaleMask(GString *imageTag, - SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - GBool interpolate); - void scaleMaskYdXd(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void scaleMaskYdXu(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void scaleMaskYuXd(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void scaleMaskYuXu(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void scaleMaskYuXuI(SplashImageMaskSource src, void *srcData, - int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void blitMask(SplashBitmap *src, int xDest, int yDest, - SplashClipResult clipRes); - void upscaleImage(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - SplashCoord *mat, GBool interpolate); - void arbitraryTransformImage(GString *imageTag, - SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, - int srcWidth, int srcHeight, - SplashCoord *mat, GBool interpolate); - SplashBitmap *scaleImage(GString *imageTag, - SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - GBool interpolate); - void scaleImageYdXd(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void scaleImageYdXu(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void scaleImageYuXd(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void scaleImageYuXu(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, + void drawImageMaskArbitraryNoInterp(Guchar *scaledMask, + SplashDrawImageMaskRowData *dd, + SplashDrawImageMaskRowFunc drawRowFunc, + SplashCoord *invMat, + int scaledWidth, int scaledHeight, + int xMin, int yMin, int xMax, int yMax); + void drawImageMaskArbitraryInterp(Guchar *scaledMask, + SplashDrawImageMaskRowData *dd, + SplashDrawImageMaskRowFunc drawRowFunc, + SplashCoord *invMat, + int scaledWidth, int scaledHeight, + int xMin, int yMin, int xMax, int yMax); + void mirrorImageMaskRow(Guchar *maskIn, Guchar *maskOut, int width); + void drawImageMaskRowNoClip(SplashDrawImageMaskRowData *data, + Guchar *maskData, + int x, int y, int width); + void drawImageMaskRowClipNoAA(SplashDrawImageMaskRowData *data, + Guchar *maskData, + int x, int y, int width); + void drawImageMaskRowClipAA(SplashDrawImageMaskRowData *data, + Guchar *maskData, + int x, int y, int width); + ImageScaler *getImageScaler(GString *imageTag, + SplashImageSource src, void *srcData, + int w, int h, int nComps, + int scaledWidth, int scaledHeight, + SplashColorMode srcMode, + GBool srcAlpha, GBool interpolate); + void getScaledImage(GString *imageTag, + SplashImageSource src, void *srcData, + int w, int h, int nComps, int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void scaleImageYuXuI(SplashImageSource src, void *srcData, - SplashColorMode srcMode, int nComps, - GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight, - SplashBitmap *dest); - void vertFlipImage(SplashBitmap *img, int width, int height, - int nComps); - void horizFlipImage(SplashBitmap *img, int width, int height, - int nComps); - void blitImage(SplashBitmap *src, GBool srcAlpha, int xDest, int yDest, - SplashClipResult clipRes); - void blitImageClipped(SplashBitmap *src, GBool srcAlpha, - int xSrc, int ySrc, int xDest, int yDest, - int w, int h); + SplashColorMode srcMode, + GBool srcAlpha, GBool interpolate, + Guchar **scaledColor, Guchar **scaledAlpha, + GBool *freeScaledImage); + void drawImageArbitraryNoInterp(Guchar *scaledColor, Guchar *scaledAlpha, + SplashDrawImageRowData *dd, + SplashDrawImageRowFunc drawRowFunc, + SplashCoord *invMat, + int scaledWidth, int scaledHeight, + int xMin, int yMin, int xMax, int yMax, + int nComps, GBool srcAlpha); + void drawImageArbitraryInterp(Guchar *scaledColor, Guchar *scaledAlpha, + SplashDrawImageRowData *dd, + SplashDrawImageRowFunc drawRowFunc, + SplashCoord *invMat, + int scaledWidth, int scaledHeight, + int xMin, int yMin, int xMax, int yMax, + int nComps, GBool srcAlpha); + void mirrorImageRow(Guchar *colorIn, Guchar *alphaIn, + Guchar *colorOut, Guchar *alphaOut, + int width, int nComps, GBool srcAlpha); + void drawImageRowNoClipNoAlpha(SplashDrawImageRowData *data, + Guchar *colorData, Guchar *alphaData, + int x, int y, int width); + void drawImageRowNoClipAlpha(SplashDrawImageRowData *data, + Guchar *colorData, Guchar *alphaData, + int x, int y, int width); + void drawImageRowClipNoAlphaNoAA(SplashDrawImageRowData *data, + Guchar *colorData, + Guchar *alphaData, + int x, int y, int width); + void drawImageRowClipNoAlphaAA(SplashDrawImageRowData *data, + Guchar *colorData, + Guchar *alphaData, + int x, int y, int width); + void drawImageRowClipAlphaNoAA(SplashDrawImageRowData *data, + Guchar *colorData, + Guchar *alphaData, + int x, int y, int width); + void drawImageRowClipAlphaAA(SplashDrawImageRowData *data, + Guchar *colorData, + Guchar *alphaData, + int x, int y, int width); void dumpPath(SplashPath *path); void dumpXPath(SplashXPath *path); @@ -474,6 +516,9 @@ private: SplashBitmap // for transparency groups, this is the bitmap *groupBackBitmap; // containing the alpha0/color0 values int groupBackX, groupBackY; // offset within groupBackBitmap + SplashGroupDestInitMode groupDestInitMode; + int groupDestInitYMin, groupDestInitYMax; + Guint *overprintMaskBitmap; SplashCoord minLineWidth; int modXMin, modYMin, modXMax, modYMax; SplashClipResult opClipRes; diff --git a/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.cc b/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.cc index 88ce49ca3b4..db341d7c733 100644 --- a/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.cc +++ b/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.cc @@ -16,6 +16,8 @@ #include #include "gmem.h" #include "gmempp.h" +#include "gfile.h" +#include "Trace.h" #include "SplashErrorCodes.h" #include "SplashBitmap.h" @@ -64,6 +66,11 @@ SplashBitmap::SplashBitmap(int widthA, int heightA, int rowPad, rowSize += rowPad - 1; rowSize -= rowSize % rowPad; + traceAlloc(this, "alloc bitmap: %d x %d x %d %s -> %lld bytes", + width, height, splashColorModeNComps[mode], + alphaA ? "with alpha" : "without alpha", + height * rowSize + (alphaA ? height * width : 0)); + parent = parentA; oldData = NULL; oldAlpha = NULL; @@ -75,8 +82,14 @@ SplashBitmap::SplashBitmap(int widthA, int heightA, int rowPad, parent->oldHeight == height) { data = parent->oldData; parent->oldData = NULL; + traceMessage("reusing bitmap memory"); } else { data = (SplashColorPtr)gmallocn64(height, rowSize); + traceMessage("not reusing bitmap memory" + " (parent=%p parent->oldData=%p same-size=%d)", + parent, parent ? parent->oldData : NULL, + parent ? (parent->oldRowSize == rowSize && + parent->oldHeight == height) : 0); } if (!topDown) { data += (height - 1) * rowSize; @@ -99,11 +112,12 @@ SplashBitmap::SplashBitmap(int widthA, int heightA, int rowPad, } SplashBitmap::~SplashBitmap() { + traceFree(this, "free bitmap"); if (data && rowSize < 0) { rowSize = -rowSize; data -= (height - 1) * rowSize; } - if (parent && rowSize > 10000000 / height) { + if (parent && rowSize > 4000000 / height) { gfree(parent->oldData); gfree(parent->oldAlpha); parent->oldData = data; @@ -123,7 +137,7 @@ SplashError SplashBitmap::writePNMFile(char *fileName) { FILE *f; SplashError err; - if (!(f = fopen(fileName, "wb"))) { + if (!(f = openFile(fileName, "wb"))) { return splashErrOpenFile; } err = writePNMFile(f); @@ -211,7 +225,7 @@ SplashError SplashBitmap::writeAlphaPGMFile(char *fileName) { if (!alpha) { return splashErrModeMismatch; } - if (!(f = fopen(fileName, "wb"))) { + if (!(f = openFile(fileName, "wb"))) { return splashErrOpenFile; } fprintf(f, "P5\n%d %d\n255\n", width, height); diff --git a/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.h b/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.h index 44980833668..54975a377a2 100644 --- a/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.h +++ b/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.h @@ -17,6 +17,10 @@ #include #include +// older compilers won't define SIZE_MAX in stdint.h without this +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif #include #include "SplashTypes.h" diff --git a/Build/source/libs/xpdf/xpdf-src/splash/SplashFont.cc b/Build/source/libs/xpdf/xpdf-src/splash/SplashFont.cc index add34311756..2fcd83d1266 100644 --- a/Build/source/libs/xpdf/xpdf-src/splash/SplashFont.cc +++ b/Build/source/libs/xpdf/xpdf-src/splash/SplashFont.cc @@ -71,7 +71,11 @@ void SplashFont::initCache() { // if the glyphs are too large, don't cache them -- setting the // cache bitmap size to something tiny will cause getGlyph() to // fall back to the uncached case - glyphW = glyphH = 1; + glyphW = glyphH = 0; + glyphSize = 0; + cacheSets = 0; + cacheAssoc = 0; + return; } if (aa) { glyphSize = glyphW * glyphH; @@ -117,29 +121,33 @@ GBool SplashFont::getGlyph(int c, int xFrac, int yFrac, } // check the cache - i = (c & (cacheSets - 1)) * cacheAssoc; - for (j = 0; j < cacheAssoc; ++j) { - if ((cacheTags[i+j].mru & 0x80000000) && - cacheTags[i+j].c == c && - (int)cacheTags[i+j].xFrac == xFrac && - (int)cacheTags[i+j].yFrac == yFrac) { - bitmap->x = cacheTags[i+j].x; - bitmap->y = cacheTags[i+j].y; - bitmap->w = cacheTags[i+j].w; - bitmap->h = cacheTags[i+j].h; - for (k = 0; k < cacheAssoc; ++k) { - if (k != j && - (cacheTags[i+k].mru & 0x7fffffff) < + if (cache) { + i = (c & (cacheSets - 1)) * cacheAssoc; + for (j = 0; j < cacheAssoc; ++j) { + if ((cacheTags[i+j].mru & 0x80000000) && + cacheTags[i+j].c == c && + (int)cacheTags[i+j].xFrac == xFrac && + (int)cacheTags[i+j].yFrac == yFrac) { + bitmap->x = cacheTags[i+j].x; + bitmap->y = cacheTags[i+j].y; + bitmap->w = cacheTags[i+j].w; + bitmap->h = cacheTags[i+j].h; + for (k = 0; k < cacheAssoc; ++k) { + if (k != j && + (cacheTags[i+k].mru & 0x7fffffff) < (cacheTags[i+j].mru & 0x7fffffff)) { - ++cacheTags[i+k].mru; + ++cacheTags[i+k].mru; + } } + cacheTags[i+j].mru = 0x80000000; + bitmap->aa = aa; + bitmap->data = cache + (i+j) * glyphSize; + bitmap->freeData = gFalse; + return gTrue; } - cacheTags[i+j].mru = 0x80000000; - bitmap->aa = aa; - bitmap->data = cache + (i+j) * glyphSize; - bitmap->freeData = gFalse; - return gTrue; } + } else { + i = 0; // make gcc happy } // generate the glyph bitmap @@ -149,7 +157,7 @@ GBool SplashFont::getGlyph(int c, int xFrac, int yFrac, // if the glyph doesn't fit in the bounding box, return a temporary // uncached bitmap - if (bitmap2.w > glyphW || bitmap2.h > glyphH) { + if (!cache || bitmap2.w > glyphW || bitmap2.h > glyphH) { *bitmap = bitmap2; return gTrue; } diff --git a/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.cc b/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.cc index fe377fe25f4..81afe0cd914 100644 --- a/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.cc +++ b/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.cc @@ -20,6 +20,7 @@ #include "gmem.h" #include "gmempp.h" #include "GString.h" +#include "GList.h" #include "SplashMath.h" #include "SplashFTFontEngine.h" #include "SplashFontFile.h" @@ -48,6 +49,7 @@ SplashFontEngine::SplashFontEngine( for (i = 0; i < splashFontCacheSize; ++i) { fontCache[i] = NULL; } + badFontFiles = new GList(); #if HAVE_FREETYPE_H if (enableFreeType) { @@ -66,6 +68,7 @@ SplashFontEngine::~SplashFontEngine() { delete fontCache[i]; } } + deleteGList(badFontFiles, SplashFontFileID); #if HAVE_FREETYPE_H if (ftEngine) { @@ -89,6 +92,15 @@ SplashFontFile *SplashFontEngine::getFontFile(SplashFontFileID *id) { return NULL; } +GBool SplashFontEngine::checkForBadFontFile(SplashFontFileID *id) { + for (int i = 0; i < badFontFiles->getLength(); ++i) { + if (((SplashFontFileID *)badFontFiles->get(i))->matches(id)) { + return gTrue; + } + } + return gFalse; +} + SplashFontFile *SplashFontEngine::loadType1Font(SplashFontFileID *idA, #if LOAD_FONTS_FROM_MEM GString *fontBuf, @@ -122,6 +134,10 @@ SplashFontFile *SplashFontEngine::loadType1Font(SplashFontFileID *idA, } #endif + if (!fontFile) { + badFontFiles->append(idA); + } + return fontFile; } @@ -162,6 +178,10 @@ SplashFontFile *SplashFontEngine::loadType1CFont(SplashFontFileID *idA, } #endif + if (!fontFile) { + badFontFiles->append(idA); + } + return fontFile; } @@ -202,6 +222,10 @@ SplashFontFile *SplashFontEngine::loadOpenTypeT1CFont(SplashFontFileID *idA, } #endif + if (!fontFile) { + badFontFiles->append(idA); + } + return fontFile; } @@ -243,6 +267,10 @@ SplashFontFile *SplashFontEngine::loadCIDFont(SplashFontFileID *idA, } #endif + if (!fontFile) { + badFontFiles->append(idA); + } + return fontFile; } @@ -284,6 +312,10 @@ SplashFontFile *SplashFontEngine::loadOpenTypeCFFFont(SplashFontFileID *idA, } #endif + if (!fontFile) { + badFontFiles->append(idA); + } + return fontFile; } @@ -327,6 +359,10 @@ SplashFontFile *SplashFontEngine::loadTrueTypeFont(SplashFontFileID *idA, } #endif + if (!fontFile) { + badFontFiles->append(idA); + } + return fontFile; } diff --git a/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.h b/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.h index f9eab4d00f4..59adb95858a 100644 --- a/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.h +++ b/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.h @@ -17,6 +17,7 @@ #include "gtypes.h" class GString; +class GList; class SplashFTFontEngine; class SplashDTFontEngine; @@ -54,6 +55,10 @@ public: // matching entry in the cache. SplashFontFile *getFontFile(SplashFontFileID *id); + // Returns true if [id] refers to a bad font file, i.e., if one of + // the loadXXXFont functions has returned NULL for that ID. + GBool checkForBadFontFile(SplashFontFileID *id); + // Load fonts - these create new SplashFontFile objects. SplashFontFile *loadType1Font(SplashFontFileID *idA, #if LOAD_FONTS_FROM_MEM @@ -114,6 +119,7 @@ public: private: SplashFont *fontCache[splashFontCacheSize]; + GList *badFontFiles; // [SplashFontFileID] #if HAVE_FREETYPE_H SplashFTFontEngine *ftEngine; diff --git a/Build/source/libs/xpdf/xpdf-src/splash/SplashXPath.cc b/Build/source/libs/xpdf/xpdf-src/splash/SplashXPath.cc index 01a94987637..4c77765723e 100644 --- a/Build/source/libs/xpdf/xpdf-src/splash/SplashXPath.cc +++ b/Build/source/libs/xpdf/xpdf-src/splash/SplashXPath.cc @@ -593,7 +593,8 @@ void SplashXPath::finishSegments() { seg->dydx = 0; } #else - if (seg->y0 == seg->y1 || seg->x0 == seg->x1) { + if (splashAbs(seg->y1 - seg->y0) < 1e-200 || + splashAbs(seg->x1 - seg->x0) < 1e-200) { seg->dxdy = 0; seg->dydx = 0; } else { diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.cc index c7f30405483..b98ca2f0189 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.cc @@ -27,6 +27,9 @@ #include "OptionalContent.h" #include "Annot.h" #include "Lexer.h" +#include "XFAScanner.h" +#include "UTF8.h" +#include "PDF417Barcode.h" #include "AcroForm.h" //------------------------------------------------------------------------ @@ -55,6 +58,11 @@ #define acroFormQuadCenter 1 #define acroFormQuadRight 2 +#define acroFormVAlignTop 0 +#define acroFormVAlignMiddle 1 +#define acroFormVAlignMiddleNoDescender 2 +#define acroFormVAlignBottom 3 + #define annotFlagHidden 0x0002 #define annotFlagPrint 0x0004 #define annotFlagNoView 0x0020 @@ -63,6 +71,256 @@ // = (4 * (sqrt(2) - 1) / 3) * r #define bezierCircle 0.55228475 +// limit recursive field-parent lookups to avoid infinite loops +#define maxFieldObjectDepth 50 + +//------------------------------------------------------------------------ + +// 5 bars + 5 spaces -- each can be wide (1) or narrow (0) +// (there are always exactly 3 wide elements; +// the last space is always narrow) +static Guchar code3Of9Data[128][10] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x00 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x10 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 1, 0, 0, 0, 1, 0, 0, 0 }, // ' ' = 0x20 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 1, 0, 1, 0, 0, 0, 0 }, // '$' = 0x24 + { 0, 0, 0, 1, 0, 1, 0, 1, 0, 0 }, // '%' = 0x25 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 1, 0, 1, 0, 0, 0 }, // '*' = 0x2a + { 0, 1, 0, 0, 0, 1, 0, 1, 0, 0 }, // '+' = 0x2b + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0 }, // '-' = 0x2d + { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0 }, // '.' = 0x2e + { 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }, // '/' = 0x2f + { 0, 0, 0, 1, 1, 0, 1, 0, 0, 0 }, // '0' = 0x30 + { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0 }, // '1' + { 0, 0, 1, 1, 0, 0, 0, 0, 1, 0 }, // '2' + { 1, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, // '3' + { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 }, // '4' + { 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // '5' + { 0, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // '6' + { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 }, // '7' + { 1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, // '8' + { 0, 0, 1, 1, 0, 0, 1, 0, 0, 0 }, // '9' + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x40 + { 1, 0, 0, 0, 0, 1, 0, 0, 1, 0 }, // 'A' = 0x41 + { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 }, // 'B' + { 1, 0, 1, 0, 0, 1, 0, 0, 0, 0 }, // 'C' + { 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 }, // 'D' + { 1, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // 'E' + { 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // 'F' + { 0, 0, 0, 0, 0, 1, 1, 0, 1, 0 }, // 'G' + { 1, 0, 0, 0, 0, 1, 1, 0, 0, 0 }, // 'H' + { 0, 0, 1, 0, 0, 1, 1, 0, 0, 0 }, // 'I' + { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, // 'J' + { 1, 0, 0, 0, 0, 0, 0, 1, 1, 0 }, // 'K' + { 0, 0, 1, 0, 0, 0, 0, 1, 1, 0 }, // 'L' + { 1, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, // 'M' + { 0, 0, 0, 0, 1, 0, 0, 1, 1, 0 }, // 'N' + { 1, 0, 0, 0, 1, 0, 0, 1, 0, 0 }, // 'O' + { 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 }, // 'P' = 0x50 + { 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, // 'Q' + { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 }, // 'R' + { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // 'S' + { 0, 0, 0, 0, 1, 0, 1, 1, 0, 0 }, // 'T' + { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0 }, // 'U' + { 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 }, // 'V' + { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, // 'W' + { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 }, // 'X' + { 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 }, // 'Y' + { 0, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, // 'Z' + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x60 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x70 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +// 3 bars + 3 spaces -- each can be 1, 2, 3, or 4 units wide +static Guchar code128Data[107][6] = { + { 2, 1, 2, 2, 2, 2 }, + { 2, 2, 2, 1, 2, 2 }, + { 2, 2, 2, 2, 2, 1 }, + { 1, 2, 1, 2, 2, 3 }, + { 1, 2, 1, 3, 2, 2 }, + { 1, 3, 1, 2, 2, 2 }, + { 1, 2, 2, 2, 1, 3 }, + { 1, 2, 2, 3, 1, 2 }, + { 1, 3, 2, 2, 1, 2 }, + { 2, 2, 1, 2, 1, 3 }, + { 2, 2, 1, 3, 1, 2 }, + { 2, 3, 1, 2, 1, 2 }, + { 1, 1, 2, 2, 3, 2 }, + { 1, 2, 2, 1, 3, 2 }, + { 1, 2, 2, 2, 3, 1 }, + { 1, 1, 3, 2, 2, 2 }, + { 1, 2, 3, 1, 2, 2 }, + { 1, 2, 3, 2, 2, 1 }, + { 2, 2, 3, 2, 1, 1 }, + { 2, 2, 1, 1, 3, 2 }, + { 2, 2, 1, 2, 3, 1 }, + { 2, 1, 3, 2, 1, 2 }, + { 2, 2, 3, 1, 1, 2 }, + { 3, 1, 2, 1, 3, 1 }, + { 3, 1, 1, 2, 2, 2 }, + { 3, 2, 1, 1, 2, 2 }, + { 3, 2, 1, 2, 2, 1 }, + { 3, 1, 2, 2, 1, 2 }, + { 3, 2, 2, 1, 1, 2 }, + { 3, 2, 2, 2, 1, 1 }, + { 2, 1, 2, 1, 2, 3 }, + { 2, 1, 2, 3, 2, 1 }, + { 2, 3, 2, 1, 2, 1 }, + { 1, 1, 1, 3, 2, 3 }, + { 1, 3, 1, 1, 2, 3 }, + { 1, 3, 1, 3, 2, 1 }, + { 1, 1, 2, 3, 1, 3 }, + { 1, 3, 2, 1, 1, 3 }, + { 1, 3, 2, 3, 1, 1 }, + { 2, 1, 1, 3, 1, 3 }, + { 2, 3, 1, 1, 1, 3 }, + { 2, 3, 1, 3, 1, 1 }, + { 1, 1, 2, 1, 3, 3 }, + { 1, 1, 2, 3, 3, 1 }, + { 1, 3, 2, 1, 3, 1 }, + { 1, 1, 3, 1, 2, 3 }, + { 1, 1, 3, 3, 2, 1 }, + { 1, 3, 3, 1, 2, 1 }, + { 3, 1, 3, 1, 2, 1 }, + { 2, 1, 1, 3, 3, 1 }, + { 2, 3, 1, 1, 3, 1 }, + { 2, 1, 3, 1, 1, 3 }, + { 2, 1, 3, 3, 1, 1 }, + { 2, 1, 3, 1, 3, 1 }, + { 3, 1, 1, 1, 2, 3 }, + { 3, 1, 1, 3, 2, 1 }, + { 3, 3, 1, 1, 2, 1 }, + { 3, 1, 2, 1, 1, 3 }, + { 3, 1, 2, 3, 1, 1 }, + { 3, 3, 2, 1, 1, 1 }, + { 3, 1, 4, 1, 1, 1 }, + { 2, 2, 1, 4, 1, 1 }, + { 4, 3, 1, 1, 1, 1 }, + { 1, 1, 1, 2, 2, 4 }, + { 1, 1, 1, 4, 2, 2 }, + { 1, 2, 1, 1, 2, 4 }, + { 1, 2, 1, 4, 2, 1 }, + { 1, 4, 1, 1, 2, 2 }, + { 1, 4, 1, 2, 2, 1 }, + { 1, 1, 2, 2, 1, 4 }, + { 1, 1, 2, 4, 1, 2 }, + { 1, 2, 2, 1, 1, 4 }, + { 1, 2, 2, 4, 1, 1 }, + { 1, 4, 2, 1, 1, 2 }, + { 1, 4, 2, 2, 1, 1 }, + { 2, 4, 1, 2, 1, 1 }, + { 2, 2, 1, 1, 1, 4 }, + { 4, 1, 3, 1, 1, 1 }, + { 2, 4, 1, 1, 1, 2 }, + { 1, 3, 4, 1, 1, 1 }, + { 1, 1, 1, 2, 4, 2 }, + { 1, 2, 1, 1, 4, 2 }, + { 1, 2, 1, 2, 4, 1 }, + { 1, 1, 4, 2, 1, 2 }, + { 1, 2, 4, 1, 1, 2 }, + { 1, 2, 4, 2, 1, 1 }, + { 4, 1, 1, 2, 1, 2 }, + { 4, 2, 1, 1, 1, 2 }, + { 4, 2, 1, 2, 1, 1 }, + { 2, 1, 2, 1, 4, 1 }, + { 2, 1, 4, 1, 2, 1 }, + { 4, 1, 2, 1, 2, 1 }, + { 1, 1, 1, 1, 4, 3 }, + { 1, 1, 1, 3, 4, 1 }, + { 1, 3, 1, 1, 4, 1 }, + { 1, 1, 4, 1, 1, 3 }, + { 1, 1, 4, 3, 1, 1 }, + { 4, 1, 1, 1, 1, 3 }, + { 4, 1, 1, 3, 1, 1 }, + { 1, 1, 3, 1, 4, 1 }, + { 1, 1, 4, 1, 3, 1 }, + { 3, 1, 1, 1, 4, 1 }, + { 4, 1, 1, 1, 3, 1 }, + { 2, 1, 1, 4, 1, 2 }, // start code A + { 2, 1, 1, 2, 1, 4 }, // start code B + { 2, 1, 1, 2, 3, 2 }, // start code C + { 2, 3, 3, 1, 1, 1 } // stop code (without final bar) +}; + //------------------------------------------------------------------------ // map an annotation ref to a page number @@ -83,13 +341,21 @@ AcroForm *AcroForm::load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA) { Object acroFormObj2; AcroForm *acroForm; AcroFormField *field; - Object fieldsObj, annotsObj, annotRef, annotObj, obj1, obj2; + Object xfaObj, fieldsObj, annotsObj, annotRef, annotObj, obj1, obj2; int pageNum, i, j; // this is the normal case: acroFormObj is a dictionary, as expected if (acroFormObjA->isDict()) { acroForm = new AcroForm(docA, acroFormObjA); + if (!acroFormObjA->dictLookup("XFA", &xfaObj)->isNull()) { + acroForm->xfaScanner = XFAScanner::load(&xfaObj); + if (!catalog->getNeedsRendering()) { + acroForm->isStaticXFA = gTrue; + } + } + xfaObj.free(); + if (acroFormObjA->dictLookup("NeedAppearances", &obj1)->isBool()) { acroForm->needAppearances = obj1.getBool(); } @@ -183,17 +449,25 @@ AcroForm *AcroForm::load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA) { return acroForm; } -AcroForm::AcroForm(PDFDoc *docA, Object *acroFormObjA): Form(docA) { +AcroForm::AcroForm(PDFDoc *docA, Object *acroFormObjA) { + doc = docA; acroFormObjA->copy(&acroFormObj); needAppearances = gFalse; annotPages = new GList(); fields = new GList(); + xfaScanner = NULL; + isStaticXFA = gFalse; } AcroForm::~AcroForm() { acroFormObj.free(); deleteGList(annotPages, AcroFormAnnotPage); deleteGList(fields, AcroFormField); + delete xfaScanner; +} + +const char *AcroForm::getType() { + return isStaticXFA ? "static XFA" : "AcroForm"; } void AcroForm::buildAnnotPageList(Catalog *catalog) { @@ -296,10 +570,44 @@ int AcroForm::getNumFields() { return fields->getLength(); } -FormField *AcroForm::getField(int idx) { +AcroFormField *AcroForm::getField(int idx) { return (AcroFormField *)fields->get(idx); } +AcroFormField *AcroForm::findField(int pg, double x, double y) { + AcroFormField *field; + double llx, lly, urx, ury; + int i; + + for (i = 0; i < fields->getLength(); ++i) { + field = (AcroFormField *)fields->get(i); + if (field->getPageNum() == pg) { + field->getBBox(&llx, &lly, &urx, &ury); + if (llx <= x && x <= urx && lly <= y && y <= ury) { + return field; + } + } + } + return NULL; +} + +int AcroForm::findFieldIdx(int pg, double x, double y) { + AcroFormField *field; + double llx, lly, urx, ury; + int i; + + for (i = 0; i < fields->getLength(); ++i) { + field = (AcroFormField *)fields->get(i); + if (field->getPageNum() == pg) { + field->getBBox(&llx, &lly, &urx, &ury); + if (llx <= x && x <= urx && lly <= y && y <= ury) { + return i; + } + } + } + return -1; +} + //------------------------------------------------------------------------ // AcroFormField //------------------------------------------------------------------------ @@ -307,11 +615,14 @@ FormField *AcroForm::getField(int idx) { AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) { GString *typeStr; TextString *nameA; + GString *xfaName; Guint flagsA; GBool haveFlags, typeFromParentA; Object fieldObjA, parentObj, parentObj2, obj1, obj2; AcroFormFieldType typeA; + XFAField *xfaFieldA; AcroFormField *field; + int depth, i0, i1; fieldRefA->fetch(acroFormA->doc->getXRef(), &fieldObjA); @@ -345,7 +656,8 @@ AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) { //----- get info from parent non-terminal fields fieldObjA.dictLookup("Parent", &parentObj); - while (parentObj.isDict()) { + depth = 0; + while (parentObj.isDict() && depth < maxFieldObjectDepth) { if (parentObj.dictLookup("T", &obj1)->isString()) { if (nameA->getLength()) { @@ -373,6 +685,8 @@ AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) { parentObj.dictLookup("Parent", &parentObj2); parentObj.free(); parentObj = parentObj2; + + ++depth; } parentObj.free(); @@ -381,6 +695,33 @@ AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) { goto err1; } + //----- get static XFA info + + xfaFieldA = NULL; + if (acroFormA->xfaScanner) { + // convert field name to UTF-8, and remove segments that start + // with '#' -- to match the XFA field name + xfaName = nameA->toUTF8(); + i0 = 0; + while (i0 < xfaName->getLength()) { + i1 = i0; + while (i1 < xfaName->getLength()) { + if (xfaName->getChar(i1) == '.') { + ++i1; + break; + } + ++i1; + } + if (xfaName->getChar(i0) == '#') { + xfaName->del(i0, i1 - i0); + } else { + i0 = i1; + } + } + xfaFieldA = acroFormA->xfaScanner->findField(xfaName); + delete xfaName; + } + //----- check for a radio button // this is a kludge: if we see a Btn-type field with kids, and the @@ -400,7 +741,9 @@ AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) { typeA = acroFormFieldCheckbox; } } else if (!typeStr->cmp("Tx")) { - if (flagsA & acroFormFlagFileSelect) { + if (xfaFieldA && xfaFieldA->getBarcodeInfo()) { + typeA = acroFormFieldBarcode; + } else if (flagsA & acroFormFlagFileSelect) { typeA = acroFormFieldFileSelect; } else if (flagsA & acroFormFlagMultiline) { typeA = acroFormFieldMultilineText; @@ -422,7 +765,7 @@ AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) { delete typeStr; field = new AcroFormField(acroFormA, fieldRefA, &fieldObjA, - typeA, nameA, flagsA, typeFromParentA); + typeA, nameA, flagsA, typeFromParentA, xfaFieldA); fieldObjA.free(); return field; @@ -436,7 +779,8 @@ AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) { AcroFormField::AcroFormField(AcroForm *acroFormA, Object *fieldRefA, Object *fieldObjA, AcroFormFieldType typeA, TextString *nameA, - Guint flagsA, GBool typeFromParentA) { + Guint flagsA, GBool typeFromParentA, + XFAField *xfaFieldA) { acroForm = acroFormA; fieldRefA->copy(&fieldRef); fieldObjA->copy(&fieldObj); @@ -444,6 +788,7 @@ AcroFormField::AcroFormField(AcroForm *acroFormA, name = nameA; flags = flagsA; typeFromParent = typeFromParentA; + xfaField = xfaFieldA; } AcroFormField::~AcroFormField() { @@ -478,6 +823,7 @@ const char *AcroFormField::getType() { case acroFormFieldFileSelect: return "FileSelect"; case acroFormFieldMultilineText: return "MultilineText"; case acroFormFieldText: return "Text"; + case acroFormFieldBarcode: return "Barcode"; case acroFormFieldComboBox: return "ComboBox"; case acroFormFieldListBox: return "ListBox"; case acroFormFieldSignature: return "Signature"; @@ -508,36 +854,47 @@ Unicode *AcroFormField::getValue(int *length) { u = NULL; *length = 0; - fieldLookup("V", &obj1); - if (obj1.isName()) { - s = obj1.getName(); - n = (int)strlen(s); - u = (Unicode *)gmallocn(n, sizeof(Unicode)); - for (i = 0; i < n; ++i) { - u[i] = s[i] & 0xff; - } - *length = n; - } else if (obj1.isString()) { - ts = new TextString(obj1.getString()); - n = ts->getLength(); - u = (Unicode *)gmallocn(n, sizeof(Unicode)); - memcpy(u, ts->getUnicode(), n * sizeof(Unicode)); - *length = n; - delete ts; - } else if (obj1.isDict()) { - obj1.dictLookup("Contents", &obj2); - if (obj2.isString()) { - gs = obj2.getString(); - n = gs->getLength(); + // if this field has a counterpart in the XFA form, take the value + // from the XFA field (NB: an XFA field with no value overrides the + // AcroForm value) + if (xfaField) { + if (xfaField->getValue()) { + u = utf8ToUnicode(xfaField->getValue(), length); + } + + // no XFA form - take the AcroForm value + } else { + fieldLookup("V", &obj1); + if (obj1.isName()) { + s = obj1.getName(); + n = (int)strlen(s); u = (Unicode *)gmallocn(n, sizeof(Unicode)); for (i = 0; i < n; ++i) { - u[i] = gs->getChar(i) & 0xff; + u[i] = s[i] & 0xff; } *length = n; + } else if (obj1.isString()) { + ts = new TextString(obj1.getString()); + n = ts->getLength(); + u = (Unicode *)gmallocn(n, sizeof(Unicode)); + memcpy(u, ts->getUnicode(), n * sizeof(Unicode)); + *length = n; + delete ts; + } else if (obj1.isDict()) { + obj1.dictLookup("Contents", &obj2); + if (obj2.isString()) { + gs = obj2.getString(); + n = gs->getLength(); + u = (Unicode *)gmallocn(n, sizeof(Unicode)); + for (i = 0; i < n; ++i) { + u[i] = gs->getChar(i) & 0xff; + } + *length = n; + } + obj2.free(); } - obj2.free(); + obj1.free(); } - obj1.free(); return u; } @@ -818,6 +1175,8 @@ void AcroFormField::drawAnnot(int pageNum, Gfx *gfx, GBool printing, render = gFalse; if (acroForm->needAppearances) { render = gTrue; + } else if (xfaField && xfaField->getValue()) { + render = gTrue; } else { if (!annotObj->dictLookup("AP", &obj1)->isDict()) { render = gTrue; @@ -889,9 +1248,17 @@ void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, double borderWidth; double *borderDash; GString *appearanceState; - int borderDashLength, rot, quadding, comb, nOptions, topIdx, i, j; + int borderDashLength, rot, quadding, vAlign, comb, nOptions, topIdx, i; appearBuf = new GString(); +#if 0 //~debug + appearBuf->appendf("1 1 0 rg 0 0 {0:.4f} {1:.4f} re f\n", + xMax - xMin, yMax - yMin); +#endif +#if 0 //~debug + appearBuf->appendf("1 1 0 RG 0 0 {0:.4f} {1:.4f} re s\n", + xMax - xMin, yMax - yMin); +#endif // get the appearance characteristics (MK) dictionary if (annot->lookup("MK", &mkObj)->isDict()) { @@ -1141,6 +1508,9 @@ void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, asObj.free(); apObj.free(); + int valueLength; + Unicode *value = getValue(&valueLength); + // draw the field contents if (ftObj.isName("Btn")) { caption = NULL; @@ -1153,12 +1523,13 @@ void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, // radio button if (flags & acroFormFlagRadio) { //~ Acrobat doesn't draw a caption if there is no AP dict (?) - if (fieldLookup("V", &obj1) - ->isName(appearanceState->getCString())) { + if (value && unicodeStringEqual(value, valueLength, + appearanceState->getCString())) { if (caption) { - drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter, - gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth, - appearBuf); + drawText(caption, da, fontDict, gFalse, 0, + acroFormQuadCenter, acroFormVAlignMiddleNoDescender, + gFalse, gTrue, rot, 0, 0, xMax - xMin, yMax - yMin, + borderWidth, gFalse, appearBuf); } else { if (mkDict) { if (mkDict->lookup("BC", &obj2)->isArray() && @@ -1173,55 +1544,112 @@ void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, } } } - obj1.free(); // pushbutton } else if (flags & acroFormFlagPushbutton) { if (caption) { - drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter, - gFalse, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth, - appearBuf); + drawText(caption, da, fontDict, gFalse, 0, + acroFormQuadCenter, acroFormVAlignMiddle, + gFalse, gFalse, rot, 0, 0, xMax - xMin, yMax - yMin, + borderWidth, gFalse, appearBuf); } // checkbox } else { - fieldLookup("V", &obj1); - if (obj1.isName() && !(obj1.isName("Off") || - obj1.isName("No") || - obj1.isName(""))) { + if (value && !(unicodeStringEqual(value, valueLength, "Off") || + unicodeStringEqual(value, valueLength, "No") || + unicodeStringEqual(value, valueLength, "0") || + valueLength == 0)) { if (!caption) { caption = new GString("3"); // ZapfDingbats checkmark } - drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter, - gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth, - appearBuf); + drawText(caption, da, fontDict, gFalse, 0, + acroFormQuadCenter, acroFormVAlignMiddleNoDescender, + gFalse, gTrue, rot, 0, 0, xMax - xMin, yMax - yMin, + borderWidth, gFalse, appearBuf); } - obj1.free(); } if (caption) { delete caption; } } else if (ftObj.isName("Tx")) { - //~ value strings can be Unicode - fieldLookup("V", &obj1); - if (obj1.isString()) { - if (fieldLookup("Q", &obj2)->isInt()) { - quadding = obj2.getInt(); + XFAFieldBarcodeInfo *barcodeInfo = xfaField ? xfaField->getBarcodeInfo() + : (XFAFieldBarcodeInfo *)NULL; + if (value) { + //~ value strings can be Unicode + GString *valueLatin1 = unicodeToLatin1(value, valueLength); + if (barcodeInfo) { + drawBarcode(valueLatin1, da, fontDict, rot, xMin, yMin, xMax, yMax, + barcodeInfo, appearBuf); } else { - quadding = acroFormQuadLeft; - } - obj2.free(); - comb = 0; - if (flags & acroFormFlagComb) { - if (fieldLookup("MaxLen", &obj2)->isInt()) { - comb = obj2.getInt(); + if (fieldLookup("Q", &obj2)->isInt()) { + quadding = obj2.getInt(); + } else { + quadding = acroFormQuadLeft; } obj2.free(); + vAlign = (flags & acroFormFlagMultiline) ? acroFormVAlignTop + : acroFormVAlignMiddle; + XFAFieldLayoutInfo *layoutInfo = xfaField ? xfaField->getLayoutInfo() + : (XFAFieldLayoutInfo *)NULL; + if (layoutInfo) { + switch (layoutInfo->hAlign) { + case xfaFieldLayoutHAlignLeft: + default: + quadding = acroFormQuadLeft; + break; + case xfaFieldLayoutHAlignCenter: + quadding = acroFormQuadCenter; + break; + case xfaFieldLayoutHAlignRight: + quadding = acroFormQuadRight; + break; + } + switch (layoutInfo->vAlign) { + case xfaFieldLayoutVAlignTop: + default: + vAlign = acroFormVAlignTop; + break; + case xfaFieldLayoutVAlignMiddle: + vAlign = acroFormVAlignMiddle; + break; + case xfaFieldLayoutVAlignBottom: + vAlign = acroFormVAlignBottom; + break; + } + } + comb = 0; + if (flags & acroFormFlagComb) { + if (fieldLookup("MaxLen", &obj2)->isInt()) { + comb = obj2.getInt(); + } + obj2.free(); + } + XFAFieldPictureInfo *pictureInfo = + xfaField ? xfaField->getPictureInfo() + : (XFAFieldPictureInfo *)NULL; + GString *value2 = valueLatin1; + if (pictureInfo) { + switch (pictureInfo->subtype) { + case xfaFieldPictureDateTime: + value2 = pictureFormatDateTime(valueLatin1, pictureInfo->format); + break; + case xfaFieldPictureNumeric: + value2 = pictureFormatNumber(valueLatin1, pictureInfo->format); + break; + case xfaFieldPictureText: + value2 = pictureFormatText(valueLatin1, pictureInfo->format); + break; + } + } + drawText(value2, da, fontDict, + flags & acroFormFlagMultiline, comb, quadding, vAlign, + gTrue, gFalse, rot, 0, 0, xMax - xMin, yMax - yMin, + borderWidth, gFalse, appearBuf); + if (value2 != valueLatin1) { + delete value2; + } } - drawText(obj1.getString(), da, fontDict, - flags & acroFormFlagMultiline, comb, quadding, - gTrue, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth, - appearBuf); + delete valueLatin1; } - obj1.free(); } else if (ftObj.isName("Ch")) { //~ value/option strings can be Unicode if (fieldLookup("Q", &obj1)->isInt()) { @@ -1230,10 +1658,39 @@ void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, quadding = acroFormQuadLeft; } obj1.free(); + vAlign = acroFormVAlignMiddle; + XFAFieldLayoutInfo *layoutInfo = xfaField ? xfaField->getLayoutInfo() + : (XFAFieldLayoutInfo *)NULL; + if (layoutInfo) { + switch (layoutInfo->hAlign) { + case xfaFieldLayoutHAlignLeft: + default: + quadding = acroFormQuadLeft; + break; + case xfaFieldLayoutHAlignCenter: + quadding = acroFormQuadCenter; + break; + case xfaFieldLayoutHAlignRight: + quadding = acroFormQuadRight; + break; + } + switch (layoutInfo->vAlign) { + case xfaFieldLayoutVAlignTop: + default: + vAlign = acroFormVAlignTop; + break; + case xfaFieldLayoutVAlignMiddle: + vAlign = acroFormVAlignMiddle; + break; + case xfaFieldLayoutVAlignBottom: + vAlign = acroFormVAlignBottom; + break; + } + } // combo box if (flags & acroFormFlagCombo) { - if (fieldLookup("V", &obj1)->isString()) { - val = obj1.getString()->copy(); + if (value) { + val = unicodeToLatin1(value, valueLength); if (fieldObj.dictLookup("Opt", &obj2)->isArray()) { for (i = 0, done = false; i < obj2.arrayGetLength() && !done; ++i) { obj2.arrayGet(i, &obj3); @@ -1254,12 +1711,12 @@ void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, } obj2.free(); drawText(val, da, fontDict, - gFalse, 0, quadding, gTrue, gFalse, rot, - xMin, yMin, xMax, yMax, borderWidth, appearBuf); + gFalse, 0, quadding, vAlign, gTrue, gFalse, rot, + 0, 0, xMax - xMin, yMax - yMin, borderWidth, + gFalse, appearBuf); delete val; //~ Acrobat draws a popup icon on the right side } - obj1.free(); // list box } else { if (fieldObj.dictLookup("Opt", &obj1)->isArray()) { @@ -1285,27 +1742,15 @@ void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, // get the selected option(s) selection = (GBool *)gmallocn(nOptions, sizeof(GBool)); //~ need to use the I field in addition to the V field - fieldLookup("V", &obj2); for (i = 0; i < nOptions; ++i) { - selection[i] = gFalse; - if (obj2.isString()) { - if (!obj2.getString()->cmp(text[i])) { - selection[i] = gTrue; - } - } else if (obj2.isArray()) { - for (j = 0; j < obj2.arrayGetLength(); ++j) { - if (obj2.arrayGet(j, &obj3)->isString() && - !obj3.getString()->cmp(text[i])) { - selection[i] = gTrue; - } - obj3.free(); - } - } + selection[i] = unicodeStringEqual(value, valueLength, text[i]); } - obj2.free(); // get the top index if (fieldObj.dictLookup("TI", &obj2)->isInt()) { topIdx = obj2.getInt(); + if (topIdx < 0 || topIdx >= nOptions) { + topIdx = 0; + } } else { topIdx = 0; } @@ -1334,14 +1779,16 @@ void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, delete da; } da = new GString("/Helv 10 Tf 1 0 0 rg"); - drawText(caption, da, fontDict, - gFalse, 0, acroFormQuadLeft, gFalse, gFalse, rot, - xMin, yMin, xMax, yMax, borderWidth, appearBuf); + drawText(caption, da, fontDict, gFalse, 0, + acroFormQuadLeft, acroFormVAlignMiddle, gFalse, gFalse, rot, + 0, 0, xMax - xMin, yMax - yMin, borderWidth, gFalse, appearBuf); delete caption; } else { error(errSyntaxError, -1, "Unknown field type"); } + gfree(value); + delete appearanceState; if (da) { delete da; @@ -1457,17 +1904,20 @@ void AcroFormField::setColor(Array *a, GBool fill, int adjust, // Draw the variable text or caption for a field. void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, - GBool multiline, int comb, int quadding, + GBool multiline, int comb, + int quadding, int vAlign, GBool txField, GBool forceZapfDingbats, int rot, - double xMin, double yMin, double xMax, double yMax, - double border, GString *appearBuf) { + double x, double y, double width, double height, + double border, GBool whiteBackground, + GString *appearBuf) { GString *text2; GList *daToks; GString *tok; GfxFont *font; double dx, dy; - double fontSize, fontSize2, x, xPrev, y, w, w2, wMax; - int tfPos, tmPos, i, j, k, c; + double fontSize, fontSize2, topBorder, xx, xPrev, yy, w, wMax; + double offset, offset2, charWidth, ascent, descent; + int tfPos, tmPos, nLines, i, j, k, c; //~ if there is no MK entry, this should use the existing content stream, //~ and only replace the marked content portion of it @@ -1501,7 +1951,7 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, if (da) { daToks = tokenize(da); for (i = 2; i < daToks->getLength(); ++i) { - if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { + if (!((GString *)daToks->get(i))->cmp("Tf")) { tfPos = i - 2; } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { tmPos = i - 6; @@ -1558,23 +2008,21 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, } appearBuf->append("q\n"); if (rot == 90) { - appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", xMax - xMin); - dx = yMax - yMin; - dy = xMax - xMin; + appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", width); + dx = height; + dy = width; } else if (rot == 180) { - appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n", - xMax - xMin, yMax - yMin); - dx = xMax - yMax; - dy = yMax - yMin; + appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n", width, height); + dx = width; + dy = height; } else if (rot == 270) { - appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin); - dx = yMax - yMin; - dy = xMax - xMin; + appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", height); + dx = height; + dy = width; } else { // assume rot == 0 - dx = xMax - xMin; - dy = yMax - yMin; + dx = width; + dy = height; } - appearBuf->append("BT\n"); // multi-line text if (multiline) { @@ -1582,22 +2030,29 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, wMax = dx - 2 * border - 4; +#if 1 //~tmp + // this is a kludge that appears to match Adobe's behavior + if (height > 15) { + topBorder = 5; + } else { + topBorder = 2; + } +#else + topBorder = 5; +#endif + // compute font autosize if (fontSize == 0) { for (fontSize = 10; fontSize > 1; --fontSize) { - y = dy - 3; - w2 = 0; + yy = dy - topBorder; i = 0; while (i < text2->getLength()) { getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); - if (w > w2) { - w2 = w; - } i = k; - y -= fontSize; + yy -= fontSize; } // approximate the descender for the last line - if (y >= 0.33 * fontSize && w <= wMax) { + if (yy >= 0.25 * fontSize && w <= wMax) { break; } } @@ -1609,22 +2064,54 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, } // starting y coordinate - // (note: each line of text starts with a Td operator that moves - // down a line) - if (dy > fontSize + 6) { - y = dy - 3; + nLines = 0; + i = 0; + while (i < text2->getLength()) { + getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); + i = k; + ++nLines; + } + if (font) { + ascent = font->getDeclaredAscent() * fontSize; + descent = font->getDescent() * fontSize; } else { - y = 0.5 * dy - 0.4 * fontSize + fontSize; + ascent = 0.75 * fontSize; + descent = -0.25 * fontSize; } + switch (vAlign) { + case acroFormVAlignTop: + default: + yy = dy - ascent - topBorder; + break; + case acroFormVAlignMiddle: + yy = 0.5 * (dy - nLines * fontSize) + (nLines - 1) * fontSize - descent; + break; + case acroFormVAlignMiddleNoDescender: + yy = 0.5 * (dy - nLines * fontSize) + (nLines - 1) * fontSize; + break; + case acroFormVAlignBottom: + yy = (nLines - 1) * fontSize - descent; + break; + } + // if the field is shorter than a line of text, Acrobat positions + // the text relative to the bottom edge + if (dy < fontSize + topBorder) { + yy = 2 - descent; + } + // each line of text starts with a Td operator that moves down a + // line -- so move up a line here + yy += fontSize; + + appearBuf->append("BT\n"); // set the font matrix if (tmPos >= 0) { tok = (GString *)daToks->get(tmPos + 4); tok->clear(); - tok->append('0'); + tok->appendf("{0:.4f}", x); tok = (GString *)daToks->get(tmPos + 5); tok->clear(); - tok->appendf("{0:.4f}", y); + tok->appendf("{0:.4f}", y + yy); } // write the DA string @@ -1636,7 +2123,7 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, // write the font matrix (if not part of the DA string) if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 0 {0:.4f} Tm\n", y); + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y + yy); } // write a series of lines of text @@ -1650,18 +2137,18 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, switch (quadding) { case acroFormQuadLeft: default: - x = border + 2; + xx = border + 2; break; case acroFormQuadCenter: - x = (dx - w) / 2; + xx = (dx - w) / 2; break; case acroFormQuadRight: - x = dx - border - 2 - w; + xx = dx - border - 2 - w; break; } // draw the line - appearBuf->appendf("{0:.4f} {1:.4f} Td\n", x - xPrev, -fontSize); + appearBuf->appendf("{0:.4f} {1:.4f} Td\n", xx - xPrev, -fontSize); appearBuf->append('('); for (; i < j; ++i) { c = text2->getChar(i) & 0xff; @@ -1678,9 +2165,11 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, // next line i = k; - xPrev = x; + xPrev = xx; } + appearBuf->append("ET\n"); + // single-line text } else { //~ replace newlines with spaces? - what does Acrobat do? @@ -1689,7 +2178,7 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, if (comb > 0) { // compute comb spacing - w = (dx - 2 * border) / comb; + w = dx / comb; // compute font autosize if (fontSize == 0) { @@ -1712,25 +2201,48 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, switch (quadding) { case acroFormQuadLeft: default: - x = border + 2; + xx = 0; break; case acroFormQuadCenter: - x = border + 2 + 0.5 * (comb - text2->getLength()) * w; + xx = ((comb - text2->getLength()) / 2) * w; break; case acroFormQuadRight: - x = border + 2 + (comb - text2->getLength()) * w; + xx = (comb - text2->getLength()) * w; + break; + } + if (font) { + ascent = font->getDeclaredAscent() * fontSize; + descent = font->getDescent() * fontSize; + } else { + ascent = 0.75 * fontSize; + descent = -0.25 * fontSize; + } + switch (vAlign) { + case acroFormVAlignTop: + default: + yy = dy - ascent; + break; + case acroFormVAlignMiddle: + yy = 0.5 * (dy - ascent - descent); + break; + case acroFormVAlignMiddleNoDescender: + yy = 0.5 * (dy - ascent); + break; + case acroFormVAlignBottom: + yy = -descent; break; } - y = 0.5 * dy - 0.4 * fontSize; + + appearBuf->append("BT\n"); // set the font matrix if (tmPos >= 0) { tok = (GString *)daToks->get(tmPos + 4); tok->clear(); - tok->appendf("{0:.4f}", x); + tok->appendf("{0:.4f}", x + xx); tok = (GString *)daToks->get(tmPos + 5); tok->clear(); - tok->appendf("{0:.4f}", y); + tok->appendf("{0:.4f}", y + yy); } // write the DA string @@ -1742,29 +2254,35 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, // write the font matrix (if not part of the DA string) if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y); + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x + xx, y + yy); } // write the text string - //~ this should center (instead of left-justify) each character within - //~ its comb cell + offset = 0; for (i = 0; i < text2->getLength(); ++i) { - if (i > 0) { - appearBuf->appendf("{0:.4f} 0 Td\n", w); - } - appearBuf->append('('); c = text2->getChar(i) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append((char)c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("{0:.4f} 0 Td\n", w); + if (c >= 0x20 && c < 0x80) { + if (font && !font->isCIDFont()) { + charWidth = ((Gfx8BitFont *)font)->getWidth((Guchar)c) * fontSize; + } else { + // otherwise, make a crude estimate + charWidth = 0.5 * fontSize; + } + offset2 = 0.5 * (w - charWidth); + appearBuf->appendf("{0:.4f} 0 Td\n", offset + offset2); + if (c == '(' || c == ')' || c == '\\') { + appearBuf->appendf("(\\{0:c}) Tj\n", c); + } else { + appearBuf->appendf("({0:c}) Tj\n", c); + } + offset = w - offset2; } else { - appearBuf->append((char)c); + offset += w; } - appearBuf->append(") Tj\n"); } + appearBuf->append("ET\n"); + // regular (non-comb) formatting } else { @@ -1802,25 +2320,54 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, switch (quadding) { case acroFormQuadLeft: default: - x = border + 2; + xx = border + 2; break; case acroFormQuadCenter: - x = (dx - w) / 2; + xx = (dx - w) / 2; break; case acroFormQuadRight: - x = dx - border - 2 - w; + xx = dx - border - 2 - w; break; } - y = 0.5 * dy - 0.4 * fontSize; + if (font) { + ascent = font->getDeclaredAscent() * fontSize; + descent = font->getDescent() * fontSize; + } else { + ascent = 0.75 * fontSize; + descent = -0.25 * fontSize; + } + switch (vAlign) { + case acroFormVAlignTop: + default: + yy = dy - ascent; + break; + case acroFormVAlignMiddle: + yy = 0.5 * (dy - ascent - descent); + break; + case acroFormVAlignMiddleNoDescender: + yy = 0.5 * (dy - ascent); + break; + case acroFormVAlignBottom: + yy = -descent; + break; + } + + if (whiteBackground) { + appearBuf->appendf("q 1 g {0:.4f} {1:.4f} {2:.4f} {3:.4f} re f Q\n", + xx - 0.25 * fontSize, yy - 0.35 * fontSize, + w + 0.5 * fontSize, 1.2 * fontSize); + } + + appearBuf->append("BT\n"); // set the font matrix if (tmPos >= 0) { tok = (GString *)daToks->get(tmPos + 4); tok->clear(); - tok->appendf("{0:.4f}", x); + tok->appendf("{0:.4f}", x + xx); tok = (GString *)daToks->get(tmPos + 5); tok->clear(); - tok->appendf("{0:.4f}", y); + tok->appendf("{0:.4f}", y + yy); } // write the DA string @@ -1832,7 +2379,7 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, // write the font matrix (if not part of the DA string) if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y); + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x + xx, y + yy); } // write the text string @@ -1850,10 +2397,11 @@ void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, } appearBuf->append(") Tj\n"); } + + appearBuf->append("ET\n"); } // cleanup - appearBuf->append("ET\n"); appearBuf->append("Q\n"); if (txField) { appearBuf->append("EMC\n"); @@ -2197,6 +2745,203 @@ void AcroFormField::drawCircleBottomRight(double cx, double cy, double r, appearBuf->append("S\n"); } +void AcroFormField::drawBarcode(GString *value, GString *da, + GfxFontDict *fontDict, int rot, + double xMin, double yMin, + double xMax, double yMax, + XFAFieldBarcodeInfo *barcodeInfo, + GString *appearBuf) { + //--- handle rotation + double w, h; + appearBuf->append("q\n"); + switch (rot) { + case 0: + default: + w = xMax - xMin; + h = yMax - yMin; + break; + case 90: + appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", xMax - xMin); + w = yMax - yMin; + h = xMax - xMin; + break; + case 180: + appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin); + w = yMax - yMin; + h = xMax - xMin; + break; + case 270: + appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin); + w = yMax - yMin; + h = xMax - xMin; + break; + } + + //--- get the font size + double fontSize = 0.2 * h; + if (da) { + GList *daToks = tokenize(da); + for (int i = 2; i < daToks->getLength(); ++i) { + if (!((GString *)daToks->get(i))->cmp("Tf")) { + fontSize = atof(((GString *)daToks->get(i - 1))->getCString()); + break; + } + } + deleteGList(daToks, GString); + } + + //--- compute the embedded text type position + GBool doText = gTrue; + double yText = 0; + int vAlign = acroFormVAlignTop; + double yBarcode = 0; + double hBarcode = 0; + GBool whiteBackground = gFalse; + //~ this uses an estimate of the font baseline position + if (barcodeInfo->textLocation && + !barcodeInfo->textLocation->cmp("above")) { + yText = h; + vAlign = acroFormVAlignTop; + yBarcode = 0; + hBarcode = h - fontSize; + } else if (barcodeInfo->textLocation && + !barcodeInfo->textLocation->cmp("belowEmbedded")) { + yText = 0; + vAlign = acroFormVAlignBottom; + yBarcode = 0; + hBarcode = h; + whiteBackground = gTrue; + } else if (barcodeInfo->textLocation && + !barcodeInfo->textLocation->cmp("aboveEmbedded")) { + yText = h; + vAlign = acroFormVAlignTop; + yBarcode = 0; + hBarcode = h; + whiteBackground = gTrue; + } else if (barcodeInfo->textLocation && + !barcodeInfo->textLocation->cmp("none")) { + doText = gFalse; + } else { // default is "below" + yText = 0; + vAlign = acroFormVAlignBottom; + yBarcode = fontSize; + hBarcode = h - fontSize; + } + double wText = w; + + //--- remove extraneous start/stop chars + GString *value2 = value->copy(); + if (!barcodeInfo->barcodeType->cmp("code3Of9")) { + if (value2->getLength() >= 1 && value2->getChar(0) == '*') { + value2->del(0); + } + if (value2->getLength() >= 1 && + value2->getChar(value2->getLength() - 1) == '*') { + value2->del(value2->getLength() - 1); + } + } + + //--- draw the bar code + if (!barcodeInfo->barcodeType->cmp("code3Of9")) { + if (!barcodeInfo->dataLength) { + error(errSyntaxError, -1, + "Missing 'dataLength' attribute in barcode field"); + goto err; + } + appearBuf->append("0 g\n"); + double wNarrow = w / ((7 + 3 * barcodeInfo->wideNarrowRatio) + * (barcodeInfo->dataLength + 2)); + double xx = 0; + for (int i = -1; i <= value2->getLength(); ++i) { + int c; + if (i < 0 || i >= value2->getLength()) { + c = '*'; + } else { + c = value2->getChar(i) & 0x7f; + } + for (int j = 0; j < 10; j += 2) { + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n", + xx, yBarcode, + (code3Of9Data[c][j] ? barcodeInfo->wideNarrowRatio + : 1) * wNarrow, + hBarcode); + xx += ((code3Of9Data[c][j] ? barcodeInfo->wideNarrowRatio : 1) + + (code3Of9Data[c][j+1] ? barcodeInfo->wideNarrowRatio : 1)) + * wNarrow; + } + } + // center the text on the drawn barcode (not the max length barcode) + wText = (value2->getLength() + 2) * (7 + 3 * barcodeInfo->wideNarrowRatio) + * wNarrow; + } else if (!barcodeInfo->barcodeType->cmp("code128B")) { + if (!barcodeInfo->dataLength) { + error(errSyntaxError, -1, + "Missing 'dataLength' attribute in barcode field"); + goto err; + } + appearBuf->append("0 g\n"); + double wNarrow = w / (11 * (barcodeInfo->dataLength + 3) + 2); + double xx = 0; + int checksum = 0; + for (int i = -1; i <= value2->getLength() + 1; ++i) { + int c; + if (i == -1) { + // start code B + c = 104; + checksum += c; + } else if (i == value2->getLength()) { + // checksum + c = checksum % 103; + } else if (i == value2->getLength() + 1) { + // stop code + c = 106; + } else { + c = value2->getChar(i) & 0xff; + if (c >= 32 && c <= 127) { + c -= 32; + } else { + c = 0; + } + checksum += (i + 1) * c; + } + for (int j = 0; j < 6; j += 2) { + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n", + xx, yBarcode, + code128Data[c][j] * wNarrow, hBarcode); + xx += (code128Data[c][j] + code128Data[c][j+1]) * wNarrow; + } + } + // final bar of the stop code + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n", + xx, yBarcode, 2 * wNarrow, hBarcode); + // center the text on the drawn barcode (not the max length barcode) + wText = (11 * (value2->getLength() + 3) + 2) * wNarrow; + } else if (!barcodeInfo->barcodeType->cmp("pdf417")) { + drawPDF417Barcode(w, h, barcodeInfo->moduleWidth, + barcodeInfo->moduleHeight, + barcodeInfo->errorCorrectionLevel, + value2, appearBuf); + doText = gFalse; + } else { + error(errSyntaxError, -1, + "Unimplemented barcode type '{0:t}' in barcode field", + barcodeInfo->barcodeType); + } + //~ add other barcode types here + + //--- draw the embedded text + if (doText) { + drawText(value2, da, fontDict, gFalse, 0, acroFormQuadCenter, + vAlign, gFalse, gFalse, 0, 0, yText, wText, yText + fontSize, + 0, whiteBackground, appearBuf); + } + + appearBuf->append("Q\n"); + + err: + delete value2; +} + GList *AcroFormField::tokenize(GString *s) { GList *toks; int i, j; @@ -2373,19 +3118,769 @@ Object *AcroFormField::fieldLookup(const char *key, Object *obj) { } Object *AcroFormField::fieldLookup(Dict *dict, const char *key, Object *obj) { - Object parent; + Object parent, parent2; + int depth; if (!dict->lookup(key, obj)->isNull()) { return obj; } obj->free(); - if (dict->lookup("Parent", &parent)->isDict()) { - fieldLookup(parent.getDict(), key, obj); - } else { - // some fields don't specify a parent, so we check the AcroForm - // dictionary just in case - acroForm->acroFormObj.dictLookup(key, obj); + + dict->lookup("Parent", &parent)->isDict(); + depth = 0; + while (parent.isDict() && depth < maxFieldObjectDepth) { + if (!parent.dictLookup(key, obj)->isNull()) { + parent.free(); + return obj; + } + obj->free(); + parent.dictLookup("Parent", &parent2); + parent.free(); + parent = parent2; + ++depth; } parent.free(); + + // some fields don't specify a parent, so we check the AcroForm + // dictionary just in case + acroForm->acroFormObj.dictLookup(key, obj); return obj; } + +Unicode *AcroFormField::utf8ToUnicode(GString *s, int *unicodeLength) { + int n = 0; + int i = 0; + Unicode u; + while (getUTF8(s, &i, &u)) { + ++n; + } + Unicode *uVec = (Unicode *)gmallocn(n, sizeof(Unicode)); + n = 0; + i = 0; + while (getUTF8(s, &i, &uVec[n])) { + ++n; + } + *unicodeLength = n; + return uVec; +} + +GString *AcroFormField::unicodeToLatin1(Unicode *u, int unicodeLength) { + GString *s = new GString(); + for (int i = 0; i < unicodeLength; ++i) { + if (u[i] <= 0xff) { + s->append((char)u[i]); + } + } + return s; +} + +GBool AcroFormField::unicodeStringEqual(Unicode *u, int unicodeLength, + GString *s) { + if (s->getLength() != unicodeLength) { + return gFalse; + } + for (int i = 0; i < unicodeLength; ++i) { + if ((s->getChar(i) & 0xff) != u[i]) { + return gFalse; + } + } + return gTrue; +} + +GBool AcroFormField::unicodeStringEqual(Unicode *u, int unicodeLength, + const char *s) { + for (int i = 0; i < unicodeLength; ++i) { + if (!s[i] || (s[i] & 0xff) != u[i]) { + return gFalse; + } + } + return gTrue; +} + +//------------------------------------------------------------------------ +// 'picture' formatting +//------------------------------------------------------------------------ + +class PictureNode { +public: + virtual ~PictureNode() {} + virtual GBool isLiteral() { return gFalse; } + virtual GBool isSign() { return gFalse; } + virtual GBool isDigit() { return gFalse; } + virtual GBool isDecPt() { return gFalse; } + virtual GBool isSeparator() { return gFalse; } + virtual GBool isYear() { return gFalse; } + virtual GBool isMonth() { return gFalse; } + virtual GBool isDay() { return gFalse; } + virtual GBool isHour() { return gFalse; } + virtual GBool isMinute() { return gFalse; } + virtual GBool isSecond() { return gFalse; } + virtual GBool isChar() { return gFalse; } +}; + +class PictureLiteral: public PictureNode { +public: + PictureLiteral(GString *sA) { s = sA; } + virtual ~PictureLiteral() { delete s; } + virtual GBool isLiteral() { return gTrue; } + GString *s; +}; + +class PictureSign: public PictureNode { +public: + PictureSign(char cA) { c = cA; } + virtual GBool isSign() { return gTrue; } + char c; +}; + +class PictureDigit: public PictureNode { +public: + PictureDigit(char cA) { c = cA; pos = 0; } + virtual GBool isDigit() { return gTrue; } + char c; + int pos; +}; + +class PictureDecPt: public PictureNode { +public: + PictureDecPt() { } + virtual GBool isDecPt() { return gTrue; } +}; + +class PictureSeparator: public PictureNode { +public: + PictureSeparator() { } + virtual GBool isSeparator() { return gTrue; } +}; + +class PictureYear: public PictureNode { +public: + PictureYear(int nDigitsA) { nDigits = nDigitsA; } + virtual GBool isYear() { return gTrue; } + int nDigits; +}; + +class PictureMonth: public PictureNode { +public: + PictureMonth(int nDigitsA) { nDigits = nDigitsA; } + virtual GBool isMonth() { return gTrue; } + int nDigits; +}; + +class PictureDay: public PictureNode { +public: + PictureDay(int nDigitsA) { nDigits = nDigitsA; } + virtual GBool isDay() { return gTrue; } + int nDigits; +}; + +class PictureHour: public PictureNode { +public: + PictureHour(GBool is24HourA, int nDigitsA) + { is24Hour = is24HourA; nDigits = nDigitsA; } + virtual GBool isHour() { return gTrue; } + GBool is24Hour; + int nDigits; +}; + +class PictureMinute: public PictureNode { +public: + PictureMinute(int nDigitsA) { nDigits = nDigitsA; } + virtual GBool isMinute() { return gTrue; } + int nDigits; +}; + +class PictureSecond: public PictureNode { +public: + PictureSecond(int nDigitsA) { nDigits = nDigitsA; } + virtual GBool isSecond() { return gTrue; } + int nDigits; +}; + +class PictureChar: public PictureNode { +public: + PictureChar() {} + virtual GBool isChar() { return gTrue; } +}; + +GString *AcroFormField::pictureFormatDateTime(GString *value, + GString *picture) { + GList *pic; + PictureNode *node; + GString *ret, *s; + char c; + int year, month, day, hour, min, sec; + int len, picStart, picEnd, u, n, i, j; + + len = value->getLength(); + if (len == 0) { + return value->copy(); + } + + //--- parse the value + + // expected format is yyyy(-mm(-dd)?)?Thh(:mm(:ss)?)? + // where: + // - the '-'s and ':'s are optional + // - the 'T' is literal + // - we're ignoring optional time zone info at the end + // (if the value is not in this canonical format, we just punt and + // return the value string) + //~ another option would be to parse the value following the + //~ element + year = month = day = hour = min = sec = 0; + i = 0; + if (!(i + 4 <= len && isValidInt(value, i, 4))) { + return value->copy(); + } + year = convertInt(value, i, 4); + i += 4; + if (i < len && value->getChar(i) == '-') { + ++i; + } + if (i + 2 <= len && isValidInt(value, i, 2)) { + month = convertInt(value, i, 2); + i += 2; + if (i < len && value->getChar(i) == '-') { + ++i; + } + if (i + 2 <= len && isValidInt(value, i, 2)) { + day = convertInt(value, i, 2); + i += 2; + } + } + if (i < len) { + if (value->getChar(i) != 'T') { + return value->copy(); + } + ++i; + if (!(i + 2 <= len && isValidInt(value, i, 2))) { + return value->copy(); + } + hour = convertInt(value, i, 2); + i += 2; + if (i < len && value->getChar(i) == ':') { + ++i; + } + if (i + 2 <= len && isValidInt(value, i, 2)) { + min = convertInt(value, i, 2); + i += 2; + if (i < len && value->getChar(i) == ':') { + ++i; + } + if (i + 2 <= len && isValidInt(value, i, 2)) { + sec = convertInt(value, i, 2); + i += 2; + } + } + } + if (i < len) { + return value->copy(); + } + + //--- skip the category and locale in the picture + + picStart = 0; + picEnd = picture->getLength(); + for (i = 0; i < picture->getLength(); ++i) { + c = picture->getChar(i); + if (c == '{') { + picStart = i + 1; + for (picEnd = picStart; + picEnd < picture->getLength() && picture->getChar(picEnd) != '}'; + ++picEnd) ; + break; + } else if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '(' || + c == ')')) { + break; + } + } + + //--- parse the picture + + pic = new GList(); + i = picStart; + while (i < picEnd) { + c = picture->getChar(i); + ++i; + if (c == '\'') { + s = new GString(); + while (i < picEnd) { + c = picture->getChar(i); + if (c == '\'') { + ++i; + if (i < picEnd && picture->getChar(i) == '\'') { + s->append('\''); + ++i; + } else { + break; + } + } else if (c == '\\') { + ++i; + if (i == picEnd) { + break; + } + c = picture->getChar(i); + ++i; + if (c == 'u' && i+4 <= picEnd) { + u = 0; + for (j = 0; j < 4; ++j, ++i) { + c = picture->getChar(i); + u <<= 4; + if (c >= '0' && c <= '9') { + u += c - '0'; + } else if (c >= 'a' && c <= 'f') { + u += c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + u += c - 'A' + 10; + } + } + //~ this should convert to UTF-8 (?) + if (u <= 0xff) { + s->append((char)u); + } + } else { + s->append(c); + } + } else { + s->append(c); + } + } + pic->append(new PictureLiteral(s)); + } else if (c == ',' || c == '-' || c == ':' || + c == '/' || c == '.' || c == ' ') { + s = new GString(); + s->append(c); + pic->append(new PictureLiteral(s)); + } else if (c == 'Y') { + for (n = 1; n < 4 && i < picEnd && picture->getChar(i) == 'Y'; ++n, ++i) ; + pic->append(new PictureYear(n)); + } else if (c == 'M') { + for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ; + pic->append(new PictureMonth(n)); + } else if (c == 'D') { + for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'D'; ++n, ++i) ; + pic->append(new PictureDay(n)); + } else if (c == 'h') { + for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'h'; ++n, ++i) ; + pic->append(new PictureHour(gFalse, n)); + } else if (c == 'H') { + for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'H'; ++n, ++i) ; + pic->append(new PictureHour(gTrue, n)); + } else if (c == 'M') { + for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ; + pic->append(new PictureMinute(n)); + } else if (c == 'S') { + for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'S'; ++n, ++i) ; + pic->append(new PictureSecond(n)); + } + } + + //--- generate formatted text + + ret = new GString(); + for (i = 0; i < pic->getLength(); ++i) { + node = (PictureNode *)pic->get(i); + if (node->isLiteral()) { + ret->append(((PictureLiteral *)node)->s); + } else if (node->isYear()) { + if (((PictureYear *)node)->nDigits == 2) { + if (year >= 1930 && year < 2030) { + ret->appendf("{0:02d}", year % 100); + } else { + ret->append("??"); + } + } else { + ret->appendf("{0:04d}", year); + } + } else if (node->isMonth()) { + if (((PictureMonth *)node)->nDigits == 1) { + ret->appendf("{0:d}", month); + } else { + ret->appendf("{0:02d}", month); + } + } else if (node->isDay()) { + if (((PictureDay *)node)->nDigits == 1) { + ret->appendf("{0:d}", day); + } else { + ret->appendf("{0:02d}", day); + } + } else if (node->isHour()) { + if (((PictureHour *)node)->is24Hour) { + n = hour; + } else { + n = hour % 12; + if (n == 0) { + n = 12; + } + } + if (((PictureHour *)node)->nDigits == 1) { + ret->appendf("{0:d}", n); + } else { + ret->appendf("{0:02d}", n); + } + } else if (node->isMinute()) { + if (((PictureMinute *)node)->nDigits == 1) { + ret->appendf("{0:d}", min); + } else { + ret->appendf("{0:02d}", min); + } + } else if (node->isSecond()) { + if (((PictureSecond *)node)->nDigits == 1) { + ret->appendf("{0:d}", sec); + } else { + ret->appendf("{0:02d}", sec); + } + } + } + deleteGList(pic, PictureNode); + + return ret; +} + +GString *AcroFormField::pictureFormatNumber(GString *value, GString *picture) { + GList *pic; + PictureNode *node; + GString *ret, *s; + GBool neg, haveDigits; + char c; + int start, decPt, trailingZero, len; + int picStart, picEnd, u, pos, i, j; + + len = value->getLength(); + if (len == 0) { + return value->copy(); + } + + //--- parse the value + + // -nnnn.nnnn0000 + // ^ ^ ^ ^ + // | | | +-- len + // | | +------ trailingZero + // | +----------- decPt + // +--------------- start + start = 0; + neg = gFalse; + if (value->getChar(start) == '-') { + neg = gTrue; + ++start; + } else if (value->getChar(start) == '+') { + ++start; + } + for (decPt = start; decPt < len && value->getChar(decPt) != '.'; ++decPt) ; + for (trailingZero = len; + trailingZero > decPt && value->getChar(trailingZero - 1) == '0'; + --trailingZero) ; + + //--- skip the category and locale in the picture + + picStart = 0; + picEnd = picture->getLength(); + for (i = 0; i < picture->getLength(); ++i) { + c = picture->getChar(i); + if (c == '{') { + picStart = i + 1; + for (picEnd = picStart; + picEnd < picture->getLength() && picture->getChar(picEnd) != '}'; + ++picEnd) ; + break; + } else if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '(' || + c == ')')) { + break; + } + } + + //--- parse the picture + + pic = new GList(); + i = picStart; + while (i < picEnd) { + c = picture->getChar(i); + ++i; + if (c == '\'') { + s = new GString(); + while (i < picEnd) { + c = picture->getChar(i); + if (c == '\'') { + ++i; + if (i < picEnd && picture->getChar(i) == '\'') { + s->append('\''); + ++i; + } else { + break; + } + } else if (c == '\\') { + ++i; + if (i == picEnd) { + break; + } + c = picture->getChar(i); + ++i; + if (c == 'u' && i+4 <= picEnd) { + u = 0; + for (j = 0; j < 4; ++j, ++i) { + c = picture->getChar(i); + u <<= 4; + if (c >= '0' && c <= '9') { + u += c - '0'; + } else if (c >= 'a' && c <= 'F') { + u += c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + u += c - 'A' + 10; + } + } + //~ this should convert to UTF-8 (?) + if (u <= 0xff) { + s->append((char)u); + } + } else { + s->append(c); + } + } else { + s->append(c); + ++i; + } + } + pic->append(new PictureLiteral(s)); + } else if (c == '-' || c == ':' || c == '/' || c == ' ') { + s = new GString(); + s->append(c); + pic->append(new PictureLiteral(s)); + } else if (c == 's' || c == 'S') { + pic->append(new PictureSign(c)); + } else if (c == 'Z' || c == 'z' || c == '8' || c == '9') { + pic->append(new PictureDigit(c)); + } else if (c == '.') { + pic->append(new PictureDecPt()); + } else if (c == ',') { + pic->append(new PictureSeparator()); + } + } + for (i = 0; i < pic->getLength(); ++i) { + node = (PictureNode *)pic->get(i); + if (node->isDecPt()) { + break; + } + } + pos = 0; + for (j = i - 1; j >= 0; --j) { + node = (PictureNode *)pic->get(j); + if (node->isDigit()) { + ((PictureDigit *)node)->pos = pos; + ++pos; + } + } + pos = -1; + for (j = i + 1; j < pic->getLength(); ++j) { + node = (PictureNode *)pic->get(j); + if (node->isDigit()) { + ((PictureDigit *)node)->pos = pos; + --pos; + } + } + + //--- generate formatted text + + ret = new GString(); + haveDigits = gFalse; + for (i = 0; i < pic->getLength(); ++i) { + node = (PictureNode *)pic->get(i); + if (node->isLiteral()) { + ret->append(((PictureLiteral *)node)->s); + } else if (node->isSign()) { + if (((PictureSign *)node)->c == 'S') { + ret->append(neg ? '-' : ' '); + } else { + if (neg) { + ret->append('-'); + } + } + } else if (node->isDigit()) { + pos = ((PictureDigit *)node)->pos; + c = ((PictureDigit *)node)->c; + if (pos >= 0 && pos < decPt - start) { + ret->append(value->getChar(decPt - 1 - pos)); + haveDigits = gTrue; + } else if (pos < 0 && -pos <= trailingZero - decPt - 1) { + ret->append(value->getChar(decPt - pos)); + haveDigits = gTrue; + } else if (c == '8' && + pos < 0 && + -pos <= len - decPt - 1) { + ret->append('0'); + haveDigits = gTrue; + } else if (c == '9') { + ret->append('0'); + haveDigits = gTrue; + } else if (c == 'Z' && pos >= 0) { + ret->append(' '); + } + } else if (node->isDecPt()) { + if (!(i+1 < pic->getLength() && + ((PictureNode *)pic->get(i+1))->isDigit() && + ((PictureDigit *)pic->get(i+1))->c == 'z') || + trailingZero > decPt + 1) { + ret->append('.'); + } + } else if (node->isSeparator()) { + if (haveDigits) { + ret->append(','); + } + } + } + deleteGList(pic, PictureNode); + + return ret; +} + +GString *AcroFormField::pictureFormatText(GString *value, GString *picture) { + GList *pic; + PictureNode *node; + GString *ret, *s; + char c; + int len, picStart, picEnd, u, i, j; + + len = value->getLength(); + if (len == 0) { + return value->copy(); + } + + //--- skip the category and locale in the picture + + picStart = 0; + picEnd = picture->getLength(); + for (i = 0; i < picture->getLength(); ++i) { + c = picture->getChar(i); + if (c == '{') { + picStart = i + 1; + for (picEnd = picStart; + picEnd < picture->getLength() && picture->getChar(picEnd) != '}'; + ++picEnd) ; + break; + } else if (!((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + c == '(' || + c == ')')) { + break; + } + } + + //--- parse the picture + + pic = new GList(); + i = picStart; + while (i < picEnd) { + c = picture->getChar(i); + ++i; + if (c == '\'') { + s = new GString(); + while (i < picEnd) { + c = picture->getChar(i); + if (c == '\'') { + ++i; + if (i < picEnd && picture->getChar(i) == '\'') { + s->append('\''); + ++i; + } else { + break; + } + } else if (c == '\\') { + ++i; + if (i == picEnd) { + break; + } + c = picture->getChar(i); + ++i; + if (c == 'u' && i+4 <= picEnd) { + u = 0; + for (j = 0; j < 4; ++j, ++i) { + c = picture->getChar(i); + u <<= 4; + if (c >= '0' && c <= '9') { + u += c - '0'; + } else if (c >= 'a' && c <= 'F') { + u += c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + u += c - 'A' + 10; + } + } + //~ this should convert to UTF-8 (?) + if (u <= 0xff) { + s->append((char)u); + } + } else { + s->append(c); + } + } else { + s->append(c); + ++i; + } + } + pic->append(new PictureLiteral(s)); + } else if (c == ',' || c == '-' || c == ':' || + c == '/' || c == '.' || c == ' ') { + s = new GString(); + s->append(c); + pic->append(new PictureLiteral(s)); + } else if (c == 'A' || c == 'X' || c == 'O' || c == '0' || c == '9') { + pic->append(new PictureChar()); + } + } + + //--- generate formatted text + + ret = new GString(); + j = 0; + for (i = 0; i < pic->getLength(); ++i) { + node = (PictureNode *)pic->get(i); + if (node->isLiteral()) { + ret->append(((PictureLiteral *)node)->s); + } else if (node->isChar()) { + // if there are more chars in the picture than in the value, + // Adobe renders the value as-is, without picture formatting + if (j >= value->getLength()) { + delete ret; + ret = value->copy(); + break; + } + ret->append(value->getChar(j)); + ++j; + } + } + deleteGList(pic, PictureNode); + + return ret; +} + +GBool AcroFormField::isValidInt(GString *s, int start, int len) { + int i; + + for (i = 0; i < len; ++i) { + if (!(start + i < s->getLength() && + s->getChar(start + i) >= '0' && + s->getChar(start + i) <= '9')) { + return gFalse; + } + } + return gTrue; +} + +int AcroFormField::convertInt(GString *s, int start, int len) { + char c; + int x, i; + + x = 0; + for (i = 0; i < len && start + i < s->getLength(); ++i) { + c = s->getChar(start + i); + if (c < '0' || c > '9') { + break; + } + x = x * 10 + (c - '0'); + } + return x; +} diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.h b/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.h index a92979a3e2f..7678d46dbc8 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.h @@ -15,27 +15,32 @@ #pragma interface #endif -#include "Form.h" - class TextString; +class Gfx; class GfxFont; class GfxFontDict; +class AcroFormField; +class XFAScanner; +class XFAField; +class XFAFieldBarcodeInfo; //------------------------------------------------------------------------ -class AcroForm: public Form { +class AcroForm { public: static AcroForm *load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA); - virtual ~AcroForm(); + ~AcroForm(); - virtual const char *getType() { return "AcroForm"; } + const char *getType(); - virtual void draw(int pageNum, Gfx *gfx, GBool printing); + void draw(int pageNum, Gfx *gfx, GBool printing); - virtual int getNumFields(); - virtual FormField *getField(int idx); + int getNumFields(); + AcroFormField *getField(int idx); + AcroFormField *findField(int pg, double x, double y); + int findFieldIdx(int pg, double x, double y); private: @@ -44,10 +49,13 @@ private: int lookupAnnotPage(Object *annotRef); void scanField(Object *fieldRef); + PDFDoc *doc; Object acroFormObj; GBool needAppearances; GList *annotPages; // [AcroFormAnnotPage] GList *fields; // [AcroFormField] + XFAScanner *xfaScanner; + GBool isStaticXFA; friend class AcroFormField; }; @@ -61,28 +69,29 @@ enum AcroFormFieldType { acroFormFieldFileSelect, acroFormFieldMultilineText, acroFormFieldText, + acroFormFieldBarcode, acroFormFieldComboBox, acroFormFieldListBox, acroFormFieldSignature }; -class AcroFormField: public FormField { +class AcroFormField { public: static AcroFormField *load(AcroForm *acroFormA, Object *fieldRefA); - virtual ~AcroFormField(); + ~AcroFormField(); - virtual int getPageNum(); - virtual const char *getType(); - virtual Unicode *getName(int *length); - virtual Unicode *getValue(int *length); - virtual void getBBox(double *llx, double *lly, double *urx, double *ury); - virtual void getFont(Ref *fontID, double *fontSize); - virtual void getColor(double *red, double *green, double *blue); + int getPageNum(); + const char *getType(); + Unicode *getName(int *length); + Unicode *getValue(int *length); + void getBBox(double *llx, double *lly, double *urx, double *ury); + void getFont(Ref *fontID, double *fontSize); + void getColor(double *red, double *green, double *blue); int getMaxLen(); - virtual Object *getResources(Object *res); + Object *getResources(Object *res); AcroFormFieldType getAcroFormFieldType() { return type; } Object *getFieldRef(Object *ref); @@ -94,7 +103,7 @@ private: AcroFormField(AcroForm *acroFormA, Object *fieldRefA, Object *fieldObjA, AcroFormFieldType typeA, TextString *nameA, - Guint flagsA, GBool typeFromParentA); + Guint flagsA, GBool typeFromParentA, XFAField *xfaFieldA); Ref findFontName(char *fontTag); void draw(int pageNum, Gfx *gfx, GBool printing); void drawAnnot(int pageNum, Gfx *gfx, GBool printing, @@ -107,10 +116,10 @@ private: double xMax, double yMax); void setColor(Array *a, GBool fill, int adjust, GString *appearBuf); void drawText(GString *text, GString *da, GfxFontDict *fontDict, - GBool multiline, int comb, int quadding, + GBool multiline, int comb, int quadding, int vAlign, GBool txField, GBool forceZapfDingbats, int rot, - double xMin, double yMin, double xMax, double yMax, - double border, GString *appearBuf); + double x, double y, double width, double height, + double border, GBool whiteBackground, GString *appearBuf); void drawListBox(GString **text, GBool *selection, int nOptions, int topIdx, GString *da, GfxFontDict *fontDict, @@ -126,12 +135,24 @@ private: GString *appearBuf); void drawCircleBottomRight(double cx, double cy, double r, GString *appearBuf); + void drawBarcode(GString *value, GString *da, GfxFontDict *fontDict, int rot, + double xMin, double yMin, double xMax, double yMax, + XFAFieldBarcodeInfo *barcodeInfo, GString *appearBuf); GList *tokenize(GString *s); Object *getAnnotObj(Object *annotObj); Object *getAnnotResources(Dict *annot, Object *res); void buildDefaultResourceDict(Object *dr); Object *fieldLookup(const char *key, Object *obj); Object *fieldLookup(Dict *dict, const char *key, Object *obj); + Unicode *utf8ToUnicode(GString *s, int *unicodeLength); + GString *unicodeToLatin1(Unicode *u, int unicodeLength); + GBool unicodeStringEqual(Unicode *u, int unicodeLength, GString *s); + GBool unicodeStringEqual(Unicode *u, int unicodeLength, const char *s); + GString *pictureFormatDateTime(GString *value, GString *picture); + GString *pictureFormatNumber(GString *value, GString *picture); + GString *pictureFormatText(GString *value, GString *picture); + GBool isValidInt(GString *s, int start, int len); + int convertInt(GString *s, int start, int len); AcroForm *acroForm; Object fieldRef; @@ -140,6 +161,7 @@ private: TextString *name; Guint flags; GBool typeFromParent; + XFAField *xfaField; friend class AcroForm; }; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Annot.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/Annot.cc index 61a1d434abf..d7229a2fe86 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Annot.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Annot.cc @@ -25,7 +25,7 @@ #include "Lexer.h" #include "PDFDoc.h" #include "OptionalContent.h" -#include "Form.h" +#include "AcroForm.h" #include "BuiltinFontTables.h" #include "FontEncodingTables.h" #include "Annot.h" diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/CMakeLists.txt b/Build/source/libs/xpdf/xpdf-src/xpdf/CMakeLists.txt index 4c88d1d351f..7b515d1d036 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/CMakeLists.txt +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/CMakeLists.txt @@ -38,7 +38,6 @@ add_library(xpdf_objs OBJECT Dict.cc Error.cc FontEncodingTables.cc - Form.cc Function.cc Gfx.cc GfxFont.cc @@ -67,7 +66,7 @@ add_library(xpdf_objs OBJECT UnicodeRemapping.cc UnicodeTypeTable.cc UTF8.cc - XFAForm.cc + XFAScanner.cc XRef.cc Zoox.cc ) @@ -88,6 +87,7 @@ if ((QT4_FOUND OR Qt5Widgets_FOUND) PDFCore.cc PreScanOutputDev.cc PSOutputDev.cc + ShadingImage.cc SplashOutputDev.cc TextOutputDev.cc TileCache.cc @@ -104,6 +104,7 @@ if (HAVE_SPLASH) $ PreScanOutputDev.cc PSOutputDev.cc + ShadingImage.cc SplashOutputDev.cc pdftops.cc ) @@ -151,6 +152,7 @@ if (HAVE_SPLASH AND PNG_FOUND) add_executable(pdftohtml $ HTMLGen.cc + ShadingImage.cc SplashOutputDev.cc TextOutputDev.cc WebFont.cc @@ -215,6 +217,7 @@ install(FILES ${PROJECT_SOURCE_DIR}/doc/pdfdetach.1 DESTINATION ${CMAKE_INSTALL_ if (HAVE_SPLASH) add_executable(pdftoppm $ + ShadingImage.cc SplashOutputDev.cc pdftoppm.cc ) @@ -234,6 +237,7 @@ endif () if (HAVE_SPLASH AND PNG_FOUND) add_executable(pdftopng $ + ShadingImage.cc SplashOutputDev.cc pdftopng.cc ) diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.cc index bec30a8dcfd..6ea8fa89ff4 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.cc @@ -28,7 +28,7 @@ #include "Page.h" #include "Error.h" #include "Link.h" -#include "Form.h" +#include "AcroForm.h" #include "TextString.h" #include "Catalog.h" @@ -159,6 +159,7 @@ Catalog::Catalog(PDFDoc *docA) { baseURI = NULL; form = NULL; embeddedFiles = NULL; + pageLabels = NULL; #if MULTITHREADED gInitMutex(&pageMutex); #endif @@ -222,7 +223,7 @@ Catalog::Catalog(PDFDoc *docA) { catDict.dictLookup("AcroForm", &acroForm); // get the NeedsRendering flag - // NB: Form::load() uses this value + // NB: AcroForm::load() uses this value needsRendering = catDict.dictLookup("NeedsRendering", &obj)->isBool() && obj.getBool(); obj.free(); @@ -230,7 +231,7 @@ Catalog::Catalog(PDFDoc *docA) { // create the Form // (if acroForm is a null object, this will still create an AcroForm // if there are unattached Widget-type annots) - form = Form::load(doc, this, &acroForm); + form = AcroForm::load(doc, this, &acroForm); // get the OCProperties dictionary catDict.dictLookup("OCProperties", &ocProperties); @@ -241,7 +242,6 @@ Catalog::Catalog(PDFDoc *docA) { // get the ViewerPreferences object catDict.dictLookupNF("ViewerPreferences", &viewerPrefs); - pageLabels = NULL; if (catDict.dictLookup("PageLabels", &obj)->isDict()) { readPageLabelTree(&obj); } @@ -1190,7 +1190,7 @@ GBool Catalog::convertPageLabelToInt(TextString *pageLabel, int prefixLength, return gFalse; } } - *n = (len - prefixLength - 1) * 26 + (u[i] - (Unicode)style) + 1; + *n = (len - prefixLength - 1) * 26 + (u[prefixLength] - (Unicode)style) + 1; return gTrue; } return gFalse; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.h b/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.h index b775ae9b9b8..0acdd2d1574 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.h @@ -30,7 +30,7 @@ struct Ref; class LinkDest; class PageTreeNode; class PageLabelNode; -class Form; +class AcroForm; class TextString; //------------------------------------------------------------------------ @@ -88,7 +88,7 @@ public: Object *getAcroForm() { return &acroForm; } - Form *getForm() { return form; } + AcroForm *getForm() { return form; } GBool getNeedsRendering() { return needsRendering; } @@ -137,7 +137,7 @@ private: Object outline; // outline dictionary Object acroForm; // AcroForm dictionary GBool needsRendering; // NeedsRendering flag - Form *form; // parsed form + AcroForm *form; // parsed form Object ocProperties; // OCProperties dictionary GList *embeddedFiles; // embedded file list [EmbeddedFile] GList *pageLabels; // page labels [PageLabelNode] diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.cc index 0d064cff379..5eec7a9fc6c 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.cc @@ -470,14 +470,24 @@ GBool CharCodeToUnicode::parseCMap1(int (*getCharFunc)(void *), void *data, void CharCodeToUnicode::addMapping(CharCode code, char *uStr, int n, int offset) { CharCode oldLen, i; +#if 1 //~utf16 + Unicode u[maxUnicodeString]; + int uLen, j; +#else Unicode u; int j; +#endif if (code > 0xffffff) { // This is an arbitrary limit to avoid integer overflow issues. // (I've seen CMaps with mappings for .) return; } +#if 1 //~utf16 + if ((uLen = parseUTF16String(uStr, n, u)) == 0) { + return; + } +#endif if (code >= mapLen) { oldLen = mapLen; mapLen = mapLen ? 2 * mapLen : 256; @@ -489,6 +499,25 @@ void CharCodeToUnicode::addMapping(CharCode code, char *uStr, int n, map[i] = 0; } } +#if 1 //~utf16 + if (uLen == 1) { + map[code] = u[0] + offset; + } else { + if (sMapLen >= sMapSize) { + sMapSize = sMapSize + 16; + sMap = (CharCodeToUnicodeString *) + greallocn(sMap, sMapSize, sizeof(CharCodeToUnicodeString)); + } + map[code] = 0; + sMap[sMapLen].c = code; + for (j = 0; j < uLen; ++j) { + sMap[sMapLen].u[j] = u[j]; + } + sMap[sMapLen].u[uLen - 1] += offset; + sMap[sMapLen].len = uLen; + ++sMapLen; + } +#else //~utf16 if (n <= 4) { if (!parseHex(uStr, n, &u)) { error(errSyntaxWarning, -1, "Illegal entry in ToUnicode CMap"); @@ -515,6 +544,36 @@ void CharCodeToUnicode::addMapping(CharCode code, char *uStr, int n, sMap[sMapLen].u[sMap[sMapLen].len - 1] += offset; ++sMapLen; } +#endif //~utf16 +} + +// Convert a UTF-16BE hex string into a sequence of up to +// maxUnicodeString Unicode chars. +int CharCodeToUnicode::parseUTF16String(char *uStr, int n, Unicode *uOut) { + int i = 0; + int uLen = 0; + while (i < n) { + Unicode u; + int j = n; + if (j - i > 4) { + j = i + 4; + } + if (!parseHex(uStr + i, j - i, &u)) { + error(errSyntaxWarning, -1, "Illegal entry in ToUnicode CMap"); + return 0; + } + // look for a UTF-16 pair + if (uLen > 0 && uOut[uLen-1] >= 0xd800 && uOut[uLen-1] <= 0xdbff && + u >= 0xdc00 && u <= 0xdfff) { + uOut[uLen-1] = 0x10000 + ((uOut[uLen-1] & 0x03ff) << 10) + (u & 0x03ff); + } else { + if (uLen < maxUnicodeString) { + uOut[uLen++] = u; + } + } + i = j; + } + return uLen; } void CharCodeToUnicode::addMappingInt(CharCode code, Unicode u) { diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.h b/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.h index 5d7bea5b057..c390a8c5c50 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.h @@ -80,6 +80,7 @@ private: GBool parseCMap1(int (*getCharFunc)(void *), void *data, int nBits); void addMapping(CharCode code, char *uStr, int n, int offset); + int parseUTF16String(char *uStr, int n, Unicode *uOut); void addMappingInt(CharCode code, Unicode u); CharCodeToUnicode(); CharCodeToUnicode(GString *tagA); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Form.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/Form.cc deleted file mode 100644 index 681401ad5de..00000000000 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Form.cc +++ /dev/null @@ -1,99 +0,0 @@ -//======================================================================== -// -// Form.cc -// -// Copyright 2012 Glyph & Cog, LLC -// -//======================================================================== - -#include - -#ifdef USE_GCC_PRAGMAS -#pragma implementation -#endif - -#include "gmempp.h" -#include "GlobalParams.h" -#include "Error.h" -#include "Object.h" -#include "PDFDoc.h" -#include "AcroForm.h" -#include "XFAForm.h" -#include "Form.h" - -//------------------------------------------------------------------------ -// Form -//------------------------------------------------------------------------ - -Form *Form::load(PDFDoc *docA, Catalog *catalog, Object *acroFormObj) { - Form *form; - Object xfaObj; - - if (acroFormObj->isDict()) { - //~ temporary: create an XFAForm only for XFAF, not for dynamic XFA - acroFormObj->dictLookup("XFA", &xfaObj); - if (globalParams->getEnableXFA() && - !xfaObj.isNull() && - !catalog->getNeedsRendering()) { - form = XFAForm::load(docA, catalog, acroFormObj, &xfaObj); - xfaObj.free(); - return form; - } - xfaObj.free(); - } - - // if acroFormObj is a null object, this will still create an - // AcroForm if there are unattached Widget-type annots - return AcroForm::load(docA, catalog, acroFormObj); -} - -Form::Form(PDFDoc *docA) { - doc = docA; -} - -Form::~Form() { -} - -FormField *Form::findField(int pg, double x, double y) { - FormField *field; - double llx, lly, urx, ury; - int i; - - for (i = 0; i < getNumFields(); ++i) { - field = getField(i); - if (field->getPageNum() == pg) { - field->getBBox(&llx, &lly, &urx, &ury); - if (llx <= x && x <= urx && lly <= y && y <= ury) { - return field; - } - } - } - return NULL; -} - -int Form::findFieldIdx(int pg, double x, double y) { - FormField *field; - double llx, lly, urx, ury; - int i; - - for (i = 0; i < getNumFields(); ++i) { - field = getField(i); - if (field->getPageNum() == pg) { - field->getBBox(&llx, &lly, &urx, &ury); - if (llx <= x && x <= urx && lly <= y && y <= ury) { - return i; - } - } - } - return -1; -} - -//------------------------------------------------------------------------ -// FormField -//------------------------------------------------------------------------ - -FormField::FormField() { -} - -FormField::~FormField() { -} diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Form.h b/Build/source/libs/xpdf/xpdf-src/xpdf/Form.h deleted file mode 100644 index 0e851fe16e0..00000000000 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Form.h +++ /dev/null @@ -1,77 +0,0 @@ -//======================================================================== -// -// Form.h -// -// Copyright 2012 Glyph & Cog, LLC -// -//======================================================================== - -#ifndef FORM_H -#define FORM_H - -#include - -#ifdef USE_GCC_PRAGMAS -#pragma interface -#endif - -#include "gtypes.h" - -class Gfx; -class FormField; - -//------------------------------------------------------------------------ - -class Form { -public: - - static Form *load(PDFDoc *docA, Catalog *catalog, Object *acroFormObj); - - virtual ~Form(); - - virtual const char *getType() = 0; - - virtual void draw(int pageNum, Gfx *gfx, GBool printing) = 0; - - virtual int getNumFields() = 0; - virtual FormField *getField(int idx) = 0; - - FormField *findField(int pg, double x, double y); - int findFieldIdx(int pg, double x, double y); - -protected: - - Form(PDFDoc *docA); - - PDFDoc *doc; -}; - -//------------------------------------------------------------------------ - -class FormField { -public: - - FormField(); - virtual ~FormField(); - - virtual int getPageNum() = 0; - virtual const char *getType() = 0; - - // Return the field name. This never returns NULL. - virtual Unicode *getName(int *length) = 0; - - // Return the field value. This returns NULL if the field does not - // have a value. - virtual Unicode *getValue(int *length) = 0; - - virtual void getBBox(double *llx, double *lly, double *urx, double *ury) = 0; - virtual void getFont(Ref *fontID, double *fontSize) = 0; - virtual void getColor(double *red, double *green, double *blue) = 0; - - // Return the resource dictionaries used to draw this field. The - // returned object must be either a dictionary or an array of - // dictonaries. - virtual Object *getResources(Object *res) = 0; -}; - -#endif diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.cc index 7a0c9c6a9a5..ed98d9d165f 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.cc @@ -21,6 +21,7 @@ #include "gmempp.h" #include "GString.h" #include "GList.h" +#include "Trace.h" #include "GlobalParams.h" #include "CharTypes.h" #include "Object.h" @@ -146,7 +147,15 @@ Operator Gfx::opTab[] = { &Gfx::opSetStrokeRGBColor}, {"S", 0, {tchkNone}, &Gfx::opStroke}, - {"SC", -4, {tchkNum, tchkNum, tchkNum, tchkNum}, + {"SC", -4, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum}, &Gfx::opSetStrokeColor}, {"SCN", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, @@ -240,7 +249,15 @@ Operator Gfx::opTab[] = { &Gfx::opSetRenderingIntent}, {"s", 0, {tchkNone}, &Gfx::opCloseStroke}, - {"sc", -4, {tchkNum, tchkNum, tchkNum, tchkNum}, + {"sc", -4, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum}, &Gfx::opSetFillColor}, {"scn", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, tchkSCN, @@ -277,6 +294,7 @@ GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) { Ref r; if (resDict) { + valid = gTrue; // build font dictionary fonts = NULL; @@ -312,6 +330,7 @@ GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) { resDict->lookup("Properties", &propsDict); } else { + valid = gFalse; fonts = NULL; xObjDict.initNull(); colorSpaceDict.initNull(); @@ -394,7 +413,8 @@ GBool GfxResources::lookupXObjectNF(const char *name, Object *obj) { return gFalse; } -void GfxResources::lookupColorSpace(const char *name, Object *obj) { +void GfxResources::lookupColorSpace(const char *name, Object *obj, + GBool inherit) { GfxResources *resPtr; //~ should also test for G, RGB, and CMYK - but only in inline images (?) @@ -411,6 +431,9 @@ void GfxResources::lookupColorSpace(const char *name, Object *obj) { } obj->free(); } + if (!inherit && valid) { + break; + } } obj->initNull(); } @@ -692,7 +715,7 @@ void Gfx::go(GBool topLevel) { aborted = gFalse; errCount = 0; numArgs = 0; - parser->getObj(&obj); + getContentObj(&obj); while (!obj.isEOF()) { // check for an abort @@ -748,7 +771,7 @@ void Gfx::go(GBool topLevel) { } // grab the next object - parser->getObj(&obj); + getContentObj(&obj); } obj.free(); @@ -772,6 +795,15 @@ void Gfx::go(GBool topLevel) { } } +void Gfx::getContentObj(Object *obj) { + parser->getObj(obj); + if (obj->isRef()) { + error(errSyntaxError, getPos(), "Indirect reference in content stream"); + obj->free(); + obj->initError(); + } +} + // Returns true if successful, false on error. GBool Gfx::execOp(Object *cmd, Object args[], int numArgs) { Operator *op; @@ -807,10 +839,9 @@ GBool Gfx::execOp(Object *cmd, Object args[], int numArgs) { } } else { if (numArgs > -op->numArgs) { - error(errSyntaxError, getPos(), + error(errSyntaxWarning, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name); - return gFalse; } } for (i = 0; i < numArgs; ++i) { @@ -951,14 +982,11 @@ void Gfx::opSetLineWidth(Object args[], int numArgs) { } void Gfx::opSetExtGState(Object args[], int numArgs) { - Object obj1, obj2, obj3, objRef3, obj4, obj5; + Object obj1, obj2, obj3, objRef3, obj4, obj5, backdropColorObj; Object args2[2]; GfxBlendMode mode; GBool haveFillOP; Function *funcs[4]; - GfxColor backdropColor; - GBool haveBackdropColor; - GfxColorSpace *blendingColorSpace; GBool alpha, knockout; double opac; int i; @@ -1167,48 +1195,20 @@ void Gfx::opSetExtGState(Object args[], int numArgs) { } } obj3.free(); - if ((haveBackdropColor = obj2.dictLookup("BC", &obj3)->isArray())) { - for (i = 0; i < gfxColorMaxComps; ++i) { - backdropColor.c[i] = 0; - } - for (i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) { - obj3.arrayGet(i, &obj4); - if (obj4.isNum()) { - backdropColor.c[i] = dblToCol(obj4.getNum()); - } - obj4.free(); - } - } - obj3.free(); + obj2.dictLookup("BC", &backdropColorObj); if (obj2.dictLookup("G", &obj3)->isStream()) { if (obj3.streamGetDict()->lookup("Group", &obj4)->isDict()) { - blendingColorSpace = NULL; knockout = gFalse; - if (!obj4.dictLookup("CS", &obj5)->isNull()) { - blendingColorSpace = GfxColorSpace::parse(&obj5 - ); - } - obj5.free(); if (obj4.dictLookup("K", &obj5)->isBool()) { knockout = obj5.getBool(); } obj5.free(); - if (!haveBackdropColor) { - if (blendingColorSpace) { - blendingColorSpace->getDefaultColor(&backdropColor); - } else { - //~ need to get the parent or default color space (?) - for (i = 0; i < gfxColorMaxComps; ++i) { - backdropColor.c[i] = 0; - } - } - } obj2.dictLookupNF("G", &objRef3); // it doesn't make sense for softmasks to be non-isolated, // because they're blended with a backdrop color, rather // than the original backdrop - doSoftMask(&obj3, &objRef3, alpha, blendingColorSpace, - gTrue, knockout, funcs[0], &backdropColor); + doSoftMask(&obj3, &objRef3, alpha, gTrue, knockout, funcs[0], + &backdropColorObj); objRef3.free(); if (funcs[0]) { delete funcs[0]; @@ -1223,6 +1223,7 @@ void Gfx::opSetExtGState(Object args[], int numArgs) { "Invalid soft mask in ExtGState - missing group"); } obj3.free(); + backdropColorObj.free(); } else if (!obj2.isNull()) { error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState"); } @@ -1233,9 +1234,8 @@ void Gfx::opSetExtGState(Object args[], int numArgs) { } void Gfx::doSoftMask(Object *str, Object *strRef, GBool alpha, - GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, - Function *transferFunc, GfxColor *backdropColor) { + Function *transferFunc, Object *backdropColorObj) { Dict *dict, *resDict; double m[6], bbox[4]; Object obj1, obj2; @@ -1291,14 +1291,10 @@ void Gfx::doSoftMask(Object *str, Object *strRef, GBool alpha, // draw it ++formDepth; - drawForm(strRef, resDict, m, bbox, gTrue, gTrue, - blendingColorSpace, isolated, knockout, - alpha, transferFunc, backdropColor); + drawForm(strRef, resDict, m, bbox, gTrue, gTrue, isolated, knockout, + alpha, transferFunc, backdropColorObj); --formDepth; - if (blendingColorSpace) { - delete blendingColorSpace; - } obj1.free(); } @@ -1502,6 +1498,10 @@ void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) { } } +// Technically, per the PDF spec, the 'sc' operator can only be used +// with device, CIE, and Indexed color spaces. Some buggy PDF +// generators use it for DeviceN, so we also allow that (same as +// 'scn', minus the optional pattern name argument). void Gfx::opSetFillColor(Object args[], int numArgs) { GfxColor color; int i; @@ -1524,6 +1524,10 @@ void Gfx::opSetFillColor(Object args[], int numArgs) { out->updateFillColor(state); } +// Technically, per the PDF spec, the 'SC' operator can only be used +// with device, CIE, and Indexed color spaces. Some buggy PDF +// generators use it for DeviceN, so we also allow that (same as +// 'SCN', minus the optional pattern name argument). void Gfx::opSetStrokeColor(Object args[], int numArgs) { GfxColor color; int i; @@ -1998,6 +2002,7 @@ void Gfx::doPatternImageMask(Object *ref, Stream *str, int width, int height, state->lineTo(0, 1); state->closePath(); doPatternFill(gTrue); + state->clearPath(); restoreState(); } @@ -2318,34 +2323,8 @@ void Gfx::doShadingPatternFill(GfxShadingPattern *sPat, state->clearPath(); } -#if 1 //~tmp: turn off anti-aliasing temporarily - out->setInShading(gTrue); -#endif - - // do shading type-specific operations - switch (shading->getType()) { - case 1: - doFunctionShFill((GfxFunctionShading *)shading); - break; - case 2: - doAxialShFill((GfxAxialShading *)shading); - break; - case 3: - doRadialShFill((GfxRadialShading *)shading); - break; - case 4: - case 5: - doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading); - break; - case 6: - case 7: - doPatchMeshShFill((GfxPatchMeshShading *)shading); - break; - } - -#if 1 //~tmp: turn off anti-aliasing temporarily - out->setInShading(gFalse); -#endif + // perform the fill + doShFill(shading); // restore graphics state restoreStateStack(savedState); @@ -2395,9 +2374,19 @@ void Gfx::opShFill(Object args[], int numArgs) { state->setFillColorSpace(shading->getColorSpace()->copy()); out->updateFillColorSpace(state); -#if 1 //~tmp: turn off anti-aliasing temporarily - out->setInShading(gTrue); -#endif + // perform the fill + doShFill(shading); + + // restore graphics state + restoreStateStack(savedState); + + delete shading; +} + +void Gfx::doShFill(GfxShading *shading) { + if (out->shadedFill(state, shading)) { + return; + } // do shading type-specific operations switch (shading->getType()) { @@ -2419,26 +2408,12 @@ void Gfx::opShFill(Object args[], int numArgs) { doPatchMeshShFill((GfxPatchMeshShading *)shading); break; } - -#if 1 //~tmp: turn off anti-aliasing temporarily - out->setInShading(gFalse); -#endif - - // restore graphics state - restoreStateStack(savedState); - - delete shading; } void Gfx::doFunctionShFill(GfxFunctionShading *shading) { double x0, y0, x1, y1; GfxColor colors[4]; - if (out->useShadedFills() && - out->functionShadedFill(state, shading)) { - return; - } - shading->getDomain(&x0, &y0, &x1, &y1); shading->getColor(x0, y0, &colors[0]); shading->getColor(x0, y1, &colors[1]); @@ -2567,11 +2542,6 @@ void Gfx::doAxialShFill(GfxAxialShading *shading) { GfxColor colors[axialSplits]; int abortCheckCounter, nComps, i, j, k; - if (out->useShadedFills() && - out->axialShadedFill(state, shading)) { - return; - } - // get the clip region bbox state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); @@ -2872,11 +2842,6 @@ void Gfx::doRadialShFill(GfxRadialShading *shading) { double theta, alpha, angle, t; int abortCheckCounter, ia, ib, k, n; - if (out->useShadedFills() && - out->radialShadedFill(state, shading)) { - return; - } - // get the shading info shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1); t0 = shading->getDomain0(); @@ -3515,8 +3480,8 @@ void Gfx::fillPatch(GfxPatch *patch, GfxPatchMeshShading *shading, int depth) { } for (i = 0; i < shading->getNComps(); ++i) { patch00.color[0][0][i] = patch->color[0][0][i]; - patch00.color[0][1][i] = 0.5 * (patch->color[0][0][i] + - patch->color[0][1][i]); + patch00.color[0][1][i] = 0.5 * (patch->color[0][0][i] + + patch->color[0][1][i]); patch01.color[0][0][i] = patch00.color[0][1][i]; patch01.color[0][1][i] = patch->color[0][1][i]; patch01.color[1][1][i] = 0.5 * (patch->color[0][1][i] + @@ -3905,6 +3870,19 @@ void Gfx::doShowText(GString *s) { //~ the CTM concat values here are wrong (but never used) out->updateCTM(state, 1, 0, 0, 1, 0, 0); state->transformDelta(dx, dy, &ddx, &ddy); +#if 0 + // The PDF spec says: "the graphics state shall be inherited + // from the environment of the text-showing operator that caused + // the [Type 3] glyph description to be invoked". However, + // Acrobat apparently copies the fill color to the stroke color. + // It looks like Ghostscript does the same. (This is only + // relevant for uncached Type 3 glyphs.) Uncomment this block + // to make Xpdf mimic Adobe (in violation of the PDF spec). + state->setStrokeColorSpace(state->getFillColorSpace()->copy()); + out->updateStrokeColorSpace(state); + state->setStrokeColor(state->getFillColor()); + out->updateStrokeColor(state); +#endif if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy, code, u, uLen)) { ((Gfx8BitFont *)font)->getCharProcNF(code, &charProcRef); @@ -4149,7 +4127,7 @@ GBool Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { GBool mask, invert; GfxColorSpace *colorSpace, *maskColorSpace; GfxImageColorMap *colorMap, *maskColorMap; - Object maskObj, smaskObj; + Object maskObj, smaskObj, maskRef; GBool haveColorKeyMask, haveExplicitMask, haveSoftMask, haveMatte; int maskColors[2*gfxColorMaxComps]; int maskWidth, maskHeight; @@ -4591,15 +4569,20 @@ GBool Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { // draw it } else { if (haveSoftMask) { + dict->lookupNF("Mask", &maskRef); out->drawSoftMaskedImage(state, ref, str, width, height, colorMap, - maskStr, maskWidth, maskHeight, maskColorMap, + &maskRef, maskStr, maskWidth, maskHeight, + maskColorMap, haveMatte ? matte : (double *)NULL, interpolate); + maskRef.free(); delete maskColorMap; } else if (haveExplicitMask) { + dict->lookupNF("Mask", &maskRef); out->drawMaskedImage(state, ref, str, width, height, colorMap, - maskStr, maskWidth, maskHeight, maskInvert, - interpolate); + &maskRef, maskStr, maskWidth, maskHeight, + maskInvert, interpolate); + maskRef.free(); } else { out->drawImage(state, ref, str, width, height, colorMap, haveColorKeyMask ? maskColors : (int *)NULL, inlineImg, @@ -4642,7 +4625,6 @@ GBool Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) { void Gfx::doForm(Object *strRef, Object *str) { Dict *dict; GBool transpGroup, isolated, knockout; - GfxColorSpace *blendingColorSpace; Object matrixObj, bboxObj; double m[6], bbox[4]; Object resObj; @@ -4705,15 +4687,9 @@ void Gfx::doForm(Object *strRef, Object *str) { // check for a transparency group transpGroup = isolated = knockout = gFalse; - blendingColorSpace = NULL; if (dict->lookup("Group", &obj1)->isDict()) { if (obj1.dictLookup("S", &obj2)->isName("Transparency")) { transpGroup = gTrue; - if (!obj1.dictLookup("CS", &obj3)->isNull()) { - blendingColorSpace = GfxColorSpace::parse(&obj3 - ); - } - obj3.free(); if (obj1.dictLookup("I", &obj3)->isBool()) { isolated = obj3.getBool(); } @@ -4729,25 +4705,23 @@ void Gfx::doForm(Object *strRef, Object *str) { // draw it ++formDepth; - drawForm(strRef, resDict, m, bbox, - transpGroup, gFalse, blendingColorSpace, isolated, knockout); + drawForm(strRef, resDict, m, bbox, transpGroup, gFalse, isolated, knockout); --formDepth; - if (blendingColorSpace) { - delete blendingColorSpace; - } resObj.free(); } void Gfx::drawForm(Object *strRef, Dict *resDict, double *matrix, double *bbox, GBool transpGroup, GBool softMask, - GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, GBool alpha, Function *transferFunc, - GfxColor *backdropColor) { + Object *backdropColorObj) { Parser *oldParser; GfxState *savedState; + GfxColorSpace *blendingColorSpace; + GfxColor backdropColor; + Object strObj, groupAttrsObj, csObj, obj1; double oldBaseMatrix[6]; int i; @@ -4779,7 +4753,23 @@ void Gfx::drawForm(Object *strRef, Dict *resDict, out->clip(state); state->clearPath(); + blendingColorSpace = NULL; if (softMask || transpGroup) { + // get the blending color space + // NB: this must be done AFTER pushing the resource dictionary, + // so that any Default*** color spaces are available + strRef->fetch(xref, &strObj); + if (strObj.streamGetDict()->lookup("Group", &groupAttrsObj)->isDict()) { + if (!groupAttrsObj.dictLookup("CS", &csObj)->isNull()) { + blendingColorSpace = GfxColorSpace::parse(&csObj + ); + } + csObj.free(); + } + groupAttrsObj.free(); + strObj.free(); + + traceBegin(oldBaseMatrix, softMask ? "begin soft mask" : "begin t-group"); if (state->getBlendMode() != gfxBlendNormal) { state->setBlendMode(gfxBlendNormal); out->updateBlendMode(state); @@ -4831,9 +4821,32 @@ void Gfx::drawForm(Object *strRef, Dict *resDict, popResources(); if (softMask) { - out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor); + for (i = 0; i < gfxColorMaxComps; ++i) { + backdropColor.c[i] = 0; + } + if (backdropColorObj->isArray()) { + for (i = 0; + i < backdropColorObj->arrayGetLength() && i < gfxColorMaxComps; + ++i) { + backdropColorObj->arrayGet(i, &obj1); + if (obj1.isNum()) { + backdropColor.c[i] = dblToCol(obj1.getNum()); + } + obj1.free(); + } + } else if (blendingColorSpace) { + blendingColorSpace->getDefaultColor(&backdropColor); + } + //~ else: need to get the parent or default color space (?) + out->setSoftMask(state, bbox, alpha, transferFunc, &backdropColor); + traceEnd(oldBaseMatrix, "end soft mask"); } else if (transpGroup) { out->paintTransparencyGroup(state, bbox); + traceEnd(oldBaseMatrix, "end t-group"); + } + + if (blendingColorSpace) { + delete blendingColorSpace; } return; @@ -4911,7 +4924,7 @@ Stream *Gfx::buildImageStream(GBool *haveLength) { // build dictionary dict.initDict(xref); - parser->getObj(&obj); + getContentObj(&obj); while (!obj.isCmd("ID") && !obj.isEOF()) { if (!obj.isName()) { error(errSyntaxError, getPos(), @@ -4920,14 +4933,19 @@ Stream *Gfx::buildImageStream(GBool *haveLength) { } else { key = copyString(obj.getName()); obj.free(); - parser->getObj(&obj); - if (obj.isEOF() || obj.isError()) { + getContentObj(&obj); + if (obj.isEOF()) { gfree(key); break; } - dict.dictAdd(key, &obj); + if (obj.isError()) { + gfree(key); + obj.free(); + } else { + dict.dictAdd(key, &obj); + } } - parser->getObj(&obj); + getContentObj(&obj); } if (obj.isEOF()) { error(errSyntaxError, getPos(), "End of file in inline image"); @@ -5108,7 +5126,7 @@ void Gfx::drawAnnot(Object *strRef, AnnotBorderStyle *borderStyle, // get the form bounding box dict->lookup("BBox", &bboxObj); - if (!bboxObj.isArray()) { + if (!bboxObj.isArray() || bboxObj.arrayGetLength() != 4) { error(errSyntaxError, getPos(), "Bad form bounding box"); bboxObj.free(); str.free(); @@ -5116,7 +5134,11 @@ void Gfx::drawAnnot(Object *strRef, AnnotBorderStyle *borderStyle, } for (i = 0; i < 4; ++i) { bboxObj.arrayGet(i, &obj1); - bbox[i] = obj1.getNum(); + if (obj1.isNum()) { + bbox[i] = obj1.getNum(); + } else { + bbox[i] = 0; + } obj1.free(); } bboxObj.free(); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.h b/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.h index 2159eaaf2e9..4625ac289d0 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.h @@ -76,7 +76,7 @@ public: GfxFont *lookupFontByRef(Ref ref); GBool lookupXObject(const char *name, Object *obj); GBool lookupXObjectNF(const char *name, Object *obj); - void lookupColorSpace(const char *name, Object *obj); + void lookupColorSpace(const char *name, Object *obj, GBool inherit = gTrue); GfxPattern *lookupPattern(const char *name ); GfxShading *lookupShading(const char *name @@ -88,6 +88,7 @@ public: private: + GBool valid; GfxFontDict *fonts; Object xObjDict; Object colorSpaceDict; @@ -164,10 +165,9 @@ public: void drawForm(Object *strRef, Dict *resDict, double *matrix, double *bbox, GBool transpGroup = gFalse, GBool softMask = gFalse, - GfxColorSpace *blendingColorSpace = NULL, GBool isolated = gFalse, GBool knockout = gFalse, GBool alpha = gFalse, Function *transferFunc = NULL, - GfxColor *backdropColor = NULL); + Object *backdropColorObj = NULL); // Take all of the content stream stack entries from . This // is useful when creating a new Gfx object to handle a pattern, @@ -214,6 +214,7 @@ private: GBool checkForContentStreamLoop(Object *ref); void go(GBool topLevel); + void getContentObj(Object *obj); GBool execOp(Object *cmd, Object args[], int numArgs); Operator *findOp(char *name); GBool checkArg(Object *arg, TchkType type); @@ -231,9 +232,8 @@ private: void opSetLineWidth(Object args[], int numArgs); void opSetExtGState(Object args[], int numArgs); void doSoftMask(Object *str, Object *strRef, GBool alpha, - GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, - Function *transferFunc, GfxColor *backdropColor); + Function *transferFunc, Object *backdropColorObj); void opSetRenderingIntent(Object args[], int numArgs); GfxRenderingIntent parseRenderingIntent(const char *name); @@ -280,6 +280,7 @@ private: void doShadingPatternFill(GfxShadingPattern *sPat, GBool stroke, GBool eoFill, GBool text); void opShFill(Object args[], int numArgs); + void doShFill(GfxShading *shading); void doFunctionShFill(GfxFunctionShading *shading); void doFunctionShFill1(GfxFunctionShading *shading, double x0, double y0, diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.cc index b10c81779ff..f224ff75b5f 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.cc @@ -467,6 +467,9 @@ void GfxFont::readFontDescriptor(XRef *xref, Dict *fontDict) { } else { t2 = 0; } + if (t != 0 && t < 1.9) { + declaredAscent = t; + } // if both Ascent and CapHeight are set, use the smaller one // (because the most common problem is that Ascent is too large) if (t2 != 0 && (t == 0 || t2 < t)) { @@ -851,19 +854,22 @@ char *GfxFont::readEmbFontFile(XRef *xref, int *len) { } str = obj2.getStream(); - size = 0; - buf = NULL; + size = 4096; + buf = (char *)gmalloc(size); + *len = 0; str->reset(); do { - if (size > INT_MAX - 4096) { - error(errSyntaxError, -1, "Embedded font file is too large"); - break; + if (*len > size - 4096) { + if (size > INT_MAX / 2) { + error(errSyntaxError, -1, "Embedded font file is too large"); + break; + } + size *= 2; + buf = (char *)grealloc(buf, size); } - buf = (char *)grealloc(buf, size + 4096); - n = str->getBlock(buf + size, 4096); - size += n; + n = str->getBlock(buf + *len, 4096); + *len += n; } while (n == 4096); - *len = size; str->close(); obj2.free(); @@ -947,6 +953,7 @@ Gfx8BitFont::Gfx8BitFont(XRef *xref, const char *tagA, Ref idA, GString *nameA, missingWidth = builtinFont->missingWidth; ascent = 0.001 * builtinFont->ascent; descent = 0.001 * builtinFont->descent; + declaredAscent = ascent; fontBBox[0] = 0.001 * builtinFont->bbox[0]; fontBBox[1] = 0.001 * builtinFont->bbox[1]; fontBBox[2] = 0.001 * builtinFont->bbox[2]; @@ -955,6 +962,7 @@ Gfx8BitFont::Gfx8BitFont(XRef *xref, const char *tagA, Ref idA, GString *nameA, missingWidth = 0; ascent = 0.75; descent = -0.25; + declaredAscent = ascent; fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0; } @@ -966,6 +974,7 @@ Gfx8BitFont::Gfx8BitFont(XRef *xref, const char *tagA, Ref idA, GString *nameA, if (builtinFont) { ascent = 0.001 * builtinFont->ascent; descent = 0.001 * builtinFont->descent; + declaredAscent = ascent; fontBBox[0] = 0.001 * builtinFont->bbox[0]; fontBBox[1] = 0.001 * builtinFont->bbox[1]; fontBBox[2] = 0.001 * builtinFont->bbox[2]; @@ -1459,13 +1468,20 @@ int *Gfx8BitFont::getCodeToGIDMap(FoFiTrueType *ff) { } // reverse map the char names through MacRomanEncoding, then map the - // char codes through the cmap + // char codes through the cmap; fall back on Unicode if that doesn't + // work if (useMacRoman) { for (i = 0; i < 256; ++i) { if ((charName = enc[i])) { if ((code = globalParams->getMacRomanCharCode(charName))) { map[i] = ff->mapCodeToGID(cmap, code); + } else if (unicodeCmap >= 0 && + (u = globalParams->mapNameToUnicode(charName))) { + map[i] = ff->mapCodeToGID(unicodeCmap, u); } + } else if (unicodeCmap >= 0 && + (n = ctu->mapToUnicode((CharCode)i, &u, 1))) { + map[i] = ff->mapCodeToGID(cmap, u); } else { map[i] = -1; } @@ -1631,6 +1647,7 @@ GfxCIDFont::GfxCIDFont(XRef *xref, const char *tagA, Ref idA, GString *nameA, missingWidth = 0; ascent = 0.95; descent = -0.35; + declaredAscent = ascent; fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0; collection = NULL; cMap = NULL; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.h b/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.h index 3b63fa69fe0..ded34bc1ee7 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.h @@ -185,6 +185,7 @@ public: // Return the ascent and descent values. double getAscent() { return ascent; } double getDescent() { return descent; } + double getDeclaredAscent() { return declaredAscent; } // Return the writing mode (0=horizontal, 1=vertical). virtual int getWMode() { return 0; } @@ -234,6 +235,7 @@ protected: double missingWidth; // "default" width double ascent; // max height above baseline double descent; // max depth below baseline + double declaredAscent; // ascent value, before munging GBool hasToUnicode; // true if the font has a ToUnicode map GBool ok; }; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.cc index 60b5f577054..5e9991e1b97 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.cc @@ -101,6 +101,7 @@ static const char *gfxColorSpaceModeNames[] = { GfxColorSpace::GfxColorSpace() { overprintMask = 0x0f; + defaultColorSpace = gFalse; } GfxColorSpace::~GfxColorSpace() { @@ -2683,6 +2684,11 @@ GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse( "Missing or invalid BitsPerCoordinate in shading dictionary"); goto err2; } + if (coordBits <= 0 || coordBits > 32) { + error(errSyntaxError, -1, + "Invalid BitsPerCoordinate in shading dictionary"); + goto err2; + } obj1.free(); if (dict->lookup("BitsPerComponent", &obj1)->isInt()) { compBits = obj1.getInt(); @@ -2691,6 +2697,11 @@ GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse( "Missing or invalid BitsPerComponent in shading dictionary"); goto err2; } + if (compBits <= 0 || compBits > 16) { + error(errSyntaxError, -1, + "Invalid BitsPerComponent in shading dictionary"); + goto err2; + } obj1.free(); flagBits = vertsPerRow = 0; // make gcc happy if (typeA == 4) { @@ -2701,6 +2712,10 @@ GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse( "Missing or invalid BitsPerFlag in shading dictionary"); goto err2; } + if (flagBits < 2 || flagBits > 8) { + error(errSyntaxError, -1, "Invalid BitsPerFlag in shading dictionary"); + goto err2; + } obj1.free(); } else { if (dict->lookup("VerticesPerRow", &obj1)->isInt()) { @@ -2711,6 +2726,11 @@ GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse( goto err2; } obj1.free(); + if (vertsPerRow < 2) { + error(errSyntaxError, -1, + "Invalid VerticesPerRow in shading dictionary"); + goto err2; + } } if (dict->lookup("Decode", &obj1)->isArray() && obj1.arrayGetLength() >= 6) { @@ -2911,6 +2931,34 @@ void GfxGouraudTriangleShading::getTriangle( } } +void GfxGouraudTriangleShading::getBBox(double *xMin, double *yMin, + double *xMax, double *yMax) { + double xxMin = 0; + double yyMin = 0; + double xxMax = 0; + double yyMax = 0; + if (nVertices > 0) { + xxMin = xxMax = vertices[0].x; + yyMin = yyMax = vertices[0].y; + } + for (int i = 1; i < nVertices; ++i) { + if (vertices[i].x < xxMin) { + xxMin = vertices[i].x; + } else if (vertices[i].x > xxMax) { + xxMax = vertices[i].x; + } + if (vertices[i].y < yyMin) { + yyMin = vertices[i].y; + } else if (vertices[i].y > yyMax) { + yyMax = vertices[i].y; + } + } + *xMin = xxMin; + *yMin = yyMin; + *xMax = xxMax; + *yMax = yyMax; +} + void GfxGouraudTriangleShading::getColor(double *in, GfxColor *out) { double c[gfxColorMaxComps]; int i; @@ -2996,6 +3044,10 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, Object obj1, obj2; int i, j; + nPatchesA = 0; + patchesA = NULL; + patchesSize = 0; + if (dict->lookup("BitsPerCoordinate", &obj1)->isInt()) { coordBits = obj1.getInt(); } else { @@ -3003,6 +3055,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, "Missing or invalid BitsPerCoordinate in shading dictionary"); goto err2; } + if (coordBits <= 0 || coordBits > 32) { + error(errSyntaxError, -1, + "Invalid BitsPerCoordinate in shading dictionary"); + goto err2; + } obj1.free(); if (dict->lookup("BitsPerComponent", &obj1)->isInt()) { compBits = obj1.getInt(); @@ -3011,6 +3068,11 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, "Missing or invalid BitsPerComponent in shading dictionary"); goto err2; } + if (compBits <= 0 || compBits > 16) { + error(errSyntaxError, -1, + "Invalid BitsPerComponent in shading dictionary"); + goto err2; + } obj1.free(); if (dict->lookup("BitsPerFlag", &obj1)->isInt()) { flagBits = obj1.getInt(); @@ -3019,6 +3081,10 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, "Missing or invalid BitsPerFlag in shading dictionary"); goto err2; } + if (flagBits < 2 || flagBits > 8) { + error(errSyntaxError, -1, "Invalid BitsPerFlag in shading dictionary"); + goto err2; + } obj1.free(); if (dict->lookup("Decode", &obj1)->isArray() && obj1.arrayGetLength() >= 6) { @@ -3076,9 +3142,6 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, } obj1.free(); - nPatchesA = 0; - patchesA = NULL; - patchesSize = 0; bitBuf = new GfxShadingBitBuf(str); while (1) { if (!bitBuf->getBits(flagBits, &flag)) { @@ -3506,6 +3569,9 @@ GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, err2: obj1.free(); err1: + if (patchesA) { + gfree(patchesA); + } return NULL; } @@ -3513,6 +3579,38 @@ GfxShading *GfxPatchMeshShading::copy() { return new GfxPatchMeshShading(this); } +void GfxPatchMeshShading::getBBox(double *xMin, double *yMin, + double *xMax, double *yMax) { + double xxMin = 0; + double yyMin = 0; + double xxMax = 0; + double yyMax = 0; + if (nPatches > 0) { + xxMin = patches[0].x[0][0]; + yyMin = patches[0].y[0][0]; + } + for (int i = 0; i < nPatches; ++i) { + for (int j = 0; j < 4; ++j) { + for (int k = 0; k < 4; ++k) { + if (patches[i].x[j][k] < xxMin) { + xxMin = patches[i].x[j][k]; + } else if (patches[i].x[j][k] > xxMax) { + xxMax = patches[i].x[j][k]; + } + if (patches[i].y[j][k] < yyMin) { + yyMin = patches[i].y[j][k]; + } else if (patches[i].y[j][k] > yyMax) { + yyMax = patches[i].y[j][k]; + } + } + } + } + *xMin = xxMin; + *yMin = yyMin; + *xMax = xxMax; + *yMax = yyMax; +} + void GfxPatchMeshShading::getColor(double *in, GfxColor *out) { double c[gfxColorMaxComps]; int i; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.h b/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.h index 50507040fed..f9c0cfcad6e 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.h @@ -207,6 +207,11 @@ public: // Return the color space's overprint mask. Guint getOverprintMask() { return overprintMask; } + // Return true if this color space object is the result of + // substituting a DefaultGray/RGB/CMYK color space for + // DeviceGray/RGB/CMYK. + GBool isDefaultColorSpace() { return defaultColorSpace; } + // Return the number of color space modes static int getNumColorSpaceModes(); @@ -216,6 +221,7 @@ public: protected: Guint overprintMask; + GBool defaultColorSpace; }; //------------------------------------------------------------------------ @@ -877,6 +883,7 @@ public: void getTriangle(int i, double *x0, double *y0, double *color0, double *x1, double *y1, double *color1, double *x2, double *y2, double *color2); + void getBBox(double *xMin, double *yMin, double *xMax, double *yMax); void getColor(double *in, GfxColor *out); private: @@ -916,6 +923,7 @@ public: int getNComps() { return nComps; } int getNPatches() { return nPatches; } GfxPatch *getPatch(int i) { return &patches[i]; } + void getBBox(double *xMin, double *yMin, double *xMax, double *yMax); void getColor(double *in, GfxColor *out); private: diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.cc index 56bfba8e3cc..5ff932479d1 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.cc @@ -178,24 +178,21 @@ class SysFontInfo { public: GString *name; - GBool bold; - GBool italic; GString *path; SysFontType type; int fontNum; // for TrueType collections - SysFontInfo(GString *nameA, GBool boldA, GBool italicA, - GString *pathA, SysFontType typeA, int fontNumA); + SysFontInfo(GString *nameA, GString *pathA, SysFontType typeA, int fontNumA); ~SysFontInfo(); - GBool match(SysFontInfo *fi); - GBool match(GString *nameA, GBool boldA, GBool italicA); + GString *mungeName1(GString *in); + GString *mungeName2(GString *in); + void mungeName3(GString *name, GBool *bold, GBool *italic); + int match(GString *nameA); }; -SysFontInfo::SysFontInfo(GString *nameA, GBool boldA, GBool italicA, - GString *pathA, SysFontType typeA, int fontNumA) { +SysFontInfo::SysFontInfo(GString *nameA, GString *pathA, + SysFontType typeA, int fontNumA) { name = nameA; - bold = boldA; - italic = italicA; path = pathA; type = typeA; fontNum = fontNumA; @@ -206,14 +203,150 @@ SysFontInfo::~SysFontInfo() { delete path; } -GBool SysFontInfo::match(SysFontInfo *fi) { - return !strcasecmp(name->getCString(), fi->name->getCString()) && - bold == fi->bold && italic == fi->italic; +// Remove space/comma/dash/underscore chars. +// Uppercase the name. +GString *SysFontInfo::mungeName1(GString *in) { + GString *out = new GString(); + for (char *p = in->getCString(); *p; ++p) { + if (*p == ' ' || *p == ',' || *p == '-' || *p == '_') { + // skip + } else if (*p >= 'a' && *p <= 'z') { + out->append((char)(*p & 0xdf)); + } else { + out->append(*p); + } + } + return out; +} + +// Remove trailing "Identity-H"/"Identity-V" from the name. +// Split the name into tokens at space/comma/dash/underscore. +// Remove trailing "MT" or "BT" from tokens. +// Remove trailing "PS" and "WGL4" from tokens. +// Uppercase each token. +// Concatenate tokens (dropping the space/comma/dash chars). +GString *SysFontInfo::mungeName2(GString *in) { + GString *out = new GString(); + char *p0 = in->getCString(); + while (*p0) { + if (!strcmp(p0, "Identity-H") || !strcmp(p0, "Identity-V")) { + break; + } + char *p1; + for (p1 = p0 + 1; + *p1 && *p1 != ' ' && *p1 != ',' && *p1 != '-' && *p1 != '_'; + ++p1) ; + char *p2 = p1; + if (p2 - p0 >= 2 && (p2[-2] == 'B' || p2[-2] == 'M') && p2[-1] == 'T') { + p2 -= 2; + } + if (p2 - p0 >= 2 && p2[-2] == 'P' && p2[-1] == 'S') { + p2 -= 2; + } + if (p2 - p0 >= 4 && + p2[-4] == 'W' && p2[-3] == 'G' && p2[-2] == 'L' && p2[-1] == '4') { + p2 -= 4; + } + for (; p0 < p2; ++p0) { + if (*p0 >= 'a' && *p0 <= 'z') { + out->append((char)(*p0 & 0xdf)); + } else { + out->append(*p0); + } + } + for (p0 = p1; *p0 == ' ' || *p0 == ',' || *p0 == '-' || *p0 == '_'; ++p0) ; + } + return out; } -GBool SysFontInfo::match(GString *nameA, GBool boldA, GBool italicA) { - return !strcasecmp(name->getCString(), nameA->getCString()) && - bold == boldA && italic == italicA; +// Remove trailing bold/italic/regular/roman tags from the name. +// (Note: the names have already been uppercased by mungeName1/2.) +void SysFontInfo::mungeName3(GString *name, GBool *bold, GBool *italic) { + *bold = gFalse; + *italic = gFalse; + int n = name->getLength(); + while (1) { + if (n >= 4 && !strcmp(name->getCString() + n - 4, "BOLD")) { + name->del(n - 4, 4); + n -= 4; + *bold = gTrue; + } else if (n >= 6 && !strcmp(name->getCString() + n - 6, "ITALIC")) { + name->del(n - 6, 6); + n -= 6; + *italic = gTrue; + } else if (n >= 7 && !strcmp(name->getCString() + n - 7, "REGULAR")) { + name->del(n - 7, 7); + n -= 7; + } else if (n >= 5 && !strcmp(name->getCString() + n - 5, "ROMAN")) { + name->del(n - 5, 5); + n -= 5; + } else { + break; + } + } +} + +// Returns a score indicating how well this font matches [nameA]. A +// higher score is better. Zero indicates a non-match. +int SysFontInfo::match(GString *nameA) { + // fast fail: check if the first two letters match + if (strncasecmp(name->getCString(), nameA->getCString(), 2)) { + return 0; + } + + GString *pdfName1 = mungeName1(nameA); + GString *sysName1 = mungeName1(name); + if (!pdfName1->cmp(sysName1)) { + delete pdfName1; + delete sysName1; + return 8; + } + + GString *pdfName2 = mungeName2(nameA); + GString *sysName2 = mungeName2(name); + if (!pdfName2->cmp(sysName2)) { + delete pdfName1; + delete sysName1; + delete pdfName2; + delete sysName2; + return 7; + } + + GBool pdfBold1, pdfItalic1, sysBold1, sysItalic1; + mungeName3(pdfName1, &pdfBold1, &pdfItalic1); + mungeName3(sysName1, &sysBold1, &sysItalic1); + int eq1 = !pdfName1->cmp(sysName1); + + GBool pdfBold2, pdfItalic2, sysBold2, sysItalic2; + mungeName3(pdfName2, &pdfBold2, &pdfItalic2); + mungeName3(sysName2, &sysBold2, &sysItalic2); + int eq2 =!pdfName2->cmp(sysName2); + + delete pdfName1; + delete sysName1; + delete pdfName2; + delete sysName2; + + if (eq1 && pdfBold1 == sysBold1 && pdfItalic1 == sysItalic1) { + return 6; + } + if (eq2 && pdfBold2 == sysBold2 && pdfItalic2 == sysItalic2) { + return 5; + } + if (eq1 && pdfItalic1 == sysItalic1) { + return 4; + } + if (eq2 && pdfItalic2 == sysItalic2) { + return 3; + } + if (eq1) { + return 2; + } + if (eq2) { + return 1; + } + + return 0; } //------------------------------------------------------------------------ @@ -254,102 +387,17 @@ SysFontList::~SysFontList() { } SysFontInfo *SysFontList::find(GString *name) { - GString *name2; - GBool bold, italic; - SysFontInfo *fi; - char c; - int n, i; - - name2 = name->copy(); - - // remove space, comma, dash chars - i = 0; - while (i < name2->getLength()) { - c = name2->getChar(i); - if (c == ' ' || c == ',' || c == '-') { - name2->del(i); - } else { - ++i; + SysFontInfo *match = NULL; + int score = 0; + for (int i = 0; i < fonts->getLength(); ++i) { + SysFontInfo *fi = (SysFontInfo *)fonts->get(i); + int s = fi->match(name); + if (s > score) { + match = fi; + score = s; } } - n = name2->getLength(); - - // font names like "Arial-BoldMT,Bold" are occasionally used, - // so run this loop twice - bold = italic = gFalse; - for (i = 0; i < 2; ++i) { - - // remove trailing "MT" (Foo-MT, Foo-BoldMT, etc.) - if (n > 2 && !strcmp(name2->getCString() + n - 2, "MT")) { - name2->del(n - 2, 2); - n -= 2; - } - - // look for "Regular" - if (n > 7 && !strcmp(name2->getCString() + n - 7, "Regular")) { - name2->del(n - 7, 7); - n -= 7; - } - - // look for "Italic" - if (n > 6 && !strcmp(name2->getCString() + n - 6, "Italic")) { - name2->del(n - 6, 6); - italic = gTrue; - n -= 6; - } - - // look for "Bold" - if (n > 4 && !strcmp(name2->getCString() + n - 4, "Bold")) { - name2->del(n - 4, 4); - bold = gTrue; - n -= 4; - } - } - - // remove trailing "PS" - if (n > 2 && !strcmp(name2->getCString() + n - 2, "PS")) { - name2->del(n - 2, 2); - n -= 2; - } - - // remove trailing "IdentityH" - if (n > 9 && !strcmp(name2->getCString() + n - 9, "IdentityH")) { - name2->del(n - 9, 9); - n -= 9; - } - - // search for the font - fi = NULL; - for (i = 0; i < fonts->getLength(); ++i) { - fi = (SysFontInfo *)fonts->get(i); - if (fi->match(name2, bold, italic)) { - break; - } - fi = NULL; - } - if (!fi && bold) { - // try ignoring the bold flag - for (i = 0; i < fonts->getLength(); ++i) { - fi = (SysFontInfo *)fonts->get(i); - if (fi->match(name2, gFalse, italic)) { - break; - } - fi = NULL; - } - } - if (!fi && (bold || italic)) { - // try ignoring the bold and italic flags - for (i = 0; i < fonts->getLength(); ++i) { - fi = (SysFontInfo *)fonts->get(i); - if (fi->match(name2, gFalse, gFalse)) { - break; - } - fi = NULL; - } - } - - delete name2; - return fi; + return match; } #ifdef _WIN32 @@ -421,50 +469,15 @@ void SysFontList::scanWindowsFonts(char *winFontDir) { SysFontInfo *SysFontList::makeWindowsFont(char *name, int fontNum, char *path) { - int n; - GBool bold, italic; - GString *s; - char c; - int i; - SysFontType type; - - n = (int)strlen(name); - bold = italic = gFalse; + int n = (int)strlen(name); - // remove trailing ' (TrueType)' - if (n > 11 && !strncmp(name + n - 11, " (TrueType)", 11)) { + // remove trailing ' (TrueType)' or ' (OpenType)' + if (n > 11 && (!strncmp(name + n - 11, " (TrueType)", 11) || + !strncmp(name + n - 11, " (OpenType)", 11))) { n -= 11; } - // remove trailing ' Italic' - if (n > 7 && !strncmp(name + n - 7, " Italic", 7)) { - n -= 7; - italic = gTrue; - } - - // remove trailing ' Bold' - if (n > 5 && !strncmp(name + n - 5, " Bold", 5)) { - n -= 5; - bold = gTrue; - } - - // remove trailing ' Regular' - if (n > 8 && !strncmp(name + n - 8, " Regular", 8)) { - n -= 8; - } - - //----- normalize the font name - s = new GString(name, n); - i = 0; - while (i < s->getLength()) { - c = s->getChar(i); - if (c == ' ' || c == ',' || c == '-') { - s->del(i); - } else { - ++i; - } - } - + SysFontType type; if (!strcasecmp(path + strlen(path) - 4, ".ttc")) { type = sysFontTTC; } else if (!strcasecmp(path + strlen(path) - 4, ".otf")) { @@ -472,31 +485,32 @@ SysFontInfo *SysFontList::makeWindowsFont(char *name, int fontNum, } else { type = sysFontTTF; } - return new SysFontInfo(s, bold, italic, new GString(path), type, fontNum); + + return new SysFontInfo(new GString(name, n), new GString(path), + type, fontNum); } -#endif +#endif // _WIN32 #if HAVE_FONTCONFIG void SysFontList::scanFontconfigFonts() { + FcConfig *cfg; FcPattern *pattern; FcObjectSet *objSet; FcFontSet *fontSet; - char *family, *file, *styleLang, *style; - GString *family2; + char *name, *file; SysFontType type; - GBool bold, italic; - char c; - int fontNum, i, j, n; + int fontNum, i, n; - FcInit(); + if (!(cfg = FcInitLoadConfigAndFonts())) { + return; + } pattern = FcPatternBuild(NULL, FC_OUTLINE, FcTypeBool, FcTrue, FC_SCALABLE, FcTypeBool, FcTrue, NULL); - objSet = FcObjectSetBuild(FC_FAMILY, FC_STYLE, FC_STYLELANG, - FC_FILE, FC_INDEX, NULL); - fontSet = FcFontList(NULL, pattern, objSet); + objSet = FcObjectSetBuild(FC_FULLNAME, FC_FILE, FC_INDEX, NULL); + fontSet = FcFontList(cfg, pattern, objSet); FcPatternDestroy(pattern); FcObjectSetDestroy(objSet); @@ -528,54 +542,21 @@ void SysFontList::scanFontconfigFonts() { fontNum = 0; } - //--- font family - if (FcPatternGetString(fontSet->fonts[i], FC_FAMILY, 0, - (FcChar8 **)&family) + //--- font name + if (FcPatternGetString(fontSet->fonts[i], FC_FULLNAME, 0, + (FcChar8 **)&name) != FcResultMatch) { continue; } - //----- normalize the font name - family2 = new GString(family); - j = 0; - while (j < family2->getLength()) { - c = family2->getChar(j); - if (c == ' ' || c == ',' || c == '-') { - family2->del(j); - } else { - ++j; - } - } - - //--- font style - style = NULL; - for (j = 0; - FcPatternGetString(fontSet->fonts[i], FC_STYLELANG, j, - (FcChar8 **)&styleLang) - == FcResultMatch; - ++j) { - if (!strcmp(styleLang, "en")) { - if (FcPatternGetString(fontSet->fonts[i], FC_STYLE, j, - (FcChar8 **)&style) - != FcResultMatch) { - style = NULL; - } - break; - } - ++j; - } - bold = style && strstr(style, "Bold") != NULL; - italic = style && (strstr(style, "Italic") != NULL || - strstr(style, "Oblique") != NULL); - - fonts->append(new SysFontInfo(family2, bold, italic, - new GString(file), type, fontNum)); + fonts->append(new SysFontInfo(new GString(name), new GString(file), + type, fontNum)); } FcFontSetDestroy(fontSet); } - FcFini(); + FcConfigDestroy(cfg); } #endif // HAVE_FONTCONFIG @@ -663,6 +644,8 @@ GlobalParams::GlobalParams(const char *cfgFileName) { #else baseDir = appendToPath(getHomeDir(), ".xpdf"); #endif + configFileVars = new GHash(gTrue); + setDataDirVar(); nameToUnicode = new NameToCharCode(); cidToUnicodes = new GHash(gTrue); unicodeToUnicodes = new GHash(gTrue); @@ -736,6 +719,7 @@ GlobalParams::GlobalParams(const char *cfgFileName) { initialDisplayMode = new GString("continuous"); initialToolbarState = gTrue; initialSidebarState = gTrue; + initialSidebarWidth = 0; initialSelectMode = new GString("linear"); maxTileWidth = 1500; maxTileHeight = 1500; @@ -761,6 +745,7 @@ GlobalParams::GlobalParams(const char *cfgFileName) { paperColor = new GString("#ffffff"); matteColor = new GString("#808080"); fullScreenMatteColor = new GString("#000000"); + selectionColor = new GString("#8080ff"); reverseVideoInvertImages = gFalse; launchCommand = NULL; movieCommand = NULL; @@ -769,7 +754,6 @@ GlobalParams::GlobalParams(const char *cfgFileName) { mapUnknownCharNames = gFalse; mapExtTrueTypeFontsViaUnicode = gTrue; droppedFonts = new GHash(gTrue); - enableXFA = gTrue; createDefaultKeyBindings(); popupMenuCmds = new GList(); tabStateFile = appendToPath(getHomeDir(), ".xpdf.tab-state"); @@ -847,14 +831,51 @@ GlobalParams::GlobalParams(const char *cfgFileName) { } } +void GlobalParams::setDataDirVar() { + GString *dir; + +#if defined(XPDFRC_DATADIR) + dir = new GString(XPDFRC_DATADIR); +#elif defined(_WIN32) + wchar_t buf[512]; + DWORD n = GetModuleFileNameW(NULL, buf, sizeof(buf) / sizeof(wchar_t)); + if (n <= 0 || n >= sizeof(buf)) { + // error or path too long for buffer - just use the current dir + buf[0] = L'\0'; + } + GString *path = fileNameToUTF8(buf); + dir = grabPath(path->getCString()); + delete path; + appendToPath(dir, "data"); +#else + //~ may be useful to allow the options of using the install dir + //~ and/or the user's home dir (?) + dir = new GString("./data"); +#endif + + configFileVars->add(new GString("DATADIR"), dir); +} + void GlobalParams::createDefaultKeyBindings() { keyBindings = new GList(); //----- mouse buttons keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress1, xpdfKeyModNone, xpdfKeyContextAny, "startSelection")); + keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress1, xpdfKeyModShift, + xpdfKeyContextAny, + "startExtendedSelection")); keyBindings->append(new KeyBinding(xpdfKeyCodeMouseRelease1, xpdfKeyModNone, xpdfKeyContextAny, "endSelection")); + keyBindings->append(new KeyBinding(xpdfKeyCodeMouseRelease1, xpdfKeyModShift, + xpdfKeyContextAny, + "endSelection")); + keyBindings->append(new KeyBinding(xpdfKeyCodeMouseDoubleClick1, + xpdfKeyModNone, xpdfKeyContextAny, + "selectWord")); + keyBindings->append(new KeyBinding(xpdfKeyCodeMouseTripleClick1, + xpdfKeyModNone, xpdfKeyContextAny, + "selectLine")); keyBindings->append(new KeyBinding(xpdfKeyCodeMouseClick1, xpdfKeyModNone, xpdfKeyContextAny, "followLinkNoSel")); keyBindings->append(new KeyBinding(xpdfKeyCodeMouseClick2, xpdfKeyModNone, @@ -876,6 +897,10 @@ void GlobalParams::createDefaultKeyBindings() { xpdfKeyContextAny, "scrollLeft(16)")); keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress7, xpdfKeyModNone, xpdfKeyContextAny, "scrollRight(16)")); + keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress4, xpdfKeyModCtrl, + xpdfKeyContextAny, "zoomIn")); + keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress5, xpdfKeyModCtrl, + xpdfKeyContextAny, "zoomOut")); //----- control keys keyBindings->append(new KeyBinding('o', xpdfKeyModCtrl, @@ -1002,26 +1027,10 @@ void GlobalParams::parseFile(GString *fileName, FILE *f) { void GlobalParams::parseLine(char *buf, GString *fileName, int line) { GList *tokens; GString *cmd, *incFile; - char *p1, *p2; FILE *f2; // break the line into tokens - tokens = new GList(); - p1 = buf; - while (*p1) { - for (; *p1 && isspace(*p1); ++p1) ; - if (!*p1) { - break; - } - if (*p1 == '"' || *p1 == '\'') { - for (p2 = p1 + 1; *p2 && *p2 != *p1; ++p2) ; - ++p1; - } else { - for (p2 = p1 + 1; *p2 && !isspace(*p2); ++p2) ; - } - tokens->append(new GString(p1, (int)(p2 - p1))); - p1 = *p2 ? p2 + 1 : p2; - } + tokens = parseLineTokens(buf, fileName, line); // parse the line if (tokens->getLength() > 0 && @@ -1153,6 +1162,9 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { } else if (!cmd->cmp("initialSidebarState")) { parseYesNo("initialSidebarState", &initialSidebarState, tokens, fileName, line); + } else if (!cmd->cmp("initialSidebarWidth")) { + parseInteger("initialSidebarWidth", &initialSidebarWidth, + tokens, fileName, line); } else if (!cmd->cmp("initialSelectMode")) { parseString("initialSelectMode", &initialSelectMode, tokens, fileName, line); @@ -1217,6 +1229,8 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { } else if (!cmd->cmp("fullScreenMatteColor")) { parseString("fullScreenMatteColor", &fullScreenMatteColor, tokens, fileName, line); + } else if (!cmd->cmp("selectionColor")) { + parseString("selectionColor", &selectionColor, tokens, fileName, line); } else if (!cmd->cmp("reverseVideoInvertImages")) { parseYesNo("reverseVideoInvertImages", &reverseVideoInvertImages, tokens, fileName, line); @@ -1238,8 +1252,6 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { tokens, fileName, line); } else if (!cmd->cmp("dropFont")) { parseDropFont(tokens, fileName, line); - } else if (!cmd->cmp("enableXFA")) { - parseYesNo("enableXFA", &enableXFA, tokens, fileName, line); } else if (!cmd->cmp("bind")) { parseBind(tokens, fileName, line); } else if (!cmd->cmp("unbind")) { @@ -1269,6 +1281,8 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { } else if (!cmd->cmp("fontpath") || !cmd->cmp("fontmap")) { error(errConfig, -1, "The config file format has changed since Xpdf 0.9x"); + } else if (!cmd->cmp("enableXFA")) { + error(errConfig, -1, "The enableXFA option is no longer used"); } } } @@ -1276,6 +1290,71 @@ void GlobalParams::parseLine(char *buf, GString *fileName, int line) { deleteGList(tokens, GString); } +// Split a line into a sequence of tokens. Tokens are separated by +// whitespace. Each token is one of: +// - unquoted string, which can contain any char other than +// whitespace, and which cannot start with a single quote, double +// quote, or at-double-quote (xxxx) +// - single-quoted string, which can contain any char other than the +// single quote ('xxxx') +// - double-quoted string, which can contain any char other than the +// double quote ("xxxx") +// - at-double-quoted string, which can contain variables and escape +// chars (@"xxxx") +// - variables look like ${name} +// - special chars (${}") can be escaped with %, e.g., +// @"foo%"bar", @"foo%$bar", @"foo%%bar" +GList *GlobalParams::parseLineTokens(char *buf, GString *fileName, int line) { + GList *tokens = new GList(); + char *p1 = buf; + while (*p1) { + for (; *p1 && isspace(*p1); ++p1) ; + if (!*p1) { + break; + } + if (*p1 == '"' || *p1 == '\'') { + char *p2; + for (p2 = p1 + 1; *p2 && *p2 != *p1; ++p2) ; + ++p1; + tokens->append(new GString(p1, (int)(p2 - p1))); + p1 = *p2 ? p2 + 1 : p2; + } else if (*p1 == '@' && p1[1] == '"') { + GString *token = new GString(); + char *p2 = p1 + 2; + while (*p2 && *p2 != '"') { + if (*p2 == '%' && p2[1]) { + token->append(p2[1]); + p2 += 2; + } else if (*p2 == '$' && p2[1] == '{') { + p2 += 2; + char *p3; + for (p3 = p2; *p3 && *p3 != '}'; ++p3) ; + GString *varName = new GString(p2, (int)(p3 - p2)); + GString *varValue = (GString *)configFileVars->lookup(varName); + if (varValue) { + token->append(varValue); + } else { + error(errConfig, -1, "Unknown config file variable '%t'", varName); + } + delete varName; + p2 = *p3 ? p3 + 1 : p3; + } else { + token->append(*p2); + ++p2; + } + } + tokens->append(token); + p1 = *p2 ? p2 + 1 : p2; + } else { + char *p2; + for (p2 = p1 + 1; *p2 && !isspace(*p2); ++p2) ; + tokens->append(new GString(p1, (int)(p2 - p1))); + p1 = p2; + } + } + return tokens; +} + void GlobalParams::parseNameToUnicode(GList *tokens, GString *fileName, int line) { GString *name; @@ -1738,6 +1817,8 @@ GBool GlobalParams::parseKey(GString *modKeyStr, GString *contextStr, *code = xpdfKeyCodeEnter; } else if (!strcmp(p0, "backspace")) { *code = xpdfKeyCodeBackspace; + } else if (!strcmp(p0, "esc")) { + *code = xpdfKeyCodeEsc; } else if (!strcmp(p0, "insert")) { *code = xpdfKeyCodeInsert; } else if (!strcmp(p0, "delete")) { @@ -1758,8 +1839,6 @@ GBool GlobalParams::parseKey(GString *modKeyStr, GString *contextStr, *code = xpdfKeyCodeUp; } else if (!strcmp(p0, "down")) { *code = xpdfKeyCodeDown; - } else if (!strcmp(p0, "esc")) { - *code = xpdfKeyCodeEsc; } else if (p0[0] == 'f' && p0[1] >= '1' && p0[1] <= '9' && !p0[2]) { *code = xpdfKeyCodeF1 + (p0[1] - '1'); } else if (p0[0] == 'f' && @@ -1782,6 +1861,16 @@ GBool GlobalParams::parseKey(GString *modKeyStr, GString *contextStr, (!p0[11] || (p0[11] >= '0' && p0[11] <= '9' && !p0[12])) && (btn = atoi(p0 + 10)) >= 1 && btn <= 32) { *code = xpdfKeyCodeMouseClick1 + btn - 1; + } else if (!strncmp(p0, "mouseDoubleClick", 16) && + p0[16] >= '0' && p0[16] <= '9' && + (!p0[17] || (p0[17] >= '0' && p0[17] <= '9' && !p0[18])) && + (btn = atoi(p0 + 16)) >= 1 && btn <= 32) { + *code = xpdfKeyCodeMouseDoubleClick1 + btn - 1; + } else if (!strncmp(p0, "mouseTripleClick", 16) && + p0[16] >= '0' && p0[16] <= '9' && + (!p0[17] || (p0[17] >= '0' && p0[17] <= '9' && !p0[18])) && + (btn = atoi(p0 + 16)) >= 1 && btn <= 32) { + *code = xpdfKeyCodeMouseTripleClick1 + btn - 1; } else if (*p0 >= 0x20 && *p0 <= 0x7e && !p0[1]) { *code = (int)*p0; } else { @@ -1981,6 +2070,7 @@ GlobalParams::~GlobalParams() { delete macRomanReverseMap; delete baseDir; + deleteGHash(configFileVars, GString); delete nameToUnicode; deleteGHash(cidToUnicodes, GString); deleteGHash(unicodeToUnicodes, GString); @@ -2009,6 +2099,9 @@ GlobalParams::~GlobalParams() { if (fullScreenMatteColor) { delete fullScreenMatteColor; } + if (selectionColor) { + delete selectionColor; + } if (launchCommand) { delete launchCommand; } @@ -2784,6 +2877,26 @@ GString *GlobalParams::getTextEncodingName() { return s; } +GList *GlobalParams::getAvailableTextEncodings() { + GList *list; // [GString] + GHashIter *iter; + GString *key; + void *val; + + list = new GList(); + lockGlobalParams; + residentUnicodeMaps->startIter(&iter); + while (residentUnicodeMaps->getNext(&iter, &key, &val)) { + list->append(key->copy()); + } + unicodeMaps->startIter(&iter); + while (unicodeMaps->getNext(&iter, &key, &val)) { + list->append(key->copy()); + } + unlockGlobalParams; + return list; +} + EndOfLineKind GlobalParams::getTextEOL() { EndOfLineKind eol; @@ -2856,6 +2969,15 @@ GBool GlobalParams::getInitialSidebarState() { return state; } +int GlobalParams::getInitialSidebarWidth() { + int w; + + lockGlobalParams; + w = initialSidebarWidth; + unlockGlobalParams; + return w; +} + GString *GlobalParams::getInitialSelectMode() { GString *s; @@ -3075,6 +3197,15 @@ GString *GlobalParams::getFullScreenMatteColor() { return s; } +GString *GlobalParams::getSelectionColor() { + GString *s; + + lockGlobalParams; + s = selectionColor->copy(); + unlockGlobalParams; + return s; +} + GBool GlobalParams::getReverseVideoInvertImages() { GBool invert; @@ -3129,15 +3260,6 @@ GBool GlobalParams::isDroppedFont(const char *fontName) { return isDropped; } -GBool GlobalParams::getEnableXFA() { - GBool enable; - - lockGlobalParams; - enable = enableXFA; - unlockGlobalParams; - return enable; -} - GList *GlobalParams::getKeyBinding(int code, int mods, int context) { KeyBinding *binding; GList *cmds; @@ -3164,6 +3286,10 @@ GList *GlobalParams::getKeyBinding(int code, int mods, int context) { return cmds; } +GList *GlobalParams::getAllKeyBindings() { + return keyBindings; +} + int GlobalParams::getNumPopupMenuCmds() { int n; @@ -3214,7 +3340,7 @@ GString *GlobalParams::getDebugLogFile() { return debugLogFile; } -void GlobalParams::debugLogPrintf(char *fmt, ...) { +void GlobalParams::debugLogPrintf(const char *fmt, ...) { GString *path; FILE *f; GBool needClose; @@ -3620,12 +3746,6 @@ void GlobalParams::setMapExtTrueTypeFontsViaUnicode(GBool map) { unlockGlobalParams; } -void GlobalParams::setEnableXFA(GBool enable) { - lockGlobalParams; - enableXFA = enable; - unlockGlobalParams; -} - void GlobalParams::setTabStateFile(char *tabStateFileA) { lockGlobalParams; delete tabStateFile; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.h b/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.h index ed1fed3152d..2823ea59421 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.h @@ -134,50 +134,68 @@ public: ~KeyBinding(); }; -#define xpdfKeyCodeTab 0x1000 -#define xpdfKeyCodeReturn 0x1001 -#define xpdfKeyCodeEnter 0x1002 -#define xpdfKeyCodeBackspace 0x1003 -#define xpdfKeyCodeEsc 0x1004 -#define xpdfKeyCodeInsert 0x1005 -#define xpdfKeyCodeDelete 0x1006 -#define xpdfKeyCodeHome 0x1007 -#define xpdfKeyCodeEnd 0x1008 -#define xpdfKeyCodePgUp 0x1009 -#define xpdfKeyCodePgDn 0x100a -#define xpdfKeyCodeLeft 0x100b -#define xpdfKeyCodeRight 0x100c -#define xpdfKeyCodeUp 0x100d -#define xpdfKeyCodeDown 0x100e -#define xpdfKeyCodeF1 0x1100 -#define xpdfKeyCodeF35 0x1122 -#define xpdfKeyCodeMousePress1 0x2001 -#define xpdfKeyCodeMousePress2 0x2002 -#define xpdfKeyCodeMousePress3 0x2003 -#define xpdfKeyCodeMousePress4 0x2004 -#define xpdfKeyCodeMousePress5 0x2005 -#define xpdfKeyCodeMousePress6 0x2006 -#define xpdfKeyCodeMousePress7 0x2007 +#define xpdfKeyCodeTab 0x1000 +#define xpdfKeyCodeReturn 0x1001 +#define xpdfKeyCodeEnter 0x1002 +#define xpdfKeyCodeBackspace 0x1003 +#define xpdfKeyCodeEsc 0x1004 +#define xpdfKeyCodeInsert 0x1005 +#define xpdfKeyCodeDelete 0x1006 +#define xpdfKeyCodeHome 0x1007 +#define xpdfKeyCodeEnd 0x1008 +#define xpdfKeyCodePgUp 0x1009 +#define xpdfKeyCodePgDn 0x100a +#define xpdfKeyCodeLeft 0x100b +#define xpdfKeyCodeRight 0x100c +#define xpdfKeyCodeUp 0x100d +#define xpdfKeyCodeDown 0x100e +#define xpdfKeyCodeF1 0x1100 +#define xpdfKeyCodeF35 0x1122 +#define xpdfKeyCodeMousePress1 0x2001 +#define xpdfKeyCodeMousePress2 0x2002 +#define xpdfKeyCodeMousePress3 0x2003 +#define xpdfKeyCodeMousePress4 0x2004 +#define xpdfKeyCodeMousePress5 0x2005 +#define xpdfKeyCodeMousePress6 0x2006 +#define xpdfKeyCodeMousePress7 0x2007 // ... -#define xpdfKeyCodeMousePress32 0x2020 -#define xpdfKeyCodeMouseRelease1 0x2101 -#define xpdfKeyCodeMouseRelease2 0x2102 -#define xpdfKeyCodeMouseRelease3 0x2103 -#define xpdfKeyCodeMouseRelease4 0x2104 -#define xpdfKeyCodeMouseRelease5 0x2105 -#define xpdfKeyCodeMouseRelease6 0x2106 -#define xpdfKeyCodeMouseRelease7 0x2107 +#define xpdfKeyCodeMousePress32 0x2020 +#define xpdfKeyCodeMouseRelease1 0x2101 +#define xpdfKeyCodeMouseRelease2 0x2102 +#define xpdfKeyCodeMouseRelease3 0x2103 +#define xpdfKeyCodeMouseRelease4 0x2104 +#define xpdfKeyCodeMouseRelease5 0x2105 +#define xpdfKeyCodeMouseRelease6 0x2106 +#define xpdfKeyCodeMouseRelease7 0x2107 // ... -#define xpdfKeyCodeMouseRelease32 0x2120 -#define xpdfKeyCodeMouseClick1 0x2201 -#define xpdfKeyCodeMouseClick2 0x2202 -#define xpdfKeyCodeMouseClick3 0x2203 -#define xpdfKeyCodeMouseClick4 0x2204 -#define xpdfKeyCodeMouseClick5 0x2205 -#define xpdfKeyCodeMouseClick6 0x2206 -#define xpdfKeyCodeMouseClick7 0x2207 +#define xpdfKeyCodeMouseRelease32 0x2120 +#define xpdfKeyCodeMouseClick1 0x2201 +#define xpdfKeyCodeMouseClick2 0x2202 +#define xpdfKeyCodeMouseClick3 0x2203 +#define xpdfKeyCodeMouseClick4 0x2204 +#define xpdfKeyCodeMouseClick5 0x2205 +#define xpdfKeyCodeMouseClick6 0x2206 +#define xpdfKeyCodeMouseClick7 0x2207 // ... -#define xpdfKeyCodeMouseClick32 0x2220 +#define xpdfKeyCodeMouseClick32 0x2220 +#define xpdfKeyCodeMouseDoubleClick1 0x2301 +#define xpdfKeyCodeMouseDoubleClick2 0x2302 +#define xpdfKeyCodeMouseDoubleClick3 0x2303 +#define xpdfKeyCodeMouseDoubleClick4 0x2304 +#define xpdfKeyCodeMouseDoubleClick5 0x2305 +#define xpdfKeyCodeMouseDoubleClick6 0x2306 +#define xpdfKeyCodeMouseDoubleClick7 0x2307 +// ... +#define xpdfKeyCodeMouseDoubleClick32 0x2320 +#define xpdfKeyCodeMouseTripleClick1 0x2401 +#define xpdfKeyCodeMouseTripleClick2 0x2402 +#define xpdfKeyCodeMouseTripleClick3 0x2403 +#define xpdfKeyCodeMouseTripleClick4 0x2404 +#define xpdfKeyCodeMouseTripleClick5 0x2405 +#define xpdfKeyCodeMouseTripleClick6 0x2406 +#define xpdfKeyCodeMouseTripleClick7 0x2407 +// ... +#define xpdfKeyCodeMouseTripleClick32 0x2420 #define xpdfKeyModNone 0 #define xpdfKeyModShift (1 << 0) #define xpdfKeyModCtrl (1 << 1) @@ -279,6 +297,7 @@ public: GBool getPSAlwaysRasterize(); GBool getPSNeverRasterize(); GString *getTextEncodingName(); + GList *getAvailableTextEncodings(); EndOfLineKind getTextEOL(); GBool getTextPageBreaks(); GBool getTextKeepTinyChars(); @@ -287,6 +306,7 @@ public: GString *getInitialDisplayMode(); GBool getInitialToolbarState(); GBool getInitialSidebarState(); + int getInitialSidebarWidth(); GString *getInitialSelectMode(); int getMaxTileWidth(); int getMaxTileHeight(); @@ -312,6 +332,7 @@ public: GString *getPaperColor(); GString *getMatteColor(); GString *getFullScreenMatteColor(); + GString *getSelectionColor(); GBool getReverseVideoInvertImages(); GString *getLaunchCommand() { return launchCommand; } GString *getMovieCommand() { return movieCommand; } @@ -320,15 +341,15 @@ public: GBool getMapUnknownCharNames(); GBool getMapExtTrueTypeFontsViaUnicode(); GBool isDroppedFont(const char *fontName); - GBool getEnableXFA(); GList *getKeyBinding(int code, int mods, int context); + GList *getAllKeyBindings(); int getNumPopupMenuCmds(); PopupMenuCmd *getPopupMenuCmd(int idx); GString *getTabStateFile(); GBool getPrintCommands(); GBool getErrQuiet(); GString *getDebugLogFile(); - void debugLogPrintf(char *fmt, ...); + void debugLogPrintf(const char *fmt, ...); CharCodeToUnicode *getCIDToUnicode(GString *collection); CharCodeToUnicode *getUnicodeToUnicode(GString *fontName); @@ -378,7 +399,6 @@ public: void setMapNumericCharNames(GBool map); void setMapUnknownCharNames(GBool map); void setMapExtTrueTypeFontsViaUnicode(GBool map); - void setEnableXFA(GBool enable); void setTabStateFile(char *tabStateFileA); void setPrintCommands(GBool printCommandsA); void setErrQuiet(GBool errQuietA); @@ -392,8 +412,10 @@ public: private: + void setDataDirVar(); void createDefaultKeyBindings(); void parseFile(GString *fileName, FILE *f); + GList *parseLineTokens(char *buf, GString *fileName, int line); void parseNameToUnicode(GList *tokens, GString *fileName, int line); void parseCIDToUnicode(GList *tokens, GString *fileName, int line); void parseUnicodeToUnicode(GList *tokens, GString *fileName, int line); @@ -438,9 +460,14 @@ private: NameToCharCode * // mapping from char name to macRomanReverseMap; // MacRomanEncoding index - //----- user-modifiable settings + //----- meta settings GString *baseDir; // base directory - for plugins, etc. + GHash *configFileVars; // variables for use in the config file + // [GString] + + //----- user-modifiable settings + NameToCharCode * // mapping from char name to Unicode nameToUnicode; GHash *cidToUnicodes; // files for mappings from char collections @@ -522,6 +549,7 @@ private: // or closed (false) GBool initialSidebarState; // initial sidebar state - open (true) // or closed (false) + int initialSidebarWidth; // initial sidebar width GString *initialSelectMode; // initial selection mode (block or linear) int maxTileWidth; // maximum rasterization tile width int maxTileHeight; // maximum rasterization tile height @@ -548,6 +576,7 @@ private: GString *paperColor; // paper (page background) color GString *matteColor; // matte (background outside of page) color GString *fullScreenMatteColor; // matte color in full-screen mode + GString *selectionColor; // selection color GBool reverseVideoInvertImages; // invert images in reverse video mode GString *launchCommand; // command executed for 'launch' links GString *movieCommand; // command executed for movie annotations @@ -558,7 +587,6 @@ private: GBool mapExtTrueTypeFontsViaUnicode; // map char codes to GID via Unicode // for external TrueType fonts? GHash *droppedFonts; // dropped fonts [int] - GBool enableXFA; // enable XFA form rendering GList *keyBindings; // key & mouse button bindings [KeyBinding] GList *popupMenuCmds; // popup menu commands [PopupMenuCmd] GString *tabStateFile; // path for the tab state save file diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/HTMLGen.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/HTMLGen.cc index 036f1116c01..6f51ad85190 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/HTMLGen.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/HTMLGen.cc @@ -214,6 +214,7 @@ HTMLGen::HTMLGen(double backgroundResolutionA) { // set up the TextOutputDev textOutControl.mode = textOutReadingOrder; textOutControl.html = gTrue; + textOutControl.splitRotatedWords = gTrue; textOut = new TextOutputDev(NULL, &textOutControl, gFalse); if (!textOut->isOk()) { ok = gFalse; @@ -492,7 +493,7 @@ void HTMLGen::appendSpans(GList *words, int firstWordIdx, int lastWordIdx, (spanDir >= 0) ? wordIdx <= lastWordIdx : wordIdx >= lastWordIdx; wordIdx += spanDir) { word1 = (TextWord *)words->get(wordIdx); - invisible = allTextInvisible || word1->isInvisible(); + invisible = allTextInvisible || word1->isInvisible() || word1->isRotated(); if (!drawInvisibleText && invisible) { continue; } @@ -513,6 +514,7 @@ void HTMLGen::appendSpans(GList *words, int firstWordIdx, int lastWordIdx, word1->getFontInfo() != word0->getFontInfo() || word1->getFontSize() != word0->getFontSize() || word1->isInvisible() != word0->isInvisible() || + word1->isRotated() != word0->isRotated() || vertAlign1 != vertAlign0 || r1 != r0 || g1 != g0 || b1 != b0) { if (word0) { @@ -717,6 +719,7 @@ HTMLGenFontDefn *HTMLGen::getFontFile(TextFontInfo *font, getFontDetails(font, &family, &weight, &style, &scale); fontSpec = GString::format("font-family:ff{0:d},{1:s}; font-weight:{2:s}; font-style:{3:s};", nextFontFaceIdx, family, weight, style); + ++nextFontFaceIdx; fontDefn = new HTMLGenFontDefn(id, fontFace, fontSpec, 1.0); } delete fontPath; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.cc index 1fb32be0b58..5a694d7b665 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.cc @@ -29,7 +29,6 @@ ImageOutputDev::ImageOutputDev(char *fileRootA, GBool dumpJPEGA, GBool dumpRawA, GBool listA) { fileRoot = copyString(fileRootA); - fileName = (char *)gmalloc((int)strlen(fileRoot) + 30); dumpJPEG = dumpJPEGA; dumpRaw = dumpRawA; list = listA; @@ -39,7 +38,6 @@ ImageOutputDev::ImageOutputDev(char *fileRootA, GBool dumpJPEGA, } ImageOutputDev::~ImageOutputDev() { - gfree(fileName); gfree(fileRoot); } @@ -60,6 +58,7 @@ void ImageOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, GBool inlineImg, GBool interpolate) { + GString *fileName; FILE *f; char buf[4096]; int size, n, i; @@ -68,11 +67,12 @@ void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, if (dumpRaw && !inlineImg) { // open the image file - sprintf(fileName, "%s-%04d.%s", - fileRoot, imgNum, getRawFileExtension(str)); + fileName = GString::format("{0:s}-{1:04d}.{2:s}", + fileRoot, imgNum, getRawFileExtension(str)); ++imgNum; - if (!(f = fopen(fileName, "wb"))) { - error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); + if (!(f = openFile(fileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open image file '{0:t}'", fileName); + delete fileName; return; } @@ -92,10 +92,11 @@ void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, } else if (dumpJPEG && str->getKind() == strDCT && !inlineImg) { // open the image file - sprintf(fileName, "%s-%04d.jpg", fileRoot, imgNum); + fileName = GString::format("{0:s}-{1:04d}.jpg", fileRoot, imgNum); ++imgNum; - if (!(f = fopen(fileName, "wb"))) { - error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); + if (!(f = openFile(fileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open image file '{0:t}'", fileName); + delete fileName; return; } @@ -115,10 +116,11 @@ void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, } else { // open the image file and write the PBM header - sprintf(fileName, "%s-%04d.pbm", fileRoot, imgNum); + fileName = GString::format("{0:s}-{1:04d}.pbm", fileRoot, imgNum); ++imgNum; - if (!(f = fopen(fileName, "wb"))) { - error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); + if (!(f = openFile(fileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open image file '{0:t}'", fileName); + delete fileName; return; } fprintf(f, "P4\n"); @@ -144,8 +146,10 @@ void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, } if (list) { - writeImageInfo(width, height, state, NULL); + writeImageInfo(fileName, width, height, state, NULL); } + + delete fileName; } void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, @@ -154,6 +158,7 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int *maskColors, GBool inlineImg, GBool interpolate) { GfxColorSpaceMode csMode; + GString *fileName; FILE *f; ImageStream *imgStr; Guchar *p; @@ -173,11 +178,12 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, if (dumpRaw && !inlineImg) { // open the image file - sprintf(fileName, "%s-%04d.%s", - fileRoot, imgNum, getRawFileExtension(str)); + fileName = GString::format("{0:s}-{1:04d}.{2:s}", + fileRoot, imgNum, getRawFileExtension(str)); ++imgNum; - if (!(f = fopen(fileName, "wb"))) { - error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); + if (!(f = openFile(fileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open image file '{0:t}'", fileName); + delete fileName; return; } @@ -200,10 +206,11 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, !inlineImg) { // open the image file - sprintf(fileName, "%s-%04d.jpg", fileRoot, imgNum); + fileName = GString::format("{0:s}-{1:04d}.jpg", fileRoot, imgNum); ++imgNum; - if (!(f = fopen(fileName, "wb"))) { - error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); + if (!(f = openFile(fileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open image file '{0:t}'", fileName); + delete fileName; return; } @@ -224,10 +231,11 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, colorMap->getBits() == 1) { // open the image file and write the PBM header - sprintf(fileName, "%s-%04d.pbm", fileRoot, imgNum); + fileName = GString::format("{0:s}-{1:04d}.pbm", fileRoot, imgNum); ++imgNum; - if (!(f = fopen(fileName, "wb"))) { - error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); + if (!(f = openFile(fileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open image file '{0:t}'", fileName); + delete fileName; return; } fprintf(f, "P4\n"); @@ -259,10 +267,11 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, (csMode == csDeviceGray || csMode == csCalGray)) { // open the image file and write the PGM header - sprintf(fileName, "%s-%04d.pgm", fileRoot, imgNum); + fileName = GString::format("{0:s}-{1:04d}.pgm", fileRoot, imgNum); ++imgNum; - if (!(f = fopen(fileName, "wb"))) { - error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); + if (!(f = openFile(fileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open image file '{0:t}'", fileName); + delete fileName; return; } fprintf(f, "P5\n"); @@ -300,10 +309,11 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, } else { // open the image file and write the PPM header - sprintf(fileName, "%s-%04d.ppm", fileRoot, imgNum); + fileName = GString::format("{0:s}-{1:04d}.ppm", fileRoot, imgNum); ++imgNum; - if (!(f = fopen(fileName, "wb"))) { - error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); + if (!(f = openFile(fileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open image file '{0:t}'", fileName); + delete fileName; return; } fprintf(f, "P6\n"); @@ -343,19 +353,21 @@ void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, } if (list) { - writeImageInfo(width, height, state, colorMap); + writeImageInfo(fileName, width, height, state, colorMap); } + + delete fileName; } void ImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GBool maskInvert, GBool interpolate) { drawImage(state, ref, str, width, height, colorMap, NULL, gFalse, interpolate); - drawImageMask(state, ref, maskStr, maskWidth, maskHeight, maskInvert, + drawImageMask(state, maskRef, maskStr, maskWidth, maskHeight, maskInvert, gFalse, interpolate); } @@ -363,13 +375,13 @@ void ImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, double *matte, GBool interpolate) { drawImage(state, ref, str, width, height, colorMap, NULL, gFalse, interpolate); - drawImage(state, ref, maskStr, maskWidth, maskHeight, maskColorMap, + drawImage(state, maskRef, maskStr, maskWidth, maskHeight, maskColorMap, NULL, gFalse, interpolate); } @@ -401,7 +413,8 @@ const char *ImageOutputDev::getRawFileExtension(Stream *str) { } } -void ImageOutputDev::writeImageInfo(int width, int height, GfxState *state, +void ImageOutputDev::writeImageInfo(GString *fileName, + int width, int height, GfxState *state, GfxImageColorMap *colorMap) { const char *mode; double hdpi, vdpi, x0, y0, x1, y1; @@ -433,7 +446,7 @@ void ImageOutputDev::writeImageInfo(int width, int height, GfxState *state, } printf("%s: page=%d width=%d height=%d hdpi=%.2f vdpi=%.2f %s%s bpc=%d\n", - fileName, curPageNum, width, height, hdpi, vdpi, + fileName->getCString(), curPageNum, width, height, hdpi, vdpi, mode ? "colorspace=" : "mask", mode ? mode : "", bpc); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.h b/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.h index 3cdae45da66..5a72925aee9 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.h @@ -85,12 +85,13 @@ public: virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, int maskWidth, int maskHeight, + Object *maskRef, Stream *maskStr, + int maskWidth, int maskHeight, GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, double *matte, GBool interpolate); @@ -99,11 +100,11 @@ private: Stream *getRawStream(Stream *str); const char *getRawFileExtension(Stream *str); - void writeImageInfo(int width, int height, GfxState *state, + void writeImageInfo(GString *fileName, + int width, int height, GfxState *state, GfxImageColorMap *colorMap); char *fileRoot; // root of output file names - char *fileName; // buffer for output file names GBool dumpJPEG; // set to dump native JPEG files GBool dumpRaw; // set to dump raw PDF-native image files GBool list; // set to write image info to stdout diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.cc index fcb9d6d0e3a..4c4e5183026 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.cc @@ -1304,7 +1304,6 @@ void JBIG2Stream::readSegments() { Guint *refSegs; int c1, c2, c3; Guint i; - GBool done; done = gFalse; while (!done && readULong(&segNum)) { @@ -1723,17 +1722,18 @@ GBool JBIG2Stream::readSymbolDictSeg(Guint segNum, Guint length, "Bad delta-height value in JBIG2 symbol dictionary"); goto syntaxError; } - // sanity check to avoid extremely long run-times with damaged streams - if (dh > 1000000) { - error(errSyntaxError, getPos(), - "Bogus delta-height value in JBIG2 symbol dictionary"); - goto syntaxError; - } symHeight += dh; symWidth = 0; totalWidth = 0; j = i; + // sanity check to avoid extremely long run-times with damaged streams + if (symHeight > 100000) { + error(errSyntaxError, getPos(), + "Bogus symbol height value in JBIG2 symbol dictionary"); + goto syntaxError; + } + // read the symbols in this height class while (1) { @@ -1760,6 +1760,13 @@ GBool JBIG2Stream::readSymbolDictSeg(Guint segNum, Guint length, goto syntaxError; } + // sanity check to avoid extremely long run-times with damaged streams + if (symWidth > 100000) { + error(errSyntaxError, getPos(), + "Bogus symbol width value in JBIG2 symbol dictionary"); + goto syntaxError; + } + // using a collective bitmap, so don't read a bitmap here if (huff && !refAgg) { symWidths[i] = symWidth; @@ -1977,6 +1984,15 @@ void JBIG2Stream::readTextRegionSeg(Guint segNum, GBool imm, error(errSyntaxError, getPos(), "Bad size in JBIG2 text region segment"); return; } + // sanity check: if the w/h/x/y values are way out of range, it likely + // indicates a damaged JBIG2 stream + if (w / 10 > pageW || h / 10 > pageH || + x / 10 > pageW || y / 10 > pageH) { + error(errSyntaxError, getPos(), + "Bad size or position in JBIG2 text region segment"); + done = gTrue; + return; + } extCombOp = segInfoFlags & 7; // rest of the text region header @@ -2557,6 +2573,15 @@ void JBIG2Stream::readHalftoneRegionSeg(Guint segNum, GBool imm, !readUByte(&segInfoFlags)) { goto eofError; } + // sanity check: if the w/h/x/y values are way out of range, it likely + // indicates a damaged JBIG2 stream + if (w / 10 > pageW || h / 10 > pageH || + x / 10 > pageW || y / 10 > pageH) { + error(errSyntaxError, getPos(), + "Bad size or position in JBIG2 halftone region segment"); + done = gTrue; + return; + } extCombOp = segInfoFlags & 7; // rest of the halftone region header @@ -2720,6 +2745,15 @@ void JBIG2Stream::readGenericRegionSeg(Guint segNum, GBool imm, "Bad bitmap size in JBIG2 generic region segment"); return; } + // sanity check: if the w/h/x/y values are way out of range, it likely + // indicates a damaged JBIG2 stream + if (w / 10 > pageW || h / 10 > pageH || + x / 10 > pageW || y / 10 > pageH) { + error(errSyntaxError, getPos(), + "Bad size or position in JBIG2 generic region segment"); + done = gTrue; + return; + } extCombOp = segInfoFlags & 7; // rest of the generic region segment header @@ -3623,6 +3657,15 @@ void JBIG2Stream::readGenericRefinementRegionSeg(Guint segNum, GBool imm, "Bad size in JBIG2 generic refinement region segment"); return; } + // sanity check: if the w/h/x/y values are way out of range, it likely + // indicates a damaged JBIG2 stream + if (w / 10 > pageW || h / 10 > pageH || + x / 10 > pageW || y / 10 > pageH) { + error(errSyntaxError, getPos(), + "Bad size or position in JBIG2 generic refinement region segment"); + done = gTrue; + return; + } extCombOp = segInfoFlags & 7; // rest of the generic refinement region segment header diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.h b/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.h index 7aa4d9d8edb..cca722b4bfc 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.h @@ -128,6 +128,7 @@ private: Guchar *dataPtr; Guchar *dataEnd; Guint byteCounter; + GBool done; JArithmeticDecoder *arithDecoder; JArithmeticDecoderStats *genericRegionStats; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.cc index a5dd6f0db0e..d9c295bb12c 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.cc @@ -20,7 +20,6 @@ #include "JPXStream.h" //~ to do: -// - precincts // - ROI // - progression order changes // - packed packet headers @@ -28,7 +27,6 @@ // - make sure all needed JP2/JPX subboxes are parsed (readBoxes) // - can we assume that QCC segments must come after the QCD segment? // - handle tilePartToEOC in readTilePartData -// - progression orders 2, 3, and 4 // - in coefficient decoding (readCodeBlockData): // - selective arithmetic coding bypass // (this also affects reading the cb->dataLen array) @@ -325,7 +323,7 @@ void JPXStream::close() { for (r = 0; r <= tileComp->nDecompLevels; ++r) { resLevel = &tileComp->resLevels[r]; if (resLevel->precincts) { - for (pre = 0; pre < 1; ++pre) { + for (pre = 0; pre < resLevel->nPrecincts; ++pre) { precinct = &resLevel->precincts[pre]; if (precinct->subbands) { for (sb = 0; sb < (Guint)(r == 0 ? 1 : 3); ++sb) { @@ -350,13 +348,13 @@ void JPXStream::close() { gfree(precinct->subbands); } } - gfree(img.tiles[i].tileComps[comp].resLevels[r].precincts); + gfree(resLevel->precincts); } } - gfree(img.tiles[i].tileComps[comp].resLevels); + gfree(tileComp->resLevels); } } - gfree(img.tiles[i].tileComps); + gfree(tile->tileComps); } } gfree(img.tiles); @@ -1036,13 +1034,6 @@ JPXDecodeResult JPXStream::readCodestream(Guint len) { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); return jpxDecodeFatalError; } -#if 1 //~ progression orders 2-4 are unimplemented - if (progOrder >= 2) { - error(errUnimplemented, -1, - "JPX progression order {0:d} is unimplemented", - progOrder); - } -#endif codeBlockW += 2; codeBlockH += 2; for (i = 0; i < img.nXTiles * img.nYTiles; ++i) { @@ -1445,7 +1436,8 @@ JPXDecodeResult JPXStream::readCodestream(Guint len) { ok = gTrue; while (1) { if (!readTilePart()) { - return jpxDecodeFatalError; + ok = gFalse; + break; } if (!readMarkerHdr(&segType, &segLen)) { error(errSyntaxError, getPos(), "Error in JPX codestream"); @@ -1497,8 +1489,11 @@ GBool JPXStream::readTilePart() { Guint style, progOrder, nLayers, multiComp, nDecompLevels; Guint codeBlockW, codeBlockH, codeBlockStyle, transform; Guint precinctSize, qStyle; - Guint n, nSBs, nx, ny, sbx0, sby0, comp, segLen; - Guint i, j, k, cbX, cbY, r, pre, sb, cbi, cbj; + Guint px0, py0, px1, py1; + Guint preCol0, preCol1, preRow0, preRow1, preCol, preRow; + Guint cbCol0, cbCol1, cbRow0, cbRow1, cbX, cbY; + Guint n, nSBs, nx, ny, comp, segLen; + Guint i, j, k, r, pre, sb, cbi, cbj; int segType, level; // process the SOT marker segment @@ -1534,6 +1529,10 @@ GBool JPXStream::readTilePart() { switch (segType) { case 0x52: // COD - coding style default cover(34); + if (tilePartIdx != 0) { + error(errSyntaxError, getPos(), "Extraneous JPX COD marker segment"); + return gFalse; + } if (!readUByte(&style) || !readUByte(&progOrder) || !readUWord(&nLayers) || @@ -1553,13 +1552,6 @@ GBool JPXStream::readTilePart() { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); return gFalse; } -#if 1 //~ progression orders 2-4 are unimplemented - if (img.tiles[tileIdx].progOrder >= 2) { - error(errUnimplemented, -1, - "JPX progression order {0:d} is unimplemented", - img.tiles[tileIdx].progOrder); - } -#endif codeBlockW += 2; codeBlockH += 2; img.tiles[tileIdx].progOrder = progOrder; @@ -1607,6 +1599,10 @@ GBool JPXStream::readTilePart() { break; case 0x53: // COC - coding style component cover(35); + if (tilePartIdx != 0) { + error(errSyntaxError, getPos(), "Extraneous JPX COC marker segment"); + return gFalse; + } if ((img.nComps > 256 && !readUWord(&comp)) || (img.nComps <= 256 && !readUByte(&comp)) || comp >= img.nComps || @@ -1623,7 +1619,7 @@ GBool JPXStream::readTilePart() { nDecompLevels > 31 || codeBlockW > 8 || codeBlockH > 8) { - error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); + error(errSyntaxError, getPos(), "Error in JPX COC marker segment"); return gFalse; } img.tiles[tileIdx].tileComps[comp].style = @@ -1659,6 +1655,10 @@ GBool JPXStream::readTilePart() { break; case 0x5c: // QCD - quantization default cover(36); + if (tilePartIdx != 0) { + error(errSyntaxError, getPos(), "Extraneous JPX QCD marker segment"); + return gFalse; + } if (!readUByte(&img.tiles[tileIdx].tileComps[0].quantStyle)) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); return gFalse; @@ -1726,6 +1726,10 @@ GBool JPXStream::readTilePart() { break; case 0x5d: // QCC - quantization component cover(37); + if (tilePartIdx != 0) { + error(errSyntaxError, getPos(), "Extraneous JPX QCC marker segment"); + return gFalse; + } if ((img.nComps > 256 && !readUWord(&comp)) || (img.nComps <= 256 && !readUByte(&comp)) || comp >= img.nComps || @@ -1786,6 +1790,10 @@ GBool JPXStream::readTilePart() { break; case 0x5e: // RGN - region of interest cover(38); + if (tilePartIdx != 0) { + error(errSyntaxError, getPos(), "Extraneous JPX RGN marker segment"); + return gFalse; + } #if 1 //~ ROI is unimplemented error(errUnimplemented, -1, "Got a JPX RGN segment"); if (segLen > 2 && @@ -1889,7 +1897,6 @@ GBool JPXStream::readTilePart() { //----- initialize the tile, precincts, and code-blocks if (tilePartIdx == 0) { tile = &img.tiles[tileIdx]; - tile->init = gTrue; i = tileIdx / img.nXTiles; j = tileIdx % img.nXTiles; if ((tile->x0 = img.xTileOffset + j * img.xTileSize) < img.xOffset) { @@ -1919,8 +1926,6 @@ GBool JPXStream::readTilePart() { tileComp->y0 = jpxCeilDiv(tile->y0, tileComp->vSep); tileComp->x1 = jpxCeilDiv(tile->x1, tileComp->hSep); tileComp->y1 = jpxCeilDiv(tile->y1, tileComp->vSep); - tileComp->cbW = 1 << tileComp->codeBlockW; - tileComp->cbH = 1 << tileComp->codeBlockH; tileComp->w = jpxCeilDivPow2(tileComp->x1, reduction) - jpxCeilDivPow2(tileComp->x0, reduction); tileComp->h = jpxCeilDivPow2(tileComp->y1, reduction) @@ -1939,12 +1944,26 @@ GBool JPXStream::readTilePart() { tileComp->buf = (int *)gmallocn(n + 8, sizeof(int)); for (r = 0; r <= tileComp->nDecompLevels; ++r) { resLevel = &tileComp->resLevels[r]; - k = r == 0 ? tileComp->nDecompLevels - : tileComp->nDecompLevels - r + 1; - resLevel->x0 = jpxCeilDivPow2(tileComp->x0, k); - resLevel->y0 = jpxCeilDivPow2(tileComp->y0, k); - resLevel->x1 = jpxCeilDivPow2(tileComp->x1, k); - resLevel->y1 = jpxCeilDivPow2(tileComp->y1, k); + resLevel->x0 = jpxCeilDivPow2(tileComp->x0, + tileComp->nDecompLevels - r); + resLevel->y0 = jpxCeilDivPow2(tileComp->y0, + tileComp->nDecompLevels - r); + resLevel->x1 = jpxCeilDivPow2(tileComp->x1, + tileComp->nDecompLevels - r); + resLevel->y1 = jpxCeilDivPow2(tileComp->y1, + tileComp->nDecompLevels - r); + resLevel->codeBlockW = r == 0 ? resLevel->precinctWidth + : resLevel->precinctWidth - 1; + if (resLevel->codeBlockW > tileComp->codeBlockW) { + resLevel->codeBlockW = tileComp->codeBlockW; + } + resLevel->cbW = 1 << resLevel->codeBlockW; + resLevel->codeBlockH = r == 0 ? resLevel->precinctHeight + : resLevel->precinctHeight - 1; + if (resLevel->codeBlockH > tileComp->codeBlockH) { + resLevel->codeBlockH = tileComp->codeBlockH; + } + resLevel->cbH = 1 << resLevel->codeBlockH; // the JPEG 2000 spec says that packets for empty res levels // should all be present in the codestream (B.6, B.9, B.10), // but it appears that encoders drop packets if the res level @@ -1952,6 +1971,7 @@ GBool JPXStream::readTilePart() { resLevel->empty = resLevel->x0 == resLevel->x1 || resLevel->y0 == resLevel->y1; if (r == 0) { + nSBs = 1; resLevel->bx0[0] = resLevel->x0; resLevel->by0[0] = resLevel->y0; resLevel->bx1[0] = resLevel->x1; @@ -1960,18 +1980,19 @@ GBool JPXStream::readTilePart() { (resLevel->bx0[0] == resLevel->bx1[0] || resLevel->by0[0] == resLevel->by1[0]); } else { - resLevel->bx0[0] = jpxCeilDivPow2(tileComp->x0 - (1 << (k-1)), k); - resLevel->by0[0] = resLevel->y0; - resLevel->bx1[0] = jpxCeilDivPow2(tileComp->x1 - (1 << (k-1)), k); - resLevel->by1[0] = resLevel->y1; - resLevel->bx0[1] = resLevel->x0; - resLevel->by0[1] = jpxCeilDivPow2(tileComp->y0 - (1 << (k-1)), k); - resLevel->bx1[1] = resLevel->x1; - resLevel->by1[1] = jpxCeilDivPow2(tileComp->y1 - (1 << (k-1)), k); - resLevel->bx0[2] = jpxCeilDivPow2(tileComp->x0 - (1 << (k-1)), k); - resLevel->by0[2] = jpxCeilDivPow2(tileComp->y0 - (1 << (k-1)), k); - resLevel->bx1[2] = jpxCeilDivPow2(tileComp->x1 - (1 << (k-1)), k); - resLevel->by1[2] = jpxCeilDivPow2(tileComp->y1 - (1 << (k-1)), k); + nSBs = 3; + resLevel->bx0[0] = jpxCeilDivPow2(resLevel->x0 - 1, 1); + resLevel->by0[0] = jpxCeilDivPow2(resLevel->y0, 1); + resLevel->bx1[0] = jpxCeilDivPow2(resLevel->x1 - 1, 1); + resLevel->by1[0] = jpxCeilDivPow2(resLevel->y1, 1); + resLevel->bx0[1] = jpxCeilDivPow2(resLevel->x0, 1); + resLevel->by0[1] = jpxCeilDivPow2(resLevel->y0 - 1, 1); + resLevel->bx1[1] = jpxCeilDivPow2(resLevel->x1, 1); + resLevel->by1[1] = jpxCeilDivPow2(resLevel->y1 - 1, 1); + resLevel->bx0[2] = jpxCeilDivPow2(resLevel->x0 - 1, 1); + resLevel->by0[2] = jpxCeilDivPow2(resLevel->y0 - 1, 1); + resLevel->bx1[2] = jpxCeilDivPow2(resLevel->x1 - 1, 1); + resLevel->by1[2] = jpxCeilDivPow2(resLevel->y1 - 1, 1); resLevel->empty = resLevel->empty && (resLevel->bx0[0] == resLevel->bx1[0] || resLevel->by0[0] == resLevel->by1[0]) && @@ -1980,135 +2001,155 @@ GBool JPXStream::readTilePart() { (resLevel->bx0[2] == resLevel->bx1[2] || resLevel->by0[2] == resLevel->by1[2]); } - resLevel->precincts = (JPXPrecinct *)gmallocn(1, sizeof(JPXPrecinct)); - for (pre = 0; pre < 1; ++pre) { + preCol0 = jpxFloorDivPow2(resLevel->x0, resLevel->precinctWidth); + preCol1 = jpxCeilDivPow2(resLevel->x1, resLevel->precinctWidth); + preRow0 = jpxFloorDivPow2(resLevel->y0, resLevel->precinctHeight); + preRow1 = jpxCeilDivPow2(resLevel->y1, resLevel->precinctHeight); + resLevel->nPrecincts = (preCol1 - preCol0) * (preRow1 - preRow0); + resLevel->precincts = (JPXPrecinct *)gmallocn(resLevel->nPrecincts, + sizeof(JPXPrecinct)); + for (pre = 0; pre < resLevel->nPrecincts; ++pre) { resLevel->precincts[pre].subbands = NULL; } - for (pre = 0; pre < 1; ++pre) { - precinct = &resLevel->precincts[pre]; - precinct->x0 = resLevel->x0; - precinct->y0 = resLevel->y0; - precinct->x1 = resLevel->x1; - precinct->y1 = resLevel->y1; - nSBs = r == 0 ? 1 : 3; - precinct->subbands = - (JPXSubband *)gmallocn(nSBs, sizeof(JPXSubband)); - for (sb = 0; sb < nSBs; ++sb) { - precinct->subbands[sb].inclusion = NULL; - precinct->subbands[sb].zeroBitPlane = NULL; - precinct->subbands[sb].cbs = NULL; - } - for (sb = 0; sb < nSBs; ++sb) { - subband = &precinct->subbands[sb]; - subband->x0 = resLevel->bx0[sb]; - subband->y0 = resLevel->by0[sb]; - subband->x1 = resLevel->bx1[sb]; - subband->y1 = resLevel->by1[sb]; - subband->nXCBs = jpxCeilDivPow2(subband->x1, - tileComp->codeBlockW) - - jpxFloorDivPow2(subband->x0, - tileComp->codeBlockW); - subband->nYCBs = jpxCeilDivPow2(subband->y1, - tileComp->codeBlockH) - - jpxFloorDivPow2(subband->y0, - tileComp->codeBlockH); - n = subband->nXCBs > subband->nYCBs ? subband->nXCBs - : subband->nYCBs; - for (subband->maxTTLevel = 0, --n; - n; - ++subband->maxTTLevel, n >>= 1) ; - n = 0; - for (level = subband->maxTTLevel; level >= 0; --level) { - nx = jpxCeilDivPow2(subband->nXCBs, level); - ny = jpxCeilDivPow2(subband->nYCBs, level); - n += nx * ny; + precinct = resLevel->precincts; + for (preRow = preRow0; preRow < preRow1; ++preRow) { + for (preCol = preCol0; preCol < preCol1; ++preCol) { + precinct->subbands = + (JPXSubband *)gmallocn(nSBs, sizeof(JPXSubband)); + for (sb = 0; sb < nSBs; ++sb) { + precinct->subbands[sb].inclusion = NULL; + precinct->subbands[sb].zeroBitPlane = NULL; + precinct->subbands[sb].cbs = NULL; } - subband->inclusion = - (JPXTagTreeNode *)gmallocn(n, sizeof(JPXTagTreeNode)); - subband->zeroBitPlane = - (JPXTagTreeNode *)gmallocn(n, sizeof(JPXTagTreeNode)); - for (k = 0; k < n; ++k) { - subband->inclusion[k].finished = gFalse; - subband->inclusion[k].val = 0; - subband->zeroBitPlane[k].finished = gFalse; - subband->zeroBitPlane[k].val = 0; - } - subband->cbs = (JPXCodeBlock *)gmallocn(subband->nXCBs * - subband->nYCBs, - sizeof(JPXCodeBlock)); - for (k = 0; k < subband->nXCBs * subband->nYCBs; ++k) { - subband->cbs[k].dataLen = NULL; - subband->cbs[k].touched = NULL; - subband->cbs[k].arithDecoder = NULL; - subband->cbs[k].stats = NULL; - } - sbx0 = jpxFloorDivPow2(subband->x0, tileComp->codeBlockW); - sby0 = jpxFloorDivPow2(subband->y0, tileComp->codeBlockH); - if (r == 0) { // (NL)LL - sbCoeffs = tileComp->data; - } else if (sb == 0) { // (NL-r+1)HL - sbCoeffs = tileComp->data - + resLevel->bx1[1] - resLevel->bx0[1]; - } else if (sb == 1) { // (NL-r+1)LH - sbCoeffs = tileComp->data - + (resLevel->by1[0] - resLevel->by0[0]) * tileComp->w; - } else { // (NL-r+1)HH - sbCoeffs = tileComp->data - + (resLevel->by1[0] - resLevel->by0[0]) * tileComp->w - + resLevel->bx1[1] - resLevel->bx0[1]; - } - cb = subband->cbs; - for (cbY = 0; cbY < subband->nYCBs; ++cbY) { - for (cbX = 0; cbX < subband->nXCBs; ++cbX) { - cb->x0 = (sbx0 + cbX) << tileComp->codeBlockW; - cb->x1 = cb->x0 + tileComp->cbW; - if (subband->x0 > cb->x0) { - cb->x0 = subband->x0; - } - if (subband->x1 < cb->x1) { - cb->x1 = subband->x1; - } - cb->y0 = (sby0 + cbY) << tileComp->codeBlockH; - cb->y1 = cb->y0 + tileComp->cbH; - if (subband->y0 > cb->y0) { - cb->y0 = subband->y0; - } - if (subband->y1 < cb->y1) { - cb->y1 = subband->y1; - } - cb->seen = gFalse; - cb->lBlock = 3; - cb->nextPass = jpxPassCleanup; - cb->nZeroBitPlanes = 0; - cb->dataLenSize = 1; - cb->dataLen = (Guint *)gmalloc(sizeof(Guint)); - if (r <= tileComp->nDecompLevels - reduction) { - cb->coeffs = sbCoeffs - + (cb->y0 - subband->y0) * tileComp->w - + (cb->x0 - subband->x0); - cb->touched = (char *)gmalloc(1 << (tileComp->codeBlockW - + tileComp->codeBlockH)); - cb->len = 0; - for (cbj = 0; cbj < cb->y1 - cb->y0; ++cbj) { - for (cbi = 0; cbi < cb->x1 - cb->x0; ++cbi) { - cb->coeffs[cbj * tileComp->w + cbi] = 0; + for (sb = 0; sb < nSBs; ++sb) { + subband = &precinct->subbands[sb]; + if (r == 0) { + px0 = preCol << resLevel->precinctWidth; + px1 = (preCol + 1) << resLevel->precinctWidth; + py0 = preRow << resLevel->precinctHeight; + py1 = (preRow + 1) << resLevel->precinctHeight; + } else { + px0 = preCol << (resLevel->precinctWidth - 1); + px1 = (preCol + 1) << (resLevel->precinctWidth - 1); + py0 = preRow << (resLevel->precinctHeight - 1); + py1 = (preRow + 1) << (resLevel->precinctHeight - 1); + } + if (px0 < resLevel->bx0[sb]) { + px0 = resLevel->bx0[sb]; + } + if (px1 > resLevel->bx1[sb]) { + px1 = resLevel->bx1[sb]; + } + if (py0 < resLevel->by0[sb]) { + py0 = resLevel->by0[sb]; + } + if (py1 > resLevel->by1[sb]) { + py1 = resLevel->by1[sb]; + } + if (r == 0) { // (NL)LL + sbCoeffs = tileComp->data; + } else if (sb == 0) { // (NL-r+1)HL + sbCoeffs = tileComp->data + + resLevel->bx1[1] - resLevel->bx0[1]; + } else if (sb == 1) { // (NL-r+1)LH + sbCoeffs = tileComp->data + + (resLevel->by1[0] - resLevel->by0[0]) * tileComp->w; + } else { // (NL-r+1)HH + sbCoeffs = tileComp->data + + (resLevel->by1[0] - resLevel->by0[0]) * tileComp->w + + (resLevel->bx1[1] - resLevel->bx0[1]); + } + cbCol0 = jpxFloorDivPow2(px0, resLevel->codeBlockW); + cbCol1 = jpxCeilDivPow2(px1, resLevel->codeBlockW); + cbRow0 = jpxFloorDivPow2(py0, resLevel->codeBlockH); + cbRow1 = jpxCeilDivPow2(py1, resLevel->codeBlockH); + subband->nXCBs = cbCol1 - cbCol0; + subband->nYCBs = cbRow1 - cbRow0; + n = subband->nXCBs > subband->nYCBs ? subband->nXCBs + : subband->nYCBs; + for (subband->maxTTLevel = 0, --n; + n; + ++subband->maxTTLevel, n >>= 1) ; + n = 0; + for (level = subband->maxTTLevel; level >= 0; --level) { + nx = jpxCeilDivPow2(subband->nXCBs, level); + ny = jpxCeilDivPow2(subband->nYCBs, level); + n += nx * ny; + } + subband->inclusion = + (JPXTagTreeNode *)gmallocn(n, sizeof(JPXTagTreeNode)); + subband->zeroBitPlane = + (JPXTagTreeNode *)gmallocn(n, sizeof(JPXTagTreeNode)); + for (k = 0; k < n; ++k) { + subband->inclusion[k].finished = gFalse; + subband->inclusion[k].val = 0; + subband->zeroBitPlane[k].finished = gFalse; + subband->zeroBitPlane[k].val = 0; + } + subband->cbs = (JPXCodeBlock *)gmallocn(subband->nXCBs * + subband->nYCBs, + sizeof(JPXCodeBlock)); + for (k = 0; k < subband->nXCBs * subband->nYCBs; ++k) { + subband->cbs[k].dataLen = NULL; + subband->cbs[k].touched = NULL; + subband->cbs[k].arithDecoder = NULL; + subband->cbs[k].stats = NULL; + } + cb = subband->cbs; + for (cbY = cbRow0; cbY < cbRow1; ++cbY) { + for (cbX = cbCol0; cbX < cbCol1; ++cbX) { + cb->x0 = cbX << resLevel->codeBlockW; + cb->x1 = cb->x0 + resLevel->cbW; + if (cb->x0 < px0) { + cb->x0 = px0; + } + if (cb->x1 > px1) { + cb->x1 = px1; + } + cb->y0 = cbY << resLevel->codeBlockH; + cb->y1 = cb->y0 + resLevel->cbH; + if (cb->y0 < py0) { + cb->y0 = py0; + } + if (cb->y1 > py1) { + cb->y1 = py1; + } + cb->seen = gFalse; + cb->lBlock = 3; + cb->nextPass = jpxPassCleanup; + cb->nZeroBitPlanes = 0; + cb->dataLenSize = 1; + cb->dataLen = (Guint *)gmalloc(sizeof(Guint)); + if (r <= tileComp->nDecompLevels - reduction) { + cb->coeffs = sbCoeffs + + (cb->y0 - resLevel->by0[sb]) * tileComp->w + + (cb->x0 - resLevel->bx0[sb]); + cb->touched = (char *)gmalloc(1 << (resLevel->codeBlockW + + resLevel->codeBlockH)); + cb->len = 0; + for (cbj = 0; cbj < cb->y1 - cb->y0; ++cbj) { + for (cbi = 0; cbi < cb->x1 - cb->x0; ++cbi) { + cb->coeffs[cbj * tileComp->w + cbi] = 0; + } } + memset(cb->touched, 0, + ((size_t)1 << (resLevel->codeBlockW + + resLevel->codeBlockH))); + } else { + cb->coeffs = NULL; + cb->touched = NULL; + cb->len = 0; } - memset(cb->touched, 0, - ((size_t)1 << (tileComp->codeBlockW - + tileComp->codeBlockH))); - } else { - cb->coeffs = NULL; - cb->touched = NULL; - cb->len = 0; + ++cb; } - ++cb; } } + ++precinct; } } } } + tile->init = gTrue; } return readTilePartData(tileIdx, tilePartLen, tilePartToEOC); @@ -2384,68 +2425,87 @@ GBool JPXStream::readTilePartData(Guint tileIdx, switch (tile->progOrder) { case 0: // layer, resolution level, component, precinct cover(58); - if (++tile->comp == img.nComps) { - tile->comp = 0; - if (++tile->res == tile->maxNDecompLevels + 1) { - tile->res = 0; - if (++tile->layer == tile->nLayers) { - tile->layer = 0; - tile->done = gTrue; + resLevel = &tile->tileComps[tile->comp].resLevels[tile->res]; + if (++tile->precinct == resLevel->nPrecincts) { + tile->precinct = 0; + if (++tile->comp == img.nComps) { + tile->comp = 0; + if (++tile->res == tile->maxNDecompLevels + 1) { + tile->res = 0; + if (++tile->layer == tile->nLayers) { + tile->layer = 0; + tile->done = gTrue; + } } } } break; case 1: // resolution level, layer, component, precinct cover(59); - if (++tile->comp == img.nComps) { - tile->comp = 0; - if (++tile->layer == tile->nLayers) { - tile->layer = 0; - if (++tile->res == tile->maxNDecompLevels + 1) { - tile->res = 0; - tile->done = gTrue; + resLevel = &tile->tileComps[tile->comp].resLevels[tile->res]; + if (++tile->precinct == resLevel->nPrecincts) { + tile->precinct = 0; + if (++tile->comp == img.nComps) { + tile->comp = 0; + if (++tile->layer == tile->nLayers) { + tile->layer = 0; + if (++tile->res == tile->maxNDecompLevels + 1) { + tile->res = 0; + tile->done = gTrue; + } } } } break; case 2: // resolution level, precinct, component, layer - //~ this isn't correct -- see B.12.1.3 cover(60); + //~ this is incorrect if there are subsampled components (?) if (++tile->layer == tile->nLayers) { tile->layer = 0; if (++tile->comp == img.nComps) { tile->comp = 0; - if (++tile->res == tile->maxNDecompLevels + 1) { - tile->res = 0; - tile->done = gTrue; + resLevel = &tile->tileComps[tile->comp].resLevels[tile->res]; + if (++tile->precinct == resLevel->nPrecincts) { + tile->precinct = 0; + if (++tile->res == tile->maxNDecompLevels + 1) { + tile->res = 0; + tile->done = gTrue; + } } } } break; case 3: // precinct, component, resolution level, layer - //~ this isn't correct -- see B.12.1.4 cover(61); + //~ this is incorrect if there are subsampled components (?) if (++tile->layer == tile->nLayers) { tile->layer = 0; if (++tile->res == tile->maxNDecompLevels + 1) { tile->res = 0; if (++tile->comp == img.nComps) { tile->comp = 0; - tile->done = gTrue; + resLevel = &tile->tileComps[tile->comp].resLevels[tile->res]; + if (++tile->precinct == resLevel->nPrecincts) { + tile->precinct = 0; + tile->done = gTrue; + } } } } break; case 4: // component, precinct, resolution level, layer - //~ this isn't correct -- see B.12.1.5 cover(62); if (++tile->layer == tile->nLayers) { tile->layer = 0; if (++tile->res == tile->maxNDecompLevels + 1) { tile->res = 0; - if (++tile->comp == img.nComps) { - tile->comp = 0; - tile->done = gTrue; + resLevel = &tile->tileComps[tile->comp].resLevels[tile->res]; + if (++tile->precinct == resLevel->nPrecincts) { + tile->precinct = 0; + if (++tile->comp == img.nComps) { + tile->comp = 0; + tile->done = gTrue; + } } } } @@ -2515,13 +2575,13 @@ GBool JPXStream::readCodeBlockData(JPXTileComp *tileComp, for (y0 = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched; y0 < cb->y1; y0 += 4, coeff0 += 4 * tileComp->w, - touched0 += 4 << tileComp->codeBlockW) { + touched0 += 4 << resLevel->codeBlockW) { for (x = cb->x0, coeff1 = coeff0, touched1 = touched0; x < cb->x1; ++x, ++coeff1, ++touched1) { for (y1 = 0, coeff = coeff1, touched = touched1; y1 < 4 && y0+y1 < cb->y1; - ++y1, coeff += tileComp->w, touched += tileComp->cbW) { + ++y1, coeff += tileComp->w, touched += resLevel->cbW) { if (!*coeff) { horiz = vert = diag = 0; horizSign = vertSign = 2; @@ -2590,13 +2650,13 @@ GBool JPXStream::readCodeBlockData(JPXTileComp *tileComp, for (y0 = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched; y0 < cb->y1; y0 += 4, coeff0 += 4 * tileComp->w, - touched0 += 4 << tileComp->codeBlockW) { + touched0 += 4 << resLevel->codeBlockW) { for (x = cb->x0, coeff1 = coeff0, touched1 = touched0; x < cb->x1; ++x, ++coeff1, ++touched1) { for (y1 = 0, coeff = coeff1, touched = touched1; y1 < 4 && y0+y1 < cb->y1; - ++y1, coeff += tileComp->w, touched += tileComp->cbW) { + ++y1, coeff += tileComp->w, touched += resLevel->cbW) { if (*coeff && !*touched) { if (*coeff == 1 || *coeff == -1) { all = 0; @@ -2651,16 +2711,16 @@ GBool JPXStream::readCodeBlockData(JPXTileComp *tileComp, for (y0 = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched; y0 < cb->y1; y0 += 4, coeff0 += 4 * tileComp->w, - touched0 += 4 << tileComp->codeBlockW) { + touched0 += 4 << resLevel->codeBlockW) { for (x = cb->x0, coeff1 = coeff0, touched1 = touched0; x < cb->x1; ++x, ++coeff1, ++touched1) { y1 = 0; if (y0 + 3 < cb->y1 && !(*touched1) && - !(touched1[tileComp->cbW]) && - !(touched1[2 * tileComp->cbW]) && - !(touched1[3 * tileComp->cbW]) && + !(touched1[resLevel->cbW]) && + !(touched1[2 * resLevel->cbW]) && + !(touched1[3 * resLevel->cbW]) && (x == cb->x0 || y0 == cb->y0 || !coeff1[-(int)tileComp->w - 1]) && (y0 == cb->y0 || @@ -2702,9 +2762,9 @@ GBool JPXStream::readCodeBlockData(JPXTileComp *tileComp, } } for (coeff = &coeff1[y1 * tileComp->w], - touched = &touched1[y1 << tileComp->codeBlockW]; + touched = &touched1[y1 << resLevel->codeBlockW]; y1 < 4 && y0 + y1 < cb->y1; - ++y1, coeff += tileComp->w, touched += tileComp->cbW) { + ++y1, coeff += tileComp->w, touched += resLevel->cbW) { if (!*touched) { horiz = vert = diag = 0; horizSign = vertSign = 2; @@ -2814,15 +2874,13 @@ void JPXStream::inverseTransform(JPXTileComp *tileComp) { int shift2; double mu; int val; - Guint r, cbX, cbY, x, y; + Guint r, pre, cbX, cbY, x, y; cover(68); //----- (NL)LL subband (resolution level 0) resLevel = &tileComp->resLevels[0]; - precinct = &resLevel->precincts[0]; - subband = &precinct->subbands[0]; // i-quant parameters qStyle = tileComp->quantStyle & 0x1f; @@ -2843,44 +2901,48 @@ void JPXStream::inverseTransform(JPXTileComp *tileComp) { } // do fixed point adjustment and dequantization on (NL)LL - cb = subband->cbs; - for (cbY = 0; cbY < subband->nYCBs; ++cbY) { - for (cbX = 0; cbX < subband->nXCBs; ++cbX) { - for (y = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched; - y < cb->y1; - ++y, coeff0 += tileComp->w, touched0 += tileComp->cbW) { - for (x = cb->x0, coeff = coeff0, touched = touched0; - x < cb->x1; - ++x, ++coeff, ++touched) { - val = *coeff; - if (val != 0) { - shift2 = shift - (cb->nZeroBitPlanes + cb->len + *touched); - if (shift2 > 0) { - cover(94); - if (val < 0) { - val = (val << shift2) - (1 << (shift2 - 1)); + for (pre = 0; pre < resLevel->nPrecincts; ++pre) { + precinct = &resLevel->precincts[pre]; + subband = &precinct->subbands[0]; + cb = subband->cbs; + for (cbY = 0; cbY < subband->nYCBs; ++cbY) { + for (cbX = 0; cbX < subband->nXCBs; ++cbX) { + for (y = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched; + y < cb->y1; + ++y, coeff0 += tileComp->w, touched0 += resLevel->cbW) { + for (x = cb->x0, coeff = coeff0, touched = touched0; + x < cb->x1; + ++x, ++coeff, ++touched) { + val = *coeff; + if (val != 0) { + shift2 = shift - (cb->nZeroBitPlanes + cb->len + *touched); + if (shift2 > 0) { + cover(94); + if (val < 0) { + val = (val << shift2) - (1 << (shift2 - 1)); + } else { + val = (val << shift2) + (1 << (shift2 - 1)); + } } else { - val = (val << shift2) + (1 << (shift2 - 1)); + cover(95); + val >>= -shift2; } - } else { - cover(95); - val >>= -shift2; - } - if (qStyle == 0) { - cover(96); - if (tileComp->transform == 0) { - cover(97); - val &= -1 << (fracBits - tileComp->prec); + if (qStyle == 0) { + cover(96); + if (tileComp->transform == 0) { + cover(97); + val &= -1 << (fracBits - tileComp->prec); + } + } else { + cover(98); + val = (int)((double)val * mu); } - } else { - cover(98); - val = (int)((double)val * mu); } + *coeff = val; } - *coeff = val; } + ++cb; } - ++cb; } } @@ -2914,11 +2976,10 @@ void JPXStream::inverseTransformLevel(JPXTileComp *tileComp, int val; int *dataPtr, *bufPtr; Guint nx1, nx2, ny1, ny2, offset; - Guint x, y, sb, cbX, cbY; + Guint x, y, sb, pre, cbX, cbY; qStyle = tileComp->quantStyle & 0x1f; guard = (tileComp->quantStyle >> 5) & 7; - precinct = &resLevel->precincts[0]; //----- compute subband bounds @@ -2931,10 +2992,10 @@ void JPXStream::inverseTransformLevel(JPXTileComp *tileComp, // | LH | HH | <- ny1 // +----+----+ // <- ny2 - nx1 = precinct->subbands[1].x1 - precinct->subbands[1].x0; - nx2 = nx1 + precinct->subbands[0].x1 - precinct->subbands[0].x0; - ny1 = precinct->subbands[0].y1 - precinct->subbands[0].y0; - ny2 = ny1 + precinct->subbands[1].y1 - precinct->subbands[1].y0; + nx1 = resLevel->bx1[1] - resLevel->bx0[1]; + nx2 = nx1 + resLevel->bx1[0] - resLevel->bx0[0]; + ny1 = resLevel->by1[0] - resLevel->by0[0]; + ny2 = ny1 + resLevel->by1[1] - resLevel->by0[1]; if (nx2 == 0 || ny2 == 0) { return; } @@ -2965,44 +3026,48 @@ void JPXStream::inverseTransformLevel(JPXTileComp *tileComp, } // fixed point adjustment and dequantization - subband = &precinct->subbands[sb]; - cb = subband->cbs; - for (cbY = 0; cbY < subband->nYCBs; ++cbY) { - for (cbX = 0; cbX < subband->nXCBs; ++cbX) { - for (y = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched; - y < cb->y1; - ++y, coeff0 += tileComp->w, touched0 += tileComp->cbW) { - for (x = cb->x0, coeff = coeff0, touched = touched0; - x < cb->x1; - ++x, ++coeff, ++touched) { - val = *coeff; - if (val != 0) { - shift2 = shift - (cb->nZeroBitPlanes + cb->len + *touched); - if (shift2 > 0) { - cover(74); - if (val < 0) { - val = (val << shift2) - (1 << (shift2 - 1)); + + for (pre = 0; pre < resLevel->nPrecincts; ++pre) { + precinct = &resLevel->precincts[pre]; + subband = &precinct->subbands[sb]; + cb = subband->cbs; + for (cbY = 0; cbY < subband->nYCBs; ++cbY) { + for (cbX = 0; cbX < subband->nXCBs; ++cbX) { + for (y = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched; + y < cb->y1; + ++y, coeff0 += tileComp->w, touched0 += resLevel->cbW) { + for (x = cb->x0, coeff = coeff0, touched = touched0; + x < cb->x1; + ++x, ++coeff, ++touched) { + val = *coeff; + if (val != 0) { + shift2 = shift - (cb->nZeroBitPlanes + cb->len + *touched); + if (shift2 > 0) { + cover(74); + if (val < 0) { + val = (val << shift2) - (1 << (shift2 - 1)); + } else { + val = (val << shift2) + (1 << (shift2 - 1)); + } } else { - val = (val << shift2) + (1 << (shift2 - 1)); + cover(75); + val >>= -shift2; } - } else { - cover(75); - val >>= -shift2; - } - if (qStyle == 0) { - cover(76); - if (tileComp->transform == 0) { - val &= -1 << (fracBits - tileComp->prec); + if (qStyle == 0) { + cover(76); + if (tileComp->transform == 0) { + val &= -1 << (fracBits - tileComp->prec); + } + } else { + cover(77); + val = (int)((double)val * mu); } - } else { - cover(77); - val = (int)((double)val * mu); } + *coeff = val; } - *coeff = val; } + ++cb; } - ++cb; } } } @@ -3010,13 +3075,9 @@ void JPXStream::inverseTransformLevel(JPXTileComp *tileComp, //----- inverse transform // horizontal (row) transforms - if (r == tileComp->nDecompLevels) { - offset = 3 + (tileComp->x0 & 1); - } else { - offset = 3 + (tileComp->resLevels[r+1].x0 & 1); - } + offset = 3 + (resLevel->x0 & 1); for (y = 0, dataPtr = tileComp->data; y < ny2; ++y, dataPtr += tileComp->w) { - if (precinct->subbands[0].x0 == precinct->subbands[1].x0) { + if (resLevel->bx0[0] == resLevel->bx0[1]) { // fetch LL/LH for (x = 0, bufPtr = tileComp->buf + offset; x < nx1; @@ -3050,13 +3111,9 @@ void JPXStream::inverseTransformLevel(JPXTileComp *tileComp, } // vertical (column) transforms - if (r == tileComp->nDecompLevels) { - offset = 3 + (tileComp->y0 & 1); - } else { - offset = 3 + (tileComp->resLevels[r+1].y0 & 1); - } + offset = 3 + (resLevel->y0 & 1); for (x = 0, dataPtr = tileComp->data; x < nx2; ++x, ++dataPtr) { - if (precinct->subbands[1].y0 == precinct->subbands[0].y0) { + if (resLevel->by0[0] == resLevel->by0[1]) { // fetch LL/HL for (y = 0, bufPtr = tileComp->buf + offset; y < ny1; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.h b/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.h index bc5eac4f5ee..510826cd162 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.h @@ -135,7 +135,6 @@ struct JPXCodeBlock { struct JPXSubband { //----- computed - Guint x0, y0, x1, y1; // bounds Guint nXCBs, nYCBs; // number of code-blocks in the x and y // directions @@ -152,9 +151,6 @@ struct JPXSubband { //------------------------------------------------------------------------ struct JPXPrecinct { - //----- computed - Guint x0, y0, x1, y1; // bounds of the precinct - //----- children JPXSubband *subbands; // the subbands }; @@ -165,12 +161,18 @@ struct JPXResLevel { //----- from the COD and COC segments (main and tile) Guint precinctWidth; // log2(precinct width) Guint precinctHeight; // log2(precinct height) + Guint nPrecincts; //----- computed - Guint x0, y0, x1, y1; // bounds of the tile-comp (for this res level) + Guint x0, y0, x1, y1; // bounds of this tile-comp at this res level Guint bx0[3], by0[3], // subband bounds bx1[3], by1[3]; - GBool empty; // true if all subbands are zero width or height + Guint codeBlockW; // log2(code-block width) + Guint codeBlockH; // log2(code-block height) + Guint cbW; // code-block width + Guint cbH; // code-block height + GBool empty; // true if all subbands and precincts are + // zero width or height //---- children JPXPrecinct *precincts; // the precincts @@ -201,8 +203,6 @@ struct JPXTileComp { //----- computed Guint x0, y0, x1, y1; // bounds of the tile-comp, in ref coords Guint w, h; // data size = {x1 - x0, y1 - y0} >> reduction - Guint cbW; // code-block width - Guint cbH; // code-block height //----- image data int *data; // the decoded image data diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Lexer.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/Lexer.cc index 6ff75b0b621..0c74dbb04ea 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Lexer.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Lexer.cc @@ -237,11 +237,6 @@ Object *Lexer::getObj(Object *obj) { switch (c = getChar()) { case EOF: -#if 0 - // This breaks some PDF files, e.g., ones from Photoshop. - case '\r': - case '\n': -#endif error(errSyntaxError, getPos(), "Unterminated string"); done = gTrue; break; @@ -259,6 +254,16 @@ Object *Lexer::getObj(Object *obj) { } break; + case '\r': + // The PDF spec says that any literal end-of-line sequence + // (LF, CR, CR+LF) is translated to a single LF char. + c = lookChar(); + if (c == '\n') { + getChar(); + } + c2 = '\n'; + break; + case '\\': switch (c = getChar()) { case 'n': diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Object.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/Object.cc index c223074677f..898fbc99e2b 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Object.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Object.cc @@ -45,11 +45,12 @@ const char *objTypeNames[numObjTypes] = { #ifdef DEBUG_MEM #if MULTITHREADED GAtomicCounter Object::numAlloc[numObjTypes] = + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; #else long Object::numAlloc[numObjTypes] = -#endif {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; #endif +#endif // DEBUG_MEM Object *Object::initArray(XRef *xref) { initObj(objArray); @@ -229,13 +230,15 @@ void Object::memCheck(FILE *f) { long t; t = 0; - for (i = 0; i < numObjTypes; ++i) + for (i = 0; i < numObjTypes; ++i) { t += numAlloc[i]; + } if (t > 0) { fprintf(f, "Allocated objects:\n"); for (i = 0; i < numObjTypes; ++i) { - if (numAlloc[i] > 0) + if (numAlloc[i] > 0) { fprintf(f, " %-20s: %6ld\n", objTypeNames[i], numAlloc[i]); + } } } #endif diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/OptionalContent.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/OptionalContent.cc index 6188856e8aa..a0fbb1b06c2 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/OptionalContent.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/OptionalContent.cc @@ -172,6 +172,7 @@ GBool OptionalContent::evalOCObject(Object *obj, GBool *visible) { int policy; Ref ref; Object obj2, obj3, obj4, obj5; + GBool gotOCG; int i; if (obj->isNull()) { @@ -224,34 +225,40 @@ GBool OptionalContent::evalOCObject(Object *obj, GBool *visible) { obj2.free(); return gFalse; } + gotOCG = gFalse; for (i = 0; i < obj4.arrayGetLength(); ++i) { obj4.arrayGetNF(i, &obj5); if (obj5.isRef()) { ref = obj5.getRef(); - if (!(ocg = findOCG(&ref))) { - obj5.free(); - obj4.free(); - obj3.free(); - obj2.free(); - return gFalse; - } - switch (policy) { - case ocPolicyAllOn: - *visible = *visible && ocg->getState(); - break; - case ocPolicyAnyOn: - *visible = *visible || ocg->getState(); - break; - case ocPolicyAnyOff: - *visible = *visible || !ocg->getState(); - break; - case ocPolicyAllOff: - *visible = *visible && !ocg->getState(); - break; + if ((ocg = findOCG(&ref))) { + gotOCG = gTrue; + switch (policy) { + case ocPolicyAllOn: + *visible = *visible && ocg->getState(); + break; + case ocPolicyAnyOn: + *visible = *visible || ocg->getState(); + break; + case ocPolicyAnyOff: + *visible = *visible || !ocg->getState(); + break; + case ocPolicyAllOff: + *visible = *visible && !ocg->getState(); + break; + } } } obj5.free(); } + if (!gotOCG) { + // if the "OCGs" array is "empty or contains references only + // to null or deleted objects", this OCMD doesn't have any + // effect + obj4.free(); + obj3.free(); + obj2.free(); + return gFalse; + } obj4.free(); } obj3.free(); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.cc index adec372117d..f2be88edfdc 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.cc @@ -113,7 +113,7 @@ void OutputDev::drawImage(GfxState *state, Object *ref, Stream *str, void OutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GBool maskInvert, GBool interpolate) { drawImage(state, ref, str, width, height, colorMap, NULL, gFalse, @@ -123,7 +123,7 @@ void OutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, void OutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, double *matte, GBool interpolate) { diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.h b/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.h index f53dab16a23..6fe7eeedb4e 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.h @@ -24,9 +24,7 @@ class GfxState; struct GfxColor; class GfxColorSpace; class GfxImageColorMap; -class GfxFunctionShading; -class GfxAxialShading; -class GfxRadialShading; +class GfxShading; class Stream; class Links; class Link; @@ -61,11 +59,6 @@ public: // operations. virtual GBool useTilingPatternFill() { return gFalse; } - // 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. - virtual GBool useShadedFills() { return gFalse; } - // Does this device use drawForm()? If this returns false, // form-type XObjects will be interpreted (i.e., unrolled). virtual GBool useDrawForm() { return gFalse; } @@ -166,12 +159,7 @@ public: double *mat, double *bbox, int x0, int y0, int x1, int y1, double xStep, double yStep) {} - virtual GBool functionShadedFill(GfxState *state, - GfxFunctionShading *shading) - { return gFalse; } - virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading) - { return gFalse; } - virtual GBool radialShadedFill(GfxState *state, GfxRadialShading *shading) + virtual GBool shadedFill(GfxState *state, GfxShading *shading) { return gFalse; } //----- path clipping @@ -212,12 +200,13 @@ public: virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, int maskWidth, int maskHeight, + Object *maskRef, Stream *maskStr, + int maskWidth, int maskHeight, GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, double *matte, GBool interpolate); @@ -253,10 +242,6 @@ public: //----- links virtual void processLink(Link *link) {} -#if 1 //~tmp: turn off anti-aliasing temporarily - virtual void setInShading(GBool sh) {} -#endif - private: double defCTM[6]; // default coordinate transform matrix diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.cc index 09b5b99759a..877bcb88255 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.cc @@ -26,7 +26,7 @@ #include "PDFDoc.h" #include "Link.h" #include "Annot.h" -#include "Form.h" +#include "AcroForm.h" #include "OptionalContent.h" #include "TileMap.h" #include "TileCache.h" @@ -260,9 +260,15 @@ void PDFCore::displayPage(int page, GBool scrollToTop, if (page <= 0 || page > doc->getNumPages()) { return; } - if (scrollToTop || - state->getDisplayMode() == displayContinuous || - state->getDisplayMode() == displaySideBySideContinuous) { + if (!scrollToTop && + (state->getDisplayMode() == displayContinuous || + state->getDisplayMode() == displaySideBySideContinuous)) { + scrollY = tileMap->getPageTopY(page) + + (state->getScrollY() + - tileMap->getPageTopY(tileMap->getFirstPage())); + } else if (scrollToTop || + state->getDisplayMode() == displayContinuous || + state->getDisplayMode() == displaySideBySideContinuous) { scrollY = tileMap->getPageTopY(page); } else if (scrollToBottom) { scrollY = tileMap->getPageBottomY(page); @@ -679,19 +685,23 @@ void PDFCore::scrollTo(int x, int y, GBool snapToPage) { state->getDisplayMode() == displaySideBySideContinuous) { next = state->getDisplayMode() == displaySideBySideContinuous ? 2 : 1; topPage = tileMap->getFirstPage(); - topPageY = tileMap->getPageTopY(topPage); - sy = state->getScrollY(); - dy = sy - topPageY; - // note: dy can be negative here if the inter-page gap is at the - // top of the window - if (-16 < dy && dy < 16) { - state->setScrollPosition(state->getScrollPage(), x, topPageY); - } else if (topPage + next <= doc->getNumPages()) { - topPage += next; + // NB: topPage can be out of bounds here, because the scroll + // position isn't adjusted until finishUpdate is called, below + if (topPage > 0 && topPage <= doc->getNumPages()) { topPageY = tileMap->getPageTopY(topPage); + sy = state->getScrollY(); dy = sy - topPageY; - if (-16 < dy && dy < 0) { + // note: dy can be negative here if the inter-page gap is at the + // top of the window + if (-16 < dy && dy < 16) { state->setScrollPosition(state->getScrollPage(), x, topPageY); + } else if (topPage + next <= doc->getNumPages()) { + topPage += next; + topPageY = tileMap->getPageTopY(topPage); + dy = sy - topPageY; + if (-16 < dy && dy < 0) { + state->setScrollPosition(state->getScrollPage(), x, topPageY); + } } } } @@ -1100,7 +1110,7 @@ void PDFCore::setLinearSelection(int page, TextPosition *pos0, // build the list of rectangles //~ this doesn't handle RtL, vertical, or rotated text - loadText(selectPage); + loadText(page); rects = new GList(); if (begin.colIdx == end.colIdx && begin.parIdx == end.parIdx && @@ -1108,51 +1118,51 @@ void PDFCore::setLinearSelection(int page, TextPosition *pos0, // same line text->convertPosToPointUpper(&begin, &x0, &y0); text->convertPosToPointLower(&end, &x1, &y1); - cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0); - cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1); - rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1)); + cvtDevToUser(page, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0); + cvtDevToUser(page, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1); + rects->append(new SelectRect(page, ux0, uy0, ux1, uy1)); } else if (begin.colIdx == end.colIdx) { // same column text->convertPosToPointUpper(&begin, &x0, &y0); text->convertPosToPointRightEdge(&begin, &x1, &y1); text->convertPosToPointLeftEdge(&end, &x2, &y2); text->convertPosToPointLower(&end, &x3, &y3); - cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0); - cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1); - rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1)); - cvtDevToUser(selectPage, (int)(x2 + 0.5), (int)(y1 + 0.5), &ux0, &uy0); - cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y2 + 0.5), &ux1, &uy1); - rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1)); - cvtDevToUser(selectPage, (int)(x2 + 0.5), (int)(y2 + 0.5), &ux0, &uy0); - cvtDevToUser(selectPage, (int)(x3 + 0.5), (int)(y3 + 0.5), &ux1, &uy1); - rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1)); + cvtDevToUser(page, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0); + cvtDevToUser(page, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1); + rects->append(new SelectRect(page, ux0, uy0, ux1, uy1)); + cvtDevToUser(page, (int)(x2 + 0.5), (int)(y1 + 0.5), &ux0, &uy0); + cvtDevToUser(page, (int)(x1 + 0.5), (int)(y2 + 0.5), &ux1, &uy1); + rects->append(new SelectRect(page, ux0, uy0, ux1, uy1)); + cvtDevToUser(page, (int)(x2 + 0.5), (int)(y2 + 0.5), &ux0, &uy0); + cvtDevToUser(page, (int)(x3 + 0.5), (int)(y3 + 0.5), &ux1, &uy1); + rects->append(new SelectRect(page, ux0, uy0, ux1, uy1)); } else { // different columns text->convertPosToPointUpper(&begin, &x0, &y0); text->convertPosToPointRightEdge(&begin, &x1, &y1); text->getColumnLowerLeft(begin.colIdx, &x2, &y2); - cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0); - cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1); - rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1)); - cvtDevToUser(selectPage, (int)(x2 + 0.5), (int)(y1 + 0.5), &ux0, &uy0); - cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y2 + 0.5), &ux1, &uy1); - rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1)); + cvtDevToUser(page, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0); + cvtDevToUser(page, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1); + rects->append(new SelectRect(page, ux0, uy0, ux1, uy1)); + cvtDevToUser(page, (int)(x2 + 0.5), (int)(y1 + 0.5), &ux0, &uy0); + cvtDevToUser(page, (int)(x1 + 0.5), (int)(y2 + 0.5), &ux1, &uy1); + rects->append(new SelectRect(page, ux0, uy0, ux1, uy1)); for (colIdx = begin.colIdx + 1; colIdx < end.colIdx; ++colIdx) { text->getColumnLowerLeft(colIdx, &x0, &y0); text->getColumnUpperRight(colIdx, &x1, &y1); - cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y1 + 0.5), &ux0, &uy0); - cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y0 + 0.5), &ux1, &uy1); - rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1)); + cvtDevToUser(page, (int)(x0 + 0.5), (int)(y1 + 0.5), &ux0, &uy0); + cvtDevToUser(page, (int)(x1 + 0.5), (int)(y0 + 0.5), &ux1, &uy1); + rects->append(new SelectRect(page, ux0, uy0, ux1, uy1)); } text->getColumnUpperRight(end.colIdx, &x0, &y0); text->convertPosToPointLeftEdge(&end, &x1, &y1); text->convertPosToPointLower(&end, &x2, &y2); - cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y0 + 0.5), &ux0, &uy0); - cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y1 + 0.5), &ux1, &uy1); - rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1)); - cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux0, &uy0); - cvtDevToUser(selectPage, (int)(x2 + 0.5), (int)(y2 + 0.5), &ux1, &uy1); - rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1)); + cvtDevToUser(page, (int)(x1 + 0.5), (int)(y0 + 0.5), &ux0, &uy0); + cvtDevToUser(page, (int)(x0 + 0.5), (int)(y1 + 0.5), &ux1, &uy1); + rects->append(new SelectRect(page, ux0, uy0, ux1, uy1)); + cvtDevToUser(page, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux0, &uy0); + cvtDevToUser(page, (int)(x2 + 0.5), (int)(y2 + 0.5), &ux1, &uy1); + rects->append(new SelectRect(page, ux0, uy0, ux1, uy1)); } // get window coord bboxes for old selection and new selection; @@ -1264,8 +1274,31 @@ void PDFCore::moveSelectionDrag(int pg, int x, int y) { } void PDFCore::finishSelectionDrag() { - selectPage = 0; - selectStartX = selectStartY = 0; + // nothing +} + +void PDFCore::selectWord(int pg, int x, int y) { + TextPosition endPos; + + loadText(pg); + if (text->findWordPoints(x, y, &selectStartPos, &endPos)) { + selectPage = pg; + setLinearSelection(pg, &selectStartPos, &endPos); + } else { + selectPage = 0; + } +} + +void PDFCore::selectLine(int pg, int x, int y) { + TextPosition endPos; + + loadText(pg); + if (text->findLinePoints(x, y, &selectStartPos, &endPos)) { + selectPage = pg; + setLinearSelection(pg, &selectStartPos, &endPos); + } else { + selectPage = 0; + } } GBool PDFCore::getSelection(int *pg, double *ulx, double *uly, @@ -1550,6 +1583,45 @@ GBool PDFCore::findU(Unicode *u, int len, GBool caseSensitive, return gTrue; } +GList *PDFCore::findAll(Unicode *u, int len, GBool caseSensitive, + GBool wholeWord, int firstPage, int lastPage) { + GList *results = new GList(); + + TextOutputDev *textOut = new TextOutputDev(NULL, &textOutCtrl, gFalse); + if (!textOut->isOk()) { + delete textOut; + return results; + } + + for (int pg = firstPage; pg <= lastPage; ++pg) { + doc->displayPage(textOut, pg, 72, 72, 0, gFalse, gTrue, gFalse); + GBool first = gTrue; + while (1) { + double xMin, yMin, xMax, yMax; + if (!textOut->findText(u, len, first, gTrue, !first, gFalse, + caseSensitive, gFalse, wholeWord, + &xMin, &yMin, &xMax, &yMax)) { + break; + } + double uxMin, uyMin, uxMax, uyMax, t; + textOut->cvtDevToUser(xMin, yMin, &uxMin, &uyMin); + textOut->cvtDevToUser(xMax, yMax, &uxMax, &uyMax); + if (uxMin > uxMax) { + t = uxMin; uxMin = uxMax; uxMax = t; + } + if (uyMin > uyMax) { + t = uyMin; uyMin = uyMax; uyMax = t; + } + results->append(new FindResult(pg, uxMin, uyMin, uxMax, uyMax)); + first = gFalse; + } + } + + delete textOut; + + return results; +} + GBool PDFCore::cvtWindowToUser(int xw, int yw, int *pg, double *xu, double *yu) { @@ -1683,7 +1755,7 @@ Annot *PDFCore::getAnnot(int idx) { return annots->getAnnot(idx); } -FormField *PDFCore::findFormField(int pg, double x, double y) { +AcroFormField *PDFCore::findFormField(int pg, double x, double y) { if (!doc->getCatalog()->getForm()) { return NULL; } @@ -1697,7 +1769,7 @@ int PDFCore::findFormFieldIdx(int pg, double x, double y) { return doc->getCatalog()->getForm()->findFieldIdx(pg, x, y); } -FormField *PDFCore::getFormField(int idx) { +AcroFormField *PDFCore::getFormField(int idx) { if (!doc->getCatalog()->getForm()) { return NULL; } diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.h b/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.h index 510c01a1a76..accf19d82ef 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.h @@ -32,7 +32,7 @@ class LinkDest; class LinkAction; class Annot; class Annots; -class FormField; +class AcroFormField; class TextPage; class HighlightFile; class OptionalContentGroup; @@ -65,6 +65,17 @@ enum SelectMode { selectModeLinear }; +//------------------------------------------------------------------------ +// FindResult +//------------------------------------------------------------------------ + +struct FindResult { + FindResult(int pageA, double xMinA, double yMinA, double xMaxA, double yMaxA) + : page(pageA), xMin(xMinA), yMin(yMinA), xMax(xMaxA), yMax(yMaxA) {} + int page; + double xMin, yMin, xMax, yMax; +}; + //------------------------------------------------------------------------ // PDFCore //------------------------------------------------------------------------ @@ -176,6 +187,8 @@ public: void startSelectionDrag(int pg, int x, int y); void moveSelectionDrag(int pg, int x, int y); void finishSelectionDrag(); + void selectWord(int pg, int x, int y); + void selectLine(int pg, int x, int y); // Retrieve the current selection. This function uses user // coordinates. Returns false if there is no selection. @@ -198,6 +211,8 @@ public: virtual GBool findU(Unicode *u, int len, GBool caseSensitive, GBool next, GBool backward, GBool wholeWord, GBool onePageOnly); + GList *findAll(Unicode *u, int len, GBool caseSensitive, + GBool wholeWord, int firstPage, int lastPage); //----- coordinate conversion @@ -242,9 +257,9 @@ public: Annot *findAnnot(int pg, double x, double y); int findAnnotIdx(int pg, double x, double y); Annot *getAnnot(int idx); - FormField *findFormField(int pg, double x, double y); + AcroFormField *findFormField(int pg, double x, double y); int findFormFieldIdx(int pg, double x, double y); - FormField *getFormField(int idx); + AcroFormField *getFormField(int idx); GBool overText(int pg, double x, double y); void forceRedraw(); void setTileDoneCbk(void (*cbk)(void *data), void *data); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.cc index a130b164f91..6efd4aac6c4 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.cc @@ -21,6 +21,7 @@ #endif #include "gmempp.h" #include "GString.h" +#include "gfile.h" #include "config.h" #include "GlobalParams.h" #include "Page.h" @@ -113,6 +114,7 @@ PDFDoc::PDFDoc(GString *fileNameA, GString *ownerPassword, ok = setup(ownerPassword, userPassword); } +#if 0 #ifdef _WIN32 PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword, GString *userPassword, PDFCore *coreA) { @@ -122,14 +124,21 @@ PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword, init(coreA); + // handle a Windows shortcut + wchar_t wPath[MAX_PATH + 1]; + int n = fileNameLen < MAX_PATH ? fileNameLen : MAX_PATH; + memcpy(wPath, fileNameA, n * sizeof(wchar_t)); + wPath[n] = L'\0'; + readWindowsShortcut(wPath, MAX_PATH + 1); + int wPathLen = (int)wcslen(wPath); + // save both Unicode and 8-bit copies of the file name fileName = new GString(); - fileNameU = (wchar_t *)gmallocn(fileNameLen + 1, sizeof(wchar_t)); - for (i = 0; i < fileNameLen; ++i) { + fileNameU = (wchar_t *)gmallocn(wPathLen + 1, sizeof(wchar_t)); + memcpy(fileNameU, wPath, (wPathLen + 1) * sizeof(wchar_t)); + for (i = 0; i < wPathLen; ++i) { fileName->append((char)fileNameA[i]); - fileNameU[i] = fileNameA[i]; } - fileNameU[fileNameLen] = L'\0'; // try to open file // NB: _wfopen is only available in NT @@ -153,6 +162,7 @@ PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword, ok = setup(ownerPassword, userPassword); } #endif +#endif /* 0 */ PDFDoc::PDFDoc(char *fileNameA, GString *ownerPassword, GString *userPassword, PDFCore *coreA) { @@ -165,7 +175,7 @@ PDFDoc::PDFDoc(char *fileNameA, GString *ownerPassword, /* #ifdef _WIN32 Unicode u; - int n, i, j; + int i, j; #endif */ @@ -175,17 +185,19 @@ PDFDoc::PDFDoc(char *fileNameA, GString *ownerPassword, #if defined(_WIN32) #if 0 - n = 0; + wchar_t wPath[MAX_PATH + 1]; i = 0; - while (getUTF8(fileName, &i, &u)) { - ++n; - } - fileNameU = (wchar_t *)gmallocn(n + 1, sizeof(wchar_t)); - i = j = 0; - while (j < n && getUTF8(fileName, &i, &u)) { - fileNameU[j++] = (wchar_t)u; + j = 0; + while (j < MAX_PATH && getUTF8(fileName, &i, &u)) { + wPath[j++] = (wchar_t)u; } - fileNameU[n] = L'\0'; + wPath[j] = L'\0'; + readWindowsShortcut(wPath, MAX_PATH + 1); + int wPathLen = (int)wcslen(wPath); + + fileNameU = (wchar_t *)gmallocn(wPathLen + 1, sizeof(wchar_t)); + memcpy(fileNameU, wPath, (wPathLen + 1) * sizeof(wchar_t)); + // NB: _wfopen is only available in NT version.dwOSVersionInfoSize = sizeof(version); GetVersionEx(&version); @@ -579,6 +591,18 @@ GBool PDFDoc::saveEmbeddedFile(int idx, const char *path) { return ret; } +GBool PDFDoc::saveEmbeddedFileU(int idx, const char *path) { + FILE *f; + GBool ret; + + if (!(f = openFile(path, "wb"))) { + return gFalse; + } + ret = saveEmbeddedFile2(idx, f); + fclose(f); + return ret; +} + #ifdef _WIN32 GBool PDFDoc::saveEmbeddedFile(int idx, const wchar_t *path, int pathLen) { FILE *f; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.h b/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.h index e7005b16eb2..5069cb78689 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.h @@ -185,6 +185,7 @@ public: int getEmbeddedFileNameLength(int idx) { return catalog->getEmbeddedFileNameLength(idx); } GBool saveEmbeddedFile(int idx, const char *path); + GBool saveEmbeddedFileU(int idx, const char *path); #ifdef _WIN32 GBool saveEmbeddedFile(int idx, const wchar_t *path, int pathLen); #endif diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.cc index f39a72e9015..0c39472ba85 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.cc @@ -40,7 +40,7 @@ #include "XRef.h" #include "PreScanOutputDev.h" #include "CharCodeToUnicode.h" -#include "Form.h" +#include "AcroForm.h" #include "TextString.h" #if HAVE_SPLASH # include "Splash.h" @@ -1287,7 +1287,8 @@ PSOutputDev::PSOutputDev(char *fileName, PDFDoc *docA, GBool manualCtrlA, PSOutCustomCodeCbk customCodeCbkA, void *customCodeCbkDataA, - GBool honorUserUnitA) { + GBool honorUserUnitA, + GBool fileNameIsUTF8) { FILE *f; PSFileType fileTypeA; @@ -1333,7 +1334,12 @@ PSOutputDev::PSOutputDev(char *fileName, PDFDoc *docA, #endif } else { fileTypeA = psFile; - if (!(f = fopen(fileName, "w"))) { + if (fileNameIsUTF8) { + f = openFile(fileName, "w"); + } else { + f = fopen(fileName, "w"); + } + if (!f) { error(errIO, -1, "Couldn't open PostScript file '{0:s}'", fileName); ok = gFalse; return; @@ -1737,7 +1743,7 @@ void PSOutputDev::writeDocSetup(Catalog *catalog) { Page *page; Dict *resDict; Annots *annots; - Form *form; + AcroForm *form; Object obj1, obj2, obj3; GString *s; GBool needDefaultFont; @@ -1994,6 +2000,7 @@ void PSOutputDev::setupResources(Dict *resDict) { } patDict.free(); + //----- recursively scan SMask transparency groups in ExtGState dicts resDict->lookup("ExtGState", &gsDict); if (gsDict.isDict()) { @@ -2211,13 +2218,15 @@ void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) { if (fontLoc->locType == gfxFontLocResident && fontLoc->fontType >= fontCIDType0) { subst = gTrue; - if ((uMap = globalParams->getUnicodeMap(fontLoc->encoding))) { - fi->ff->encoding = fontLoc->encoding->copy(); - uMap->decRefCnt(); - } else { - error(errSyntaxError, -1, - "Couldn't find Unicode map for 16-bit font encoding '{0:t}'", - fontLoc->encoding); + if (!fi->ff->encoding) { + if ((uMap = globalParams->getUnicodeMap(fontLoc->encoding))) { + fi->ff->encoding = fontLoc->encoding->copy(); + uMap->decRefCnt(); + } else { + error(errSyntaxError, -1, + "Couldn't find Unicode map for 16-bit font encoding '{0:t}'", + fontLoc->encoding); + } } } @@ -2244,7 +2253,7 @@ void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) { if (font->getType() == fontTrueType && !subst && !((Gfx8BitFont *)font)->getHasEncoding()) { - sprintf(buf, "c%02x", i+j); + snprintf(buf, sizeof(buf), "c%02x", i+j); charName = buf; } else { charName = ((Gfx8BitFont *)font)->getCharName(i+j); @@ -2392,6 +2401,7 @@ PSFontFileInfo *PSOutputDev::setupExternalType1Font(GfxFont *font, // open the font file if (!(fontFile = fopen(fileName->getCString(), "rb"))) { error(errIO, -1, "Couldn't open external font file"); + delete psName; return NULL; } @@ -3623,7 +3633,7 @@ GString *PSOutputDev::copyType1PFB(Guchar *font, int fontSize) { p[0] == 0x80 && (p[1] == 0x01 || p[1] == 0x02)) { len = p[2] + (p[3] << 8) + (p[4] << 16) + (p[5] << 24); - if (len > remain - 6) { + if (len < 0 || len > remain - 6) { break; } if (p[1] == 0x01) { @@ -4102,6 +4112,7 @@ void PSOutputDev::setupForm(Object *strRef, Object *strObj) { resObj.free(); } + GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI, int rotateA, GBool useMediaBox, GBool crop, int sliceX, int sliceY, @@ -4441,17 +4452,27 @@ void PSOutputDev::startPage(int pageNum, GfxState *state) { landscape = gFalse; } else { rotate = (360 - state->getRotate()) % 360; + double scaledWidth = width * userUnit; + double scaledHeight = height * userUnit; + if (xScale0 > 0 && yScale0 > 0) { + scaledWidth *= xScale0; + scaledHeight *= yScale0; + } if (rotate == 0 || rotate == 180) { - if ((width < height && imgWidth > imgHeight && height > imgHeight) || - (width > height && imgWidth < imgHeight && width > imgWidth)) { + if ((scaledWidth < scaledHeight && imgWidth > imgHeight && + scaledHeight > imgHeight) || + (scaledWidth > scaledHeight && imgWidth < imgHeight && + scaledWidth > imgWidth)) { rotate += 90; landscape = gTrue; } else { landscape = gFalse; } } else { // rotate == 90 || rotate == 270 - if ((height < width && imgWidth > imgHeight && width > imgHeight) || - (height > width && imgWidth < imgHeight && height > imgWidth)) { + if ((scaledHeight < scaledWidth && imgWidth > imgHeight && + scaledWidth > imgHeight) || + (scaledHeight > scaledWidth && imgWidth < imgHeight && + scaledHeight > imgWidth)) { rotate = 270 - rotate; landscape = gTrue; } else { @@ -5252,6 +5273,29 @@ void PSOutputDev::tilingPatternFillL2(GfxState *state, Gfx *gfx, noStateChanges = gFalse; } +GBool PSOutputDev::shadedFill(GfxState *state, GfxShading *shading) { + if (level != psLevel2 && + level != psLevel2Sep && + level != psLevel3 && + level != psLevel3Sep) { + return gFalse; + } + + switch (shading->getType()) { + case 1: + functionShadedFill(state, (GfxFunctionShading *)shading); + return gTrue; + case 2: + axialShadedFill(state, (GfxAxialShading *)shading); + return gTrue; + case 3: + radialShadedFill(state, (GfxRadialShading *)shading); + return gTrue; + default: + return gFalse; + } +} + GBool PSOutputDev::functionShadedFill(GfxState *state, GfxFunctionShading *shading) { double x0, y0, x1, y1; @@ -5957,7 +6001,7 @@ void PSOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, void PSOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GBool maskInvert, GBool interpolate) { int len; @@ -8301,7 +8345,7 @@ void PSOutputDev::writePSString(GString *s) { writePSChar((char)*p); line += 2; } else if (*p < 0x20 || *p >= 0x80) { - sprintf(buf, "\\%03o", *p); + snprintf(buf, sizeof(buf), "\\%03o", *p); writePS(buf); line += 4; } else { @@ -8351,7 +8395,7 @@ GString *PSOutputDev::filterPSName(GString *name) { c == '(' || c == ')' || c == '<' || c == '>' || c == '[' || c == ']' || c == '{' || c == '}' || c == '/' || c == '%') { - sprintf(buf, "#%02x", c & 0xff); + snprintf(buf, sizeof(buf), "#%02x", c & 0xff); name2->append(buf); } else { name2->append(c); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.h b/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.h index 66057a7cb74..466f81f1992 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.h @@ -38,6 +38,9 @@ class GfxICCBasedColorSpace; class GfxIndexedColorSpace; class GfxSeparationColorSpace; class GfxDeviceNColorSpace; +class GfxFunctionShading; +class GfxAxialShading; +class GfxRadialShading; class PDFRectangle; class PSOutCustomColor; class PSOutputDev; @@ -82,7 +85,8 @@ public: GBool manualCtrlA = gFalse, PSOutCustomCodeCbk customCodeCbkA = NULL, void *customCodeCbkDataA = NULL, - GBool honorUserUnitA = gFalse); + GBool honorUserUnitA = gFalse, + GBool fileNameIsUTF8 = gFalse); // Open a PSOutputDev that will write to a generic stream. PSOutputDev(PSOutputFunc outputFuncA, void *outputStreamA, @@ -118,13 +122,6 @@ public: // operations. virtual GBool useTilingPatternFill() { 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. - virtual GBool useShadedFills() - { return level == psLevel2 || level == psLevel2Sep || - level == psLevel3 || level == psLevel3Sep; } - // Does this device use drawForm()? If this returns false, // form-type XObjects will be interpreted (i.e., unrolled). virtual GBool useDrawForm() { return preload; } @@ -214,10 +211,7 @@ public: double *mat, double *bbox, int x0, int y0, int x1, int y1, double xStep, double yStep); - virtual GBool functionShadedFill(GfxState *state, - GfxFunctionShading *shading); - virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading); - virtual GBool radialShadedFill(GfxState *state, GfxRadialShading *shading); + virtual GBool shadedFill(GfxState *state, GfxShading *shading); //----- path clipping virtual void clip(GfxState *state); @@ -238,7 +232,8 @@ public: virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, int maskWidth, int maskHeight, + Object *maskRef, Stream *maskStr, + int maskWidth, int maskHeight, GBool maskInvert, GBool interpolate); #if OPI_SUPPORT @@ -342,6 +337,10 @@ private: double *mat, double *bbox, int x0, int y0, int x1, int y1, double xStep, double yStep); + GBool functionShadedFill(GfxState *state, + GfxFunctionShading *shading); + GBool axialShadedFill(GfxState *state, GfxAxialShading *shading); + GBool radialShadedFill(GfxState *state, GfxRadialShading *shading); void doPath(GfxPath *path); void doImageL1(Object *ref, GfxState *state, GfxImageColorMap *colorMap, diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Page.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/Page.cc index b2820d3a2bf..ace0b33d85c 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Page.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Page.cc @@ -14,6 +14,7 @@ #include #include "gmempp.h" +#include "Trace.h" #include "GlobalParams.h" #include "Object.h" #include "Array.h" @@ -26,7 +27,7 @@ #include "Gfx.h" #include "GfxState.h" #include "Annot.h" -#include "Form.h" +#include "AcroForm.h" #endif #include "Error.h" #include "Catalog.h" @@ -335,7 +336,7 @@ void Page::displaySlice(OutputDev *out, double hDPI, double vDPI, Gfx *gfx; Object obj; Annots *annotList; - Form *form; + AcroForm *form; int i; if (!out->checkPageSlice(this, hDPI, vDPI, rotate, useMediaBox, crop, @@ -344,6 +345,8 @@ void Page::displaySlice(OutputDev *out, double hDPI, double vDPI, return; } + traceBegin(this, "begin page"); + rotate += getRotate(); if (rotate >= 360) { rotate -= 360; @@ -404,7 +407,9 @@ void Page::displaySlice(OutputDev *out, double hDPI, double vDPI, } delete gfx; -#endif +#endif // PDF_PARSER_ONLY + + traceEnd(this, "end page"); } void Page::makeBox(double hDPI, double vDPI, int rotate, diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Page.h b/Build/source/libs/xpdf/xpdf-src/xpdf/Page.h index ee30a1a8b47..8ec5a10995a 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Page.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Page.h @@ -127,6 +127,7 @@ public: // Get page parameters. int getNum() { return num; } + PageAttrs *getAttrs() { return attrs; } PDFRectangle *getMediaBox() { return attrs->getMediaBox(); } PDFRectangle *getCropBox() { return attrs->getCropBox(); } GBool isCropped() { return attrs->isCropped(); } diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.cc index d6f40333215..e5e00b85f51 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.cc @@ -76,36 +76,7 @@ void PreScanOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, } } -GBool PreScanOutputDev::functionShadedFill(GfxState *state, - GfxFunctionShading *shading) { - if (shading->getColorSpace()->getMode() != csDeviceGray && - shading->getColorSpace()->getMode() != csCalGray) { - gray = gFalse; - } - mono = gFalse; - if (state->getFillOpacity() != 1 || - state->getBlendMode() != gfxBlendNormal) { - transparency = gTrue; - } - return gTrue; -} - -GBool PreScanOutputDev::axialShadedFill(GfxState *state, - GfxAxialShading *shading) { - if (shading->getColorSpace()->getMode() != csDeviceGray && - shading->getColorSpace()->getMode() != csCalGray) { - gray = gFalse; - } - mono = gFalse; - if (state->getFillOpacity() != 1 || - state->getBlendMode() != gfxBlendNormal) { - transparency = gTrue; - } - return gTrue; -} - -GBool PreScanOutputDev::radialShadedFill(GfxState *state, - GfxRadialShading *shading) { +GBool PreScanOutputDev::shadedFill(GfxState *state, GfxShading *shading) { if (shading->getColorSpace()->getMode() != csDeviceGray && shading->getColorSpace()->getMode() != csCalGray) { gray = gFalse; @@ -229,7 +200,7 @@ void PreScanOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GBool maskInvert, GBool interpolate) { GfxColorSpace *colorSpace; @@ -258,7 +229,7 @@ void PreScanOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, double *matte, GBool interpolate) { diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.h b/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.h index 882d8ef39f5..253733772bf 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.h @@ -46,11 +46,6 @@ public: // operations. virtual GBool useTilingPatternFill() { 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. - virtual GBool useShadedFills() { return gTrue; } - // Does this device use beginType3Char/endType3Char? Otherwise, // text in Type 3 fonts will be drawn with drawChar/drawString. virtual GBool interpretType3Chars() { return gTrue; } @@ -72,10 +67,7 @@ public: double *mat, double *bbox, int x0, int y0, int x1, int y1, double xStep, double yStep); - virtual GBool functionShadedFill(GfxState *state, - GfxFunctionShading *shading); - virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading); - virtual GBool radialShadedFill(GfxState *state, GfxRadialShading *shading); + virtual GBool shadedFill(GfxState *state, GfxShading *shading); //----- path clipping virtual void clip(GfxState *state); @@ -99,12 +91,13 @@ public: virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, int maskWidth, int maskHeight, + Object *maskRef, Stream *maskStr, + int maskWidth, int maskHeight, GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, double *matte, GBool interpolate); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.cc new file mode 100644 index 00000000000..d8dc24e0fc6 --- /dev/null +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.cc @@ -0,0 +1,1327 @@ +//======================================================================== +// +// ShadingImage.cc +// +// Copyright 2020 Glyph & Cog, LLC +// +//======================================================================== + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include +#include "Trace.h" +#include "GfxState.h" +#include "SplashBitmap.h" +#include "SplashPattern.h" +#include "SplashPath.h" +#include "Splash.h" +#include "ShadingImage.h" + +// Max recursive depth for a patch mesh shading fill. +#define patchMaxDepth 10 + +// Max delta allowed in any color component for a patch mesh shading +// fill. +#define patchColorDelta (dblToCol(1 / 256.0)) + +SplashBitmap *ShadingImage::generateBitmap(GfxState *state, + GfxShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut) { + switch (shading->getType()) { + case 1: + return generateFunctionBitmap(state, (GfxFunctionShading *)shading, + mode, reverseVideo, + parentSplash, parentBitmap, xOut, yOut); + break; + case 2: + return generateAxialBitmap(state, (GfxAxialShading *)shading, + mode, reverseVideo, + parentSplash, parentBitmap, xOut, yOut); + break; + case 3: + return generateRadialBitmap(state, (GfxRadialShading *)shading, + mode, reverseVideo, + parentSplash, parentBitmap, xOut, yOut); + break; + case 4: + case 5: + return generateGouraudTriangleBitmap(state, + (GfxGouraudTriangleShading *)shading, + mode, reverseVideo, + parentSplash, parentBitmap, + xOut, yOut); + break; + case 6: + case 7: + return generatePatchMeshBitmap(state, (GfxPatchMeshShading *)shading, + mode, reverseVideo, + parentSplash, parentBitmap, xOut, yOut); + break; + default: + return NULL; + } +} + +SplashBitmap *ShadingImage::generateFunctionBitmap(GfxState *state, + GfxFunctionShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut) { + // get the shading parameters + double x0, y0, x1, y1; + shading->getDomain(&x0, &y0, &x1, &y1); + double *patternMat = shading->getMatrix(); + + // get the clip bbox + double fxMin, fyMin, fxMax, fyMax; + state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax); + if (fxMin > fxMax || fyMin > fyMax) { + return NULL; + } + + // convert to integer coords + int xMin = (int)floor(fxMin); + int yMin = (int)floor(fyMin); + int xMax = (int)floor(fxMax) + 1; + int yMax = (int)floor(fyMax) + 1; + int bitmapWidth = xMax - xMin; + int bitmapHeight = yMax - yMin; + + // allocate the bitmap + traceMessage("function shading fill bitmap"); + SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode, + gTrue, gTrue, parentBitmap); + int nComps = splashColorModeNComps[mode]; + + // compute the domain -> device space transform = mat * CTM + double *ctm = state->getCTM(); + double mat[6]; + mat[0] = patternMat[0] * ctm[0] + patternMat[1] * ctm[2]; + mat[1] = patternMat[0] * ctm[1] + patternMat[1] * ctm[3]; + mat[2] = patternMat[2] * ctm[0] + patternMat[3] * ctm[2]; + mat[3] = patternMat[2] * ctm[1] + patternMat[3] * ctm[3]; + mat[4] = patternMat[4] * ctm[0] + patternMat[5] * ctm[2] + ctm[4]; + mat[5] = patternMat[4] * ctm[1] + patternMat[5] * ctm[3] + ctm[5]; + + // compute the device space -> domain transform + double det = mat[0] * mat[3] - mat[1] * mat[2]; + if (fabs(det) < 0.000001) { + return NULL; + } + det = 1 / det; + double iMat[6]; + iMat[0] = mat[3] * det; + iMat[1] = -mat[1] * det; + iMat[2] = -mat[2] * det; + iMat[3] = mat[0] * det; + iMat[4] = (mat[2] * mat[5] - mat[3] * mat[4]) * det; + iMat[5] = (mat[1] * mat[4] - mat[0] * mat[5]) * det; + + // fill the bitmap + SplashColorPtr dataPtr = bitmap->getDataPtr(); + Guchar *alphaPtr = bitmap->getAlphaPtr(); + for (int y = 0; y < bitmapHeight; ++y) { + for (int x = 0; x < bitmapWidth; ++x) { + + // convert coords to the pattern domain + double tx = xMin + x + 0.5; + double ty = yMin + y + 0.5; + double xx = tx * iMat[0] + ty * iMat[2] + iMat[4]; + double yy = tx * iMat[1] + ty * iMat[3] + iMat[5]; + + // get the color + if (xx >= x0 && xx <= x1 && yy >= y0 && yy <= y1) { + GfxColor color; + shading->getColor(xx, yy, &color); + SplashColor sColor; + computeShadingColor(state, mode, reverseVideo, &color, sColor); + for (int i = 0; i < nComps; ++i) { + *dataPtr++ = sColor[i]; + } + *alphaPtr++ = 0xff; + } else { + dataPtr += nComps; + *alphaPtr++ = 0; + } + } + } + + *xOut = xMin; + *yOut = yMin; + return bitmap; +} + +SplashBitmap *ShadingImage::generateAxialBitmap(GfxState *state, + GfxAxialShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut) { + // get the shading parameters + double x0, y0, x1, y1; + shading->getCoords(&x0, &y0, &x1, &y1); + double t0 = shading->getDomain0(); + double t1 = shading->getDomain1(); + GBool ext0 = shading->getExtend0(); + GBool ext1 = shading->getExtend1(); + double dx = x1 - x0; + double dy = y1 - y0; + double d = dx * dx + dy * dy; + GBool dZero = fabs(d) < 0.0001; + if (!dZero) { + d = 1 / d; + } + if (dZero && !ext0 && !ext1) { + return NULL; + } + + // get the clip bbox + double fxMin, fyMin, fxMax, fyMax; + state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax); + if (fxMin > fxMax || fyMin > fyMax) { + return NULL; + } + + // convert to integer coords + int xMin = (int)floor(fxMin); + int yMin = (int)floor(fyMin); + int xMax = (int)floor(fxMax) + 1; + int yMax = (int)floor(fyMax) + 1; + int bitmapWidth = xMax - xMin; + int bitmapHeight = yMax - yMin; + + // compute the inverse CTM + double *ctm = state->getCTM(); + double det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; + if (fabs(det) < 0.000001) { + return NULL; + } + det = 1 / det; + double ictm[6]; + ictm[0] = ctm[3] * det; + ictm[1] = -ctm[1] * det; + ictm[2] = -ctm[2] * det; + ictm[3] = ctm[0] * det; + ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; + ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; + + // convert axis endpoints to device space + double xx0, yy0, xx1, yy1; + state->transform(x0, y0, &xx0, &yy0); + state->transform(x1, y1, &xx1, &yy1); + + // allocate the bitmap + traceMessage("axial shading fill bitmap"); + SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode, + gTrue, gTrue, parentBitmap); + int nComps = splashColorModeNComps[mode]; + + // special case: zero-length axis + if (dZero) { + GfxColor color; + if (ext0) { + shading->getColor(t0, &color); + } else { + shading->getColor(t1, &color); + } + SplashColor sColor; + computeShadingColor(state, mode, reverseVideo, &color, sColor); + SplashColorPtr dataPtr = bitmap->getDataPtr(); + for (int y = 0; y < bitmapHeight; ++y) { + for (int x = 0; x < bitmapWidth; ++x) { + for (int i = 0; i < nComps; ++i) { + *dataPtr++ = sColor[i]; + } + } + } + memset(bitmap->getAlphaPtr(), 0xff, (size_t)bitmapWidth * bitmapHeight); + + // special case: horizontal axis (in device space) + } else if (fabs(yy0 - yy1) < 0.01) { + for (int x = 0; x < bitmapWidth; ++x) { + SplashColorPtr dataPtr = bitmap->getDataPtr() + x * nComps; + Guchar *alphaPtr = bitmap->getAlphaPtr() + x; + double tx = xMin + x + 0.5; + double ty = yMin + 0.5; + double xx = tx * ictm[0] + ty * ictm[2] + ictm[4]; + double yy = tx * ictm[1] + ty * ictm[3] + ictm[5]; + double s = ((xx - x0) * dx + (yy - y0) * dy) * d; + GBool go = gFalse; + if (s < 0) { + go = ext0; + } else if (s > 1) { + go = ext1; + } else { + go = gTrue; + } + if (go) { + GfxColor color; + if (s <= 0) { + shading->getColor(t0, &color); + } else if (s >= 1) { + shading->getColor(t1, &color); + } else { + double t = t0 + s * (t1 - t0); + shading->getColor(t, &color); + } + SplashColor sColor; + computeShadingColor(state, mode, reverseVideo, &color, sColor); + for (int y = 0; y < bitmapHeight; ++y) { + for (int i = 0; i < nComps; ++i) { + dataPtr[i] = sColor[i]; + } + dataPtr += bitmap->getRowSize(); + *alphaPtr = 0xff; + alphaPtr += bitmapWidth; + } + } else { + for (int y = 0; y < bitmapHeight; ++y) { + *alphaPtr = 0; + alphaPtr += bitmapWidth; + } + } + } + + // special case: vertical axis (in device space) + } else if (fabs(xx0 - xx1) < 0.01) { + for (int y = 0; y < bitmapHeight; ++y) { + SplashColorPtr dataPtr = bitmap->getDataPtr() + y * bitmap->getRowSize(); + Guchar *alphaPtr = bitmap->getAlphaPtr() + y * bitmapWidth; + double tx = xMin + 0.5; + double ty = yMin + y + 0.5; + double xx = tx * ictm[0] + ty * ictm[2] + ictm[4]; + double yy = tx * ictm[1] + ty * ictm[3] + ictm[5]; + double s = ((xx - x0) * dx + (yy - y0) * dy) * d; + GBool go = gFalse; + if (s < 0) { + go = ext0; + } else if (s > 1) { + go = ext1; + } else { + go = gTrue; + } + if (go) { + GfxColor color; + if (s <= 0) { + shading->getColor(t0, &color); + } else if (s >= 1) { + shading->getColor(t1, &color); + } else { + double t = t0 + s * (t1 - t0); + shading->getColor(t, &color); + } + SplashColor sColor; + computeShadingColor(state, mode, reverseVideo, &color, sColor); + for (int x = 0; x < bitmapWidth; ++x) { + for (int i = 0; i < nComps; ++i) { + dataPtr[i] = sColor[i]; + } + dataPtr += nComps; + } + memset(alphaPtr, 0xff, bitmapWidth); + } else { + memset(alphaPtr, 0, bitmapWidth); + } + } + + // general case + } else { + // pre-compute colors along the axis + int nColors = (int)(1.5 * sqrt((xx1 - xx0) * (xx1 - xx0) + + (yy1 - yy0) * (yy1 - yy0))); + if (nColors < 16) { + nColors = 16; + } else if (nColors > 1024) { + nColors = 1024; + } + SplashColorPtr sColors = (SplashColorPtr)gmallocn(nColors, nComps); + SplashColorPtr sColor = sColors; + for (int i = 0; i < nColors; ++i) { + double s = (double)i / (double)(nColors - 1); + double t = t0 + s * (t1 - t0); + GfxColor color; + shading->getColor(t, &color); + computeShadingColor(state, mode, reverseVideo, &color, sColor); + sColor += nComps; + } + + SplashColorPtr dataPtr = bitmap->getDataPtr(); + Guchar *alphaPtr = bitmap->getAlphaPtr(); + for (int y = 0; y < bitmapHeight; ++y) { + for (int x = 0; x < bitmapWidth; ++x) { + + // convert coords to user space + double tx = xMin + x + 0.5; + double ty = yMin + y + 0.5; + double xx = tx * ictm[0] + ty * ictm[2] + ictm[4]; + double yy = tx * ictm[1] + ty * ictm[3] + ictm[5]; + + // compute the position along the axis + double s = ((xx - x0) * dx + (yy - y0) * dy) * d; + GBool go = gFalse; + if (s < 0) { + go = ext0; + } else if (s > 1) { + go = ext1; + } else { + go = gTrue; + } + if (go) { + if (s <= 0) { + sColor = sColors; + } else if (s >= 1) { + sColor = sColors + (nColors - 1) * nComps; + } else { + int i = (int)((nColors - 1) * s + 0.5); + sColor = sColors + i * nComps; + } + for (int i = 0; i < nComps; ++i) { + *dataPtr++ = sColor[i]; + } + *alphaPtr++ = 0xff; + } else { + dataPtr += nComps; + *alphaPtr++ = 0; + } + } + } + gfree(sColors); + } + + *xOut = xMin; + *yOut = yMin; + return bitmap; +} + +SplashBitmap *ShadingImage::generateRadialBitmap(GfxState *state, + GfxRadialShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut) { + // get the shading parameters + double x0, y0, r0, x1, y1, r1; + shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1); + double t0 = shading->getDomain0(); + double t1 = shading->getDomain1(); + GBool ext0 = shading->getExtend0(); + GBool ext1 = shading->getExtend1(); + double h = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); + GBool enclosed = fabs(r1 - r0) >= h; + + // get the clip bbox + double fxMin, fyMin, fxMax, fyMax; + state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax); + if (fxMin > fxMax || fyMin > fyMax) { + return NULL; + } + + // intersect with shading region (in user space): if the extend + // flags are false (or just the larger extend flag is false, in the + // "enclosed" case), we can use the bbox for the two circles + if ((!ext0 && !ext1) || + (enclosed && !(r0 > r1 ? ext0 : ext1))) { + double uxMin = (x0 - r0) < (x1 - r1) ? (x0 - r0) : (x1 - r1); + double uxMax = (x0 + r0) > (x1 + r1) ? (x0 + r0) : (x1 + r1); + double uyMin = (y0 - r0) < (y1 - r1) ? (y0 - r0) : (y1 - r1); + double uyMax = (y0 + r0) > (y1 + r1) ? (y0 + r0) : (y1 + r1); + double dxMin, dyMin, dxMax, dyMax; + transformBBox(state, uxMin, uyMin, uxMax, uyMax, + &dxMin, &dyMin, &dxMax, &dyMax); + if (dxMin > fxMin) { + fxMin = dxMin; + } + if (dxMax < dxMax) { + fxMax = dxMax; + } + if (dyMin > fyMin) { + fyMin = dyMin; + } + if (dyMax < fyMax) { + fyMax = dyMax; + } + if (fxMin > fxMax || fyMin > fyMax) { + return NULL; + } + } + + // convert to integer coords + int xMin = (int)floor(fxMin); + int yMin = (int)floor(fyMin); + int xMax = (int)floor(fxMax) + 1; + int yMax = (int)floor(fyMax) + 1; + int bitmapWidth = xMax - xMin; + int bitmapHeight = yMax - yMin; + + // compute the inverse CTM + double *ctm = state->getCTM(); + double det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; + if (fabs(det) < 0.000001) { + return NULL; + } + det = 1 / det; + double ictm[6]; + ictm[0] = ctm[3] * det; + ictm[1] = -ctm[1] * det; + ictm[2] = -ctm[2] * det; + ictm[3] = ctm[0] * det; + ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; + ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; + + // allocate the bitmap + traceMessage("radial shading fill bitmap"); + SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode, + gTrue, gTrue, parentBitmap); + int nComps = splashColorModeNComps[mode]; + + // pre-compute colors along the axis + int nColors = (int)sqrt((double)(bitmapWidth * bitmapWidth + + bitmapHeight * bitmapHeight)); + if (nColors < 16) { + nColors = 16; + } else if (nColors > 1024) { + nColors = 1024; + } + SplashColorPtr sColors = (SplashColorPtr)gmallocn(nColors, nComps); + SplashColorPtr sColor = sColors; + for (int i = 0; i < nColors; ++i) { + double s = (double)i / (double)(nColors - 1); + double t = t0 + s * (t1 - t0); + GfxColor color; + shading->getColor(t, &color); + computeShadingColor(state, mode, reverseVideo, &color, sColor); + sColor += nComps; + } + + // special case: in the "enclosed" + extended case, we can fill the + // bitmap with the outer color and just render inside the larger + // circle + int bxMin, byMin, bxMax, byMax; + if (enclosed && + ((r0 > r1 && ext0) || (r1 > r0 && ext1))) { + double uxMin, uyMin, uxMax, uyMax; + if (r0 > r1) { + sColor = sColors; + uxMin = x0 - r0; + uxMax = x0 + r0; + uyMin = y0 - r0; + uyMax = y0 + r0; + } else { + sColor = sColors + (nColors - 1) * nComps; + uxMin = x1 - r1; + uxMax = x1 + r1; + uyMin = y1 - r1; + uyMax = y1 + r1; + } + + // convert bbox of larger circle to device space + double dxMin, dyMin, dxMax, dyMax; + transformBBox(state, uxMin, uyMin, uxMax, uyMax, + &dxMin, &dyMin, &dxMax, &dyMax); + bxMin = (int)floor(dxMin - xMin); + if (bxMin < 0) { + bxMin = 0; + } + byMin = (int)floor(dyMin - yMin); + if (byMin < 0) { + byMin = 0; + } + bxMax = (int)floor(dxMax - xMin) + 1; + if (bxMax > bitmapWidth) { + bxMax = bitmapWidth; + } + byMax = (int)floor(dyMax - yMin) + 1; + if (byMax > bitmapHeight) { + byMax = bitmapHeight; + } + + // fill bitmap (except for the rectangle containing the larger circle) + SplashColorPtr dataPtr = bitmap->getDataPtr(); + Guchar *alphaPtr = bitmap->getAlphaPtr(); + for (int y = 0; y < bitmapHeight; ++y) { + for (int x = 0; x < bitmapWidth; ++x) { + if (y >= byMin && y < byMax && x >= bxMin && x < bxMax) { + dataPtr += nComps; + ++alphaPtr; + } else { + for (int i = 0; i < nComps; ++i) { + *dataPtr++ = sColor[i]; + } + *alphaPtr++ = 0xff; + } + } + } + + } else { + bxMin = 0; + byMin = 0; + bxMax = bitmapWidth; + byMax = bitmapHeight; + } + + // render the shading into the bitmap + double dx = x1 - x0; + double dy = y1 - y0; + double dr = r1 - r0; + double r0dr = r0 * dr; + double r02 = r0 * r0; + double a = dx * dx + dy * dy - dr * dr; + GBool aIsZero; + double a2; + if (fabs(a) < 0.00001) { + aIsZero = gTrue; + a2 = 0; + } else { + aIsZero = gFalse; + a2 = 1 / (2 * a); + } + for (int y = byMin; y < byMax; ++y) { + SplashColorPtr dataPtr = bitmap->getDataPtr() + + y * bitmap->getRowSize() + bxMin * nComps; + Guchar *alphaPtr = bitmap->getAlphaPtr() + + y * bitmap->getAlphaRowSize() + bxMin; + for (int x = bxMin; x < bxMax; ++x) { + + // convert coords to user space + double tx = xMin + x + 0.5; + double ty = yMin + y + 0.5; + double xx = tx * ictm[0] + ty * ictm[2] + ictm[4]; + double yy = tx * ictm[1] + ty * ictm[3] + ictm[5]; + + // compute the radius of the circle at x,y + double b = 2 * ((xx - x0) * dx + (yy - y0) * dy + r0dr); + double c = (xx - x0) * (xx - x0) + (yy - y0) * (yy - y0) - r02; + double s = 0; + GBool go = gFalse; + if (aIsZero) { + if (fabs(b) < 0.000001) { + if (c <= 0) { + if (ext0) { + s = 0; + go = gTrue; + } + } else { + if (ext1) { + s = 1; + go = gTrue; + } + } + } else { + double s0 = c / b; + double rs0 = r0 + s0 * (r1 - r0); + if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) { + s = s0; + go = gTrue; + } + } + } else { + double e = b*b - 4*a*c; + if (e >= 0) { + double es = sqrt(e); + double s0 = (b + es) * a2; + double s1 = (b - es) * a2; + double rs0 = r0 + s0 * (r1 - r0); + double rs1 = r0 + s1 * (r1 - r0); + if (s0 > s1) { + if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) { + s = s0; + go = gTrue; + } else if ((s1 >= 0 || ext0) && (s1 <= 1 || ext1) && rs1 >= 0) { + s = s1; + go = gTrue; + } + } else { + if ((s1 >= 0 || ext0) && (s1 <= 1 || ext1) && rs1 >= 0) { + s = s1; + go = gTrue; + } else if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) { + s = s0; + go = gTrue; + } + } + } + } + if (!go) { + dataPtr += nComps; + *alphaPtr++ = 0x00; + continue; + } + if (s <= 0) { + sColor = sColors; + } else if (s >= 1) { + sColor = sColors + (nColors - 1) * nComps; + } else { + int i = (int)((nColors - 1) * s + 0.5); + sColor = sColors + i * nComps; + } + for (int i = 0; i < nComps; ++i) { + *dataPtr++ = sColor[i]; + } + *alphaPtr++ = 0xff; + } + } + + gfree(sColors); + + *xOut = xMin; + *yOut = yMin; + return bitmap; +} + +SplashBitmap *ShadingImage::generateGouraudTriangleBitmap( + GfxState *state, + GfxGouraudTriangleShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut) { + // get the clip bbox + double fxMin, fyMin, fxMax, fyMax; + state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax); + if (fxMin > fxMax || fyMin > fyMax) { + return NULL; + } + + // get the shading bbox + double tx0, ty0, tx1, ty1, dx, dy, txMin, tyMin, txMax, tyMax; + shading->getBBox(&tx0, &ty0, &tx1, &ty1); + state->transform(tx0, ty0, &dx, &dy); + txMin = txMax = dx; + tyMin = tyMax = dy; + state->transform(tx0, ty1, &dx, &dy); + if (dx < txMin) { + txMin = dx; + } else if (dx > txMax) { + txMax = dx; + } + if (dy < tyMin) { + tyMin = dy; + } else if (dy > tyMax) { + tyMax = dy; + } + state->transform(tx1, ty0, &dx, &dy); + if (dx < txMin) { + txMin = dx; + } else if (dx > txMax) { + txMax = dx; + } + if (dy < tyMin) { + tyMin = dy; + } else if (dy > tyMax) { + tyMax = dy; + } + state->transform(tx1, ty1, &dx, &dy); + if (dx < txMin) { + txMin = dx; + } else if (dx > txMax) { + txMax = dx; + } + if (dy < tyMin) { + tyMin = dy; + } else if (dy > tyMax) { + tyMax = dy; + } + if (txMin > fxMin) { + fxMin = txMin; + } + if (txMax < fxMax) { + fxMax = txMax; + } + if (tyMin > fyMin) { + fyMin = tyMin; + } + if (tyMax < fyMax) { + fyMax = tyMax; + } + if (fxMin > fxMax || fyMin > fyMax) { + return NULL; + } + + // convert to integer coords + int xMin = (int)floor(fxMin); + int yMin = (int)floor(fyMin); + int xMax = (int)floor(fxMax) + 1; + int yMax = (int)floor(fyMax) + 1; + int bitmapWidth = xMax - xMin; + int bitmapHeight = yMax - yMin; + + // allocate the bitmap + traceMessage("Gouraud triangle shading fill bitmap"); + SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode, + gTrue, gTrue, parentBitmap); + + // clear the bitmap + memset(bitmap->getDataPtr(), 0, bitmap->getHeight() * bitmap->getRowSize()); + memset(bitmap->getAlphaPtr(), 0, bitmap->getHeight() * bitmap->getWidth()); + + // draw the triangles + for (int i = 0; i < shading->getNTriangles(); ++i) { + double x0, y0, x1, y1, x2, y2; + double color0[gfxColorMaxComps]; + double color1[gfxColorMaxComps]; + double color2[gfxColorMaxComps]; + shading->getTriangle(i, &x0, &y0, color0, + &x1, &y1, color1, + &x2, &y2, color2); + gouraudFillTriangle(state, bitmap, mode, reverseVideo, + xMin, yMin, xMax, yMax, + x0, y0, color0, x1, y1, color1, x2, y2, color2, + shading); + } + + *xOut = xMin; + *yOut = yMin; + return bitmap; +} + +void ShadingImage::gouraudFillTriangle(GfxState *state, SplashBitmap *bitmap, + SplashColorMode mode, + GBool reverseVideo, + int xMin, int yMin, int xMax, int yMax, + double x0, double y0, double *color0, + double x1, double y1, double *color1, + double x2, double y2, double *color2, + GfxGouraudTriangleShading *shading) { + int nShadingComps = shading->getNComps(); + int nBitmapComps = splashColorModeNComps[mode]; + + //--- transform the vertices to device space, sort by y + double dx0, dy0, dx1, dy1, dx2, dy2; + state->transform(x0, y0, &dx0, &dy0); + state->transform(x1, y1, &dx1, &dy1); + state->transform(x2, y2, &dx2, &dy2); + if (dy0 > dy1) { + double t = dx0; dx0 = dx1; dx1 = t; + t = dy0; dy0 = dy1; dy1 = t; + double *tc = color0; color0 = color1; color1 = tc; + } + if (dy1 > dy2) { + double t = dx1; dx1 = dx2; dx2 = t; + t = dy1; dy1 = dy2; dy2 = t; + double *tc = color1; color1 = color2; color2 = tc; + } + if (dy0 > dy1) { + double t = dx0; dx0 = dx1; dx1 = t; + t = dy0; dy0 = dy1; dy1 = t; + double *tc = color0; color0 = color1; color1 = tc; + } + + //--- y loop + int syMin = (int)floor(dy0); + if (syMin < yMin) { + syMin = yMin; + } + int syMax = (int)floor(dy2) + 1; + if (syMax > yMax) { + syMax = yMax; + } + for (int sy = syMin; sy < syMax; ++sy) { + + //--- vertical interpolation + double xx0, xx1; + double cc0[gfxColorMaxComps], cc1[gfxColorMaxComps]; + if (sy <= dy0) { + xx0 = xx1 = dx0; + for (int i = 0; i < nShadingComps; ++i) { + cc0[i] = cc1[i] = color0[i]; + } + } else if (sy >= dy2) { + xx0 = xx1 = dx2; + for (int i = 0; i < nShadingComps; ++i) { + cc0[i] = cc1[i] = color2[i]; + } + } else { + if (sy <= dy1) { + double interp = (sy - dy0) / (dy1 - dy0); + xx0 = dx0 + interp * (dx1 - dx0); + for (int i = 0; i < nShadingComps; ++i) { + cc0[i] = color0[i] + interp * (color1[i] - color0[i]); + } + } else { + double interp = (sy - dy1) / (dy2 - dy1); + xx0 = dx1 + interp * (dx2 - dx1); + for (int i = 0; i < nShadingComps; ++i) { + cc0[i] = color1[i] + interp * (color2[i] - color1[i]); + } + } + double interp = (sy - dy0) / (dy2 - dy0); + xx1 = dx0 + interp * (dx2 - dx0); + for (int i = 0; i < nShadingComps; ++i) { + cc1[i] = color0[i] + interp * (color2[i] - color0[i]); + } + } + + //--- x loop + if (xx0 > xx1) { + double t = xx0; xx0 = xx1; xx1 = t; + for (int i = 0; i < nShadingComps; ++i) { + t = cc0[i]; cc0[i] = cc1[i]; cc1[i] = t; + } + } + int sxMin = (int)floor(xx0); + if (sxMin < xMin) { + sxMin = xMin; + } + int sxMax = (int)floor(xx1) + 1; + if (sxMax > xMax) { + sxMax = xMax; + } + SplashColorPtr dataPtr = bitmap->getDataPtr() + + (sy - yMin) * bitmap->getRowSize() + + (sxMin - xMin) * nBitmapComps; + if (sxMin < sxMax) { + Guchar *alphaPtr = bitmap->getAlphaPtr() + + (sy - yMin) * bitmap->getWidth() + + (sxMin - xMin); + memset(alphaPtr, 0xff, sxMax - sxMin); + } + for (int sx = sxMin; sx < sxMax; ++sx) { + + //--- horizontal interpolation + double cc[gfxColorMaxComps]; + if (sx <= xx0) { + for (int i = 0; i < nShadingComps; ++i) { + cc[i] = cc0[i]; + } + } else if (sx >= xx1) { + for (int i = 0; i < nShadingComps; ++i) { + cc[i] = cc1[i]; + } + } else { + for (int i = 0; i < nShadingComps; ++i) { + double interp = (sx - xx0) / (xx1 - xx0); + cc[i] = cc0[i] + interp * (cc1[i] - cc0[i]); + } + } + + //--- compute color and set pixel + GfxColor gColor; + shading->getColor(cc, &gColor); + SplashColor sColor; + computeShadingColor(state, mode, reverseVideo, &gColor, sColor); + for (int i = 0; i < nBitmapComps; ++i) { + dataPtr[i] = sColor[i]; + } + dataPtr += nBitmapComps; + } + } +} + +SplashBitmap *ShadingImage::generatePatchMeshBitmap( + GfxState *state, + GfxPatchMeshShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut) { + // get the clip bbox + double fxMin, fyMin, fxMax, fyMax; + state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax); + if (fxMin > fxMax || fyMin > fyMax) { + return NULL; + } + + // get the shading bbox + double tx0, ty0, tx1, ty1, dx, dy, txMin, tyMin, txMax, tyMax; + shading->getBBox(&tx0, &ty0, &tx1, &ty1); + state->transform(tx0, ty0, &dx, &dy); + txMin = txMax = dx; + tyMin = tyMax = dy; + state->transform(tx0, ty1, &dx, &dy); + if (dx < txMin) { + txMin = dx; + } else if (dx > txMax) { + txMax = dx; + } + if (dy < tyMin) { + tyMin = dy; + } else if (dy > tyMax) { + tyMax = dy; + } + state->transform(tx1, ty0, &dx, &dy); + if (dx < txMin) { + txMin = dx; + } else if (dx > txMax) { + txMax = dx; + } + if (dy < tyMin) { + tyMin = dy; + } else if (dy > tyMax) { + tyMax = dy; + } + state->transform(tx1, ty1, &dx, &dy); + if (dx < txMin) { + txMin = dx; + } else if (dx > txMax) { + txMax = dx; + } + if (dy < tyMin) { + tyMin = dy; + } else if (dy > tyMax) { + tyMax = dy; + } + if (txMin > fxMin) { + fxMin = txMin; + } + if (txMax < fxMax) { + fxMax = txMax; + } + if (tyMin > fyMin) { + fyMin = tyMin; + } + if (tyMax < fyMax) { + fyMax = tyMax; + } + if (fxMin > fxMax || fyMin > fyMax) { + return NULL; + } + + // convert to integer coords + int xMin = (int)floor(fxMin); + int yMin = (int)floor(fyMin); + int xMax = (int)floor(fxMax) + 1; + int yMax = (int)floor(fyMax) + 1; + int bitmapWidth = xMax - xMin; + int bitmapHeight = yMax - yMin; + + // allocate the bitmap + traceMessage("Gouraud triangle shading fill bitmap"); + SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode, + gTrue, gTrue, parentBitmap); + + // allocate a Splash object + // vector antialiasing is disabled to avoid artifacts along triangle edges + Splash *splash = new Splash(bitmap, gFalse, + parentSplash->getImageCache(), + parentSplash->getScreen()); + SplashColor zero; + for (int i = 0; i < splashColorModeNComps[mode]; ++i) { + zero[i] = 0; + } + splash->clear(zero, 0x00); + + // draw the patches + int start; + if (shading->getNPatches() > 128) { + start = 3; + } else if (shading->getNPatches() > 64) { + start = 2; + } else if (shading->getNPatches() > 16) { + start = 1; + } else { + start = 0; + } + for (int i = 0; i < shading->getNPatches(); ++i) { + fillPatch(state, splash, mode, reverseVideo, + xMin, yMin, shading->getPatch(i), shading, start); + } + + delete splash; + + *xOut = xMin; + *yOut = yMin; + return bitmap; +} + +void ShadingImage::fillPatch(GfxState *state, Splash *splash, + SplashColorMode mode, GBool reverseVideo, + int xMin, int yMin, + GfxPatch *patch, + GfxPatchMeshShading *shading, + int depth) { + GfxColor c00; + shading->getColor(patch->color[0][0], &c00); + GBool stop = gFalse; + + // stop subdivision at max depth + if (depth == patchMaxDepth) { + stop = gTrue; + } + + // stop subdivision if colors are close enough + if (!stop) { + int nComps = shading->getColorSpace()->getNComps(); + GfxColor c01, c10, c11; + shading->getColor(patch->color[0][1], &c01); + shading->getColor(patch->color[1][0], &c10); + shading->getColor(patch->color[1][1], &c11); + int i; + for (i = 0; i < nComps; ++i) { + if (abs(c00.c[i] - c01.c[i]) > patchColorDelta || + abs(c01.c[i] - c11.c[i]) > patchColorDelta || + abs(c11.c[i] - c10.c[i]) > patchColorDelta || + abs(c10.c[i] - c00.c[i]) > patchColorDelta) { + break; + } + } + if (i == nComps) { + stop = gTrue; + } + } + + // stop subdivision if patch is small enough + if (!stop) { + double xxMin = 0; + double yyMin = 0; + double xxMax = 0; + double yyMax = 0; + for (int j = 0; j < 4; ++j) { + for (int i = 0; i < 4; ++i) { + double xx, yy; + state->transformDelta(patch->x[i][j], patch->y[i][j], &xx, &yy); + if (i == 0 && j == 0) { + xxMin = xxMax = xx; + yyMin = yyMax = yy; + } else { + if (xx < xxMin) { + xxMin = xx; + } else if (xx > xxMax) { + xxMax = xx; + } + if (yy < yyMin) { + yyMin = yy; + } else if (yy > yyMax) { + yyMax = yy; + } + } + } + } + if (xxMax - xxMin < 1 && yyMax - yyMin < 1) { + stop = gTrue; + } + } + + // draw the patch + if (stop) { + SplashColor sColor; + computeShadingColor(state, mode, reverseVideo, &c00, sColor); + splash->setFillPattern(new SplashSolidColor(sColor)); + SplashPath *path = new SplashPath(); + double xx0, yy0, xx1, yy1, xx2, yy2, xx3, yy3; + state->transform(patch->x[0][0], patch->y[0][0], &xx0, &yy0); + path->moveTo(xx0 - xMin, yy0 - yMin); + state->transform(patch->x[0][1], patch->y[0][1], &xx1, &yy1); + state->transform(patch->x[0][2], patch->y[0][2], &xx2, &yy2); + state->transform(patch->x[0][3], patch->y[0][3], &xx3, &yy3); + path->curveTo(xx1 - xMin, yy1 - yMin, xx2 - xMin, yy2 - yMin, + xx3 - xMin, yy3 - yMin); + state->transform(patch->x[1][3], patch->y[1][3], &xx1, &yy1); + state->transform(patch->x[2][3], patch->y[2][3], &xx2, &yy2); + state->transform(patch->x[3][3], patch->y[3][3], &xx3, &yy3); + path->curveTo(xx1 - xMin, yy1 - yMin, xx2 - xMin, yy2 - yMin, + xx3 - xMin, yy3 - yMin); + state->transform(patch->x[3][2], patch->y[3][2], &xx1, &yy1); + state->transform(patch->x[3][1], patch->y[3][1], &xx2, &yy2); + state->transform(patch->x[3][0], patch->y[3][0], &xx3, &yy3); + path->curveTo(xx1 - xMin, yy1 - yMin, xx2 - xMin, yy2 - yMin, + xx3 - xMin, yy3 - yMin); + state->transform(patch->x[2][0], patch->y[2][0], &xx1, &yy1); + state->transform(patch->x[1][0], patch->y[1][0], &xx2, &yy2); + path->curveTo(xx1 - xMin, yy1 - yMin, xx2 - xMin, yy2 - yMin, + xx0 - xMin, yy0 - yMin); + path->close(); + splash->fill(path, gFalse); + delete path; + + // subdivide the patch + } else { + double xx[4][8], yy[4][8]; + for (int i = 0; i < 4; ++i) { + xx[i][0] = patch->x[i][0]; + yy[i][0] = patch->y[i][0]; + xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]); + yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]); + double xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]); + double yym = 0.5 * (patch->y[i][1] + patch->y[i][2]); + xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]); + yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]); + xx[i][2] = 0.5 * (xx[i][1] + xxm); + yy[i][2] = 0.5 * (yy[i][1] + yym); + xx[i][5] = 0.5 * (xxm + xx[i][6]); + yy[i][5] = 0.5 * (yym + yy[i][6]); + xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]); + yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]); + xx[i][7] = patch->x[i][3]; + yy[i][7] = patch->y[i][3]; + } + GfxPatch patch00, patch01, patch10, patch11; + for (int i = 0; i < 4; ++i) { + patch00.x[0][i] = xx[0][i]; + patch00.y[0][i] = yy[0][i]; + patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]); + patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]); + double xxm = 0.5 * (xx[1][i] + xx[2][i]); + double yym = 0.5 * (yy[1][i] + yy[2][i]); + patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]); + patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]); + patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm); + patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym); + patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]); + patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]); + patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]); + patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]); + patch10.x[0][i] = patch00.x[3][i]; + patch10.y[0][i] = patch00.y[3][i]; + patch10.x[3][i] = xx[3][i]; + patch10.y[3][i] = yy[3][i]; + } + for (int i = 4; i < 8; ++i) { + patch01.x[0][i-4] = xx[0][i]; + patch01.y[0][i-4] = yy[0][i]; + patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]); + patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]); + double xxm = 0.5 * (xx[1][i] + xx[2][i]); + double yym = 0.5 * (yy[1][i] + yy[2][i]); + patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]); + patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]); + patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm); + patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym); + patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]); + patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]); + patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]); + patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]); + patch11.x[0][i-4] = patch01.x[3][i-4]; + patch11.y[0][i-4] = patch01.y[3][i-4]; + patch11.x[3][i-4] = xx[3][i]; + patch11.y[3][i-4] = yy[3][i]; + } + for (int i = 0; i < shading->getNComps(); ++i) { + patch00.color[0][0][i] = patch->color[0][0][i]; + patch00.color[0][1][i] = 0.5 * (patch->color[0][0][i] + + patch->color[0][1][i]); + patch01.color[0][0][i] = patch00.color[0][1][i]; + patch01.color[0][1][i] = patch->color[0][1][i]; + patch01.color[1][1][i] = 0.5 * (patch->color[0][1][i] + + patch->color[1][1][i]); + patch11.color[0][1][i] = patch01.color[1][1][i]; + patch11.color[1][1][i] = patch->color[1][1][i]; + patch11.color[1][0][i] = 0.5 * (patch->color[1][1][i] + + patch->color[1][0][i]); + patch10.color[1][1][i] = patch11.color[1][0][i]; + patch10.color[1][0][i] = patch->color[1][0][i]; + patch10.color[0][0][i] = 0.5 * (patch->color[1][0][i] + + patch->color[0][0][i]); + patch00.color[1][0][i] = patch10.color[0][0][i]; + patch00.color[1][1][i] = 0.5 * (patch00.color[1][0][i] + + patch01.color[1][1][i]); + patch01.color[1][0][i] = patch00.color[1][1][i]; + patch11.color[0][0][i] = patch00.color[1][1][i]; + patch10.color[0][1][i] = patch00.color[1][1][i]; + } + fillPatch(state, splash, mode, reverseVideo, xMin, yMin, &patch00, + shading, depth + 1); + fillPatch(state, splash, mode, reverseVideo, xMin, yMin, &patch10, + shading, depth + 1); + fillPatch(state, splash, mode, reverseVideo, xMin, yMin, &patch01, + shading, depth + 1); + fillPatch(state, splash, mode, reverseVideo, xMin, yMin, &patch11, + shading, depth + 1); + } +} + +void ShadingImage::computeShadingColor(GfxState *state, + SplashColorMode mode, + GBool reverseVideo, + GfxColor *color, + SplashColorPtr sColor) { + GfxGray gray; + GfxRGB rgb; +#if SPLASH_CMYK + GfxCMYK cmyk; +#endif + + state->setFillColor(color); + switch (mode) { + case splashModeMono8: + state->getFillGray(&gray); + if (reverseVideo) { + gray = gfxColorComp1 - gray; + } + sColor[0] = colToByte(gray); + break; + case splashModeRGB8: + state->getFillRGB(&rgb); + if (reverseVideo) { + rgb.r = gfxColorComp1 - rgb.r; + rgb.g = gfxColorComp1 - rgb.g; + rgb.b = gfxColorComp1 - rgb.b; + } + sColor[0] = colToByte(rgb.r); + sColor[1] = colToByte(rgb.g); + sColor[2] = colToByte(rgb.b); + break; +#if SPLASH_CMYK + case splashModeCMYK8: + state->getFillCMYK(&cmyk); + sColor[0] = colToByte(cmyk.c); + sColor[1] = colToByte(cmyk.m); + sColor[2] = colToByte(cmyk.y); + sColor[3] = colToByte(cmyk.k); + break; +#endif + case splashModeMono1: + case splashModeBGR8: + // mode cannot be Mono1 or BGR8 + break; + } +} + +// Transform a user space bbox to a device space bbox. +void ShadingImage::transformBBox(GfxState *state, + double uxMin, double uyMin, + double uxMax, double uyMax, + double *dxMin, double *dyMin, + double *dxMax, double *dyMax) { + double tx, ty; + state->transform(uxMin, uyMin, &tx, &ty); + *dxMin = *dxMax = tx; + *dyMin = *dyMax = ty; + state->transform(uxMin, uyMax, &tx, &ty); + if (tx < *dxMin) { + *dxMin = tx; + } else if (tx > *dxMax) { + *dxMax = tx; + } + if (ty < *dyMin) { + *dyMin = ty; + } else if (ty > *dyMax) { + *dyMax = ty; + } + state->transform(uxMax, uyMin, &tx, &ty); + if (tx < *dxMin) { + *dxMin = tx; + } else if (tx > *dxMax) { + *dxMax = tx; + } + if (ty < *dyMin) { + *dyMin = ty; + } else if (ty > *dyMax) { + *dyMax = ty; + } + state->transform(uxMax, uyMax, &tx, &ty); + if (tx < *dxMin) { + *dxMin = tx; + } else if (tx > *dxMax) { + *dxMax = tx; + } + if (ty < *dyMin) { + *dyMin = ty; + } else if (ty > *dyMax) { + *dyMax = ty; + } +} + diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.h b/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.h new file mode 100644 index 00000000000..3bbff6922a4 --- /dev/null +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.h @@ -0,0 +1,106 @@ +//======================================================================== +// +// ShadingImage.h +// +// Convert shading patterns to bitmaps. +// +// Copyright 2020 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef SHADINGIMAGE_H +#define SHADINGIMAGE_H + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "SplashTypes.h" + +class GfxState; +class GfxShading; +class SplashBitmap; +class Splash; + +class ShadingImage { +public: + + // Generate a [mode] bitmap for [shading], using the clipping and + // CTM info from [state]. Returns the bitmap and sets [xOut],[yOut] + // to the upper-left corner of the region in device space. Returns + // NULL if the clip region is empty. + static SplashBitmap *generateBitmap(GfxState *state, GfxShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut); + + +private: + + static SplashBitmap *generateFunctionBitmap(GfxState *state, + GfxFunctionShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut); + static SplashBitmap *generateAxialBitmap(GfxState *state, + GfxAxialShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut); + static SplashBitmap *generateRadialBitmap(GfxState *state, + GfxRadialShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut); + static SplashBitmap *generateGouraudTriangleBitmap( + GfxState *state, + GfxGouraudTriangleShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut); + static void gouraudFillTriangle(GfxState *state, SplashBitmap *bitmap, + SplashColorMode mode, + GBool reverseVideo, + int xMin, int yMin, int xMax, int yMax, + double x0, double y0, double *color0, + double x1, double y1, double *color1, + double x2, double y2, double *color2, + GfxGouraudTriangleShading *shading); + static SplashBitmap *generatePatchMeshBitmap(GfxState *state, + GfxPatchMeshShading *shading, + SplashColorMode mode, + GBool reverseVideo, + Splash *parentSplash, + SplashBitmap *parentBitmap, + int *xOut, int *yOut); + static void fillPatch(GfxState *state, Splash *splash, + SplashColorMode mode, GBool reverseVideo, + int xMin, int yMin, + GfxPatch *patch, + GfxPatchMeshShading *shading, + int depth); + static void computeShadingColor(GfxState *state, + SplashColorMode mode, + GBool reverseVideo, + GfxColor *color, + SplashColorPtr sColor); + static void transformBBox(GfxState *state, + double uxMin, double uyMin, + double uxMax, double uyMax, + double *dxMin, double *dyMin, + double *dxMax, double *dyMax); +}; + +#endif diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.cc index 187ef5657b5..89c7c02a7b6 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.cc @@ -17,11 +17,13 @@ #include #include "gmempp.h" #include "gfile.h" +#include "Trace.h" #include "GlobalParams.h" #include "Error.h" #include "Object.h" #include "Gfx.h" #include "GfxFont.h" +#include "ShadingImage.h" #include "Link.h" #include "CharCodeToUnicode.h" #include "FontEncodingTables.h" @@ -52,6 +54,18 @@ extern "C" int unlink(char *filename); //------------------------------------------------------------------------ +// max tile size (used in tilingPatternFill()) +// - Adobe uses a resolution-independent threshold here, of 6M sq pts +// - xpdf uses a resolution-dependent max, but with different values +// on 32-bit and 64-bit systems +#if SplashBitmapRowSizeMax == INT_MAX +# define maxTileSize 200000000 +#else +# define maxTileSize 2000000000 +#endif + +//------------------------------------------------------------------------ + // Type 3 font cache size parameters #define type3FontCacheAssoc 8 #define type3FontCacheMaxSets 8 @@ -67,6 +81,11 @@ static SplashStrokeAdjustMode mapStrokeAdjustMode[3] = { //------------------------------------------------------------------------ +// Divide a 16-bit value (in [0, 255*255]) by 255, returning an 8-bit result. +static inline Guchar div255(int x) { + return (Guchar)((x + (x >> 8) + 0x80) >> 8); +} + // Clip x to lie in [0, 255]. static inline Guchar clip255(int x) { return x < 0 ? 0 : x > 255 ? 255 : (Guchar)x; @@ -499,7 +518,7 @@ public: int cacheAssoc; // cache associativity (glyphs per set) Guchar *cacheData; // glyph pixmap cache T3FontCacheTag *cacheTags; // cache tags, i.e., char codes - GBool inUse; // set while this T3 font is in active use + int refCount; // active reference count for this T3 font }; T3FontCache::T3FontCache(Ref *fontIDA, double m11A, double m12A, @@ -541,7 +560,7 @@ T3FontCache::T3FontCache(Ref *fontIDA, double m11A, double m12A, for (i = 0; i < cacheSets * cacheAssoc; ++i) { cacheTags[i].mru = (Gushort)(i & (cacheAssoc - 1)); } - inUse = gFalse; + refCount = 0; } T3FontCache::~T3FontCache() { @@ -565,6 +584,7 @@ struct T3GlyphStack { SplashBitmap *origBitmap; Splash *origSplash; double origCTM4, origCTM5; + SplashStrokeAdjustMode savedStrokeAdjust; T3GlyphStack *next; // next object on stack }; @@ -711,6 +731,9 @@ SplashOutputDev::~SplashOutputDev() { if (bitmap) { delete bitmap; } + if (textClipPath) { + delete textClipPath; + } } void SplashOutputDev::startDoc(XRef *xrefA) { @@ -763,6 +786,7 @@ void SplashOutputDev::startPage(int pageNum, GfxState *state) { delete bitmap; bitmap = NULL; } + traceMessage("page bitmap"); bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, colorMode != splashModeMono1, bitmapTopDown, NULL); @@ -1194,18 +1218,21 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { goto err1; } - // sanity-check the font size - skip anything larger than 20 inches - // (this avoids problems allocating memory for the font cache) + // sanity-check the font size: skip anything larger than 10k x 10k, + // to avoid problems allocating a bitmap (note that code in + // SplashFont disables caching at a smaller size than this) state->textTransformDelta(state->getFontSize(), state->getFontSize(), &fsx, &fsy); state->transformDelta(fsx, fsy, &fsx, &fsy); - if (fabs(fsx) > 20 * state->getHDPI() || - fabs(fsy) > 20 * state->getVDPI()) { + if (fabs(fsx) > 20000 || fabs(fsy) > 20000) { goto err1; } // check the font file cache id = new SplashOutFontFileID(gfxFont->getID()); + if (fontEngine->checkForBadFontFile(id)) { + goto err2; + } if ((fontFile = fontEngine->getFontFile(id))) { delete id; @@ -1308,7 +1335,7 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { gfxFont->getName() ? gfxFont->getName()->getCString() : "(unnamed)"); delete fontLoc; - goto err2; + goto err1; } break; case fontType1C: @@ -1337,7 +1364,7 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { gfxFont->getName() ? gfxFont->getName()->getCString() : "(unnamed)"); delete fontLoc; - goto err2; + goto err1; } break; case fontType1COT: @@ -1370,7 +1397,7 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { gfxFont->getName() ? gfxFont->getName()->getCString() : "(unnamed)"); delete fontLoc; - goto err2; + goto err1; } break; case fontTrueType: @@ -1415,7 +1442,7 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { gfxFont->getName() ? gfxFont->getName()->getCString() : "(unnamed)"); delete fontLoc; - goto err2; + goto err1; } break; case fontCIDType0: @@ -1443,7 +1470,7 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { gfxFont->getName() ? gfxFont->getName()->getCString() : "(unnamed)"); delete fontLoc; - goto err2; + goto err1; } break; case fontCIDType0COT: @@ -1513,7 +1540,7 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { gfxFont->getName() ? gfxFont->getName()->getCString() : "(unnamed)"); delete fontLoc; - goto err2; + goto err1; } break; case fontCIDType2: @@ -1588,7 +1615,7 @@ void SplashOutputDev::doUpdateFont(GfxState *state) { gfxFont->getName() ? gfxFont->getName()->getCString() : "(unnamed)"); delete fontLoc; - goto err2; + goto err1; } break; default: @@ -1734,6 +1761,7 @@ void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, SplashBitmap *origBitmap, *tileBitmap; Splash *origSplash; SplashColor color; + Guint *overprintMaskBitmap; double *ctm; double ictm[6], tileMat[6], mat1[6], mat2[6]; double tileXMin, tileYMin, tileXMax, tileYMax; @@ -1818,7 +1846,7 @@ void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, if (tileXMax - tileXMin + 0.5 > (double)INT_MAX || tileYMax - tileYMin + 0.5 > (double)INT_MAX || tileW > INT_MAX / tileH || - tileSize > 1000000) { + tileSize > maxTileSize) { mat1[0] = mat[0]; mat1[1] = mat[1]; mat1[2] = mat[2]; @@ -1969,6 +1997,7 @@ void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, // create a temporary bitmap origBitmap = bitmap; origSplash = splash; + traceMessage("tiling pattern bitmap"); bitmap = tileBitmap = new SplashBitmap(tileW, tileH, bitmapRowPad, colorMode, gTrue, bitmapTopDown, origBitmap); @@ -1978,6 +2007,20 @@ void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, color[i] = 0; } splash->clear(color); +#if SPLASH_CMYK + // if we're doing overprint preview, we need to track the overprint + // mask at each pixel in the tile bitmap + if (globalParams->getOverprintPreview() && + colorMode == splashModeCMYK8) { + overprintMaskBitmap = (Guint *)gmallocn(tileH, tileW * (int)sizeof(Guint)); + memset(overprintMaskBitmap, 0, tileH * tileW * sizeof(Guint)); + splash->setOverprintMaskBitmap(overprintMaskBitmap); + } else { + overprintMaskBitmap = NULL; + } +#else // SPLASH_CMYK + overprintMaskBitmap = NULL; +#endif // SPLASH_CMYK splash->setMinLineWidth(globalParams->getMinLineWidth()); splash->setStrokeAdjust( mapStrokeAdjustMode[globalParams->getStrokeAdjust()]); @@ -2005,434 +2048,48 @@ void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, splash->setOverprintMask(0xffffffff); // draw the tiles - for (iy = iyMin; iy < iyMax; ++iy) { - for (ix = ixMin; ix < ixMax; ++ix) { - x = (int)(adjXMin + ix * xStepX + iy * yStepX + 0.5); - y = (int)(adjYMin + ix * xStepY + iy * yStepY + 0.5); - splash->composite(tileBitmap, 0, 0, x, y, tileW, tileH, - gFalse, gFalse); - } - } - - delete tileBitmap; -} - -GBool SplashOutputDev::axialShadedFill(GfxState *state, - GfxAxialShading *shading) { - double x0, y0, x1, y1, t0, t1; - GBool ext0, ext1; - double uxMin, uyMin, uxMax, uyMax, det; - double *ctm; - double ictm[6]; - double xMin, yMin, xMax, yMax, tx, ty, xx, yy; - double xx0, yy0, xx1, yy1, dx, dy, d, s, t; - GBool dZero, go; - int ixMin, iyMin, ixMax, iyMax, bitmapWidth, bitmapHeight, nColors; - SplashClipResult clipRes; - SplashColorMode srcMode; - SplashBitmap *tBitmap; - int nComps; - int x, y, i; - SplashColorPtr dataPtr; - Guchar *alphaPtr; - GfxColor color; - SplashColorPtr sColors, sColor; - SplashColor sColor0; - - - // get the shading parameters - shading->getCoords(&x0, &y0, &x1, &y1); - t0 = shading->getDomain0(); - t1 = shading->getDomain1(); - ext0 = shading->getExtend0(); - ext1 = shading->getExtend1(); - dx = x1 - x0; - dy = y1 - y0; - d = dx * dx + dy * dy; - dZero = fabs(d) < 0.0001; - if (!dZero) { - d = 1 / d; - } - if (dZero && !ext0 && !ext1) { - return gTrue; - } - - // get clip region (in user space) - state->getUserClipBBox(&uxMin, &uyMin, &uxMax, &uyMax); - if (uxMin > uxMax || uyMin > uyMax) { - return gTrue; - } - - // convert the region to device space - ctm = state->getCTM(); - tx = uxMin * ctm[0] + uyMin * ctm[2] + ctm[4]; - ty = uxMin * ctm[1] + uyMin * ctm[3] + ctm[5]; - xMin = xMax = tx; - yMin = yMax = ty; - tx = uxMin * ctm[0] + uyMax * ctm[2] + ctm[4]; - ty = uxMin * ctm[1] + uyMax * ctm[3] + ctm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - tx = uxMax * ctm[0] + uyMin * ctm[2] + ctm[4]; - ty = uxMax * ctm[1] + uyMin * ctm[3] + ctm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - tx = uxMax * ctm[0] + uyMax * ctm[2] + ctm[4]; - ty = uxMax * ctm[1] + uyMax * ctm[3] + ctm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - ixMin = (int)floor(xMin); - iyMin = (int)floor(yMin); - ixMax = (int)floor(xMax) + 1; - iyMax = (int)floor(yMax) + 1; - clipRes = splash->limitRectToClipRect(&ixMin, &iyMin, &ixMax, &iyMax); - if (clipRes == splashClipAllOutside) { - return gTrue; - } - - // allocate a bitmap - if (colorMode == splashModeMono1) { - srcMode = splashModeMono8; - } else if (colorMode == splashModeBGR8) { - srcMode = splashModeRGB8; - } else { - srcMode = colorMode; - } - bitmapWidth = ixMax - ixMin; - bitmapHeight = iyMax - iyMin; - tBitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, - srcMode, gTrue, gTrue, bitmap); - memset(tBitmap->getAlphaPtr(), 0, (size_t)bitmapWidth * bitmapHeight); - nComps = splashColorModeNComps[srcMode]; - - // compute the inverse CTM - det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); - ictm[0] = ctm[3] * det; - ictm[1] = -ctm[1] * det; - ictm[2] = -ctm[2] * det; - ictm[3] = ctm[0] * det; - ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; - ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; - - // render the shading into the bitmap - xx0 = x0 * ctm[0] + y0 * ctm[2] + ctm[4]; - yy0 = x0 * ctm[1] + y0 * ctm[3] + ctm[5]; - xx1 = x1 * ctm[0] + y1 * ctm[2] + ctm[4]; - yy1 = x1 * ctm[1] + y1 * ctm[3] + ctm[5]; - sColors = NULL; - - // special case: zero-length axis - if (dZero) { - if (ext0) { - shading->getColor(t0, &color); - } else { - shading->getColor(t1, &color); - } - computeShadingColor(state, srcMode, &color, sColor0); - dataPtr = tBitmap->getDataPtr(); - alphaPtr = tBitmap->getAlphaPtr(); - for (y = 0; y < bitmapHeight; ++y) { - for (x = 0; x < bitmapWidth; ++x) { - for (i = 0; i < nComps; ++i) { - *dataPtr++ = sColor0[i]; - } - *alphaPtr++ = 0xff; - } - } - - // special case: horizontal axis (in device space) - } else if (fabs(yy0 - yy1) < 0.01) { - for (x = 0; x < bitmapWidth; ++x) { - dataPtr = tBitmap->getDataPtr() + x * nComps; - alphaPtr = tBitmap->getAlphaPtr() + x; - tx = ixMin + x + 0.5; - ty = iyMin + 0.5; - xx = tx * ictm[0] + ty * ictm[2] + ictm[4]; - yy = tx * ictm[1] + ty * ictm[3] + ictm[5]; - s = ((xx - x0) * dx + (yy - y0) * dy) * d; - go = gFalse; - if (s < 0) { - go = ext0; - } else if (s > 1) { - go = ext1; - } else { - go = gTrue; - } - if (!go) { - continue; - } - if (s <= 0) { - shading->getColor(t0, &color); - } else if (s >= 1) { - shading->getColor(t1, &color); - } else { - t = t0 + s * (t1 - t0); - shading->getColor(t, &color); - } - computeShadingColor(state, srcMode, &color, sColor0); - for (y = 0; y < bitmapHeight; ++y) { - for (i = 0; i < nComps; ++i) { - dataPtr[i] = sColor0[i]; - } - *alphaPtr = 0xff; - dataPtr += tBitmap->getRowSize(); - alphaPtr += bitmapWidth; + if (tileW == 1 && tileH == 1 && + fabs(xStepX * yStepY - xStepY * yStepX) < 0.9) { + // if the tile is 1x1 pixel, and the stepping completely fills the + // area, just composite the 1x1 image across the clip region + // (this avoids performance problems in cases where the step size + // is very small) (we compare to 0.9 instead of 1.0 to avoid fp + // jitter issues) + ixMin = (int)floor(clipXMin); + ixMax = (int)floor(clipXMax) + 1; + iyMin = (int)floor(clipYMin); + iyMax = (int)floor(clipYMax) + 1; + for (iy = iyMin; iy < iyMax; ++iy) { + for (ix = ixMin; ix < ixMax; ++ix) { + splash->composite(tileBitmap, 0, 0, ix, iy, tileW, tileH, + gFalse, gFalse); } } - - // special case: vertical axis (in device space) - } else if (fabs(xx0 - xx1) < 0.01) { - dataPtr = tBitmap->getDataPtr(); - alphaPtr = tBitmap->getAlphaPtr(); - for (y = 0; y < bitmapHeight; ++y) { - tx = ixMin + 0.5; - ty = iyMin + y + 0.5; - xx = tx * ictm[0] + ty * ictm[2] + ictm[4]; - yy = tx * ictm[1] + ty * ictm[3] + ictm[5]; - s = ((xx - x0) * dx + (yy - y0) * dy) * d; - go = gFalse; - if (s < 0) { - go = ext0; - } else if (s > 1) { - go = ext1; - } else { - go = gTrue; - } - if (!go) { - continue; - } - if (s <= 0) { - shading->getColor(t0, &color); - } else if (s >= 1) { - shading->getColor(t1, &color); - } else { - t = t0 + s * (t1 - t0); - shading->getColor(t, &color); - } - computeShadingColor(state, srcMode, &color, sColor0); - for (x = 0; x < bitmapWidth; ++x) { - for (i = 0; i < nComps; ++i) { - *dataPtr++ = sColor0[i]; - } - *alphaPtr++ = 0xff; - } - } - - // general case } else { - // pre-compute colors along the axis - nColors = (int)(1.5 * sqrt((xx1 - xx0) * (xx1 - xx0) - + (yy1 - yy0) * (yy1 - yy0))); - if (nColors < 16) { - nColors = 16; - } else if (nColors > 1024) { - nColors = 1024; - } - sColors = (SplashColorPtr)gmallocn(nColors, nComps); - sColor = sColors; - for (i = 0; i < nColors; ++i) { - s = (double)i / (double)(nColors - 1); - t = t0 + s * (t1 - t0); - shading->getColor(t, &color); - computeShadingColor(state, srcMode, &color, sColor); - sColor += nComps; - } - - dataPtr = tBitmap->getDataPtr(); - alphaPtr = tBitmap->getAlphaPtr(); - for (y = 0; y < bitmapHeight; ++y) { - for (x = 0; x < bitmapWidth; ++x) { - - // convert coords to user space - tx = ixMin + x + 0.5; - ty = iyMin + y + 0.5; - xx = tx * ictm[0] + ty * ictm[2] + ictm[4]; - yy = tx * ictm[1] + ty * ictm[3] + ictm[5]; - - // compute the position along the axis - s = ((xx - x0) * dx + (yy - y0) * dy) * d; - go = gFalse; - if (s < 0) { - go = ext0; - } else if (s > 1) { - go = ext1; + for (iy = iyMin; iy < iyMax; ++iy) { + for (ix = ixMin; ix < ixMax; ++ix) { + x = (int)(adjXMin + ix * xStepX + iy * yStepX + 0.5); + y = (int)(adjYMin + ix * xStepY + iy * yStepY + 0.5); + if (overprintMaskBitmap) { + splash->compositeWithOverprint(tileBitmap, overprintMaskBitmap, + 0, 0, x, y, tileW, tileH, + gFalse, gFalse); } else { - go = gTrue; - } - if (!go) { - dataPtr += nComps; - ++alphaPtr; - continue; + splash->composite(tileBitmap, 0, 0, x, y, tileW, tileH, + gFalse, gFalse); } - if (s <= 0) { - sColor = sColors; - } else if (s >= 1) { - sColor = sColors + (nColors - 1) * nComps; - } else { - i = (int)((nColors - 1) * s + 0.5); - sColor = sColors + i * nComps; - } - for (i = 0; i < nComps; ++i) { - *dataPtr++ = sColor[i]; - } - *alphaPtr++ = 0xff; } } } - // composite the bitmap - setOverprintMask(state, state->getFillColorSpace(), - state->getFillOverprint(), state->getOverprintMode(), - NULL); - splash->composite(tBitmap, 0, 0, ixMin, iyMin, bitmapWidth, bitmapHeight, - clipRes == splashClipAllInside, gFalse); - - gfree(sColors); - delete tBitmap; - - return gTrue; + gfree(overprintMaskBitmap); + delete tileBitmap; } -GBool SplashOutputDev::radialShadedFill(GfxState *state, - GfxRadialShading *shading) { - double x0, y0, r0, x1, y1, r1, t0, t1, h; - GBool ext0, ext1, enclosed; - double uxMin, uyMin, uxMax, uyMax, det; - double *ctm; - double ictm[6]; - double xMin, yMin, xMax, yMax, tx, ty, xx, yy; - double dx, dy, dr, r0dr, r02, a, a2, b, c, e, es, s, s0, s1, rs0, rs1, t; - GBool aIsZero, go; - int ixMin, iyMin, ixMax, iyMax, bitmapWidth, bitmapHeight, nColors; - int bxMin, byMin, bxMax, byMax; - SplashClipResult clipRes; - SplashColorMode srcMode; - SplashBitmap *tBitmap; - int nComps; - int x, y, i; - SplashColorPtr dataPtr; - Guchar *alphaPtr; - GfxColor color; - SplashColorPtr sColors, sColor; - - - // get the shading parameters - shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1); - t0 = shading->getDomain0(); - t1 = shading->getDomain1(); - ext0 = shading->getExtend0(); - ext1 = shading->getExtend1(); - h = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); - enclosed = fabs(r1 - r0) >= h; - - // get clip region (in user space) - state->getUserClipBBox(&uxMin, &uyMin, &uxMax, &uyMax); - if (uxMin > uxMax || uyMin > uyMax) { - return gTrue; - } +GBool SplashOutputDev::shadedFill(GfxState *state, GfxShading *shading) { - // intersect with shading region (in user space): if the extend - // flags are false (or just the larger extend flag is false, in the - // "enclosed" case), we can use the bbox for the two circles - if ((!ext0 && !ext1) || - (enclosed && !(r0 > r1 ? ext0 : ext1))) { - tx = (x0 - r0) < (x1 - r1) ? (x0 - r0) : (x1 - r1); - if (tx > uxMin) { - uxMin = tx; - } - tx = (x0 + r0) > (x1 + r1) ? (x0 + r0) : (x1 + r1); - if (tx < uxMax) { - uxMax = tx; - } - ty = (y0 - r0) < (y1 - r1) ? (y0 - r0) : (y1 - r1); - if (ty > uyMin) { - uyMin = ty; - } - ty = (y0 + r0) > (y1 + r1) ? (y0 + r0) : (y1 + r1); - if (ty < uyMax) { - uyMax = ty; - } - } - if (uxMin > uxMax || uyMin > uyMax) { - return gTrue; - } - - // convert the region to device space - ctm = state->getCTM(); - tx = uxMin * ctm[0] + uyMin * ctm[2] + ctm[4]; - ty = uxMin * ctm[1] + uyMin * ctm[3] + ctm[5]; - xMin = xMax = tx; - yMin = yMax = ty; - tx = uxMin * ctm[0] + uyMax * ctm[2] + ctm[4]; - ty = uxMin * ctm[1] + uyMax * ctm[3] + ctm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - tx = uxMax * ctm[0] + uyMin * ctm[2] + ctm[4]; - ty = uxMax * ctm[1] + uyMin * ctm[3] + ctm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - tx = uxMax * ctm[0] + uyMax * ctm[2] + ctm[4]; - ty = uxMax * ctm[1] + uyMax * ctm[3] + ctm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - ixMin = (int)floor(xMin); - iyMin = (int)floor(yMin); - ixMax = (int)floor(xMax) + 1; - iyMax = (int)floor(yMax) + 1; - clipRes = splash->limitRectToClipRect(&ixMin, &iyMin, &ixMax, &iyMax); - if (clipRes == splashClipAllOutside) { - return gTrue; - } - - // allocate a bitmap + // generate the bitmap + SplashColorMode srcMode; if (colorMode == splashModeMono1) { srcMode = splashModeMono8; } else if (colorMode == splashModeBGR8) { @@ -2440,287 +2097,36 @@ GBool SplashOutputDev::radialShadedFill(GfxState *state, } else { srcMode = colorMode; } - bitmapWidth = ixMax - ixMin; - bitmapHeight = iyMax - iyMin; - tBitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, - srcMode, gTrue, gTrue, bitmap); - memset(tBitmap->getAlphaPtr(), 0, tBitmap->getAlphaRowSize() * bitmapHeight); - nComps = splashColorModeNComps[srcMode]; - - // compute the inverse CTM - det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); - ictm[0] = ctm[3] * det; - ictm[1] = -ctm[1] * det; - ictm[2] = -ctm[2] * det; - ictm[3] = ctm[0] * det; - ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; - ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; - - // pre-compute colors along the axis - nColors = (int)sqrt((double)(bitmapWidth * bitmapWidth - + bitmapHeight * bitmapHeight)); - if (nColors < 16) { - nColors = 16; - } else if (nColors > 1024) { - nColors = 1024; - } - sColors = (SplashColorPtr)gmallocn(nColors, nComps); - sColor = sColors; - for (i = 0; i < nColors; ++i) { - s = (double)i / (double)(nColors - 1); - t = t0 + s * (t1 - t0); - shading->getColor(t, &color); - computeShadingColor(state, srcMode, &color, sColor); - sColor += nComps; - } - - // special case: in the "enclosed" + extended case, we can fill the - // bitmap with the outer color and just render inside the larger - // circle - if (enclosed && - ((r0 > r1 && ext0) || (r1 > r0 && ext1))) { - if (r0 > r1) { - sColor = sColors; - uxMin = x0 - r0; - uxMax = x0 + r0; - uyMin = y0 - r0; - uyMax = y0 + r0; - } else { - sColor = sColors + (nColors - 1) * nComps; - uxMin = x1 - r1; - uxMax = x1 + r1; - uyMin = y1 - r1; - uyMax = y1 + r1; - } - - // convert bbox of larger circle to device space - tx = uxMin * ctm[0] + uyMin * ctm[2] + ctm[4]; - ty = uxMin * ctm[1] + uyMin * ctm[3] + ctm[5]; - xMin = xMax = tx; - yMin = yMax = ty; - tx = uxMin * ctm[0] + uyMax * ctm[2] + ctm[4]; - ty = uxMin * ctm[1] + uyMax * ctm[3] + ctm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - tx = uxMax * ctm[0] + uyMin * ctm[2] + ctm[4]; - ty = uxMax * ctm[1] + uyMin * ctm[3] + ctm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - tx = uxMax * ctm[0] + uyMax * ctm[2] + ctm[4]; - ty = uxMax * ctm[1] + uyMax * ctm[3] + ctm[5]; - if (tx < xMin) { - xMin = tx; - } else if (tx > xMax) { - xMax = tx; - } - if (ty < yMin) { - yMin = ty; - } else if (ty > yMax) { - yMax = ty; - } - bxMin = (int)floor(xMin - ixMin); - if (bxMin < 0) { - bxMin = 0; - } - byMin = (int)floor(yMin - iyMin); - if (byMin < 0) { - byMin = 0; - } - bxMax = (int)floor(xMax - ixMin) + 1; - if (bxMax > bitmapWidth) { - bxMax = bitmapWidth; - } - byMax = (int)floor(yMax - iyMin) + 1; - if (byMax > bitmapHeight) { - byMax = bitmapHeight; - } - - // fill bitmap (except for the rectangle containing the larger circle) - dataPtr = tBitmap->getDataPtr(); - alphaPtr = tBitmap->getAlphaPtr(); - for (y = 0; y < bitmapHeight; ++y) { - for (x = 0; x < bitmapWidth; ++x) { - if (y >= byMin && y < byMax && x >= bxMin && x < bxMax) { - dataPtr += nComps; - ++alphaPtr; - } else { - for (i = 0; i < nComps; ++i) { - *dataPtr++ = sColor[i]; - } - *alphaPtr++ = 0xff; - } - } - } - - } else { - bxMin = 0; - byMin = 0; - bxMax = bitmapWidth; - byMax = bitmapHeight; - } - - // render the shading into the bitmap - dx = x1 - x0; - dy = y1 - y0; - dr = r1 - r0; - r0dr = r0 * dr; - r02 = r0 * r0; - a = dx * dx + dy * dy - dr * dr; - if (fabs(a) < 0.00001) { - aIsZero = gTrue; - a2 = 0; - } else { - aIsZero = gFalse; - a2 = 1 / (2 * a); - } - for (y = byMin; y < byMax; ++y) { - dataPtr = tBitmap->getDataPtr() - + y * tBitmap->getRowSize() + bxMin * nComps; - alphaPtr = tBitmap->getAlphaPtr() + y * tBitmap->getAlphaRowSize() + bxMin; - for (x = bxMin; x < bxMax; ++x) { - - // convert coords to user space - tx = ixMin + x + 0.5; - ty = iyMin + y + 0.5; - xx = tx * ictm[0] + ty * ictm[2] + ictm[4]; - yy = tx * ictm[1] + ty * ictm[3] + ictm[5]; - - // compute the radius of the circle at x,y - b = 2 * ((xx - x0) * dx + (yy - y0) * dy + r0dr); - c = (xx - x0) * (xx - x0) + (yy - y0) * (yy - y0) - r02; - s = 0; - go = gFalse; - if (aIsZero) { - if (fabs(b) < 0.000001) { - if (c <= 0) { - if (ext0) { - s = 0; - go = gTrue; - } - } else { - if (ext1) { - s = 1; - go = gTrue; - } - } - } else { - s0 = c / b; - rs0 = r0 + s0 * (r1 - r0); - if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) { - s = s0; - go = gTrue; - } - } - } else { - e = b*b - 4*a*c; - if (e >= 0) { - es = sqrt(e); - s0 = (b + es) * a2; - s1 = (b - es) * a2; - rs0 = r0 + s0 * (r1 - r0); - rs1 = r0 + s1 * (r1 - r0); - if (s0 > s1) { - if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) { - s = s0; - go = gTrue; - } else if ((s1 >= 0 || ext0) && (s1 <= 1 || ext1) && rs1 >= 0) { - s = s1; - go = gTrue; - } - } else { - if ((s1 >= 0 || ext0) && (s1 <= 1 || ext1) && rs1 >= 0) { - s = s1; - go = gTrue; - } else if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) { - s = s0; - go = gTrue; - } - } - } - } - if (!go) { - dataPtr += nComps; - ++alphaPtr; - continue; - } - if (s <= 0) { - sColor = sColors; - } else if (s >= 1) { - sColor = sColors + (nColors - 1) * nComps; - } else { - i = (int)((nColors - 1) * s + 0.5); - sColor = sColors + i * nComps; - } - for (i = 0; i < nComps; ++i) { - *dataPtr++ = sColor[i]; - } - *alphaPtr++ = 0xff; - } + int x, y; + SplashBitmap *tBitmap = ShadingImage::generateBitmap(state, shading, srcMode, + reverseVideo, + splash, bitmap, &x, &y); + if (!tBitmap) { + // clip region is empty - nothing to draw + return gTrue; } - // composite the bitmap - setOverprintMask(state, state->getFillColorSpace(), - state->getFillOverprint(), state->getOverprintMode(), - NULL); - splash->composite(tBitmap, 0, 0, ixMin, iyMin, bitmapWidth, bitmapHeight, - clipRes == splashClipAllInside, gFalse); + // check clipping and composite the bitmap + int xMin = x; + int yMin = y; + int xMax = x + tBitmap->getWidth(); + int yMax = y + tBitmap->getHeight(); + SplashClipResult clipRes = splash->limitRectToClipRect(&xMin, &yMin, + &xMax, &yMax); + if (clipRes != splashClipAllOutside) { + setOverprintMask(state, state->getFillColorSpace(), + state->getFillOverprint(), state->getOverprintMode(), + NULL); + splash->composite(tBitmap, xMin - x, yMin - y, xMin, yMin, + xMax - xMin, yMax - yMin, + clipRes == splashClipAllInside, gFalse); + } - gfree(sColors); delete tBitmap; return gTrue; } -void SplashOutputDev::computeShadingColor(GfxState *state, - SplashColorMode mode, - GfxColor *color, - SplashColorPtr sColor) { - GfxGray gray; - GfxRGB rgb; -#if SPLASH_CMYK - GfxCMYK cmyk; -#endif - - state->setFillColor(color); - switch (mode) { - case splashModeMono8: - state->getFillGray(&gray); - getColor(gray, sColor); - break; - case splashModeRGB8: - state->getFillRGB(&rgb); - getColor(&rgb, sColor); - break; -#if SPLASH_CMYK - case splashModeCMYK8: - state->getFillCMYK(&cmyk); - getColor(&cmyk, sColor); - break; -#endif - case splashModeMono1: - case splashModeBGR8: - // mode cannot be Mono1 or BGR8 - break; - } -} - - void SplashOutputDev::clip(GfxState *state) { SplashPath *path; @@ -2799,6 +2205,7 @@ void SplashOutputDev::drawChar(GfxState *state, double x, double y, if (skipHorizText || skipRotatedText) { state->getFontTransMat(&m[0], &m[1], &m[2], &m[3]); + // this matches the 'diagonal' test in TextPage::updateFont() horiz = m[0] > 0 && fabs(m[1]) < 0.001 && fabs(m[2]) < 0.001 && m[3] < 0; if ((skipHorizText && horiz) || (skipRotatedText && !horiz)) { @@ -2924,7 +2331,7 @@ GBool SplashOutputDev::beginType3Char(GfxState *state, double x, double y, } if (!(gfxFont = state->getFont())) { - return gFalse; + return gTrue; } fontID = gfxFont->getID(); ctm = state->getCTM(); @@ -2954,7 +2361,7 @@ GBool SplashOutputDev::beginType3Char(GfxState *state, double x, double y, } } else { for (j = nT3Fonts - 1; j >= 0; --j) { - if (!t3FontCache[j]->inUse) { + if (t3FontCache[j]->refCount == 0) { break; } } @@ -3038,7 +2445,11 @@ GBool SplashOutputDev::beginType3Char(GfxState *state, double x, double y, } } - t3Font->inUse = gTrue; + if (t3Font->refCount > 1000) { + error(errSyntaxError, -1, "Type 3 CharProcs nested too deeply"); + return gTrue; + } + ++t3Font->refCount; // push a new Type 3 glyph record t3gs = new T3GlyphStack(); @@ -3050,6 +2461,10 @@ GBool SplashOutputDev::beginType3Char(GfxState *state, double x, double y, t3GlyphStack->cacheData = NULL; t3GlyphStack->haveDx = gFalse; t3GlyphStack->doNotCache = gFalse; +#if 1 //~t3-sa + t3GlyphStack->savedStrokeAdjust = splash->getStrokeAdjust(); + splash->setStrokeAdjust(splashStrokeAdjustOff); +#endif return gFalse; } @@ -3074,9 +2489,12 @@ void SplashOutputDev::endType3Char(GfxState *state) { drawType3Glyph(state, t3GlyphStack->cache, t3GlyphStack->cacheTag, t3GlyphStack->cacheData); } +#if 1 //~t3-sa + splash->setStrokeAdjust(t3GlyphStack->savedStrokeAdjust); +#endif t3gs = t3GlyphStack; t3GlyphStack = t3gs->next; - t3gs->cache->inUse = gFalse; + --t3gs->cache->refCount; delete t3gs; } @@ -3186,6 +2604,7 @@ void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, // create the temporary bitmap if (colorMode == splashModeMono1) { colorMode = splashModeMono1; + traceMessage("T3 glyph bitmap"); bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1, splashModeMono1, gFalse, gTrue, bitmap); splash = new Splash(bitmap, gFalse, @@ -3196,6 +2615,7 @@ void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, color[0] = 0xff; } else { colorMode = splashModeMono8; + traceMessage("T3 glyph bitmap"); bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1, splashModeMono8, gFalse, gTrue, bitmap); splash = new Splash(bitmap, vectorAntialias, @@ -3299,7 +2719,7 @@ void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, imgMaskData.height = height; imgMaskData.y = 0; - imgTag = makeImageTag(ref); + imgTag = makeImageTag(ref, gfxRenderingIntentRelativeColorimetric, NULL); splash->fillImageMask(imgTag, &imageMaskSrc, &imgMaskData, width, height, mat, t3GlyphStack != NULL, interpolate); @@ -3344,6 +2764,7 @@ void SplashOutputDev::setSoftMaskFromImageMask(GfxState *state, imgMaskData.width = width; imgMaskData.height = height; imgMaskData.y = 0; + traceMessage("image mask soft mask bitmap"); maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, gFalse, gTrue, bitmap); maskSplash = new Splash(maskBitmap, gTrue, splash->getImageCache()); @@ -3354,7 +2775,7 @@ void SplashOutputDev::setSoftMaskFromImageMask(GfxState *state, clearMaskRegion(state, maskSplash, 0, 0, 1, 1); maskColor[0] = 0xff; maskSplash->setFillPattern(new SplashSolidColor(maskColor)); - imgTag = makeImageTag(ref); + imgTag = makeImageTag(ref, gfxRenderingIntentRelativeColorimetric, NULL); maskSplash->fillImageMask(imgTag, &imageMaskSrc, &imgMaskData, width, height, mat, gFalse, interpolate); delete imgTag; @@ -3643,7 +3064,8 @@ void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, srcMode = colorMode; } src = maskColors ? &alphaImageSrc : &imageSrc; - imgTag = makeImageTag(ref); + imgTag = makeImageTag(ref, state->getRenderingIntent(), + colorMap->getColorSpace()); splash->drawImage(imgTag, src, &imgData, srcMode, maskColors ? gTrue : gFalse, width, height, mat, interpolate); @@ -3775,9 +3197,9 @@ GBool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine, void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, int maskWidth, - int maskHeight, GBool maskInvert, - GBool interpolate) { + Object *maskRef, Stream *maskStr, + int maskWidth, int maskHeight, + GBool maskInvert, GBool interpolate) { GfxImageColorMap *maskColorMap; Object maskDecode, decodeLow, decodeHigh; double *ctm; @@ -3817,7 +3239,7 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, new GfxDeviceGrayColorSpace()); maskDecode.free(); drawSoftMaskedImage(state, ref, str, width, height, colorMap, - maskStr, maskWidth, maskHeight, maskColorMap, + maskRef, maskStr, maskWidth, maskHeight, maskColorMap, NULL, interpolate); delete maskColorMap; @@ -3837,6 +3259,7 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, imgMaskData.width = maskWidth; imgMaskData.height = maskHeight; imgMaskData.y = 0; + traceMessage("masked image bitmap"); maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1, gFalse, gTrue, bitmap); maskSplash = new Splash(maskBitmap, gFalse, splash->getImageCache()); @@ -3931,7 +3354,8 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref, } else { srcMode = colorMode; } - imgTag = makeImageTag(ref); + imgTag = makeImageTag(ref, state->getRenderingIntent(), + colorMap->getColorSpace()); splash->drawImage(imgTag, &maskedImageSrc, &imgData, srcMode, gTrue, width, height, mat, interpolate); @@ -4055,7 +3479,7 @@ GBool SplashOutputDev::softMaskMatteImageSrc(void *data, void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, double *matte, GBool interpolate) { @@ -4147,18 +3571,13 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, #endif } //~ could add the matteImgData.lookup special case - if (colorMap->getBits() <= 8) { - n = 1 << maskColorMap->getBits(); - } else { - // GfxImageColorMap and ImageStream compress 16-bit samples to 8-bit - n = 1 << 8; - } matteImgData.colorMode = colorMode; matteImgData.invert = reverseVideo && reverseVideoInvertImages; matteImgData.width = width; matteImgData.height = height; matteImgData.y = 0; - imgTag = makeImageTag(ref); + imgTag = makeImageTag(ref, state->getRenderingIntent(), + colorMap->getColorSpace()); splash->drawImage(imgTag, &softMaskMatteImageSrc, &matteImgData, srcMode, gTrue, width, height, mat, interpolate); delete imgTag; @@ -4186,13 +3605,19 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, imgMaskData.width = maskWidth; imgMaskData.height = maskHeight; imgMaskData.y = 0; - n = 1 << maskColorMap->getBits(); + if (maskColorMap->getBits() <= 8) { + n = 1 << maskColorMap->getBits(); + } else { + // GfxImageColorMap and ImageStream compress 16-bit samples to 8-bit + n = 1 << 8; + } imgMaskData.lookup = (SplashColorPtr)gmalloc(n); for (i = 0; i < n; ++i) { pix = (Guchar)i; maskColorMap->getGray(&pix, &gray, state->getRenderingIntent()); imgMaskData.lookup[i] = colToByte(gray); } + traceMessage("soft masked image bitmap"); maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, gFalse, gTrue, bitmap); maskSplash = new Splash(maskBitmap, vectorAntialias, @@ -4273,7 +3698,8 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, } } - imgTag = makeImageTag(ref); + imgTag = makeImageTag(ref, state->getRenderingIntent(), + colorMap->getColorSpace()); splash->drawImage(imgTag, &imageSrc, &imgData, srcMode, gFalse, width, height, mat, interpolate); @@ -4288,11 +3714,14 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, } } -GString *SplashOutputDev::makeImageTag(Object *ref) { - if (!ref || !ref->isRef()) { +GString *SplashOutputDev::makeImageTag(Object *ref, GfxRenderingIntent ri, + GfxColorSpace *colorSpace) { + if (!ref || !ref->isRef() || + (colorSpace && colorSpace->isDefaultColorSpace())) { return NULL; } - return GString::format("{0:d}_{1:d}", ref->getRefNum(), ref->getRefGen()); + return GString::format("{0:d}_{1:d}_{2:d}", + ref->getRefNum(), ref->getRefGen(), (int)ri); } void SplashOutputDev::reduceImageResolution(Stream *str, double *ctm, @@ -4471,14 +3900,16 @@ void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox, ty = bh - 1; } w = (int)ceil(xMax) - tx + 1; - if (tx + w > bw) { + // NB bw and tx are both non-negative, so 'bw - tx' can't overflow + if (bw - tx < w) { w = bw - tx; } if (w < 1) { w = 1; } h = (int)ceil(yMax) - ty + 1; - if (ty + h > bh) { + // NB bh and ty are both non-negative, so 'bh - ty' can't overflow + if (bh - ty < h) { h = bh - ty; } if (h < 1) { @@ -4524,6 +3955,7 @@ void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox, } // create the temporary bitmap + traceMessage("t-group bitmap"); bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, gTrue, bitmapTopDown, transpGroup->origBitmap); splash = new Splash(bitmap, vectorAntialias, @@ -4535,32 +3967,70 @@ void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox, splash->setEnablePathSimplification( globalParams->getEnablePathSimplification()); copyState(transpGroup->origSplash, gTrue); + if (!isolated || knockout) { + // non-isolated and knockout groups nested in another group will + // read the parent group bitmap, so we need to force any deferred + // initialization on the parent + transpGroup->origSplash->forceDeferredInit(ty, h); + } if (isolated) { - for (i = 0; i < splashMaxColorComps; ++i) { - color[i] = 0; + // isolated group + backdropBitmap = transpGroup->origBitmap; + transpGroup->backdropBitmap = NULL; + if (forSoftMask) { + // setSoftMask uses the whole bitmap, not just the mod region, + // so we can't use the deferred initialization optimization + for (i = 0; i < splashMaxColorComps; ++i) { + color[i] = 0; + } + splash->clear(color, 0); + splash->setInTransparencyGroup(backdropBitmap, tx, ty, + splashGroupDestPreInit, + gFalse, knockout); + } else { + splash->setInTransparencyGroup(backdropBitmap, tx, ty, + splashGroupDestInitZero, + gFalse, knockout); } - splash->clear(color, 0); - } else { - splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h); - } - if (!isolated && - transpGroup->origBitmap->getAlphaPtr() && - transpGroup->origSplash->getInNonIsolatedGroup() && - colorMode != splashModeMono1) { - // when drawing a non-isolated group into another non-isolated group, + } else if (transpGroup->origBitmap->getAlphaPtr() && + transpGroup->origSplash->getInNonIsolatedGroup() && + colorMode != splashModeMono1) { + // non-isolated group drawn in another non-isolated group: // compute a backdrop bitmap with corrected alpha values + traceMessage("t-group backdrop bitmap"); backdropBitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, gTrue, bitmapTopDown, transpGroup->origBitmap); transpGroup->origSplash->blitCorrectedAlpha(backdropBitmap, tx, ty, 0, 0, w, h); transpGroup->backdropBitmap = backdropBitmap; - splash->setInTransparencyGroup(backdropBitmap, 0, 0, - !isolated, knockout); + if (forSoftMask) { + // setSoftMask uses the whole bitmap, not just the mod region, + // so we can't use the deferred initialization optimization + splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h); + splash->setInTransparencyGroup(backdropBitmap, 0, 0, + splashGroupDestPreInit, + gTrue, knockout); + } else { + splash->setInTransparencyGroup(backdropBitmap, 0, 0, + splashGroupDestInitCopy, + gTrue, knockout); + } } else { + // other non-isolated group backdropBitmap = transpGroup->origBitmap; transpGroup->backdropBitmap = NULL; - splash->setInTransparencyGroup(backdropBitmap, tx, ty, - !isolated, knockout); + if (forSoftMask) { + // setSoftMask uses the whole bitmap, not just the mod region, + // so we can't use the deferred initialization optimization + splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h); + splash->setInTransparencyGroup(backdropBitmap, tx, ty, + splashGroupDestPreInit, + gTrue, knockout); + } else { + splash->setInTransparencyGroup(backdropBitmap, tx, ty, + splashGroupDestInitCopy, + gTrue, knockout); + } } splash->clearModRegion(); transpGroup->tBitmap = bitmap; @@ -4632,14 +4102,17 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox, Splash *tSplash; SplashTransparencyGroup *transpGroup; SplashColor color; - SplashColorPtr p; + SplashColorPtr p, colorPtr, colorPtr2; + Guchar *alphaPtr; GfxGray gray; GfxRGB rgb; #if SPLASH_CMYK GfxCMYK cmyk; #endif double backdrop, backdrop2, lum, lum2; - int tx, ty, x, y; + Guchar lum8; + SplashBitmapRowSize rowSize; + int tw, th, tNComps, tx, ty, x, y; tx = transpGroupStack->tx; ty = transpGroupStack->ty; @@ -4709,51 +4182,72 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox, backdrop2 = backdrop; } + traceMessage("soft mask bitmap"); softMask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, gFalse, gTrue, bitmap); memset(softMask->getDataPtr(), (int)(backdrop2 * 255.0 + 0.5), softMask->getRowSize() * softMask->getHeight()); if (tx < softMask->getWidth() && ty < softMask->getHeight()) { p = softMask->getDataPtr() + ty * softMask->getRowSize() + tx; - for (y = 0; y < tBitmap->getHeight(); ++y) { - for (x = 0; x < tBitmap->getWidth(); ++x) { - if (alpha) { - lum = tBitmap->getAlpha(x, y) / 255.0; - } else { - tBitmap->getPixel(x, y, color); + tw = tBitmap->getWidth(); + th = tBitmap->getHeight(); + rowSize = softMask->getRowSize(); + if (alpha) { + alphaPtr = tBitmap->getAlphaPtr(); + for (y = 0; y < th; ++y) { + for (x = 0; x < tw; ++x) { + lum = *alphaPtr++ / 255.0; + if (transferFunc) { + transferFunc->transform(&lum, &lum2); + } else { + lum2 = lum; + } + p[x] = (Guchar)(lum2 * 255.0 + 0.5); + } + p += rowSize; + } + } else { + colorPtr = tBitmap->getDataPtr(); + tNComps = splashColorModeNComps[tBitmap->getMode()]; + lum8 = 0; // make gcc happy + for (y = 0; y < th; ++y) { + colorPtr2 = colorPtr; + for (x = 0; x < tw; ++x) { // convert to luminosity switch (tBitmap->getMode()) { case splashModeMono1: + lum8 = 0; + break; case splashModeMono8: - lum = color[0] / 255.0; + lum8 = colorPtr2[0]; break; case splashModeRGB8: case splashModeBGR8: - lum = (0.3 / 255.0) * color[0] + - (0.59 / 255.0) * color[1] + - (0.11 / 255.0) * color[2]; + // [0.3, 0.59, 0.11] * 255 = [77, 150, 28] + lum8 = div255(77 * colorPtr2[0] + + 150 * colorPtr2[1] + + 28 * colorPtr2[1]); break; #if SPLASH_CMYK case splashModeCMYK8: - lum = (1 - color[3] / 255.0) - - (0.3 / 255.0) * color[0] - - (0.59 / 255.0) * color[1] - - (0.11 / 255.0) * color[2]; - if (lum < 0) { - lum = 0; - } + lum8 = clip255(255 - colorPtr2[3] + - div255(77 * colorPtr2[0] + + 150 * colorPtr2[1] + + 28 * colorPtr2[2])); break; #endif } + if (transferFunc) { + lum = lum8 / 255.0; + transferFunc->transform(&lum, &lum2); + lum8 = (Guchar)(lum2 * 255.0 + 0.5); + } + p[x] = lum8; + colorPtr2 += tNComps; } - if (transferFunc) { - transferFunc->transform(&lum, &lum2); - } else { - lum2 = lum; - } - p[x] = (Guchar)(lum2 * 255.0 + 0.5); + p += rowSize; + colorPtr += tBitmap->getRowSize(); } - p += softMask->getRowSize(); } } splash->setSoftMask(softMask); @@ -4993,6 +4487,10 @@ void SplashOutputDev::copyState(Splash *oldSplash, GBool copyColors) { } #if 1 //~tmp: turn off anti-aliasing temporarily +// This was originally used with gradient shadings -- that's no longer +// necessary, now that shadings are all converted to device space +// images. It's still used with knockout groups, however, because the +// rasterizer doesn't properly separate opacity and shape. void SplashOutputDev::setInShading(GBool sh) { splash->setInShading(sh); } diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.h b/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.h index 2215f883d32..cbe47b61cb7 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.h @@ -69,11 +69,6 @@ public: // operations. virtual GBool useTilingPatternFill() { 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. - virtual GBool useShadedFills() { return gTrue; } - // Does this device use beginType3Char/endType3Char? Otherwise, // text in Type 3 fonts will be drawn with drawChar/drawString. virtual GBool interpretType3Chars() { return gTrue; } @@ -123,8 +118,7 @@ public: double *mat, double *bbox, int x0, int y0, int x1, int y1, double xStep, double yStep); - virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading); - virtual GBool radialShadedFill(GfxState *state, GfxRadialShading *shading); + virtual GBool shadedFill(GfxState *state, GfxShading *shading); //----- path clipping virtual void clip(GfxState *state); @@ -156,12 +150,13 @@ public: virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, int maskWidth, int maskHeight, + Object *maskRef, Stream *maskStr, + int maskWidth, int maskHeight, GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - Stream *maskStr, + Object *maskRef, Stream *maskStr, int maskWidth, int maskHeight, GfxImageColorMap *maskColorMap, double *matte, GBool interpolate); @@ -240,10 +235,6 @@ public: // Get the screen parameters. SplashScreenParams *getScreenParams() { return &screenParams; } -#if 1 //~tmp: turn off anti-aliasing temporarily - virtual void setInShading(GBool sh); -#endif - private: void setupScreenParams(double hDPI, double vDPI); @@ -260,10 +251,6 @@ private: void setOverprintMask(GfxState *state, GfxColorSpace *colorSpace, GBool overprintFlag, int overprintMode, GfxColor *singleColor); - void computeShadingColor(GfxState *state, - SplashColorMode mode, - GfxColor *color, - SplashColorPtr sColor); SplashPath *convertPath(GfxState *state, GfxPath *path, GBool dropEmptySubpaths); void doUpdateFont(GfxState *state); @@ -279,7 +266,8 @@ private: static GBool softMaskMatteImageSrc(void *data, SplashColorPtr colorLine, Guchar *alphaLine); - GString *makeImageTag(Object *ref); + GString *makeImageTag(Object *ref, GfxRenderingIntent ri, + GfxColorSpace *colorSpace); void reduceImageResolution(Stream *str, double *mat, int *width, int *height); void clearMaskRegion(GfxState *state, @@ -287,6 +275,9 @@ private: double xMin, double yMin, double xMax, double yMax); void copyState(Splash *oldSplash, GBool copyColors); +#if 1 //~tmp: turn off anti-aliasing temporarily + void setInShading(GBool sh); +#endif SplashColorMode colorMode; int bitmapRowPad; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.cc index a7c13c9eab3..601add89be4 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.cc @@ -1317,6 +1317,7 @@ void LZWStream::reset() { eof = gFalse; inputBits = 0; clearTable(); + totalIn = totalOut = 0; } GBool LZWStream::processNextCode() { @@ -1382,6 +1383,14 @@ GBool LZWStream::processNextCode() { nextBits = 12; } prevCode = code; + totalOut += seqLength; + + // check for a 'decompression bomb' + if (totalOut > 50000000 && totalIn < totalOut / 250) { + error(errSyntaxError, getPos(), "Decompression bomb in flate stream"); + eof = gTrue; + return gFalse; + } // reset buffer seqIndex = 0; @@ -1405,6 +1414,7 @@ int LZWStream::getCode() { return EOF; inputBuf = (inputBuf << 8) | (c & 0xff); inputBits += 8; + ++totalIn; } code = (inputBuf >> (inputBits - nextBits)) & ((1 << nextBits) - 1); inputBits -= nextBits; @@ -2268,7 +2278,6 @@ short CCITTFaxStream::lookBits(int n) { GString *CCITTFaxStream::getPSFilter(int psLevel, const char *indent, GBool okToReadStream) { GString *s; - char s1[50]; if (psLevel < 2) { return NULL; @@ -2278,8 +2287,7 @@ GString *CCITTFaxStream::getPSFilter(int psLevel, const char *indent, } s->append(indent)->append("<< "); if (encoding != 0) { - sprintf(s1, "/K %d ", encoding); - s->append(s1); + s->appendf("/K {0:d} ", encoding); } if (endOfLine) { s->append("/EndOfLine true "); @@ -2287,11 +2295,9 @@ GString *CCITTFaxStream::getPSFilter(int psLevel, const char *indent, if (byteAlign) { s->append("/EncodedByteAlign true "); } - sprintf(s1, "/Columns %d ", columns); - s->append(s1); + s->appendf("/Columns {0:d} ", columns); if (rows != 0) { - sprintf(s1, "/Rows %d ", rows); - s->append(s1); + s->appendf("/Rows {0:d} ", rows); } if (!endOfBlock) { s->append("/EndOfBlock false "); @@ -4059,7 +4065,7 @@ GBool DCTStream::readQuantTables() { error(errSyntaxError, getPos(), "Bad DCT quantization table"); return gFalse; } - if (index == numQuantTables) { + if (index >= numQuantTables) { numQuantTables = index + 1; } for (i = 0; i < 64; ++i) { @@ -4951,6 +4957,8 @@ void FlateStream::reset() { endOfBlock = eof = gTrue; cmf = str->getChar(); flg = str->getChar(); + totalIn = 2; + totalOut = 0; if (cmf == EOF || flg == EOF) return; if ((cmf & 0x0f) != 0x08) { @@ -5017,7 +5025,7 @@ int FlateStream::getRawChar() { } int FlateStream::getBlock(char *blk, int size) { - int n; + int n, k; if (pred) { return pred->getBlock(blk, size); @@ -5031,11 +5039,17 @@ int FlateStream::getBlock(char *blk, int size) { } readSome(); } - while (remain && n < size) { - blk[n++] = buf[index]; - index = (index + 1) & flateMask; - --remain; + k = remain; + if (size - n < k) { + k = size - n; + } + if (flateWindow - index < k) { + k = flateWindow - index; } + memcpy(blk + n, buf + index, k); + n += k; + index = (index + k) & flateMask; + remain -= k; } return n; } @@ -5061,7 +5075,7 @@ GBool FlateStream::isBinary(GBool last) { void FlateStream::readSome() { int code1, code2; int len, dist; - int i, j, k; + int src, dest, n1, n2, n3, i, j, k; int c; if (endOfBlock) { @@ -5090,12 +5104,78 @@ void FlateStream::readSome() { if (code2 > 0 && (code2 = getCodeWord(code2)) == EOF) goto err; dist = distDecode[code1].first + code2; - i = index; - j = (index - dist) & flateMask; - for (k = 0; k < len; ++k) { - buf[i] = buf[j]; - i = (i + 1) & flateMask; - j = (j + 1) & flateMask; + dest = index; + src = (index - dist) & flateMask; + // the following is an optimized version of: + // for (k = 0; k < len; ++k) { + // buf[dest] = buf[src]; + // dest = (dest + 1) & flateMask; + // src = (src + 1) & flateMask; + // } + if (dest + len <= flateWindow) { + if (src + len <= flateWindow) { + for (k = 0; k < len; ++k) { + buf[dest + k] = buf[src + k]; + } + } else { + n1 = flateWindow - src; + n2 = len - n1; + for (k = 0; k < n1; ++k) { + buf[dest + k] = buf[src + k]; + } + dest = dest + n1; + src = 0; + for (k = 0; k < n2; ++k) { + buf[dest + k] = buf[src + k]; + } + } + } else { + if (src + len <= flateWindow) { + n1 = flateWindow - dest; + n2 = len - n1; + for (k = 0; k < n1; ++k) { + buf[dest + k] = buf[src + k]; + } + dest = 0; + src = src + n1; + for (k = 0; k < n2; ++k) { + buf[dest + k] = buf[src + k]; + } + } else if (src < dest) { + n1 = flateWindow - dest; + n2 = dest - src; + n3 = len - n1 - n2; + for (k = 0; k < n1; ++k) { + buf[dest + k] = buf[src + k]; + } + dest = 0; + src = src + n1; + for (k = 0; k < n2; ++k) { + buf[dest + k] = buf[src + k]; + } + dest = n2; + src = 0; + for (k = 0; k < n3; ++k) { + buf[dest + k] = buf[src + k]; + } + } else { + n1 = flateWindow - src; + n2 = src - dest; + n3 = len - n1 - n2; + for (k = 0; k < n1; ++k) { + buf[dest + k] = buf[src + k]; + } + dest = dest + n1; + src = 0; + for (k = 0; k < n2; ++k) { + buf[dest + k] = buf[src + k]; + } + dest = 0; + src = n2; + for (k = 0; k < n3; ++k) { + buf[dest + k] = buf[src + k]; + } + } } remain = len; } @@ -5113,6 +5193,15 @@ void FlateStream::readSome() { blockLen -= len; if (blockLen == 0) endOfBlock = gTrue; + totalIn += remain; + } + totalOut += remain; + + // check for a 'decompression bomb' + if (totalOut > 50000000 && totalIn < totalOut / 250) { + error(errSyntaxError, getPos(), "Decompression bomb in flate stream"); + endOfBlock = eof = gTrue; + remain = 0; } return; @@ -5164,6 +5253,7 @@ GBool FlateStream::startBlock() { "Bad uncompressed block length in flate stream"); codeBuf = 0; codeSize = 0; + totalIn += 4; // compressed block with fixed codes } else if (blockHdr == 1) { @@ -5358,6 +5448,7 @@ int FlateStream::getHuffmanCodeWord(FlateHuffmanTab *tab) { } codeBuf |= (c & 0xff) << codeSize; codeSize += 8; + ++totalIn; } code = &tab->codes[codeBuf & ((1 << tab->maxLen) - 1)]; if (codeSize == 0 || codeSize < code->len || code->len == 0) { @@ -5376,6 +5467,7 @@ int FlateStream::getCodeWord(int bits) { return EOF; codeBuf |= (c & 0xff) << codeSize; codeSize += 8; + ++totalIn; } c = codeBuf & ((1 << bits) - 1); codeBuf >>= bits; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.h b/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.h index 38a8dd3497c..59cafadc14f 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.h @@ -501,6 +501,8 @@ private: int seqLength; // length of current sequence int seqIndex; // index into current sequence GBool first; // first code after a table clear + unsigned long long totalIn; // total number of encoded bytes read so far + unsigned long long totalOut; // total number of bytes decoded so far GBool processNextCode(); void clearTable(); @@ -814,6 +816,8 @@ private: int blockLen; // remaining length of uncompressed block GBool endOfBlock; // set when end of block is reached GBool eof; // set when end of stream is reached + unsigned long long totalIn; // total number of encoded bytes read so far + unsigned long long totalOut; // total number of bytes decoded so far static int // code length code reordering codeLenCodeMap[flateMaxCodeLenCodes]; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc index 4f594384915..9ec906c7379 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc @@ -26,6 +26,7 @@ #include "gmempp.h" #include "GString.h" #include "GList.h" +#include "gfile.h" #include "config.h" #include "Error.h" #include "GlobalParams.h" @@ -88,11 +89,13 @@ // Inter-character spacing that varies by less than this multiple of // font size is assumed to be equivalent. #define uniformSpacing 0.07 +#define tableModeUniformSpacing 0.14 // Typical word spacing, as a fraction of font size. This will be // added to the minimum inter-character spacing, to account for wide // character spacing. #define wordSpacing 0.1 +#define tableModeWordSpacing 0.2 // Minimum paragraph indent from left margin, as a fraction of font // size. @@ -114,6 +117,9 @@ // value multiplied by font size. #define simpleLayoutGapThreshold 0.7 +// Minimum overlap in simple2 mode. +#define simple2MinOverlap 0.2 + // Table cells (TextColumns) are allowed to overlap by this much // in table layout mode (as a fraction of cell width or height). #define tableCellOverlapSlack 0.05 @@ -157,8 +163,27 @@ // rectangles, in order to work around flakey ascent values in fonts. #define selectionAscent 0.8 +// Grid size used to bin sort characters for overlap detection. +#define overlapGridWidth 20 +#define overlapGridHeight 20 + +// Minimum character bbox overlap (horizontal and vertical) as a +// fraction of character bbox width/height for a character to be +// treated as overlapping. +#define minCharOverlap 0.3 + #define maxUnicodeLen 16 +//------------------------------------------------------------------------ + +static inline double dmin(double x, double y) { + return x < y ? x : y; +} + +static inline double dmax(double x, double y) { + return x > y ? x : y; +} + //------------------------------------------------------------------------ // TextChar //------------------------------------------------------------------------ @@ -168,31 +193,36 @@ public: TextChar(Unicode cA, int charPosA, int charLenA, double xMinA, double yMinA, double xMaxA, double yMaxA, - int rotA, GBool clippedA, GBool invisibleA, + int rotA, GBool rotatedA, GBool clippedA, GBool invisibleA, TextFontInfo *fontA, double fontSizeA, double colorRA, double colorGA, double colorBA); static int cmpX(const void *p1, const void *p2); static int cmpY(const void *p1, const void *p2); + static int cmpCharPos(const void *p1, const void *p2); Unicode c; int charPos; int charLen; double xMin, yMin, xMax, yMax; - Guchar rot; - char clipped; - char invisible; - char spaceAfter; TextFontInfo *font; double fontSize; double colorR, colorG, colorB; + + // group the byte-size fields to minimize object size + Guchar rot; + char rotated; + char clipped; + char invisible; + char spaceAfter; + char overlap; }; TextChar::TextChar(Unicode cA, int charPosA, int charLenA, double xMinA, double yMinA, double xMaxA, double yMaxA, - int rotA, GBool clippedA, GBool invisibleA, + int rotA, GBool rotatedA, GBool clippedA, GBool invisibleA, TextFontInfo *fontA, double fontSizeA, double colorRA, double colorGA, double colorBA) { double t; @@ -228,6 +258,7 @@ TextChar::TextChar(Unicode cA, int charPosA, int charLenA, yMax = 1e8; } rot = (Guchar)rotA; + rotated = (char)rotatedA; clipped = (char)clippedA; invisible = (char)invisibleA; spaceAfter = (char)gFalse; @@ -236,6 +267,7 @@ TextChar::TextChar(Unicode cA, int charPosA, int charLenA, colorR = colorRA; colorG = colorGA; colorB = colorBA; + overlap = gFalse; } int TextChar::cmpX(const void *p1, const void *p2) { @@ -264,6 +296,12 @@ int TextChar::cmpY(const void *p1, const void *p2) { } } +int TextChar::cmpCharPos(const void *p1, const void *p2) { + const TextChar *ch1 = *(const TextChar **)p1; + const TextChar *ch2 = *(const TextChar **)p2; + return ch1->charPos - ch2->charPos; +} + //------------------------------------------------------------------------ // TextBlock //------------------------------------------------------------------------ @@ -407,6 +445,40 @@ void TextBlock::updateBounds(int childIdx) { } } +//------------------------------------------------------------------------ +// TextCharLine +//------------------------------------------------------------------------ + +class TextCharLine { +public: + + TextCharLine(int rotA); + ~TextCharLine(); + void add(TextChar *ch); + + GList *chars; + double yMin, yMax; + int rot; + TextCharLine *next, *prev; +}; + +TextCharLine::TextCharLine(int rotA) { + chars = new GList(); + yMin = yMax = 0; + rot = rotA; + next = prev = NULL; +} + +TextCharLine::~TextCharLine() { + delete chars; +} + +void TextCharLine::add(TextChar *ch) { + chars->append(ch); + yMin = ch->yMin; + yMax = ch->yMax; +} + //------------------------------------------------------------------------ // TextGaps //------------------------------------------------------------------------ @@ -539,8 +611,12 @@ TextOutputControl::TextOutputControl() { html = gFalse; clipText = gFalse; discardDiagonalText = gFalse; + discardRotatedText = gFalse; discardInvisibleText = gFalse; discardClippedText = gFalse; + splitRotatedWords = gFalse; + overlapHandling = textOutIgnoreOverlaps; + separateLargeChars = gTrue; insertBOM = gFalse; marginLeft = 0; marginRight = 0; @@ -602,13 +678,15 @@ TextFontInfo::~TextFontInfo() { } GBool TextFontInfo::matches(GfxState *state) { - Ref *id; + Ref id; - if (!state->getFont()) { - return gFalse; + if (state->getFont()) { + id = *state->getFont()->getID(); + } else { + id.num = -1; + id.gen = -1; } - id = state->getFont()->getID(); - return id->num == fontID.num && id->gen == fontID.gen; + return id.num == fontID.num && id.gen == fontID.gen; } //------------------------------------------------------------------------ @@ -618,11 +696,12 @@ GBool TextFontInfo::matches(GfxState *state) { // Build a TextWord object, using chars[start .. start+len-1]. // (If rot >= 2, the chars list is in reverse order.) TextWord::TextWord(GList *chars, int start, int lenA, - int rotA, int dirA, GBool spaceAfterA) { + int rotA, GBool rotatedA, int dirA, GBool spaceAfterA) { TextChar *ch; int i; - rot = rotA; + rot = (char)rotA; + rotated = (char)rotatedA; len = lenA; text = (Unicode *)gmallocn(len, sizeof(Unicode)); edge = (double *)gmallocn(len + 1, sizeof(double)); @@ -680,8 +759,8 @@ TextWord::TextWord(GList *chars, int start, int lenA, ch = (TextChar *)chars->get(start); font = ch->font; fontSize = ch->fontSize; - dir = dirA; - spaceAfter = spaceAfterA; + dir = (char)dirA; + spaceAfter = (char)spaceAfterA; underlined = gFalse; link = NULL; colorR = ch->colorR; @@ -1057,6 +1136,7 @@ TextPage::TextPage(TextOutputControl *controlA) { curFontSize = 0; curRot = 0; diagonal = gFalse; + rotated = gFalse; nTinyChars = 0; actualText = NULL; actualTextLen = 0; @@ -1108,6 +1188,7 @@ void TextPage::clear() { curFontSize = 0; curRot = 0; diagonal = gFalse; + rotated = gFalse; nTinyChars = 0; gfree(actualText); actualText = NULL; @@ -1219,7 +1300,12 @@ void TextPage::updateFont(GfxState *state) { m[2] = m2[2]; m[3] = m2[3]; } - if (fabs(m[0]) >= fabs(m[1])) { + if (curFontSize == 0) { + // special case - if the font size is zero, just assume plain + // horizontal text + curRot = 0; + diagonal = gFalse; + } else if (fabs(m[0]) >= fabs(m[1])) { if (m[0] > 0) { curRot = 0; } else { @@ -1234,6 +1320,9 @@ void TextPage::updateFont(GfxState *state) { } diagonal = fabs(m[0]) > diagonalThreshold * fabs(m[1]); } + // this matches the 'horiz' test in SplashOutputDev::drawChar() + rotated = !(m[0] > 0 && fabs(m[1]) < 0.001 && + fabs(m[2]) < 0.001 && m[3] < 0); } void TextPage::addChar(GfxState *state, double x, double y, @@ -1260,8 +1349,9 @@ void TextPage::addChar(GfxState *state, double x, double y, return; } - // throw away diagonal chars - if (control.discardDiagonalText && diagonal) { + // throw away diagonal/rotated chars + if ((control.discardDiagonalText && diagonal) || + (control.discardRotatedText && rotated)) { charPos += nBytes; return; } @@ -1299,9 +1389,15 @@ void TextPage::addChar(GfxState *state, double x, double y, } // skip space, tab, and non-breaking space characters - if (uLen == 1 && (u[0] == (Unicode)0x20 || - u[0] == (Unicode)0x09 || - u[0] == (Unicode)0xa0)) { + // (ActualText spans can result in multiple space chars) + for (i = 0; i < uLen; ++i) { + if (u[i] != (Unicode)0x20 && + u[i] != (Unicode)0x09 && + u[i] != (Unicode)0xa0) { + break; + } + } + if (i == uLen && uLen >= 1) { charPos += nBytes; if (chars->getLength() > 0) { ((TextChar *)chars->get(chars->getLength() - 1))->spaceAfter = @@ -1400,7 +1496,7 @@ void TextPage::addChar(GfxState *state, double x, double y, } chars->append(new TextChar(uBuf[j], charPos, nBytes, xMin, yMin, xMax, yMax, - curRot, clipped, + curRot, rotated, clipped, state->getRender() == 3 || alpha < 0.001, curFont, curFontSize, colToDbl(rgb.r), colToDbl(rgb.g), @@ -1503,6 +1599,10 @@ void TextPage::write(void *outputStream, TextOutputFunc outputFunc) { writeSimpleLayout(outputStream, outputFunc, uMap, space, spaceLen, eol, eolLen); break; + case textOutSimple2Layout: + writeSimple2Layout(outputStream, outputFunc, uMap, space, spaceLen, + eol, eolLen); + break; case textOutLinePrinter: writeLinePrinter(outputStream, outputFunc, uMap, space, spaceLen, eol, eolLen); @@ -1530,11 +1630,20 @@ void TextPage::writeReadingOrder(void *outputStream, TextColumn *col; TextParagraph *par; TextLine *line; + GList *overlappingChars; GList *columns; GBool primaryLR; GString *s; int colIdx, parIdx, lineIdx, rot, n; +#if 0 //~debug + dumpChars(chars); +#endif + if (control.overlapHandling != textOutIgnoreOverlaps) { + overlappingChars = separateOverlappingText(chars); + } else { + overlappingChars = NULL; + } rot = rotateChars(chars); primaryLR = checkPrimaryLR(chars); tree = splitChars(chars); @@ -1553,6 +1662,12 @@ void TextPage::writeReadingOrder(void *outputStream, rotateUnderlinesAndLinks(rot); generateUnderlinesAndLinks(columns); } + if (overlappingChars) { + if (overlappingChars->getLength() > 0) { + columns->append(buildOverlappingTextColumn(overlappingChars)); + } + deleteGList(overlappingChars, TextChar); + } #if 0 //~debug dumpColumns(columns); #endif @@ -1585,23 +1700,43 @@ void TextPage::writeReadingOrder(void *outputStream, GList *TextPage::makeColumns() { TextBlock *tree; + GList *overlappingChars; GList *columns; GBool primaryLR; int rot; - rot = rotateChars(chars); - primaryLR = checkPrimaryLR(chars); - if ((tree = splitChars(chars))) { - columns = buildColumns(tree, primaryLR); - delete tree; + if (control.mode == textOutSimple2Layout) { + primaryLR = checkPrimaryLR(chars); + rotateCharsToZero(chars); + columns = buildSimple2Columns(chars); + unrotateCharsFromZero(chars); + unrotateColumnsFromZero(columns); } else { - // no text - columns = new GList(); - } - unrotateChars(chars, rot); - unrotateColumns(columns, rot); - if (control.html) { - generateUnderlinesAndLinks(columns); + if (control.overlapHandling != textOutIgnoreOverlaps) { + overlappingChars = separateOverlappingText(chars); + } else { + overlappingChars = NULL; + } + rot = rotateChars(chars); + primaryLR = checkPrimaryLR(chars); + if ((tree = splitChars(chars))) { + columns = buildColumns(tree, primaryLR); + delete tree; + } else { + // no text + columns = new GList(); + } + unrotateChars(chars, rot); + unrotateColumns(columns, rot); + if (control.html) { + generateUnderlinesAndLinks(columns); + } + if (overlappingChars) { + if (overlappingChars->getLength() > 0) { + columns->append(buildOverlappingTextColumn(overlappingChars)); + } + deleteGList(overlappingChars, TextChar); + } } return columns; } @@ -1618,6 +1753,7 @@ void TextPage::writePhysLayout(void *outputStream, TextColumn *col; TextParagraph *par; TextLine *line; + GList *overlappingChars; GList *columns; GBool primaryLR; int ph, colIdx, parIdx, lineIdx, rot, y, i; @@ -1628,6 +1764,11 @@ void TextPage::writePhysLayout(void *outputStream, #if 0 //~debug dumpUnderlines(); #endif + if (control.overlapHandling != textOutIgnoreOverlaps) { + overlappingChars = separateOverlappingText(chars); + } else { + overlappingChars = NULL; + } rot = rotateChars(chars); primaryLR = checkPrimaryLR(chars); tree = splitChars(chars); @@ -1700,6 +1841,29 @@ void TextPage::writePhysLayout(void *outputStream, gfree(outLen); deleteGList(columns, TextColumn); + + if (overlappingChars) { + if (overlappingChars->getLength() > 0) { + TextColumn *col = buildOverlappingTextColumn(overlappingChars); + (*outputFunc)(outputStream, eol, eolLen); + for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + GString *s = new GString(); + encodeFragment(line->text, line->len, uMap, primaryLR, s); + s->append(eol, eolLen); + (*outputFunc)(outputStream, s->getCString(), s->getLength()); + delete s; + } + if (parIdx < col->paragraphs->getLength() - 1) { + (*outputFunc)(outputStream, eol, eolLen); + } + } + delete col; + } + deleteGList(overlappingChars, TextChar); + } } void TextPage::writeSimpleLayout(void *outputStream, @@ -1762,6 +1926,46 @@ void TextPage::writeSimpleLayout(void *outputStream, deleteGList(superLines, TextSuperLine); } +void TextPage::writeSimple2Layout(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen) { + GList *columns; + TextColumn *col; + TextParagraph *par; + TextLine *line; + GString *out; + GBool primaryLR; + int colIdx, parIdx, lineIdx; + + primaryLR = checkPrimaryLR(chars); + rotateCharsToZero(chars); +#if 0 //~debug + dumpChars(chars); +#endif + columns = buildSimple2Columns(chars); + unrotateCharsFromZero(chars); + unrotateColumnsFromZero(columns); + + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + out = new GString(); + encodeFragment(line->text, line->len, uMap, primaryLR, out); + (*outputFunc)(outputStream, out->getCString(), out->getLength()); + delete out; + (*outputFunc)(outputStream, eol, eolLen); + } + } + } + + deleteGList(columns, TextColumn); +} + void TextPage::writeLinePrinter(void *outputStream, TextOutputFunc outputFunc, UnicodeMap *uMap, @@ -1778,7 +1982,8 @@ void TextPage::writeLinePrinter(void *outputStream, rot = rotateChars(chars); chars->sort(&TextChar::cmpX); - removeDuplicates(chars, 0); + // don't call removeDuplicates here, because it expects to be + // working on a secondary list that doesn't own the TextChar objects chars->sort(&TextChar::cmpY); // get character pitch @@ -1796,7 +2001,7 @@ void TextPage::writeLinePrinter(void *outputStream, ch->yMin + ascentAdjustFactor * (ch->yMax - ch->yMin) < ch2->yMax - descentAdjustFactor * (ch2->yMax - ch2->yMin)) { delta = fabs(ch2->xMin - ch->xMin); - if (delta > 0 && delta < pitch) { + if (delta > 0.01 && delta < pitch) { pitch = delta; } } @@ -2135,6 +2340,53 @@ int TextPage::rotateChars(GList *charsA) { return rot; } +// Rotate all chars to zero rotation. This leaves the TextChar.rot +// fields unchanged. +void TextPage::rotateCharsToZero(GList *charsA) { + TextChar *ch; + double xMin, yMin, xMax, yMax; + int i; + + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + switch (ch->rot) { + case 0: + default: + break; + case 1: + xMin = ch->yMin; + xMax = ch->yMax; + yMin = pageWidth - ch->xMax; + yMax = pageWidth - ch->xMin; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + break; + case 2: + xMin = pageWidth - ch->xMax; + xMax = pageWidth - ch->xMin; + yMin = pageHeight - ch->yMax; + yMax = pageHeight - ch->yMin; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + break; + case 3: + xMin = pageHeight - ch->yMax; + xMax = pageHeight - ch->yMin; + yMin = ch->xMin; + yMax = ch->xMax; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + break; + } + } +} + // Rotate the TextUnderlines and TextLinks to match the transform // performed by rotateChars(). void TextPage::rotateUnderlinesAndLinks(int rot) { @@ -2282,7 +2534,225 @@ void TextPage::unrotateChars(GList *charsA, int rot) { ch->yMax = yMax; ch->rot = (ch->rot + 3) & 3; } - break; + break; + } +} + +// Undo the coordinate transform performed by rotateCharsToZero(). +void TextPage::unrotateCharsFromZero(GList *charsA) { + TextChar *ch; + double xMin, yMin, xMax, yMax; + int i; + + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + switch (ch->rot) { + case 0: + default: + break; + case 1: + xMin = pageWidth - ch->yMax; + xMax = pageWidth - ch->yMin; + yMin = ch->xMin; + yMax = ch->xMax; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + break; + case 2: + xMin = pageWidth - ch->xMax; + xMax = pageWidth - ch->xMin; + yMin = pageHeight - ch->yMax; + yMax = pageHeight - ch->yMin; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + break; + case 3: + xMin = ch->yMin; + xMax = ch->yMax; + yMin = pageHeight - ch->xMax; + yMax = pageHeight - ch->xMin; + ch->xMin = xMin; + ch->xMax = xMax; + ch->yMin = yMin; + ch->yMax = yMax; + break; + } + } +} + +// Undo the coordinate transform performed by rotateCharsToZero(). +void TextPage::unrotateColumnsFromZero(GList *columns) { + TextColumn *col; + TextParagraph *par; + TextLine *line; + TextWord *word; + double xMin, yMin, xMax, yMax; + int colIdx, parIdx, lineIdx, wordIdx, i; + + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + switch (col->getRotation()) { + case 0: + default: + break; + case 1: + xMin = pageWidth - col->yMax; + xMax = pageWidth - col->yMin; + yMin = col->xMin; + yMax = col->xMax; + col->xMin = xMin; + col->xMax = xMax; + col->yMin = yMin; + col->yMax = yMax; + for (parIdx = 0; + parIdx < col->paragraphs->getLength(); + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + xMin = pageWidth - par->yMax; + xMax = pageWidth - par->yMin; + yMin = par->xMin; + yMax = par->xMax; + par->xMin = xMin; + par->xMax = xMax; + par->yMin = yMin; + par->yMax = yMax; + for (lineIdx = 0; + lineIdx < par->lines->getLength(); + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + xMin = pageWidth - line->yMax; + xMax = pageWidth - line->yMin; + yMin = line->xMin; + yMax = line->xMax; + line->xMin = xMin; + line->xMax = xMax; + line->yMin = yMin; + line->yMax = yMax; + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + xMin = pageWidth - word->yMax; + xMax = pageWidth - word->yMin; + yMin = word->xMin; + yMax = word->xMax; + word->xMin = xMin; + word->xMax = xMax; + word->yMin = yMin; + word->yMax = yMax; + } + } + } + break; + case 2: + xMin = pageWidth - col->xMax; + xMax = pageWidth - col->xMin; + yMin = pageHeight - col->yMax; + yMax = pageHeight - col->yMin; + col->xMin = xMin; + col->xMax = xMax; + col->yMin = yMin; + col->yMax = yMax; + for (parIdx = 0; + parIdx < col->paragraphs->getLength(); + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + xMin = pageWidth - par->xMax; + xMax = pageWidth - par->xMin; + yMin = pageHeight - par->yMax; + yMax = pageHeight - par->yMin; + par->xMin = xMin; + par->xMax = xMax; + par->yMin = yMin; + par->yMax = yMax; + for (lineIdx = 0; + lineIdx < par->lines->getLength(); + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + xMin = pageWidth - line->xMax; + xMax = pageWidth - line->xMin; + yMin = pageHeight - line->yMax; + yMax = pageHeight - line->yMin; + line->xMin = xMin; + line->xMax = xMax; + line->yMin = yMin; + line->yMax = yMax; + for (i = 0; i <= line->len; ++i) { + line->edge[i] = pageWidth - line->edge[i]; + } + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + xMin = pageWidth - word->xMax; + xMax = pageWidth - word->xMin; + yMin = pageHeight - word->yMax; + yMax = pageHeight - word->yMin; + word->xMin = xMin; + word->xMax = xMax; + word->yMin = yMin; + word->yMax = yMax; + for (i = 0; i <= word->len; ++i) { + word->edge[i] = pageWidth - word->edge[i]; + } + } + } + } + break; + case 3: + xMin = col->yMin; + xMax = col->yMax; + yMin = pageHeight - col->xMax; + yMax = pageHeight - col->xMin; + col->xMin = xMin; + col->xMax = xMax; + col->yMin = yMin; + col->yMax = yMax; + for (parIdx = 0; + parIdx < col->paragraphs->getLength(); + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + xMin = par->yMin; + xMax = par->yMax; + yMin = pageHeight - par->xMax; + yMax = pageHeight - par->xMin; + par->xMin = xMin; + par->xMax = xMax; + par->yMin = yMin; + par->yMax = yMax; + for (lineIdx = 0; + lineIdx < par->lines->getLength(); + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + xMin = line->yMin; + xMax = line->yMax; + yMin = pageHeight - line->xMax; + yMax = pageHeight - line->xMin; + line->xMin = xMin; + line->xMax = xMax; + line->yMin = yMin; + line->yMax = yMax; + for (i = 0; i <= line->len; ++i) { + line->edge[i] = pageHeight - line->edge[i]; + } + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + xMin = word->yMin; + xMax = word->yMax; + yMin = pageHeight - word->xMax; + yMax = pageHeight - word->xMin; + word->xMin = xMin; + word->xMax = xMax; + word->yMin = yMin; + word->yMax = yMax; + for (i = 0; i <= word->len; ++i) { + word->edge[i] = pageHeight - word->edge[i]; + } + } + } + } + break; + } } } @@ -2601,7 +3071,8 @@ void TextPage::removeDuplicates(GList *charsA, int rot) { int i, j; if (rot & 1) { - for (i = 0; i < charsA->getLength(); ++i) { + i = 0; + while (i < charsA->getLength()) { ch = (TextChar *)charsA->get(i); xDelta = dupMaxSecDelta * ch->fontSize; yDelta = dupMaxPriDelta * ch->fontSize; @@ -2615,6 +3086,11 @@ void TextPage::removeDuplicates(GList *charsA, int rot) { fabs(ch2->xMin - ch->xMin) < xDelta && fabs(ch2->xMax - ch->xMax) < xDelta && fabs(ch2->yMax - ch->yMax) < yDelta) { + if (ch->invisible && !ch2->invisible) { + charsA->del(i); + --i; + break; + } if (ch2->spaceAfter) { ch->spaceAfter = (char)gTrue; } @@ -2623,9 +3099,11 @@ void TextPage::removeDuplicates(GList *charsA, int rot) { ++j; } } + ++i; } } else { - for (i = 0; i < charsA->getLength(); ++i) { + i = 0; + while (i < charsA->getLength()) { ch = (TextChar *)charsA->get(i); xDelta = dupMaxPriDelta * ch->fontSize; yDelta = dupMaxSecDelta * ch->fontSize; @@ -2639,13 +3117,255 @@ void TextPage::removeDuplicates(GList *charsA, int rot) { fabs(ch2->xMax - ch->xMax) < xDelta && fabs(ch2->yMin - ch->yMin) < yDelta && fabs(ch2->yMax - ch->yMax) < yDelta) { + if (ch->invisible && !ch2->invisible) { + charsA->del(i); + --i; + break; + } + if (ch2->spaceAfter) { + ch->spaceAfter = (char)gTrue; + } charsA->del(j); } else { ++j; } } + ++i; + } + } +} + +struct TextCharNode { + TextCharNode(TextChar *chA, TextCharNode *nextA): ch(chA), next(nextA) {} + TextChar *ch; + TextCharNode *next; +}; + +// Separate out any overlapping text. If handling is +// textOutAppendOverlaps, return a list of the overlapping chars; else +// delete them and return NULL. +GList *TextPage::separateOverlappingText(GList *charsA) { + // bin-sort the TextChars + TextCharNode *grid[overlapGridHeight][overlapGridWidth]; + for (int y = 0; y < overlapGridHeight; ++y) { + for (int x = 0; x < overlapGridWidth; ++x) { + grid[y][x] = NULL; + } + } + for (int i = 0; i < charsA->getLength(); ++i) { + TextChar *ch = (TextChar *)charsA->get(i); + int y0 = (int)floor(overlapGridHeight * ch->yMin / pageHeight); + int y1 = (int)ceil(overlapGridHeight * ch->yMax / pageHeight); + int x0 = (int)floor(overlapGridWidth * ch->xMin / pageWidth); + int x1 = (int)ceil(overlapGridWidth * ch->yMin / pageWidth); + if (y0 < 0) { + y0 = 0; + } + if (y1 >= overlapGridHeight) { + y1 = overlapGridHeight - 1; + } + if (x0 < 0) { + x0 = 0; + } + if (x1 >= overlapGridWidth) { + x1 = overlapGridWidth - 1; + } + for (int y = y0; y <= y1; ++y) { + for (int x = x0; x <= x1; ++x) { + grid[y][x] = new TextCharNode(ch, grid[y][x]); + } + } + } + + // look for overlaps in each cell + GBool foundOverlaps = gFalse; + for (int y = 0; y < overlapGridHeight; ++y) { + for (int x = 0; x < overlapGridWidth; ++x) { + for (TextCharNode *p0 = grid[y][x]; p0; p0 = p0->next) { + for (TextCharNode *p1 = p0->next; p1; p1 = p1->next) { + if (p0->ch->colorR != p1->ch->colorR || + p0->ch->colorG != p1->ch->colorG || + p0->ch->colorB != p1->ch->colorB) { + double ovx = (dmin(p0->ch->xMax, p1->ch->xMax) + - dmax(p0->ch->xMin, p1->ch->xMin)) + / dmin(p0->ch->xMax - p0->ch->xMin, + p1->ch->xMax - p1->ch->xMin); + double ovy = (dmin(p0->ch->yMax, p1->ch->yMax) + - dmax(p0->ch->yMin, p1->ch->yMin)) + / dmin(p0->ch->yMax - p0->ch->yMin, + p1->ch->yMax - p1->ch->yMin); + if (ovx > minCharOverlap && ovy > minCharOverlap) { + // assume the lighter colored text is extraneous + if (p0->ch->colorR + p0->ch->colorG + p0->ch->colorB + < p1->ch->colorR + p1->ch->colorG + p1->ch->colorB) { + p1->ch->overlap = gTrue; + } else { + p0->ch->overlap = gTrue; + } + foundOverlaps = gTrue; + } + } + } + } + } + } + + // find overlapped strings + GList *overlapChars = NULL; + if (control.overlapHandling == textOutAppendOverlaps) { + overlapChars = new GList(); + } + if (foundOverlaps) { + charsA->sort(&TextChar::cmpCharPos); + int i = 0; + while (i < charsA->getLength()) { + TextChar *ch0 = (TextChar *)charsA->get(i); + if (ch0->overlap) { + int j0, j1; + for (j0 = i - 1; j0 >= 0; --j0) { + TextChar *ch1 = (TextChar *)charsA->get(j0); + if (ch1->colorR != ch0->colorR || + ch1->colorG != ch0->colorG || + ch1->colorB != ch0->colorB || + ch1->rot != ch0->rot) { + break; + } + } + ++j0; + for (j1 = i + 1; j1 < charsA->getLength(); ++j1) { + TextChar *ch1 = (TextChar *)charsA->get(j1); + if (ch1->colorR != ch0->colorR || + ch1->colorG != ch0->colorG || + ch1->colorB != ch0->colorB || + ch1->rot != ch0->rot) { + break; + } + } + --j1; + for (int j = j0; j <= j1; ++j) { + if (overlapChars) { + overlapChars->append(charsA->get(j0)); + } else { + delete (TextChar *)charsA->get(j0); + } + charsA->del(j0); + } + i = j0; + } else { + ++i; + } + } + } + + // free memory + for (int y = 0; y < overlapGridHeight; ++y) { + for (int x = 0; x < overlapGridWidth; ++x) { + TextCharNode *p0 = grid[y][x]; + while (p0) { + TextCharNode *p1 = p0->next; + delete p0; + p0 = p1; + } + } + } + + return overlapChars; +} + +// Construct a TextColumn from the list of separated overlapping +// chars. +TextColumn *TextPage::buildOverlappingTextColumn(GList *overlappingChars) { + GList *pars = new GList(); + GList *lines = new GList(); + GList *words = new GList(); + int wordStart = 0; + double lineXMin = 0, lineYMin = 0, lineXMax = 0, lineYMax = 0; + double colXMin = 0, colYMin = 0, colXMax = 0, colYMax = 0; + for (int i = 0; i < overlappingChars->getLength(); ++i) { + TextChar *ch = (TextChar *)overlappingChars->get(i); + TextChar *chNext = NULL; + if (i + 1 < overlappingChars->getLength()) { + chNext = (TextChar *)overlappingChars->get(i + 1); + } + double sp = 0; + double dy = 0; + if (chNext) { + switch (ch->rot) { + case 0: + default: + sp = chNext->xMin - ch->xMax; + dy = chNext->yMin - ch->yMin; + break; + case 1: + sp = chNext->yMin - ch->yMax; + dy = chNext->xMax - ch->xMax; + break; + case 2: + sp = ch->xMin - chNext->xMax; + dy = ch->yMax - chNext->yMax; + break; + case 3: + sp = ch->yMin - chNext->yMax; + dy = ch->xMin - chNext->xMin; + break; + } + } + // the +1 here allows for a space character after ch + GBool parBreak = !chNext || + chNext->rot != ch->rot || + chNext->charPos > ch->charPos + ch->charLen + 1; + GBool lineBreak = parBreak || + sp < -rawModeCharOverlap * ch->fontSize || + fabs(dy) > rawModeLineDelta * ch->fontSize; + GBool wordBreak = lineBreak || + ch->spaceAfter || + sp > rawModeWordSpacing * ch->fontSize; + if (!wordBreak) { + continue; } + TextWord *word = new TextWord(overlappingChars, wordStart, + i - wordStart + 1, ch->rot, ch->rotated, + getCharDirection(ch), !lineBreak); + words->append(word); + if (words->getLength() == 0) { + lineXMin = word->xMin; + lineYMin = word->yMin; + lineXMax = word->xMax; + lineYMax = word->yMax; + } else { + lineXMin = dmin(lineXMin, word->xMin); + lineYMin = dmin(lineYMin, word->yMin); + lineXMax = dmax(lineXMax, word->xMax); + lineYMax = dmax(lineYMax, word->yMax); + } + wordStart = i + 1; + if (!lineBreak) { + continue; + } + lines->append(new TextLine(words, lineXMin, lineYMin, lineXMax, lineYMax, + ((TextWord *)words->get(0))->fontSize)); + words = new GList(); + if (!parBreak) { + continue; + } + TextParagraph *par = new TextParagraph(lines, gFalse); + pars->append(par); + if (pars->getLength() == 0) { + colXMin = par->xMin; + colYMin = par->yMin; + colXMax = par->xMax; + colYMax = par->yMax; + } else { + colXMin = dmin(colXMin, par->xMin); + colYMin = dmin(colYMin, par->yMin); + colXMax = dmax(colXMax, par->xMax); + colYMax = dmax(colYMax, par->yMax); + } + lines = new GList(); } + delete words; + delete lines; + return new TextColumn(pars, colXMin, colYMin, colXMax, colYMax); } // Split the characters into trees of TextBlocks, one tree for each @@ -2860,12 +3580,15 @@ TextBlock *TextPage::split(GList *charsA, int rot) { // largeCharSize could otherwise have slightly different values // here and where it's used below to do the large char partition // (because it gets truncated from 80 to 64 bits when spilled) - largeCharSize = (int)(largeCharThreshold * avgFontSize * 256) / 256.0; nLargeChars = 0; - for (i = 0; i < charsA->getLength(); ++i) { - ch = (TextChar *)charsA->get(i); - if (ch->fontSize > largeCharSize) { - ++nLargeChars; + largeCharSize = 0; + if (control.separateLargeChars) { + largeCharSize = (int)(largeCharThreshold * avgFontSize * 256) / 256.0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (ch->fontSize > largeCharSize) { + ++nLargeChars; + } } } @@ -3092,7 +3815,7 @@ void TextPage::findGaps(GList *charsA, int rot, if (xMin * invSplitPrecision < 0.5 * INT_MIN || xMax * invSplitPrecision > 0.5 * INT_MAX || yMin * invSplitPrecision < 0.5 * INT_MIN || - xMax * invSplitPrecision > 0.5 * INT_MAX) { + yMax * invSplitPrecision > 0.5 * INT_MAX) { return; } // add some slack to the array bounds to avoid floating point @@ -3543,7 +4266,7 @@ TextColumn *TextPage::buildColumn(TextBlock *blk) { int i; lines = new GList(); - buildLines(blk, lines); + buildLines(blk, lines, gFalse); spaceThresh = paragraphSpacingThreshold * getAverageLineSpacing(lines); @@ -3712,43 +4435,231 @@ double TextPage::getLineSpacing(TextLine *line0, TextLine *line1) { return sp; } -void TextPage::buildLines(TextBlock *blk, GList *lines) { +void TextPage::buildLines(TextBlock *blk, GList *lines, + GBool splitSuperLines) { TextLine *line; int i; - switch (blk->tag) { - case blkTagLine: + if (blk->tag == blkTagLine || + (blk->tag == blkTagSuperLine && !splitSuperLines)) { line = buildLine(blk); if (blk->rot == 1 || blk->rot == 2) { lines->insert(0, line); } else { lines->append(line); } - break; - case blkTagSuperLine: - case blkTagColumn: - case blkTagMulticolumn: // multicolumn should never happen here + } else { for (i = 0; i < blk->children->getLength(); ++i) { - buildLines((TextBlock *)blk->children->get(i), lines); + buildLines((TextBlock *)blk->children->get(i), lines, splitSuperLines); } - break; } } +GList *TextPage::buildSimple2Columns(GList *charsA) { + GList *columns, *paragraphs, *lines; + TextParagraph *paragraph; + int rot; + + charsA->sort(&TextChar::cmpX); + columns = new GList(); + for (rot = 0; rot < 4; ++rot) { + lines = buildSimple2Lines(charsA, rot); + if (lines->getLength() == 0) { + delete lines; + continue; + } + paragraph = new TextParagraph(lines, gFalse); + paragraphs = new GList(); + paragraphs->append(paragraph); + columns->append(new TextColumn(paragraphs, + paragraph->xMin, paragraph->yMin, + paragraph->xMax, paragraph->yMax)); + } + return columns; +} + +GList *TextPage::buildSimple2Lines(GList *charsA, int rot) { + GList *openCharLines, *lines; + TextCharLine *firstCharLine, *lastCharLine, *charLine, *p; + TextChar *ch; + TextLine *line; + double bestOverlap, overlap, xMin, yMin, xMax, yMax; + int bestLine, i, j, k, m; + + firstCharLine = lastCharLine = NULL; + openCharLines = new GList(); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (ch->rot != rot) { + continue; + } + + // find the first open line with line.yMax > ch.yMin + j = -1; + k = openCharLines->getLength(); + while (j < k - 1) { + // invariants: openLines[j].yMax <= ch.yMin (or j = -1) + // openLines[k].yMax > ch.yMin (or k = nOpenLines) + // j < k - 1 + m = j + (k - j) / 2; + charLine = (TextCharLine *)openCharLines->get(m); + if (charLine->yMax <= ch->yMin) { + j = m; + } else { + k = m; + } + } + + // check overlap for all overlapping lines + // i.e., all lines with line.yMin < ch.yMax and line.yMax > ch.yMin + bestLine = -1; + bestOverlap = 0; + for (; k < openCharLines->getLength(); ++k) { + charLine = (TextCharLine *)openCharLines->get(k); + if (charLine->yMin >= ch->yMax) { + break; + } + overlap = ((ch->yMax < charLine->yMax ? ch->yMax : charLine->yMax) + - (ch->yMin > charLine->yMin ? ch->yMin : charLine->yMin)) + / (ch->yMax - ch->yMin); + if (overlap > bestOverlap) { + bestLine = k; + bestOverlap = overlap; + } + } + + // found an overlapping line + if (bestLine >= 0 && bestOverlap > simple2MinOverlap) { + k = bestLine; + charLine = (TextCharLine *)openCharLines->get(k); + + // else insert a new line immediately before line k + } else { + charLine = new TextCharLine(ch->rot); + if (k < openCharLines->getLength()) { + p = (TextCharLine *)openCharLines->get(k); + if (p->prev) { + p->prev->next = charLine; + charLine->prev = p->prev; + } else { + firstCharLine = charLine; + } + p->prev = charLine; + charLine->next = p; + } else { + if (lastCharLine) { + lastCharLine->next = charLine; + charLine->prev = lastCharLine; + } else { + firstCharLine = charLine; + } + lastCharLine = charLine; + } + openCharLines->insert(k, charLine); + } + + // add the char to the line + charLine->add(ch); + charLine->yMin = ch->yMin; + charLine->yMax = ch->yMax; + + // update open lines before k + j = k - 1; + while (j >= 0) { + charLine = (TextCharLine *)openCharLines->get(j); + if (charLine->yMax <= ch->yMin) { + break; + } + charLine->yMax = ch->yMin; + if (charLine->yMin < charLine->yMax) { + break; + } + openCharLines->del(j); + --j; + } + + // update open lines after k + j = k + 1; + while (j < openCharLines->getLength()) { + charLine = (TextCharLine *)openCharLines->get(j); + if (charLine->yMin >= ch->yMax) { + break; + } + charLine->yMin = ch->yMax; + if (charLine->yMin < charLine->yMax) { + break; + } + openCharLines->del(j); + } + } + + // build TextLine objects + lines = new GList(); + for (charLine = firstCharLine; charLine; charLine = p) { + xMin = yMin = xMax = yMax = 0; + for (j = 0; j < charLine->chars->getLength(); ++j) { + ch = (TextChar *)charLine->chars->get(j); + if (j == 0) { + xMin = ch->xMin; + yMin = ch->yMin; + xMax = ch->xMax; + yMax = ch->yMax; + } else { + if (ch->xMin < xMin) { + xMin = ch->xMin; + } + if (ch->yMin < yMin) { + yMin = ch->yMin; + } + if (ch->xMax < xMax) { + xMax = ch->xMax; + } + if (ch->yMax < yMax) { + yMax = ch->yMax; + } + } + } + // the chars have been rotated to 0, without changing the + // TextChar.rot values, so we need to tell buildLine to use rot=0, + // and then set the word and line rotation correctly afterward + line = buildLine(charLine->chars, 0, xMin, yMin, xMax, yMax); + line->rot = charLine->rot; + for (i = 0; i < line->words->getLength(); ++i) { + ((TextWord *)line->words->get(i))->rot = (char)charLine->rot; + } + lines->append(line); + p = charLine->next; + delete charLine; + } + + delete openCharLines; + + return lines; +} + TextLine *TextPage::buildLine(TextBlock *blk) { GList *charsA; + + charsA = new GList(); + getLineChars(blk, charsA); + TextLine *line = buildLine(charsA, blk->rot, + blk->xMin, blk->yMin, blk->xMax, blk->yMax); + delete charsA; + return line; +} + +TextLine *TextPage::buildLine(GList *charsA, int rot, + double xMin, double yMin, + double xMax, double yMax) { GList *words; TextChar *ch, *ch2; TextWord *word; double wordSp, lineFontSize, sp; int dir, dir2; - GBool spaceAfter, spaceBefore; + GBool rotated, spaceAfter, spaceBefore; int i, j; - charsA = new GList(); - getLineChars(blk, charsA); - - wordSp = computeWordSpacingThreshold(charsA, blk->rot); + wordSp = computeWordSpacingThreshold(charsA, rot); words = new GList(); lineFontSize = 0; @@ -3758,10 +4669,11 @@ TextLine *TextPage::buildLine(TextBlock *blk) { sp = wordSp - 1; spaceAfter = gFalse; dir = getCharDirection((TextChar *)charsA->get(i)); + rotated = ((TextChar *)charsA->get(i))->rotated; for (j = i+1; j < charsA->getLength(); ++j) { ch = (TextChar *)charsA->get(j-1); ch2 = (TextChar *)charsA->get(j); - sp = (blk->rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax); + sp = (rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax); if (sp > wordSp) { spaceAfter = gTrue; break; @@ -3775,6 +4687,7 @@ TextLine *TextPage::buildLine(TextBlock *blk) { dir2 = getCharDirection(ch2); if (ch->font != ch2->font || fabs(ch->fontSize - ch2->fontSize) > 0.01 || + (control.splitRotatedWords && ch2->rotated != rotated) || (dir && dir2 && dir2 != dir) || (control.mode == textOutRawOrder && ch2->charPos != ch->charPos + ch->charLen)) { @@ -3785,11 +4698,11 @@ TextLine *TextPage::buildLine(TextBlock *blk) { } sp = wordSp - 1; } - word = new TextWord(charsA, i, j - i, blk->rot, dir, - (blk->rot >= 2) ? spaceBefore : spaceAfter); + word = new TextWord(charsA, i, j - i, rot, rotated, dir, + (rot >= 2) ? spaceBefore : spaceAfter); spaceBefore = spaceAfter; i = j; - if (blk->rot >= 2) { + if (rot >= 2) { words->insert(0, word); } else { words->append(word); @@ -3799,10 +4712,7 @@ TextLine *TextPage::buildLine(TextBlock *blk) { } } - delete charsA; - - return new TextLine(words, blk->xMin, blk->yMin, blk->xMax, blk->yMax, - lineFontSize); + return new TextLine(words, xMin, yMin, xMax, yMax, lineFontSize); } void TextPage::getLineChars(TextBlock *blk, GList *charsA) { @@ -3822,10 +4732,19 @@ void TextPage::getLineChars(TextBlock *blk, GList *charsA) { // spaces. double TextPage::computeWordSpacingThreshold(GList *charsA, int rot) { TextChar *ch, *ch2; + double uniformSp, wordSp; double avgFontSize; double minAdjGap, maxAdjGap, minSpGap, maxSpGap, minGap, maxGap, gap, gap2; int i; + if (control.mode == textOutTableLayout) { + uniformSp = tableModeUniformSpacing; + wordSp = tableModeWordSpacing; + } else { + uniformSp = uniformSpacing; + wordSp = wordSpacing; + } + avgFontSize = 0; minGap = maxGap = 0; minAdjGap = minSpGap = 1; @@ -3876,13 +4795,13 @@ double TextPage::computeWordSpacingThreshold(GList *charsA, int rot) { // (3) otherwise assume it's a single word (technically it could be // either "ABC" or "A B C", but it's essentially impossible to // tell) - if (maxGap - minGap < uniformSpacing * avgFontSize) { + if (maxGap - minGap < uniformSp * avgFontSize) { if (minSpGap <= maxSpGap) { if (minAdjGap <= maxAdjGap && minSpGap - maxAdjGap > 0.01) { return 0.5 * (maxAdjGap + minSpGap); } else if (minAdjGap > maxAdjGap && - maxSpGap - minSpGap < uniformSpacing * avgFontSize) { + maxSpGap - minSpGap < uniformSp * avgFontSize) { return minSpGap - 1; } } @@ -3890,7 +4809,7 @@ double TextPage::computeWordSpacingThreshold(GList *charsA, int rot) { // if there is some variation in spacing, but it's small, assume // there are some inter-word spaces - } else if (maxGap - minGap < wordSpacing * avgFontSize) { + } else if (maxGap - minGap < wordSp * avgFontSize) { return 0.5 * (minGap + maxGap); // if there is a large variation in spacing, use the SpGap/AdjGap @@ -3901,12 +4820,12 @@ double TextPage::computeWordSpacingThreshold(GList *charsA, int rot) { } else { if (minAdjGap <= maxAdjGap && minSpGap <= maxSpGap && - minSpGap - maxAdjGap > uniformSpacing * avgFontSize) { - gap = wordSpacing * avgFontSize; + minSpGap - maxAdjGap > uniformSp * avgFontSize) { + gap = wordSp * avgFontSize; gap2 = 0.5 * (minSpGap - minGap); return minGap + (gap < gap2 ? gap : gap2); } else { - return minGap + wordSpacing * avgFontSize; + return minGap + wordSp * avgFontSize; } } } @@ -4069,15 +4988,9 @@ void TextPage::buildSuperLines(TextBlock *blk, GList *superLines) { GList *lines; int i; - if (blk->tag == blkTagLine) { - lines = new GList(); - buildLines(blk, lines); - superLines->append(new TextSuperLine(lines)); - } else if (blk->tag == blkTagSuperLine) { + if (blk->tag == blkTagLine || blk->tag == blkTagSuperLine) { lines = new GList(); - for (i = 0; i < blk->children->getLength(); ++i) { - buildLines((TextBlock *)blk->children->get(i), lines); - } + buildLines(blk, lines, gTrue); superLines->append(new TextSuperLine(lines)); } else { for (i = 0; i < blk->children->getLength(); ++i) { @@ -4681,6 +5594,59 @@ GBool TextPage::findPointNear(double x, double y, TextPosition *pos) { return gTrue; } +GBool TextPage::findWordPoints(double x, double y, + TextPosition *startPos, TextPosition *endPos) { + TextPosition pos; + TextColumn *col; + TextParagraph *par; + TextLine *line; + int startCharIdx, endCharIdx; + + if (!findPointInside(x, y, &pos)) { + return gFalse; + } + col = (TextColumn *)findCols->get(pos.colIdx); + par = (TextParagraph *)col->getParagraphs()->get(pos.parIdx); + line = (TextLine *)par->getLines()->get(pos.lineIdx); + + for (startCharIdx = pos.charIdx; + startCharIdx > 0 && line->text[startCharIdx - 1] != 0x20; + --startCharIdx) ; + *startPos = pos; + startPos->charIdx = startCharIdx; + + for (endCharIdx = pos.charIdx; + endCharIdx < line->len && line->text[endCharIdx] != 0x20; + ++endCharIdx) ; + *endPos = pos; + endPos->charIdx = endCharIdx; + + return gTrue; +} + +GBool TextPage::findLinePoints(double x, double y, + TextPosition *startPos, TextPosition *endPos) { + TextPosition pos; + TextColumn *col; + TextParagraph *par; + TextLine *line; + + if (!findPointInside(x, y, &pos)) { + return gFalse; + } + col = (TextColumn *)findCols->get(pos.colIdx); + par = (TextParagraph *)col->getParagraphs()->get(pos.parIdx); + line = (TextLine *)par->getLines()->get(pos.lineIdx); + + *startPos = pos; + startPos->charIdx = 0; + + *endPos = pos; + endPos->charIdx = line->len; + + return gTrue; +} + // Find the position in [col] corresponding to [x],[y]. The column, // [col], was found by findPointInside() or findPointNear(). void TextPage::findPointInColumn(TextColumn *col, double x, double y, @@ -4860,33 +5826,56 @@ TextWordList *TextPage::makeWordListForChars(GList *charList) { TextParagraph *par; TextLine *line; TextWord *word; + GList *overlappingChars; GList *words; GBool primaryLR; int rot, colIdx, parIdx, lineIdx, wordIdx; #if 0 //~debug - dumpCharList(charList); + dumpChars(charList); #endif - rot = rotateChars(charList); - primaryLR = checkPrimaryLR(charList); - tree = splitChars(charList); + + if (control.mode == textOutSimple2Layout) { + rot = 0; + primaryLR = checkPrimaryLR(chars); + rotateCharsToZero(chars); + columns = buildSimple2Columns(chars); + unrotateCharsFromZero(chars); + unrotateColumnsFromZero(columns); + + } else { + if (control.overlapHandling != textOutIgnoreOverlaps) { + overlappingChars = separateOverlappingText(chars); + } else { + overlappingChars = NULL; + } + rot = rotateChars(charList); + primaryLR = checkPrimaryLR(charList); + tree = splitChars(charList); #if 0 //~debug - dumpTree(tree); + dumpTree(tree); #endif - if (!tree) { - // no text - unrotateChars(charList, rot); - return new TextWordList(new GList(), gTrue); - } - columns = buildColumns(tree, primaryLR); + if (!tree) { + // no text + unrotateChars(charList, rot); + return new TextWordList(new GList(), gTrue); + } + columns = buildColumns(tree, primaryLR); #if 0 //~debug - dumpColumns(columns, gTrue); + dumpColumns(columns, gTrue); #endif - delete tree; - unrotateChars(charList, rot); - if (control.html) { - rotateUnderlinesAndLinks(rot); - generateUnderlinesAndLinks(columns); + delete tree; + unrotateChars(charList, rot); + if (control.html) { + rotateUnderlinesAndLinks(rot); + generateUnderlinesAndLinks(columns); + } + if (overlappingChars) { + if (overlappingChars->getLength() > 0) { + columns->append(buildOverlappingTextColumn(overlappingChars)); + } + deleteGList(overlappingChars, TextChar); + } } words = new GList(); @@ -4897,8 +5886,12 @@ TextWordList *TextPage::makeWordListForChars(GList *charList) { for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) { line = (TextLine *)par->lines->get(lineIdx); for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { - word = (TextWord *)line->words->get(wordIdx); - words->append(word->copy()); + word = ((TextWord *)line->words->get(wordIdx))->copy(); + if (wordIdx == line->words->getLength() - 1 && + !line->getHyphenated()) { + word->spaceAfter = gTrue; + } + words->append(word); } } } @@ -4906,6 +5899,7 @@ TextWordList *TextPage::makeWordListForChars(GList *charList) { switch (control.mode) { case textOutReadingOrder: + case textOutSimple2Layout: // already in reading order break; case textOutPhysLayout: @@ -4958,7 +5952,8 @@ void TextPage::dumpTree(TextBlock *tree, int indent) { tree->type == blkLeaf ? "leaf" : tree->type == blkHorizSplit ? "horiz" : "vert", tree->tag == blkTagMulticolumn ? "multicolumn" : - tree->tag == blkTagColumn ? "column" : "line", + tree->tag == blkTagColumn ? "column" : + tree->tag == blkTagSuperLine ? "superline" : "line", tree->smallSplit, tree->rot, tree->xMin, tree->yMin, tree->xMax, tree->yMax); if (tree->type == blkLeaf) { @@ -5001,18 +5996,18 @@ void TextPage::dumpColumns(GList *columns, GBool dumpWords) { word = (TextWord *)line->words->get(wordIdx); printf(" word: xMin=%g yMin=%g xMax=%g yMax=%g\n", word->xMin, word->yMin, word->xMax, word->yMax); - printf(" "); + printf(" '"); for (i = 0; i < word->len; ++i) { printf("%c", word->text[i] & 0xff); } - printf("\n"); + printf("'\n"); } } else { - printf(" "); + printf(" '"); for (i = 0; i < line->len; ++i) { printf("%c", line->text[i] & 0xff); } - printf("\n"); + printf("'\n"); } } } @@ -5041,7 +6036,7 @@ static void outputToFile(void *stream, const char *text, int len) { } TextOutputDev::TextOutputDev(char *fileName, TextOutputControl *controlA, - GBool append) { + GBool append, GBool fileNameIsUTF8) { text = NULL; control = *controlA; ok = gTrue; @@ -5055,15 +6050,22 @@ TextOutputDev::TextOutputDev(char *fileName, TextOutputControl *controlA, // keep DOS from munging the end-of-line characters setmode(fileno(stdout), O_BINARY); #endif - } else if ((outputStream = fopen(fileName, append ? "ab" : "wb"))) { - needClose = gTrue; } else { - error(errIO, -1, "Couldn't open text file '{0:s}'", fileName); - ok = gFalse; - return; + if (fileNameIsUTF8) { + outputStream = openFile(fileName, append ? "ab" : "wb"); + } else { + outputStream = fopen(fileName, append ? "ab" : "wb"); + } + if (!outputStream) { + error(errIO, -1, "Couldn't open text file '{0:s}'", fileName); + ok = gFalse; + return; + } + needClose = gTrue; } outputFunc = &outputToFile; } else { + outputFunc = NULL; outputStream = NULL; } diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.h b/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.h index 0f00cf5ae7b..a1d19c22d7d 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.h @@ -42,12 +42,19 @@ enum TextOutputMode { textOutReadingOrder, // format into reading order textOutPhysLayout, // maintain original physical layout textOutSimpleLayout, // simple one-column physical layout + textOutSimple2Layout, // simple one-column physical layout textOutTableLayout, // similar to PhysLayout, but optimized // for tables textOutLinePrinter, // strict fixed-pitch/height layout textOutRawOrder // keep text in content stream order }; +enum TextOutputOverlapHandling { + textOutIgnoreOverlaps, // no special handling for overlaps + textOutAppendOverlaps, // append overlapping text to main text + textOutDiscardOverlaps // discard overlapping text +}; + class TextOutputControl { public: @@ -66,8 +73,17 @@ public: // in after forming columns GBool discardDiagonalText; // discard all text that's not close to // 0/90/180/270 degrees + GBool discardRotatedText; // discard all text that's not horizontal + // (0 degrees) GBool discardInvisibleText; // discard all invisible characters GBool discardClippedText; // discard all clipped characters + GBool splitRotatedWords; // do not combine horizontal and + // non-horizontal chars in a single + // word + TextOutputOverlapHandling // how to handle overlapping text + overlapHandling; + GBool separateLargeChars; // separate "large" characters from + // "regular" characters GBool insertBOM; // insert a Unicode BOM at the start of // the text output double marginLeft, // characters outside the margins are @@ -124,7 +140,7 @@ class TextWord { public: TextWord(GList *chars, int start, int lenA, - int rotA, int dirA, GBool spaceAfterA); + int rotA, GBool rotatedA, int dirA, GBool spaceAfterA); ~TextWord(); TextWord *copy() { return new TextWord(this); } @@ -144,6 +160,7 @@ public: double *xMaxA, double *yMaxA); double getFontSize() { return fontSize; } int getRotation() { return rot; } + GBool isRotated() { return (GBool)rotated; } int getCharPos() { return charPos[0]; } int getCharLen() { return charPos[len] - charPos[0]; } int getDirection() { return dir; } @@ -158,8 +175,6 @@ private: static int cmpYX(const void *p1, const void *p2); static int cmpCharPos(const void *p1, const void *p2); - int rot; // rotation, multiple of 90 degrees - // (0, 1, 2, or 3) double xMin, xMax; // bounding box x coordinates double yMin, yMax; // bounding box y coordinates Unicode *text; // the text @@ -171,19 +186,22 @@ private: int len; // number of characters TextFontInfo *font; // font information double fontSize; // font size - int dir; // character direction (+1 = left-to-right; - // -1 = right-to-left; 0 = neither) - GBool spaceAfter; // set if there is a space between this - // word and the next word on the line - - GBool underlined; TextLink *link; - double colorR, // word color colorG, colorB; GBool invisible; // set for invisible text (render mode 3) + // group the byte-size fields to minimize object size + Guchar rot; // rotation, multiple of 90 degrees + // (0, 1, 2, or 3) + char rotated; // set if this word is non-horizontal + char dir; // character direction (+1 = left-to-right; + // -1 = right-to-left; 0 = neither) + char spaceAfter; // set if there is a space between this + // word and the next word on the line + char underlined; + friend class TextBlock; friend class TextLine; friend class TextPage; @@ -209,6 +227,7 @@ public: GList *getWords() { return words; } int getLength() { return len; } double getEdge(int idx) { return edge[idx]; } + GBool getHyphenated() { return hyphenated; } private: @@ -407,6 +426,16 @@ public: // are no columns. GBool findPointNear(double x, double y, TextPosition *pos); + // Find the start and end of a word inside a column. Returns false + // if x,y fall outside all columns. + GBool findWordPoints(double x, double y, + TextPosition *startPos, TextPosition *endPos); + + // Find the start and end of a line inside a column. Returns false + // if x,y fall outside all columns. + GBool findLinePoints(double x, double y, + TextPosition *startPos, TextPosition *endPos); + // Get the upper point of a TextPosition. void convertPosToPointUpper(TextPosition *pos, double *x, double *y); @@ -478,6 +507,11 @@ private: UnicodeMap *uMap, char *space, int spaceLen, char *eol, int eolLen); + void writeSimple2Layout(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen); void writeLinePrinter(void *outputStream, TextOutputFunc outputFunc, UnicodeMap *uMap, @@ -493,12 +527,17 @@ private: // analysis int rotateChars(GList *charsA); + void rotateCharsToZero(GList *charsA); void rotateUnderlinesAndLinks(int rot); void unrotateChars(GList *charsA, int rot); + void unrotateCharsFromZero(GList *charsA); + void unrotateColumnsFromZero(GList *columns); void unrotateColumns(GList *columns, int rot); void unrotateWords(GList *words, int rot); GBool checkPrimaryLR(GList *charsA); void removeDuplicates(GList *charsA, int rot); + GList *separateOverlappingText(GList *charsA); + TextColumn *buildOverlappingTextColumn(GList *overlappingChars); TextBlock *splitChars(GList *charsA); TextBlock *split(GList *charsA, int rot); GList *getChars(GList *charsA, double xMin, double yMin, @@ -522,8 +561,12 @@ private: double getLineIndent(TextLine *line, TextBlock *blk); double getAverageLineSpacing(GList *lines); double getLineSpacing(TextLine *line0, TextLine *line1); - void buildLines(TextBlock *blk, GList *lines); + void buildLines(TextBlock *blk, GList *lines, GBool splitSuperLines); + GList *buildSimple2Columns(GList *charsA); + GList *buildSimple2Lines(GList *charsA, int rot); TextLine *buildLine(TextBlock *blk); + TextLine *buildLine(GList *charsA, int rot, + double xMin, double yMin, double xMax, double yMax); void getLineChars(TextBlock *blk, GList *charsA); double computeWordSpacingThreshold(GList *charsA, int rot); int getCharDirection(TextChar *ch); @@ -563,6 +606,7 @@ private: int curRot; // current rotation GBool diagonal; // set if rotation is not close to // 0/90/180/270 degrees + GBool rotated; // set if text is not horizontal (0 degrees) int nTinyChars; // number of "tiny" chars seen so far Unicode *actualText; // current "ActualText" span int actualTextLen; @@ -605,7 +649,7 @@ public: // is maintained. If is true, the text is kept in // content stream order. TextOutputDev(char *fileName, TextOutputControl *controlA, - GBool append); + GBool append, GBool fileNameIsUTF8 = gFalse); // Create a TextOutputDev which will write to a generic stream. If // is true, the original physical layout of the text diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.cc index 4701fed98eb..8e54a1d5471 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.cc @@ -201,3 +201,13 @@ GString *TextString::toPDFTextString() { } return s; } + +GString *TextString::toUTF8() { + GString *s = new GString(); + for (int i = 0; i < len; ++i) { + char buf[8]; + int n = mapUTF8(u[i], buf, sizeof(buf)); + s->append(buf, n); + } + return s; +} diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.h b/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.h index c5e7e613af8..c296d7d6d7a 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.h @@ -56,6 +56,9 @@ public: // Create a PDF text string from a TextString. GString *toPDFTextString(); + // Convert a TextString to UTF-8. + GString *toUTF8(); + private: void expand(int delta); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/WebFont.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/WebFont.cc index 95f0e983a31..9ae636226ae 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/WebFont.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/WebFont.cc @@ -22,6 +22,7 @@ WebFont::WebFont(GfxFont *gfxFontA, XRef *xref) { GfxFontType type; + Ref id; gfxFont = gfxFontA; fontBuf = NULL; @@ -29,23 +30,25 @@ WebFont::WebFont(GfxFont *gfxFontA, XRef *xref) { ffType1C = NULL; isOpenType = gFalse; - type = gfxFont->getType(); - if (type == fontTrueType || - type == fontTrueTypeOT || - type == fontCIDType2 || - type == fontCIDType2OT) { - if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) { - ffTrueType = FoFiTrueType::make(fontBuf, fontLength, 0); - } - } else if (type == fontType1C || - type == fontCIDType0C) { - if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) { - ffType1C = FoFiType1C::make(fontBuf, fontLength); - } - } else if (type == fontType1COT || - type == fontCIDType0COT) { - if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) { - isOpenType = gTrue; + if (gfxFont->getEmbeddedFontID(&id)) { + type = gfxFont->getType(); + if (type == fontTrueType || + type == fontTrueTypeOT || + type == fontCIDType2 || + type == fontCIDType2OT) { + if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) { + ffTrueType = FoFiTrueType::make(fontBuf, fontLength, 0); + } + } else if (type == fontType1C || + type == fontCIDType0C) { + if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) { + ffType1C = FoFiType1C::make(fontBuf, fontLength); + } + } else if (type == fontType1COT || + type == fontCIDType0COT) { + if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) { + isOpenType = gTrue; + } } } } diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.cc deleted file mode 100644 index 3b88270c798..00000000000 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.cc +++ /dev/null @@ -1,3245 +0,0 @@ -//======================================================================== -// -// XFAForm.cc -// -// Copyright 2012 Glyph & Cog, LLC -// -//======================================================================== - -#include - -#ifdef USE_GCC_PRAGMAS -#pragma implementation -#endif - -#include -#include "gmem.h" -#include "gmempp.h" -#include "GString.h" -#include "GList.h" -#include "GHash.h" -#include "Error.h" -#include "Object.h" -#include "PDFDoc.h" -#include "Gfx.h" -#include "GfxFont.h" -#include "Zoox.h" -#include "PDF417Barcode.h" -#include "UTF8.h" -#include "XFAForm.h" - -#ifdef _WIN32 -# undef strcasecmp -# undef strncasecmp -# define strcasecmp _stricmp -# define strncasecmp _strnicmp -#endif - -//------------------------------------------------------------------------ - -// 5 bars + 5 spaces -- each can be wide (1) or narrow (0) -// (there are always exactly 3 wide elements; -// the last space is always narrow) -static Guchar code3Of9Data[128][10] = { - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x00 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x10 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 1, 0, 0, 0, 1, 0, 0, 0 }, // ' ' = 0x20 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 1, 0, 1, 0, 0, 0, 0 }, // '$' = 0x24 - { 0, 0, 0, 1, 0, 1, 0, 1, 0, 0 }, // '%' = 0x25 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 1, 0, 1, 0, 0, 0 }, // '*' = 0x2a - { 0, 1, 0, 0, 0, 1, 0, 1, 0, 0 }, // '+' = 0x2b - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0 }, // '-' = 0x2d - { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0 }, // '.' = 0x2e - { 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }, // '/' = 0x2f - { 0, 0, 0, 1, 1, 0, 1, 0, 0, 0 }, // '0' = 0x30 - { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0 }, // '1' - { 0, 0, 1, 1, 0, 0, 0, 0, 1, 0 }, // '2' - { 1, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, // '3' - { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 }, // '4' - { 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // '5' - { 0, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // '6' - { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 }, // '7' - { 1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, // '8' - { 0, 0, 1, 1, 0, 0, 1, 0, 0, 0 }, // '9' - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x40 - { 1, 0, 0, 0, 0, 1, 0, 0, 1, 0 }, // 'A' = 0x41 - { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 }, // 'B' - { 1, 0, 1, 0, 0, 1, 0, 0, 0, 0 }, // 'C' - { 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 }, // 'D' - { 1, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // 'E' - { 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // 'F' - { 0, 0, 0, 0, 0, 1, 1, 0, 1, 0 }, // 'G' - { 1, 0, 0, 0, 0, 1, 1, 0, 0, 0 }, // 'H' - { 0, 0, 1, 0, 0, 1, 1, 0, 0, 0 }, // 'I' - { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, // 'J' - { 1, 0, 0, 0, 0, 0, 0, 1, 1, 0 }, // 'K' - { 0, 0, 1, 0, 0, 0, 0, 1, 1, 0 }, // 'L' - { 1, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, // 'M' - { 0, 0, 0, 0, 1, 0, 0, 1, 1, 0 }, // 'N' - { 1, 0, 0, 0, 1, 0, 0, 1, 0, 0 }, // 'O' - { 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 }, // 'P' = 0x50 - { 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, // 'Q' - { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 }, // 'R' - { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // 'S' - { 0, 0, 0, 0, 1, 0, 1, 1, 0, 0 }, // 'T' - { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0 }, // 'U' - { 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 }, // 'V' - { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, // 'W' - { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 }, // 'X' - { 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 }, // 'Y' - { 0, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, // 'Z' - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x60 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x70 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } -}; - -// 3 bars + 3 spaces -- each can be 1, 2, 3, or 4 units wide -static Guchar code128Data[107][6] = { - { 2, 1, 2, 2, 2, 2 }, - { 2, 2, 2, 1, 2, 2 }, - { 2, 2, 2, 2, 2, 1 }, - { 1, 2, 1, 2, 2, 3 }, - { 1, 2, 1, 3, 2, 2 }, - { 1, 3, 1, 2, 2, 2 }, - { 1, 2, 2, 2, 1, 3 }, - { 1, 2, 2, 3, 1, 2 }, - { 1, 3, 2, 2, 1, 2 }, - { 2, 2, 1, 2, 1, 3 }, - { 2, 2, 1, 3, 1, 2 }, - { 2, 3, 1, 2, 1, 2 }, - { 1, 1, 2, 2, 3, 2 }, - { 1, 2, 2, 1, 3, 2 }, - { 1, 2, 2, 2, 3, 1 }, - { 1, 1, 3, 2, 2, 2 }, - { 1, 2, 3, 1, 2, 2 }, - { 1, 2, 3, 2, 2, 1 }, - { 2, 2, 3, 2, 1, 1 }, - { 2, 2, 1, 1, 3, 2 }, - { 2, 2, 1, 2, 3, 1 }, - { 2, 1, 3, 2, 1, 2 }, - { 2, 2, 3, 1, 1, 2 }, - { 3, 1, 2, 1, 3, 1 }, - { 3, 1, 1, 2, 2, 2 }, - { 3, 2, 1, 1, 2, 2 }, - { 3, 2, 1, 2, 2, 1 }, - { 3, 1, 2, 2, 1, 2 }, - { 3, 2, 2, 1, 1, 2 }, - { 3, 2, 2, 2, 1, 1 }, - { 2, 1, 2, 1, 2, 3 }, - { 2, 1, 2, 3, 2, 1 }, - { 2, 3, 2, 1, 2, 1 }, - { 1, 1, 1, 3, 2, 3 }, - { 1, 3, 1, 1, 2, 3 }, - { 1, 3, 1, 3, 2, 1 }, - { 1, 1, 2, 3, 1, 3 }, - { 1, 3, 2, 1, 1, 3 }, - { 1, 3, 2, 3, 1, 1 }, - { 2, 1, 1, 3, 1, 3 }, - { 2, 3, 1, 1, 1, 3 }, - { 2, 3, 1, 3, 1, 1 }, - { 1, 1, 2, 1, 3, 3 }, - { 1, 1, 2, 3, 3, 1 }, - { 1, 3, 2, 1, 3, 1 }, - { 1, 1, 3, 1, 2, 3 }, - { 1, 1, 3, 3, 2, 1 }, - { 1, 3, 3, 1, 2, 1 }, - { 3, 1, 3, 1, 2, 1 }, - { 2, 1, 1, 3, 3, 1 }, - { 2, 3, 1, 1, 3, 1 }, - { 2, 1, 3, 1, 1, 3 }, - { 2, 1, 3, 3, 1, 1 }, - { 2, 1, 3, 1, 3, 1 }, - { 3, 1, 1, 1, 2, 3 }, - { 3, 1, 1, 3, 2, 1 }, - { 3, 3, 1, 1, 2, 1 }, - { 3, 1, 2, 1, 1, 3 }, - { 3, 1, 2, 3, 1, 1 }, - { 3, 3, 2, 1, 1, 1 }, - { 3, 1, 4, 1, 1, 1 }, - { 2, 2, 1, 4, 1, 1 }, - { 4, 3, 1, 1, 1, 1 }, - { 1, 1, 1, 2, 2, 4 }, - { 1, 1, 1, 4, 2, 2 }, - { 1, 2, 1, 1, 2, 4 }, - { 1, 2, 1, 4, 2, 1 }, - { 1, 4, 1, 1, 2, 2 }, - { 1, 4, 1, 2, 2, 1 }, - { 1, 1, 2, 2, 1, 4 }, - { 1, 1, 2, 4, 1, 2 }, - { 1, 2, 2, 1, 1, 4 }, - { 1, 2, 2, 4, 1, 1 }, - { 1, 4, 2, 1, 1, 2 }, - { 1, 4, 2, 2, 1, 1 }, - { 2, 4, 1, 2, 1, 1 }, - { 2, 2, 1, 1, 1, 4 }, - { 4, 1, 3, 1, 1, 1 }, - { 2, 4, 1, 1, 1, 2 }, - { 1, 3, 4, 1, 1, 1 }, - { 1, 1, 1, 2, 4, 2 }, - { 1, 2, 1, 1, 4, 2 }, - { 1, 2, 1, 2, 4, 1 }, - { 1, 1, 4, 2, 1, 2 }, - { 1, 2, 4, 1, 1, 2 }, - { 1, 2, 4, 2, 1, 1 }, - { 4, 1, 1, 2, 1, 2 }, - { 4, 2, 1, 1, 1, 2 }, - { 4, 2, 1, 2, 1, 1 }, - { 2, 1, 2, 1, 4, 1 }, - { 2, 1, 4, 1, 2, 1 }, - { 4, 1, 2, 1, 2, 1 }, - { 1, 1, 1, 1, 4, 3 }, - { 1, 1, 1, 3, 4, 1 }, - { 1, 3, 1, 1, 4, 1 }, - { 1, 1, 4, 1, 1, 3 }, - { 1, 1, 4, 3, 1, 1 }, - { 4, 1, 1, 1, 1, 3 }, - { 4, 1, 1, 3, 1, 1 }, - { 1, 1, 3, 1, 4, 1 }, - { 1, 1, 4, 1, 3, 1 }, - { 3, 1, 1, 1, 4, 1 }, - { 4, 1, 1, 1, 3, 1 }, - { 2, 1, 1, 4, 1, 2 }, // start code A - { 2, 1, 1, 2, 1, 4 }, // start code B - { 2, 1, 1, 2, 3, 2 }, // start code C - { 2, 3, 3, 1, 1, 1 } // stop code (without final bar) -}; - -//------------------------------------------------------------------------ - -class XFATableInfo { -public: - - XFATableInfo(ZxAttr *columnWidthsAttr); - ~XFATableInfo(); - void computeRowHeight(ZxElement *rowSubform); - - int nColumns; // number of columns - double *columnRight; // right edge (x coord) of each column - int rowIdx; // current row index - int columnIdx; // current column index - double rowTop; // top edge (y coord) of current row - double rowHeight; // height of current row (max cell height - // so far) -}; - -XFATableInfo::XFATableInfo(ZxAttr *columnWidthsAttr) { - GString *s; - double w; - int i, columnRightSize; - - nColumns = 0; - columnRight = NULL; - columnRightSize = 0; - if (columnWidthsAttr) { - s = columnWidthsAttr->getValue(); - i = 0; - while (1) { - for (; - i < s->getLength() && - (s->getChar(i) == ' ' || s->getChar(i) == '\t' || - s->getChar(i) == '\r' || s->getChar(i) == '\n'); - ++i) ; - if (i == s->getLength()) { - break; - } - w = XFAFormField::getMeasurement(s, i); - if (nColumns == columnRightSize) { - columnRightSize = columnRightSize ? 2 * columnRightSize : 8; - columnRight = (double *)greallocn(columnRight, columnRightSize, - sizeof(double)); - } - columnRight[nColumns] = (nColumns > 0 ? columnRight[nColumns - 1] : 0) + w; - ++nColumns; - for (++i; - i < s->getLength() && - !(s->getChar(i) == ' ' || s->getChar(i) == '\t' || - s->getChar(i) == '\r' || s->getChar(i) == '\n'); - ++i) ; - } - } - rowIdx = -1; - columnIdx = 0; - rowTop = rowHeight = 0; -} - -XFATableInfo::~XFATableInfo() { - gfree(columnRight); -} - -void XFATableInfo::computeRowHeight(ZxElement *rowSubform) { - ZxNode *child; - ZxAttr *attr; - double h; - - rowHeight = 0; - for (child = rowSubform->getFirstChild(); - child; - child = child->getNextChild()) { - if (child->isElement("field") || child->isElement("draw")) { - if (!(attr = ((ZxElement *)child)->findAttr("h"))) { - attr = ((ZxElement *)child)->findAttr("minH"); - } - h = XFAFormField::getMeasurement(attr, 0); - if (h > rowHeight) { - rowHeight = h; - } - } - } -} - -//------------------------------------------------------------------------ -// XFAForm -//------------------------------------------------------------------------ - -XFAForm *XFAForm::load(PDFDoc *docA, Catalog *catalog, - Object *acroFormObj, Object *xfaObj) { - XFAForm *xfaForm; - XFAFormField *field; - ZxDoc *xmlA; - ZxElement *tmpl; - Object catDict, resourceDictA, obj1; - GString *data; - GBool fullXFAA; - GString *name, *fullName; - GHash *nameCount, *nameIdx, *fullNameCount, *fullNameIdx; - char buf[4096]; - int n, i; - - if (catalog->getNumPages() == 0) { - return NULL; - } - - docA->getXRef()->getCatalog(&catDict); - catDict.dictLookup("NeedsRendering", &obj1); - fullXFAA = obj1.isBool() && obj1.getBool(); - obj1.free(); - catDict.free(); - - if (xfaObj->isStream()) { - data = new GString(); - xfaObj->streamReset(); - while ((n = xfaObj->getStream()->getBlock(buf, sizeof(buf))) > 0) { - data->append(buf, n); - } - } else if (xfaObj->isArray()) { - data = new GString(); - for (i = 1; i < xfaObj->arrayGetLength(); i += 2) { - if (!xfaObj->arrayGet(i, &obj1)->isStream()) { - error(errSyntaxError, -1, "XFA array element is wrong type"); - obj1.free(); - delete data; - return NULL; - } - obj1.streamReset(); - while ((n = obj1.getStream()->getBlock(buf, sizeof(buf))) > 0) { - data->append(buf, n); - } - obj1.free(); - } - } else { - error(errSyntaxError, -1, "XFA object is wrong type"); - return NULL; - } - - xmlA = ZxDoc::loadMem(data->getCString(), data->getLength()); - delete data; - if (!xmlA) { - error(errSyntaxError, -1, "Invalid XML in XFA form"); - return NULL; - } - - if (acroFormObj->isDict()) { - acroFormObj->dictLookup("DR", &resourceDictA); - } - - xfaForm = new XFAForm(docA, catalog->getNumPages(), - xmlA, &resourceDictA, fullXFAA); - - resourceDictA.free(); - - if (xfaForm->xml->getRoot()) { - if ((tmpl = xfaForm->xml->getRoot()->findFirstChildElement("template"))) { - xfaForm->curPageNum = 0; - xfaForm->curXOffset = xfaForm->curYOffset = 0; - name = new GString(); - fullName = new GString(); - nameCount = new GHash(); - nameIdx = new GHash(); - fullNameCount = new GHash(); - fullNameIdx = new GHash(); - xfaForm->scanNode(tmpl, name, fullName, gFalse, NULL, - nameCount, nameIdx, fullNameCount, fullNameIdx, - NULL, catalog); - delete nameCount; - delete nameIdx; - delete fullNameCount; - delete fullNameIdx; - delete name; - delete fullName; - - // apply pageOffsetX/Y (the pageSet/pageArea/contentArea offset) - // to all fields - if (xfaForm->pageSetNPages >= 1 && - xfaForm->pageSetNPages < xfaForm->nPages) { - for (i = xfaForm->pageSetNPages + 1; i <= xfaForm->nPages; ++i) { - xfaForm->pageOffsetX[i - 1] = - xfaForm->pageOffsetX[xfaForm->pageSetNPages - 1]; - xfaForm->pageOffsetY[i - 1] = - xfaForm->pageOffsetY[xfaForm->pageSetNPages - 1]; - } - } - for (i = 0; i < xfaForm->fields->getLength(); ++i) { - field = (XFAFormField *)xfaForm->fields->get(i); - if (field->pageNum >= 1 && field->pageNum <= xfaForm->nPages) { - field->xOffset += xfaForm->pageOffsetX[field->pageNum - 1]; - field->yOffset += xfaForm->pageOffsetY[field->pageNum - 1]; - } - } - } - } - - return xfaForm; -} - -XFAForm::XFAForm(PDFDoc *docA, int nPagesA, ZxDoc *xmlA, - Object *resourceDictA, GBool fullXFAA): - Form(docA) { - int pg; - - xml = xmlA; - fields = new GList(); - resourceDictA->copy(&resourceDict); - fullXFA = fullXFAA; - nPages = nPagesA; - pageSetNPages = 0; - pageOffsetX = (double *)gmallocn(nPages, sizeof(double)); - pageOffsetY = (double *)gmallocn(nPages, sizeof(double)); - for (pg = 0; pg < nPages; ++pg) { - pageOffsetX[pg] = pageOffsetY[pg] = 0; - } -} - -XFAForm::~XFAForm() { - delete xml; - deleteGList(fields, XFAFormField); - resourceDict.free(); - gfree(pageOffsetX); - gfree(pageOffsetY); -} - -// Scan . Constructs the node's name and full name. If -// is a field, creates an XFAFormField; else scans 's children. -void XFAForm::scanNode(ZxElement *elem, - GString *parentName, GString *parentFullName, - GBool inPageSet, XFATableInfo *tableInfo, - GHash *nameCount, GHash *nameIdx, - GHash *fullNameCount, GHash *fullNameIdx, - GString *exclGroupName, Catalog *catalog) { - ZxAttr *attr; - GString *name, *fullName, *namePart, *fullNamePart; - GHash *childNameCount, *childNameIdx, *childFullNameCount, *childFullNameIdx; - int colSpan, i; - - if (elem->isElement("template")) { - name = new GString("form"); - fullName = new GString("form"); - childNameCount = new GHash(); - scanNames(elem, childNameCount); - childNameIdx = new GHash(); - childFullNameCount = new GHash(); - scanFullNames(elem, childFullNameCount); - childFullNameIdx = new GHash(); - } else { - if ((namePart = getNodeName(elem))) { - name = GString::format("{0:t}.{1:t}", parentName, namePart); - if (nameCount->lookupInt(namePart) > 1) { - i = nameIdx->lookupInt(namePart); - name->appendf("[{0:d}]", i); - nameIdx->replace(namePart, i + 1); - } - childNameCount = new GHash(); - scanNames(elem, childNameCount); - childNameIdx = new GHash(); - } else { - name = parentName->copy(); - childNameCount = nameCount; - childNameIdx = nameIdx; - } - if ((fullNamePart = getNodeFullName(elem))) { - fullName = GString::format("{0:t}.{1:t}", parentFullName, fullNamePart); - if (fullNameCount->lookupInt(fullNamePart) > 1) { - i = fullNameIdx->lookupInt(fullNamePart); - fullName->appendf("[{0:d}]", i); - fullNameIdx->replace(fullNamePart, i + 1); - } - childFullNameCount = new GHash(); - scanFullNames(elem, childFullNameCount); - childFullNameIdx = new GHash(); - } else { - fullName = parentFullName->copy(); - childFullNameCount = fullNameCount; - childFullNameIdx = fullNameIdx; - } - } - - if (tableInfo && (elem->isElement("field") || elem->isElement("draw"))) { - if ((attr = elem->findAttr("colSpan"))) { - colSpan = atoi(attr->getValue()->getCString()); - } else { - colSpan = 1; - } - } else { - colSpan = 0; - } - - if (elem->isElement("field")) { - scanField(elem, name, fullName, exclGroupName, - inPageSet, tableInfo, colSpan, catalog); - } else { - scanNonField(elem, name, fullName, inPageSet, tableInfo, colSpan, - childNameCount, childNameIdx, - childFullNameCount, childFullNameIdx, - catalog); - } - - if (tableInfo) { - tableInfo->columnIdx += colSpan; - } - - delete name; - delete fullName; - if (childNameCount != nameCount) { - delete childNameCount; - } - if (childNameIdx != nameIdx) { - delete childNameIdx; - } - if (childFullNameCount != fullNameCount) { - delete childFullNameCount; - } - if (childFullNameIdx != fullNameIdx) { - delete childFullNameIdx; - } -} - -// Traverse all children of , incrementing nameCount[name] for -// each named child. Traversal stops at each named child. -void XFAForm::scanNames(ZxElement *elem, GHash *nameCount) { - ZxNode *node; - ZxElement *child; - GString *namePart; - - for (node = elem->getFirstChild(); node; node = node->getNextChild()) { - if (node->isElement()) { - child = (ZxElement *)node; - if ((namePart = getNodeName(child))) { - if (nodeIsBindGlobal(child)) { - nameCount->replace(namePart, 1); - } else { - nameCount->replace(namePart, nameCount->lookupInt(namePart) + 1); - } - } else { - scanNames(child, nameCount); - } - } - } -} - -// Traverse all children of , incrementing fullNameCount[name] -// for each full-named child. Traversal stops at each full-named -// child. -void XFAForm::scanFullNames(ZxElement *elem, GHash *fullNameCount) { - ZxNode *node; - ZxElement *child; - GString *fullNamePart; - - for (node = elem->getFirstChild(); node; node = node->getNextChild()) { - if (node->isElement()) { - child = (ZxElement *)node; - if ((fullNamePart = getNodeFullName(child))) { - if (nodeIsBindGlobal(child)) { - fullNameCount->replace(fullNamePart, 1); - } else { - fullNameCount->replace(fullNamePart, - fullNameCount->lookupInt(fullNamePart) + 1); - } - } else { - scanFullNames(child, fullNameCount); - } - } - } -} - -void XFAForm::scanField(ZxElement *elem, GString *name, GString *fullName, - GString *exclGroupName, GBool inPageSet, - XFATableInfo *tableInfo, int colSpan, - Catalog *catalog) { - double xSubOffset, ySubOffset, columnWidth, rowHeight; - - if (curPageNum == 0) { - curPageNum = 1; - } - - xSubOffset = ySubOffset = 0; - columnWidth = rowHeight = 0; - if (tableInfo) { - if (tableInfo->columnIdx > 0 && - tableInfo->columnIdx <= tableInfo->nColumns) { - xSubOffset = tableInfo->columnRight[tableInfo->columnIdx - 1]; - } - if (tableInfo->columnIdx + colSpan <= tableInfo->nColumns) { - columnWidth = tableInfo->columnRight[tableInfo->columnIdx + colSpan - 1] - - xSubOffset; - } - ySubOffset = tableInfo->rowTop; - rowHeight = tableInfo->rowHeight; - curXOffset += xSubOffset; - curYOffset += ySubOffset; - } - - fields->append(new XFAFormField(this, elem, name->copy(), fullName->copy(), - exclGroupName ? exclGroupName->copy() - : (GString *)NULL, - curPageNum, curXOffset, curYOffset, - columnWidth, rowHeight)); - - if (tableInfo) { - curXOffset -= xSubOffset; - curYOffset -= ySubOffset; - } -} - -void XFAForm::scanNonField(ZxElement *elem, GString *name, GString *fullName, - GBool inPageSet, - XFATableInfo *tableInfo, int colSpan, - GHash *nameCount, GHash *nameIdx, - GHash *fullNameCount, GHash *fullNameIdx, - Catalog *catalog) { - XFATableInfo *newTableInfo; - ZxElement *brk, *contentArea; - ZxNode *child; - ZxAttr *attr; - PDFRectangle *box; - GString *exclGroupName; - double xSubOffset, ySubOffset; - int savedPageNum; - - newTableInfo = tableInfo; - - if (elem->isElement("subform")) { - - // update page number - if (((brk = elem->findFirstChildElement("breakBefore")) && - (attr = brk->findAttr("targetType")) && - !attr->getValue()->cmp("pageArea")) || - ((brk = elem->findFirstChildElement("break")) && - (attr = brk->findAttr("before")) && - !attr->getValue()->cmp("pageArea")) || - (curPageNum < nPages && - (attr = elem->findAttr("w")) && - (box = catalog->getPage(curPageNum + 1)->getMediaBox()) && - XFAFormField::getMeasurement(attr, 0) == box->x2 - box->x1 && - (attr = elem->findAttr("h")) && - XFAFormField::getMeasurement(attr, 0) == box->y2 - box->y1)) { - if (curPageNum < nPages) { - ++curPageNum; - } - } - - // update tableInfo - if ((attr = elem->findAttr("layout"))) { - if (!attr->getValue()->cmp("table")) { - newTableInfo = new XFATableInfo(elem->findAttr("columnWidths")); - newTableInfo->rowIdx = -1; - newTableInfo->columnIdx = 0; - } else if (tableInfo && !attr->getValue()->cmp("row")) { - ++tableInfo->rowIdx; - tableInfo->columnIdx = 0; - tableInfo->rowTop += tableInfo->rowHeight; - tableInfo->computeRowHeight(elem); - } - } - - // update position - xSubOffset = XFAFormField::getMeasurement(elem->findAttr("x"), 0); - ySubOffset = XFAFormField::getMeasurement(elem->findAttr("y"), 0); - curXOffset += xSubOffset; - curYOffset += ySubOffset; - - } else if (elem->isElement("area") || - elem->isElement("exclGroup")) { - xSubOffset = XFAFormField::getMeasurement(elem->findAttr("x"), 0); - ySubOffset = XFAFormField::getMeasurement(elem->findAttr("y"), 0); - curXOffset += xSubOffset; - curYOffset += ySubOffset; - - } else { - xSubOffset = ySubOffset = 0; - } - - savedPageNum = curPageNum; - if (elem->isElement("pageSet")) { - inPageSet = gTrue; - curPageNum = 0; - - } else if (elem->isElement("pageArea")) { - if (inPageSet) { - if (curPageNum < nPages) { - ++curPageNum; - } - if ((contentArea = elem->findFirstChildElement("contentArea"))) { - pageOffsetX[curPageNum - 1] = - XFAFormField::getMeasurement(contentArea->findAttr("x"), 0); - pageOffsetY[curPageNum - 1] = - XFAFormField::getMeasurement(contentArea->findAttr("y"), 0); - // looks like the contentArea offset (pageOffsetX/Y) should - // not be added to fields defined inside the pageArea - // element (?) - xSubOffset -= pageOffsetX[curPageNum - 1]; - ySubOffset -= pageOffsetY[curPageNum - 1]; - curXOffset -= pageOffsetX[curPageNum - 1]; - curYOffset -= pageOffsetY[curPageNum - 1]; - } - } - } - - if (elem->isElement("exclGroup")) { - exclGroupName = name; - } else { - exclGroupName = NULL; - } - - for (child = elem->getFirstChild(); child; child = child->getNextChild()) { - if (child->isElement()) { - scanNode((ZxElement *)child, name, fullName, inPageSet, - newTableInfo, nameCount, nameIdx, fullNameCount, fullNameIdx, - exclGroupName, catalog); - } - } - - curXOffset -= xSubOffset; - curYOffset -= ySubOffset; - - if (newTableInfo != tableInfo) { - delete newTableInfo; - } - - if (elem->isElement("pageSet")) { - pageSetNPages = curPageNum; - curPageNum = savedPageNum; - inPageSet = gFalse; - } -} - -GString *XFAForm::getNodeName(ZxElement *elem) { - ZxElement *bindElem; - ZxAttr *attr; - - if (!(elem->getType()->cmp("field") && - (bindElem = elem->findFirstChildElement("bind")) && - (attr = bindElem->findAttr("match")) && - !attr->getValue()->cmp("none")) && - !elem->isElement("area") && - (attr = elem->findAttr("name"))) { - return attr->getValue(); - } - return NULL; -} - -GString *XFAForm::getNodeFullName(ZxElement *elem) { - ZxAttr *attr; - - if (!elem->isElement("area") && - (attr = elem->findAttr("name"))) { - return attr->getValue(); - } - return NULL; -} - -GBool XFAForm::nodeIsBindGlobal(ZxElement *elem) { - ZxElement *bindElem; - ZxAttr *attr; - - return (bindElem = elem->findFirstChildElement("bind")) && - (attr = bindElem->findAttr("match")) && - !attr->getValue()->cmp("global"); -} - -void XFAForm::draw(int pageNum, Gfx *gfx, GBool printing) { - GfxFontDict *fontDict; - Object obj1; - int i; - - // build the font dictionary - fontDict = NULL; - if (resourceDict.isDict()) { - if (resourceDict.dictLookup("Font", &obj1)->isDict()) { - fontDict = new GfxFontDict(doc->getXRef(), NULL, obj1.getDict()); - } - obj1.free(); - } - - for (i = 0; i < fields->getLength(); ++i) { - ((XFAFormField *)fields->get(i))->draw(pageNum, gfx, printing, fontDict); - } - - delete fontDict; -} - -int XFAForm::getNumFields() { - return fields->getLength(); -} - -FormField *XFAForm::getField(int idx) { - return (XFAFormField *)fields->get(idx); -} - -//------------------------------------------------------------------------ -// XFAFormField -//------------------------------------------------------------------------ - -XFAFormField::XFAFormField(XFAForm *xfaFormA, ZxElement *xmlA, - GString *nameA, GString *fullNameA, - GString *exclGroupNameA, - int pageNumA, double xOffsetA, double yOffsetA, - double columnWidthA, double rowHeightA) { - xfaForm = xfaFormA; - xml = xmlA; - name = nameA; - fullName = fullNameA; - exclGroupName = exclGroupNameA; - pageNum = pageNumA; - xOffset = xOffsetA; - yOffset = yOffsetA; - columnWidth = columnWidthA; - rowHeight = rowHeightA; -} - -XFAFormField::~XFAFormField() { - delete name; - delete fullName; - if (exclGroupName) { - delete exclGroupName; - } -} - -int XFAFormField::getPageNum() { - return pageNum; -} - -const char *XFAFormField::getType() { - ZxElement *uiElem; - ZxNode *node; - - if ((uiElem = xml->findFirstChildElement("ui"))) { - for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) { - if (node->isElement("textEdit")) { - return "Text"; - } else if (node->isElement("numericEdit")) { - return "Numeric"; - } else if (node->isElement("dateTimeEdit")) { - return "DateTime"; - } else if (node->isElement("choiceList")) { - return "ChoiceList"; - } else if (node->isElement("checkButton")) { - return "CheckButton"; - } else if (node->isElement("barcode")) { - return "BarCode"; - } - //~ other field types go here - } - } - return NULL; -} - -Unicode *XFAFormField::getName(int *length) { - //~ assumes name is UTF-8 - return utf8ToUnicode(name, length); -} - -Unicode *XFAFormField::getValue(int *length) { - ZxElement *uiElem; - ZxNode *node; - GString *s; - - //~ assumes value is UTF-8 - s = NULL; - if ((uiElem = xml->findFirstChildElement("ui"))) { - for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) { - if (node->isElement("textEdit")) { - s = getFieldValue("text"); - break; - } else if (node->isElement("numericEdit")) { - //~ not sure if this is correct - s = getFieldValue("text"); - break; - } else if (node->isElement("dateTimeEdit")) { - s = getFieldValue("text"); - break; - } else if (node->isElement("checkButton")) { - if (!(s = getFieldValue("integer"))) { - s = getFieldValue("text"); - } - break; - } else if (node->isElement("barcode")) { - s = getFieldValue("text"); - break; - } - //~ other field types go here - } - } - if (!s) { - return NULL; - } - return utf8ToUnicode(s, length); -} - -Unicode *XFAFormField::utf8ToUnicode(GString *s, int *length) { - Unicode *u; - int n, size, c0, c1, c2, c3, c4, c5, i; - - n = size = 0; - u = NULL; - i = 0; - while (i < s->getLength()) { - if (n == size) { - size = size ? size * 2 : 16; - u = (Unicode *)greallocn(u, size, sizeof(Unicode)); - } - c0 = s->getChar(i++) & 0xff; - if (c0 <= 0x7f) { - u[n++] = c0; - } else if (c0 <= 0xdf && i < n) { - c1 = s->getChar(i++) & 0xff; - u[n++] = ((c0 & 0x1f) << 6) | (c1 & 0x3f); - } else if (c0 <= 0xef && i+1 < n) { - c1 = s->getChar(i++) & 0xff; - c2 = s->getChar(i++) & 0xff; - u[n++] = ((c0 & 0x0f) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f); - } else if (c0 <= 0xf7 && i+2 < n) { - c1 = s->getChar(i++) & 0xff; - c2 = s->getChar(i++) & 0xff; - c3 = s->getChar(i++) & 0xff; - u[n++] = ((c0 & 0x07) << 18) | ((c1 & 0x3f) << 12) | ((c2 & 0x3f) << 6) - | (c3 & 0x3f); - } else if (c0 <= 0xfb && i+3 < n) { - c1 = s->getChar(i++) & 0xff; - c2 = s->getChar(i++) & 0xff; - c3 = s->getChar(i++) & 0xff; - c4 = s->getChar(i++) & 0xff; - u[n++] = ((c0 & 0x03) << 24) | ((c1 & 0x3f) << 18) | ((c2 & 0x3f) << 12) - | ((c3 & 0x3f) << 6) | (c4 & 0x3f); - } else if (c0 <= 0xfd && i+4 < n) { - c1 = s->getChar(i++) & 0xff; - c2 = s->getChar(i++) & 0xff; - c3 = s->getChar(i++) & 0xff; - c4 = s->getChar(i++) & 0xff; - c5 = s->getChar(i++) & 0xff; - u[n++] = ((c0 & 0x01) << 30) | ((c1 & 0x3f) << 24) | ((c2 & 0x3f) << 18) - | ((c3 & 0x3f) << 12) | ((c4 & 0x3f) << 6) | (c5 & 0x3f); - } else { - u[n++] = '?'; - } - } - *length = n; - return u; -} - -void XFAFormField::getBBox(double *llx, double *lly, - double *urx, double *ury) { - double xfaX, xfaY, xfaW, xfaH, pdfX, pdfY, pdfW, pdfH; - int pdfRot; - - getRectangle(&xfaX, &xfaY, &xfaW, &xfaH, - &pdfX, &pdfY, &pdfW, &pdfH, &pdfRot); - *llx = pdfX; - *lly = pdfY; - *urx = pdfX + pdfW; - *ury = pdfY + pdfH; -} - -void XFAFormField::getFont(Ref *fontID, double *fontSize) { - ZxElement *fontElem; - ZxAttr *attr; - GBool bold, italic; - - fontID->num = fontID->gen = -1; - *fontSize = 0; - if ((fontElem = xml->findFirstChildElement("font"))) { - bold = italic = gFalse; - if ((attr = fontElem->findAttr("weight"))) { - if (!attr->getValue()->cmp("bold")) { - bold = gTrue; - } - } - if ((attr = fontElem->findAttr("posture"))) { - if (!attr->getValue()->cmp("italic")) { - italic = gTrue; - } - } - if ((attr = fontElem->findAttr("typeface"))) { - *fontID = findFontName(attr->getValue(), bold, italic); - } - if ((attr = fontElem->findAttr("size"))) { - *fontSize = getMeasurement(attr, 0); - } - } -} - -void XFAFormField::getColor(double *red, double *green, double *blue) { - ZxElement *fontElem, *fillElem, *colorElem; - ZxAttr *attr; - int r, g, b; - - *red = *green = *blue = 0; - if ((fontElem = xml->findFirstChildElement("font"))) { - if ((fillElem = fontElem->findFirstChildElement("fill"))) { - if ((colorElem = fillElem->findFirstChildElement("color"))) { - if ((attr = colorElem->findAttr("value"))) { - if (sscanf(attr->getValue()->getCString(), "%d,%d,%d", - &r, &g, &b) == 3) { - *red = r / 255.0; - *green = g / 255.0; - *blue = b / 255.0; - } - } - } - } - } -} - -void XFAFormField::draw(int pageNumA, Gfx *gfx, GBool printing, - GfxFontDict *fontDict) { - ZxElement *uiElem; - ZxNode *node; - ZxAttr *attr; - GString *appearBuf; - MemStream *appearStream; - Object appearDict, appearance, resourceDict, resourceSubdict; - Object fontResources, fontResource, defaultFont; - Object obj1, obj2; - char *resType, *fontName; - double mat[6]; - double xfaX, xfaY, xfaW, xfaH, pdfX, pdfY, pdfW, pdfH; - int rot3, i; - - if (pageNumA != pageNum) { - return; - } - - // check the 'presence' attribute - if ((attr = xml->findAttr("presence"))) { - if (!attr->getValue()->cmp("hidden") || - !attr->getValue()->cmp("invisible")) { - return; - } - } - - getRectangle(&xfaX, &xfaY, &xfaW, &xfaH, &pdfX, &pdfY, &pdfW, &pdfH, &rot3); - - // generate transform matrix - switch (rot3) { - case 0: - default: - mat[0] = 1; mat[1] = 0; - mat[2] = 0; mat[3] = 1; - mat[4] = 0; mat[5] = 0; - break; - case 90: - mat[0] = 0; mat[1] = 1; - mat[2] = -1; mat[3] = 0; - mat[4] = xfaH; mat[5] = 0; - break; - case 180: - mat[0] = -1; mat[1] = 0; - mat[2] = 0; mat[3] = -1; - mat[4] = xfaW; mat[5] = xfaH; - break; - case 270: - mat[0] = 0; mat[1] = -1; - mat[2] = 1; mat[3] = 0; - mat[4] = 0; mat[5] = xfaW; - break; - } - - // get the appearance stream data - appearBuf = new GString(); -#if 0 //~ for debugging - double debugPad = 0; - appearBuf->appendf("q 1 1 0 rg {0:.4f} {1:.4f} {2:.4f} {3:.4f} re f Q\n", - debugPad, debugPad, xfaW - 2*debugPad, xfaH - 2*debugPad); -#endif - if ((uiElem = xml->findFirstChildElement("ui"))) { - for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) { - if (node->isElement("textEdit") || - node->isElement("numericEdit")) { - drawTextEdit(fontDict, xfaW, xfaH, rot3, appearBuf); - break; - } else if (node->isElement("dateTimeEdit")) { - drawTextEdit(fontDict, xfaW, xfaH, rot3, appearBuf); - break; - } else if (node->isElement("choiceList")) { - drawTextEdit(fontDict, xfaW, xfaH, rot3, appearBuf); - break; - } else if (node->isElement("checkButton")) { - drawCheckButton(fontDict, xfaW, xfaH, rot3, appearBuf); - break; - } else if (node->isElement("barcode")) { - drawBarCode(fontDict, xfaW, xfaH, rot3, appearBuf); - break; - } - //~ other field types go here - } - } else { - drawTextEdit(fontDict, xfaW, xfaH, rot3, appearBuf); - } - - // create the appearance stream - appearDict.initDict(xfaForm->doc->getXRef()); - appearDict.dictAdd(copyString("Length"), - obj1.initInt(appearBuf->getLength())); - appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); - obj1.initArray(xfaForm->doc->getXRef()); - obj1.arrayAdd(obj2.initReal(0)); - obj1.arrayAdd(obj2.initReal(0)); - obj1.arrayAdd(obj2.initReal(xfaW)); - obj1.arrayAdd(obj2.initReal(xfaH)); - appearDict.dictAdd(copyString("BBox"), &obj1); - obj1.initArray(xfaForm->doc->getXRef()); - obj1.arrayAdd(obj2.initReal(mat[0])); - obj1.arrayAdd(obj2.initReal(mat[1])); - obj1.arrayAdd(obj2.initReal(mat[2])); - obj1.arrayAdd(obj2.initReal(mat[3])); - obj1.arrayAdd(obj2.initReal(mat[4])); - obj1.arrayAdd(obj2.initReal(mat[5])); - appearDict.dictAdd(copyString("Matrix"), &obj1); - // NB: we need to deep-copy the resource dict and font resource - // subdict, because the font subdict is modified, and multiple - // threads can be sharing these objects. - resourceDict.initDict(xfaForm->doc->getXRef()); - if (xfaForm->resourceDict.isDict()) { - for (i = 0; i < xfaForm->resourceDict.dictGetLength(); ++i) { - resType = xfaForm->resourceDict.dictGetKey(i); - if (strcmp(resType, "Font")) { - xfaForm->resourceDict.dictGetVal(i, &resourceSubdict); - resourceDict.dictAdd(copyString(resType), &resourceSubdict); - } - } - } - fontResources.initDict(xfaForm->doc->getXRef()); - if (xfaForm->resourceDict.isDict() && - xfaForm->resourceDict.dictLookup("Font", &resourceSubdict)->isDict()) { - for (i = 0; i < resourceSubdict.dictGetLength(); ++i) { - fontName = resourceSubdict.dictGetKey(i); - resourceSubdict.dictGetVal(i, &fontResource); - fontResources.dictAdd(copyString(fontName), &fontResource); - } - resourceSubdict.free(); - } - defaultFont.initDict(xfaForm->doc->getXRef()); - defaultFont.dictAdd(copyString("Type"), obj1.initName("Font")); - defaultFont.dictAdd(copyString("Subtype"), obj1.initName("Type1")); - defaultFont.dictAdd(copyString("BaseFont"), obj1.initName("Helvetica")); - defaultFont.dictAdd(copyString("Encoding"), obj1.initName("WinAnsiEncoding")); - fontResources.dictAdd(copyString("xpdf_default_font"), &defaultFont); - resourceDict.dictAdd(copyString("Font"), &fontResources); - appearDict.dictAdd(copyString("Resources"), &resourceDict); - appearStream = new MemStream(appearBuf->getCString(), 0, - appearBuf->getLength(), &appearDict); - appearance.initStream(appearStream); - gfx->drawAnnot(&appearance, NULL, pdfX, pdfY, pdfX + pdfW, pdfY + pdfH); - appearance.free(); - delete appearBuf; -} - -void XFAFormField::getRectangle(double *xfaX, double *xfaY, - double *xfaW, double *xfaH, - double *pdfX, double *pdfY, - double *pdfW, double *pdfH, - int *pdfRot) { - Page *page; - PDFRectangle *pageRect; - ZxElement *captionElem, *paraElem, *marginElem; - ZxAttr *attr; - double x2, y2, w2, h2, t; - double anchorX, anchorY; - int pageRot, rot; - - page = xfaForm->doc->getCatalog()->getPage(pageNum); - pageRect = page->getMediaBox(); - pageRot = page->getRotate(); - - anchorX = 0; - anchorY = 0; - if ((attr = xml->findAttr("anchorType"))) { - if (!attr->getValue()->cmp("topLeft")) { - anchorX = 0; - anchorY = 0; - } else if (!attr->getValue()->cmp("topCenter")) { - anchorX = 0.5; - anchorY = 0; - } else if (!attr->getValue()->cmp("topRight")) { - anchorX = 1; - anchorY = 0; - } else if (!attr->getValue()->cmp("middleLeft")) { - anchorX = 0; - anchorY = 0.5; - } else if (!attr->getValue()->cmp("middleCenter")) { - anchorX = 0.5; - anchorY = 0.5; - } else if (!attr->getValue()->cmp("middleRight")) { - anchorX = 1; - anchorY = 0.5; - } else if (!attr->getValue()->cmp("bottomLeft")) { - anchorX = 0; - anchorY = 1; - } else if (!attr->getValue()->cmp("bottomCenter")) { - anchorX = 0.5; - anchorY = 1; - } else if (!attr->getValue()->cmp("bottomRight")) { - anchorX = 1; - anchorY = 1; - } - } - *xfaX = getMeasurement(xml->findAttr("x"), 0) + xOffset; - *xfaY = getMeasurement(xml->findAttr("y"), 0) + yOffset; - if (!(attr = xml->findAttr("w"))) { - attr = xml->findAttr("minW"); - } - *xfaW = getMeasurement(attr, 0); - if (*xfaW < columnWidth) { - *xfaW = columnWidth; - } - if (!(attr = xml->findAttr("h"))) { - attr = xml->findAttr("minH"); - } - *xfaH = getMeasurement(attr, 0); - if (*xfaH < rowHeight) { - *xfaH = rowHeight; - } - if ((attr = xml->findAttr("rotate"))) { - rot = atoi(attr->getValue()->getCString()); - if ((rot %= 360) < 0) { - rot += 360; - } - } else { - rot = 0; - } - - // look for -- add the caption width/height - // (plus a margin allowance) - if ((captionElem = xml->findFirstChildElement("caption"))) { - if ((attr = captionElem->findAttr("reserve"))) { - t = getMeasurement(attr, 0); - if ((attr = captionElem->findAttr("placement"))) { - if (!attr->getValue()->cmp("left")) { - *xfaX += t + 1.5; - *xfaW -= t + 1.5; - } else if (!attr->getValue()->cmp("right")) { - *xfaW -= t + 1.5; - } else if (!attr->getValue()->cmp("top")) { - *xfaY += t; - *xfaH -= t; - } else if (!attr->getValue()->cmp("bottom")) { - *xfaH -= t; - } - } else { - // default is placement="left" - *xfaX += t + 1.5; - *xfaW -= t + 1.5; - } - } - } - - // look for - if ((marginElem = xml->findFirstChildElement("margin"))) { - if ((attr = marginElem->findAttr("leftInset"))) { - t = getMeasurement(attr, 0); - *xfaX += t; - *xfaW -= t; - } - if ((attr = marginElem->findAttr("rightInset"))) { - t = getMeasurement(attr, 0); - *xfaW -= t; - } - if ((attr = marginElem->findAttr("topInset"))) { - t = getMeasurement(attr, 0); - *xfaY += t; - *xfaH -= t; - } - if ((attr = marginElem->findAttr("bottomInset"))) { - t = getMeasurement(attr, 0); - *xfaH -= t; - } - } - - // look for -- add the margins - if ((paraElem = xml->findFirstChildElement("para"))) { - if ((attr = paraElem->findAttr("marginLeft"))) { - t = getMeasurement(attr, 0); - *xfaX += t; - *xfaW -= t; - } - if ((attr = paraElem->findAttr("marginRight"))) { - t = getMeasurement(attr, 0); - *xfaW -= t; - } - if ((attr = paraElem->findAttr("spaceAbove"))) { - t = getMeasurement(attr, 0); - *xfaY += t; - *xfaH -= t; - } - if ((attr = paraElem->findAttr("spaceBelow"))) { - t = getMeasurement(attr, 0); - *xfaH -= t; - } - } - - // get annot rect (UL corner, width, height) in XFA coords - // notes: - // - XFA coordinates are top-left origin, after page rotation - // - XFA coordinates are dependent on choice of anchor point - // and field rotation - switch (rot) { - case 0: - default: - x2 = *xfaX - anchorX * *xfaW; - y2 = *xfaY - anchorY * *xfaH; - w2 = *xfaW; - h2 = *xfaH; - break; - case 90: - x2 = *xfaX - anchorY * *xfaH; - y2 = *xfaY - (1 - anchorX) * *xfaW; - w2 = *xfaH; - h2 = *xfaW; - break; - case 180: - x2 = *xfaX - (1 - anchorX) * *xfaW; - y2 = *xfaY - (1 - anchorY) * *xfaH; - w2 = *xfaW; - h2 = *xfaH; - break; - case 270: - x2 = *xfaX - (1 - anchorY) * *xfaH; - y2 = *xfaY - anchorX * *xfaW; - w2 = *xfaH; - h2 = *xfaW; - break; - } - - // convert annot rect to PDF coords (LL corner, width, height), - // taking page rotation into account - switch (pageRot) { - case 0: - default: - *pdfX = pageRect->x1 + x2; - *pdfY = pageRect->y2 - (y2 + h2); - *pdfW = w2; - *pdfH = h2; - break; - case 90: - *pdfX = pageRect->x1 + y2; - *pdfY = pageRect->y1 + x2; - *pdfW = h2; - *pdfH = w2; - break; - case 180: - *pdfX = pageRect->x2 - (x2 + w2); - *pdfY = pageRect->y1 + y2; - *pdfW = w2; - *pdfH = h2; - break; - case 270: - *pdfX = pageRect->x2 - (y2 + h2); - *pdfY = pageRect->y1 + (x2 + w2); - *pdfW = h2; - *pdfH = w2; - break; - } - *pdfRot = (rot + pageRot) % 360; -} - -void XFAFormField::drawTextEdit(GfxFontDict *fontDict, - double w, double h, int rot, - GString *appearBuf) { - ZxElement *formatElem, *pictureElem; - ZxNode *pictureChildElem; - ZxElement *valueElem, *textElem, *uiElem, *textEditElem, *combElem; - ZxElement *fontElem, *paraElem; - ZxAttr *attr; - GString *picture, *value, *formattedValue, *fontName; - double fontSize; - int maxChars, combCells; - GBool multiLine, bold, italic; - XFAHorizAlign hAlign; - XFAVertAlign vAlign; - - if (!(value = getFieldValue("text"))) { - return; - } - - uiElem = xml->findFirstChildElement("ui"); - - //--- picture formatting - if (uiElem && - (formatElem = xml->findFirstChildElement("format")) && - (pictureElem = formatElem->findFirstChildElement("picture")) && - (pictureChildElem = pictureElem->getFirstChild()) && - pictureChildElem->isCharData()) { - picture = ((ZxCharData *)pictureChildElem)->getData(); - if (uiElem->findFirstChildElement("dateTimeEdit")) { - formattedValue = pictureFormatDateTime(value, picture); - } else if (uiElem->findFirstChildElement("numericEdit")) { - formattedValue = pictureFormatNumber(value, picture); - } else if (uiElem->findFirstChildElement("textEdit")) { - formattedValue = pictureFormatText(value, picture); - } else { - formattedValue = value->copy(); - } - } else { - formattedValue = value->copy(); - } - - maxChars = 0; - if ((valueElem = xml->findFirstChildElement("value")) && - (textElem = valueElem->findFirstChildElement("text")) && - (attr = textElem->findAttr("maxChars"))) { - maxChars = atoi(attr->getValue()->getCString()); - } - - multiLine = gFalse; - combCells = 0; - if (uiElem && - (textEditElem = uiElem->findFirstChildElement("textEdit"))) { - if ((attr = textEditElem->findAttr("multiLine")) && - !attr->getValue()->cmp("1")) { - multiLine = gTrue; - } - if ((combElem = textEditElem->findFirstChildElement("comb"))) { - if ((attr = combElem->findAttr("numberOfCells"))) { - combCells = atoi(attr->getValue()->getCString()); - } else { - combCells = maxChars; - } - } - } - - fontName = NULL; - fontSize = 10; - bold = gFalse; - italic = gFalse; - if ((fontElem = xml->findFirstChildElement("font"))) { - if ((attr = fontElem->findAttr("typeface"))) { - fontName = attr->getValue()->copy(); - } - if ((attr = fontElem->findAttr("weight"))) { - if (!attr->getValue()->cmp("bold")) { - bold = gTrue; - } - } - if ((attr = fontElem->findAttr("posture"))) { - if (!attr->getValue()->cmp("italic")) { - italic = gTrue; - } - } - if ((attr = fontElem->findAttr("size"))) { - fontSize = getMeasurement(attr, fontSize); - } - } - if (!fontName) { - fontName = new GString("Courier"); - } - - hAlign = xfaHAlignLeft; - vAlign = xfaVAlignTop; - if ((paraElem = xml->findFirstChildElement("para"))) { - if ((attr = paraElem->findAttr("hAlign"))) { - if (!attr->getValue()->cmp("left")) { - hAlign = xfaHAlignLeft; - } else if (!attr->getValue()->cmp("center")) { - hAlign = xfaHAlignCenter; - } else if (!attr->getValue()->cmp("right")) { - hAlign = xfaHAlignRight; - } - //~ other hAlign values (justify, justifyAll, radix) are - //~ currently unsupported - } - if ((attr = paraElem->findAttr("vAlign"))) { - if (!attr->getValue()->cmp("top")) { - vAlign = xfaVAlignTop; - } else if (!attr->getValue()->cmp("bottom")) { - vAlign = xfaVAlignBottom; - } else if (!attr->getValue()->cmp("middle")) { - vAlign = xfaVAlignMiddle; - } - } - } - - drawText(formattedValue, multiLine, combCells, - fontName, bold, italic, fontSize, - hAlign, vAlign, 0, 0, w, h, gFalse, fontDict, appearBuf); - delete fontName; - delete formattedValue; -} - -void XFAFormField::drawCheckButton(GfxFontDict *fontDict, - double w, double h, int rot, - GString *appearBuf) { - ZxElement *items; - ZxNode *firstItem, *firstItemVal; - const char *itemType; - const char *onValue; - GString *value; - double x, y; - - // get the "on" value for this check button - itemType = "integer"; - onValue = "1"; - if ((items = xml->findFirstChildElement("items"))) { - if ((firstItem = items->getFirstChild())) { - if (firstItem->isElement("text") && firstItem->getFirstChild()) { - itemType = "text"; - if ((firstItemVal = firstItem->getFirstChild())->isCharData()) { - onValue = ((ZxCharData *)firstItemVal)->getData()->getCString(); - } - } else if (firstItem->isElement("integer") && - firstItem->getFirstChild()) { - itemType = "integer"; - if ((firstItemVal = firstItem->getFirstChild())->isCharData()) { - onValue = ((ZxCharData *)firstItemVal)->getData()->getCString(); - } - } - } - } - - // compare its value to the "on" value - if (!(value = getFieldValue(itemType))) { - return; - } - if (value->cmp(onValue)) { - return; - } - - // adjust shape to be square - x = y = 0; - if (w > h) { - x += 0.5 * (w - h); - w = h; - } else { - y += 0.5 * (h - w); - h = w; - } - - // margins - x += 1.5; - w -= 3; - y += 1.5; - h -= 3; - - //~ this should look at the "shape" and "mark" attributes - - appearBuf->appendf("0.5 w {0:.4f} {1:.4f} m {2:.4f} {3:.4f} l {0:.4f} {3:.4f} m {2:.4f} {1:.4f} l S\n", - x, y, x + w, y + h); -} - -void XFAFormField::drawBarCode(GfxFontDict *fontDict, - double w, double h, int rot, - GString *appearBuf) { - ZxElement *uiElem, *barcodeElem, *fontElem; - ZxAttr *attr; - GString *value, *value2, *barcodeType, *textLocation, *fontName, *s1, *s2; - XFAVertAlign textAlign; - double wideNarrowRatio, moduleWidth, moduleHeight, fontSize; - double yText, wText, yBarcode, hBarcode, wNarrow, xx; - GBool doText; - int dataLength, errorCorrectionLevel, checksum; - GBool bold, italic; - char *p; - int i, j, c; - - //--- get field value - if (!(value = getFieldValue("text"))) { - return; - } - - //--- get font - fontName = NULL; - fontSize = 0.2 * h; - bold = gFalse; - italic = gFalse; - if ((fontElem = xml->findFirstChildElement("font"))) { - if ((attr = fontElem->findAttr("typeface"))) { - fontName = attr->getValue()->copy(); - } - if ((attr = fontElem->findAttr("weight"))) { - if (!attr->getValue()->cmp("bold")) { - bold = gTrue; - } - } - if ((attr = fontElem->findAttr("posture"))) { - if (!attr->getValue()->cmp("italic")) { - italic = gTrue; - } - } - if ((attr = fontElem->findAttr("size"))) { - fontSize = getMeasurement(attr, fontSize); - } - } - if (!fontName) { - fontName = new GString("Courier"); - } - - //--- get field attributes - barcodeType = NULL; - wideNarrowRatio = 3; - moduleWidth = 0; - moduleHeight = 0; - dataLength = 0; - errorCorrectionLevel = 0; - textLocation = NULL; - if ((uiElem = xml->findFirstChildElement("ui")) && - (barcodeElem = uiElem->findFirstChildElement("barcode"))) { - if ((attr = barcodeElem->findAttr("type"))) { - barcodeType = attr->getValue(); - } - if ((attr = barcodeElem->findAttr("wideNarrowRatio"))) { - s1 = attr->getValue(); - if ((p = strchr(s1->getCString(), ':'))) { - s2 = new GString(s1, 0, (int)(p - s1->getCString())); - wideNarrowRatio = atof(p + 1); - if (wideNarrowRatio == 0) { - wideNarrowRatio = 1; - } - wideNarrowRatio = atof(s2->getCString()) / wideNarrowRatio; - delete s2; - } else { - wideNarrowRatio = atof(s1->getCString()); - } - } - if ((attr = barcodeElem->findAttr("moduleWidth"))) { - moduleWidth = getMeasurement(attr, (0.25 / 25.4) * 72.0); // 0.25mm - } - if ((attr = barcodeElem->findAttr("moduleHeight"))) { - moduleHeight = getMeasurement(attr, (0.5 / 25.4) * 72.0); // 0.5mm - } - if ((attr = barcodeElem->findAttr("dataLength"))) { - dataLength = atoi(attr->getValue()->getCString()); - } - if ((attr = barcodeElem->findAttr("errorCorrectionLevel"))) { - errorCorrectionLevel = atoi(attr->getValue()->getCString()); - } - if ((attr = barcodeElem->findAttr("textLocation"))) { - textLocation = attr->getValue(); - } - } - if (!barcodeType) { - error(errSyntaxError, -1, "Missing 'type' attribute in XFA barcode field"); - delete fontName; - return; - } - - //--- compute the embedded text type position - doText = gTrue; - yText = yBarcode = hBarcode = 0; - if (textLocation && !textLocation->cmp("above")) { - textAlign = xfaVAlignTop; - yText = h; - yBarcode = 0; - hBarcode = h - fontSize; - } else if (textLocation && !textLocation->cmp("belowEmbedded")) { - textAlign = xfaVAlignBottom; - yText = 0; - yBarcode = 0; - hBarcode = h; - } else if (textLocation && !textLocation->cmp("aboveEmbedded")) { - textAlign = xfaVAlignTop; - yText = h; - yBarcode = 0; - hBarcode = h; - } else if (textLocation && !textLocation->cmp("none")) { - textAlign = xfaVAlignBottom; // make gcc happy - doText = gFalse; - } else { // default is "below" - textAlign = xfaVAlignBottom; - yText = 0; - yBarcode = fontSize; - hBarcode = h - fontSize; - } - wText = w; - - //--- remove extraneous start/stop chars - value2 = value->copy(); - if (!barcodeType->cmp("code3Of9")) { - if (value2->getLength() >= 1 && value2->getChar(0) == '*') { - value2->del(0); - } - if (value2->getLength() >= 1 && - value2->getChar(value2->getLength() - 1) == '*') { - value2->del(value2->getLength() - 1); - } - } - - //--- draw the bar code - if (!barcodeType->cmp("code3Of9")) { - if (!dataLength) { - error(errSyntaxError, -1, - "Missing 'dataLength' attribute in XFA barcode field"); - goto err; - } - appearBuf->append("0 g\n"); - wNarrow = w / ((7 + 3 * wideNarrowRatio) * (dataLength + 2)); - xx = 0; - for (i = -1; i <= value2->getLength(); ++i) { - if (i < 0 || i >= value2->getLength()) { - c = '*'; - } else { - c = value2->getChar(i) & 0x7f; - } - for (j = 0; j < 10; j += 2) { - appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n", - xx, yBarcode, - (code3Of9Data[c][j] ? wideNarrowRatio : 1) * wNarrow, - hBarcode); - xx += ((code3Of9Data[c][j] ? wideNarrowRatio : 1) + - (code3Of9Data[c][j+1] ? wideNarrowRatio : 1)) * wNarrow; - } - } - // center the text on the drawn barcode (not the max length barcode) - wText = (value2->getLength() + 2) * (7 + 3 * wideNarrowRatio) * wNarrow; - } else if (!barcodeType->cmp("code128B")) { - if (!dataLength) { - error(errSyntaxError, -1, - "Missing 'dataLength' attribute in XFA barcode field"); - goto err; - } - appearBuf->append("0 g\n"); - wNarrow = w / (11 * (dataLength + 3) + 2); - xx = 0; - checksum = 0; - for (i = -1; i <= value2->getLength() + 1; ++i) { - if (i == -1) { - // start code B - c = 104; - checksum += c; - } else if (i == value2->getLength()) { - // checksum - c = checksum % 103; - } else if (i == value2->getLength() + 1) { - // stop code - c = 106; - } else { - c = value2->getChar(i) & 0xff; - if (c >= 32 && c <= 127) { - c -= 32; - } else { - c = 0; - } - checksum += (i + 1) * c; - } - for (j = 0; j < 6; j += 2) { - appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n", - xx, yBarcode, - code128Data[c][j] * wNarrow, hBarcode); - xx += (code128Data[c][j] + code128Data[c][j+1]) * wNarrow; - } - } - // final bar of the stop code - appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n", - xx, yBarcode, 2 * wNarrow, hBarcode); - // center the text on the drawn barcode (not the max length barcode) - wText = (11 * (value2->getLength() + 3) + 2) * wNarrow; - } else if (!barcodeType->cmp("pdf417")) { - drawPDF417Barcode(w, h, moduleWidth, moduleHeight, errorCorrectionLevel, - value2, appearBuf); - doText = gFalse; - } else { - error(errSyntaxError, -1, - "Unimplemented barcode type '{0:t}' in XFA barcode field", - barcodeType); - } - //~ add other barcode types here - - //--- draw the embedded text - if (doText) { - appearBuf->append("0 g\n"); - drawText(value2, gFalse, 0, - fontName, bold, italic, fontSize, - xfaHAlignCenter, textAlign, 0, yText, wText, h, gTrue, - fontDict, appearBuf); - } - - err: - delete fontName; - delete value2; -} - -Object *XFAFormField::getResources(Object *res) { - return xfaForm->resourceDict.copy(res); -} - -double XFAFormField::getMeasurement(ZxAttr *attr, double defaultVal) { - GString *s; - - if (!attr) { - return defaultVal; - } - s = attr->getValue(); - return getMeasurement(s, 0); -} - -double XFAFormField::getMeasurement(GString *s, int begin) { - double val, mul; - GBool neg; - int i; - - i = begin; - neg = gFalse; - if (i < s->getLength() && s->getChar(i) == '+') { - ++i; - } else if (i < s->getLength() && s->getChar(i) == '-') { - neg = gTrue; - ++i; - } - val = 0; - while (i < s->getLength() && s->getChar(i) >= '0' && s->getChar(i) <= '9') { - val = val * 10 + s->getChar(i) - '0'; - ++i; - } - if (i < s->getLength() && s->getChar(i) == '.') { - ++i; - mul = 0.1; - while (i < s->getLength() && s->getChar(i) >= '0' && s->getChar(i) <= '9') { - val += mul * (s->getChar(i) - '0'); - mul *= 0.1; - ++i; - } - } - if (neg) { - val = -val; - } - if (i+1 < s->getLength()) { - if (s->getChar(i) == 'i' && s->getChar(i+1) == 'n') { - val *= 72; - } else if (s->getChar(i) == 'p' && s->getChar(i+1) == 't') { - // no change - } else if (s->getChar(i) == 'c' && s->getChar(i+1) == 'm') { - val *= 72 / 2.54; - } else if (s->getChar(i) == 'm' && s->getChar(i+1) == 'm') { - val *= 72 / 25.4; - } else { - // default to inches - val *= 72; - } - } else { - // default to inches - val *= 72; - } - return val; -} - -GString *XFAFormField::getFieldValue(const char *valueChildType) { - ZxElement *valueElem, *datasets, *data, *formElem, *elem; - char *p; - - // check the packet - p = name->getCString(); - if (xfaForm->xml->getRoot() && !strncmp(p, "form.", 5)) { - if ((datasets = - xfaForm->xml->getRoot()->findFirstChildElement("xfa:datasets")) && - (data = datasets->findFirstChildElement("xfa:data"))) { - elem = findFieldInDatasets(data, p + 5); - if (elem && - elem->getFirstChild() && - elem->getFirstChild()->isCharData() && - ((ZxCharData *)elem->getFirstChild())->getData()->getLength() > 0) { - return ((ZxCharData *)elem->getFirstChild())->getData(); - } - } - } - if (exclGroupName) { - p = exclGroupName->getCString(); - if (xfaForm->xml->getRoot() && !strncmp(p, "form.", 5)) { - if ((datasets = - xfaForm->xml->getRoot()->findFirstChildElement("xfa:datasets")) && - (data = datasets->findFirstChildElement("xfa:data"))) { - elem = findFieldInDatasets(data, p + 5); - if (elem && - elem->getFirstChild() && - elem->getFirstChild()->isCharData() && - ((ZxCharData *)elem->getFirstChild())->getData()->getLength() > 0) { - return ((ZxCharData *)elem->getFirstChild())->getData(); - } - } - } - } - - // check the
element - p = fullName->getCString(); - if (xfaForm->xml->getRoot() && !strncmp(p, "form.", 5)) { - if ((formElem = xfaForm->xml->getRoot()->findFirstChildElement("form"))) { - elem = findFieldInFormElem(formElem, p + 5); - if (elem && - (valueElem = elem->findFirstChildElement("value")) && - (elem = valueElem->findFirstChildElement(valueChildType))) { - if (elem->getFirstChild() && - elem->getFirstChild()->isCharData() && - ((ZxCharData *)elem->getFirstChild()) - ->getData()->getLength() > 0) { - return ((ZxCharData *)elem->getFirstChild())->getData(); - } - } - } - } - - // check the element within the field - if ((valueElem = xml->findFirstChildElement("value")) && - (elem = valueElem->findFirstChildElement(valueChildType))) { - if (elem->getFirstChild() && - elem->getFirstChild()->isCharData() && - ((ZxCharData *)elem->getFirstChild())->getData()->getLength() > 0) { - return ((ZxCharData *)elem->getFirstChild())->getData(); - } - } - - return NULL; -} - -ZxElement *XFAFormField::findFieldInDatasets(ZxElement *elem, char *partName) { - ZxNode *node; - ZxElement *result; - GString *nodeName; - char *next; - int curIdx, idx, n; - - curIdx = 0; - for (node = elem->getFirstChild(); node; node = node->getNextChild()) { - if (node->isElement()) { - nodeName = ((ZxElement *)node)->getType(); - n = nodeName->getLength(); - if (!strncmp(partName, nodeName->getCString(), n)) { - if (partName[n] == '[') { - idx = atoi(partName + n + 1); - if (idx == curIdx) { - for (++n; partName[n] && partName[n-1] != ']'; ++n) ; - } else { - ++curIdx; - continue; - } - } - if (!partName[n]) { - return (ZxElement *)node; - } else if (partName[n] == '.') { - if ((result = findFieldInDatasets((ZxElement *)node, - partName + n + 1))) { - return result; - } - break; - } - } - } - } - - // search for an "ancestor match" - if ((next = strchr(partName, '.'))) { - return findFieldInDatasets(elem, next + 1); - } - - return NULL; -} - -ZxElement *XFAFormField::findFieldInFormElem(ZxElement *elem, char *partName) { - ZxElement *elem2; - ZxNode *node; - ZxAttr *attr; - GString *nodeName; - int curIdx, idx, n; - - curIdx = 0; - for (node = elem->getFirstChild(); node; node = node->getNextChild()) { - if ((node->isElement("subform") || node->isElement("field")) && - ((attr = ((ZxElement *)node)->findAttr("name")))) { - nodeName = attr->getValue(); - n = nodeName->getLength(); - if (!strncmp(partName, nodeName->getCString(), n)) { - if (partName[n] == '[') { - idx = atoi(partName + n + 1); - if (idx == curIdx) { - for (++n; partName[n] && partName[n-1] != ']'; ++n) ; - } else { - ++curIdx; - continue; - } - } - if (!partName[n]) { - return (ZxElement *)node; - } else if (partName[n] == '.') { - return findFieldInFormElem((ZxElement *)node, partName + n + 1); - } - } - } else if (node->isElement("subform")) { - if ((elem2 = findFieldInFormElem((ZxElement *)node, partName))) { - return elem2; - } - } - } - return NULL; -} - -void XFAFormField::transform(int rot, double w, double h, - double *wNew, double *hNew, GString *appearBuf) { - switch (rot) { - case 0: - default: - appearBuf->appendf("1 0 0 1 0 {0:.4f} cm\n", -h); - break; - case 90: - appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", w); - *wNew = h; - *hNew = w; - break; - case 180: - appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n", w, h); - *wNew = w; - *hNew = h; - break; - case 270: - appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", h); - *wNew = h; - *hNew = w; - break; - } -} - -void XFAFormField::drawText(GString *text, GBool multiLine, int combCells, - GString *fontName, GBool bold, - GBool italic, double fontSize, - XFAHorizAlign hAlign, XFAVertAlign vAlign, - double x, double y, double w, double h, - GBool whiteBackground, - GfxFontDict *fontDict, GString *appearBuf) { - GString *text2; - GfxFont *font; - const char *fontTag; - GString *s; - Unicode u; - double yTop, xx, yy, tw, charWidth, lineHeight; - double ascent, descent, rectX, rectY, rectW, rectH, blkH; - int nLines, line, i, j, k, c, rectI; - - // convert UTF-8 to Latin1 - //~ this currently drops all non-Latin1 characters - text2 = new GString(); - i = 0; - while (getUTF8(text, &i, &u)) { - if (u <= 0xff) { - text2->append((char)u); - } else { - text2->append('?'); - } - } - - // find the font - if ((font = findFont(fontDict, fontName, bold, italic))) { - fontTag = font->getTag()->getCString(); - ascent = font->getAscent() * fontSize; - descent = font->getDescent() * fontSize; - } else { - error(errSyntaxError, -1, "Couldn't find a font for '{0:t}', {1:s}, {2:s} used in XFA field", - fontName, bold ? "bold" : "non-bold", - italic ? "italic" : "non-italic"); - fontTag = "xpdf_default_font"; - ascent = 0.718 * fontSize; - descent = -0.207 * fontSize; - } - - // setup - rectW = rectH = 0; - rectI = appearBuf->getLength(); - appearBuf->append("BT\n"); - appearBuf->appendf("/{0:s} {1:.2f} Tf\n", fontTag, fontSize); - - // multi-line text - if (multiLine) { - - // compute line height - lineHeight = 1.2 * fontSize; - - // handle bottom/middle alignment - if (vAlign == xfaVAlignBottom || vAlign == xfaVAlignMiddle) { - nLines = 0; - i = 0; - while (i < text2->getLength()) { - getNextLine(text2, i, font, fontSize, w, &j, &tw, &k); - ++nLines; - i = k; - } - blkH = nLines * lineHeight - (lineHeight - fontSize); - if (vAlign == xfaVAlignBottom) { - yTop = y + blkH; - } else { - yTop = y + 0.5 * (h + blkH); - } - if (yTop > y + h) { - yTop = y + h; - } - } else { - yTop = y + h; - } - - // write a series of lines of text - line = 0; - i = 0; - while (i < text2->getLength()) { - - getNextLine(text2, i, font, fontSize, w, &j, &tw, &k); - if (tw > rectW) { - rectW = tw; - } - - // compute text start position - switch (hAlign) { - case xfaHAlignLeft: - default: - xx = x; - break; - case xfaHAlignCenter: - xx = x + 0.5 * (w - tw); - break; - case xfaHAlignRight: - xx = x + w - tw; - break; - } - yy = yTop - line * lineHeight - ascent; - - // draw the line - appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", xx, yy); - appearBuf->append('('); - for (; i < j; ++i) { - c = text2->getChar(i) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append((char)c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("\\{0:03o}", c); - } else { - appearBuf->append((char)c); - } - } - appearBuf->append(") Tj\n"); - - // next line - i = k; - ++line; - } - rectH = line * lineHeight; - rectY = y + h - rectH; - - // comb formatting - } else if (combCells > 0) { - - // compute comb spacing - tw = w / combCells; - - // compute text start position - switch (hAlign) { - case xfaHAlignLeft: - default: - xx = x; - break; - case xfaHAlignCenter: - xx = x + (int)(0.5 * (combCells - text2->getLength())) * tw; - break; - case xfaHAlignRight: - xx = x + w - text2->getLength() * tw; - break; - } - rectW = text2->getLength() * tw; - switch (vAlign) { - case xfaVAlignTop: - default: - yy = y + h - ascent; - break; - case xfaVAlignMiddle: - yy = y + 0.5 * (h - (ascent + descent)); - break; - case xfaVAlignBottom: - yy = y - descent; - break; - } - rectY = yy + descent; - rectH = ascent - descent; - - // write the text string - for (i = 0; i < text2->getLength(); ++i) { - c = text2->getChar(i) & 0xff; - if (font && !font->isCIDFont()) { - charWidth = fontSize * ((Gfx8BitFont *)font)->getWidth((Guchar)c); - appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", - xx + i * tw + 0.5 * (tw - charWidth), yy); - } else { - appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", - xx + i * tw, yy); - } - appearBuf->append('('); - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append((char)c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("{0:.4f} 0 Td\n", w); - } else { - appearBuf->append((char)c); - } - appearBuf->append(") Tj\n"); - } - - // regular (non-comb) formatting - } else { - - // compute string width - if (font && !font->isCIDFont()) { - tw = 0; - for (i = 0; i < text2->getLength(); ++i) { - tw += ((Gfx8BitFont *)font)->getWidth(text2->getChar(i)); - } - } else { - // otherwise, make a crude estimate - tw = text2->getLength() * 0.5; - } - tw *= fontSize; - rectW = tw; - - // compute text start position - switch (hAlign) { - case xfaHAlignLeft: - default: - xx = x; - break; - case xfaHAlignCenter: - xx = x + 0.5 * (w - tw); - break; - case xfaHAlignRight: - xx = x + w - tw; - break; - } - switch (vAlign) { - case xfaVAlignTop: - default: - yy = y + h - ascent; - break; - case xfaVAlignMiddle: - yy = y + 0.5 * (h - (ascent + descent)); - break; - case xfaVAlignBottom: - yy = y - descent; - break; - } - rectY = yy + descent; - rectH = ascent - descent; - appearBuf->appendf("{0:.4f} {1:.4f} Td\n", xx, yy); - - // write the text string - appearBuf->append('('); - for (i = 0; i < text2->getLength(); ++i) { - c = text2->getChar(i) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append((char)c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("\\{0:03o}", c); - } else { - appearBuf->append((char)c); - } - } - appearBuf->append(") Tj\n"); - } - - // cleanup - appearBuf->append("ET\n"); - - // draw a white rectangle behind the text - if (whiteBackground) { - switch (hAlign) { - case xfaHAlignLeft: - default: - rectX = x; - break; - case xfaHAlignCenter: - rectX = x + 0.5 * (w - rectW); - break; - case xfaHAlignRight: - rectX = x + w - rectW; - break; - } - rectX -= 0.25 * fontSize; - rectW += 0.5 * fontSize; - rectY -= 0.1 * fontSize; - rectH += 0.2 * fontSize; - s = GString::format("q 1 g {0:.4f} {1:.4f} {2:.4f} {3:.4f} re f Q\n", - rectX, rectY, rectW, rectH); - appearBuf->insert(rectI, s); - delete s; - } - - delete text2; -} - -// Searches for a font matching(, , -// ). -GfxFont *XFAFormField::findFont(GfxFontDict *fontDict, GString *fontName, - GBool bold, GBool italic) { - GString *reqName, *testName; - GfxFont *font; - GBool foundName, foundBold, foundItalic; - char *p; - char c; - int i, j; - - if (!fontDict) { - return NULL; - } - - reqName = new GString(); - for (i = 0; i < fontName->getLength(); ++i) { - c = fontName->getChar(i); - if (c != ' ') { - reqName->append(c); - } - } - - for (i = 0; i < fontDict->getNumFonts(); ++i) { - font = fontDict->getFont(i); - if (!font || !font->getName()) { - continue; - } - testName = new GString(); - for (j = 0; j < font->getName()->getLength(); ++j) { - c = font->getName()->getChar(j); - if (c != ' ') { - testName->append(c); - } - } - foundName = foundBold = foundItalic = gFalse; - for (p = testName->getCString(); *p; ++p) { - if (!strncasecmp(p, reqName->getCString(), reqName->getLength())) { - foundName = gTrue; - } - if (!strncasecmp(p, "bold", 4)) { - foundBold = gTrue; - } - if (!strncasecmp(p, "italic", 6) || !strncasecmp(p, "oblique", 7)) { - foundItalic = gTrue; - } - } - delete testName; - if (foundName && foundBold == bold && foundItalic == italic) { - delete reqName; - return font; - } - } - - delete reqName; - return NULL; -} - -// Searches the font resource dict for a font matching(, , -// ), and returns the font object ID. This does not require a -// GfxFontDict, and it does not do any parsing of font objects beyond -// looking at BaseFont. -Ref XFAFormField::findFontName(GString *name, GBool bold, GBool italic) { - Object fontDictObj, fontObj, baseFontObj, fontRef; - Ref fontID; - GString *reqName, *testName; - GBool foundName, foundBold, foundItalic; - char *p; - char c; - int i; - - fontID.num = fontID.gen = -1; - - // remove space chars from the requested name - reqName = new GString(); - for (i = 0; i < name->getLength(); ++i) { - c = name->getChar(i); - if (c != ' ') { - reqName->append(c); - } - } - - if (xfaForm->resourceDict.isDict()) { - if (xfaForm->resourceDict.dictLookup("Font", &fontDictObj)->isDict()) { - for (i = 0; i < fontDictObj.dictGetLength() && fontID.num < 0; ++i) { - fontDictObj.dictGetVal(i, &fontObj); - if (fontObj.dictLookup("BaseFont", &baseFontObj)->isName()) { - - // remove space chars from the font name - testName = new GString(); - for (p = baseFontObj.getName(); *p; ++p) { - if (*p != ' ') { - testName->append(p); - } - } - - // compare the names - foundName = foundBold = foundItalic = gFalse; - for (p = testName->getCString(); *p; ++p) { - if (!strncasecmp(p, reqName->getCString(), reqName->getLength())) { - foundName = gTrue; - } - if (!strncasecmp(p, "bold", 4)) { - foundBold = gTrue; - } - if (!strncasecmp(p, "italic", 6) || !strncasecmp(p, "oblique", 7)) { - foundItalic = gTrue; - } - } - delete testName; - if (foundName && foundBold == bold && foundItalic == italic) { - if (fontObj.dictGetValNF(i, &fontRef)) { - fontID = fontRef.getRef(); - } - fontRef.free(); - } - } - - baseFontObj.free(); - fontObj.free(); - } - } - fontDictObj.free(); - } - delete reqName; - - return fontID; -} - -// Figure out how much text will fit on the next line. Returns: -// *end = one past the last character to be included -// *width = width of the characters start .. end-1 -// *next = index of first character on the following line -void XFAFormField::getNextLine(GString *text, int start, - GfxFont *font, double fontSize, double wMax, - int *end, double *width, int *next) { - double w, dw; - int j, k, c; - - // figure out how much text will fit on the line - //~ what does Adobe do with tabs? - w = 0; - for (j = start; j < text->getLength() && w <= wMax; ++j) { - c = text->getChar(j) & 0xff; - if (c == 0x0a || c == 0x0d) { - break; - } - if (font && !font->isCIDFont()) { - dw = ((Gfx8BitFont *)font)->getWidth((Guchar)c) * fontSize; - } else { - // otherwise, make a crude estimate - dw = 0.5 * fontSize; - } - w += dw; - } - if (w > wMax) { - for (k = j; k > start && text->getChar(k-1) != ' '; --k) ; - for (; k > start && text->getChar(k-1) == ' '; --k) ; - if (k > start) { - j = k; - } - if (j == start) { - // handle the pathological case where the first character is - // too wide to fit on the line all by itself - j = start + 1; - } - } - *end = j; - - // compute the width - w = 0; - for (k = start; k < j; ++k) { - if (font && !font->isCIDFont()) { - dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize; - } else { - // otherwise, make a crude estimate - dw = 0.5 * fontSize; - } - w += dw; - } - *width = w; - - // next line - while (j < text->getLength() && text->getChar(j) == ' ') { - ++j; - } - if (j < text->getLength() && text->getChar(j) == 0x0d) { - ++j; - } - if (j < text->getLength() && text->getChar(j) == 0x0a) { - ++j; - } - *next = j; -} - -//------------------------------------------------------------------------ -// 'picture' formatting -//------------------------------------------------------------------------ - -class XFAPictureNode { -public: - virtual ~XFAPictureNode() {} - virtual GBool isLiteral() { return gFalse; } - virtual GBool isSign() { return gFalse; } - virtual GBool isDigit() { return gFalse; } - virtual GBool isDecPt() { return gFalse; } - virtual GBool isSeparator() { return gFalse; } - virtual GBool isYear() { return gFalse; } - virtual GBool isMonth() { return gFalse; } - virtual GBool isDay() { return gFalse; } - virtual GBool isHour() { return gFalse; } - virtual GBool isMinute() { return gFalse; } - virtual GBool isSecond() { return gFalse; } - virtual GBool isChar() { return gFalse; } -}; - -class XFAPictureLiteral: public XFAPictureNode { -public: - XFAPictureLiteral(GString *sA) { s = sA; } - virtual ~XFAPictureLiteral() { delete s; } - virtual GBool isLiteral() { return gTrue; } - GString *s; -}; - -class XFAPictureSign: public XFAPictureNode { -public: - XFAPictureSign(char cA) { c = cA; } - virtual GBool isSign() { return gTrue; } - char c; -}; - -class XFAPictureDigit: public XFAPictureNode { -public: - XFAPictureDigit(char cA) { c = cA; pos = 0; } - virtual GBool isDigit() { return gTrue; } - char c; - int pos; -}; - -class XFAPictureDecPt: public XFAPictureNode { -public: - XFAPictureDecPt() { } - virtual GBool isDecPt() { return gTrue; } -}; - -class XFAPictureSeparator: public XFAPictureNode { -public: - XFAPictureSeparator() { } - virtual GBool isSeparator() { return gTrue; } -}; - -class XFAPictureYear: public XFAPictureNode { -public: - XFAPictureYear(int nDigitsA) { nDigits = nDigitsA; } - virtual GBool isYear() { return gTrue; } - int nDigits; -}; - -class XFAPictureMonth: public XFAPictureNode { -public: - XFAPictureMonth(int nDigitsA) { nDigits = nDigitsA; } - virtual GBool isMonth() { return gTrue; } - int nDigits; -}; - -class XFAPictureDay: public XFAPictureNode { -public: - XFAPictureDay(int nDigitsA) { nDigits = nDigitsA; } - virtual GBool isDay() { return gTrue; } - int nDigits; -}; - -class XFAPictureHour: public XFAPictureNode { -public: - XFAPictureHour(GBool is24HourA, int nDigitsA) - { is24Hour = is24HourA; nDigits = nDigitsA; } - virtual GBool isHour() { return gTrue; } - GBool is24Hour; - int nDigits; -}; - -class XFAPictureMinute: public XFAPictureNode { -public: - XFAPictureMinute(int nDigitsA) { nDigits = nDigitsA; } - virtual GBool isMinute() { return gTrue; } - int nDigits; -}; - -class XFAPictureSecond: public XFAPictureNode { -public: - XFAPictureSecond(int nDigitsA) { nDigits = nDigitsA; } - virtual GBool isSecond() { return gTrue; } - int nDigits; -}; - -class XFAPictureChar: public XFAPictureNode { -public: - XFAPictureChar() {} - virtual GBool isChar() { return gTrue; } -}; - -GString *XFAFormField::pictureFormatDateTime(GString *value, GString *picture) { - GList *pic; - XFAPictureNode *node; - GString *ret, *s; - char c; - int year, month, day, hour, min, sec; - int len, picStart, picEnd, u, n, i, j; - - len = value->getLength(); - if (len == 0) { - return value->copy(); - } - - //--- parse the value - - // expected format is yyyy(-mm(-dd)?)?Thh(:mm(:ss)?)? - // where: - // - the '-'s and ':'s are optional - // - the 'T' is literal - // - we're ignoring optional time zone info at the end - // (if the value is not in this canonical format, we just punt and - // return the value string) - //~ another option would be to parse the value following the - //~ element - year = month = day = hour = min = sec = 0; - i = 0; - if (!(i + 4 <= len && isValidInt(value, i, 4))) { - return value->copy(); - } - year = convertInt(value, i, 4); - i += 4; - if (i < len && value->getChar(i) == '-') { - ++i; - } - if (i + 2 <= len && isValidInt(value, i, 2)) { - month = convertInt(value, i, 2); - i += 2; - if (i < len && value->getChar(i) == '-') { - ++i; - } - if (i + 2 <= len && isValidInt(value, i, 2)) { - day = convertInt(value, i, 2); - i += 2; - } - } - if (i < len) { - if (value->getChar(i) != 'T') { - return value->copy(); - } - ++i; - if (!(i + 2 <= len && isValidInt(value, i, 2))) { - return value->copy(); - } - hour = convertInt(value, i, 2); - i += 2; - if (i < len && value->getChar(i) == ':') { - ++i; - } - if (i + 2 <= len && isValidInt(value, i, 2)) { - min = convertInt(value, i, 2); - i += 2; - if (i < len && value->getChar(i) == ':') { - ++i; - } - if (i + 2 <= len && isValidInt(value, i, 2)) { - sec = convertInt(value, i, 2); - i += 2; - } - } - } - if (i < len) { - return value->copy(); - } - - //--- skip the category and locale in the picture - - picStart = 0; - picEnd = picture->getLength(); - for (i = 0; i < picture->getLength(); ++i) { - c = picture->getChar(i); - if (c == '{') { - picStart = i + 1; - for (picEnd = picStart; - picEnd < picture->getLength() && picture->getChar(picEnd) != '}'; - ++picEnd) ; - break; - } else if (!((c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - c == '(' || - c == ')')) { - break; - } - } - - //--- parse the picture - - pic = new GList(); - i = picStart; - while (i < picEnd) { - c = picture->getChar(i); - ++i; - if (c == '\'') { - s = new GString(); - while (i < picEnd) { - c = picture->getChar(i); - if (c == '\'') { - ++i; - if (i < picEnd && picture->getChar(i) == '\'') { - s->append('\''); - ++i; - } else { - break; - } - } else if (c == '\\') { - ++i; - if (i == picEnd) { - break; - } - c = picture->getChar(i); - ++i; - if (c == 'u' && i+4 <= picEnd) { - u = 0; - for (j = 0; j < 4; ++j, ++i) { - c = picture->getChar(i); - u <<= 4; - if (c >= '0' && c <= '9') { - u += c - '0'; - } else if (c >= 'a' && c <= 'f') { - u += c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - u += c - 'A' + 10; - } - } - //~ this should convert to UTF-8 (?) - if (u <= 0xff) { - s->append((char)u); - } - } else { - s->append(c); - } - } else { - s->append(c); - } - } - pic->append(new XFAPictureLiteral(s)); - } else if (c == ',' || c == '-' || c == ':' || - c == '/' || c == '.' || c == ' ') { - s = new GString(); - s->append(c); - pic->append(new XFAPictureLiteral(s)); - } else if (c == 'Y') { - for (n = 1; n < 4 && i < picEnd && picture->getChar(i) == 'Y'; ++n, ++i) ; - pic->append(new XFAPictureYear(n)); - } else if (c == 'M') { - for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ; - pic->append(new XFAPictureMonth(n)); - } else if (c == 'D') { - for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'D'; ++n, ++i) ; - pic->append(new XFAPictureDay(n)); - } else if (c == 'h') { - for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'h'; ++n, ++i) ; - pic->append(new XFAPictureHour(gFalse, n)); - } else if (c == 'H') { - for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'H'; ++n, ++i) ; - pic->append(new XFAPictureHour(gTrue, n)); - } else if (c == 'M') { - for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ; - pic->append(new XFAPictureMinute(n)); - } else if (c == 'S') { - for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'S'; ++n, ++i) ; - pic->append(new XFAPictureSecond(n)); - } - } - - //--- generate formatted text - - ret = new GString(); - for (i = 0; i < pic->getLength(); ++i) { - node = (XFAPictureNode *)pic->get(i); - if (node->isLiteral()) { - ret->append(((XFAPictureLiteral *)node)->s); - } else if (node->isYear()) { - if (((XFAPictureYear *)node)->nDigits == 2) { - if (year >= 1930 && year < 2030) { - ret->appendf("{0:02d}", year % 100); - } else { - ret->append("??"); - } - } else { - ret->appendf("{0:04d}", year); - } - } else if (node->isMonth()) { - if (((XFAPictureMonth *)node)->nDigits == 1) { - ret->appendf("{0:d}", month); - } else { - ret->appendf("{0:02d}", month); - } - } else if (node->isDay()) { - if (((XFAPictureDay *)node)->nDigits == 1) { - ret->appendf("{0:d}", day); - } else { - ret->appendf("{0:02d}", day); - } - } else if (node->isHour()) { - if (((XFAPictureHour *)node)->is24Hour) { - n = hour; - } else { - n = hour % 12; - if (n == 0) { - n = 12; - } - } - if (((XFAPictureHour *)node)->nDigits == 1) { - ret->appendf("{0:d}", n); - } else { - ret->appendf("{0:02d}", n); - } - } else if (node->isMinute()) { - if (((XFAPictureMinute *)node)->nDigits == 1) { - ret->appendf("{0:d}", min); - } else { - ret->appendf("{0:02d}", min); - } - } else if (node->isSecond()) { - if (((XFAPictureSecond *)node)->nDigits == 1) { - ret->appendf("{0:d}", sec); - } else { - ret->appendf("{0:02d}", sec); - } - } - } - deleteGList(pic, XFAPictureNode); - - return ret; -} - -GString *XFAFormField::pictureFormatNumber(GString *value, GString *picture) { - GList *pic; - XFAPictureNode *node; - GString *ret, *s; - GBool neg, haveDigits; - char c; - int start, decPt, trailingZero, len; - int picStart, picEnd, u, pos, i, j; - - len = value->getLength(); - if (len == 0) { - return value->copy(); - } - - //--- parse the value - - // -nnnn.nnnn0000 - // ^ ^ ^ ^ - // | | | +-- len - // | | +------ trailingZero - // | +----------- decPt - // +--------------- start - start = 0; - neg = gFalse; - if (value->getChar(start) == '-') { - neg = gTrue; - ++start; - } else if (value->getChar(start) == '+') { - ++start; - } - for (decPt = start; decPt < len && value->getChar(decPt) != '.'; ++decPt) ; - for (trailingZero = len; - trailingZero > decPt && value->getChar(trailingZero - 1) == '0'; - --trailingZero) ; - - //--- skip the category and locale in the picture - - picStart = 0; - picEnd = picture->getLength(); - for (i = 0; i < picture->getLength(); ++i) { - c = picture->getChar(i); - if (c == '{') { - picStart = i + 1; - for (picEnd = picStart; - picEnd < picture->getLength() && picture->getChar(picEnd) != '}'; - ++picEnd) ; - break; - } else if (!((c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - c == '(' || - c == ')')) { - break; - } - } - - //--- parse the picture - - pic = new GList(); - i = picStart; - while (i < picEnd) { - c = picture->getChar(i); - ++i; - if (c == '\'') { - s = new GString(); - while (i < picEnd) { - c = picture->getChar(i); - if (c == '\'') { - ++i; - if (i < picEnd && picture->getChar(i) == '\'') { - s->append('\''); - ++i; - } else { - break; - } - } else if (c == '\\') { - ++i; - if (i == picEnd) { - break; - } - c = picture->getChar(i); - ++i; - if (c == 'u' && i+4 <= picEnd) { - u = 0; - for (j = 0; j < 4; ++j, ++i) { - c = picture->getChar(i); - u <<= 4; - if (c >= '0' && c <= '9') { - u += c - '0'; - } else if (c >= 'a' && c <= 'F') { - u += c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - u += c - 'A' + 10; - } - } - //~ this should convert to UTF-8 (?) - if (u <= 0xff) { - s->append((char)u); - } - } else { - s->append(c); - } - } else { - s->append(c); - ++i; - } - } - pic->append(new XFAPictureLiteral(s)); - } else if (c == '-' || c == ':' || c == '/' || c == ' ') { - s = new GString(); - s->append(c); - pic->append(new XFAPictureLiteral(s)); - } else if (c == 's' || c == 'S') { - pic->append(new XFAPictureSign(c)); - } else if (c == 'Z' || c == 'z' || c == '8' || c == '9') { - pic->append(new XFAPictureDigit(c)); - } else if (c == '.') { - pic->append(new XFAPictureDecPt()); - } else if (c == ',') { - pic->append(new XFAPictureSeparator()); - } - } - for (i = 0; i < pic->getLength(); ++i) { - node = (XFAPictureNode *)pic->get(i); - if (node->isDecPt()) { - break; - } - } - pos = 0; - for (j = i - 1; j >= 0; --j) { - node = (XFAPictureNode *)pic->get(j); - if (node->isDigit()) { - ((XFAPictureDigit *)node)->pos = pos; - ++pos; - } - } - pos = -1; - for (j = i + 1; j < pic->getLength(); ++j) { - node = (XFAPictureNode *)pic->get(j); - if (node->isDigit()) { - ((XFAPictureDigit *)node)->pos = pos; - --pos; - } - } - - //--- generate formatted text - - ret = new GString(); - haveDigits = gFalse; - for (i = 0; i < pic->getLength(); ++i) { - node = (XFAPictureNode *)pic->get(i); - if (node->isLiteral()) { - ret->append(((XFAPictureLiteral *)node)->s); - } else if (node->isSign()) { - if (((XFAPictureSign *)node)->c == 'S') { - ret->append(neg ? '-' : ' '); - } else { - if (neg) { - ret->append('-'); - } - } - } else if (node->isDigit()) { - pos = ((XFAPictureDigit *)node)->pos; - c = ((XFAPictureDigit *)node)->c; - if (pos >= 0 && pos < decPt - start) { - ret->append(value->getChar(decPt - 1 - pos)); - haveDigits = gTrue; - } else if (pos < 0 && -pos <= trailingZero - decPt - 1) { - ret->append(value->getChar(decPt - pos)); - haveDigits = gTrue; - } else if (c == '8' && - pos < 0 && - -pos <= len - decPt - 1) { - ret->append('0'); - haveDigits = gTrue; - } else if (c == '9') { - ret->append('0'); - haveDigits = gTrue; - } else if (c == 'Z' && pos >= 0) { - ret->append(' '); - } - } else if (node->isDecPt()) { - if (!(i+1 < pic->getLength() && - ((XFAPictureNode *)pic->get(i+1))->isDigit() && - ((XFAPictureDigit *)pic->get(i+1))->c == 'z') || - trailingZero > decPt + 1) { - ret->append('.'); - } - } else if (node->isSeparator()) { - if (haveDigits) { - ret->append(','); - } - } - } - deleteGList(pic, XFAPictureNode); - - return ret; -} - -GString *XFAFormField::pictureFormatText(GString *value, GString *picture) { - GList *pic; - XFAPictureNode *node; - GString *ret, *s; - char c; - int len, picStart, picEnd, u, i, j; - - len = value->getLength(); - if (len == 0) { - return value->copy(); - } - - //--- skip the category and locale in the picture - - picStart = 0; - picEnd = picture->getLength(); - for (i = 0; i < picture->getLength(); ++i) { - c = picture->getChar(i); - if (c == '{') { - picStart = i + 1; - for (picEnd = picStart; - picEnd < picture->getLength() && picture->getChar(picEnd) != '}'; - ++picEnd) ; - break; - } else if (!((c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - c == '(' || - c == ')')) { - break; - } - } - - //--- parse the picture - - pic = new GList(); - i = picStart; - while (i < picEnd) { - c = picture->getChar(i); - ++i; - if (c == '\'') { - s = new GString(); - while (i < picEnd) { - c = picture->getChar(i); - if (c == '\'') { - ++i; - if (i < picEnd && picture->getChar(i) == '\'') { - s->append('\''); - ++i; - } else { - break; - } - } else if (c == '\\') { - ++i; - if (i == picEnd) { - break; - } - c = picture->getChar(i); - ++i; - if (c == 'u' && i+4 <= picEnd) { - u = 0; - for (j = 0; j < 4; ++j, ++i) { - c = picture->getChar(i); - u <<= 4; - if (c >= '0' && c <= '9') { - u += c - '0'; - } else if (c >= 'a' && c <= 'F') { - u += c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - u += c - 'A' + 10; - } - } - //~ this should convert to UTF-8 (?) - if (u <= 0xff) { - s->append((char)u); - } - } else { - s->append(c); - } - } else { - s->append(c); - ++i; - } - } - pic->append(new XFAPictureLiteral(s)); - } else if (c == ',' || c == '-' || c == ':' || - c == '/' || c == '.' || c == ' ') { - s = new GString(); - s->append(c); - pic->append(new XFAPictureLiteral(s)); - } else if (c == 'A' || c == 'X' || c == 'O' || c == '0' || c == '9') { - pic->append(new XFAPictureChar()); - } - } - - //--- generate formatted text - - ret = new GString(); - j = 0; - for (i = 0; i < pic->getLength(); ++i) { - node = (XFAPictureNode *)pic->get(i); - if (node->isLiteral()) { - ret->append(((XFAPictureLiteral *)node)->s); - } else if (node->isChar()) { - // if there are more chars in the picture than in the value, - // Adobe renders the value as-is, without picture formatting - if (j >= value->getLength()) { - delete ret; - ret = value->copy(); - break; - } - ret->append(value->getChar(j)); - ++j; - } - } - deleteGList(pic, XFAPictureNode); - - return ret; -} - -GBool XFAFormField::isValidInt(GString *s, int start, int len) { - int i; - - for (i = 0; i < len; ++i) { - if (!(start + i < s->getLength() && - s->getChar(start + i) >= '0' && - s->getChar(start + i) <= '9')) { - return gFalse; - } - } - return gTrue; -} - -int XFAFormField::convertInt(GString *s, int start, int len) { - char c; - int x, i; - - x = 0; - for (i = 0; i < len && start + i < s->getLength(); ++i) { - c = s->getChar(start + i); - if (c < '0' || c > '9') { - break; - } - x = x * 10 + (c - '0'); - } - return x; -} diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.h b/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.h deleted file mode 100644 index 2f56e728a98..00000000000 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.h +++ /dev/null @@ -1,181 +0,0 @@ -//======================================================================== -// -// XFAForm.h -// -// Copyright 2012 Glyph & Cog, LLC -// -//======================================================================== - -#ifndef XFAFORM_H -#define XFAFORM_H - -#include - -#ifdef USE_GCC_PRAGMAS -#pragma interface -#endif - -#include "Form.h" - -class GHash; -class XFATableInfo; -class ZxDoc; -class ZxElement; -class ZxAttr; - -//------------------------------------------------------------------------ - -enum XFAHorizAlign { - xfaHAlignLeft, - xfaHAlignCenter, - xfaHAlignRight -}; - -enum XFAVertAlign { - xfaVAlignTop, - xfaVAlignBottom, - xfaVAlignMiddle -}; - -//------------------------------------------------------------------------ - -class XFAForm: public Form { -public: - - static XFAForm *load(PDFDoc *docA, Catalog *catalog, - Object *acroFormObj, Object *xfaObj); - - virtual ~XFAForm(); - - virtual const char *getType() { return "XFA"; } - - virtual void draw(int pageNum, Gfx *gfx, GBool printing); - - virtual int getNumFields(); - virtual FormField *getField(int idx); - -private: - - XFAForm(PDFDoc *docA, int nPagesA, ZxDoc *xmlA, - Object *resourceDictA, GBool fullXFAA); - void scanNode(ZxElement *elem, - GString *parentName, GString *parentFullName, - GBool inPageSet, XFATableInfo *tableInfo, - GHash *nameCount, GHash *nameIdx, - GHash *fullNameCount, GHash *fullNameIdx, - GString *exclGroupName, Catalog *catalog); - void scanNames(ZxElement *elem, GHash *nameCount); - void scanFullNames(ZxElement *elem, GHash *fullNameCount); - void scanField(ZxElement *elem, GString *name, GString *fullName, - GString *exclGroupName, GBool inPageSet, - XFATableInfo *tableInfo, int colSpan, - Catalog *catalog); - void scanNonField(ZxElement *elem, GString *name, GString *fullName, - GBool inPageSet, - XFATableInfo *tableInfo, int colSpan, - GHash *nameCount, GHash *nameIdx, - GHash *fullNameCount, GHash *fullNameIdx, - Catalog *catalog); - GString *getNodeName(ZxElement *elem); - GString *getNodeFullName(ZxElement *elem); - GBool nodeIsBindGlobal(ZxElement *elem); - - ZxDoc *xml; - GList *fields; // [XFAFormField] - Object resourceDict; - GBool fullXFA; // true for "Full XFA", false for - // "XFA Foreground" - int nPages; // number of pages in PDF file - double *pageOffsetX, // x,y offset for each page - *pageOffsetY; - int pageSetNPages; // number of pages in pageSet element - int curPageNum; // current page number - used by scanFields() - double curXOffset, // current x,y offset - used by scanFields() - curYOffset; - - friend class XFAFormField; -}; - -//------------------------------------------------------------------------ - -class XFAFormField: public FormField { -public: - - XFAFormField(XFAForm *xfaFormA, ZxElement *xmlA, - GString *nameA, GString *fullNameA, GString *exclGroupNameA, - int pageNumA, double xOffsetA, double yOffsetA, - double columnWidthA, double rowHeightA); - - virtual ~XFAFormField(); - - virtual int getPageNum(); - virtual const char *getType(); - virtual Unicode *getName(int *length); - virtual Unicode *getValue(int *length); - virtual void getBBox(double *llx, double *lly, double *urx, double *ury); - virtual void getFont(Ref *fontID, double *fontSize); - virtual void getColor(double *red, double *green, double *blue); - - virtual Object *getResources(Object *res); - -private: - - Unicode *utf8ToUnicode(GString *s, int *length); - void draw(int pageNumA, Gfx *gfx, GBool printing, GfxFontDict *fontDict); - void getRectangle(double *xfaX, double *xfaY, - double *xfaW, double *xfaH, - double *pdfX, double *pdfY, - double *pdfW, double *pdfH, - int *pdfRot); - void drawTextEdit(GfxFontDict *fontDict, - double w, double h, int rot, - GString *appearBuf); - void drawCheckButton(GfxFontDict *fontDict, - double w, double h, int rot, - GString *appearBuf); - void drawBarCode(GfxFontDict *fontDict, - double w, double h, int rot, - GString *appearBuf); - static double getMeasurement(ZxAttr *attr, double defaultVal); - static double getMeasurement(GString *s, int begin); - GString *getFieldValue(const char *valueChildType); - ZxElement *findFieldInDatasets(ZxElement *elem, char *partName); - ZxElement *findFieldInFormElem(ZxElement *elem, char *partName); - void transform(int rot, double w, double h, - double *wNew, double *hNew, GString *appearBuf); - void drawText(GString *text, GBool multiLine, int combCells, - GString *fontName, GBool bold, - GBool italic, double fontSize, - XFAHorizAlign hAlign, XFAVertAlign vAlign, - double x, double y, double w, double h, - GBool whiteBackground, - GfxFontDict *fontDict, GString *appearBuf); - GfxFont *findFont(GfxFontDict *fontDict, GString *fontName, - GBool bold, GBool italic); - Ref findFontName(GString *name, GBool bold, GBool italic); - void getNextLine(GString *text, int start, - GfxFont *font, double fontSize, double wMax, - int *end, double *width, int *next); - GString *pictureFormatDateTime(GString *value, GString *picture); - GString *pictureFormatNumber(GString *value, GString *picture); - GString *pictureFormatText(GString *value, GString *picture); - GBool isValidInt(GString *s, int start, int len); - int convertInt(GString *s, int start, int len); - - XFAForm *xfaForm; - ZxElement *xml; - GString *name; - GString *fullName; - GString *exclGroupName; - int pageNum; - double xOffset, yOffset; - double columnWidth; // column width, if this field is in a - // table; otherwise zero - double rowHeight; // row height, if this field is in a - // table; otherwise zero - - friend class XFAForm; - friend class XFATableInfo; -}; - -#endif diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.cc new file mode 100644 index 00000000000..6928fa9c9a8 --- /dev/null +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.cc @@ -0,0 +1,675 @@ +//======================================================================== +// +// XFAScanner.cc +// +// Copyright 2020 Glyph & Cog, LLC +// +//======================================================================== + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include "GString.h" +#include "GHash.h" +#include "Object.h" +#include "Error.h" +#include "Zoox.h" +#include "XFAScanner.h" + +//------------------------------------------------------------------------ + +// fields have two names: +// +// name: +// - nodes with bind=global set the index to 0 ("foo[0]") regardless +// of the number of nodes with the same name +// - nodes with bind=none are dropped from the name +// - nodes are dropped from the name +// - used for field value lookup in +// +// fullName: +// - all named nodes are treated the same, regardless of bind=global +// or bind=none +// - nodes are included in the name, but don't reset the +// numbering (i.e., nodes are "transparent" with respect to +// node numbering) +// - used for field value lookup in +// - used for matching with AcroForm names +// +// Both names use indexes on all nodes, even if there's only one node +// with the name -- this isn't correct for XFA naming, but it matches +// the AcroForm behavior. + +//------------------------------------------------------------------------ + +XFAFieldLayoutInfo::XFAFieldLayoutInfo(XFAFieldLayoutHAlign hAlignA, + XFAFieldLayoutVAlign vAlignA) { + hAlign = hAlignA; + vAlign = vAlignA; +} + +//------------------------------------------------------------------------ + +XFAFieldPictureInfo::XFAFieldPictureInfo(XFAFieldPictureSubtype subtypeA, + GString *formatA) { + subtype = subtypeA; + format = formatA; +} + +XFAFieldPictureInfo::~XFAFieldPictureInfo() { + delete format; +} + +//------------------------------------------------------------------------ + +XFAFieldBarcodeInfo::XFAFieldBarcodeInfo(GString *barcodeTypeA, + double wideNarrowRatioA, + double moduleWidthA, + double moduleHeightA, + int dataLengthA, + int errorCorrectionLevelA, + GString *textLocationA) { + barcodeType = barcodeTypeA; + wideNarrowRatio = wideNarrowRatioA; + moduleWidth = moduleWidthA; + moduleHeight = moduleHeightA; + dataLength = dataLengthA; + errorCorrectionLevel = errorCorrectionLevelA; + textLocation = textLocationA; +} + +XFAFieldBarcodeInfo::~XFAFieldBarcodeInfo() { + delete barcodeType; + delete textLocation; +} + +//------------------------------------------------------------------------ + +XFAField::XFAField(GString *nameA, GString *fullNameA, GString *valueA, + XFAFieldLayoutInfo *layoutInfoA, + XFAFieldPictureInfo *pictureInfoA, + XFAFieldBarcodeInfo *barcodeInfoA) + : name(nameA) + , fullName(fullNameA) + , value(valueA) + , layoutInfo(layoutInfoA) + , pictureInfo(pictureInfoA) + , barcodeInfo(barcodeInfoA) +{ +} + +XFAField::~XFAField() { + delete name; + delete fullName; + delete value; + delete layoutInfo; + delete pictureInfo; + delete barcodeInfo; +} + +//------------------------------------------------------------------------ + +XFAScanner *XFAScanner::load(Object *xfaObj) { + GString *xfaData = readXFAStreams(xfaObj); + if (!xfaData) { + return NULL; + } + ZxDoc *xml = ZxDoc::loadMem(xfaData->getCString(), xfaData->getLength()); + delete xfaData; + if (!xml) { + error(errSyntaxError, -1, "Invalid XML in XFA form"); + return NULL; + } + + XFAScanner *scanner = new XFAScanner(); + + if (xml->getRoot()) { + GHash *formValues = scanner->scanFormValues(xml->getRoot()); + ZxElement *dataElem = NULL; + ZxElement *datasets = + xml->getRoot()->findFirstChildElement("xfa:datasets"); + if (datasets) { + dataElem = datasets->findFirstChildElement("xfa:data"); + } + ZxElement *tmpl = xml->getRoot()->findFirstChildElement("template"); + if (tmpl) { + scanner->scanNode(tmpl, NULL, NULL, NULL, NULL, NULL, + dataElem, formValues); + } + deleteGHash(formValues, GString); + } + + delete xml; + + return scanner; +} + +XFAScanner::XFAScanner() { + fields = new GHash(); +} + +XFAScanner::~XFAScanner() { + deleteGHash(fields, XFAField); +} + +XFAField *XFAScanner::findField(GString *acroFormFieldName) { + return (XFAField *)fields->lookup(acroFormFieldName); +} + +GString *XFAScanner::readXFAStreams(Object *xfaObj) { + GString *data = new GString(); + char buf[4096]; + int n; + if (xfaObj->isStream()) { + xfaObj->streamReset(); + while ((n = xfaObj->getStream()->getBlock(buf, sizeof(buf))) > 0) { + data->append(buf, n); + } + } else if (xfaObj->isArray()) { + for (int i = 1; i < xfaObj->arrayGetLength(); i += 2) { + Object obj; + if (!xfaObj->arrayGet(i, &obj)->isStream()) { + error(errSyntaxError, -1, "XFA array element is wrong type"); + obj.free(); + delete data; + return NULL; + } + obj.streamReset(); + while ((n = obj.getStream()->getBlock(buf, sizeof(buf))) > 0) { + data->append(buf, n); + } + obj.free(); + } + } else { + error(errSyntaxError, -1, "XFA object is wrong type"); + return NULL; + } + return data; +} + +GHash *XFAScanner::scanFormValues(ZxElement *xmlRoot) { + GHash *formValues = new GHash(gTrue); + ZxElement *formElem = xmlRoot->findFirstChildElement("form"); + if (formElem) { + scanFormNode(formElem, NULL, formValues); + } + return formValues; +} + +void XFAScanner::scanFormNode(ZxElement *elem, GString *fullName, + GHash *formValues) { + GHash *fullNameIdx = new GHash(); + for (ZxNode *node = elem->getFirstChild(); + node; + node = node->getNextChild()) { + if (node->isElement("value")) { + if (fullName) { + ZxNode *child1Node = ((ZxElement *)node)->getFirstChild(); + if (child1Node && child1Node->isElement()) { + ZxNode *child2Node = ((ZxElement *)child1Node)->getFirstChild(); + if (child2Node && child2Node->isCharData()) { + formValues->add(fullName->copy(), + ((ZxCharData *)child2Node)->getData()->copy()); + } + } + } + } else if (node->isElement()) { + ZxAttr *nameAttr = ((ZxElement *)node)->findAttr("name"); + if (nameAttr && (node->isElement("subform") || + node->isElement("field"))) { + GString *nodeName = nameAttr->getValue(); + GString *childFullName; + if (fullName) { + childFullName = GString::format("{0:t}.{1:t}", fullName, nodeName); + } else { + childFullName = nodeName->copy(); + } + int idx = fullNameIdx->lookupInt(nodeName); + childFullName->appendf("[{0:d}]", idx); + fullNameIdx->replace(nodeName, idx + 1); + scanFormNode((ZxElement *)node, childFullName, formValues); + delete childFullName; + } else if (node->isElement("subform")) { + scanFormNode((ZxElement *)node, fullName, formValues); + } + } + } + delete fullNameIdx; +} + +void XFAScanner::scanNode(ZxElement *elem, + GString *parentName, GString *parentFullName, + GHash *nameIdx, GHash *fullNameIdx, + GString *exclGroupName, ZxElement *dataElem, + GHash *formValues) { + GString *nodeName = getNodeName(elem); + GHash *childNameIdx; + if (!nameIdx || nodeName) { + childNameIdx = new GHash(); + } else { + childNameIdx = nameIdx; + } + GString *nodeFullName = getNodeFullName(elem); + GHash *childFullNameIdx; + if (!fullNameIdx || (nodeFullName && !elem->isElement("area"))) { + childFullNameIdx = new GHash(); + } else { + childFullNameIdx = fullNameIdx; + } + + GString *childName; + if (nodeName) { + if (parentName) { + childName = GString::format("{0:t}.{1:t}", parentName, nodeName); + } else { + childName = nodeName->copy(); + } + int idx = nameIdx->lookupInt(nodeName); + nameIdx->replace(nodeName, idx + 1); + if (nodeIsBindGlobal(elem)) { + childName->appendf("[0]"); + } else { + childName->appendf("[{0:d}]", idx); + } + } else { + childName = parentName; + } + GString *childFullName; + if (nodeFullName) { + if (parentFullName) { + childFullName = GString::format("{0:t}.{1:t}", + parentFullName, nodeFullName); + } else { + childFullName = nodeFullName->copy(); + } + int idx = fullNameIdx->lookupInt(nodeFullName); + fullNameIdx->replace(nodeFullName, idx + 1); + childFullName->appendf("[{0:d}]", idx); + } else { + childFullName = parentFullName; + } + + if (elem->isElement("field")) { + scanField(elem, childName, childFullName, exclGroupName, + dataElem, formValues); + } else { + GString *childExclGroupName; + if (elem->isElement("exclGroup")) { + childExclGroupName = childName; + } else { + childExclGroupName = NULL; + } + for (ZxNode *child = elem->getFirstChild(); + child; + child = child->getNextChild()) { + if (child->isElement()) { + scanNode((ZxElement *)child, childName, childFullName, + childNameIdx, childFullNameIdx, childExclGroupName, + dataElem, formValues); + } + } + } + + if (childName != parentName) { + delete childName; + } + if (childFullName != parentFullName) { + delete childFullName; + } + if (childNameIdx != nameIdx) { + delete childNameIdx; + } + if (childFullNameIdx != fullNameIdx) { + delete childFullNameIdx; + } +} + +void XFAScanner::scanField(ZxElement *elem, GString *name, GString *fullName, + GString *exclGroupName, ZxElement *dataElem, + GHash *formValues) { + GString *value = getFieldValue(elem, name, fullName, exclGroupName, + dataElem, formValues); + XFAFieldLayoutInfo *layoutInfo = getFieldLayoutInfo(elem); + XFAFieldPictureInfo *pictureInfo = getFieldPictureInfo(elem); + XFAFieldBarcodeInfo *barcodeInfo = getFieldBarcodeInfo(elem); + XFAField *field = new XFAField(name->copy(), fullName->copy(), value, + layoutInfo, pictureInfo, barcodeInfo); + fields->add(field->fullName, field); +} + +GString *XFAScanner::getFieldValue(ZxElement *elem, GString *name, + GString *fullName, GString *exclGroupName, + ZxElement *dataElem, GHash *formValues) { + GString *val = NULL; + + //--- check the packet + val = getDatasetsValue(name->getCString(), dataElem); + if (!val && exclGroupName) { + val = (GString *)getDatasetsValue(exclGroupName->getCString(), dataElem); + } + + //--- check the element + if (!val) { + val = (GString *)formValues->lookup(fullName); + } + + //--- check the element within the field + if (!val) { + ZxElement *valueElem = elem->findFirstChildElement("value"); + if (valueElem) { + ZxNode *child1Node = valueElem->getFirstChild(); + if (child1Node && child1Node->isElement()) { + ZxNode *child2Node = ((ZxElement *)child1Node)->getFirstChild(); + if (child2Node && child2Node->isCharData()) { + val = ((ZxCharData *)child2Node)->getData(); + } + } + } + } + + //--- get the checkbutton item value + GString *checkbuttonItem = NULL; + ZxElement *uiElem = elem->findFirstChildElement("ui"); + if (uiElem) { + ZxNode *uiChild = uiElem->getFirstChild(); + if (uiChild && uiChild->isElement("checkButton")) { + ZxElement *itemsElem = elem->findFirstChildElement("items"); + if (itemsElem) { + ZxNode *node1 = itemsElem->getFirstChild(); + if (node1 && node1->isElement()) { + ZxNode *node2 = ((ZxElement *)node1)->getFirstChild(); + if (node2 && node2->isCharData()) { + checkbuttonItem = ((ZxCharData *)node2)->getData(); + } + } + } + } + } + // convert XFA checkbutton value to AcroForm-style On/Off value + if (checkbuttonItem && val) { + if (val->cmp(checkbuttonItem)) { + val = new GString("Off"); + } else { + val = new GString("On"); + } + } else if (val) { + val = val->copy(); + } + + return val; +} + +GString *XFAScanner::getDatasetsValue(char *partName, ZxElement *elem) { + if (!elem) { + return NULL; + } + + // partName = xxxx[nn].yyyy---- + char *p = strchr(partName, '['); + if (!p) { + return NULL; + } + int partLen = (int)(p - partName); + int idx = atoi(p + 1); + p = strchr(p + 1, '.'); + if (p) { + ++p; + } + + int curIdx = 0; + for (ZxNode *node = elem->getFirstChild(); + node; + node = node->getNextChild()) { + if (!node->isElement()) { + continue; + } + GString *nodeName = ((ZxElement *)node)->getType(); + if (nodeName->getLength() != partLen || + strncmp(nodeName->getCString(), partName, partLen)) { + continue; + } + if (curIdx != idx) { + ++curIdx; + continue; + } + if (p) { + GString *val = getDatasetsValue(p, (ZxElement *)node); + if (val) { + return val; + } + break; + } else { + ZxNode *child = ((ZxElement *)node)->getFirstChild(); + if (!child || !child->isCharData()) { + return NULL; + } + return ((ZxCharData *)child)->getData(); + } + } + + // search for an 'ancestor match' + if (p) { + return getDatasetsValue(p, elem); + } + + return NULL; +} + +XFAFieldLayoutInfo *XFAScanner::getFieldLayoutInfo(ZxElement *elem) { + ZxElement *paraElem = elem->findFirstChildElement("para"); + if (!paraElem) { + return NULL; + } + XFAFieldLayoutHAlign hAlign = xfaFieldLayoutHAlignLeft; + ZxAttr *hAlignAttr = paraElem->findAttr("hAlign"); + if (hAlignAttr) { + if (!hAlignAttr->getValue()->cmp("left")) { + hAlign = xfaFieldLayoutHAlignLeft; + } else if (!hAlignAttr->getValue()->cmp("center")) { + hAlign = xfaFieldLayoutHAlignCenter; + } else if (!hAlignAttr->getValue()->cmp("right")) { + hAlign = xfaFieldLayoutHAlignRight; + } + } + XFAFieldLayoutVAlign vAlign = xfaFieldLayoutVAlignTop; + ZxAttr *vAlignAttr = paraElem->findAttr("vAlign"); + if (vAlignAttr) { + if (!vAlignAttr->getValue()->cmp("top")) { + vAlign = xfaFieldLayoutVAlignTop; + } else if (!vAlignAttr->getValue()->cmp("middle")) { + vAlign = xfaFieldLayoutVAlignMiddle; + } else if (!vAlignAttr->getValue()->cmp("bottom")) { + vAlign = xfaFieldLayoutVAlignBottom; + } + } + return new XFAFieldLayoutInfo(hAlign, vAlign); +} + +XFAFieldPictureInfo *XFAScanner::getFieldPictureInfo(ZxElement *elem) { + ZxElement *uiElem = elem->findFirstChildElement("ui"); + if (!uiElem) { + return NULL; + } + XFAFieldPictureSubtype subtype; + if (uiElem->findFirstChildElement("dateTimeEdit")) { + subtype = xfaFieldPictureDateTime; + } else if (uiElem->findFirstChildElement("numericEdit")) { + subtype = xfaFieldPictureNumeric; + } else if (uiElem->findFirstChildElement("textEdit")) { + subtype = xfaFieldPictureText; + } else { + return NULL; + } + + ZxElement *formatElem, *pictureElem; + ZxNode *pictureChildNode; + if (!(formatElem = elem->findFirstChildElement("format")) || + !(pictureElem = formatElem->findFirstChildElement("picture")) || + !(pictureChildNode = pictureElem->getFirstChild()) || + !pictureChildNode->isCharData()) { + return NULL; + } + GString *format = ((ZxCharData *)pictureChildNode)->getData()->copy(); + + return new XFAFieldPictureInfo(subtype, format); +} + +XFAFieldBarcodeInfo *XFAScanner::getFieldBarcodeInfo(ZxElement *elem) { + ZxElement *uiElem, *barcodeElem; + if (!(uiElem = elem->findFirstChildElement("ui")) || + !(barcodeElem = uiElem->findFirstChildElement("barcode"))) { + return NULL; + } + + ZxAttr *attr; + if (!(attr = barcodeElem->findAttr("type"))) { + return NULL; + } + GString *barcodeType = attr->getValue()->copy(); + + double wideNarrowRatio = 3; + if ((attr = barcodeElem->findAttr("wideNarrowRatio"))) { + char *s = attr->getValue()->getCString(); + char *colon = strchr(s, ':'); + if (colon) { + GString *numStr = new GString(s, (int)(colon - s)); + double num = atof(numStr->getCString()); + delete numStr; + double den = atof(colon + 1); + if (den == 0) { + wideNarrowRatio = num; + } else { + wideNarrowRatio = num / den; + } + } else { + wideNarrowRatio = atof(s); + } + } + + double moduleWidth = (0.25 / 25.4) * 72.0; // 0.25mm + if ((attr = barcodeElem->findAttr("moduleWidth"))) { + moduleWidth = getMeasurement(attr->getValue()); + } + + double moduleHeight = (5.0 / 25.4) * 72.0; // 5mm + if ((attr = barcodeElem->findAttr("moduleHeight"))) { + moduleHeight = getMeasurement(attr->getValue()); + } + + int dataLength = 0; + if ((attr = barcodeElem->findAttr("dataLength"))) { + dataLength = atoi(attr->getValue()->getCString()); + } + + int errorCorrectionLevel = 0; + if ((attr = barcodeElem->findAttr("errorCorrectionLevel"))) { + errorCorrectionLevel = atoi(attr->getValue()->getCString()); + } + + GString *textLocation; + if ((attr = barcodeElem->findAttr("textLocation"))) { + textLocation = attr->getValue()->copy(); + } else { + textLocation = new GString("below"); + } + + return new XFAFieldBarcodeInfo(barcodeType, wideNarrowRatio, + moduleWidth, moduleHeight, dataLength, + errorCorrectionLevel, textLocation); +} + +double XFAScanner::getMeasurement(GString *s) { + int i = 0; + GBool neg = gFalse; + if (i < s->getLength() && s->getChar(i) == '+') { + ++i; + } else if (i < s->getLength() && s->getChar(i) == '-') { + neg = gTrue; + ++i; + } + double val = 0; + while (i < s->getLength() && s->getChar(i) >= '0' && s->getChar(i) <= '9') { + val = val * 10 + s->getChar(i) - '0'; + ++i; + } + if (i < s->getLength() && s->getChar(i) == '.') { + ++i; + double mul = 0.1; + while (i < s->getLength() && + s->getChar(i) >= '0' && s->getChar(i) <= '9') { + val += mul * (s->getChar(i) - '0'); + mul *= 0.1; + ++i; + } + } + if (neg) { + val = -val; + } + if (i+1 < s->getLength()) { + if (s->getChar(i) == 'i' && s->getChar(i+1) == 'n') { + val *= 72; + } else if (s->getChar(i) == 'p' && s->getChar(i+1) == 't') { + // no change + } else if (s->getChar(i) == 'c' && s->getChar(i+1) == 'm') { + val *= 72 / 2.54; + } else if (s->getChar(i) == 'm' && s->getChar(i+1) == 'm') { + val *= 72 / 25.4; + } else { + // default to inches + val *= 72; + } + } else { + // default to inches + val *= 72; + } + return val; +} + +GString *XFAScanner::getNodeName(ZxElement *elem) { + if (elem->isElement("template") || + elem->isElement("area") || + elem->isElement("draw")) { + return NULL; + } + if (!elem->isElement("field") && nodeIsBindNone(elem)) { + return NULL; + } + ZxAttr *nameAttr = elem->findAttr("name"); + if (!nameAttr) { + return NULL; + } + return nameAttr->getValue(); +} + +GString *XFAScanner::getNodeFullName(ZxElement *elem) { + if (elem->isElement("template") || + elem->isElement("draw")) { + return NULL; + } + ZxAttr *nameAttr = elem->findAttr("name"); + if (!nameAttr) { + return NULL; + } + return nameAttr->getValue(); +} + +GBool XFAScanner::nodeIsBindGlobal(ZxElement *elem) { + ZxElement *bindElem = elem->findFirstChildElement("bind"); + if (!bindElem) { + return gFalse; + } + ZxAttr *attr = bindElem->findAttr("match"); + return attr && !attr->getValue()->cmp("global"); +} + +GBool XFAScanner::nodeIsBindNone(ZxElement *elem) { + ZxElement *bindElem = elem->findFirstChildElement("bind"); + if (!bindElem) { + return gFalse; + } + ZxAttr *attr = bindElem->findAttr("match"); + return attr && !attr->getValue()->cmp("none"); +} diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.h b/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.h new file mode 100644 index 00000000000..00ee6f7fcee --- /dev/null +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.h @@ -0,0 +1,166 @@ +//======================================================================== +// +// XFAScanner.h +// +// Copyright 2020 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef XFASCANNER_H +#define XFASCANNER_H + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +class GHash; +class ZxElement; + +//------------------------------------------------------------------------ + +enum XFAFieldLayoutHAlign { + xfaFieldLayoutHAlignLeft, + xfaFieldLayoutHAlignCenter, + xfaFieldLayoutHAlignRight +}; + +enum XFAFieldLayoutVAlign { + xfaFieldLayoutVAlignTop, + xfaFieldLayoutVAlignMiddle, + xfaFieldLayoutVAlignBottom +}; + +class XFAFieldLayoutInfo { +public: + + XFAFieldLayoutInfo(XFAFieldLayoutHAlign hAlignA, + XFAFieldLayoutVAlign vAlignA); + + XFAFieldLayoutHAlign hAlign; + XFAFieldLayoutVAlign vAlign; +}; + +//------------------------------------------------------------------------ + +enum XFAFieldPictureSubtype { + xfaFieldPictureDateTime, + xfaFieldPictureNumeric, + xfaFieldPictureText +}; + +class XFAFieldPictureInfo { +public: + + XFAFieldPictureInfo(XFAFieldPictureSubtype subtypeA, GString *formatA); + ~XFAFieldPictureInfo(); + + XFAFieldPictureSubtype subtype; + GString *format; // picture format string +}; + +//------------------------------------------------------------------------ + +class XFAFieldBarcodeInfo { +public: + + XFAFieldBarcodeInfo(GString *barcodeTypeA, double wideNarrowRatioA, + double moduleWidthA, double moduleHeightA, + int dataLengthA, int errorCorrectionLevelA, + GString *textLocationA); + ~XFAFieldBarcodeInfo(); + + GString *barcodeType; + double wideNarrowRatio; + double moduleWidth; + double moduleHeight; + int dataLength; + int errorCorrectionLevel; + GString *textLocation; +}; + +//------------------------------------------------------------------------ + +class XFAField { +public: + + XFAField(GString *nameA, GString *fullNameA, GString *valueA, + XFAFieldLayoutInfo *layoutInfoA, + XFAFieldPictureInfo *pictureInfoA, + XFAFieldBarcodeInfo *barcodeInfoA); + ~XFAField(); + + // Get the field's value, or NULL if it doesn't have a value. Sets + // *[length] to the length of the Unicode string. + GString *getValue() { return value; } + + // Return a pointer to the field's picture formatting info object, + // or NULL if the field doesn't have picture formatting. + XFAFieldPictureInfo *getPictureInfo() { return pictureInfo; } + + // Return a pointer to the field's layout info object, or NULL if + // the field doesn't have layout info. + XFAFieldLayoutInfo *getLayoutInfo() { return layoutInfo; } + + // Return a pointer to the field's barcode info object, or NULL if + // the field isn't a barcode. + XFAFieldBarcodeInfo *getBarcodeInfo() { return barcodeInfo; } + +private: + + friend class XFAScanner; + + GString *name; // UTF-8 + GString *fullName; // UTF-8 + GString *value; // UTF-8 + XFAFieldLayoutInfo *layoutInfo; + XFAFieldPictureInfo *pictureInfo; + XFAFieldBarcodeInfo *barcodeInfo; +}; + +//------------------------------------------------------------------------ + +class XFAScanner { +public: + + static XFAScanner *load(Object *xfaObj); + + virtual ~XFAScanner(); + + // Find an XFA field matchined the specified AcroForm field name. + // Returns NULL if there is no matching field. + XFAField *findField(GString *acroFormFieldName); + +private: + + XFAScanner(); + static GString *readXFAStreams(Object *xfaObj); + GHash *scanFormValues(ZxElement *xmlRoot); + void scanFormNode(ZxElement *elem, GString *fullName, + GHash *formValues); + void scanNode(ZxElement *elem, + GString *parentName, GString *parentFullName, + GHash *nameIdx, GHash *fullNameIdx, + GString *exclGroupName, ZxElement *xmlRoot, + GHash *formValues); + void scanField(ZxElement *elem, GString *name, GString *fullName, + GString *exclGroupName, ZxElement *xmlRoot, + GHash *formValues); + GString *getFieldValue(ZxElement *elem, GString *name, + GString *fullName, GString *exclGroupName, + ZxElement *xmlRoot, GHash *formValues); + GString *getDatasetsValue(char *partName, ZxElement *elem); + XFAFieldLayoutInfo *getFieldLayoutInfo(ZxElement *elem); + XFAFieldPictureInfo *getFieldPictureInfo(ZxElement *elem); + XFAFieldBarcodeInfo *getFieldBarcodeInfo(ZxElement *elem); + double getMeasurement(GString *s); + GString *getNodeName(ZxElement *elem); + GString *getNodeFullName(ZxElement *elem); + GBool nodeIsBindGlobal(ZxElement *elem); + GBool nodeIsBindNone(ZxElement *elem); + + GHash *fields; // [XFAField] +}; + +#endif diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.cc index d6b43cd3bea..0c364638b39 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.cc @@ -350,7 +350,7 @@ XRef::XRef(BaseStream *strA, GBool repair) { // read the xref table posSet = new XRefPosSet(); - while (readXRef(&pos, posSet)) ; + while (readXRef(&pos, posSet, gFalse)) ; xrefTablePosLen = posSet->getLength(); xrefTablePos = (GFileOffset *)gmallocn(xrefTablePosLen, sizeof(GFileOffset)); @@ -438,8 +438,10 @@ GFileOffset XRef::getStartXref() { } // Read one xref table section. Also reads the associated trailer -// dictionary, and returns the prev pointer (if any). -GBool XRef::readXRef(GFileOffset *pos, XRefPosSet *posSet) { +// dictionary, and returns the prev pointer (if any). The [hybrid] +// flag is true when following the XRefStm link in a hybrid-reference +// file. +GBool XRef::readXRef(GFileOffset *pos, XRefPosSet *posSet, GBool hybrid) { Parser *parser; Object obj; GBool more; @@ -461,47 +463,44 @@ GBool XRef::readXRef(GFileOffset *pos, XRefPosSet *posSet) { for (i = 0; i < n && Lexer::isSpace(buf[i]); ++i) ; // parse an old-style xref table - if (i + 4 < n && + if (!hybrid && + i + 4 < n && buf[i] == 'x' && buf[i+1] == 'r' && buf[i+2] == 'e' && buf[i+3] == 'f' && Lexer::isSpace(buf[i+4])) { more = readXRefTable(pos, i + 5, posSet); // parse an xref stream - } else if (i < n && buf[i] >= '0' && buf[i] <= '9') { + } else { obj.initNull(); parser = new Parser(NULL, new Lexer(NULL, str->makeSubStream(start + *pos, gFalse, 0, &obj)), gTrue); if (!parser->getObj(&obj, gTrue)->isInt()) { - goto err2; + goto err; } obj.free(); if (!parser->getObj(&obj, gTrue)->isInt()) { - goto err2; + goto err; } obj.free(); if (!parser->getObj(&obj, gTrue)->isCmd("obj")) { - goto err2; + goto err; } obj.free(); if (!parser->getObj(&obj)->isStream()) { - goto err2; + goto err; } - more = readXRefStream(obj.getStream(), pos); + more = readXRefStream(obj.getStream(), pos, hybrid); obj.free(); delete parser; - - } else { - goto err1; } return more; - err2: + err: obj.free(); delete parser; - err1: ok = gFalse; return gFalse; } @@ -662,7 +661,7 @@ GBool XRef::readXRefTable(GFileOffset *pos, int offset, XRefPosSet *posSet) { //~ this can be a 64-bit int (?) if (obj.getDict()->lookup("XRefStm", &obj2)->isInt()) { pos2 = (GFileOffset)(Guint)obj2.getInt(); - readXRef(&pos2, posSet); + readXRef(&pos2, posSet, gTrue); if (!ok) { obj2.free(); obj.free(); @@ -679,7 +678,7 @@ GBool XRef::readXRefTable(GFileOffset *pos, int offset, XRefPosSet *posSet) { return gFalse; } -GBool XRef::readXRefStream(Stream *xrefStr, GFileOffset *pos) { +GBool XRef::readXRefStream(Stream *xrefStr, GFileOffset *pos, GBool hybrid) { Dict *dict; int w[3]; GBool more; @@ -857,125 +856,250 @@ GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) { // Attempt to construct an xref table for a damaged file. GBool XRef::constructXRef() { - Parser *parser; - Object newTrailerDict, obj; - char buf[256]; - GFileOffset pos; - int num, gen; - int newSize; - int streamEndsSize; - char *p; - int i; - GBool gotRoot; - - gfree(entries); - size = 0; - entries = NULL; - - gotRoot = gFalse; - streamEndsLen = streamEndsSize = 0; - + int *streamObjNums = NULL; + int streamObjNumsLen = 0; + int streamObjNumsSize = 0; + int lastObjNum = -1; + rootNum = -1; + int streamEndsSize = 0; + streamEndsLen = 0; + char buf[4096 + 1]; str->reset(); + GFileOffset bufPos = start; + char *p = buf; + char *end = buf; + GBool startOfLine = gTrue; + GBool eof = gFalse; while (1) { - pos = str->getPos(); - if (!str->getLine(buf, 256)) { + if (end - p < 256 && !eof) { + memcpy(buf, p, end - p); + bufPos += p - buf; + p = buf + (end - p); + int n = (int)(buf + 4096 - p); + int m = str->getBlock(p, n); + end = p + m; + *end = '\0'; + p = buf; + eof = m < n; + } + if (p == end && eof) { break; } - p = buf; + if (startOfLine && !strncmp(p, "trailer", 7)) { + constructTrailerDict((GFileOffset)(bufPos + (p + 7 - buf))); + p += 7; + startOfLine = gFalse; + } else if (startOfLine && !strncmp(p, "endstream", 9)) { + if (streamEndsLen == streamEndsSize) { + streamEndsSize += 64; + streamEnds = (GFileOffset *)greallocn(streamEnds, streamEndsSize, + sizeof(GFileOffset)); + } + streamEnds[streamEndsLen++] = (GFileOffset)(bufPos + (p - buf)); + p += 9; + startOfLine = gFalse; + } else if (startOfLine && *p >= '0' && *p <= '9') { + p = constructObjectEntry(p, (GFileOffset)(bufPos + (p - buf)), + &lastObjNum); + startOfLine = gFalse; + } else if (p[0] == '>' && p[1] == '>') { + p += 2; + startOfLine = gFalse; + // skip any PDF whitespace except for '\0' + while (*p == '\t' || *p == '\n' || *p == '\x0c' || + *p == '\r' || *p == ' ') { + startOfLine = *p == '\n' || *p == '\r'; + ++p; + } + if (!strncmp(p, "stream", 6)) { + if (lastObjNum >= 0) { + if (streamObjNumsLen == streamObjNumsSize) { + streamObjNumsSize += 64; + streamObjNums = (int *)greallocn(streamObjNums, streamObjNumsSize, + sizeof(int)); + } + streamObjNums[streamObjNumsLen++] = lastObjNum; + } + p += 6; + startOfLine = gFalse; + } + } else { + startOfLine = *p == '\n' || *p == '\r'; + ++p; + } + } - // skip whitespace - while (*p && Lexer::isSpace(*p & 0xff)) ++p; + // read each stream object, check for xref or object stream + for (int i = 0; i < streamObjNumsLen; ++i) { + Object obj; + fetch(streamObjNums[i], entries[streamObjNums[i]].gen, &obj); + if (obj.isStream()) { + Dict *dict = obj.streamGetDict(); + Object type; + dict->lookup("Type", &type); + if (type.isName("XRef")) { + saveTrailerDict(dict, gTrue); + } else if (type.isName("ObjStm")) { + constructObjectStreamEntries(&obj, streamObjNums[i]); + } + type.free(); + } + obj.free(); + } + + gfree(streamObjNums); - // got trailer dictionary - if (!strncmp(p, "trailer", 7)) { - obj.initNull(); - parser = new Parser(NULL, + if (rootNum < 0) { + error(errSyntaxError, -1, "Couldn't find trailer dictionary"); + return gFalse; + } + return gTrue; +} + +// Attempt to construct a trailer dict at [pos] in the stream. +void XRef::constructTrailerDict(GFileOffset pos) { + Object newTrailerDict, obj; + obj.initNull(); + Parser *parser = + new Parser(NULL, new Lexer(NULL, - str->makeSubStream(pos + 7, gFalse, 0, &obj)), + str->makeSubStream(pos, gFalse, 0, &obj)), gFalse); - parser->getObj(&newTrailerDict); - if (newTrailerDict.isDict()) { - newTrailerDict.dictLookupNF("Root", &obj); - if (obj.isRef()) { - rootNum = obj.getRefNum(); - rootGen = obj.getRefGen(); - if (!trailerDict.isNone()) { - trailerDict.free(); - } - newTrailerDict.copy(&trailerDict); - gotRoot = gTrue; - } - obj.free(); - } - newTrailerDict.free(); - delete parser; + parser->getObj(&newTrailerDict); + if (newTrailerDict.isDict()) { + saveTrailerDict(newTrailerDict.getDict(), gFalse); + } + newTrailerDict.free(); + delete parser; +} - // look for object - } else if (isdigit(*p & 0xff)) { - num = atoi(p); - if (num > 0) { - do { - ++p; - } while (*p && isdigit(*p & 0xff)); - if (isspace(*p & 0xff)) { - do { - ++p; - } while (*p && isspace(*p & 0xff)); - if (isdigit(*p & 0xff)) { - gen = atoi(p); - do { - ++p; - } while (*p && isdigit(*p & 0xff)); - if (isspace(*p & 0xff)) { - do { - ++p; - } while (*p && isspace(*p & 0xff)); - if (!strncmp(p, "obj", 3)) { - if (num >= size) { - newSize = (num + 1 + 255) & ~255; - if (newSize < 0) { - error(errSyntaxError, -1, "Bad object number"); - return gFalse; - } - entries = (XRefEntry *) - greallocn(entries, newSize, sizeof(XRefEntry)); - for (i = size; i < newSize; ++i) { - entries[i].offset = (GFileOffset)-1; - entries[i].type = xrefEntryFree; - } - size = newSize; - } - if (entries[num].type == xrefEntryFree || - gen >= entries[num].gen) { - entries[num].offset = pos - start; - entries[num].gen = gen; - entries[num].type = xrefEntryUncompressed; - if (num > last) { - last = num; - } - } - } - } - } - } +// If [dict] "looks like" a trailer dict (i.e., has a Root entry), +// save it as the trailer dict. +void XRef::saveTrailerDict(Dict *dict, GBool isXRefStream) { + Object obj; + dict->lookupNF("Root", &obj); + if (obj.isRef()) { + int newRootNum = obj.getRefNum(); + // the xref stream scanning code runs after all objects are found, + // so we can check for a valid root object number at that point + if (!isXRefStream || newRootNum <= last) { + rootNum = newRootNum; + rootGen = obj.getRefGen(); + if (!trailerDict.isNone()) { + trailerDict.free(); } + trailerDict.initDict(dict); + } + } + obj.free(); +} - } else if (!strncmp(p, "endstream", 9)) { - if (streamEndsLen == streamEndsSize) { - streamEndsSize += 64; - streamEnds = (GFileOffset *)greallocn(streamEnds, streamEndsSize, - sizeof(GFileOffset)); +// Look for an object header ("nnn ggg obj") at [p]. The first +// character at *[p] is a digit. [pos] is the position of *[p]. +char *XRef::constructObjectEntry(char *p, GFileOffset pos, int *objNum) { + // we look for non-end-of-line space characters here, to deal with + // situations like: + // nnn <-- garbage digits on a line + // nnn nnn obj <-- actual object + // and we also ignore '\0' (because it's used to terminate the + // buffer in this damage-scanning code) + int num = 0; + do { + num = (num * 10) + (*p - '0'); + ++p; + } while (*p >= '0' && *p <= '9' && num < 100000000); + if (*p != '\t' && *p != '\x0c' && *p != ' ') { + return p; + } + do { + ++p; + } while (*p == '\t' || *p == '\x0c' || *p == ' '); + if (!(*p >= '0' && *p <= '9')) { + return p; + } + int gen = 0; + do { + gen = (gen * 10) + (*p - '0'); + ++p; + } while (*p >= '0' && *p <= '9' && gen < 100000000); + if (*p != '\t' && *p != '\x0c' && *p != ' ') { + return p; + } + do { + ++p; + } while (*p == '\t' || *p == '\x0c' || *p == ' '); + if (strncmp(p, "obj", 3)) { + return p; + } + + if (constructXRefEntry(num, gen, pos - start, xrefEntryUncompressed)) { + *objNum = num; + } + + return p; +} + +// Read the header from an object stream, and add xref entries for all +// of its objects. +void XRef::constructObjectStreamEntries(Object *objStr, int objStrObjNum) { + Object obj1, obj2; + + // get the object count + if (!objStr->streamGetDict()->lookup("N", &obj1)->isInt()) { + obj1.free(); + return; + } + int nObjects = obj1.getInt(); + obj1.free(); + if (nObjects <= 0 || nObjects > 1000000) { + return; + } + + // parse the header: object numbers and offsets + Parser *parser = new Parser(NULL, + new Lexer(NULL, objStr->getStream()->copy()), + gFalse); + for (int i = 0; i < nObjects; ++i) { + parser->getObj(&obj1, gTrue); + parser->getObj(&obj2, gTrue); + if (obj1.isInt() && obj2.isInt()) { + int num = obj1.getInt(); + if (num >= 0 && num < 1000000) { + constructXRefEntry(num, i, objStrObjNum, xrefEntryCompressed); } - streamEnds[streamEndsLen++] = pos; } + obj2.free(); + obj1.free(); } + delete parser; +} - if (gotRoot) { - return gTrue; +GBool XRef::constructXRefEntry(int num, int gen, GFileOffset pos, + XRefEntryType type) { + if (num >= size) { + int newSize = (num + 1 + 255) & ~255; + if (newSize < 0) { + return gFalse; + } + entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); + for (int i = size; i < newSize; ++i) { + entries[i].offset = (GFileOffset)-1; + entries[i].type = xrefEntryFree; + } + size = newSize; } - error(errSyntaxError, -1, "Couldn't find trailer dictionary"); - return gFalse; + if (entries[num].type == xrefEntryFree || + gen >= entries[num].gen) { + entries[num].offset = pos; + entries[num].gen = gen; + entries[num].type = type; + if (num > last) { + last = num; + } + } + + return gTrue; } void XRef::setEncryption(int permFlagsA, GBool ownerPasswordOkA, @@ -1153,6 +1277,9 @@ GBool XRef::getObjectStreamObject(int objStrNum, int objIdx, gLockMutex(&objStrsMutex); #endif if (!(objStr = getObjectStream(objStrNum))) { +#if MULTITHREADED + gUnlockMutex(&objStrsMutex); +#endif return gFalse; } cleanObjectStreamCache(); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.h b/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.h index 9e63dcf04da..00a8644f3a9 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.h @@ -166,11 +166,17 @@ private: #endif GFileOffset getStartXref(); - GBool readXRef(GFileOffset *pos, XRefPosSet *posSet); + GBool readXRef(GFileOffset *pos, XRefPosSet *posSet, GBool hybrid); GBool readXRefTable(GFileOffset *pos, int offset, XRefPosSet *posSet); + GBool readXRefStream(Stream *xrefStr, GFileOffset *pos, GBool hybrid); GBool readXRefStreamSection(Stream *xrefStr, int *w, int first, int n); - GBool readXRefStream(Stream *xrefStr, GFileOffset *pos); GBool constructXRef(); + void constructTrailerDict(GFileOffset pos); + void saveTrailerDict(Dict *dict, GBool isXRefStream); + char *constructObjectEntry(char *p, GFileOffset pos, int *objNum); + void constructObjectStreamEntries(Object *objStr, int objStrObjNum); + GBool constructXRefEntry(int num, int gen, GFileOffset pos, + XRefEntryType type); GBool getObjectStreamObject(int objStrNum, int objIdx, int objNum, Object *obj); ObjectStream *getObjectStream(int objStrNum); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.cc index ba6e11496ae..9e4fb47900c 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.cc @@ -430,7 +430,8 @@ ZxAttr *ZxDoc::parseAttr() { GString *name, *value; const char *start; char quote, c; - int x, n; + unsigned int x; + int n; name = parseName(); parseSpace(); @@ -559,7 +560,8 @@ void ZxDoc::parseCharData(ZxElement *par) { GString *data; const char *start; char c; - int x, n; + unsigned int x; + int n; data = new GString(); while (parsePtr < parseEnd && *parsePtr != '<') { @@ -638,7 +640,7 @@ void ZxDoc::parseCharData(ZxElement *par) { par->addChild(new ZxCharData(data, true)); } -void ZxDoc::appendUTF8(GString *s, int c) { +void ZxDoc::appendUTF8(GString *s, unsigned int c) { if (c <= 0x7f) { s->append((char)c); } else if (c <= 0x7ff) { diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.h b/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.h index 49f9f6b1da8..c04ca95064b 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.h @@ -103,7 +103,7 @@ private: ZxAttr *parseAttr(); void parseContent(ZxElement *par); void parseCharData(ZxElement *par); - void appendUTF8(GString *s, int c); + void appendUTF8(GString *s, unsigned int c); void parseCDSect(ZxNode *par); void parseMisc(ZxNode *par); void parseComment(ZxNode *par); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/config.h b/Build/source/libs/xpdf/xpdf-src/xpdf/config.h index 3dbed2ae93c..39cade1682c 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/config.h +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/config.h @@ -2,7 +2,7 @@ // // config.h // -// Copyright 1996-2019 Glyph & Cog, LLC +// Copyright 1996-2021 Glyph & Cog, LLC // //======================================================================== @@ -14,13 +14,13 @@ //------------------------------------------------------------------------ // xpdf version -#define xpdfVersion "4.02" -#define xpdfVersionNum 4.02 +#define xpdfVersion "4.03" +#define xpdfVersionNum 4.03 #define xpdfMajorVersion 4 -#define xpdfMinorVersion 2 +#define xpdfMinorVersion 3 #define xpdfUpdateVersion 0 #define xpdfMajorVersionStr "4" -#define xpdfMinorVersionStr "2" +#define xpdfMinorVersionStr "3" #define xpdfUpdateVersionStr "0" // supported PDF version @@ -28,11 +28,11 @@ #define supportedPDFVersionNum 2.0 // copyright notice -#define xpdfCopyright "Copyright 1996-2019 Glyph & Cog, LLC" +#define xpdfCopyright "Copyright 1996-2021 Glyph & Cog, LLC" // Windows resource file stuff -#define winxpdfVersion "WinXpdf 4.02" -#define xpdfCopyrightAmp "Copyright 1996-2019 Glyph && Cog, LLC" +#define winxpdfVersion "WinXpdf 4.03" +#define xpdfCopyrightAmp "Copyright 1996-2021 Glyph && Cog, LLC" //------------------------------------------------------------------------ // paper size @@ -59,12 +59,14 @@ #endif // system config file name (set via the configure script) -#ifdef SYSTEM_XPDFRC -#define xpdfSysConfigFile SYSTEM_XPDFRC -#else +#if defined(_WIN32) // under Windows, we get the directory with the executable and then // append this file name #define xpdfSysConfigFile "xpdfrc" +#elif defined(SYSTEM_XPDFRC) +#define xpdfSysConfigFile SYSTEM_XPDFRC +#else +#define xpdfSysConfigFile "/etc/xpdfrc" #endif //------------------------------------------------------------------------ diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/pdfdetach.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/pdfdetach.cc index 34fd439bd76..e8b74381017 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/pdfdetach.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/pdfdetach.cc @@ -16,6 +16,7 @@ #include "PDFDoc.h" #include "CharTypes.h" #include "UnicodeMap.h" +#include "UTF8.h" #include "Error.h" #include "config.h" @@ -67,8 +68,7 @@ int main(int argc, char *argv[]) { PDFDoc *doc; Unicode *name; char uBuf[8]; - char path[1024]; - char *p; + GString *path; GBool ok; int exitCode; int nFiles, nameLen, n, i, j; @@ -84,7 +84,7 @@ int main(int argc, char *argv[]) { ok = gFalse; } if (!ok || argc != 2 || printVersion || printHelp) { - fprintf(stderr, "pdfdetach version %s\n", xpdfVersion); + fprintf(stderr, "pdfdetach version %s [www.xpdfreader.com]\n", xpdfVersion); fprintf(stderr, "%s\n", xpdfCopyright); if (!printVersion) { printUsage("pdfdetach", "", argDesc); @@ -148,32 +148,24 @@ int main(int argc, char *argv[]) { } else if (saveAll) { for (i = 0; i < nFiles; ++i) { if (savePath[0]) { - n = (int)strlen(savePath); - if (n > (int)sizeof(path) - 2) { - n = sizeof(path) - 2; - } - memcpy(path, savePath, n); - path[n] = '/'; - p = path + n + 1; + path = new GString(savePath); + path->append('/'); } else { - p = path; + path = new GString(); } name = doc->getEmbeddedFileName(i); nameLen = doc->getEmbeddedFileNameLength(i); for (j = 0; j < nameLen; ++j) { - n = uMap->mapUnicode(name[j], uBuf, sizeof(uBuf)); - if (p + n >= path + sizeof(path)) { - break; - } - memcpy(p, uBuf, n); - p += n; + n = mapUTF8(name[j], uBuf, sizeof(uBuf)); + path->append(uBuf, n); } - *p = '\0'; - if (!doc->saveEmbeddedFile(i, path)) { - error(errIO, -1, "Error saving embedded file as '{0:s}'", p); + if (!doc->saveEmbeddedFileU(i, path->getCString())) { + error(errIO, -1, "Error saving embedded file as '{0:t}'", path); + delete path; exitCode = 2; goto err2; } + delete path; } // save an embedded file @@ -183,27 +175,23 @@ int main(int argc, char *argv[]) { goto err2; } if (savePath[0]) { - p = savePath; + path = new GString(savePath); } else { name = doc->getEmbeddedFileName(saveNum - 1); nameLen = doc->getEmbeddedFileNameLength(saveNum - 1); - p = path; + path = new GString(); for (j = 0; j < nameLen; ++j) { - n = uMap->mapUnicode(name[j], uBuf, sizeof(uBuf)); - if (p + n >= path + sizeof(path)) { - break; - } - memcpy(p, uBuf, n); - p += n; + n = mapUTF8(name[j], uBuf, sizeof(uBuf)); + path->append(uBuf, n); } - *p = '\0'; - p = path; } - if (!doc->saveEmbeddedFile(saveNum - 1, p)) { - error(errIO, -1, "Error saving embedded file as '{0:s}'", p); + if (!doc->saveEmbeddedFileU(saveNum - 1, path->getCString())) { + error(errIO, -1, "Error saving embedded file as '{0:t}'", path); + delete path; exitCode = 2; goto err2; } + delete path; } exitCode = 0; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/pdffonts.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/pdffonts.cc index 22298b138c5..42c46223d8b 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/pdffonts.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/pdffonts.cc @@ -23,7 +23,7 @@ #include "Dict.h" #include "GfxFont.h" #include "Annot.h" -#include "Form.h" +#include "AcroForm.h" #include "PDFDoc.h" #include "config.h" @@ -102,7 +102,7 @@ int main(int argc, char *argv[]) { Page *page; Dict *resDict; Annots *annots; - Form *form; + AcroForm *form; Object obj1, obj2; int pg, i, j; int exitCode; @@ -113,7 +113,7 @@ int main(int argc, char *argv[]) { fixCommandLine(&argc, &argv); ok = parseArgs(argDesc, &argc, argv); if (!ok || argc != 2 || printVersion || printHelp) { - fprintf(stderr, "pdffonts version %s\n", xpdfVersion); + fprintf(stderr, "pdffonts version %s [www.xpdfreader.com]\n", xpdfVersion); fprintf(stderr, "%s\n", xpdfCopyright); if (!printVersion) { printUsage("pdffonts", "", argDesc); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/pdfimages.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/pdfimages.cc index 24b7c0d448b..cd5d34014f4 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/pdfimages.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/pdfimages.cc @@ -87,7 +87,7 @@ int main(int argc, char *argv[]) { fixCommandLine(&argc, &argv); ok = parseArgs(argDesc, &argc, argv); if (!ok || argc != 3 || printVersion || printHelp) { - fprintf(stderr, "pdfimages version %s\n", xpdfVersion); + fprintf(stderr, "pdfimages version %s [www.xpdfreader.com]\n", xpdfVersion); fprintf(stderr, "%s\n", xpdfCopyright); if (!printVersion) { printUsage("pdfimages", " ", argDesc); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/pdfinfo.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/pdfinfo.cc index f5f593b9f13..621fb69ea6f 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/pdfinfo.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/pdfinfo.cc @@ -102,6 +102,9 @@ int main(int argc, char *argv[]) { FILE *f; GString *metadata; ZxDoc *xmp; + int permFlags, keyLength, encVersion; + GBool ownerPasswordOk; + CryptAlgorithm encAlgorithm; GBool ok; int exitCode; int pg, i; @@ -113,7 +116,7 @@ int main(int argc, char *argv[]) { fixCommandLine(&argc, &argv); ok = parseArgs(argDesc, &argc, argv); if (!ok || argc != 2 || printVersion || printHelp) { - fprintf(stderr, "pdfinfo version %s\n", xpdfVersion); + fprintf(stderr, "pdfinfo version %s [www.xpdfreader.com]\n", xpdfVersion); fprintf(stderr, "%s\n", xpdfCopyright); if (!printVersion) { printUsage("pdfinfo", "", argDesc); @@ -216,15 +219,19 @@ int main(int argc, char *argv[]) { printf("Pages: %d\n", doc->getNumPages()); // print encryption info - printf("Encrypted: "); if (doc->isEncrypted()) { - printf("yes (print:%s copy:%s change:%s addNotes:%s)\n", + doc->getXRef()->getEncryption(&permFlags, &ownerPasswordOk, &keyLength, + &encVersion, &encAlgorithm); + printf("Encrypted: %s %d-bit\n", + encAlgorithm == cryptRC4 ? "RC4" : "AES", + keyLength * 8); + printf("Permissions: print:%s copy:%s change:%s addNotes:%s\n", doc->okToPrint(gTrue) ? "yes" : "no", doc->okToCopy(gTrue) ? "yes" : "no", doc->okToChange(gTrue) ? "yes" : "no", doc->okToAddNotes(gTrue) ? "yes" : "no"); } else { - printf("no\n"); + printf("Encrypted: no\n"); } // print page size @@ -261,18 +268,18 @@ int main(int argc, char *argv[]) { if (multiPage) { for (pg = firstPage; pg <= lastPage; ++pg) { page = doc->getCatalog()->getPage(pg); - sprintf(buf, "Page %4d MediaBox: ", pg); + snprintf(buf, sizeof(buf), "Page %4d MediaBox: ", pg); printBox(buf, page->getMediaBox()); - sprintf(buf, "Page %4d CropBox: ", pg); + snprintf(buf, sizeof(buf), "Page %4d CropBox: ", pg); printBox(buf, page->getCropBox()); - sprintf(buf, "Page %4d BleedBox: ", pg); + snprintf(buf, sizeof(buf), "Page %4d BleedBox: ", pg); printBox(buf, page->getBleedBox()); - sprintf(buf, "Page %4d TrimBox: ", pg); + snprintf(buf, sizeof(buf), "Page %4d TrimBox: ", pg); printBox(buf, page->getTrimBox()); - sprintf(buf, "Page %4d ArtBox: ", pg); + snprintf(buf, sizeof(buf), "Page %4d ArtBox: ", pg); printBox(buf, page->getArtBox()); } - } else { + } else if (doc->getNumPages() > 0) { page = doc->getCatalog()->getPage(firstPage); printBox("MediaBox: ", page->getMediaBox()); printBox("CropBox: ", page->getCropBox()); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftohtml.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftohtml.cc index 29095509bed..af8fe93fbd5 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftohtml.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftohtml.cc @@ -31,6 +31,7 @@ static int firstPage = 1; static int lastPage = 0; static double zoom = 1; static int resolution = 150; +static GBool noFonts = gFalse; static GBool skipInvisible = gFalse; static GBool allInvisible = gFalse; static char ownerPassword[33] = "\001"; @@ -45,10 +46,12 @@ static ArgDesc argDesc[] = { "first page to convert"}, {"-l", argInt, &lastPage, 0, "last page to convert"}, - {"-z", argFP, &zoom, 0, + {"-z", argFP, &zoom, 0, "initial zoom level (1.0 means 72dpi)"}, - {"-r", argInt, &resolution, 0, + {"-r", argInt, &resolution, 0, "resolution, in DPI (default is 150)"}, + {"-nofonts", argFlag, &noFonts, 0, + "do not extract embedded fonts"}, {"-skipinvisible", argFlag, &skipInvisible, 0, "do not draw invisible text"}, {"-allinvisible", argFlag, &allInvisible, 0, @@ -97,7 +100,7 @@ int main(int argc, char *argv[]) { fixCommandLine(&argc, &argv); ok = parseArgs(argDesc, &argc, argv); if (!ok || argc != 3 || printVersion || printHelp) { - fprintf(stderr, "pdftohtml version %s\n", xpdfVersion); + fprintf(stderr, "pdftohtml version %s [www.xpdfreader.com]\n", xpdfVersion); fprintf(stderr, "%s\n", xpdfCopyright); if (!printVersion) { printUsage("pdftohtml", " ", argDesc); @@ -155,7 +158,7 @@ int main(int argc, char *argv[]) { } // create HTML directory - if (!createDir(htmlDir, 0755)) { + if (makeDir(htmlDir, 0755)) { error(errIO, -1, "Couldn't create HTML output directory '{0:s}'", htmlDir); exitCode = 2; @@ -171,20 +174,20 @@ int main(int argc, char *argv[]) { htmlGen->setZoom(zoom); htmlGen->setDrawInvisibleText(!skipInvisible); htmlGen->setAllTextInvisible(allInvisible); - htmlGen->setExtractFontFiles(gTrue); + htmlGen->setExtractFontFiles(!noFonts); htmlGen->startDoc(doc); // convert the pages for (pg = firstPage; pg <= lastPage; ++pg) { htmlFileName = GString::format("{0:s}/page{1:d}.html", htmlDir, pg); pngFileName = GString::format("{0:s}/page{1:d}.png", htmlDir, pg); - if (!(htmlFile = fopen(htmlFileName->getCString(), "wb"))) { + if (!(htmlFile = openFile(htmlFileName->getCString(), "wb"))) { error(errIO, -1, "Couldn't open HTML file '{0:t}'", htmlFileName); delete htmlFileName; delete pngFileName; goto err2; } - if (!(pngFile = fopen(pngFileName->getCString(), "wb"))) { + if (!(pngFile = openFile(pngFileName->getCString(), "wb"))) { error(errIO, -1, "Couldn't open PNG file '{0:t}'", pngFileName); fclose(htmlFile); delete htmlFileName; @@ -236,7 +239,7 @@ static GBool createIndex(char *htmlDir) { int pg; htmlFileName = GString::format("{0:s}/index.html", htmlDir); - html = fopen(htmlFileName->getCString(), "w"); + html = openFile(htmlFileName->getCString(), "w"); delete htmlFileName; if (!html) { error(errIO, -1, "Couldn't open HTML file '{0:t}'", htmlFileName); diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftopng.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftopng.cc index 64a0087c7d3..f19baccf581 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftopng.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftopng.cc @@ -18,6 +18,7 @@ #include "gmempp.h" #include "parseargs.h" #include "GString.h" +#include "gfile.h" #include "GlobalParams.h" #include "Object.h" #include "PDFDoc.h" @@ -32,6 +33,7 @@ static double resolution = 150; static GBool mono = gFalse; static GBool gray = gFalse; static GBool pngAlpha = gFalse; +static int rotate = 0; static char enableFreeTypeStr[16] = ""; static char antialiasStr[16] = ""; static char vectorAntialiasStr[16] = ""; @@ -55,6 +57,8 @@ static ArgDesc argDesc[] = { "generate a grayscale PNG file"}, {"-alpha", argFlag, &pngAlpha, 0, "include an alpha channel in the PNG file"}, + {"-rot", argInt, &rotate, 0, + "set page rotation: 0, 90, 180, or 270"}, #if HAVE_FREETYPE_H {"-freetype", argString, enableFreeTypeStr, sizeof(enableFreeTypeStr), "enable FreeType font rasterizer: yes, no"}, @@ -119,7 +123,7 @@ int main(int argc, char *argv[]) { goto err0; } if (!ok || argc != 3 || printVersion || printHelp) { - fprintf(stderr, "pdftopng version %s\n", xpdfVersion); + fprintf(stderr, "pdftopng version %s [www.xpdfreader.com]\n", xpdfVersion); fprintf(stderr, "%s\n", xpdfCopyright); if (!printVersion) { printUsage("pdftopng", " ", argDesc); @@ -197,7 +201,7 @@ int main(int argc, char *argv[]) { } splashOut->startDoc(doc->getXRef()); for (pg = firstPage; pg <= lastPage; ++pg) { - doc->displayPage(splashOut, pg, resolution, resolution, 0, + doc->displayPage(splashOut, pg, resolution, resolution, rotate, gFalse, gTrue, gFalse); if (mono) { if (!strcmp(pngRoot, "-")) { @@ -207,7 +211,7 @@ int main(int argc, char *argv[]) { #endif } else { pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg); - if (!(f = fopen(pngFile->getCString(), "wb"))) { + if (!(f = openFile(pngFile->getCString(), "wb"))) { exit(2); } delete pngFile; @@ -225,7 +229,7 @@ int main(int argc, char *argv[]) { #endif } else { pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg); - if (!(f = fopen(pngFile->getCString(), "wb"))) { + if (!(f = openFile(pngFile->getCString(), "wb"))) { exit(2); } delete pngFile; @@ -244,7 +248,7 @@ int main(int argc, char *argv[]) { #endif } else { pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg); - if (!(f = fopen(pngFile->getCString(), "wb"))) { + if (!(f = openFile(pngFile->getCString(), "wb"))) { exit(2); } delete pngFile; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftoppm.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftoppm.cc index e01ac5c1304..0b084b014e5 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftoppm.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftoppm.cc @@ -36,6 +36,7 @@ static GBool gray = gFalse; #if SPLASH_CMYK static GBool cmyk = gFalse; #endif +static int rotate = 0; static char enableFreeTypeStr[16] = ""; static char antialiasStr[16] = ""; static char vectorAntialiasStr[16] = ""; @@ -61,6 +62,8 @@ static ArgDesc argDesc[] = { {"-cmyk", argFlag, &cmyk, 0, "generate a CMYK PAM file"}, #endif + {"-rot", argInt, &rotate, 0, + "set page rotation: 0, 90, 180, or 270"}, #if HAVE_FREETYPE_H {"-freetype", argString, enableFreeTypeStr, sizeof(enableFreeTypeStr), "enable FreeType font rasterizer: yes, no"}, @@ -133,7 +136,7 @@ int main(int argc, char *argv[]) { ok = gFalse; } if (!ok || argc != 3 || printVersion || printHelp) { - fprintf(stderr, "pdftoppm version %s\n", xpdfVersion); + fprintf(stderr, "pdftoppm version %s [www.xpdfreader.com]\n", xpdfVersion); fprintf(stderr, "%s\n", xpdfCopyright); if (!printVersion) { printUsage("pdftoppm", " ", argDesc); @@ -226,7 +229,7 @@ int main(int argc, char *argv[]) { } splashOut->startDoc(doc->getXRef()); for (pg = firstPage; pg <= lastPage; ++pg) { - doc->displayPage(splashOut, pg, resolution, resolution, 0, + doc->displayPage(splashOut, pg, resolution, resolution, rotate, gFalse, gTrue, gFalse); if (!strcmp(ppmRoot, "-")) { #ifdef _WIN32 diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftops.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftops.cc index 8e75dd83f55..36294551a3b 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftops.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftops.cc @@ -182,7 +182,7 @@ int main(int argc, char *argv[]) { fixCommandLine(&argc, &argv); ok = parseArgs(argDesc, &argc, argv); if (!ok || argc < 2 || argc > 3 || printVersion || printHelp) { - fprintf(stderr, "pdftops version %s\n", xpdfVersion); + fprintf(stderr, "pdftops version %s [www.xpdfreader.com]\n", xpdfVersion); fprintf(stderr, "%s\n", xpdfCopyright); if (!printVersion) { printUsage("pdftops", " []", argDesc); @@ -355,7 +355,7 @@ int main(int argc, char *argv[]) { // write PostScript file psOut = new PSOutputDev(psFileName->getCString(), doc, firstPage, lastPage, mode, - 0, 0, 0, 0, gFalse, NULL, NULL, userUnit); + 0, 0, 0, 0, gFalse, NULL, NULL, userUnit, gTrue); if (!psOut->isOk()) { delete psOut; exitCode = 2; diff --git a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftotext.cc b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftotext.cc index aa74fb68612..975cd6a1959 100644 --- a/Build/source/libs/xpdf/xpdf-src/xpdf/pdftotext.cc +++ b/Build/source/libs/xpdf/xpdf-src/xpdf/pdftotext.cc @@ -19,6 +19,7 @@ #include "gmempp.h" #include "parseargs.h" #include "GString.h" +#include "GList.h" #include "GlobalParams.h" #include "Object.h" #include "Stream.h" @@ -39,6 +40,7 @@ static int firstPage = 1; static int lastPage = 0; static GBool physLayout = gFalse; static GBool simpleLayout = gFalse; +static GBool simple2Layout = gFalse; static GBool tableLayout = gFalse; static GBool linePrinter = gFalse; static GBool rawOrder = gFalse; @@ -58,6 +60,7 @@ static char ownerPassword[33] = "\001"; static char userPassword[33] = "\001"; static GBool quiet = gFalse; static char cfgFileName[256] = ""; +static GBool listEncodings = gFalse; static GBool printVersion = gFalse; static GBool printHelp = gFalse; @@ -70,6 +73,8 @@ static ArgDesc argDesc[] = { "maintain original physical layout"}, {"-simple", argFlag, &simpleLayout, 0, "simple one-column page layout"}, + {"-simple2", argFlag, &simple2Layout, 0, + "simple one-column page layout, version 2"}, {"-table", argFlag, &tableLayout, 0, "similar to -layout, but optimized for tables"}, {"-lineprinter", argFlag, &linePrinter, 0, @@ -108,6 +113,8 @@ static ArgDesc argDesc[] = { "don't print any messages or errors"}, {"-cfg", argString, cfgFileName, sizeof(cfgFileName), "configuration file to use in place of .xpdfrc"}, + {"-listencodings", argFlag, &listEncodings, 0, + "list all available output text encodings"}, {"-v", argFlag, &printVersion, 0, "print copyright and version info"}, {"-h", argFlag, &printHelp, 0, @@ -153,8 +160,19 @@ int main(int argc, char *argv[]) { // parse args fixCommandLine(&argc, &argv); ok = parseArgs(argDesc, &argc, argv); + if (ok && listEncodings) { + // list available encodings + globalParams = new GlobalParams(cfgFileName); + GList *encs = globalParams->getAvailableTextEncodings(); + for (int i = 0; i < encs->getLength(); ++i) { + printf("%s\n", ((GString *)encs->get(i))->getCString()); + } + deleteGList(encs, GString); + delete globalParams; + goto err0; + } if (!ok || argc < 2 || argc > 3 || printVersion || printHelp) { - fprintf(stderr, "pdftotext version %s\n", xpdfVersion); + fprintf(stderr, "pdftotext version %s [www.xpdfreader.com]\n", xpdfVersion); fprintf(stderr, "%s\n", xpdfCopyright); if (!printVersion) { printUsage("pdftotext", " []", argDesc); @@ -247,6 +265,8 @@ int main(int argc, char *argv[]) { textOutControl.fixedPitch = fixedPitch; } else if (simpleLayout) { textOutControl.mode = textOutSimpleLayout; + } else if (simple2Layout) { + textOutControl.mode = textOutSimple2Layout; } else if (linePrinter) { textOutControl.mode = textOutLinePrinter; textOutControl.fixedPitch = fixedPitch; @@ -264,7 +284,7 @@ int main(int argc, char *argv[]) { textOutControl.marginTop = marginTop; textOutControl.marginBottom = marginBottom; textOut = new TextOutputDev(textFileName->getCString(), &textOutControl, - gFalse); + gFalse, gTrue); if (textOut->isOk()) { doc->displayPages(textOut, firstPage, lastPage, 72, 72, 0, gFalse, gTrue, gFalse); -- cgit v1.2.3