aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2020-01-03 04:01:10 +0100
committerJay Berkenbilt <ejb@ql.org>2020-01-14 17:53:19 +0100
commit388990f7bcddec0583fd5a84f62d4aa0eba88d39 (patch)
tree922c0ff883b7e2e1f317305d96291d0419217fd6
parenta44b5a34a07b9f2905d419d5571fd53832c1f6c0 (diff)
downloadqpdf-388990f7bcddec0583fd5a84f62d4aa0eba88d39.tar.zst
Rewrite fix-qdf in C++
-rw-r--r--ChangeLog4
-rw-r--r--make/installwin.mk2
-rw-r--r--make/libtool.mk4
-rw-r--r--manual/qpdf-manual.xml10
-rw-r--r--qpdf/build.mk6
-rwxr-xr-xqpdf/fix-qdf397
-rw-r--r--qpdf/fix-qdf.cc511
7 files changed, 533 insertions, 401 deletions
diff --git a/ChangeLog b/ChangeLog
index a1211834..522c80a0 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
2020-01-14 Jay Berkenbilt <ejb@ql.org>
+ * Rewrite fix-qdf in C++. This means fix-qdf is a proper
+ executable now, and there is no longer a runtime requirement on
+ perl.
+
* Add QUtil::call_main_from_wmain, a helper function that can be
called in the body of wmain to convert UTF-16 arguments to UTF-8
arguments and then call another main function.
diff --git a/make/installwin.mk b/make/installwin.mk
index ff162175..3ed541ae 100644
--- a/make/installwin.mk
+++ b/make/installwin.mk
@@ -14,7 +14,7 @@ installwin: all
perl copy_dlls libqpdf/$(OUTPUT_DIR)/qpdf*.dll $(DEST)/bin $(OBJDUMP) $(WINDOWS_WORDSIZE)
cp qpdf/$(OUTPUT_DIR)/qpdf.exe $(DEST)/bin
cp zlib-flate/$(OUTPUT_DIR)/zlib-flate.exe $(DEST)/bin
- cp qpdf/fix-qdf $(DEST)/bin
+ cp qpdf/$(OUTPUT_DIR)/fix-qdf.exe $(DEST)/bin
cp include/qpdf/*.h $(DEST)/include/qpdf
cp include/qpdf/*.hh $(DEST)/include/qpdf
cp doc/stylesheet.css $(DEST)/doc
diff --git a/make/libtool.mk b/make/libtool.mk
index 0aa7b0d7..abd0aa7e 100644
--- a/make/libtool.mk
+++ b/make/libtool.mk
@@ -122,7 +122,9 @@ install: all
$(LIBTOOL) --mode=install ./install-sh \
zlib-flate/$(OUTPUT_DIR)/zlib-flate \
$(DESTDIR)$(bindir)/zlib-flate
- ./install-sh -m 0755 qpdf/fix-qdf $(DESTDIR)$(bindir)
+ $(LIBTOOL) --mode=install ./install-sh \
+ qpdf/$(OUTPUT_DIR)/fix-qdf \
+ $(DESTDIR)$(bindir)/fix-qdf
./install-sh -m 0644 include/qpdf/*.h $(DESTDIR)$(includedir)/qpdf
./install-sh -m 0644 include/qpdf/*.hh $(DESTDIR)$(includedir)/qpdf
./install-sh -m 0644 doc/stylesheet.css $(DESTDIR)$(docdir)
diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml
index fe4d20c1..88372c85 100644
--- a/manual/qpdf-manual.xml
+++ b/manual/qpdf-manual.xml
@@ -150,7 +150,8 @@
<para>
perl version 5.8 or newer:
<ulink url="http://www.perl.org/">http://www.perl.org/</ulink>;
- required for <command>fix-qdf</command> and the test suite.
+ required for running the test suite. Starting with qpdf version
+ 9.1.1, perl is no longer required at runtime.
</para>
</listitem>
<listitem>
@@ -473,6 +474,13 @@ make
<itemizedlist>
<listitem>
<para>
+ Starting in qpdf version 9.1.1, qpdf no longer has a runtime
+ dependency on perl. This is because fix-qdf was rewritten in
+ C++. However, qpdf still has a build-time dependency on perl.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
Make sure you are getting the intended behavior with regard to
crypto providers. Read <xref linkend="ref.crypto.build"/> for
details.
diff --git a/qpdf/build.mk b/qpdf/build.mk
index 44916f93..a9dd7187 100644
--- a/qpdf/build.mk
+++ b/qpdf/build.mk
@@ -1,5 +1,6 @@
BINS_qpdf = \
qpdf \
+ fix-qdf \
pdf_from_scratch \
test_driver \
test_large_file \
@@ -24,10 +25,13 @@ TC_SRCS_qpdf = $(wildcard libqpdf/*.cc) $(wildcard qpdf/*.cc)
XCXXFLAGS_qpdf_qpdf := $(WINDOWS_WMAIN_COMPILE)
XLDFLAGS_qpdf_qpdf := $(WINDOWS_WMAIN_LINK)
+XLINK_FLAGS_qpdf_qpdf := $(WINDOWS_WMAIN_XLINK_FLAGS)
XCXXFLAGS_qpdf_test_unicode_filenames := $(WINDOWS_WMAIN_COMPILE)
XLDFLAGS_qpdf_test_unicode_filenames := $(WINDOWS_WMAIN_LINK)
-XLINK_FLAGS_qpdf_qpdf := $(WINDOWS_WMAIN_XLINK_FLAGS)
XLINK_FLAGS_qpdf_test_unicode_filenames := $(WINDOWS_WMAIN_XLINK_FLAGS)
+XCXXFLAGS_qpdf_fix-qdf := $(WINDOWS_WMAIN_COMPILE)
+XLDFLAGS_qpdf_fix-qdf := $(WINDOWS_WMAIN_LINK)
+XLINK_FLAGS_qpdf_fix-qdf := $(WINDOWS_WMAIN_XLINK_FLAGS)
$(foreach B,$(BINS_qpdf),$(eval \
OBJS_$(B) = $(call src_to_obj,qpdf/$(B).cc)))
diff --git a/qpdf/fix-qdf b/qpdf/fix-qdf
deleted file mode 100755
index 2892a462..00000000
--- a/qpdf/fix-qdf
+++ /dev/null
@@ -1,397 +0,0 @@
-#!/usr/bin/env perl
-
-require 5.008_001;
-use warnings;
-use strict;
-use File::Basename;
-
-my $whoami = basename($0);
-my $dirname = dirname($0);
-
-if ((@ARGV == 1) && ($ARGV[0] eq '--version'))
-{
- exec "$dirname/qpdf", '--version';
- exit 2;
-}
-
-my $offset = 0;
-my $last_offset = 0;
-
-my $file = shift(@ARGV);
-if (defined $file)
-{
- open(F, "<$file") or die "$whoami: can't open $file: $!\n";
-}
-else
-{
- $file = 'stdin';
- open(F, "<&STDIN") or die "$whoami: can't dup stdin: $!\n";
-}
-binmode F;
-binmode STDOUT;
-
-my $line = get_line();
-if (! ((defined $line) && ($line =~ m/^%PDF-1\.\d+\b/)))
-{
- die "$whoami: $file: not a pdf file\n";
-}
-print $line;
-$line = get_line();
-die "$whoami: $file: premature EOF\n" unless defined $line;
-print $line;
-$line = get_line();
-if (! ((defined $line) && ($line =~ m/^%QDF-1.\d+\b/)))
-{
- die "$whoami: $file: not a qdf file\n";
-}
-print $line;
-
-my $last_obj = 0;
-my @xref = ();
-
-my $stream_start = 0;
-my $stream_length = 0;
-my $xref_offset = 0;
-my $xref_f1_nbytes = 0;
-my $xref_f2_nbytes = 0;
-my $xref_size = 0;
-
-my $cur_state = 0;
-my $st_top = ++$cur_state;
-my $st_in_obj = ++$cur_state;
-my $st_in_stream = ++$cur_state;
-my $st_after_stream = ++$cur_state;
-my $st_in_ostream_dict = ++$cur_state;
-my $st_in_ostream_offsets = ++$cur_state;
-my $st_in_ostream_outer = ++$cur_state;
-my $st_in_ostream_obj = ++$cur_state;
-my $st_in_xref_stream_dict = ++$cur_state;
-my $st_in_length = ++$cur_state;
-my $st_at_xref = ++$cur_state;
-my $st_before_trailer = ++$cur_state;
-my $st_in_trailer = ++$cur_state;
-my $st_done = ++$cur_state;
-
-my @ostream = ();
-my @ostream_offsets = ();
-my @ostream_discarded = ();
-my $ostream_idx = 0;
-my $ostream_id = 0;
-my $ostream_extends = "";
-
-my $state = $st_top;
-while (defined($line = get_line()))
-{
- if ($state == $st_top)
- {
- if ($line =~ m/^(\d+) 0 obj$/)
- {
- check_obj_id($1);
- $state = $st_in_obj;
- }
- elsif ($line =~ m/^xref$/)
- {
- $xref_offset = $last_offset;
- $state = $st_at_xref;
- }
- print $line;
- }
- elsif ($state == $st_in_obj)
- {
- print $line;
- if ($line =~ m/^stream$/)
- {
- $state = $st_in_stream;
- $stream_start = $offset;
- }
- elsif ($line =~ m/^endobj$/)
- {
- $state = $st_top;
- }
- elsif ($line =~ m,/Type /ObjStm,)
- {
- $state = $st_in_ostream_dict;
- $ostream_id = $last_obj;
- }
- elsif ($line =~ m,/Type /XRef,)
- {
- $xref_offset = $xref[-1][1];
- $xref_f1_nbytes = 0;
- my $t = $xref_offset;
- while ($t)
- {
- $t >>= 8;
- ++$xref_f1_nbytes;
- }
- # Figure out how many bytes we need for ostream index.
- # Make sure we get at least 1 byte even if there are no
- # object streams.
- my $max_objects = 1;
- foreach my $e (@xref)
- {
- my ($type, $f1, $f2) = @$e;
- if ((defined $f2) && ($f2 > $max_objects))
- {
- $max_objects = $f2;
- }
- }
- while ($max_objects)
- {
- $max_objects >>=8;
- ++$xref_f2_nbytes;
- }
- my $esize = 1 + $xref_f1_nbytes + $xref_f2_nbytes;
- $xref_size = 1 + @xref;
- my $length = $xref_size * $esize;
- print " /Length $length\n";
- print " /W [ 1 $xref_f1_nbytes $xref_f2_nbytes ]\n";
- $state = $st_in_xref_stream_dict;
- }
- }
- elsif ($state == $st_in_ostream_dict)
- {
- if ($line =~ m/^stream/)
- {
- $state = $st_in_ostream_offsets;
- }
- else
- {
- push(@ostream_discarded, $line);
- if ($line =~ m,/Extends (\d+ 0 R),)
- {
- $ostream_extends = $1;
- }
- }
- # discard line
- }
- elsif ($state == $st_in_ostream_offsets)
- {
- if ($line =~ m/^\%\% Object stream: object (\d+)/)
- {
- check_obj_id($1);
- $stream_start = $last_offset;
- $state = $st_in_ostream_outer;
- push(@ostream, $line);
- }
- else
- {
- push(@ostream_discarded, $line);
- }
- # discard line
- }
- elsif ($state == $st_in_ostream_outer)
- {
- adjust_ostream_xref();
- push(@ostream_offsets, $last_offset - $stream_start);
- $state = $st_in_ostream_obj;
- push(@ostream, $line);
- }
- elsif ($state == $st_in_ostream_obj)
- {
- push(@ostream, $line);
- if ($line =~ m/^\%\% Object stream: object (\d+)/)
- {
- check_obj_id($1);
- $state = $st_in_ostream_outer;
- }
- elsif ($line =~ m/^endstream/)
- {
- $stream_length = $last_offset - $stream_start;
- write_ostream();
- $state = $st_in_obj;
- }
- }
- elsif ($state == $st_in_xref_stream_dict)
- {
- if ($line =~ m,/(Length|W) ,)
- {
- # already printed
- }
- elsif ($line =~ m,/Size ,)
- {
- my $size = 1 + @xref;
- print " /Size $xref_size\n";
- }
- else
- {
- print $line;
- }
- if ($line =~ m/^stream\n/)
- {
- my $pack = "(C C$xref_f1_nbytes C$xref_f2_nbytes)";
- print pack($pack, 0, 0, 0);
- foreach my $x (@xref)
- {
- my ($type, $f1, $f2) = @$x;
- $f2 = 0 unless defined $f2;
- my @f1 = ();
- my @f2 = ();
- foreach my $d ([\@f1, $f1, $xref_f1_nbytes],
- [\@f2, $f2, $xref_f2_nbytes])
- {
- my ($fa, $f, $nbytes) = @$d;
- for (my $i = 0; $i < $nbytes; ++$i)
- {
- unshift(@$fa, $f & 0xff);
- $f >>= 8;
- }
- }
- print pack($pack, $type, @f1, @f2);
- }
- print "\nendstream\nendobj\n\n";
- print "startxref\n$xref_offset\n\%\%EOF\n";
- $state = $st_done;
- }
- }
- elsif ($state == $st_in_stream)
- {
- if ($line =~ m/^endstream$/)
- {
- $stream_length = $last_offset - $stream_start;
- $state = $st_after_stream;
- }
- print $line;
- }
- elsif ($state == $st_after_stream)
- {
- if ($line =~ m/^\%QDF: ignore_newline$/)
- {
- --$stream_length;
- }
- elsif ($line =~ m/^(\d+) 0 obj$/)
- {
- check_obj_id($1);
- $state = $st_in_length;
- }
- print $line;
- }
- elsif ($state == $st_in_length)
- {
- if ($line !~ m/^\d+$/)
- {
- die "$file:$.: expected integer\n";
- }
- my $new = "$stream_length\n";
- $offset -= length($line);
- $offset += length($new);
- print $new;
- $state = $st_top;
- }
- elsif ($state == $st_at_xref)
- {
- my $n = scalar(@xref);
- print "0 ", $n+1, "\n0000000000 65535 f \n";
- for (@xref)
- {
- my ($type, $f1, $f2) = @$_;
- printf("%010d 00000 n \n", $f1);
- }
- $state = $st_before_trailer;
- }
- elsif ($state == $st_before_trailer)
- {
- if ($line =~ m/^trailer <</)
- {
- print $line;
- $state = $st_in_trailer;
- }
- # no output
- }
- elsif ($state == $st_in_trailer)
- {
- if ($line =~ m/^ \/Size \d+$/)
- {
- print " /Size ", scalar(@xref) + 1, "\n";
- }
- else
- {
- print $line;
- }
- if ($line =~ m/^>>$/)
- {
- print "startxref\n$xref_offset\n\%\%EOF\n";
- $state = $st_done;
- }
- }
- elsif ($state == $st_done)
- {
- # ignore
- }
-}
-
-die "$whoami: $file: premature EOF\n" unless $state == $st_done;
-
-sub get_line
-{
- my $line = scalar(<F>);
- if (defined $line)
- {
- $last_offset = $offset;
- $offset += length($line);
- }
- $line;
-}
-
-sub check_obj_id
-{
- my $cur_obj = shift;
- if ($cur_obj != $last_obj + 1)
- {
- die "$file:$.: expected object ", $last_obj + 1, "\n";
- }
- $last_obj = $cur_obj;
- push(@xref, [1, $last_offset]);
-}
-
-sub adjust_ostream_xref
-{
- pop(@xref);
- push(@xref, [2, $ostream_id, $ostream_idx++]);
-}
-
-sub write_ostream
-{
- my $first = $ostream_offsets[0];
- my $onum = $ostream_id;
- my $offsets = "";
- my $n = scalar(@ostream_offsets);
- for (@ostream_offsets)
- {
- $_ -= $first;
- ++$onum;
- $offsets .= "$onum $_\n";
- }
- my $offset_adjust = length($offsets);
- $first += length($offsets);
- $stream_length += length($offsets);
- my $dict_data = "";
- $dict_data .= " /Length $stream_length\n";
- $dict_data .= " /N $n\n";
- $dict_data .= " /First $first\n";
- if ($ostream_extends)
- {
- $dict_data .= " /Extends $ostream_extends\n";
- }
- $dict_data .= ">>\n";
- $offset_adjust += length($dict_data);
- print $dict_data;
- print "stream\n";
- print $offsets;
- foreach (@ostream)
- {
- print $_;
- }
-
- for (@ostream_discarded)
- {
- $offset -= length($_);
- }
- $offset += $offset_adjust;
-
- $ostream_idx = 0;
- $ostream_id = 0;
- @ostream = ();
- @ostream_offsets = ();
- @ostream_discarded = ();
- $ostream_extends = "";
-}
diff --git a/qpdf/fix-qdf.cc b/qpdf/fix-qdf.cc
new file mode 100644
index 00000000..2516a0bd
--- /dev/null
+++ b/qpdf/fix-qdf.cc
@@ -0,0 +1,511 @@
+#include <qpdf/QUtil.hh>
+#include <qpdf/QPDF.hh>
+#include <qpdf/QPDFXRefEntry.hh>
+#include <qpdf/QIntC.hh>
+#include <cstdio>
+#include <iostream>
+#include <cstring>
+#include <regex>
+
+static char const* whoami = 0;
+
+static void usage()
+{
+ std::cerr << "Usage: " << whoami << " [filename]" << std::endl;
+ exit(2);
+}
+
+class QdfFixer
+{
+ public:
+ QdfFixer(std::string const& filename);
+ void processLines(std::list<std::string>& lines);
+ private:
+ void fatal(std::string const&);
+ void checkObjId(std::string const& obj_id);
+ void adjustOstreamXref();
+ void writeOstream();
+ void writeBinary(unsigned long long val, size_t bytes);
+
+ std::string filename;
+ enum {
+ st_top,
+ st_in_obj,
+ st_in_stream,
+ st_after_stream,
+ st_in_ostream_dict,
+ st_in_ostream_offsets,
+ st_in_ostream_outer,
+ st_in_ostream_obj,
+ st_in_xref_stream_dict,
+ st_in_length,
+ st_at_xref,
+ st_before_trailer,
+ st_in_trailer,
+ st_done,
+ } state;
+
+ size_t lineno;
+ qpdf_offset_t offset;
+ qpdf_offset_t last_offset;
+ int last_obj;
+ std::vector<QPDFXRefEntry> xref;
+ qpdf_offset_t stream_start;
+ size_t stream_length;
+ qpdf_offset_t xref_offset;
+ size_t xref_f1_nbytes;
+ size_t xref_f2_nbytes;
+ size_t xref_size;
+ std::vector<std::string> ostream;
+ std::vector<qpdf_offset_t> ostream_offsets;
+ std::vector<std::string> ostream_discarded;
+ size_t ostream_idx;
+ int ostream_id;
+ std::string ostream_extends;
+};
+
+QdfFixer::QdfFixer(std::string const& filename) :
+ filename(filename),
+ state(st_top),
+ lineno(0),
+ offset(0),
+ last_offset(0),
+ last_obj(0),
+ stream_start(0),
+ stream_length(0),
+ xref_offset(0),
+ xref_f1_nbytes(0),
+ xref_f2_nbytes(0),
+ xref_size(0),
+ ostream_idx(0),
+ ostream_id(0)
+{
+}
+
+void
+QdfFixer::fatal(std::string const& msg)
+{
+ std::cerr << msg << std::endl;
+ exit(2);
+}
+
+void
+QdfFixer::processLines(std::list<std::string>& lines)
+{
+ static std::regex re_n_0_obj("^(\\d+) 0 obj\n$");
+ static std::regex re_xref("^xref\n$");
+ static std::regex re_stream("^stream\n$");
+ static std::regex re_endobj("^endobj\n$");
+ static std::regex re_type_objstm("/Type /ObjStm");
+ static std::regex re_type_xref("/Type /XRef");
+ static std::regex re_extends("/Extends (\\d+ 0 R)");
+ static std::regex re_ostream_obj("^%% Object stream: object (\\d+)");
+ static std::regex re_endstream("^endstream\n$");
+ static std::regex re_length_or_w("/(Length|W) ");
+ static std::regex re_size("/Size ");
+ static std::regex re_ignore_newline("^%QDF: ignore_newline\n$");
+ static std::regex re_num("^\\d+\n$");
+ static std::regex re_trailer("^trailer <<");
+ static std::regex re_size_n("^ /Size \\d+\n$");
+ static std::regex re_dict_end("^>>\n$");
+
+ lineno = 0;
+ for (auto line: lines)
+ {
+ ++lineno;
+ last_offset = offset;
+ offset += QIntC::to_offset(line.length());
+ std::smatch m;
+ auto matches = [&m, &line](std::regex& r){
+ return std::regex_search(line, m, r); };
+ if (state == st_top)
+ {
+ if (matches(re_n_0_obj))
+ {
+ checkObjId(m[1].str());
+ state = st_in_obj;
+ }
+ else if (matches(re_xref))
+ {
+ xref_offset = last_offset;
+ state = st_at_xref;
+ }
+ std::cout << line;
+ }
+ else if (state == st_in_obj)
+ {
+ std::cout << line;
+ if (matches(re_stream))
+ {
+ state = st_in_stream;
+ stream_start = offset;
+ }
+ else if (matches(re_endobj))
+ {
+ state = st_top;
+ }
+ else if (matches(re_type_objstm))
+ {
+ state = st_in_ostream_dict;
+ ostream_id = last_obj;
+ }
+ else if (matches(re_type_xref))
+ {
+ xref_offset = xref.back().getOffset();
+ xref_f1_nbytes = 0;
+ auto t = xref_offset;
+ while (t)
+ {
+ t >>= 8;
+ ++xref_f1_nbytes;
+ }
+ // Figure out how many bytes we need for ostream
+ // index. Make sure we get at least 1 byte even if
+ // there are no object streams.
+ int max_objects = 1;
+ for (auto e: xref)
+ {
+ if ((e.getType() == 2) &&
+ (e.getObjStreamIndex() > max_objects))
+ {
+ max_objects = e.getObjStreamIndex();
+ }
+ }
+ while (max_objects)
+ {
+ max_objects >>=8;
+ ++xref_f2_nbytes;
+ }
+ auto esize = 1 + xref_f1_nbytes + xref_f2_nbytes;
+ xref_size = 1 + xref.size();
+ auto length = xref_size * esize;
+ std::cout << " /Length " << length << "\n"
+ << " /W [ 1 " << xref_f1_nbytes << " "
+ << xref_f2_nbytes << " ]" << "\n";
+ state = st_in_xref_stream_dict;
+ }
+ }
+ else if (state == st_in_ostream_dict)
+ {
+ if (matches(re_stream))
+ {
+ state = st_in_ostream_offsets;
+ }
+ else
+ {
+ ostream_discarded.push_back(line);
+ if (matches(re_extends))
+ {
+ ostream_extends = m[1].str();
+ }
+ }
+ // discard line
+ }
+ else if (state == st_in_ostream_offsets)
+ {
+ if (matches(re_ostream_obj))
+ {
+ checkObjId(m[1].str());
+ stream_start = last_offset;
+ state = st_in_ostream_outer;
+ ostream.push_back(line);
+ }
+ else
+ {
+ ostream_discarded.push_back(line);
+ }
+ // discard line
+ }
+ else if (state == st_in_ostream_outer)
+ {
+ adjustOstreamXref();
+ ostream_offsets.push_back(last_offset - stream_start);
+ state = st_in_ostream_obj;
+ ostream.push_back(line);
+ }
+ else if (state == st_in_ostream_obj)
+ {
+ ostream.push_back(line);
+ if (matches(re_ostream_obj))
+ {
+ checkObjId(m[1].str());
+ state = st_in_ostream_outer;
+ }
+ else if (matches(re_endstream))
+ {
+ stream_length = QIntC::to_size(last_offset - stream_start);
+ writeOstream();
+ state = st_in_obj;
+ }
+ }
+ else if (state == st_in_xref_stream_dict)
+ {
+ if (matches(re_length_or_w))
+ {
+ // already printed
+ }
+ else if (matches(re_size))
+ {
+ auto xref_size = 1 + xref.size();
+ std::cout << " /Size " << xref_size << "\n";
+ }
+ else
+ {
+ std::cout << line;
+ }
+ if (matches(re_stream))
+ {
+ writeBinary(0, 1);
+ writeBinary(0, xref_f1_nbytes);
+ writeBinary(0, xref_f2_nbytes);
+ for (auto x: xref)
+ {
+ unsigned long long f1 = 0;
+ unsigned long long f2 = 0;
+ unsigned int type = QIntC::to_uint(x.getType());
+ if (1 == type)
+ {
+ f1 = QIntC::to_ulonglong(x.getOffset());
+ }
+ else
+ {
+ f1 = QIntC::to_ulonglong(x.getObjStreamNumber());
+ f2 = QIntC::to_ulonglong(x.getObjStreamIndex());
+ }
+ writeBinary(type, 1);
+ writeBinary(f1, xref_f1_nbytes);
+ writeBinary(f2, xref_f2_nbytes);
+ }
+ std::cout << "\nendstream\nendobj\n\n"
+ << "startxref\n" << xref_offset << "\n%%EOF\n";
+ state = st_done;
+ }
+ }
+ else if (state == st_in_stream)
+ {
+ if (matches(re_endstream))
+ {
+ stream_length = QIntC::to_size(last_offset - stream_start);
+ state = st_after_stream;
+ }
+ std::cout << line;
+ }
+ else if (state == st_after_stream)
+ {
+ if (matches(re_ignore_newline))
+ {
+ --stream_length;
+ }
+ else if (matches(re_n_0_obj))
+ {
+ checkObjId(m[1].str());
+ state = st_in_length;
+ }
+ std::cout << line;
+ }
+ else if (state == st_in_length)
+ {
+ if (! matches(re_num))
+ {
+ fatal(filename + ":" + QUtil::uint_to_string(lineno) +
+ ": expected integer");
+ }
+ std::string new_length =
+ QUtil::uint_to_string(stream_length) + "\n";
+ offset -= QIntC::to_offset(line.length());
+ offset += QIntC::to_offset(new_length.length());
+ std::cout << new_length;
+ state = st_top;
+ }
+ else if (state == st_at_xref)
+ {
+ auto n = xref.size();
+ std::cout << "0 " << 1 + n << "\n0000000000 65535 f \n";
+ for (auto e: xref)
+ {
+ std::cout << QUtil::int_to_string(e.getOffset(), 10)
+ << " 00000 n \n";
+ }
+ state = st_before_trailer;
+ }
+ else if (state == st_before_trailer)
+ {
+ if (matches(re_trailer))
+ {
+ std::cout << line;
+ state = st_in_trailer;
+ }
+ // no output
+ }
+ else if (state == st_in_trailer)
+ {
+ if (matches(re_size_n))
+ {
+ std::cout << " /Size " << 1 + xref.size() << "\n";
+ }
+ else
+ {
+ std::cout << line;
+ }
+ if (matches(re_dict_end))
+ {
+ std::cout << "startxref\n" << xref_offset<< "\n%%EOF\n";
+ state = st_done;
+ }
+ }
+ else if (state == st_done)
+ {
+ // ignore
+ }
+ }
+}
+
+void
+QdfFixer::checkObjId(std::string const& cur_obj_str)
+{
+ int cur_obj = QUtil::string_to_int(cur_obj_str.c_str());
+ if (cur_obj != last_obj + 1)
+ {
+ fatal(filename + ":" + QUtil::uint_to_string(lineno) +
+ ": expected object " + QUtil::int_to_string(last_obj + 1));
+ }
+ last_obj = cur_obj;
+ xref.push_back(QPDFXRefEntry(1, QIntC::to_offset(last_offset), 0));
+}
+
+void
+QdfFixer::adjustOstreamXref()
+{
+ xref.pop_back();
+ xref.push_back(QPDFXRefEntry(2, ostream_id, QIntC::to_int(ostream_idx++)));
+}
+
+void
+QdfFixer::writeOstream()
+{
+ auto first = ostream_offsets.at(0);
+ auto onum = ostream_id;
+ std::string offsets;
+ auto n = ostream_offsets.size();
+ for (auto iter = ostream_offsets.begin();
+ iter != ostream_offsets.end(); ++iter)
+ {
+ (*iter) -= QIntC::to_offset(first);
+ ++onum;
+ offsets += QUtil::int_to_string(onum) + " " +
+ QUtil::int_to_string(*iter) + "\n";
+ }
+ auto offset_adjust = QIntC::to_offset(offsets.size());
+ first += offset_adjust;
+ stream_length += QIntC::to_size(offset_adjust);
+ std::string dict_data = "";
+ dict_data += " /Length " + QUtil::uint_to_string(stream_length) + "\n";
+ dict_data += " /N " + QUtil::uint_to_string(n) + "\n";
+ dict_data += " /First " + QUtil::int_to_string(first) + "\n";
+ if (! ostream_extends.empty())
+ {
+ dict_data += " /Extends " + ostream_extends + "\n";
+ }
+ dict_data += ">>\n";
+ offset_adjust += QIntC::to_offset(dict_data.length());
+ std::cout << dict_data
+ << "stream\n"
+ << offsets;
+ for (auto o: ostream)
+ {
+ std::cout << o;
+ }
+
+ for (auto o: ostream_discarded)
+ {
+ offset -= QIntC::to_offset(o.length());
+ }
+ offset += offset_adjust;
+
+ ostream_idx = 0;
+ ostream_id = 0;
+ ostream.clear();
+ ostream_offsets.clear();
+ ostream_discarded.clear();
+ ostream_extends.clear();
+}
+
+void
+QdfFixer::writeBinary(unsigned long long val, size_t bytes)
+{
+ if (bytes > sizeof(unsigned long long))
+ {
+ throw std::logic_error(
+ "fix-qdf::writeBinary called with too many bytes");
+ }
+ std::string data;
+ data.reserve(bytes);
+ for (size_t i = 0; i < bytes; ++i)
+ {
+ data.append(1, '\0');
+ }
+ for (size_t i = 0; i < bytes; ++i)
+ {
+ data.at(bytes - i - 1) =
+ static_cast<char>(QIntC::to_uchar(val & 0xff));
+ val >>= 8;
+ }
+ std::cout << data;
+}
+
+static int realmain(int argc, char* argv[])
+{
+ whoami = QUtil::getWhoami(argv[0]);
+ QUtil::setLineBuf(stdout);
+ char const* filename = 0;
+ if (argc > 2)
+ {
+ usage();
+ }
+ else if ((argc > 1) && (strcmp(argv[1], "--version") == 0))
+ {
+ std::cout << whoami << " from qpdf version "
+ << QPDF::QPDFVersion() << std::endl;
+ return 0;
+ }
+ else if ((argc > 1) && (strcmp(argv[1], "--help") == 0))
+ {
+ usage();
+ }
+ else if (argc == 2)
+ {
+ filename = argv[1];
+ }
+ std::list<std::string> lines;
+ if (filename == 0)
+ {
+ filename = "standard input";
+ QUtil::binary_stdin();
+ lines = QUtil::read_lines_from_file(stdin, true);
+ }
+ else
+ {
+ lines = QUtil::read_lines_from_file(filename, true);
+ }
+ QUtil::binary_stdout();
+ QdfFixer qf(filename);
+ qf.processLines(lines);
+ return 0;
+}
+
+#ifdef WINDOWS_WMAIN
+
+extern "C"
+int wmain(int argc, wchar_t* argv[])
+{
+ return QUtil::call_main_from_wmain(argc, argv, realmain);
+}
+
+#else
+
+int main(int argc, char* argv[])
+{
+ return realmain(argc, argv);
+}
+
+#endif