Skip to content

Commit f6566d6

Browse files
committed
Fix #878
1 parent 5260c4b commit f6566d6

File tree

3 files changed

+294
-11
lines changed

3 files changed

+294
-11
lines changed
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
namespace UglyToad.PdfPig.Tests.Writer
2+
{
3+
using System.Globalization;
4+
using UglyToad.PdfPig.Graphics.Operations;
5+
6+
public class OperationWriteHelperTests
7+
{
8+
[Fact]
9+
public void WriteDouble0()
10+
{
11+
using (var memStream = new MemoryStream())
12+
{
13+
OperationWriteHelper.WriteDouble(memStream, 0);
14+
15+
// Read Test
16+
memStream.Position = 0;
17+
using (var streamReader = new StreamReader(memStream))
18+
{
19+
var line = streamReader.ReadToEnd();
20+
Assert.Equal("0", line);
21+
}
22+
}
23+
}
24+
25+
[Fact]
26+
public void WriteDouble5()
27+
{
28+
using (var memStream = new MemoryStream())
29+
{
30+
OperationWriteHelper.WriteDouble(memStream, 5);
31+
32+
// Read Test
33+
memStream.Position = 0;
34+
using (var streamReader = new StreamReader(memStream))
35+
{
36+
var line = streamReader.ReadToEnd();
37+
Assert.Equal("5", line);
38+
}
39+
}
40+
}
41+
42+
[Fact]
43+
public void WriteDoubleMinus5()
44+
{
45+
using (var memStream = new MemoryStream())
46+
{
47+
OperationWriteHelper.WriteDouble(memStream, -5);
48+
49+
// Read Test
50+
memStream.Position = 0;
51+
using (var streamReader = new StreamReader(memStream))
52+
{
53+
var line = streamReader.ReadToEnd();
54+
Assert.Equal("-5", line);
55+
}
56+
}
57+
}
58+
59+
[Fact]
60+
public void WriteDouble10()
61+
{
62+
using (var memStream = new MemoryStream())
63+
{
64+
OperationWriteHelper.WriteDouble(memStream, 10);
65+
66+
// Read Test
67+
memStream.Position = 0;
68+
using (var streamReader = new StreamReader(memStream))
69+
{
70+
var line = streamReader.ReadToEnd();
71+
Assert.Equal("10", line);
72+
}
73+
}
74+
}
75+
76+
[Fact]
77+
public void WriteDoubleMinus10()
78+
{
79+
using (var memStream = new MemoryStream())
80+
{
81+
OperationWriteHelper.WriteDouble(memStream, -10);
82+
83+
// Read Test
84+
memStream.Position = 0;
85+
using (var streamReader = new StreamReader(memStream))
86+
{
87+
var line = streamReader.ReadToEnd();
88+
Assert.Equal("-10", line);
89+
}
90+
}
91+
}
92+
93+
[Fact]
94+
public void WriteDouble1()
95+
{
96+
using (var memStream = new MemoryStream())
97+
{
98+
OperationWriteHelper.WriteDouble(memStream, 0.00000001);
99+
100+
// Read Test
101+
memStream.Position = 0;
102+
using (var streamReader = new StreamReader(memStream))
103+
{
104+
var line = streamReader.ReadToEnd();
105+
Assert.Equal("0.00000001", line);
106+
}
107+
}
108+
}
109+
110+
[Fact]
111+
public void WriteDouble1bis()
112+
{
113+
using (var memStream = new MemoryStream())
114+
{
115+
OperationWriteHelper.WriteDouble(memStream, -0.00000001);
116+
117+
// Read Test
118+
memStream.Position = 0;
119+
using (var streamReader = new StreamReader(memStream))
120+
{
121+
var line = streamReader.ReadToEnd();
122+
Assert.Equal("-0.00000001", line);
123+
}
124+
}
125+
}
126+
127+
[Fact]
128+
public void WriteDouble2()
129+
{
130+
using (var memStream = new MemoryStream())
131+
{
132+
OperationWriteHelper.WriteDouble(memStream, .00000005100);
133+
134+
// Read Test
135+
memStream.Position = 0;
136+
using (var streamReader = new StreamReader(memStream))
137+
{
138+
var line = streamReader.ReadToEnd();
139+
Assert.Equal("0.000000051", line);
140+
}
141+
}
142+
}
143+
144+
[Fact]
145+
public void WriteDouble2bis()
146+
{
147+
using (var memStream = new MemoryStream())
148+
{
149+
OperationWriteHelper.WriteDouble(memStream, -.0000000510);
150+
151+
// Read Test
152+
memStream.Position = 0;
153+
using (var streamReader = new StreamReader(memStream))
154+
{
155+
var line = streamReader.ReadToEnd();
156+
Assert.Equal("-0.000000051", line);
157+
}
158+
}
159+
}
160+
161+
[Fact]
162+
public void WriteDouble3()
163+
{
164+
using (var memStream = new MemoryStream())
165+
{
166+
OperationWriteHelper.WriteDouble(memStream, 15001.98);
167+
168+
// Read Test
169+
memStream.Position = 0;
170+
using (var streamReader = new StreamReader(memStream))
171+
{
172+
var line = streamReader.ReadToEnd();
173+
var v = double.Parse(line, CultureInfo.InvariantCulture);
174+
Assert.Equal(15001.98, v);
175+
}
176+
}
177+
}
178+
179+
[Fact]
180+
public void WriteDouble4()
181+
{
182+
using (var memStream = new MemoryStream())
183+
{
184+
OperationWriteHelper.WriteDouble(memStream, 10000.000);
185+
186+
// Read Test
187+
memStream.Position = 0;
188+
using (var streamReader = new StreamReader(memStream))
189+
{
190+
var line = streamReader.ReadToEnd();
191+
Assert.Equal("10000", line);
192+
}
193+
}
194+
}
195+
196+
#if NET
197+
// See here why we are not running on framework - thanks @cremor
198+
// https://stackoverflow.com/a/1658420/631802
199+
[Fact]
200+
public void WriteMinValue()
201+
{
202+
string expected = "-340282346638528859811704183484516925440";
203+
using (var memStream = new MemoryStream())
204+
{
205+
OperationWriteHelper.WriteDouble(memStream, -340282346638528859811704183484516925440d);
206+
207+
// Read Test
208+
memStream.Position = 0;
209+
using (var streamReader = new StreamReader(memStream))
210+
{
211+
var line = streamReader.ReadToEnd();
212+
Assert.Equal(expected, line);
213+
}
214+
}
215+
}
216+
217+
[Fact]
218+
public void WriteMaxValue()
219+
{
220+
string expected = "340282346638528859811704183484516925440";
221+
using (var memStream = new MemoryStream())
222+
{
223+
OperationWriteHelper.WriteDouble(memStream, 340282346638528859811704183484516925440d);
224+
225+
// Read Test
226+
memStream.Position = 0;
227+
using (var streamReader = new StreamReader(memStream))
228+
{
229+
var line = streamReader.ReadToEnd();
230+
Assert.Equal(expected, line);
231+
}
232+
}
233+
}
234+
#endif
235+
}
236+
}

