blob: f305765389685625d3bc7d5a8310077d129f1bc6 [file] [log] [blame]
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +00001/*
epoger@google.comec3ed6a2011-07-28 14:26:00 +00002 * Copyright 2011 Google Inc.
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +00003 *
epoger@google.comec3ed6a2011-07-28 14:26:00 +00004 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +00006 */
7
epoger@google.comec3ed6a2011-07-28 14:26:00 +00008
vandebo@chromium.org683001c2012-05-09 17:17:51 +00009#include "SkData.h"
reed@google.coma44ea512011-07-27 18:24:25 +000010#include "SkGeometry.h"
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000011#include "SkPaint.h"
12#include "SkPath.h"
commit-bot@chromium.org47401352013-07-23 21:49:29 +000013#include "SkPDFResourceDict.h"
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000014#include "SkPDFUtils.h"
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000015#include "SkStream.h"
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000016#include "SkString.h"
17#include "SkPDFTypes.h"
18
halcanary8e9f5e32016-02-24 15:46:46 -080019#include <cmath>
20
halcanary8103a342016-03-08 15:10:16 -080021sk_sp<SkPDFArray> SkPDFUtils::RectToArray(const SkRect& rect) {
22 auto result = sk_make_sp<SkPDFArray>();
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +000023 result->reserve(4);
24 result->appendScalar(rect.fLeft);
25 result->appendScalar(rect.fTop);
26 result->appendScalar(rect.fRight);
27 result->appendScalar(rect.fBottom);
28 return result;
29}
30
halcanary8103a342016-03-08 15:10:16 -080031sk_sp<SkPDFArray> SkPDFUtils::MatrixToArray(const SkMatrix& matrix) {
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000032 SkScalar values[6];
bungeman@google.com1ddd7c32011-07-13 19:41:55 +000033 if (!matrix.asAffine(values)) {
34 SkMatrix::SetAffineIdentity(values);
35 }
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000036
halcanary8103a342016-03-08 15:10:16 -080037 auto result = sk_make_sp<SkPDFArray>();
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000038 result->reserve(6);
39 for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) {
reed@google.comc789cf12011-07-20 12:14:33 +000040 result->appendScalar(values[i]);
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000041 }
42 return result;
43}
44
45// static
vandebo@chromium.org75f97e42011-04-11 23:24:18 +000046void SkPDFUtils::AppendTransform(const SkMatrix& matrix, SkWStream* content) {
47 SkScalar values[6];
bungeman@google.com1ddd7c32011-07-13 19:41:55 +000048 if (!matrix.asAffine(values)) {
49 SkMatrix::SetAffineIdentity(values);
50 }
vandebo@chromium.org75f97e42011-04-11 23:24:18 +000051 for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) {
halcanarybc4696b2015-05-06 10:56:04 -070052 SkPDFUtils::AppendScalar(values[i], content);
vandebo@chromium.org75f97e42011-04-11 23:24:18 +000053 content->writeText(" ");
54 }
55 content->writeText("cm\n");
56}
57
58// static
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000059void SkPDFUtils::MoveTo(SkScalar x, SkScalar y, SkWStream* content) {
halcanarybc4696b2015-05-06 10:56:04 -070060 SkPDFUtils::AppendScalar(x, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000061 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070062 SkPDFUtils::AppendScalar(y, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000063 content->writeText(" m\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000064}
65
66// static
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000067void SkPDFUtils::AppendLine(SkScalar x, SkScalar y, SkWStream* content) {
halcanarybc4696b2015-05-06 10:56:04 -070068 SkPDFUtils::AppendScalar(x, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000069 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070070 SkPDFUtils::AppendScalar(y, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000071 content->writeText(" l\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000072}
73
74// static
75void SkPDFUtils::AppendCubic(SkScalar ctl1X, SkScalar ctl1Y,
76 SkScalar ctl2X, SkScalar ctl2Y,
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000077 SkScalar dstX, SkScalar dstY, SkWStream* content) {
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000078 SkString cmd("y\n");
halcanarybc4696b2015-05-06 10:56:04 -070079 SkPDFUtils::AppendScalar(ctl1X, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000080 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070081 SkPDFUtils::AppendScalar(ctl1Y, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000082 content->writeText(" ");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000083 if (ctl2X != dstX || ctl2Y != dstY) {
84 cmd.set("c\n");
halcanarybc4696b2015-05-06 10:56:04 -070085 SkPDFUtils::AppendScalar(ctl2X, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000086 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070087 SkPDFUtils::AppendScalar(ctl2Y, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000088 content->writeText(" ");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000089 }
halcanarybc4696b2015-05-06 10:56:04 -070090 SkPDFUtils::AppendScalar(dstX, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000091 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070092 SkPDFUtils::AppendScalar(dstY, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000093 content->writeText(" ");
94 content->writeText(cmd.c_str());
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000095}
96
reed40c85e42015-01-05 10:01:25 -080097static void append_quad(const SkPoint quad[], SkWStream* content) {
98 SkPoint cubic[4];
99 SkConvertQuadToCubic(quad, cubic);
100 SkPDFUtils::AppendCubic(cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY,
101 cubic[3].fX, cubic[3].fY, content);
102}
103
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000104// static
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000105void SkPDFUtils::AppendRectangle(const SkRect& rect, SkWStream* content) {
106 // Skia has 0,0 at top left, pdf at bottom left. Do the right thing.
107 SkScalar bottom = SkMinScalar(rect.fBottom, rect.fTop);
108
halcanarybc4696b2015-05-06 10:56:04 -0700109 SkPDFUtils::AppendScalar(rect.fLeft, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000110 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -0700111 SkPDFUtils::AppendScalar(bottom, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000112 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -0700113 SkPDFUtils::AppendScalar(rect.width(), content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000114 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -0700115 SkPDFUtils::AppendScalar(rect.height(), content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000116 content->writeText(" re\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000117}
118
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000119// static
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000120void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle,
halcanary8b2bc252015-10-06 09:41:47 -0700121 bool doConsumeDegerates, SkWStream* content) {
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000122 // Filling a path with no area results in a drawing in PDF renderers but
123 // Chrome expects to be able to draw some such entities with no visible
124 // result, so we detect those cases and discard the drawing for them.
125 // Specifically: moveTo(X), lineTo(Y) and moveTo(X), lineTo(X), lineTo(Y).
126 enum SkipFillState {
halcanary8b2bc252015-10-06 09:41:47 -0700127 kEmpty_SkipFillState,
128 kSingleLine_SkipFillState,
129 kNonSingleLine_SkipFillState,
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000130 };
131 SkipFillState fillState = kEmpty_SkipFillState;
halcanary8b2bc252015-10-06 09:41:47 -0700132 //if (paintStyle != SkPaint::kFill_Style) {
133 // fillState = kNonSingleLine_SkipFillState;
134 //}
sugoi@google.come2e81132013-03-05 18:35:55 +0000135 SkPoint lastMovePt = SkPoint::Make(0,0);
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000136 SkDynamicMemoryWStream currentSegment;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000137 SkPoint args[4];
138 SkPath::Iter iter(path, false);
halcanary8b2bc252015-10-06 09:41:47 -0700139 for (SkPath::Verb verb = iter.next(args, doConsumeDegerates);
140 verb != SkPath::kDone_Verb;
141 verb = iter.next(args, doConsumeDegerates)) {
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000142 // args gets all the points, even the implicit first point.
143 switch (verb) {
144 case SkPath::kMove_Verb:
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000145 MoveTo(args[0].fX, args[0].fY, &currentSegment);
146 lastMovePt = args[0];
147 fillState = kEmpty_SkipFillState;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000148 break;
149 case SkPath::kLine_Verb:
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000150 AppendLine(args[1].fX, args[1].fY, &currentSegment);
halcanary8b2bc252015-10-06 09:41:47 -0700151 if ((fillState == kEmpty_SkipFillState) && (args[0] != lastMovePt)) {
152 fillState = kSingleLine_SkipFillState;
153 break;
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000154 }
halcanary8b2bc252015-10-06 09:41:47 -0700155 fillState = kNonSingleLine_SkipFillState;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000156 break;
reed40c85e42015-01-05 10:01:25 -0800157 case SkPath::kQuad_Verb:
158 append_quad(args, &currentSegment);
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000159 fillState = kNonSingleLine_SkipFillState;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000160 break;
reed40c85e42015-01-05 10:01:25 -0800161 case SkPath::kConic_Verb: {
162 const SkScalar tol = SK_Scalar1 / 4;
163 SkAutoConicToQuads converter;
164 const SkPoint* quads = converter.computeQuads(args, iter.conicWeight(), tol);
165 for (int i = 0; i < converter.countQuads(); ++i) {
166 append_quad(&quads[i * 2], &currentSegment);
167 }
halcanary8b2bc252015-10-06 09:41:47 -0700168 fillState = kNonSingleLine_SkipFillState;
reed40c85e42015-01-05 10:01:25 -0800169 } break;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000170 case SkPath::kCubic_Verb:
171 AppendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000172 args[3].fX, args[3].fY, &currentSegment);
173 fillState = kNonSingleLine_SkipFillState;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000174 break;
175 case SkPath::kClose_Verb:
halcanary8b2bc252015-10-06 09:41:47 -0700176
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000177 ClosePath(&currentSegment);
halcanary8b2bc252015-10-06 09:41:47 -0700178
179 currentSegment.writeToStream(content);
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000180 currentSegment.reset();
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000181 break;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000182 default:
183 SkASSERT(false);
184 break;
185 }
186 }
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000187 if (currentSegment.bytesWritten() > 0) {
halcanary7af21502015-02-23 12:17:59 -0800188 currentSegment.writeToStream(content);
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000189 }
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000190}
191
192// static
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000193void SkPDFUtils::ClosePath(SkWStream* content) {
194 content->writeText("h\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000195}
196
197// static
198void SkPDFUtils::PaintPath(SkPaint::Style style, SkPath::FillType fill,
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000199 SkWStream* content) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000200 if (style == SkPaint::kFill_Style) {
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000201 content->writeText("f");
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000202 } else if (style == SkPaint::kStrokeAndFill_Style) {
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000203 content->writeText("B");
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000204 } else if (style == SkPaint::kStroke_Style) {
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000205 content->writeText("S");
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000206 }
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000207
208 if (style != SkPaint::kStroke_Style) {
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000209 NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false);
210 NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000211 if (fill == SkPath::kEvenOdd_FillType) {
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000212 content->writeText("*");
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000213 }
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000214 }
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000215 content->writeText("\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000216}
217
218// static
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000219void SkPDFUtils::StrokePath(SkWStream* content) {
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000220 SkPDFUtils::PaintPath(
221 SkPaint::kStroke_Style, SkPath::kWinding_FillType, content);
222}
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000223
224// static
225void SkPDFUtils::DrawFormXObject(int objectIndex, SkWStream* content) {
commit-bot@chromium.org47401352013-07-23 21:49:29 +0000226 content->writeText("/");
227 content->writeText(SkPDFResourceDict::getResourceName(
228 SkPDFResourceDict::kXObject_ResourceType,
229 objectIndex).c_str());
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000230 content->writeText(" Do\n");
231}
232
233// static
234void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) {
commit-bot@chromium.org47401352013-07-23 21:49:29 +0000235 content->writeText("/");
236 content->writeText(SkPDFResourceDict::getResourceName(
237 SkPDFResourceDict::kExtGState_ResourceType,
238 objectIndex).c_str());
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000239 content->writeText(" gs\n");
240}
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000241
242// static
243void SkPDFUtils::ApplyPattern(int objectIndex, SkWStream* content) {
244 // Select Pattern color space (CS, cs) and set pattern object as current
245 // color (SCN, scn)
246 SkString resourceName = SkPDFResourceDict::getResourceName(
247 SkPDFResourceDict::kPattern_ResourceType,
248 objectIndex);
249 content->writeText("/Pattern CS/Pattern cs/");
250 content->writeText(resourceName.c_str());
251 content->writeText(" SCN/");
252 content->writeText(resourceName.c_str());
253 content->writeText(" scn\n");
254}
halcanarybc4696b2015-05-06 10:56:04 -0700255
256void SkPDFUtils::AppendScalar(SkScalar value, SkWStream* stream) {
halcanary8e9f5e32016-02-24 15:46:46 -0800257 char result[kMaximumFloatDecimalLength];
258 size_t len = SkPDFUtils::FloatToDecimal(SkScalarToFloat(value), result);
259 SkASSERT(len < kMaximumFloatDecimalLength);
260 stream->write(result, len);
261}
halcanarybc4696b2015-05-06 10:56:04 -0700262
halcanary8e9f5e32016-02-24 15:46:46 -0800263/** Write a string into result, includeing a terminating '\0' (for
264 unit testing). Return strlen(result) (for SkWStream::write) The
265 resulting string will be in the form /[-]?([0-9]*.)?[0-9]+/ and
266 sscanf(result, "%f", &x) will return the original value iff the
267 value is finite. This function accepts all possible input values.
halcanarybc4696b2015-05-06 10:56:04 -0700268
halcanary8e9f5e32016-02-24 15:46:46 -0800269 Motivation: "PDF does not support [numbers] in exponential format
270 (such as 6.02e23)." Otherwise, this function would rely on a
271 sprintf-type function from the standard library. */
272size_t SkPDFUtils::FloatToDecimal(float value,
273 char result[kMaximumFloatDecimalLength]) {
274 /* The longest result is -FLT_MIN.
275 We serialize it as "-.0000000000000000000000000000000000000117549435"
276 which has 48 characters plus a terminating '\0'. */
halcanarybc4696b2015-05-06 10:56:04 -0700277
halcanary8e9f5e32016-02-24 15:46:46 -0800278 /* section C.1 of the PDF1.4 spec (http://goo.gl/0SCswJ) says that
279 most PDF rasterizers will use fixed-point scalars that lack the
280 dynamic range of floats. Even if this is the case, I want to
281 serialize these (uncommon) very small and very large scalar
282 values with enough precision to allow a floating-point
283 rasterizer to read them in with perfect accuracy.
284 Experimentally, rasterizers such as pdfium do seem to benefit
285 from this. Rasterizers that rely on fixed-point scalars should
286 gracefully ignore these values that they can not parse. */
287 char* output = &result[0];
288 const char* const end = &result[kMaximumFloatDecimalLength - 1];
289 // subtract one to leave space for '\0'.
290
291 /* This function is written to accept any possible input value,
292 including non-finite values such as INF and NAN. In that case,
293 we ignore value-correctness and and output a syntacticly-valid
294 number. */
295 if (value == SK_FloatInfinity) {
296 value = FLT_MAX; // nearest finite float.
halcanarybc4696b2015-05-06 10:56:04 -0700297 }
halcanary8e9f5e32016-02-24 15:46:46 -0800298 if (value == SK_FloatNegativeInfinity) {
299 value = -FLT_MAX; // nearest finite float.
halcanarybc4696b2015-05-06 10:56:04 -0700300 }
halcanary8e9f5e32016-02-24 15:46:46 -0800301 if (!std::isfinite(value) || value == 0.0f) {
302 // NAN is unsupported in PDF. Always output a valid number.
303 // Also catch zero here, as a special case.
304 *output++ = '0';
305 *output = '\0';
306 return output - result;
halcanarybc4696b2015-05-06 10:56:04 -0700307 }
halcanary8e9f5e32016-02-24 15:46:46 -0800308 // Inspired by:
309 // http://www.exploringbinary.com/quick-and-dirty-floating-point-to-decimal-conversion/
310
311 if (value < 0.0) {
312 *output++ = '-';
313 value = -value;
halcanarybc4696b2015-05-06 10:56:04 -0700314 }
halcanary8e9f5e32016-02-24 15:46:46 -0800315 SkASSERT(value >= 0.0f);
316
317 // Must use double math to keep precision right.
318 double intPart;
319 double fracPart = std::modf(static_cast<double>(value), &intPart);
320 SkASSERT(intPart + fracPart == static_cast<double>(value));
321 size_t significantDigits = 0;
322 const size_t maxSignificantDigits = 9;
323 // Any fewer significant digits loses precision. The unit test
324 // checks round-trip correctness.
325 SkASSERT(intPart >= 0.0 && fracPart >= 0.0); // negative handled already.
326 SkASSERT(intPart > 0.0 || fracPart > 0.0); // zero already caught.
327 if (intPart > 0.0) {
328 // put the intPart digits onto a stack for later reversal.
329 char reversed[1 + FLT_MAX_10_EXP]; // 39 == 1 + FLT_MAX_10_EXP
330 // the largest integer part is FLT_MAX; it has 39 decimal digits.
331 size_t reversedIndex = 0;
332 do {
333 SkASSERT(reversedIndex < sizeof(reversed));
334 int digit = static_cast<int>(std::fmod(intPart, 10.0));
335 SkASSERT(digit >= 0 && digit <= 9);
336 reversed[reversedIndex++] = '0' + digit;
337 intPart = std::floor(intPart / 10.0);
338 } while (intPart > 0.0);
339 significantDigits = reversedIndex;
340 SkASSERT(reversedIndex <= sizeof(reversed));
341 SkASSERT(output + reversedIndex <= end);
342 while (reversedIndex-- > 0) { // pop from stack, append to result
343 *output++ = reversed[reversedIndex];
344 }
345 }
346 if (fracPart > 0 && significantDigits < maxSignificantDigits) {
347 *output++ = '.';
348 SkASSERT(output <= end);
349 do {
350 fracPart = std::modf(fracPart * 10.0, &intPart);
351 int digit = static_cast<int>(intPart);
352 SkASSERT(digit >= 0 && digit <= 9);
353 *output++ = '0' + digit;
354 SkASSERT(output <= end);
355 if (digit > 0 || significantDigits > 0) {
356 // start counting significantDigits after first non-zero digit.
357 ++significantDigits;
358 }
359 } while (fracPart > 0.0
360 && significantDigits < maxSignificantDigits
361 && output < end);
362 // When fracPart == 0, additional digits will be zero.
363 // When significantDigits == maxSignificantDigits, we can stop.
364 // when output == end, we have filed the string.
365 // Note: denormalized numbers will not have the same number of
366 // significantDigits, but do not need them to round-trip.
367 }
368 SkASSERT(output <= end);
369 *output = '\0';
370 return output - result;
halcanarybc4696b2015-05-06 10:56:04 -0700371}
372
373SkString SkPDFUtils::FormatString(const char* cin, size_t len) {
374 SkDEBUGCODE(static const size_t kMaxLen = 65535;)
375 SkASSERT(len <= kMaxLen);
376
377 // 7-bit clean is a heuristic to decide what string format to use;
378 // a 7-bit clean string should require little escaping.
379 bool sevenBitClean = true;
380 size_t characterCount = 2 + len;
381 for (size_t i = 0; i < len; i++) {
382 if (cin[i] > '~' || cin[i] < ' ') {
383 sevenBitClean = false;
384 break;
385 }
386 if (cin[i] == '\\' || cin[i] == '(' || cin[i] == ')') {
387 ++characterCount;
388 }
389 }
390 SkString result;
391 if (sevenBitClean) {
392 result.resize(characterCount);
393 char* str = result.writable_str();
394 *str++ = '(';
395 for (size_t i = 0; i < len; i++) {
396 if (cin[i] == '\\' || cin[i] == '(' || cin[i] == ')') {
397 *str++ = '\\';
398 }
399 *str++ = cin[i];
400 }
401 *str++ = ')';
402 } else {
403 result.resize(2 * len + 2);
404 char* str = result.writable_str();
405 *str++ = '<';
406 for (size_t i = 0; i < len; i++) {
407 uint8_t c = static_cast<uint8_t>(cin[i]);
408 static const char gHex[] = "0123456789ABCDEF";
409 *str++ = gHex[(c >> 4) & 0xF];
410 *str++ = gHex[(c ) & 0xF];
411 }
412 *str++ = '>';
413 }
414 return result;
415}