blob: d62d463a1f4801a8dcb62612c10ef5372f2573a3 [file]
// Copyright 2019 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h"
#include <array>
#include <cmath>
#include <limits>
#include <ostream>
#include "core/fxcrt/span.h"
#include "third_party/dragonbox/src/include/dragonbox/dragonbox.h"
namespace {
constexpr unsigned kMaximumFloatToDecimalLength = 49;
// Convert a float into a decimal string.
//
// The resulting string will be in the form `[-]?([0-9]*\.)?[0-9]+` (it does
// not use scientific notation).
//
// INFINITY and -INFINITY are rounded to FLT_MAX and -FLT_MAX.
//
// NAN values are converted to 0.
//
// This function will always add a terminating '\0' to the output.
unsigned FloatToDecimal(
float value,
pdfium::span<char, kMaximumFloatToDecimalLength> output) {
// The longest result is -FLT_MIN.
// Serialize it as "-.0000000000000000000000000000000000000117549435"
// which has 48 characters plus a terminating '\0'.
static_assert(kMaximumFloatToDecimalLength == 49, "");
static_assert(kMaximumFloatToDecimalLength ==
3 + 9 - std::numeric_limits<float>::min_exponent10,
"");
size_t out_idx = 0;
if (std::isnan(value) || value == 0.0f) {
// NAN is unsupported in PDF. Always output a valid number.
// Also catch zero here, as a special case.
output[out_idx++] = '0';
output[out_idx] = '\0';
return static_cast<unsigned>(out_idx);
}
if (value == INFINITY) {
value = std::numeric_limits<float>::max();
} else if (value == -INFINITY) {
value = std::numeric_limits<float>::lowest();
}
if (value < 0.0f) {
output[out_idx++] = '-';
value = -value;
}
const auto decimal =
jkj::dragonbox::to_decimal(value, jkj::dragonbox::policy::sign::ignore,
jkj::dragonbox::policy::trailing_zero::remove);
std::array<char, 10> digits;
size_t num_digits = 0;
uint32_t significand = decimal.significand;
do {
digits[num_digits++] = static_cast<char>('0' + (significand % 10));
significand /= 10;
} while (significand > 0);
const int exponent = decimal.exponent;
if (exponent >= 0) {
while (num_digits > 0) {
output[out_idx++] = digits[--num_digits];
}
for (int i = 0; i < exponent; ++i) {
output[out_idx++] = '0';
}
} else {
const int places_before_decimal = static_cast<int>(num_digits) + exponent;
if (places_before_decimal > 0) {
for (int i = 0; i < places_before_decimal; ++i) {
output[out_idx++] = digits[--num_digits];
}
output[out_idx++] = '.';
while (num_digits > 0) {
output[out_idx++] = digits[--num_digits];
}
} else {
output[out_idx++] = '.';
for (int i = 0; i < -places_before_decimal; ++i) {
output[out_idx++] = '0';
}
while (num_digits > 0) {
output[out_idx++] = digits[--num_digits];
if (out_idx + 1 >= output.size()) {
break;
}
}
}
}
output[out_idx] = '\0';
return static_cast<unsigned>(out_idx);
}
} // namespace
std::ostream& WriteFloat(std::ostream& stream, float value) {
char buffer[kMaximumFloatToDecimalLength];
unsigned size = FloatToDecimal(value, buffer);
stream.write(buffer, size);
return stream;
}
std::ostream& WriteMatrix(std::ostream& stream, const CFX_Matrix& matrix) {
WriteFloat(stream, matrix.a) << " ";
WriteFloat(stream, matrix.b) << " ";
WriteFloat(stream, matrix.c) << " ";
WriteFloat(stream, matrix.d) << " ";
WriteFloat(stream, matrix.e) << " ";
WriteFloat(stream, matrix.f);
return stream;
}
std::ostream& WritePoint(std::ostream& stream, const CFX_PointF& point) {
WriteFloat(stream, point.x) << " ";
WriteFloat(stream, point.y);
return stream;
}
std::ostream& WriteRect(std::ostream& stream, const CFX_FloatRect& rect) {
WriteFloat(stream, rect.left) << " ";
WriteFloat(stream, rect.bottom) << " ";
WriteFloat(stream, rect.Width()) << " ";
WriteFloat(stream, rect.Height());
return stream;
}