Skip to content

Commit

Permalink
QUtil::double_to_string: trim trailing zeroes with option to disable
Browse files Browse the repository at this point in the history
  • Loading branch information
jberkenbilt committed Feb 13, 2021
1 parent 8fbc857 commit 07f40bd
Show file tree
Hide file tree
Showing 55 changed files with 131 additions and 30 deletions.
12 changes: 12 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
2021-02-12 Jay Berkenbilt <[email protected]>

* QUtil::double_to_string: trim trailing zeroes by default, and
add option to not trim trailing zeroes. This causes a syntactic
but semantically preserving change in output when doubles are
converted to strings. The library uses double_to_string in only a
few places. In practice, output will be different (trailing zeroes
removed) in code that creates form XObjects (mostly generation of
appearance streams for form fields as well as overlay and
underlay) and in the flatten rotation code that was added in qpdf
10.1.

2021-02-10 Jay Berkenbilt <[email protected]>

* Require a C++-14 compiler.
Expand Down
Binary file modified examples/qtest/double-page-size/out.pdf
Binary file not shown.
Binary file modified examples/qtest/filter-tokens/a.pdf
Binary file not shown.
Binary file modified examples/qtest/filter-tokens/out.pdf
Binary file not shown.
Binary file modified examples/qtest/overlay-page/out.pdf
Binary file not shown.
Binary file modified examples/qtest/set-form-values/form-out.pdf
Binary file not shown.
5 changes: 5 additions & 0 deletions include/qpdf/QPDFObjectHandle.hh
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,11 @@ class QPDFObjectHandle
static QPDFObjectHandle newReal(std::string const& value);
QPDF_DLL
static QPDFObjectHandle newReal(double value, int decimal_places = 0);
// ABI: combine with other newReal by adding trim_trailing_zeroes
// above as an optional parameter with a default of true.
QPDF_DLL
static QPDFObjectHandle newReal(double value, int decimal_places,
bool trim_trailing_zeroes);
QPDF_DLL
static QPDFObjectHandle newName(std::string const& name);
QPDF_DLL
Expand Down
9 changes: 8 additions & 1 deletion include/qpdf/QUtil.hh
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,16 @@ namespace QUtil
QPDF_DLL
std::string int_to_string_base(long long, int base, int length = 0);
QPDF_DLL
std::string uint_to_string_base(unsigned long long, int base, int length = 0);
std::string uint_to_string_base(unsigned long long, int base,
int length = 0);
QPDF_DLL
std::string double_to_string(double, int decimal_places = 0);
// ABI: combine with other double_to_string by adding
// trim_trailing_zeroes above as an optional parameter with a
// default of true.
QPDF_DLL
std::string double_to_string(double, int decimal_places,
bool trim_trailing_zeroes);

// These string to number methods throw std::runtime_error on
// underflow/overflow.
Expand Down
11 changes: 10 additions & 1 deletion libqpdf/QPDFObjectHandle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2422,7 +2422,16 @@ QPDFObjectHandle::newReal(std::string const& value)
QPDFObjectHandle
QPDFObjectHandle::newReal(double value, int decimal_places)
{
return QPDFObjectHandle(new QPDF_Real(value, decimal_places));
return QPDFObjectHandle(
new QPDF_Real(value, decimal_places, true));
}

QPDFObjectHandle
QPDFObjectHandle::newReal(double value, int decimal_places,
bool trim_trailing_zeroes)
{
return QPDFObjectHandle(
new QPDF_Real(value, decimal_places, trim_trailing_zeroes));
}

QPDFObjectHandle
Expand Down
5 changes: 3 additions & 2 deletions libqpdf/QPDF_Real.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ QPDF_Real::QPDF_Real(std::string const& val) :
{
}

QPDF_Real::QPDF_Real(double value, int decimal_places) :
val(QUtil::double_to_string(value, decimal_places))
QPDF_Real::QPDF_Real(double value, int decimal_places,
bool trim_trailing_zeroes) :
val(QUtil::double_to_string(value, decimal_places, trim_trailing_zeroes))
{
}

Expand Down
25 changes: 22 additions & 3 deletions libqpdf/QUtil.cc
Original file line number Diff line number Diff line change
Expand Up @@ -323,19 +323,38 @@ QUtil::uint_to_string_base(unsigned long long num, int base, int length)

std::string
QUtil::double_to_string(double num, int decimal_places)
{
return double_to_string(num, decimal_places, true);
}

std::string
QUtil::double_to_string(double num, int decimal_places,
bool trim_trailing_zeroes)
{
// Backward compatibility -- this code used to use sprintf and
// treated decimal_places <= 0 to mean to use the default, which
// was six decimal places. Also sprintf with %*.f interprets the
// length as fixed point rather than significant figures.
// was six decimal places. Starting in 10.2, we trim trailing
// zeroes by default.
if (decimal_places <= 0)
{
decimal_places = 6;
}
std::ostringstream buf;
buf.imbue(std::locale::classic());
buf << std::setprecision(decimal_places) << std::fixed << num;
return buf.str();
std::string result = buf.str();
if (trim_trailing_zeroes)
{
while ((result.length() > 1) && (result.back() == '0'))
{
result.pop_back();
}
if ((result.length() > 1) && (result.back() == '.'))
{
result.pop_back();
}
}
return result;
}

