blob: 30d6ee7d68899b7f3415352abb668d1966c432fb [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
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +000021//static
22SkPDFArray* SkPDFUtils::RectToArray(const SkRect& rect) {
23 SkPDFArray* result = new SkPDFArray();
24 result->reserve(4);
25 result->appendScalar(rect.fLeft);
26 result->appendScalar(rect.fTop);
27 result->appendScalar(rect.fRight);
28 result->appendScalar(rect.fBottom);
29 return result;
30}
31
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000032// static
33SkPDFArray* SkPDFUtils::MatrixToArray(const SkMatrix& matrix) {
34 SkScalar values[6];
bungeman@google.com1ddd7c32011-07-13 19:41:55 +000035 if (!matrix.asAffine(values)) {
36 SkMatrix::SetAffineIdentity(values);
37 }
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000038
39 SkPDFArray* result = new SkPDFArray;
40 result->reserve(6);
41 for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) {
reed@google.comc789cf12011-07-20 12:14:33 +000042 result->appendScalar(values[i]);
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000043 }
44 return result;
45}
46
47// static
vandebo@chromium.org75f97e42011-04-11 23:24:18 +000048void SkPDFUtils::AppendTransform(const SkMatrix& matrix, SkWStream* content) {
49 SkScalar values[6];
bungeman@google.com1ddd7c32011-07-13 19:41:55 +000050 if (!matrix.asAffine(values)) {
51 SkMatrix::SetAffineIdentity(values);
52 }
vandebo@chromium.org75f97e42011-04-11 23:24:18 +000053 for (size_t i = 0; i < SK_ARRAY_COUNT(values); i++) {
halcanarybc4696b2015-05-06 10:56:04 -070054 SkPDFUtils::AppendScalar(values[i], content);
vandebo@chromium.org75f97e42011-04-11 23:24:18 +000055 content->writeText(" ");
56 }
57 content->writeText("cm\n");
58}
59
60// static
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000061void SkPDFUtils::MoveTo(SkScalar x, SkScalar y, SkWStream* content) {
halcanarybc4696b2015-05-06 10:56:04 -070062 SkPDFUtils::AppendScalar(x, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000063 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070064 SkPDFUtils::AppendScalar(y, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000065 content->writeText(" m\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000066}
67
68// static
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000069void SkPDFUtils::AppendLine(SkScalar x, SkScalar y, SkWStream* content) {
halcanarybc4696b2015-05-06 10:56:04 -070070 SkPDFUtils::AppendScalar(x, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000071 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070072 SkPDFUtils::AppendScalar(y, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000073 content->writeText(" l\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000074}
75
76// static
77void SkPDFUtils::AppendCubic(SkScalar ctl1X, SkScalar ctl1Y,
78 SkScalar ctl2X, SkScalar ctl2Y,
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000079 SkScalar dstX, SkScalar dstY, SkWStream* content) {
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000080 SkString cmd("y\n");
halcanarybc4696b2015-05-06 10:56:04 -070081 SkPDFUtils::AppendScalar(ctl1X, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000082 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070083 SkPDFUtils::AppendScalar(ctl1Y, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000084 content->writeText(" ");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000085 if (ctl2X != dstX || ctl2Y != dstY) {
86 cmd.set("c\n");
halcanarybc4696b2015-05-06 10:56:04 -070087 SkPDFUtils::AppendScalar(ctl2X, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000088 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070089 SkPDFUtils::AppendScalar(ctl2Y, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000090 content->writeText(" ");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000091 }
halcanarybc4696b2015-05-06 10:56:04 -070092 SkPDFUtils::AppendScalar(dstX, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000093 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -070094 SkPDFUtils::AppendScalar(dstY, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +000095 content->writeText(" ");
96 content->writeText(cmd.c_str());
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +000097}
98
reed40c85e42015-01-05 10:01:25 -080099static void append_quad(const SkPoint quad[], SkWStream* content) {
100 SkPoint cubic[4];
101 SkConvertQuadToCubic(quad, cubic);
102 SkPDFUtils::AppendCubic(cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY,
103 cubic[3].fX, cubic[3].fY, content);
104}
105
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000106// static
vandebo@chromium.org9fbdf872011-05-09 07:55:58 +0000107void SkPDFUtils::AppendRectangle(const SkRect& rect, SkWStream* content) {
108 // Skia has 0,0 at top left, pdf at bottom left. Do the right thing.
109 SkScalar bottom = SkMinScalar(rect.fBottom, rect.fTop);
110
halcanarybc4696b2015-05-06 10:56:04 -0700111 SkPDFUtils::AppendScalar(rect.fLeft, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000112 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -0700113 SkPDFUtils::AppendScalar(bottom, content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000114 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -0700115 SkPDFUtils::AppendScalar(rect.width(), content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000116 content->writeText(" ");
halcanarybc4696b2015-05-06 10:56:04 -0700117 SkPDFUtils::AppendScalar(rect.height(), content);
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000118 content->writeText(" re\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000119}
120
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000121// static
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000122void SkPDFUtils::EmitPath(const SkPath& path, SkPaint::Style paintStyle,
halcanary8b2bc252015-10-06 09:41:47 -0700123 bool doConsumeDegerates, SkWStream* content) {
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000124 // Filling a path with no area results in a drawing in PDF renderers but
125 // Chrome expects to be able to draw some such entities with no visible
126 // result, so we detect those cases and discard the drawing for them.
127 // Specifically: moveTo(X), lineTo(Y) and moveTo(X), lineTo(X), lineTo(Y).
128 enum SkipFillState {
halcanary8b2bc252015-10-06 09:41:47 -0700129 kEmpty_SkipFillState,
130 kSingleLine_SkipFillState,
131 kNonSingleLine_SkipFillState,
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000132 };
133 SkipFillState fillState = kEmpty_SkipFillState;
halcanary8b2bc252015-10-06 09:41:47 -0700134 //if (paintStyle != SkPaint::kFill_Style) {
135 // fillState = kNonSingleLine_SkipFillState;
136 //}
sugoi@google.come2e81132013-03-05 18:35:55 +0000137 SkPoint lastMovePt = SkPoint::Make(0,0);
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000138 SkDynamicMemoryWStream currentSegment;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000139 SkPoint args[4];
140 SkPath::Iter iter(path, false);
halcanary8b2bc252015-10-06 09:41:47 -0700141 for (SkPath::Verb verb = iter.next(args, doConsumeDegerates);
142 verb != SkPath::kDone_Verb;
143 verb = iter.next(args, doConsumeDegerates)) {
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000144 // args gets all the points, even the implicit first point.
145 switch (verb) {
146 case SkPath::kMove_Verb:
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000147 MoveTo(args[0].fX, args[0].fY, &currentSegment);
148 lastMovePt = args[0];
149 fillState = kEmpty_SkipFillState;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000150 break;
151 case SkPath::kLine_Verb:
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000152 AppendLine(args[1].fX, args[1].fY, &currentSegment);
halcanary8b2bc252015-10-06 09:41:47 -0700153 if ((fillState == kEmpty_SkipFillState) && (args[0] != lastMovePt)) {
154 fillState = kSingleLine_SkipFillState;
155 break;
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000156 }
halcanary8b2bc252015-10-06 09:41:47 -0700157 fillState = kNonSingleLine_SkipFillState;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000158 break;
reed40c85e42015-01-05 10:01:25 -0800159 case SkPath::kQuad_Verb:
160 append_quad(args, &currentSegment);
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000161 fillState = kNonSingleLine_SkipFillState;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000162 break;
reed40c85e42015-01-05 10:01:25 -0800163 case SkPath::kConic_Verb: {
164 const SkScalar tol = SK_Scalar1 / 4;
165 SkAutoConicToQuads converter;
166 const SkPoint* quads = converter.computeQuads(args, iter.conicWeight(), tol);
167 for (int i = 0; i < converter.countQuads(); ++i) {
168 append_quad(&quads[i * 2], &currentSegment);
169 }
halcanary8b2bc252015-10-06 09:41:47 -0700170 fillState = kNonSingleLine_SkipFillState;
reed40c85e42015-01-05 10:01:25 -0800171 } break;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000172 case SkPath::kCubic_Verb:
173 AppendCubic(args[1].fX, args[1].fY, args[2].fX, args[2].fY,
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000174 args[3].fX, args[3].fY, &currentSegment);
175 fillState = kNonSingleLine_SkipFillState;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000176 break;
177 case SkPath::kClose_Verb:
halcanary8b2bc252015-10-06 09:41:47 -0700178
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000179 ClosePath(&currentSegment);
halcanary8b2bc252015-10-06 09:41:47 -0700180
181 currentSegment.writeToStream(content);
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000182 currentSegment.reset();
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000183 break;
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000184 default:
185 SkASSERT(false);
186 break;
187 }
188 }
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000189 if (currentSegment.bytesWritten() > 0) {
halcanary7af21502015-02-23 12:17:59 -0800190 currentSegment.writeToStream(content);
vandebo@chromium.org683001c2012-05-09 17:17:51 +0000191 }
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000192}
193
194// static
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000195void SkPDFUtils::ClosePath(SkWStream* content) {
196 content->writeText("h\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000197}
198
199// static
200void SkPDFUtils::PaintPath(SkPaint::Style style, SkPath::FillType fill,
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000201 SkWStream* content) {
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000202 if (style == SkPaint::kFill_Style) {
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000203 content->writeText("f");
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000204 } else if (style == SkPaint::kStrokeAndFill_Style) {
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000205 content->writeText("B");
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000206 } else if (style == SkPaint::kStroke_Style) {
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000207 content->writeText("S");
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000208 }
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000209
210 if (style != SkPaint::kStroke_Style) {
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000211 NOT_IMPLEMENTED(fill == SkPath::kInverseEvenOdd_FillType, false);
212 NOT_IMPLEMENTED(fill == SkPath::kInverseWinding_FillType, false);
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000213 if (fill == SkPath::kEvenOdd_FillType) {
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000214 content->writeText("*");
ctguil@chromium.org769fa6a2011-08-20 00:36:18 +0000215 }
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000216 }
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000217 content->writeText("\n");
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000218}
219
220// static
vandebo@chromium.orgcae5fba2011-03-28 19:03:50 +0000221void SkPDFUtils::StrokePath(SkWStream* content) {
ctguil@chromium.orgf966fd32011-03-04 21:47:04 +0000222 SkPDFUtils::PaintPath(
223 SkPaint::kStroke_Style, SkPath::kWinding_FillType, content);
224}
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000225
226// static
227void SkPDFUtils::DrawFormXObject(int objectIndex, SkWStream* content) {
commit-bot@chromium.org47401352013-07-23 21:49:29 +0000228 content->writeText("/");
229 content->writeText(SkPDFResourceDict::getResourceName(
230 SkPDFResourceDict::kXObject_ResourceType,
231 objectIndex).c_str());
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000232 content->writeText(" Do\n");
233}
234
235// static
236void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) {
commit-bot@chromium.org47401352013-07-23 21:49:29 +0000237 content->writeText("/");
238 content->writeText(SkPDFResourceDict::getResourceName(
239 SkPDFResourceDict::kExtGState_ResourceType,
240 objectIndex).c_str());
vandebo@chromium.org6112c212011-05-13 03:50:38 +0000241 content->writeText(" gs\n");
242}
commit-bot@chromium.org93a2e212013-07-23 23:16:03 +0000243
244// static
245void SkPDFUtils::ApplyPattern(int objectIndex, SkWStream* content) {
246 // Select Pattern color space (CS, cs) and set pattern object as current
247 // color (SCN, scn)
248 SkString resourceName = SkPDFResourceDict::getResourceName(
249 SkPDFResourceDict::kPattern_ResourceType,
250 objectIndex);
251 content->writeText("/Pattern CS/Pattern cs/");
252 content->writeText(resourceName.c_str());
253 content->writeText(" SCN/");
254 content->writeText(resourceName.c_str());
255 content->writeText(" scn\n");
256}
halcanarybc4696b2015-05-06 10:56:04 -0700257
258void SkPDFUtils::AppendScalar(SkScalar value, SkWStream* stream) {
halcanary8e9f5e32016-02-24 15:46:46 -0800259 char result[kMaximumFloatDecimalLength];
260 size_t len = SkPDFUtils::FloatToDecimal(SkScalarToFloat(value), result);
261 SkASSERT(len < kMaximumFloatDecimalLength);
262 stream->write(result, len);
263}
halcanarybc4696b2015-05-06 10:56:04 -0700264
halcanary8e9f5e32016-02-24 15:46:46 -0800265/** Write a string into result, includeing a terminating '\0' (for
266 unit testing). Return strlen(result) (for SkWStream::write) The
267 resulting string will be in the form /[-]?([0-9]*.)?[0-9]+/ and
268 sscanf(result, "%f", &x) will return the original value iff the
269 value is finite. This function accepts all possible input values.
halcanarybc4696b2015-05-06 10:56:04 -0700270
halcanary8e9f5e32016-02-24 15:46:46 -0800271 Motivation: "PDF does not support [numbers] in exponential format
272 (such as 6.02e23)." Otherwise, this function would rely on a
273 sprintf-type function from the standard library. */
274size_t SkPDFUtils::FloatToDecimal(float value,
275 char result[kMaximumFloatDecimalLength]) {
276 /* The longest result is -FLT_MIN.
277 We serialize it as "-.0000000000000000000000000000000000000117549435"
278 which has 48 characters plus a terminating '\0'. */
halcanarybc4696b2015-05-06 10:56:04 -0700279
halcanary8e9f5e32016-02-24 15:46:46 -0800280 /* section C.1 of the PDF1.4 spec (http://goo.gl/0SCswJ) says that
281 most PDF rasterizers will use fixed-point scalars that lack the
282 dynamic range of floats. Even if this is the case, I want to
283 serialize these (uncommon) very small and very large scalar
284 values with enough precision to allow a floating-point
285 rasterizer to read them in with perfect accuracy.
286 Experimentally, rasterizers such as pdfium do seem to benefit
287 from this. Rasterizers that rely on fixed-point scalars should
288 gracefully ignore these values that they can not parse. */
289 char* output = &result[0];
290 const char* const end = &result[kMaximumFloatDecimalLength - 1];
291 // subtract one to leave space for '\0'.
292
293 /* This function is written to accept any possible input value,
294 including non-finite values such as INF and NAN. In that case,
295 we ignore value-correctness and and output a syntacticly-valid
296 number. */
297 if (value == SK_FloatInfinity) {
298 value = FLT_MAX; // nearest finite float.
halcanarybc4696b2015-05-06 10:56:04 -0700299 }
halcanary8e9f5e32016-02-24 15:46:46 -0800300 if (value == SK_FloatNegativeInfinity) {
301 value = -FLT_MAX; // nearest finite float.
halcanarybc4696b2015-05-06 10:56:04 -0700302 }
halcanary8e9f5e32016-02-24 15:46:46 -0800303 if (!std::isfinite(value) || value == 0.0f) {
304 // NAN is unsupported in PDF. Always output a valid number.
305 // Also catch zero here, as a special case.
306 *output++ = '0';
307 *output = '\0';
308 return output - result;
halcanarybc4696b2015-05-06 10:56:04 -0700309 }
halcanary8e9f5e32016-02-24 15:46:46 -0800310 // Inspired by:
311 // http://www.exploringbinary.com/quick-and-dirty-floating-point-to-decimal-conversion/
312
313 if (value < 0.0) {
314 *output++ = '-';
315 value = -value;
halcanarybc4696b2015-05-06 10:56:04 -0700316 }
halcanary8e9f5e32016-02-24 15:46:46 -0800317 SkASSERT(value >= 0.0f);
318
319 // Must use double math to keep precision right.
320 double intPart;
321 double fracPart = std::modf(static_cast<double>(value), &intPart);
322 SkASSERT(intPart + fracPart == static_cast<double>(value));
323 size_t significantDigits = 0;
324 const size_t maxSignificantDigits = 9;
325 // Any fewer significant digits loses precision. The unit test
326 // checks round-trip correctness.
327 SkASSERT(intPart >= 0.0 && fracPart >= 0.0); // negative handled already.
328 SkASSERT(intPart > 0.0 || fracPart > 0.0); // zero already caught.
329 if (intPart > 0.0) {
330 // put the intPart digits onto a stack for later reversal.
331 char reversed[1 + FLT_MAX_10_EXP]; // 39 == 1 + FLT_MAX_10_EXP
332 // the largest integer part is FLT_MAX; it has 39 decimal digits.
333 size_t reversedIndex = 0;
334 do {
335 SkASSERT(reversedIndex < sizeof(reversed));
336 int digit = static_cast<int>(std::fmod(intPart, 10.0));
337 SkASSERT(digit >= 0 && digit <= 9);
338 reversed[reversedIndex++] = '0' + digit;
339 intPart = std::floor(intPart / 10.0);
340 } while (intPart > 0.0);
341 significantDigits = reversedIndex;
342 SkASSERT(reversedIndex <= sizeof(reversed));
343 SkASSERT(output + reversedIndex <= end);
344 while (reversedIndex-- > 0) { // pop from stack, append to result
345 *output++ = reversed[reversedIndex];
346 }
347 }
348 if (fracPart > 0 && significantDigits < maxSignificantDigits) {
349 *output++ = '.';
350 SkASSERT(output <= end);
351 do {
352 fracPart = std::modf(fracPart * 10.0, &intPart);
353 int digit = static_cast<int>(intPart);
354 SkASSERT(digit >= 0 && digit <= 9);
355 *output++ = '0' + digit;
356 SkASSERT(output <= end);
357 if (digit > 0 || significantDigits > 0) {
358 // start counting significantDigits after first non-zero digit.
359 ++significantDigits;
360 }
361 } while (fracPart > 0.0
362 && significantDigits < maxSignificantDigits
363 && output < end);
364 // When fracPart == 0, additional digits will be zero.
365 // When significantDigits == maxSignificantDigits, we can stop.
366 // when output == end, we have filed the string.
367 // Note: denormalized numbers will not have the same number of
368 // significantDigits, but do not need them to round-trip.
369 }
370 SkASSERT(output <= end);
371 *output = '\0';
372 return output - result;
halcanarybc4696b2015-05-06 10:56:04 -0700373}
374
375SkString SkPDFUtils::FormatString(const char* cin, size_t len) {
376 SkDEBUGCODE(static const size_t kMaxLen = 65535;)
377 SkASSERT(len <= kMaxLen);
378
379 // 7-bit clean is a heuristic to decide what string format to use;
380 // a 7-bit clean string should require little escaping.
381 bool sevenBitClean = true;
382 size_t characterCount = 2 + len;
383 for (size_t i = 0; i < len; i++) {
384 if (cin[i] > '~' || cin[i] < ' ') {
385 sevenBitClean = false;
386 break;
387 }
388 if (cin[i] == '\\' || cin[i] == '(' || cin[i] == ')') {
389 ++characterCount;
390 }
391 }
392 SkString result;
393 if (sevenBitClean) {
394 result.resize(characterCount);
395 char* str = result.writable_str();
396 *str++ = '(';
397 for (size_t i = 0; i < len; i++) {
398 if (cin[i] == '\\' || cin[i] == '(' || cin[i] == ')') {
399 *str++ = '\\';
400 }
401 *str++ = cin[i];
402 }
403 *str++ = ')';
404 } else {
405 result.resize(2 * len + 2);
406 char* str = result.writable_str();
407 *str++ = '<';
408 for (size_t i = 0; i < len; i++) {
409 uint8_t c = static_cast<uint8_t>(cin[i]);
410 static const char gHex[] = "0123456789ABCDEF";
411 *str++ = gHex[(c >> 4) & 0xF];
412 *str++ = gHex[(c ) & 0xF];
413 }
414 *str++ = '>';
415 }
416 return result;
417}