Rewrite fix-qdf in C++

This commit is contained in:
Jay Berkenbilt 2020-01-02 22:01:10 -05:00
parent a44b5a34a0
commit 388990f7bc
7 changed files with 533 additions and 401 deletions

View File

@ -1,5 +1,9 @@
2020-01-14 Jay Berkenbilt <ejb@ql.org> 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 * 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 called in the body of wmain to convert UTF-16 arguments to UTF-8
arguments and then call another main function. arguments and then call another main function.

View File

@ -14,7 +14,7 @@ installwin: all
perl copy_dlls libqpdf/$(OUTPUT_DIR)/qpdf*.dll $(DEST)/bin $(OBJDUMP) $(WINDOWS_WORDSIZE) perl copy_dlls libqpdf/$(OUTPUT_DIR)/qpdf*.dll $(DEST)/bin $(OBJDUMP) $(WINDOWS_WORDSIZE)
cp qpdf/$(OUTPUT_DIR)/qpdf.exe $(DEST)/bin cp qpdf/$(OUTPUT_DIR)/qpdf.exe $(DEST)/bin
cp zlib-flate/$(OUTPUT_DIR)/zlib-flate.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/*.h $(DEST)/include/qpdf
cp include/qpdf/*.hh $(DEST)/include/qpdf cp include/qpdf/*.hh $(DEST)/include/qpdf
cp doc/stylesheet.css $(DEST)/doc cp doc/stylesheet.css $(DEST)/doc

View File

@ -122,7 +122,9 @@ install: all
$(LIBTOOL) --mode=install ./install-sh \ $(LIBTOOL) --mode=install ./install-sh \
zlib-flate/$(OUTPUT_DIR)/zlib-flate \ zlib-flate/$(OUTPUT_DIR)/zlib-flate \
$(DESTDIR)$(bindir)/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/*.h $(DESTDIR)$(includedir)/qpdf
./install-sh -m 0644 include/qpdf/*.hh $(DESTDIR)$(includedir)/qpdf ./install-sh -m 0644 include/qpdf/*.hh $(DESTDIR)$(includedir)/qpdf
./install-sh -m 0644 doc/stylesheet.css $(DESTDIR)$(docdir) ./install-sh -m 0644 doc/stylesheet.css $(DESTDIR)$(docdir)

View File

@ -150,7 +150,8 @@
<para> <para>
perl version 5.8 or newer: perl version 5.8 or newer:
<ulink url="http://www.perl.org/">http://www.perl.org/</ulink>; <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> </para>
</listitem> </listitem>
<listitem> <listitem>
@ -471,6 +472,13 @@ make
If you are packaging qpdf for an operating system distribution, If you are packaging qpdf for an operating system distribution,
here are some things you may want to keep in mind: here are some things you may want to keep in mind:
<itemizedlist> <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> <listitem>
<para> <para>
Make sure you are getting the intended behavior with regard to Make sure you are getting the intended behavior with regard to

View File

@ -1,5 +1,6 @@
BINS_qpdf = \ BINS_qpdf = \
qpdf \ qpdf \
fix-qdf \
pdf_from_scratch \ pdf_from_scratch \
test_driver \ test_driver \
test_large_file \ test_large_file \
@ -24,10 +25,13 @@ TC_SRCS_qpdf = $(wildcard libqpdf/*.cc) $(wildcard qpdf/*.cc)
XCXXFLAGS_qpdf_qpdf := $(WINDOWS_WMAIN_COMPILE) XCXXFLAGS_qpdf_qpdf := $(WINDOWS_WMAIN_COMPILE)
XLDFLAGS_qpdf_qpdf := $(WINDOWS_WMAIN_LINK) XLDFLAGS_qpdf_qpdf := $(WINDOWS_WMAIN_LINK)
XLINK_FLAGS_qpdf_qpdf := $(WINDOWS_WMAIN_XLINK_FLAGS)
XCXXFLAGS_qpdf_test_unicode_filenames := $(WINDOWS_WMAIN_COMPILE) XCXXFLAGS_qpdf_test_unicode_filenames := $(WINDOWS_WMAIN_COMPILE)
XLDFLAGS_qpdf_test_unicode_filenames := $(WINDOWS_WMAIN_LINK) 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) 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 \ $(foreach B,$(BINS_qpdf),$(eval \
OBJS_$(B) = $(call src_to_obj,qpdf/$(B).cc))) OBJS_$(B) = $(call src_to_obj,qpdf/$(B).cc)))

View File

@ -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 = "";
}

511
qpdf/fix-qdf.cc Normal file
View File

@ -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