mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-15 19:54:52 +08:00
#47 improve flate filter performance by streaming all data in single operation
also improves page constructor performance by removing linq and invoking stringbuilder directly. removes page rotation overhead by skipping multiplication for non-rotated pages and using cached transformation matrices for rotations. removes linq from filter provider and shares instances of filter types.
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Text;
|
||||||
using Annotations;
|
using Annotations;
|
||||||
using Graphics.Operations;
|
using Graphics.Operations;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
@@ -104,7 +104,13 @@
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Join(string.Empty, content.Letters.Select(x => x.Value));
|
var builder = new StringBuilder();
|
||||||
|
for (var i = 0; i < content.Letters.Count; i++)
|
||||||
|
{
|
||||||
|
builder.Append(content.Letters[i].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -9,6 +9,10 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public struct PageRotationDegrees : IEquatable<PageRotationDegrees>
|
public struct PageRotationDegrees : IEquatable<PageRotationDegrees>
|
||||||
{
|
{
|
||||||
|
private static readonly TransformationMatrix Rotate90 = TransformationMatrix.FromArray(new[] {0m, -1, 1, 0});
|
||||||
|
private static readonly TransformationMatrix Rotate180 = TransformationMatrix.FromArray(new[] { -1m, 0, 0, -1 });
|
||||||
|
private static readonly TransformationMatrix Rotate270 = TransformationMatrix.FromArray(new[] { 0m, 1, -1, 0 });
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The rotation of the page in degrees clockwise.
|
/// The rotation of the page in degrees clockwise.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -54,26 +58,19 @@
|
|||||||
[Pure]
|
[Pure]
|
||||||
internal TransformationMatrix Rotate(TransformationMatrix matrix)
|
internal TransformationMatrix Rotate(TransformationMatrix matrix)
|
||||||
{
|
{
|
||||||
TransformationMatrix thisMatrix;
|
|
||||||
switch (Value)
|
switch (Value)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
thisMatrix = TransformationMatrix.FromArray(new[]{ 1m, 0, 0, 1 });
|
return matrix;
|
||||||
break;
|
|
||||||
case 90:
|
case 90:
|
||||||
thisMatrix = TransformationMatrix.FromArray(new[] {0m, -1, 1, 0});
|
return Rotate90.Multiply(matrix);
|
||||||
break;
|
|
||||||
case 180:
|
case 180:
|
||||||
thisMatrix = TransformationMatrix.FromArray(new[] {-1m, 0, 0, -1});
|
return Rotate180.Multiply(matrix);
|
||||||
break;
|
|
||||||
case 270:
|
case 270:
|
||||||
thisMatrix = TransformationMatrix.FromArray(new[] {0m, 1, -1, 0});
|
return Rotate270.Multiply(matrix);
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException($"Invalid value for rotation: {Value}.");
|
throw new InvalidOperationException($"Invalid value for rotation: {Value}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return thisMatrix.Multiply(matrix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@@ -76,6 +76,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var memoryStream = new MemoryStream(input))
|
using (var memoryStream = new MemoryStream(input))
|
||||||
|
using (var output = new MemoryStream())
|
||||||
{
|
{
|
||||||
// The first 2 bytes are the header which DeflateStream does not support.
|
// The first 2 bytes are the header which DeflateStream does not support.
|
||||||
memoryStream.ReadByte();
|
memoryStream.ReadByte();
|
||||||
@@ -83,23 +84,8 @@
|
|||||||
|
|
||||||
using (var deflate = new DeflateStream(memoryStream, CompressionMode.Decompress))
|
using (var deflate = new DeflateStream(memoryStream, CompressionMode.Decompress))
|
||||||
{
|
{
|
||||||
var bytes = new List<byte>();
|
deflate.CopyTo(output);
|
||||||
|
return output.ToArray();
|
||||||
var x = deflate.ReadByte();
|
|
||||||
while (x != -1)
|
|
||||||
{
|
|
||||||
bytes.Add((byte)x);
|
|
||||||
x = deflate.ReadByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = new byte[bytes.Count];
|
|
||||||
|
|
||||||
for (var i = 0; i < bytes.Count; i++)
|
|
||||||
{
|
|
||||||
result[i] = bytes[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,35 +2,35 @@
|
|||||||
{
|
{
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using Exceptions;
|
using Exceptions;
|
||||||
using Logging;
|
using Logging;
|
||||||
using Tokens;
|
using Tokens;
|
||||||
|
using Util;
|
||||||
|
|
||||||
internal class MemoryFilterProvider : IFilterProvider
|
internal class MemoryFilterProvider : IFilterProvider
|
||||||
{
|
{
|
||||||
private readonly IReadOnlyDictionary<string, Func<IFilter>> filterFactories;
|
private readonly IReadOnlyDictionary<string, IFilter> filterInstances;
|
||||||
|
|
||||||
public MemoryFilterProvider(IDecodeParameterResolver decodeParameterResolver, IPngPredictor pngPredictor, ILog log)
|
public MemoryFilterProvider(IDecodeParameterResolver decodeParameterResolver, IPngPredictor pngPredictor, ILog log)
|
||||||
{
|
{
|
||||||
IFilter Ascii85Func() => new Ascii85Filter();
|
var ascii85 = new Ascii85Filter();
|
||||||
IFilter AsciiHexFunc() => new AsciiHexDecodeFilter();
|
var asciiHex = new AsciiHexDecodeFilter();
|
||||||
IFilter FlateFunc() => new FlateFilter(decodeParameterResolver, pngPredictor, log);
|
var flate = new FlateFilter(decodeParameterResolver, pngPredictor, log);
|
||||||
IFilter RunLengthFunc() => new RunLengthFilter();
|
var runLength = new RunLengthFilter();
|
||||||
IFilter LzwFunc() => new LzwFilter(decodeParameterResolver, pngPredictor);
|
var lzw = new LzwFilter(decodeParameterResolver, pngPredictor);
|
||||||
|
|
||||||
filterFactories = new Dictionary<string, Func<IFilter>>
|
filterInstances = new Dictionary<string, IFilter>
|
||||||
{
|
{
|
||||||
{NameToken.Ascii85Decode.Data, Ascii85Func},
|
{NameToken.Ascii85Decode.Data, ascii85},
|
||||||
{NameToken.Ascii85DecodeAbbreviation.Data, Ascii85Func},
|
{NameToken.Ascii85DecodeAbbreviation.Data, ascii85},
|
||||||
{NameToken.AsciiHexDecode.Data, AsciiHexFunc},
|
{NameToken.AsciiHexDecode.Data, asciiHex},
|
||||||
{NameToken.AsciiHexDecodeAbbreviation.Data, AsciiHexFunc},
|
{NameToken.AsciiHexDecodeAbbreviation.Data, asciiHex},
|
||||||
{NameToken.FlateDecode.Data, FlateFunc},
|
{NameToken.FlateDecode.Data, flate},
|
||||||
{NameToken.FlateDecodeAbbreviation.Data, FlateFunc},
|
{NameToken.FlateDecodeAbbreviation.Data, flate},
|
||||||
{NameToken.RunLengthDecode.Data, RunLengthFunc},
|
{NameToken.RunLengthDecode.Data, runLength},
|
||||||
{NameToken.RunLengthDecodeAbbreviation.Data, RunLengthFunc},
|
{NameToken.RunLengthDecodeAbbreviation.Data, runLength},
|
||||||
{NameToken.LzwDecode, LzwFunc},
|
{NameToken.LzwDecode, lzw},
|
||||||
{NameToken.LzwDecodeAbbreviation, LzwFunc}
|
{NameToken.LzwDecodeAbbreviation, lzw}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,14 +43,21 @@
|
|||||||
|
|
||||||
if (!dictionary.TryGet(NameToken.Filter, out var token))
|
if (!dictionary.TryGet(NameToken.Filter, out var token))
|
||||||
{
|
{
|
||||||
return new IFilter[0];
|
return EmptyArray<IFilter>.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (token)
|
switch (token)
|
||||||
{
|
{
|
||||||
case ArrayToken filters:
|
case ArrayToken filters:
|
||||||
// TODO: presumably this may be invalid...
|
var result = new IFilter[filters.Data.Count];
|
||||||
return filters.Data.Select(x => GetFilterStrict(((NameToken)x).Data)).ToList();
|
for (var i = 0; i < filters.Data.Count; i++)
|
||||||
|
{
|
||||||
|
var filterToken = filters.Data[i];
|
||||||
|
var filterName = ((NameToken) filterToken).Data;
|
||||||
|
result[i] = GetFilterStrict(filterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
case NameToken name:
|
case NameToken name:
|
||||||
return new[] { GetFilterStrict(name.Data) };
|
return new[] { GetFilterStrict(name.Data) };
|
||||||
default:
|
default:
|
||||||
@@ -60,12 +67,12 @@
|
|||||||
|
|
||||||
private IFilter GetFilterStrict(string name)
|
private IFilter GetFilterStrict(string name)
|
||||||
{
|
{
|
||||||
if (!filterFactories.TryGetValue(name, out var factory))
|
if (!filterInstances.TryGetValue(name, out var factory))
|
||||||
{
|
{
|
||||||
throw new NotSupportedException($"The filter with the name {name} is not supported yet. Please raise an issue.");
|
throw new NotSupportedException($"The filter with the name {name} is not supported yet. Please raise an issue.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return factory();
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<IFilter> GetAllFilters()
|
public IReadOnlyList<IFilter> GetAllFilters()
|
||||||
|
Reference in New Issue
Block a user