long long
Expand Down
2 changes: 1 addition & 1 deletion libqpdf/qpdf/QPDF_Real.hh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class QPDF_Real: public QPDFObject
{
public:
QPDF_Real(std::string const& val);
QPDF_Real(double value, int decimal_places = 0);
QPDF_Real(double value, int decimal_places, bool trim_trailing_zeroes);
virtual ~QPDF_Real();
virtual std::string unparse();
virtual JSON getJSON();
Expand Down
4 changes: 2 additions & 2 deletions libtests/json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ static void test_main()
" \"a\\tb\",\n"
" null,\n"
" 16059,\n"
" 3.141590,\n"
" 3.14159,\n"
" 2.1e5\n"
"]");
JSON jmap = JSON::makeDictionary();
Expand All @@ -56,7 +56,7 @@ static void test_main()
" \"a\\tb\",\n"
" null,\n"
" 16059,\n"
" 3.141590,\n"
" 3.14159,\n"
" 2.1e5\n"
" ],\n"
" \"b\": \"a\\tb\",\n"
Expand Down
28 changes: 14 additions & 14 deletions libtests/matrix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,39 +44,39 @@ static void check_rect(QPDFObjectHandle::Rectangle const& r,
int main()
{
QPDFMatrix m;
check(m, "1.00000 0.00000 0.00000 1.00000 0.00000 0.00000");
check(m, "1 0 0 1 0 0");
m.translate(10, 20);
check(m, "1.00000 0.00000 0.00000 1.00000 10.00000 20.00000");
check(m, "1 0 0 1 10 20");
m.scale(1.5, 2);
check(m, "1.50000 0.00000 0.00000 2.00000 10.00000 20.00000");
check(m, "1.5 0 0 2 10 20");
double xp = 0;
double yp = 0;
m.transform(10, 100, xp, yp);
check_xy(xp, yp, "25.00 220.00");
check_xy(xp, yp, "25 220");
m.translate(30, 40);
check(m, "1.50000 0.00000 0.00000 2.00000 55.00000 100.00000");
check(m, "1.5 0 0 2 55 100");
m.transform(10, 100, xp, yp);
check_xy(xp, yp, "70.00 300.00");
check_xy(xp, yp, "70 300");
m.concat(QPDFMatrix(1, 2, 3, 4, 5, 6));
check(m, "1.50000 4.00000 4.50000 8.00000 62.50000 112.00000");
check(m, "1.5 4 4.5 8 62.5 112");
m.rotatex90(90);
check(m, "4.50000 8.00000 -1.50000 -4.00000 62.50000 112.00000");
check(m, "4.5 8 -1.5 -4 62.5 112");
m.rotatex90(180);
check(m, "-4.50000 -8.00000 1.50000 4.00000 62.50000 112.00000");
check(m, "-4.5 -8 1.5 4 62.5 112");
m.rotatex90(270);
check(m, "-1.50000 -4.00000 -4.50000 -8.00000 62.50000 112.00000");
check(m, "-1.5 -4 -4.5 -8 62.5 112");
m.rotatex90(180);
check(m, "1.50000 4.00000 4.50000 8.00000 62.50000 112.00000");
check(m, "1.5 4 4.5 8 62.5 112");
m.rotatex90(12345);
check(m, "1.50000 4.00000 4.50000 8.00000 62.50000 112.00000");
check(m, "1.5 4 4.5 8 62.5 112");

m.transform(240, 480, xp, yp);
check_xy(xp, yp, "2582.50 4912.00");
check_xy(xp, yp, "2582.5 4912");

check(QPDFMatrix(
QPDFObjectHandle::parse(
"[3 1 4 1 5 9.26535]").getArrayAsMatrix()),
"3.00000 1.00000 4.00000 1.00000 5.00000 9.26535");
"3 1 4 1 5 9.26535");

m = QPDFMatrix();
m.rotatex90(90);
Expand Down
6 changes: 6 additions & 0 deletions libtests/qtest/qutil/qutil.out
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
0.00012
0.12346
0.00012
1.0102
1
1
1.00000
10.00
10
16059
37273
3ebb
Expand Down
12 changes: 9 additions & 3 deletions libtests/qutil.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,19 @@ void string_conversion_test()
std::cout << QUtil::int_to_string(16059) << std::endl
<< QUtil::int_to_string(16059, 7) << std::endl
<< QUtil::int_to_string(16059, -7) << std::endl
<< QUtil::double_to_string(3.14159) << std::endl
<< QUtil::double_to_string(3.14159, 0, false) << std::endl
<< QUtil::double_to_string(3.14159, 3) << std::endl
<< QUtil::double_to_string(1000.123, -1024) << std::endl
<< QUtil::double_to_string(.1234, 5) << std::endl
<< QUtil::double_to_string(1000.123, -1024, false) << std::endl
<< QUtil::double_to_string(.1234, 5, false) << std::endl
<< QUtil::double_to_string(.0001234, 5) << std::endl
<< QUtil::double_to_string(.123456, 5) << std::endl
<< QUtil::double_to_string(.000123456, 5) << std::endl
<< QUtil::double_to_string(1.01020, 5, true) << std::endl
<< QUtil::double_to_string(1.00000, 5, true) << std::endl
<< QUtil::double_to_string(1, 5, true) << std::endl
<< QUtil::double_to_string(1, 5, false) << std::endl
<< QUtil::double_to_string(10, 2, false) << std::endl
<< QUtil::double_to_string(10, 2, true) << std::endl
<< QUtil::int_to_string_base(16059, 10) << std::endl
<< QUtil::int_to_string_base(16059, 8) << std::endl
<< QUtil::int_to_string_base(16059, 16) << std::endl
Expand Down
38 changes: 37 additions & 1 deletion manual/qpdf-manual.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5068,6 +5068,21 @@ print "\n";
Library Behavior Changes
</para>
<itemizedlist>
<listitem>
<para>
Note: the changes in this section cause differences in
output in some cases. These differences change the syntax of
the PDF but do not change the semantics (meaning). I make a
strong effort to avoid gratuitous changes in qpdf's output
so that qpdf changes don't break people's tests. In this
case, the changes significantly improve the readability of
the generated PDF and don't affect any output that's
generated by simple transformation. If you are annoyed by
having to update test files, please rest assured that
changes like this have been and will continue to be rare
events.
</para>
</listitem>
<listitem>
<para>
<function>QPDFObjectHandle::newUnicodeString</function> now
Expand All @@ -5076,7 +5091,19 @@ print "\n";
reduces needless encoding in UTF-16 of strings that can be
encoded in ASCII. This change may cause qpdf to generate
different output than before when form field values are set
using <classname>QPDFFormFieldObjectHelper</classname>.
using <classname>QPDFFormFieldObjectHelper</classname> but
does not change the meaning of the output.
</para>
</listitem>
<listitem>
<para>
The code that places form XObjects and also the code that
flattens rotations trim trailing zeroes from real numbers
that they calculate. This causes slight (but semantically
equivalent) differences in generated appearance streams and
form XObject invocations in overlay/underlay code or in user
code that calls the methods that place form XObjects on a
page.
</para>
</listitem>
</itemizedlist>
Expand Down Expand Up @@ -5184,6 +5211,15 @@ print "\n";
contents of a file through a pipeline as binary data.
</para>
</listitem>
<listitem>
<para>
Add option to <function>QUtil::double_to_string</function>
to trim trailing zeroes, which is on by default. Within the
qpdf library, this causes changes to output the from code
that places form XObjects and the code that flattens
rotations.
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
Expand Down
2 changes: 1 addition & 1 deletion qpdf/qpdf-ctest.c
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ static void test24(char const* infile,
qpdf_oh_new_name(qpdf, "/Quack"));
qpdf_oh_append_item(
qpdf, new_array,
qpdf_oh_new_real_from_double(qpdf, 4.0, 2));
qpdf_oh_new_real_from_double(qpdf, 4.123, 2));
qpdf_oh_append_item(
qpdf, new_array,
qpdf_oh_new_real_from_string(qpdf, "5.0"));
Expand Down
Binary file modified qpdf/qtest/qpdf/appearances-1.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/appearances-11.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/appearances-12.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/appearances-2.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/appearances-a-more.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/appearances-a-more2.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/appearances-a.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/appearances-b.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/appearances-quack.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/boxes-flattened.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/c-object-handles-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/comment-annotation-direct-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/comment-annotation-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/form-filled-by-acrobat-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/form-xobjects-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/fx-overlay-56.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/fx-overlay-57.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/fx-overlay-58.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/fx-overlay-59.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/fx-overlay-64.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/fx-overlay-65.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/fx-overlay-66.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/fx-overlay-67.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/manual-appearances-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/manual-appearances-print-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/manual-appearances-screen-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/need-appearances-more-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/need-appearances-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/sample-form-out.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/uo-1.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/uo-2.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/uo-3.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/uo-4.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/uo-5.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/uo-6.pdf
Binary file not shown.
Binary file modified qpdf/qtest/qpdf/uo-7.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion qpdf/test_driver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ void runtest(int n, char const* filename1, char const* arg2)
for (int i = 0; i < nitems; ++i)
{
std::cout << QUtil::double_to_string(
qnumbers.getArrayItem(i).getNumericValue(), 3)
qnumbers.getArrayItem(i).getNumericValue(), 3, false)
<< std::endl;
}
}
Expand Down

0 comments on commit 07f40bd

Please sign in to comment.