src/UglyToad.PdfPig/Graphics/Operations/OperationWriteHelper.cs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,31 @@
22
{
33
using PdfPig.Core;
44
using System;
5+
using System.Buffers;
56
using System.Buffers.Text;
7+
using System.Globalization;
68
using System.IO;
7-
using System.Text;
89
using Util;
910

1011
internal static class OperationWriteHelper
1112
{
1213
private const byte Whitespace = (byte)' ';
1314
private const byte NewLine = (byte)'\n';
15+
private const byte Zero = (byte)'0';
16+
private const byte Point = (byte)'.';
17+
18+
private static readonly StandardFormat StandardFormatDouble = new StandardFormat('F', 9);
1419

1520
public static void WriteText(this Stream stream, string text, bool appendWhitespace = false)
1621
{
1722
#if NET8_0_OR_GREATER
18-
if (Ascii.IsValid(text))
23+
if (System.Text.Ascii.IsValid(text))
1924
{
2025
Span<byte> buffer = text.Length <= 64
2126
? stackalloc byte[text.Length]
2227
: new byte[text.Length];
2328

24-
Ascii.FromUtf16(text, buffer, out _);
29+
System.Text.Ascii.FromUtf16(text, buffer, out _);
2530

2631
stream.Write(buffer);
2732
}
@@ -75,11 +80,57 @@ public static void WriteNewLine(this Stream stream)
7580

7681
public static void WriteDouble(this Stream stream, double value)
7782
{
78-
Span<byte> buffer = stackalloc byte[32]; // matches dotnet Number.CharStackBufferSize
83+
int stackSize = 32; // matches dotnet Number.CharStackBufferSize
84+
85+
bool success = TryWriteDouble(stream, value, stackSize);
86+
while (!success && stackSize <= 1024)
87+
{
88+
stackSize *= 2;
89+
success = TryWriteDouble(stream, value, stackSize);
90+
}
91+
92+
if (!success)
93+
{
94+
ReadOnlySpan<byte> buffer = System.Text.Encoding.UTF8.GetBytes(value.ToString("F9", CultureInfo.InvariantCulture));
95+
int lastIndex = GetLastSignificantDigitIndex(buffer, buffer.Length);
96+
stream.Write(buffer.Slice(0, lastIndex));
97+
}
98+
}
99+
100+
private static bool TryWriteDouble(Stream stream, double value, int stackSize)
101+
{
102+
System.Diagnostics.Debug.Assert(stackSize <= 1024);
103+
104+
Span<byte> buffer = stackalloc byte[stackSize];
105+
106+
if (Utf8Formatter.TryFormat(value, buffer, out int bytesWritten, StandardFormatDouble))
107+
{
108+
int lastIndex = GetLastSignificantDigitIndex(buffer, bytesWritten);
109+
stream.Write(buffer.Slice(0, lastIndex));
110+
return true;
111+
}
112+
113+
return false;
114+
}
115+
116+
private static int GetLastSignificantDigitIndex(ReadOnlySpan<byte> buffer, int bytesWritten)
117+
{
118+
int lastIndex = bytesWritten;
119+
for (int i = bytesWritten - 1; i > 1; --i)
120+
{
121+
if (buffer[i] != Zero)
122+
{
123+
break;
124+
}
125+
lastIndex--;
126+
}
79127

80-
Utf8Formatter.TryFormat(value, buffer, out int bytesWritten);
128+
if (buffer[lastIndex - 1] == Point)
129+
{
130+
lastIndex--;
131+
}
81132

82-
stream.Write(buffer.Slice(0, bytesWritten));
133+
return lastIndex;
83134
}
84135

85136
public static void WriteNumberText(this Stream stream, int number, string text)

src/UglyToad.PdfPig/Writer/TokenWriter.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -461,11 +461,7 @@ protected virtual void WriteNumber(NumericToken number, Stream outputStream)
461461
}
462462
else
463463
{
464-
Span<byte> buffer = stackalloc byte[32]; // matches dotnet Number.CharStackBufferSize
465-
466-
Utf8Formatter.TryFormat(number.Data, buffer, out int bytesWritten);
467-
468-
outputStream.Write(buffer.Slice(0, bytesWritten));
464+
outputStream.WriteDouble(number.Data);
469465
}
470466

471467
WriteWhitespace(outputStream);

0 commit comments

Comments
 (0)