Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1124)

Unified Diff: src/pdf/SkPDFUtils.cpp

Issue 1720863003: SkPDF: fix scalar serialization (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: 2016-02-24 (Wednesday) 16:44:08 EST Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/pdf/SkPDFUtils.h ('k') | tests/PDFPrimitivesTest.cpp » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: src/pdf/SkPDFUtils.cpp
diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp
index 108f2c10abd56e6d0ae94b263c89c5c9a5d34dd4..30d6ee7d68899b7f3415352abb668d1966c432fb 100644
--- a/src/pdf/SkPDFUtils.cpp
+++ b/src/pdf/SkPDFUtils.cpp
@@ -16,6 +16,8 @@
#include "SkString.h"
#include "SkPDFTypes.h"
+#include <cmath>
+
//static
SkPDFArray* SkPDFUtils::RectToArray(const SkRect& rect) {
SkPDFArray* result = new SkPDFArray();
@@ -254,51 +256,120 @@ void SkPDFUtils::ApplyPattern(int objectIndex, SkWStream* content) {
}
void SkPDFUtils::AppendScalar(SkScalar value, SkWStream* stream) {
- // The range of reals in PDF/A is the same as SkFixed: +/- 32,767 and
- // +/- 1/65,536 (though integers can range from 2^31 - 1 to -2^31).
- // When using floats that are outside the whole value range, we can use
- // integers instead.
-
-#if !defined(SK_ALLOW_LARGE_PDF_SCALARS)
- if (value > 32767 || value < -32767) {
- stream->writeDecAsText(SkScalarRoundToInt(value));
- return;
- }
+ char result[kMaximumFloatDecimalLength];
+ size_t len = SkPDFUtils::FloatToDecimal(SkScalarToFloat(value), result);
+ SkASSERT(len < kMaximumFloatDecimalLength);
+ stream->write(result, len);
+}
+
+/** Write a string into result, includeing a terminating '\0' (for
+ unit testing). Return strlen(result) (for SkWStream::write) The
+ resulting string will be in the form /[-]?([0-9]*.)?[0-9]+/ and
+ sscanf(result, "%f", &x) will return the original value iff the
+ value is finite. This function accepts all possible input values.
+
+ Motivation: "PDF does not support [numbers] in exponential format
+ (such as 6.02e23)." Otherwise, this function would rely on a
+ sprintf-type function from the standard library. */
+size_t SkPDFUtils::FloatToDecimal(float value,
+ char result[kMaximumFloatDecimalLength]) {
+ /* The longest result is -FLT_MIN.
+ We serialize it as "-.0000000000000000000000000000000000000117549435"
+ which has 48 characters plus a terminating '\0'. */
+
+ /* section C.1 of the PDF1.4 spec (http://goo.gl/0SCswJ) says that
+ most PDF rasterizers will use fixed-point scalars that lack the
+ dynamic range of floats. Even if this is the case, I want to
+ serialize these (uncommon) very small and very large scalar
+ values with enough precision to allow a floating-point
+ rasterizer to read them in with perfect accuracy.
+ Experimentally, rasterizers such as pdfium do seem to benefit
+ from this. Rasterizers that rely on fixed-point scalars should
+ gracefully ignore these values that they can not parse. */
+ char* output = &result[0];
+ const char* const end = &result[kMaximumFloatDecimalLength - 1];
+ // subtract one to leave space for '\0'.
- char buffer[SkStrAppendScalar_MaxSize];
- char* end = SkStrAppendFixed(buffer, SkScalarToFixed(value));
- stream->write(buffer, end - buffer);
- return;
-#endif // !SK_ALLOW_LARGE_PDF_SCALARS
-
-#if defined(SK_ALLOW_LARGE_PDF_SCALARS)
- // Floats have 24bits of significance, so anything outside that range is
- // no more precise than an int. (Plus PDF doesn't support scientific
- // notation, so this clamps to SK_Max/MinS32).
- if (value > (1 << 24) || value < -(1 << 24)) {
- stream->writeDecAsText(value);
- return;
+ /* This function is written to accept any possible input value,
+ including non-finite values such as INF and NAN. In that case,
+ we ignore value-correctness and and output a syntacticly-valid
+ number. */
+ if (value == SK_FloatInfinity) {
+ value = FLT_MAX; // nearest finite float.
}
- // Continue to enforce the PDF limits for small floats.
- if (value < 1.0f/65536 && value > -1.0f/65536) {
- stream->writeDecAsText(0);
- return;
+ if (value == SK_FloatNegativeInfinity) {
+ value = -FLT_MAX; // nearest finite float.
}
- // SkStrAppendFloat might still use scientific notation, so use snprintf
- // directly..
- static const int kFloat_MaxSize = 19;
- char buffer[kFloat_MaxSize];
- int len = SNPRINTF(buffer, kFloat_MaxSize, "%#.8f", value);
- // %f always prints trailing 0s, so strip them.
- for (; buffer[len - 1] == '0' && len > 0; len--) {
- buffer[len - 1] = '\0';
+ if (!std::isfinite(value) || value == 0.0f) {
+ // NAN is unsupported in PDF. Always output a valid number.
+ // Also catch zero here, as a special case.
+ *output++ = '0';
+ *output = '\0';
+ return output - result;
}
- if (buffer[len - 1] == '.') {
- buffer[len - 1] = '\0';
+ // Inspired by:
+ // http://www.exploringbinary.com/quick-and-dirty-floating-point-to-decimal-conversion/
+
+ if (value < 0.0) {
+ *output++ = '-';
+ value = -value;
+ }
+ SkASSERT(value >= 0.0f);
+
+ // Must use double math to keep precision right.
+ double intPart;
+ double fracPart = std::modf(static_cast<double>(value), &intPart);
+ SkASSERT(intPart + fracPart == static_cast<double>(value));
+ size_t significantDigits = 0;
+ const size_t maxSignificantDigits = 9;
+ // Any fewer significant digits loses precision. The unit test
+ // checks round-trip correctness.
+ SkASSERT(intPart >= 0.0 && fracPart >= 0.0); // negative handled already.
+ SkASSERT(intPart > 0.0 || fracPart > 0.0); // zero already caught.
+ if (intPart > 0.0) {
+ // put the intPart digits onto a stack for later reversal.
+ char reversed[1 + FLT_MAX_10_EXP]; // 39 == 1 + FLT_MAX_10_EXP
+ // the largest integer part is FLT_MAX; it has 39 decimal digits.
+ size_t reversedIndex = 0;
+ do {
+ SkASSERT(reversedIndex < sizeof(reversed));
+ int digit = static_cast<int>(std::fmod(intPart, 10.0));
+ SkASSERT(digit >= 0 && digit <= 9);
+ reversed[reversedIndex++] = '0' + digit;
+ intPart = std::floor(intPart / 10.0);
+ } while (intPart > 0.0);
+ significantDigits = reversedIndex;
+ SkASSERT(reversedIndex <= sizeof(reversed));
+ SkASSERT(output + reversedIndex <= end);
+ while (reversedIndex-- > 0) { // pop from stack, append to result
+ *output++ = reversed[reversedIndex];
+ }
+ }
+ if (fracPart > 0 && significantDigits < maxSignificantDigits) {
+ *output++ = '.';
+ SkASSERT(output <= end);
+ do {
+ fracPart = std::modf(fracPart * 10.0, &intPart);
+ int digit = static_cast<int>(intPart);
+ SkASSERT(digit >= 0 && digit <= 9);
+ *output++ = '0' + digit;
+ SkASSERT(output <= end);
+ if (digit > 0 || significantDigits > 0) {
+ // start counting significantDigits after first non-zero digit.
+ ++significantDigits;
+ }
+ } while (fracPart > 0.0
+ && significantDigits < maxSignificantDigits
+ && output < end);
+ // When fracPart == 0, additional digits will be zero.
+ // When significantDigits == maxSignificantDigits, we can stop.
+ // when output == end, we have filed the string.
+ // Note: denormalized numbers will not have the same number of
+ // significantDigits, but do not need them to round-trip.
}
- stream->writeText(buffer);
- return;
-#endif // SK_ALLOW_LARGE_PDF_SCALARS
+ SkASSERT(output <= end);
+ *output = '\0';
+ return output - result;
}
SkString SkPDFUtils::FormatString(const char* cin, size_t len) {
« no previous file with comments | « src/pdf/SkPDFUtils.h ('k') | tests/PDFPrimitivesTest.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698