diff options
author | Robin Watts <robin.watts@artifex.com> | 2013-07-17 17:32:12 +0100 |
---|---|---|
committer | Robin Watts <robin.watts@artifex.com> | 2013-07-19 19:56:27 +0100 |
commit | 42eb247ea0c69ba8cc249b1bb44fafcdcbdd2621 (patch) | |
tree | 6fde9d99a9551faa8abc6e0d4d3aa24db5824e4f /platform | |
parent | 90d1d2cf603ba9d61e8160c0ba12325cd8249034 (diff) | |
download | mupdf-42eb247ea0c69ba8cc249b1bb44fafcdcbdd2621.tar.xz |
Add mupdf-curl app
Windows and X11. Allows files to be fetched and displayed
as they are downloaded both with and without linearization, using
hints if available.
Diffstat (limited to 'platform')
-rw-r--r-- | platform/win32/mupdf-curl.vcproj | 421 | ||||
-rw-r--r-- | platform/win32/mupdf.sln | 39 | ||||
-rw-r--r-- | platform/x11/curl_stream.c | 525 | ||||
-rw-r--r-- | platform/x11/curl_stream.h | 7 | ||||
-rw-r--r-- | platform/x11/jstest_main.c | 4 | ||||
-rw-r--r-- | platform/x11/pdfapp.c | 110 | ||||
-rw-r--r-- | platform/x11/pdfapp.h | 7 | ||||
-rw-r--r-- | platform/x11/win_main.c | 24 | ||||
-rw-r--r-- | platform/x11/x11_main.c | 24 |
9 files changed, 1140 insertions, 21 deletions
diff --git a/platform/win32/mupdf-curl.vcproj b/platform/win32/mupdf-curl.vcproj new file mode 100644 index 00000000..91217d8b --- /dev/null +++ b/platform/win32/mupdf-curl.vcproj @@ -0,0 +1,421 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="mupdf-curl" + ProjectGUID="{27B53E5C-ACAB-423C-854E-BECE56D73544}" + RootNamespace="mupdf" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)\$(ProjectName)" + ConfigurationType="1" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\..\include,..\..\thirdparty\curl\include" + PreprocessorDefinitions="FT2_BUILD_LIBRARY;OPJ_STATIC;DEBUG=1;HAVE_CURL=1" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + WarningLevel="3" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib" + GenerateDebugInformation="true" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)\$(ProjectName)" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="2" + EnableIntrinsicFunctions="true" + AdditionalIncludeDirectories="..\..\include,..\..\thirdparty\curl\include" + PreprocessorDefinitions="HAVE_CURL=1" + RuntimeLibrary="0" + EnableFunctionLevelLinking="true" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib" + GenerateDebugInformation="true" + OptimizeReferences="2" + EnableCOMDATFolding="2" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Memento|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)\$(ProjectName)" + ConfigurationType="1" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\..\include,..\..\thirdparty\curl\include" + PreprocessorDefinitions="FT2_BUILD_LIBRARY;OPJ_STATIC;MEMENTO=1;DEBUG=1;HAVE_CURL=1" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + WarningLevel="3" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib" + GenerateDebugInformation="true" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="DebugOpenssl|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="1" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories="..\..\include,..\..\thirdparty\curl\include" + PreprocessorDefinitions="FT2_BUILD_LIBRARY;OPJ_STATIC;DEBUG=1;HAVE_OPENSSL;HAVE_CURL=1" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + WarningLevel="3" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib" + AdditionalLibraryDirectories="..\..\thirdparty\openssl\lib" + GenerateDebugInformation="true" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="ReleaseOpenssl|Win32" + OutputDirectory="$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="2" + EnableIntrinsicFunctions="true" + AdditionalIncludeDirectories="..\..\include,..\..\thirdparty\curl\include" + PreprocessorDefinitions="HAVE_OPENSSL;HAVE_CURL=1" + RuntimeLibrary="0" + EnableFunctionLevelLinking="true" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="ws2_32.lib" + AdditionalLibraryDirectories="..\..\thirdparty\openssl\lib" + GenerateDebugInformation="true" + OptimizeReferences="2" + EnableCOMDATFolding="2" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <File + RelativePath="..\x11\curl_stream.c" + > + </File> + <File + RelativePath="..\x11\curl_stream.h" + > + </File> + <File + RelativePath="..\..\platform\x11\pdfapp.c" + > + </File> + <File + RelativePath="..\..\platform\x11\pdfapp.h" + > + </File> + <File + RelativePath="..\..\platform\x11\win_main.c" + > + </File> + <File + RelativePath="..\..\platform\x11\win_res.rc" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/platform/win32/mupdf.sln b/platform/win32/mupdf.sln index a179a008..b8de7e2a 100644 --- a/platform/win32/mupdf.sln +++ b/platform/win32/mupdf.sln @@ -3,8 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2005 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mupdf", "mupdf.vcproj", "{E74F29F0-FA43-4ADC-B92C-6AFA08E4A417}" ProjectSection(ProjectDependencies) = postProject - {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6} = {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6} + {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libthirdparty", "libthirdparty.vcproj", "{5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C}" @@ -16,14 +16,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmupdf", "libmupdf.vcproj EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mudraw", "mudraw.vcproj", "{0B51171B-B10E-4EAC-8FFA-19226A1828A3}" ProjectSection(ProjectDependencies) = postProject - {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6} = {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6} + {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mutool", "mutool.vcproj", "{00811970-815B-4F64-BC9D-219078B1F3AA}" ProjectSection(ProjectDependencies) = postProject - {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6} = {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6} + {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "generated", "generated.vcproj", "{A5053AA7-02E5-4903-B596-04F17AEB1526}" @@ -35,14 +35,14 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmupdf-js-v8", "libmupdf- EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mupdf-v8", "mupdf-v8.vcproj", "{9035A4F3-4219-45A5-985D-FBF4D9609713}" ProjectSection(ProjectDependencies) = postProject - {2E5DAFDB-A060-4011-B760-32F6A3A4BC9D} = {2E5DAFDB-A060-4011-B760-32F6A3A4BC9D} {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} + {2E5DAFDB-A060-4011-B760-32F6A3A4BC9D} = {2E5DAFDB-A060-4011-B760-32F6A3A4BC9D} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mujstest-v8", "mujstest-v8.vcproj", "{21E28758-E4D2-4B84-8EC5-B631CEE66B30}" ProjectSection(ProjectDependencies) = postProject - {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} {2E5DAFDB-A060-4011-B760-32F6A3A4BC9D} = {2E5DAFDB-A060-4011-B760-32F6A3A4BC9D} + {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmupdf-js-none", "libmupdf-js-none.vcproj", "{EC81A9F3-88A6-4170-B7B4-C41CB789A7F6}" @@ -50,6 +50,15 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmupdf-js-none", "libmupd {5F615F91-DFF8-4F05-BF48-6222B7D86519} = {5F615F91-DFF8-4F05-BF48-6222B7D86519} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcurl", "..\..\thirdparty\curl\vs\vc8\lib\vc8libcurl.vcproj", "{87EE9DA4-DE1E-4448-8324-183C98DCA588}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mupdf-curl", "mupdf-curl.vcproj", "{27B53E5C-ACAB-423C-854E-BECE56D73544}" + ProjectSection(ProjectDependencies) = postProject + {87EE9DA4-DE1E-4448-8324-183C98DCA588} = {87EE9DA4-DE1E-4448-8324-183C98DCA588} + {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6} = {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6} + {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} = {5EDCF4FD-0291-4FB9-8D96-D58957CA5E3C} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -159,6 +168,26 @@ Global {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6}.Release|Win32.Build.0 = Release|Win32 {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6}.ReleaseOpenssl|Win32.ActiveCfg = ReleaseOpenssl|Win32 {EC81A9F3-88A6-4170-B7B4-C41CB789A7F6}.ReleaseOpenssl|Win32.Build.0 = ReleaseOpenssl|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.Debug|Win32.ActiveCfg = Debug|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.Debug|Win32.Build.0 = Debug|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.DebugOpenssl|Win32.ActiveCfg = Debug|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.DebugOpenssl|Win32.Build.0 = Debug|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.Memento|Win32.ActiveCfg = Memento|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.Memento|Win32.Build.0 = Memento|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.Release|Win32.ActiveCfg = Release|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.Release|Win32.Build.0 = Release|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.ReleaseOpenssl|Win32.ActiveCfg = Release|Win32 + {87EE9DA4-DE1E-4448-8324-183C98DCA588}.ReleaseOpenssl|Win32.Build.0 = Release|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.Debug|Win32.ActiveCfg = Debug|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.Debug|Win32.Build.0 = Debug|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.DebugOpenssl|Win32.ActiveCfg = DebugOpenssl|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.DebugOpenssl|Win32.Build.0 = DebugOpenssl|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.Memento|Win32.ActiveCfg = Memento|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.Memento|Win32.Build.0 = Memento|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.Release|Win32.ActiveCfg = Release|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.Release|Win32.Build.0 = Release|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.ReleaseOpenssl|Win32.ActiveCfg = ReleaseOpenssl|Win32 + {27B53E5C-ACAB-423C-854E-BECE56D73544}.ReleaseOpenssl|Win32.Build.0 = ReleaseOpenssl|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/platform/x11/curl_stream.c b/platform/x11/curl_stream.c new file mode 100644 index 00000000..3a0de68c --- /dev/null +++ b/platform/x11/curl_stream.c @@ -0,0 +1,525 @@ +/* Simple example fz_stream implementation using curl */ + +#include "mupdf/fitz.h" +#include "curl_stream.h" + +#define CURL_STATICLIB +#include <curl/curl.h> + +#undef DEBUG_BLOCK_FETCHING + +#ifdef DEBUG_BLOCK_FETCHING +#define DEBUG_MESSAGE(A) do { fz_warn A; } while(0) +#else +#define DEBUG_MESSAGE(A) do { } while(0) +#endif + +#define BLOCK_SHIFT 12 +#define BLOCK_SIZE (1<<BLOCK_SHIFT) + +#ifdef _WIN32 +#include <windows.h> +#else +#include "pthread.h" +#include <ctype.h> +#endif + +typedef struct curl_stream_state_s curl_stream_state; + +struct curl_stream_state_s +{ + fz_context *ctx; + CURL *handle; + char *filename; + int data_arrived; + int content_length; /* As returned by curl. -1 for unknown. */ + int total_length; /* As obtained from the Content-Range header. */ + int buffer_max; + int buffer_fill; + unsigned char *buffer; + int map_length; + unsigned char *map; + int fill_point; /* The next file offset we will fetch to */ + int current_fill_point; /* The current file offset we are fetching to */ + int complete; + int kill_thread; + void (*more_data)(void *, int); + void *more_data_arg; + +#ifdef _WIN32 + void *thread; + DWORD thread_id; + HANDLE mutex; +#else + pthread_t thread; + pthread_mutex_t mutex; +#endif +}; + +static void fetcher_thread(curl_stream_state *state); + +#ifdef _WIN32 +static void +lock(curl_stream_state *state) +{ + WaitForSingleObject(state->mutex, INFINITE); +} + +static void +unlock(curl_stream_state *state) +{ + ReleaseMutex(state->mutex); +} + +static DWORD WINAPI +win_thread(void *lparam) +{ + fetcher_thread((curl_stream_state *)lparam); + + return 0; +} + +#else /* Anything else assumed to be pthreads */ + +static void +lock(curl_stream_state *state) +{ + pthread_mutex_lock(&state->mutex); +} + +static void +unlock(curl_stream_state *state) +{ + pthread_mutex_unlock(&state->mutex); +} + + +static void * +pthread_thread(void *arg) +{ + fetcher_thread((curl_stream_state *)arg); + + return NULL; +} +#endif + +static size_t header_arrived(void *ptr, size_t size, size_t nmemb, void *state_) +{ + curl_stream_state *state = (curl_stream_state *)state_; + + if (strncmp(ptr, "Content-Range:", 14) == 0) + { + char *p = (char *)ptr; + int len = nmemb * size; + int start, end, total; + while (len && !isdigit(*p)) + p++, len--; + start = 0; + while (len && isdigit(*p)) + { + start = start*10 + *p-'0'; + p++, len--; + } + while (len && !isdigit(*p)) + p++, len--; + end = 0; + while (len && isdigit(*p)) + { + end = end*10 + *p-'0'; + p++, len--; + } + while (len && !isdigit(*p)) + p++, len--; + total = 0; + while (len && isdigit(*p)) + { + total = total*10 + *p-'0'; + p++, len--; + } + state->total_length = total; + } + + return nmemb * size; +} + +static size_t data_arrived(void *ptr, size_t size, size_t nmemb, void *state_) +{ + curl_stream_state *state = (curl_stream_state *)state_; + int old_start; + + size *= nmemb; + + if (state->data_arrived == 0) + { + double d; + long response; + int len; + /* This is the first time data has arrived. If the response + * code is 206, then we can do byte requests, and we will + * known the total_length from having processed the header + * already. */ + curl_easy_getinfo(state->handle, CURLINFO_RESPONSE_CODE, &response); + if (state->total_length && response == 206) + { + /* We got a range header, and the correct http response + * code. We can assume that byte fetches are accepted + * and we'll run without progressive mode. */ + state->content_length = len = state->total_length; + state->map_length = (len+BLOCK_SIZE-1)>>BLOCK_SHIFT; + state->map = fz_malloc_no_throw(state->ctx, (state->map_length+7)>>3); + state->buffer = fz_malloc_no_throw(state->ctx, len); + state->buffer_max = len; + if (state->map == NULL || state->buffer == NULL) + { + /* FIXME: Crap error handling! */ + exit(1); + } + memset(state->map, 0, (state->map_length+7)>>3); + } + else + { + /* So we can't use ByteRanges. Do we at least know the + * complete length of the file? */ + curl_easy_getinfo(state->handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d); + state->content_length = len = (int)d; + if (len > 0 && response == 200) + { + /* Yes. We can run as a progressive file */ + state->buffer = fz_malloc_no_throw(state->ctx, len); + state->buffer_max = len; + if (state->buffer == NULL) + { + /* FIXME: Crap error handling! */ + exit(1); + } + } + else + { + /* What a crap server. Won't tell us how big + * the file is. We'll have to expand as data + * as arrives. */ + state->content_length = -1; + } + } + state->data_arrived = 1; + } + if (state->content_length < 0) + { + int newsize = state->current_fill_point + size; + if (newsize > state->buffer_max) + { + /* Expand the buffer */ + int new_max = state->buffer_max * 2; + if (new_max == 0) + new_max = 4096; + state->buffer = fz_resize_array_no_throw(state->ctx, state->buffer, new_max, 1); + if (state->buffer == NULL) + { + /* FIXME: Crap error handling! */ + exit(1); + } + state->buffer_max = new_max; + } + } + + DEBUG_MESSAGE((state->ctx, "data arrived: offset=%d len=%d", state->current_fill_point, size)); + old_start = state->current_fill_point; + memcpy(state->buffer + state->current_fill_point, ptr, size); + state->current_fill_point += size; + if (state->current_fill_point == state->content_length || + (((state->current_fill_point ^ old_start) & ~(BLOCK_SIZE-1)) != 0)) + { + if (state->map) + { + old_start >>= BLOCK_SHIFT; + state->map[old_start>>3] |= 1<<(old_start & 7); + } + } + + if (state->more_data) + state->more_data(state->more_data_arg, 0); + + return size; +} + +#define HAVE_BLOCK(map, num) \ + (((map)[(num)>>3] & (1<<((num) & 7))) != 0) + +static void +fetch_chunk(curl_stream_state *state) +{ + char text[32]; + int fill, start, end; + + lock(state); + + if (state->kill_thread) + { + state->complete = 1; + unlock(state); + return; + } + + fill = state->fill_point; + if (state->content_length > 0) + { + /* Find the next block that we haven't got */ + int map_length = state->map_length; + unsigned char *map = state->map; + for ( ; (fill < map_length && HAVE_BLOCK(map, fill)); fill++); + if (fill == map_length) + { + for (fill = 0; + (fill < map_length && HAVE_BLOCK(map, fill)); + fill++); + if (fill == map_length) + { + /* We've got it all! */ + state->complete = 1; + state->kill_thread = 1; + unlock(state); + if (state->more_data) + state->more_data(state->more_data_arg, 1); + fz_warn(state->ctx, "Background fetch complete!"); + return; + } + } + DEBUG_MESSAGE((state->ctx, "block requested was %d, fetching %d", state->fill_point, fill)); + state->fill_point = fill; + } + + unlock(state); + + /* Fetch that block */ + start = fill * BLOCK_SIZE; + end = start + BLOCK_SIZE-1; + state->current_fill_point = start; + if (state->content_length > 0 && start >= state->content_length) + state->complete = 1; + if (state->content_length > 0 && end >= state->content_length) + end = state->content_length-1; + snprintf(text, 32, "%d-%d", start, end); + curl_easy_setopt(state->handle, CURLOPT_RANGE, text); + curl_easy_perform(state->handle); +} + +static void +fetcher_thread(curl_stream_state *state) +{ + while (!state->complete) + fetch_chunk(state); +} + +static int +stream_read(fz_stream *stream, unsigned char *buf, int len) +{ + curl_stream_state *state = (curl_stream_state *)stream->state; + int len_read = 0; + int read_point = stream->pos; + int block = read_point>>BLOCK_SHIFT; + int left_over = (-read_point) & (BLOCK_SIZE-1); + + if (state->content_length == 0) + fz_throw(stream->ctx, FZ_ERROR_TRYLATER, "read of a block we don't have (A) (offset=%d)", read_point); + + if (state->map == NULL) + { + /* We are doing a simple linear fetch as we don't know the + * content length. */ + if (read_point + len > state->current_fill_point) + { + stream->rp = stream->wp = stream->bp; + fz_throw(stream->ctx, FZ_ERROR_TRYLATER, "read of a block we don't have (B) (offset=%d)", read_point); + } + memcpy(buf, state->buffer + read_point, len); + return len; + } + + if (read_point + len > state->content_length) + len = state->content_length - read_point; + if (left_over > len) + left_over = len; + + if (left_over) + { + /* We are starting midway through a block */ + if (!HAVE_BLOCK(state->map, block)) + { + lock(state); + state->fill_point = block; + unlock(state); + stream->rp = stream->wp = stream->bp; + fz_throw(stream->ctx, FZ_ERROR_TRYLATER, "read of a block we don't have (C) (offset=%d)", read_point); + } + block++; + if (left_over > len) + left_over = len; + memcpy(buf, state->buffer + read_point, left_over); + buf += left_over; + read_point += left_over; + len -= left_over; + len_read += left_over; + } + + /* Copy any complete blocks */ + while (len > BLOCK_SIZE) + { + if (!HAVE_BLOCK(state->map, block)) + { + lock(state); + state->fill_point = block; + unlock(state); + stream->rp = stream->wp = stream->bp; + fz_throw(stream->ctx, FZ_ERROR_TRYLATER, "read of a block we don't have (D) (offset=%d)", read_point); + } + block++; + memcpy(buf, state->buffer + read_point, BLOCK_SIZE); + buf += BLOCK_SIZE; + read_point += BLOCK_SIZE; + len -= BLOCK_SIZE; + len_read += BLOCK_SIZE; + } + + /* Copy any trailing bytes */ + if (len > 0) + { + if (!HAVE_BLOCK(state->map, block)) + { + lock(state); + state->fill_point = block; + unlock(state); + stream->rp = stream->wp = stream->bp; + fz_throw(stream->ctx, FZ_ERROR_TRYLATER, "read of a block we don't have (E) (offset=%d)", read_point); + } + memcpy(buf, state->buffer + read_point, len); + len_read += len; + } + + return len_read; +} + +static void +stream_close(fz_context *ctx, void *state_) +{ + curl_stream_state *state = (curl_stream_state *)state_; + + if (!state || state->kill_thread) + return; + + lock(state); + state->kill_thread = 1; + unlock(state); + +#ifdef _WIN32 + WaitForSingleObject(state->thread, INFINITE); + CloseHandle(state->thread); + CloseHandle(state->mutex); +#else + pthread_join(state->thread, NULL); + pthread_mutex_destroy(&state->mutex); +#endif + + fz_free(state->ctx, state->buffer); + fz_free(state->ctx, state->map); + fz_free(state->ctx, state); +} + +static fz_stream hack_stream; +static curl_stream_state hack; +static int hack_pos; + +static void +stream_seek(fz_stream *stream, int offset, int whence) +{ + curl_stream_state *state = (curl_stream_state *)stream->state; + + switch(whence) + { + case SEEK_CUR: + offset += stream->pos; + break; + case SEEK_END: + offset += state->content_length; + break; + default: + case SEEK_SET: + break; + } + if (offset < 0) + offset = 0; + else if (state->content_length > 0 && offset > state->content_length) + offset = state->content_length; + stream->wp = stream->rp = stream->bp; + stream->pos = offset; + hack = *state; + hack_pos = offset; + hack_stream = *stream; +} + +static int +stream_meta(fz_stream *stream, int key, int size, void *ptr) +{ + curl_stream_state *state = (curl_stream_state *)stream->state; + + switch(key) + { + case FZ_STREAM_META_LENGTH: + if (!state->data_arrived) + fz_throw(stream->ctx, FZ_ERROR_TRYLATER, "still awaiting file length"); + return state->content_length; + case FZ_STREAM_META_PROGRESSIVE: + return 1; + } + return -1; +} + +fz_stream *fz_stream_from_curl(fz_context *ctx, char *filename, void (*more_data)(void *,int), void *more_data_arg) +{ + CURLcode ret; + CURL *handle; + curl_stream_state *state = fz_malloc_struct(ctx, curl_stream_state); + fz_stream *stream; + + ret = curl_global_init(CURL_GLOBAL_ALL); + if (ret != 0) + fz_throw(ctx, FZ_ERROR_GENERIC, "curl init failed (code %d)", ret); + + state->ctx = ctx; + state->handle = handle = curl_easy_init(); + state->more_data = more_data; + state->more_data_arg = more_data_arg; + + curl_easy_setopt(handle, CURLOPT_URL, filename); + + curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1); + + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, data_arrived); + + curl_easy_setopt(handle, CURLOPT_WRITEDATA, state); + + curl_easy_setopt(handle, CURLOPT_WRITEHEADER, state); + + curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, header_arrived); + +#ifdef _WIN32 + state->mutex = CreateMutex(NULL, FALSE, NULL); + if (state->mutex == NULL) + fz_throw(ctx, FZ_ERROR_GENERIC, "mutex creation failed"); + + state->thread = CreateThread(NULL, 0, win_thread, state, 0, &state->thread_id); + if (state->thread == NULL) + fz_throw(ctx, FZ_ERROR_GENERIC, "thread creation failed"); +#else + if (pthread_mutex_init(&state->mutex, NULL)) + fz_throw(ctx, FZ_ERROR_GENERIC, "mutex creation failed"); + + if (pthread_create(&state->thread, NULL, pthread_thread, state)) + fz_throw(ctx, FZ_ERROR_GENERIC, "thread creation failed"); + +#endif + + stream = fz_new_stream(ctx, state, stream_read, stream_close); + stream->seek = stream_seek; + stream->meta = stream_meta; + return stream; +} diff --git a/platform/x11/curl_stream.h b/platform/x11/curl_stream.h new file mode 100644 index 00000000..8672df17 --- /dev/null +++ b/platform/x11/curl_stream.h @@ -0,0 +1,7 @@ +#if !defined(CURL_STREAM_H) && defined(HAVE_CURL) + +#define CURL_STREAM_H + +fz_stream *fz_stream_from_curl(fz_context *ctx, char *url, void (*more_data)(void *,int), void *more_data_arg); + +#endif diff --git a/platform/x11/jstest_main.c b/platform/x11/jstest_main.c index a07385bc..4cb11185 100644 --- a/platform/x11/jstest_main.c +++ b/platform/x11/jstest_main.c @@ -158,6 +158,10 @@ void winreloadfile(pdfapp_t *app) pdfapp_open(app, filename, 1); } +void winreloadpage(pdfapp_t *app) +{ +} + void winopenuri(pdfapp_t *app, char *buf) { } diff --git a/platform/x11/pdfapp.c b/platform/x11/pdfapp.c index c891558f..af4cb095 100644 --- a/platform/x11/pdfapp.c +++ b/platform/x11/pdfapp.c @@ -1,4 +1,5 @@ #include "pdfapp.h" +#include "curl_stream.h" #include <ctype.h> /* for tolower() */ @@ -19,6 +20,12 @@ enum panning PAN_TO_BOTTOM }; +enum +{ + PDFAPP_OUTLINE_DEFERRED = 1, + PDFAPP_OUTLINE_LOAD_NOW = 2 +}; + static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint, int transition); static void pdfapp_updatepage(pdfapp_t *app); @@ -160,6 +167,22 @@ void pdfapp_open(pdfapp_t *app, char *filename, int reload) pdfapp_open_progressive(app, filename, reload, 0); } +#ifdef HAVE_CURL +static void +pdfapp_more_data(void *app_, int complete) +{ + pdfapp_t *app = (pdfapp_t *)app_; + + if (complete && app->outline_deferred == PDFAPP_OUTLINE_DEFERRED) + { + app->outline_deferred = PDFAPP_OUTLINE_LOAD_NOW; + winreloadpage(app); + } + else if (app->incomplete) + winreloadpage(app); +} +#endif + void pdfapp_open_progressive(pdfapp_t *app, char *filename, int reload, int bps) { fz_context *ctx = app->ctx; @@ -169,6 +192,31 @@ void pdfapp_open_progressive(pdfapp_t *app, char *filename, int reload, int bps) { pdf_document *idoc; +#ifdef HAVE_CURL + if (!strncmp(filename, "http://", 7)) + { + app->stream = fz_stream_from_curl(ctx, filename, pdfapp_more_data, app); + while (1) + { + fz_try(ctx) + { + fz_seek(app->stream, 0, SEEK_SET); + app->doc = fz_open_document_with_stream(ctx, filename, app->stream); + } + fz_catch(ctx) + { + if (fz_caught(ctx) == FZ_ERROR_TRYLATER) + { + pdfapp_warn(app, "not enough data to open yet"); + continue; + } + fz_rethrow(ctx); + } + break; + } + } + else +#endif if (bps == 0) { app->doc = fz_open_document(ctx, filename); @@ -250,8 +298,7 @@ void pdfapp_open_progressive(pdfapp_t *app, char *filename, int reload, int bps) { if (fz_caught(ctx) == FZ_ERROR_TRYLATER) { - pdfapp_warn(app, "not enough data to load outline yet - ignoring"); - /* FIXME: Set 'outline_deferred' and retry at end? */ + app->outline_deferred = PDFAPP_OUTLINE_DEFERRED; } else fz_rethrow(ctx); @@ -325,6 +372,10 @@ void pdfapp_close(pdfapp_t *app) fz_close_document(app->doc); app->doc = NULL; +#ifdef HAVE_CURL + fz_close(app->stream); +#endif + fz_flush_warnings(app->ctx); } @@ -486,6 +537,8 @@ static void pdfapp_loadpage(pdfapp_t *app) app->page_bbox.x1 = 100; app->page_bbox.y1 = 100; + app->incomplete = 0; + fz_try(app->ctx) { app->page = fz_load_page(app->doc, app->pageno - 1); @@ -494,7 +547,10 @@ static void pdfapp_loadpage(pdfapp_t *app) } fz_catch(app->ctx) { - pdfapp_warn(app, "Cannot load page"); + if (fz_caught(app->ctx) == FZ_ERROR_TRYLATER) + app->incomplete = 1; + else + pdfapp_warn(app, "Cannot load page"); return; } @@ -512,14 +568,15 @@ static void pdfapp_loadpage(pdfapp_t *app) mdev = fz_new_list_device(app->ctx, app->annotations_list); for (annot = fz_first_annot(app->doc, app->page); annot; annot = fz_next_annot(app->doc, annot)) fz_run_annot(app->doc, app->page, annot, mdev, &fz_identity, &cookie); - if (cookie.errors) + if (cookie.incomplete) { - pdfapp_warn(app, "Errors found on page"); - errored = 1; + app->incomplete = 1; + //pdfapp_warn(app, "Incomplete page rendering"); } - if (cookie.incomplete) + else if (cookie.errors) { - pdfapp_warn(app, "Incomplete page rendering"); + pdfapp_warn(app, "Errors found on page"); + errored = 1; } } fz_always(app->ctx) @@ -528,8 +585,13 @@ static void pdfapp_loadpage(pdfapp_t *app) } fz_catch(app->ctx) { - pdfapp_warn(app, "Cannot load page"); - errored = 1; + if (fz_caught(app->ctx) == FZ_ERROR_TRYLATER) + app->incomplete = 1; + else + { + pdfapp_warn(app, "Cannot load page"); + errored = 1; + } } fz_try(app->ctx) @@ -538,7 +600,9 @@ static void pdfapp_loadpage(pdfapp_t *app) } fz_catch(app->ctx) { - if (!errored) + if (fz_caught(app->ctx) == FZ_ERROR_TRYLATER) + app->incomplete = 1; + else if (!errored) pdfapp_warn(app, "Cannot load page"); } @@ -564,7 +628,12 @@ static void pdfapp_recreate_annotationslist(pdfapp_t *app) mdev = fz_new_list_device(app->ctx, app->annotations_list); for (annot = fz_first_annot(app->doc, app->page); annot; annot = fz_next_annot(app->doc, annot)) fz_run_annot(app->doc, app->page, annot, mdev, &fz_identity, &cookie); - if (cookie.errors) + if (cookie.incomplete) + { + app->incomplete = 1; + //pdfapp_warn(app, "Incomplete page rendering"); + } + else if (cookie.errors) { pdfapp_warn(app, "Errors found on page"); errored = 1; @@ -621,6 +690,23 @@ static void pdfapp_updatepage(pdfapp_t *app) pdfapp_showpage(app, 0, 0, 1, 0); } +void pdfapp_reloadpage(pdfapp_t *app) +{ + if (app->outline_deferred == PDFAPP_OUTLINE_LOAD_NOW) + { + fz_try(app->ctx) + { + app->outline = fz_load_outline(app->doc); + } + fz_catch(app->ctx) + { + /* Ignore any error now */ + } + app->outline_deferred = 0; + } + pdfapp_showpage(app, 1, 1, 1, 0); +} + static void pdfapp_showpage(pdfapp_t *app, int loadpage, int drawpage, int repaint, int transition) { char buf[MAX_TITLE]; diff --git a/platform/x11/pdfapp.h b/platform/x11/pdfapp.h index eabb6ca5..9c1dcd0a 100644 --- a/platform/x11/pdfapp.h +++ b/platform/x11/pdfapp.h @@ -43,6 +43,7 @@ extern void winprint(pdfapp_t *); extern void winadvancetimer(pdfapp_t *, float duration); extern void winreplacefile(char *source, char *target); extern void wincopyfile(char *source, char *target); +extern void winreloadpage(pdfapp_t *); struct pdfapp_s { @@ -51,6 +52,7 @@ struct pdfapp_s char *docpath; char *doctitle; fz_outline *outline; + int outline_deferred; int pagecount; @@ -82,6 +84,7 @@ struct pdfapp_s fz_text_sheet *page_sheet; fz_link *page_links; int errored; + int incomplete; /* snapback history */ int hist[256]; @@ -126,6 +129,9 @@ struct pdfapp_s void *userdata; fz_context *ctx; +#ifdef HAVE_CURL + fz_stream *stream; +#endif }; void pdfapp_init(fz_context *ctx, pdfapp_t *app); @@ -142,6 +148,7 @@ void pdfapp_onmouse(pdfapp_t *app, int x, int y, int btn, int modifiers, int sta void pdfapp_oncopy(pdfapp_t *app, unsigned short *ucsbuf, int ucslen); void pdfapp_onresize(pdfapp_t *app, int w, int h); void pdfapp_gotopage(pdfapp_t *app, int number); +void pdfapp_reloadpage(pdfapp_t *app); void pdfapp_invert(pdfapp_t *app, const fz_rect *rect); void pdfapp_inverthit(pdfapp_t *app); diff --git a/platform/x11/win_main.c b/platform/x11/win_main.c index 51dada02..6ddd171a 100644 --- a/platform/x11/win_main.c +++ b/platform/x11/win_main.c @@ -25,7 +25,7 @@ static HWND hwndview = NULL; static HDC hdc; static HBRUSH bgbrush; static HBRUSH shbrush; -static BITMAPINFO *dibinf; +static BITMAPINFO *dibinf = NULL; static HCURSOR arrowcurs, handcurs, waitcurs, caretcurs; static LRESULT CALLBACK frameproc(HWND, UINT, WPARAM, LPARAM); static LRESULT CALLBACK viewproc(HWND, UINT, WPARAM, LPARAM); @@ -644,11 +644,18 @@ void winopen() SetCursor(arrowcurs); } +static void +do_close(pdfapp_t *app) +{ + pdfapp_close(app); + free(dibinf); +} + void winclose(pdfapp_t *app) { if (pdfapp_preclose(app)) { - pdfapp_close(app); + do_close(app); exit(0); } } @@ -877,6 +884,11 @@ void winreloadfile(pdfapp_t *app) pdfapp_open(app, filename, 1); } +void winreloadpage(pdfapp_t *app) +{ + SendMessage(hwndview, WM_APP, 0, 0); +} + void winopenuri(pdfapp_t *app, char *buf) { ShellExecuteA(hwndframe, "open", buf, 0, 0, SW_SHOWNORMAL); @@ -1126,6 +1138,12 @@ viewproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) handlemouse(oldx, oldy, 0, 0); /* update cursor */ } return 0; + + /* We use WM_APP to trigger a reload and repaint of a page */ + case WM_APP: + pdfapp_reloadpage(&gapp); + break; + } fflush(stdout); @@ -1199,7 +1217,7 @@ WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShow DispatchMessage(&msg); } - pdfapp_close(&gapp); + do_close(&gapp); fz_free_context(ctx); return 0; diff --git a/platform/x11/x11_main.c b/platform/x11/x11_main.c index 0bcb98f4..0857046b 100644 --- a/platform/x11/x11_main.c +++ b/platform/x11/x11_main.c @@ -70,6 +70,7 @@ static Atom XA_UTF8_STRING; static Atom WM_DELETE_WINDOW; static Atom NET_WM_STATE; static Atom NET_WM_STATE_FULLSCREEN; +static Atom WM_RELOAD_PAGE; static int x11fd; static int xscr; static Window xwin; @@ -182,6 +183,7 @@ static void winopen(void) WM_DELETE_WINDOW = XInternAtom(xdpy, "WM_DELETE_WINDOW", False); NET_WM_STATE = XInternAtom(xdpy, "_NET_WM_STATE", False); NET_WM_STATE_FULLSCREEN = XInternAtom(xdpy, "_NET_WM_STATE_FULLSCREEN", False); + WM_RELOAD_PAGE = XInternAtom(xdpy, "_WM_RELOAD_PAGE", False); xscr = DefaultScreen(xdpy); @@ -652,6 +654,24 @@ void onselreq(Window requestor, Atom selection, Atom target, Atom property, Time XSendEvent(xdpy, requestor, False, 0, &nevt); } +void winreloadpage(pdfapp_t *app) +{ + XEvent xev; + Display *dpy = XOpenDisplay(NULL); + + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.window = xwin; + xev.xclient.message_type = WM_RELOAD_PAGE; + xev.xclient.format = 32; + xev.xclient.data.l[0] = 0; + xev.xclient.data.l[1] = 0; + xev.xclient.data.l[2] = 0; + XSendEvent(dpy, xwin, 0, 0, &xev); + XCloseDisplay(dpy); +} + void winreloadfile(pdfapp_t *app) { pdfapp_close(app); @@ -886,7 +906,9 @@ int main(int argc, char **argv) break; case ClientMessage: - if (xevt.xclient.format == 32 && xevt.xclient.data.l[0] == WM_DELETE_WINDOW) + if (xevt.xclient.message_type == WM_RELOAD_PAGE) + pdfapp_reloadpage(&gapp); + else if (xevt.xclient.format == 32 && xevt.xclient.data.l[0] == WM_DELETE_WINDOW) closing = 1; break; } |