From 388990f7bcddec0583fd5a84f62d4aa0eba88d39 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Thu, 2 Jan 2020 22:01:10 -0500 Subject: [PATCH] Rewrite fix-qdf in C++ --- ChangeLog | 4 + make/installwin.mk | 2 +- make/libtool.mk | 4 +- manual/qpdf-manual.xml | 10 +- qpdf/build.mk | 6 +- qpdf/fix-qdf | 397 -------------------------------- qpdf/fix-qdf.cc | 511 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 533 insertions(+), 401 deletions(-) delete mode 100755 qpdf/fix-qdf create mode 100644 qpdf/fix-qdf.cc diff --git a/ChangeLog b/ChangeLog index a1211834..522c80a0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ 2020-01-14 Jay Berkenbilt + * 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 @@ perl version 5.8 or newer: http://www.perl.org/; - required for fix-qdf and the test suite. + required for running the test suite. Starting with qpdf version + 9.1.1, perl is no longer required at runtime. @@ -471,6 +472,13 @@ make If you are packaging qpdf for an operating system distribution, here are some things you may want to keep in mind: + + + 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. + + Make sure you are getting the intended behavior with regard to 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 "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(); - 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 +#include +#include +#include +#include +#include +#include +#include + +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& 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 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 ostream; + std::vector ostream_offsets; + std::vector 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& 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(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 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