mirror of
https://github.com/qpdf/qpdf.git
synced 2025-01-02 22:50:20 +00:00
Rewrite fix-qdf in C++
This commit is contained in:
parent
a44b5a34a0
commit
388990f7bc
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
@ -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:
|
||||
<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
|
||||
|
@ -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)))
|
||||
|
397
qpdf/fix-qdf
397
qpdf/fix-qdf
@ -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
511
qpdf/fix-qdf.cc
Normal 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
|
Loading…
Reference in New Issue
Block a user