mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Compare commits
3 Commits
0.1.13
...
fix-common
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14af2a3858 | ||
|
|
853ce8b93e | ||
|
|
c57cd5008b |
5
.github/workflows/publish_release.yml
vendored
5
.github/workflows/publish_release.yml
vendored
@@ -44,10 +44,5 @@ jobs:
|
||||
.\tools\set-version.ps1 $newVer -UpdateAssemblyAndFileVersion
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
|
||||
git fetch origin master
|
||||
git checkout master
|
||||
git pull
|
||||
|
||||
git commit -am "Increment version to $newVer"
|
||||
git push
|
||||
2
.github/workflows/run_common_crawl_tests.yml
vendored
2
.github/workflows/run_common_crawl_tests.yml
vendored
@@ -55,4 +55,4 @@ jobs:
|
||||
done < tools/common-crawl-ignore.txt
|
||||
|
||||
- name: Run tests against corpus
|
||||
run: dotnet run --project tools/UglyToad.PdfPig.ConsoleRunner/UglyToad.PdfPig.ConsoleRunner.csproj "corpus/extracted" --configuration Release
|
||||
run: dotnet run --project tools/UglyToad.PdfPig.ConsoleRunner/UglyToad.PdfPig.ConsoleRunner.csproj "corpus/extracted"
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -249,4 +249,3 @@ _Pvt_Extensions
|
||||
|
||||
/docs/doxygen
|
||||
/tools/UglyToad.PdfPig.ConsoleRunner/Properties/launchSettings.json
|
||||
/src/UglyToad.PdfPig.Tests/Images/Files/Pdf/indexed-png-with-mask.png
|
||||
|
||||
@@ -11,12 +11,11 @@
|
||||
{
|
||||
private readonly Stream stream;
|
||||
private readonly bool shouldDispose;
|
||||
private byte? peekByte;
|
||||
|
||||
private bool isAtEnd;
|
||||
|
||||
/// <inheritdoc />
|
||||
public long CurrentOffset => peekByte.HasValue ? stream.Position - 1 : stream.Position;
|
||||
public long CurrentOffset => stream.Position;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte CurrentByte { get; private set; }
|
||||
@@ -53,8 +52,7 @@
|
||||
/// <inheritdoc />
|
||||
public bool MoveNext()
|
||||
{
|
||||
var b = peekByte ?? stream.ReadByte();
|
||||
peekByte = null;
|
||||
var b = stream.ReadByte();
|
||||
|
||||
if (b == -1)
|
||||
{
|
||||
@@ -70,21 +68,18 @@
|
||||
/// <inheritdoc />
|
||||
public byte? Peek()
|
||||
{
|
||||
if (!peekByte.HasValue)
|
||||
{
|
||||
var v = stream.ReadByte();
|
||||
var current = CurrentOffset;
|
||||
|
||||
if (v >= 0)
|
||||
{
|
||||
peekByte = (byte)v;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var b = stream.ReadByte();
|
||||
|
||||
stream.Seek(current, SeekOrigin.Begin);
|
||||
|
||||
if (b == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return peekByte;
|
||||
return (byte)b;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -97,7 +92,6 @@
|
||||
public void Seek(long position)
|
||||
{
|
||||
isAtEnd = false;
|
||||
peekByte = null;
|
||||
|
||||
if (position == 0)
|
||||
{
|
||||
@@ -118,15 +112,9 @@
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (peekByte.HasValue)
|
||||
{
|
||||
buffer[0] = peekByte.Value;
|
||||
peekByte = null;
|
||||
|
||||
return Read(buffer.Slice(1)) + 1;
|
||||
}
|
||||
|
||||
int read = stream.Read(buffer);
|
||||
|
||||
if (read > 0)
|
||||
{
|
||||
CurrentByte = buffer[read - 1];
|
||||
|
||||
@@ -224,18 +224,9 @@
|
||||
[Pure]
|
||||
public double TransformX(double x)
|
||||
{
|
||||
return A * x + E; // + C * 0
|
||||
}
|
||||
var xt = A * x + C * 0 + E;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Transform an Y coordinate using this transformation matrix.
|
||||
/// </summary>
|
||||
/// <param name="y">The Y coordinate.</param>
|
||||
/// <returns>The transformed Y coordinate.</returns>
|
||||
public double TransformY(double y)
|
||||
{
|
||||
return D * y + F;
|
||||
return xt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.13</Version>
|
||||
<Version>0.1.12</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -216,37 +216,71 @@
|
||||
yield return group.Select(i => elements[i]).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<List<int>> GroupIndexes(int[] edges)
|
||||
|
||||
/// <summary>
|
||||
/// Group elements using Depth-first search.
|
||||
/// <para>https://en.wikipedia.org/wiki/Depth-first_search</para>
|
||||
/// </summary>
|
||||
/// <param name="edges">The graph. edges[i] = j indicates that there is an edge between i and j.</param>
|
||||
/// <returns>A List of HashSets containing the grouped indexes.</returns>
|
||||
internal static List<HashSet<int>> GroupIndexes(int[] edges)
|
||||
{
|
||||
// Improved thanks to https://github.com/UglyToad/PdfPig/issues/1178
|
||||
var adjacency = new List<int>[edges.Length];
|
||||
int[][] adjacency = new int[edges.Length][];
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
adjacency[i] = new List<int>();
|
||||
}
|
||||
|
||||
// one pass O(n)
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
int j = edges[i];
|
||||
if (j != -1)
|
||||
HashSet<int> matches = new HashSet<int>();
|
||||
if (edges[i] != -1) matches.Add(edges[i]);
|
||||
for (int j = 0; j < edges.Length; j++)
|
||||
{
|
||||
// i <-> j
|
||||
adjacency[i].Add(j);
|
||||
adjacency[j].Add(i);
|
||||
if (edges[j] == i) matches.Add(j);
|
||||
}
|
||||
adjacency[i] = matches.ToArray();
|
||||
}
|
||||
|
||||
List<List<int>> groupedIndexes = new List<List<int>>();
|
||||
List<HashSet<int>> groupedIndexes = new List<HashSet<int>>();
|
||||
bool[] isDone = new bool[edges.Length];
|
||||
|
||||
for (int p = 0; p < edges.Length; p++)
|
||||
{
|
||||
if (isDone[p])
|
||||
if (isDone[p]) continue;
|
||||
groupedIndexes.Add(DfsIterative(p, adjacency, ref isDone));
|
||||
}
|
||||
return groupedIndexes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Group elements using Depth-first search.
|
||||
/// <para>https://en.wikipedia.org/wiki/Depth-first_search</para>
|
||||
/// </summary>
|
||||
/// <param name="edges">The graph. edges[i] = [j, k, l, ...] indicates that there is an edge between i and each element j, k, l, ...</param>
|
||||
/// <returns>A List of HashSets containing the grouped indexes.</returns>
|
||||
internal static List<HashSet<int>> GroupIndexes(int[][] edges)
|
||||
{
|
||||
int[][] adjacency = new int[edges.Length][];
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
HashSet<int> matches = new HashSet<int>();
|
||||
for (int j = 0; j < edges[i].Length; j++)
|
||||
{
|
||||
continue;
|
||||
if (edges[i][j] != -1) matches.Add(edges[i][j]);
|
||||
}
|
||||
|
||||
for (int j = 0; j < edges.Length; j++)
|
||||
{
|
||||
for (int k = 0; k < edges[j].Length; k++)
|
||||
{
|
||||
if (edges[j][k] == i) matches.Add(j);
|
||||
}
|
||||
}
|
||||
adjacency[i] = matches.ToArray();
|
||||
}
|
||||
|
||||
List<HashSet<int>> groupedIndexes = new List<HashSet<int>>();
|
||||
bool[] isDone = new bool[edges.Length];
|
||||
|
||||
for (int p = 0; p < edges.Length; p++)
|
||||
{
|
||||
if (isDone[p]) continue;
|
||||
groupedIndexes.Add(DfsIterative(p, adjacency, ref isDone));
|
||||
}
|
||||
return groupedIndexes;
|
||||
@@ -256,33 +290,22 @@
|
||||
/// Depth-first search
|
||||
/// <para>https://en.wikipedia.org/wiki/Depth-first_search</para>
|
||||
/// </summary>
|
||||
private static List<int> DfsIterative(int s, List<int>[] adj, ref bool[] isDone)
|
||||
private static HashSet<int> DfsIterative(int s, int[][] adj, ref bool[] isDone)
|
||||
{
|
||||
List<int> group = new List<int>();
|
||||
Stack<int> S = new Stack<int>(4);
|
||||
HashSet<int> group = new HashSet<int>();
|
||||
Stack<int> S = new Stack<int>();
|
||||
S.Push(s);
|
||||
|
||||
isDone[s] = true;
|
||||
while (S.Count > 0)
|
||||
{
|
||||
var u = S.Pop();
|
||||
group.Add(u);
|
||||
|
||||
#if NET
|
||||
var currentAdj = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(adj[u]);
|
||||
int count = currentAdj.Length;
|
||||
#else
|
||||
var currentAdj = adj[u];
|
||||
int count = currentAdj.Count;
|
||||
#endif
|
||||
for (int i = 0; i < count; ++i)
|
||||
if (!isDone[u])
|
||||
{
|
||||
var v = currentAdj[i];
|
||||
ref bool done = ref isDone[v];
|
||||
if (!done)
|
||||
group.Add(u);
|
||||
isDone[u] = true;
|
||||
foreach (var v in adj[u])
|
||||
{
|
||||
S.Push(v);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,11 +68,25 @@
|
||||
// update textSequence?
|
||||
|
||||
// update font details to bold
|
||||
var fontDetails = new FontDetails(letter.Font.Name, true, letter.Font.Weight, letter.Font.IsItalic);
|
||||
|
||||
var newLetter = new Letter(letter.Value,
|
||||
letter.GlyphRectangle,
|
||||
letter.StartBaseLine,
|
||||
letter.EndBaseLine,
|
||||
letter.Width,
|
||||
letter.FontSize,
|
||||
fontDetails,
|
||||
letter.RenderingMode,
|
||||
letter.StrokeColor,
|
||||
letter.FillColor,
|
||||
letter.PointSize,
|
||||
letter.TextSequence);
|
||||
|
||||
// update markedContentStack?
|
||||
|
||||
// update letters
|
||||
cleanLetters[duplicatesOverlappingIndex] = letter.AsBold();
|
||||
cleanLetters[duplicatesOverlappingIndex] = newLetter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,12 +60,11 @@
|
||||
letter = new Letter(
|
||||
" ",
|
||||
letter.GlyphRectangle,
|
||||
letter.GlyphRectangleLoose,
|
||||
letter.StartBaseLine,
|
||||
letter.EndBaseLine,
|
||||
letter.Width,
|
||||
letter.FontSize,
|
||||
letter.GetFont()!,
|
||||
letter.Font,
|
||||
letter.RenderingMode,
|
||||
letter.StrokeColor,
|
||||
letter.FillColor,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.13</Version>
|
||||
<Version>0.1.12</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -770,8 +770,6 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
}
|
||||
}
|
||||
|
||||
values.TrimExcess();
|
||||
|
||||
return new Type2CharStrings.CommandSequence(values, commandIdentifiers);
|
||||
}
|
||||
|
||||
|
||||
@@ -257,10 +257,6 @@
|
||||
gidToStringIdAndNameMap[gid++] = pair;
|
||||
}
|
||||
|
||||
#if NET
|
||||
gidToStringIdAndNameMap.TrimExcess();
|
||||
#endif
|
||||
|
||||
glyphIdToStringIdAndName = gidToStringIdAndNameMap;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tables;
|
||||
using UglyToad.PdfPig.Fonts.CompactFontFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Parses TrueType fonts.
|
||||
@@ -60,36 +59,7 @@
|
||||
|
||||
private static TrueTypeFont ParseTables(float version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tables, TrueTypeDataBytes data)
|
||||
{
|
||||
bool isPostScript = false;
|
||||
CompactFontFormatFontCollection? cffFontCollection = null;
|
||||
|
||||
if (tables.TryGetValue(TrueTypeHeaderTable.Cff, out var cffTable))
|
||||
{
|
||||
isPostScript = true;
|
||||
try
|
||||
{
|
||||
/*
|
||||
* The presence of a CFF (Compact Font Format) table in a TrueType font creates a hybrid situation where the font
|
||||
* container uses TrueType structure but contains PostScript-based glyph descriptions. According to the OpenType
|
||||
* specification, when a TrueType font contains a CFF table instead of a traditional glyf table, it indicates
|
||||
* "an OpenType font with PostScript outlines". This creates what's known as an OpenType CFF font, which uses
|
||||
* PostScript Type 2 charstrings for glyph descriptions rather than TrueType quadratic curves.
|
||||
*
|
||||
* This is to fix P2P-33713919.pdf
|
||||
* See https://github.com/BobLd/PdfPig.Rendering.Skia/issues/46
|
||||
* TODO - Add test coverage and need to review if the logic belongs here
|
||||
*/
|
||||
|
||||
data.Seek(cffTable.Offset);
|
||||
var buffer = data.ReadByteArray((int)cffTable.Length);
|
||||
cffFontCollection = CompactFontFormatParser.Parse(new CompactFontFormatData(buffer));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(e);
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
var isPostScript = tables.ContainsKey(TrueTypeHeaderTable.Cff);
|
||||
|
||||
var builder = new TableRegister.Builder();
|
||||
|
||||
@@ -132,7 +102,7 @@
|
||||
{
|
||||
builder.Os2Table = TableParser.Parse<Os2Table>(os2Table, data, builder);
|
||||
}
|
||||
|
||||
|
||||
if (!isPostScript)
|
||||
{
|
||||
if (!tables.TryGetValue(TrueTypeHeaderTable.Loca, out var indexToLocationHeaderTable))
|
||||
@@ -155,7 +125,7 @@
|
||||
|
||||
OptionallyParseTables(tables, data, builder);
|
||||
|
||||
return new TrueTypeFont(version, tables, builder.Build(), cffFontCollection);
|
||||
return new TrueTypeFont(version, tables, builder.Build());
|
||||
}
|
||||
|
||||
internal static NameTable GetNameTable(TrueTypeDataBytes data)
|
||||
@@ -164,7 +134,7 @@
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
|
||||
// Read these data points to move to the correct data location.
|
||||
data.Read32Fixed();
|
||||
int numberOfTables = data.ReadUnsignedShort();
|
||||
|
||||
@@ -121,23 +121,14 @@
|
||||
|
||||
for (var i = 0; i < glyphCount; i++)
|
||||
{
|
||||
var offset = offsets[i];
|
||||
|
||||
if (offsets[i + 1] <= offset)
|
||||
if (offsets[i + 1] <= offsets[i])
|
||||
{
|
||||
// empty glyph
|
||||
result[i] = emptyGlyph;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Invalid table, just sub in the empty glyph
|
||||
if (offset >= data.Length)
|
||||
{
|
||||
result[i] = emptyGlyph;
|
||||
continue;
|
||||
}
|
||||
|
||||
data.Seek(offset);
|
||||
data.Seek(offsets[i]);
|
||||
|
||||
var contourCount = data.ReadSignedShort();
|
||||
|
||||
@@ -240,15 +231,9 @@
|
||||
flags = (CompositeGlyphFlags)data.ReadUnsignedShort();
|
||||
var glyphIndex = data.ReadUnsignedShort();
|
||||
|
||||
if (glyphIndex >= glyphs.Length)
|
||||
{
|
||||
// Unsure why this happens but fixes #1213
|
||||
continue; // TODO - Is there a better fix?
|
||||
}
|
||||
var childGlyph = glyphs[glyphIndex];
|
||||
|
||||
IGlyphDescription? childGlyph = glyphs[glyphIndex];
|
||||
|
||||
if (childGlyph is null)
|
||||
if (childGlyph == null)
|
||||
{
|
||||
if (!compositeLocations.TryGetValue(glyphIndex, out var missingComposite))
|
||||
{
|
||||
@@ -325,7 +310,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
builderGlyph ??= emptyGlyph;
|
||||
builderGlyph = builderGlyph ?? emptyGlyph;
|
||||
|
||||
return new Glyph(false, builderGlyph.Instructions, builderGlyph.EndPointsOfContours, builderGlyph.Points, compositeLocation.Bounds);
|
||||
}
|
||||
@@ -344,15 +329,7 @@
|
||||
|
||||
for (int j = 0; j < numberOfRepeats; j++)
|
||||
{
|
||||
int p = i + j + 1;
|
||||
if (p >= result.Length)
|
||||
{
|
||||
// Unsure why this happens but fixes #1199
|
||||
// TODO - Is there a better fix?
|
||||
break;
|
||||
}
|
||||
|
||||
result[p] = result[i];
|
||||
result[i + j + 1] = result[i];
|
||||
}
|
||||
|
||||
i += numberOfRepeats;
|
||||
@@ -432,7 +409,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CompositeComponent
|
||||
private class CompositeComponent
|
||||
{
|
||||
public int Index { get; }
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
using Core;
|
||||
using Parser;
|
||||
using Tables.CMapSubTables;
|
||||
using UglyToad.PdfPig.Fonts.CompactFontFormat;
|
||||
|
||||
/// <summary>
|
||||
/// A TrueType font.
|
||||
@@ -55,33 +54,17 @@
|
||||
/// </summary>
|
||||
public int NumberOfTables { get; }
|
||||
|
||||
// TODO - It would be better to use 'PdfCidCompactFontFormatFont' but the class is not accessible from here.
|
||||
private readonly CompactFontFormatFontCollection? cffFontCollection;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TrueTypeFont"/>.
|
||||
/// </summary>
|
||||
internal TrueTypeFont(float version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tableHeaders, TableRegister tableRegister, CompactFontFormatFontCollection? cffFontCollection)
|
||||
internal TrueTypeFont(float version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tableHeaders, TableRegister tableRegister)
|
||||
{
|
||||
Version = version;
|
||||
TableHeaders = tableHeaders ?? throw new ArgumentNullException(nameof(tableHeaders));
|
||||
TableRegister = tableRegister ?? throw new ArgumentNullException(nameof(tableRegister));
|
||||
NumberOfTables = tableHeaders.Count;
|
||||
|
||||
/*
|
||||
* The presence of a CFF (Compact Font Format) table in a TrueType font creates a hybrid situation where the font
|
||||
* container uses TrueType structure but contains PostScript-based glyph descriptions. According to the OpenType
|
||||
* specification, when a TrueType font contains a CFF table instead of a traditional glyf table, it indicates
|
||||
* "an OpenType font with PostScript outlines". This creates what's known as an OpenType CFF font, which uses
|
||||
* PostScript Type 2 charstrings for glyph descriptions rather than TrueType quadratic curves.
|
||||
*
|
||||
* This is to fix P2P-33713919.pdf
|
||||
* See https://github.com/BobLd/PdfPig.Rendering.Skia/issues/46
|
||||
* TODO - Add test coverage and need to review if the logic belongs here
|
||||
*/
|
||||
this.cffFontCollection = cffFontCollection;
|
||||
|
||||
if (TableRegister.CMapTable is not null)
|
||||
if (TableRegister.CMapTable != null)
|
||||
{
|
||||
const int encodingSymbol = 0;
|
||||
const int encodingUnicode = 1;
|
||||
@@ -89,19 +72,19 @@
|
||||
|
||||
foreach (var subTable in TableRegister.CMapTable.SubTables)
|
||||
{
|
||||
if (WindowsSymbolCMap is null
|
||||
if (WindowsSymbolCMap == null
|
||||
&& subTable.PlatformId == TrueTypeCMapPlatform.Windows
|
||||
&& subTable.EncodingId == encodingSymbol)
|
||||
{
|
||||
WindowsSymbolCMap = subTable;
|
||||
}
|
||||
else if (WindowsUnicodeCMap is null
|
||||
else if (WindowsUnicodeCMap == null
|
||||
&& subTable.PlatformId == TrueTypeCMapPlatform.Windows
|
||||
&& subTable.EncodingId == encodingUnicode)
|
||||
{
|
||||
WindowsUnicodeCMap = subTable;
|
||||
}
|
||||
else if (MacRomanCMap is null
|
||||
else if (MacRomanCMap == null
|
||||
&& subTable.PlatformId == TrueTypeCMapPlatform.Macintosh
|
||||
&& subTable.EncodingId == encodingMacRoman)
|
||||
{
|
||||
@@ -124,36 +107,8 @@
|
||||
{
|
||||
boundingBox = default(PdfRectangle);
|
||||
|
||||
if (TableRegister.GlyphTable is null)
|
||||
if (TableRegister.GlyphTable == null)
|
||||
{
|
||||
if (cffFontCollection is not null)
|
||||
{
|
||||
/*
|
||||
* The presence of a CFF (Compact Font Format) table in a TrueType font creates a hybrid situation where the font
|
||||
* container uses TrueType structure but contains PostScript-based glyph descriptions. According to the OpenType
|
||||
* specification, when a TrueType font contains a CFF table instead of a traditional glyf table, it indicates
|
||||
* "an OpenType font with PostScript outlines". This creates what's known as an OpenType CFF font, which uses
|
||||
* PostScript Type 2 charstrings for glyph descriptions rather than TrueType quadratic curves.
|
||||
*
|
||||
* This is to fix P2P-33713919.pdf
|
||||
* See https://github.com/BobLd/PdfPig.Rendering.Skia/issues/46
|
||||
* TODO - Add test coverage and need to review if the logic belongs here
|
||||
*/
|
||||
|
||||
var name = cffFontCollection.FirstFont.GetCharacterName(characterCode, true); // TODO cid?
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bbox = cffFontCollection.FirstFont.GetCharacterBoundingBox(name);
|
||||
if (bbox.HasValue)
|
||||
{
|
||||
boundingBox = bbox.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -188,30 +143,8 @@
|
||||
{
|
||||
path = null;
|
||||
|
||||
if (TableRegister.GlyphTable is null)
|
||||
if (TableRegister.GlyphTable == null)
|
||||
{
|
||||
if (cffFontCollection is not null)
|
||||
{
|
||||
/*
|
||||
* The presence of a CFF (Compact Font Format) table in a TrueType font creates a hybrid situation where the font
|
||||
* container uses TrueType structure but contains PostScript-based glyph descriptions. According to the OpenType
|
||||
* specification, when a TrueType font contains a CFF table instead of a traditional glyf table, it indicates
|
||||
* "an OpenType font with PostScript outlines". This creates what's known as an OpenType CFF font, which uses
|
||||
* PostScript Type 2 charstrings for glyph descriptions rather than TrueType quadratic curves.
|
||||
*
|
||||
* This is to fix P2P-33713919.pdf
|
||||
* See https://github.com/BobLd/PdfPig.Rendering.Skia/issues/46
|
||||
* TODO - Add test coverage and need to review if the logic belongs here
|
||||
*/
|
||||
|
||||
var name = cffFontCollection.FirstFont.GetCharacterName(characterCode, true);
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return cffFontCollection.FirstFont.TryGetPath(name, out path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -255,7 +188,7 @@
|
||||
{
|
||||
width = 0;
|
||||
|
||||
if (TableRegister.HorizontalMetricsTable is null)
|
||||
if (TableRegister.HorizontalMetricsTable == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -277,7 +210,7 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TableRegister.CMapTable is null)
|
||||
if (TableRegister.CMapTable == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
private const int PfbFileIndicator = 0x80;
|
||||
|
||||
private static readonly char[] Separators = [' '];
|
||||
|
||||
private static readonly Type1EncryptedPortionParser EncryptedPortionParser = new Type1EncryptedPortionParser();
|
||||
|
||||
/// <summary>
|
||||
@@ -50,9 +48,9 @@
|
||||
{
|
||||
throw new InvalidFontFormatException("The Type1 program did not start with '%!'.");
|
||||
}
|
||||
|
||||
|
||||
string name;
|
||||
var parts = comment.Data.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
|
||||
var parts = comment.Data.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
name = parts[1];
|
||||
@@ -62,9 +60,11 @@
|
||||
name = "Unknown";
|
||||
}
|
||||
|
||||
while (scanner.MoveNext() && scanner.CurrentToken is CommentToken)
|
||||
var comments = new List<string>();
|
||||
|
||||
while (scanner.MoveNext() && scanner.CurrentToken is CommentToken commentToken)
|
||||
{
|
||||
// We ignore comments
|
||||
comments.Add(commentToken.Data);
|
||||
}
|
||||
|
||||
var dictionaries = new List<DictionaryToken>();
|
||||
@@ -441,7 +441,7 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
private sealed class PreviousTokenSet
|
||||
private class PreviousTokenSet
|
||||
{
|
||||
private readonly IToken[] tokens = new IToken[3];
|
||||
|
||||
|
||||
@@ -62,11 +62,6 @@
|
||||
/// </summary>
|
||||
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
||||
{
|
||||
if (string.Equals(characterName, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var glyph = GetCharacterPath(characterName);
|
||||
return PdfSubpath.GetBoundingRectangle(glyph);
|
||||
}
|
||||
@@ -99,13 +94,8 @@
|
||||
/// <summary>
|
||||
/// Get the pdfpath for the character with the given name.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PdfSubpath>? GetCharacterPath(string characterName)
|
||||
public IReadOnlyList<PdfSubpath> GetCharacterPath(string characterName)
|
||||
{
|
||||
if (string.Equals(characterName, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!CharStrings.TryGenerate(characterName, out var glyph))
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.13</Version>
|
||||
<Version>0.1.12</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
|
||||
<Nullable>annotations</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\AdobeFontMetrics\*" />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Tests.ContentStream
|
||||
{
|
||||
using PdfPig.Core;
|
||||
using System.Globalization;
|
||||
|
||||
public class IndirectReferenceTests
|
||||
{
|
||||
@@ -34,59 +33,50 @@
|
||||
[Fact]
|
||||
public void IndirectReferenceHashTest()
|
||||
{
|
||||
CultureInfo lastCulture = CultureInfo.CurrentCulture;
|
||||
CultureInfo.CurrentCulture = new CultureInfo("en-US");
|
||||
try
|
||||
{
|
||||
var reference0 = new IndirectReference(1574, 690);
|
||||
Assert.Equal(1574, reference0.ObjectNumber);
|
||||
Assert.Equal(690, reference0.Generation);
|
||||
var reference0 = new IndirectReference(1574, 690);
|
||||
Assert.Equal(1574, reference0.ObjectNumber);
|
||||
Assert.Equal(690, reference0.Generation);
|
||||
|
||||
var reference1 = new IndirectReference(-1574, 690);
|
||||
Assert.Equal(-1574, reference1.ObjectNumber);
|
||||
Assert.Equal(690, reference1.Generation);
|
||||
var reference1 = new IndirectReference(-1574, 690);
|
||||
Assert.Equal(-1574, reference1.ObjectNumber);
|
||||
Assert.Equal(690, reference1.Generation);
|
||||
|
||||
var reference2 = new IndirectReference(58949797283757, 16);
|
||||
Assert.Equal(58949797283757, reference2.ObjectNumber);
|
||||
Assert.Equal(16, reference2.Generation);
|
||||
var reference2 = new IndirectReference(58949797283757, 16);
|
||||
Assert.Equal(58949797283757, reference2.ObjectNumber);
|
||||
Assert.Equal(16, reference2.Generation);
|
||||
|
||||
var reference3 = new IndirectReference(-58949797283757, ushort.MaxValue);
|
||||
Assert.Equal(-58949797283757, reference3.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference3.Generation);
|
||||
var reference3 = new IndirectReference(-58949797283757, ushort.MaxValue);
|
||||
Assert.Equal(-58949797283757, reference3.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference3.Generation);
|
||||
|
||||
var reference4 = new IndirectReference(140737488355327, ushort.MaxValue);
|
||||
Assert.Equal(140737488355327, reference4.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference4.Generation);
|
||||
var reference4 = new IndirectReference(140737488355327, ushort.MaxValue);
|
||||
Assert.Equal(140737488355327, reference4.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference4.Generation);
|
||||
|
||||
var reference5 = new IndirectReference(-140737488355327, ushort.MaxValue);
|
||||
Assert.Equal(-140737488355327, reference5.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference5.Generation);
|
||||
var reference5 = new IndirectReference(-140737488355327, ushort.MaxValue);
|
||||
Assert.Equal(-140737488355327, reference5.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference5.Generation);
|
||||
|
||||
var ex0 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(140737488355328, 0));
|
||||
Assert.StartsWith("Object number must be between -140,737,488,355,327 and 140,737,488,355,327.", ex0.Message);
|
||||
var ex1 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(-140737488355328, 0));
|
||||
Assert.StartsWith("Object number must be between -140,737,488,355,327 and 140,737,488,355,327.", ex1.Message);
|
||||
var ex0 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(140737488355328, 0));
|
||||
Assert.StartsWith("Object number must be between -140,737,488,355,327 and 140,737,488,355,327.", ex0.Message);
|
||||
var ex1 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(-140737488355328, 0));
|
||||
Assert.StartsWith("Object number must be between -140,737,488,355,327 and 140,737,488,355,327.", ex1.Message);
|
||||
|
||||
var ex2 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(1574, -1));
|
||||
Assert.StartsWith("Generation number must not be a negative value.", ex2.Message);
|
||||
|
||||
// We make sure object number is still correct even if generation is not
|
||||
var reference6 = new IndirectReference(1574, int.MaxValue);
|
||||
Assert.Equal(1574, reference6.ObjectNumber);
|
||||
|
||||
var reference7 = new IndirectReference(-1574, ushort.MaxValue + 10);
|
||||
Assert.Equal(-1574, reference7.ObjectNumber);
|
||||
|
||||
var ex2 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(1574, -1));
|
||||
Assert.StartsWith("Generation number must not be a negative value.", ex2.Message);
|
||||
var reference9 = new IndirectReference(-140737488355327, ushort.MaxValue + 10);
|
||||
Assert.Equal(-140737488355327, reference9.ObjectNumber);
|
||||
|
||||
// We make sure object number is still correct even if generation is not
|
||||
var reference6 = new IndirectReference(1574, int.MaxValue);
|
||||
Assert.Equal(1574, reference6.ObjectNumber);
|
||||
|
||||
var reference7 = new IndirectReference(-1574, ushort.MaxValue + 10);
|
||||
Assert.Equal(-1574, reference7.ObjectNumber);
|
||||
|
||||
var reference9 = new IndirectReference(-140737488355327, ushort.MaxValue + 10);
|
||||
Assert.Equal(-140737488355327, reference9.ObjectNumber);
|
||||
|
||||
var reference10 = new IndirectReference(140737488355327, ushort.MaxValue * 10);
|
||||
Assert.Equal(140737488355327, reference10.ObjectNumber);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CultureInfo.CurrentCulture = lastCulture;
|
||||
}
|
||||
var reference10 = new IndirectReference(140737488355327, ushort.MaxValue * 10);
|
||||
Assert.Equal(140737488355327, reference10.ObjectNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -2,23 +2,16 @@
|
||||
{
|
||||
internal static class DlaHelper
|
||||
{
|
||||
private static readonly string DlaFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Dla", "Documents"));
|
||||
private static readonly string IntegrationFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
|
||||
|
||||
public static string GetDocumentPath(string name, bool isPdf = true)
|
||||
{
|
||||
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Dla", "Documents"));
|
||||
|
||||
if (!name.EndsWith(".pdf") && isPdf)
|
||||
{
|
||||
name += ".pdf";
|
||||
}
|
||||
|
||||
string doc = Path.Combine(DlaFolder, name);
|
||||
if (File.Exists(doc))
|
||||
{
|
||||
return doc;
|
||||
}
|
||||
|
||||
return Path.Combine(IntegrationFolder, name);
|
||||
return Path.Combine(documentFolder, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Random Big Title Lorem Ipsum text with lists Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sodales gravida felis, in rhoncus velit rutrum at. Curabitur hendrerit dapibus nulla, ut hendrerit diam imperdiet quis. Pellentesque id neque ali-quam, pulvinar neque in, vulputate elit. Pel-lentesque ut erat sit amet massa suscipit ullamcor-per. Sed porttitor viverra convallis. Duis vitae sem-per metus. Pellentesque eros purus, egestas eget velit eget, elementum aliquet velit. Suspendisse potenti. Nulla vitae massa rutrum, blandit erat vi-tae, aliquet arcu. Aenean feugiat leo sed enim sodales vehicula. Sus-pendisse tempus hendrerit magna sagittis dictum. Duis ultrices dapibus egestas. Cras eu felis eu lectus suscipit pharetra at at lacus. Nulla facilisi. Proin in-terdum faucibus elit nec rhoncus. Proin sodaless metus sed tincidunt hendrerit. • Duis leo enim, convallis sit amet orci eget, condimentum mattis mi ; • Etiam dolor erat, maximus nec mi sed, con-vallis convallis orci ; • Morbi viverra diam in diam cursus, vitae aliquet velit tempus ; • Donec at nisi fermentum, ultricies odio eget, egestas massa at nisi fermentum, ul-tricies odio eget, egestas massa. Donec ultricies cursus odio sed rutrum. Nam ven-enatis metus vitae elementum scelerisque. Ali-quam tempor sapien at turpis posuere eleifend. Sed placerat posuere nunc vel efficitur. Quisque auctor felis vel lectus dictum fringilla. Quisque vo-lutpat pulvinar© elit. Aliquam ultrices feugiat ali-quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sus-pendisse imperdiet ex lorem, porta bibendum pu-rus ultricies id. Integer vel lacus sapien. Nam sodales ante eu risus facilisis placerat. Aliquam suscipit pulvinar ultricies. Aenean pulvinar, ex ac fermentum egestas, erat nisi feugiat velit, vitae suscipit tellus odio vitae quam. Morbi elementum sem in elit posuere, non rhoncus magna fringilla. Phasellus cursus in dolor laoreet rutrum. Curabitur tincidunt risus ullamcor-per, vehicula velit at, pulvinar metus. Donec quis ante leo. Vivamus pharetra, nisl ac vehi-cula tempor, tellus lacus aliquam sapien, eu congue nibh quam sit amet odio. Quisque metus arcu, sem-per nec consequat eu, pellentesque vel sem. Sed purus risus, tincidunt¹ sit amet dictum vitae, euis-mod id nibh. Praesent ultrices libero quis enim porta, sit amet pellentesque augue pretium. Viva-mus nec molestie nunc. Donec finibus enim nec tel-lus laoreet elementum. Curabitur efficitur placerat dolor et semper. Morbi laoreet dui eu tortor luctus, nec ultrices do-lor ullamcorper. Ut gravida sed nisl a efficitur. In tincidunt orci a condimentum semper. Suspendisse scelerisque fermentum lacinia. Vestibulum sit amet ornare tellus, aliquet euismod mauris. Cras suscipit venenatis ultrices. Sed diam erat, aliquet a tellus ut, viverra 12º ongue magna. Cras id justo tortor. Mauris in tortor vulputate, pellentesque nisl ac, facilisis ligula. Class aptent taciti² sociosqu ad li-tora torquent per conubia nostra³, per inceptos himenaeos. Aliquam eget dolor turpis. Mauris id molestie tellus. Sed elementum molestie nisi, at ali-quet sem vehicula nec. Morbi tempus nulla enim, a vulputate magna €51 luctus £66 eu. Fusce sodales, libero quis suscipit ultrices, metus erat auctor urna, sit amet dictum arcu tortor eu metus. 1. Ut volutpat, velit at interdum consectetur, nisl lorem consequat mauris, feugiat dignissim tellus massa ut nisl. 2. Praesent at est nisi. Pellentesque rutrum lorem sed dui accumsan gravida. 3. Pellentesque dictum nisl vitae urna luctus, congue pulvinar mi congue. Morbi vestibulum varius ipsum nec molestie. Proin auctor efficitur diam ut luctus. Phasellus cursus maximus ultricies. Mauris eu neque ut sem semper tempus. Curabitur non lorem eu nunc lobortis vi-verra at in diam. Pellentesque euismod purus a leo lobortis tempor. Maecenas mollis ligula at sem sus-cipit fringilla. Mauris sollicitudin tincidunt lectus id tempor. Etiam ut nisi est.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,84 +4,19 @@
|
||||
|
||||
public class NearestNeighbourWordExtractorTests
|
||||
{
|
||||
public static IEnumerable<object[]> DataWords => new[]
|
||||
[Fact]
|
||||
public void Words2559Doc()
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
"2559 words.pdf",
|
||||
5118,
|
||||
2559
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"fseprd1102849.pdf",
|
||||
12903,
|
||||
11177
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"90 180 270 rotated.pdf",
|
||||
589,
|
||||
292
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"complex rotated.pdf",
|
||||
805,
|
||||
403
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no horizontal distance.pdf",
|
||||
4,
|
||||
2
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no vertical distance.pdf",
|
||||
22,
|
||||
10
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no vertical horizontal distance.pdf",
|
||||
4,
|
||||
2
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Random 2 Columns Lists Hyph - Justified.pdf",
|
||||
1191,
|
||||
607
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"caly-issues-56-1.pdf",
|
||||
184,
|
||||
156
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"caly-issues-58-2.pdf",
|
||||
49,
|
||||
49
|
||||
},
|
||||
};
|
||||
// Microsoft Word count of words = 2559
|
||||
|
||||
[SkippableTheory]
|
||||
[MemberData(nameof(DataWords))]
|
||||
public void WordCount(string path, int wordCount, int noSpacesWordCount)
|
||||
{
|
||||
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath(path)))
|
||||
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath("2559 words.pdf")))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var words = NearestNeighbourWordExtractor.Instance.GetWords(page.Letters).ToArray();
|
||||
|
||||
Assert.Equal(wordCount, words.Length);
|
||||
|
||||
var noSpacesWords = words.Where(x => !string.IsNullOrEmpty(x.Text.Trim())).ToArray();
|
||||
|
||||
Assert.Equal(noSpacesWordCount, noSpacesWords.Length);
|
||||
Assert.Equal(2559, noSpacesWords.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
namespace UglyToad.PdfPig.Tests.Dla
|
||||
{
|
||||
using PdfFonts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UglyToad.PdfPig.Content;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.ReadingOrderDetector;
|
||||
using UglyToad.PdfPig.Core;
|
||||
|
||||
public class UnsupervisedReadingOrderTests
|
||||
{
|
||||
@@ -60,11 +62,10 @@
|
||||
private static TextBlock CreateFakeTextBlock(PdfRectangle boundingBox)
|
||||
{
|
||||
var letter = new Letter("a",
|
||||
boundingBox,
|
||||
boundingBox,
|
||||
boundingBox.BottomLeft,
|
||||
boundingBox.BottomRight,
|
||||
10, 1, (FontDetails)null, TextRenderingMode.NeitherClip, null, null, 0, 0);// These don't matter
|
||||
10, 1, null, TextRenderingMode.NeitherClip, null, null, 0, 0);// These don't matter
|
||||
var leftTextBlock = new TextBlock(new[] { new TextLine(new[] { new Word(new[] { letter }) }) });
|
||||
return leftTextBlock;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.Encodings
|
||||
{
|
||||
using System.Text;
|
||||
using PdfPig.Fonts;
|
||||
|
||||
public class GlyphListTests
|
||||
@@ -93,7 +92,7 @@
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
[Fact(Skip = "TODO - String don't match")]
|
||||
public void NameToUnicodeConvertAglSpecification()
|
||||
{
|
||||
// https://github.com/adobe-type-tools/agl-specification?tab=readme-ov-file#3-examples
|
||||
@@ -104,22 +103,7 @@
|
||||
|
||||
var result = list.NameToUnicode("Lcommaaccent_uni20AC0308_u1040C.alternate");
|
||||
|
||||
// This value is not encodable in single characters in UTF-16, so we get a surrogate pair for the final unicode character
|
||||
Assert.Equal("\u013B\u20AC\u0308\uD801\uDC0C", result);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
// But in .Net we can get the unicode rune values to verify that this is really the expected value
|
||||
var runes = result.EnumerateRunes().ToList();
|
||||
|
||||
Assert.Equal(4, runes.Count);
|
||||
|
||||
Assert.Equal(0x013B, runes[0].Value);
|
||||
Assert.Equal(0x20AC, runes[1].Value);
|
||||
Assert.Equal(0x0308, runes[2].Value);
|
||||
Assert.Equal(0x1040C, runes[3].Value);
|
||||
#endif
|
||||
// Ok, so we know this is what the actual string is. Now lets encode that last value the C# way
|
||||
Assert.Equal("\u013B\u20AC\u0308\U0001040C", result);
|
||||
Assert.Equal("\u013B\u20AC\u0308\u1040C", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using UglyToad.PdfPig.Tests.Dla;
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
|
||||
{
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Geometry;
|
||||
|
||||
public class Linux
|
||||
{
|
||||
@@ -69,10 +68,7 @@ namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
|
||||
Assert.Equal(expectedData.TopLeft.Y, current.GlyphRectangle.TopLeft.Y, 6);
|
||||
Assert.Equal(expectedData.Width, current.GlyphRectangle.Width, 6);
|
||||
Assert.Equal(expectedData.Height, current.GlyphRectangle.Height, 6);
|
||||
Assert.Equal(expectedData.Rotation, current.GlyphRectangle.Rotation, 3);
|
||||
|
||||
Assert.True(current.GlyphRectangle.IntersectsWith(current.GlyphRectangleLoose));
|
||||
Assert.Equal(current.GlyphRectangle.Rotation, current.GlyphRectangleLoose.Rotation, 3);
|
||||
Assert.Equal(expectedData.Rotation, current.GlyphRectangle.Rotation, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,19 +229,5 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
Assert.NotNull(font.TableRegister.NameTable);
|
||||
Assert.NotEmpty(font.TableRegister.NameTable.NameRecords);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse12623CorruptFileAndGetGlyphs()
|
||||
{
|
||||
var bytes = TrueTypeTestHelper.GetFileBytes("corrupt-12623");
|
||||
|
||||
var input = new TrueTypeDataBytes(new MemoryInputBytes(bytes));
|
||||
|
||||
var font = TrueTypeFontParser.Parse(input);
|
||||
|
||||
Assert.NotNull(font);
|
||||
|
||||
font.TryGetPath(1, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -270,10 +270,10 @@
|
||||
GetCurrentState().FontState.WordSpacing = spacing;
|
||||
}
|
||||
|
||||
public void ModifyCurrentTransformationMatrix(TransformationMatrix value)
|
||||
public void ModifyCurrentTransformationMatrix(double[] value)
|
||||
{
|
||||
var state = GetCurrentState();
|
||||
state.CurrentTransformationMatrix = value.Multiply(state.CurrentTransformationMatrix);
|
||||
var ctm = GetCurrentState().CurrentTransformationMatrix;
|
||||
GetCurrentState().CurrentTransformationMatrix = TransformationMatrix.FromArray(value).Multiply(ctm);
|
||||
}
|
||||
|
||||
public void SetCharacterSpacing(double spacing)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
@@ -1,55 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
public class CMapLocalCachingTests
|
||||
{
|
||||
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents")));
|
||||
private static readonly Lazy<string> DlaFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Dla", "Documents")));
|
||||
|
||||
public static object[][] DocumentsData = new object[][]
|
||||
{
|
||||
["68-1990-01_A.pdf"],
|
||||
["Type0 Font.pdf"],
|
||||
["11194059_2017-11_de_s.pdf"],
|
||||
["2108.11480.pdf"],
|
||||
["reference-2-numeric-error.pdf"],
|
||||
["MOZILLA-3136-0.pdf"],
|
||||
["FICTIF_TABLE_INDEX.pdf"],
|
||||
["Approved_Document_B__fire_safety__volume_2_-_Buildings_other_than_dwellings__2019_edition_incorporating_2020_and_2022_amendments.pdf"],
|
||||
["dotnet-ai.pdf"],
|
||||
["Old Gutnish Internet Explorer.pdf"],
|
||||
["Random 2 Columns Lists Hyph - Justified.pdf"]
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DocumentsData))]
|
||||
public void CheckText(string documentName)
|
||||
{
|
||||
string fullPath = Path.Combine(DocumentFolder.Value, documentName);
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
fullPath = Path.Combine(DlaFolder.Value, documentName);
|
||||
}
|
||||
|
||||
Assert.True(File.Exists(fullPath));
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
using (var document = PdfDocument.Open(fullPath, new ParsingOptions { UseLenientParsing = true }))
|
||||
{
|
||||
for (var i = 0; i < document.NumberOfPages; i++)
|
||||
{
|
||||
var page = document.GetPage(i + 1);
|
||||
sb.Append(page.Text);
|
||||
}
|
||||
}
|
||||
|
||||
//File.WriteAllText(Path.ChangeExtension(fullPath, "txt"), sb.ToString());
|
||||
|
||||
string expected = File.ReadAllText(Path.ChangeExtension(fullPath, "txt"));
|
||||
Assert.Equal(expected, sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,5 @@
|
||||
using var document = PdfDocument.Open(path);
|
||||
Assert.Equal(3, document.NumberOfPages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanReadDocumentWithCircularXRef()
|
||||
{
|
||||
string path = IntegrationHelpers.GetSpecificTestDocumentPath("B17-2000-transportation-fuels.pdf");
|
||||
|
||||
// If parser can't deal with xrefs that have circular references then
|
||||
// opening the document will loop forever
|
||||
using var document = PdfDocument.Open(path);
|
||||
|
||||
Assert.Equal(1, document.NumberOfPages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1 +0,0 @@
|
||||
TypeWeekly newspaperEditorJoe StuverFoundedOctober, 1918 (asthe BroadusIndependent), 1935(as the PowderRiver CountyExaminer), and1965 (as thePowder RiverExaminer)Headquarters119 1/2 N. ParkAve.Broadus, MT 59317United StatesPowder River ExaminerPowder River ExaminerThe Powder River Examiner, originally established in October, 1918 asthe Broadus Independent, is the only newspaper printed in Powder RiverCounty, Montana, and is located in the county seat of Broadus.The Broadus Independent was first published in Broadus, Montana inOctober, 1918, and continued until February, 1919.From March 6, 1919 until April 17, 1919, the paper was published inOlive, Montana as the Olive Branch.The Broadus Independent was published weekly from April 24, 1919until 1935.The Powder River County Examiner replaced the BroadusIndependent in 1935, beginning publication and continuing weekly until1965.In 1965 the newspaper's name was shortened to Powder RiverExaminer, and remains that today.Broadus Independent, Broadus, Montana, October, 1918-February, 1919.Olive Branch, Olive, Montana, March 6, 1919 – April 17, 1919.Broadus Independent, Broadus, Montana, April 24, 1919 – 1935.Powder River County Examiner, Broadus, Montana, 1935-1965.Powder River Examiner, Broadus, Montana, 1965-current.Joe Stuver, (current editor)Retrieved from "https://en.wikipedia.org/w/index.php?title=Powder_River_Examiner&oldid=747264669"This page was last edited on 1 November 2016, at 11:53.Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By usingthis site, you agree to the Terms of Use and Privacy Policy. Wikipedia® is a registered trademark of the WikimediaFoundation, Inc., a non-profit organization.HistoryPreceding TitlesNotable contributorsPowder River Examiner - Wikipediahttps://en.wikipedia.org/wiki/Powder_River_Examiner1 of 130/03/2018, 03:50
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
(Jl σ3 法海阔』咱国司被NA咿清峭阉且啊辛茹海耀表3-19车辆齿轮用钢系列牌号化学成分(质量分数)(%)国内牌号国外牌号c Si Mn p s Ni Cr Mo Al Cu Ti B 16CrMnTiH 0.13~0.18 0. \Kl -1. 20 20CrMnTiHI 20CrMnTiH2 。.80 -1. 10 1.00-1.30 20CrMnTiH3 0. 17 -0. 37军三0.0350. 04 -0. 10 0.18句0.2320CrMnTiH4 20CrMnTiH5 0. \Kl -1. 25 I.I。”1.4520CrMnTiH6 16MnCrH 16MnCr5 0. 14 -0. 20 I. 00~1.40 O.\KJ-1.20 20MnCrH 20MnCr5 0.17~0.23 1.10-1.50 1.00-1.30 0.02~ 罢王0.20运0.120.02~0.仍525MnCrH 25MnCr5 0.23~0.28 0.055 o. ro -o. so0. 80-1.10 28MnCrH 28MnCr5 0. 25 -0. 30 运0.15运0.1016CrMnBH ZF6 0. 13 -0. 18。.80-1.100.001 -18CrMnBH ZF7 0.15 -0. 40 1.00-1.30 0. 15 -0. 20军军aα丑。0. 015 -0. 035 1.00-1.30 0.α)317CrMnBH ZF7B 17Cr2Ni2H ZFI 0.15 -0.19 0.15 -0. 40 0.40~o.ro1.40~I. 70 1.40-1.70 16CrNiH 16CrNi4 0.13“0.18 0.02-0.04 0.15~0.35 0.70句1.100. 80-1. 200. 80~1.20 :;;;0.10 0.02-0. 05 19CrNiH 19CrNi5 0.16~0. 21 0. 02 -0. 035 17Cr2Ni2MoH ZFlA 0.15”0.19 0.15~0.40 o. 40 -o. ro0.015~0.035 I. 4。”I.70 1.50-1.80 0. 25 -0. 35 20CrNiMoHI 8620Hl 0.02-0.17”0.23 0.15町0.35o. ro -o. 95 0.017~0.032 0.35~0. 75 0.35”0.65 0. 15 -0. 25 20CrNiMoH2 8620田0.045 15CrMoH 0.13”0.18 0.25~0.45 0.17~0. 37 0.4。”0.70 髦。但50.8。”1.1020CrMo 0. 18 -0. 230.15 -0. 25 20CrMoH SCM420 0.17 -0. 23 0.17町0.350.55~0. \Kl 0.85句I.25 0.15”0.35 0.02-0.0击:;;;0.15 35CrMo 0. 32 -0. 40 0.40~0. 70 0.80斗100. 15 -0. 25 运0.03520CrH 0.17~0.37 0.70-1.00 运0.200.50~0.80 40Cr 0. 18 -0. 23 0.37~0.440. 80 -1.10
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 196 KiB |
@@ -86,8 +86,8 @@
|
||||
var letter = page.Letters[l];
|
||||
var expected = DataBoldItalic[l];
|
||||
Assert.Equal((string)expected[0], letter.Value);
|
||||
Assert.Equal((bool)expected[1], letter.FontDetails.IsBold);
|
||||
Assert.Equal((bool)expected[2], letter.FontDetails.IsItalic);
|
||||
Assert.Equal((bool)expected[1], letter.Font.IsBold);
|
||||
Assert.Equal((bool)expected[2], letter.Font.IsItalic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,229 +4,9 @@
|
||||
using DocumentLayoutAnalysis.PageSegmenter;
|
||||
using DocumentLayoutAnalysis.WordExtractor;
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Tokens;
|
||||
using SkiaSharp;
|
||||
using UglyToad.PdfPig.AcroForms;
|
||||
using UglyToad.PdfPig.AcroForms.Fields;
|
||||
|
||||
public class GithubIssuesTests
|
||||
{
|
||||
[Fact]
|
||||
public void Issue1223()
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("23056.PMC2132516.pdf");
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
Assert.NotNull(document);
|
||||
var firstPage = document.GetPage(1);
|
||||
Assert.NotNull(firstPage);
|
||||
Assert.Contains("The Rockefeller University Press", firstPage.Text);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1213()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("GlyphDataTableReadCompositeGlyphError.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
for (int p = 1; p <= document.NumberOfPages; p++)
|
||||
{
|
||||
var page = document.GetPage(p);
|
||||
Assert.NotNull(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1208()
|
||||
{
|
||||
string[] files = ["Input.visible.pdf", "Input.invisible.pdf"];
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath(file);
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
Assert.True(document.TryGetForm(out AcroForm form));
|
||||
Assert.Single(form.Fields);
|
||||
Assert.Equal(AcroFieldType.Signature, form.Fields[0].FieldType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1209()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("MOZILLA-9176-2.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
for (int p = 1; p <= document.NumberOfPages; p++)
|
||||
{
|
||||
var page = document.GetPage(p);
|
||||
Assert.NotNull(page);
|
||||
|
||||
foreach (var image in page.GetImages())
|
||||
{
|
||||
Assert.True(image.ImageDictionary.ContainsKey(NameToken.Height)); // Was missing
|
||||
Assert.True(image.ImageDictionary.ContainsKey(NameToken.Width));
|
||||
|
||||
if (image.ImageDictionary.TryGet<DictionaryToken>(NameToken.DecodeParms, out var decodeParms))
|
||||
{
|
||||
Assert.True(decodeParms.ContainsKey(NameToken.Columns)); // Was missing
|
||||
Assert.True(decodeParms.ContainsKey(NameToken.Rows));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Revert_e11dc6b()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("GHOSTSCRIPT-699488-0.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var images = page.GetImages().ToArray();
|
||||
|
||||
Assert.Equal(9, images.Length);
|
||||
|
||||
foreach (var image in images)
|
||||
{
|
||||
if (image.ImageDictionary.TryGet(NameToken.Filter, out var token) && token is NameToken nt)
|
||||
{
|
||||
if (nt.Data.Contains("DCT"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(image.TryGetPng(out _));
|
||||
}
|
||||
|
||||
var paths = page.Paths;
|
||||
Assert.Equal(66, paths.Count);
|
||||
var letters = page.Letters;
|
||||
Assert.Equal(2685, letters.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1199()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("TrueTypeTablesGlyphDataTableReadGlyphsError.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
for (int p = 1; p <= document.NumberOfPages; p++)
|
||||
{
|
||||
var page = document.GetPage(p);
|
||||
Assert.NotNull(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1183()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("test_a.pdf");
|
||||
|
||||
byte[] expected =
|
||||
[
|
||||
82, 85, 134, 255, 87, 90, 139, 255, 81, 84, 133, 255, 87, 89, 139, 255, 89, 91, 141, 255, 81, 83, 133,
|
||||
255, 84, 86, 136, 255, 84, 86, 136, 255, 70, 59, 113, 255, 69, 62, 116, 255, 75, 73, 126, 255, 45, 48,
|
||||
100, 255, 42, 48, 99, 255, 50, 55, 107, 255, 56, 59, 111, 255, 64, 66, 118, 255, 68, 63, 118, 255, 61,
|
||||
56, 111, 255, 70, 64, 120, 255, 67, 62, 117, 255, 61, 56, 111, 255, 68, 63, 118, 255, 68, 62, 118, 255,
|
||||
59, 54, 109, 255, 61, 60, 117, 255, 69, 65, 122, 255, 67, 59, 116, 255, 71, 62, 118, 255, 66, 60, 115,
|
||||
255, 47, 49, 102, 255, 40, 51, 102, 255, 35, 51, 100, 255, 70, 58, 114, 255, 68, 56, 112, 255, 76, 65,
|
||||
121, 255, 68, 58, 114, 255, 66, 58, 114, 255, 71, 64, 119, 255, 62, 56, 111, 255, 67, 62, 117, 255, 77,
|
||||
61, 118, 255, 71, 56, 113, 255, 76, 63, 119, 255, 74, 63, 118, 255, 63, 55, 108, 255, 71, 64, 116, 255,
|
||||
73, 68, 119, 255, 52, 49, 99, 255, 38, 51, 99, 255, 49, 62, 110, 255, 39, 51, 100, 255, 46, 55, 106,
|
||||
255, 50, 55, 107, 255, 63, 62, 116, 255, 67, 60, 116, 255, 71, 60, 116, 255, 67, 58, 112, 255, 68, 61,
|
||||
114, 255, 70, 67, 119, 255, 50, 50, 101, 255, 42, 47, 96, 255, 49, 59, 106, 255, 40, 54, 100, 255, 42,
|
||||
57, 103, 255, 51, 51, 102, 255, 67, 60, 112, 255, 73, 62, 114, 255, 71, 65, 117, 255, 48, 53, 103, 255,
|
||||
45, 55, 104, 255, 49, 55, 105, 255, 63, 63, 114, 255, 68, 59, 115, 255, 71, 59, 115, 255, 73, 59, 115,
|
||||
255, 74, 61, 118, 255, 66, 58, 114, 255, 50, 51, 105, 255, 39, 51, 104, 255, 34, 52, 103, 255, 64, 60,
|
||||
116, 255, 67, 64, 119, 255, 66, 66, 120, 255, 46, 49, 102, 255, 45, 51, 102, 255, 52, 61, 111, 255, 39,
|
||||
51, 99, 255, 41, 54, 102, 255, 42, 54, 100, 255, 43, 53, 99, 255, 47, 55, 103, 255, 51, 56, 104, 255,
|
||||
56, 57, 108, 255, 67, 65, 117, 255, 67, 63, 116, 255, 52, 47, 100, 255, 44, 55, 106, 255, 44, 56, 106,
|
||||
255, 42, 54, 103, 255, 42, 54, 102, 255, 40, 52, 100, 255, 41, 52, 99, 255, 45, 57, 103, 255, 42, 53,
|
||||
99, 255, 38, 54, 95, 255, 39, 55, 97, 255, 47, 64, 105, 255, 37, 53, 95, 255, 37, 53, 95, 255, 46, 63,
|
||||
104, 255, 39, 55, 96, 255, 42, 58, 99, 255, 41, 55, 105, 255, 45, 55, 106, 255, 46, 51, 103, 255, 51,
|
||||
51, 103, 255, 63, 61, 114, 255, 70, 68, 121, 255, 60, 60, 113, 255, 46, 48, 100, 255, 49, 51, 101, 255,
|
||||
51, 52, 103, 255, 58, 58, 109, 255, 69, 66, 119, 255, 64, 60, 113, 255, 61, 55, 109, 255, 70, 62, 118,
|
||||
255, 67, 58, 114, 255, 72, 59, 115, 255, 70, 58, 115, 255, 72, 62, 118, 255, 61, 55, 110, 255, 64, 62,
|
||||
116, 255, 65, 65, 119, 255, 47, 50, 104, 255, 52, 56, 109, 255, 39, 53, 106, 255, 41, 54, 107, 255, 40,
|
||||
50, 102, 255, 45, 51, 103, 255, 64, 66, 117, 255, 62, 61, 112, 255, 67, 63, 114, 255, 53, 47, 98, 255,
|
||||
49, 54, 101, 255, 51, 56, 104, 255, 43, 48, 95, 255, 50, 55, 102, 255, 49, 54, 102, 255, 42, 47, 94,
|
||||
255, 51, 56, 103, 255, 47, 52, 100, 255, 72, 62, 114, 255, 71, 62, 114, 255, 72, 67, 119, 255, 52, 52,
|
||||
103, 255, 44, 48, 99, 255, 48, 57, 106, 255, 39, 52, 100, 255, 43, 58, 106, 255, 43, 51, 98, 255, 44,
|
||||
52, 99, 255, 48, 57, 104, 255, 46, 55, 102, 255, 41, 50, 97, 255, 45, 55, 101, 255, 49, 59, 105, 255,
|
||||
43, 53, 100, 255, 51, 57, 106, 255, 41, 49, 98, 255, 40, 52, 100, 255, 45, 60, 107, 255, 38, 53, 101,
|
||||
255, 36, 48, 96, 255, 46, 54, 102, 255, 49, 55, 104, 255, 44, 55, 104, 255, 46, 56, 105, 255, 48, 58,
|
||||
107, 255, 41, 49, 99, 255, 43, 50, 100, 255, 52, 59, 108, 255, 50, 55, 105, 255, 50, 55, 105, 255, 43,
|
||||
54, 105, 255, 42, 51, 102, 255, 45, 53, 104, 255, 45, 49, 101, 255, 63, 63, 116, 255, 66, 63, 116, 255,
|
||||
68, 63, 117, 255, 62, 55, 109, 255, 74, 60, 120, 255, 73, 59, 119, 255, 72, 58, 119, 255, 76, 62, 122,
|
||||
255, 74, 60, 120, 255, 71, 57, 118, 255, 75, 61, 121, 255, 76, 62, 123, 255
|
||||
];
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(16);
|
||||
var images = page.GetImages().ToArray();
|
||||
|
||||
Assert.Single(images);
|
||||
|
||||
var image = images[0];
|
||||
|
||||
Assert.True(image.TryGetPng(out var bytes));
|
||||
|
||||
File.WriteAllBytes("test_a_16.png", bytes);
|
||||
|
||||
using (SKBitmap actual = SKBitmap.Decode(bytes, new SKImageInfo(431, 690, SKColorType.Bgra8888)))
|
||||
{
|
||||
var pixels = actual.GetPixelSpan();
|
||||
Assert.Equal(1189560, pixels.Length);
|
||||
Assert.Equal(expected, pixels.Slice(0, 4 * 200).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1156()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("felltypes-test.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
|
||||
var letters = page.Letters;
|
||||
|
||||
var words = NearestNeighbourWordExtractor.Instance.GetWords(letters).ToArray();
|
||||
|
||||
var wordThe = words[0];
|
||||
Assert.Equal("THE", wordThe.Text);
|
||||
Assert.Equal(wordThe.BoundingBox.BottomLeft, new PdfPoint(x: 242.9877, y: 684.7435));
|
||||
Assert.Equal(wordThe.BoundingBox.BottomRight, new PdfPoint(x: 323.93999999999994, y: 684.7435));
|
||||
|
||||
var wordBook = words[2];
|
||||
Assert.Equal("BOOK:", wordBook.Text);
|
||||
Assert.Equal(wordBook.BoundingBox.BottomLeft, new PdfPoint(x: 280.4371, y: 652.0399));
|
||||
Assert.Equal(wordBook.BoundingBox.BottomRight, new PdfPoint(x: 405.65439999999995, y: 652.0399));
|
||||
|
||||
var wordPremeffa = words[35];
|
||||
Assert.Equal("preme\ue009a.", wordPremeffa.Text); // The 'ff' glyph is not properly parsed
|
||||
Assert.Equal(wordPremeffa.BoundingBox.BottomLeft, new PdfPoint(x: 331.16020000000003, y: 515.2256999999998));
|
||||
Assert.Equal(wordPremeffa.BoundingBox.BottomRight, new PdfPoint(x: 374.2954000000001, y: 515.2256999999998));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1148()
|
||||
{
|
||||
@@ -255,7 +35,7 @@
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("StackOverflow_Issue_1122.pdf");
|
||||
|
||||
var ex = Assert.Throws<PdfDocumentFormatException>(() => PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }));
|
||||
Assert.Equal("The root object in the trailer did not resolve to a readable dictionary.", ex.Message);
|
||||
Assert.StartsWith("Reached maximum search depth while getting indirect reference.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -314,7 +94,7 @@
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("SpookyPass.pdf");
|
||||
var ex = Assert.Throws<PdfDocumentFormatException>(() => PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }));
|
||||
Assert.Equal("The root object in the trailer did not resolve to a readable dictionary.", ex.Message);
|
||||
Assert.Equal("Avoiding infinite recursion in ObjectLocationProvider.TryGetOffset() as 'offset' and 'reference.ObjectNumber' have the same value and opposite signs.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -479,8 +259,7 @@
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
|
||||
{
|
||||
var page = document.GetPage(13);
|
||||
// This used to fail with an overflow exception when we failed to validate the zlib encoded data
|
||||
Assert.NotNull(DocstrumBoundingBoxes.Instance.GetBlocks(page.GetWords()));
|
||||
Assert.Throws<OverflowException>(() => DocstrumBoundingBoxes.Instance.GetBlocks(page.GetWords()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,13 +442,19 @@
|
||||
{
|
||||
var doc = IntegrationHelpers.GetDocumentPath("ErcotFacts.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(doc, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = false }))
|
||||
using (var document = PdfDocument.Open(doc, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
|
||||
{
|
||||
var page1 = document.GetPage(1);
|
||||
Assert.Equal(1939, page1.Letters.Count);
|
||||
Assert.Equal(1788, page1.Letters.Count);
|
||||
|
||||
var page2 = document.GetPage(2);
|
||||
Assert.Equal(2434, page2.Letters.Count);
|
||||
Assert.Equal(2430, page2.Letters.Count);
|
||||
}
|
||||
|
||||
using (var document = PdfDocument.Open(doc, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = false }))
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentNullException>(() => document.GetPage(1));
|
||||
Assert.StartsWith("Value cannot be null.", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using PdfPig.Geometry;
|
||||
|
||||
public class IntegrationDocumentTests
|
||||
{
|
||||
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents")));
|
||||
@@ -13,36 +11,6 @@
|
||||
"cmap-parsing-exception.pdf"
|
||||
];
|
||||
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetAllDocuments))]
|
||||
public void CheckGlyphLooseBoundingBoxes(string documentName)
|
||||
{
|
||||
// Add the full path back on, we removed it so we could see it in the test explorer.
|
||||
documentName = Path.Combine(DocumentFolder.Value, documentName);
|
||||
|
||||
using (var document = PdfDocument.Open(documentName, new ParsingOptions { UseLenientParsing = true }))
|
||||
{
|
||||
for (var i = 0; i < document.NumberOfPages; i++)
|
||||
{
|
||||
var page = document.GetPage(i + 1);
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
var bbox = letter.GlyphRectangle;
|
||||
if (bbox.Height > 0)
|
||||
{
|
||||
if (letter.GlyphRectangleLoose.Height <= 0)
|
||||
{
|
||||
_ = letter.GetFont().GetAscent();
|
||||
}
|
||||
|
||||
Assert.True(letter.GlyphRectangleLoose.Height > 0, $"Page {i + 1}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetAllDocuments))]
|
||||
public void CanReadAllPages(string documentName)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -53,8 +53,6 @@ namespace UglyToad.PdfPig.Tests.Integration
|
||||
|
||||
Assert.Contains(page.Letters, x => x.GlyphRectangle.Width != 0);
|
||||
Assert.Contains(page.Letters, x => x.GlyphRectangle.Height != 0);
|
||||
Assert.Contains(page.Letters, x => x.GlyphRectangleLoose.Width != 0);
|
||||
Assert.Contains(page.Letters, x => x.GlyphRectangleLoose.Height != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,13 +147,6 @@
|
||||
Run(Type3FontZeroHeight, 1255);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void test_a()
|
||||
{
|
||||
// Rendered glyphs are not correct, but we use the grid to assess
|
||||
Run("test_a", 1584, 1);
|
||||
}
|
||||
|
||||
private static void Run(string file, int imageHeight = 792, int pageNo = 1)
|
||||
{
|
||||
var pdfFileName = GetFilename(file);
|
||||
@@ -200,32 +193,6 @@
|
||||
d.SaveTo(fs);
|
||||
}
|
||||
}
|
||||
|
||||
using (var bitmap = SKBitmap.FromImage(image))
|
||||
using (var graphics = new SKCanvas(bitmap))
|
||||
{
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
DrawRectangle(letter.GlyphRectangleLoose, graphics, violetPen, imageHeight, scale);
|
||||
}
|
||||
|
||||
graphics.Flush();
|
||||
|
||||
var imageName = $"{file}_loose.jpg";
|
||||
|
||||
if (!Directory.Exists(OutputPath))
|
||||
{
|
||||
Directory.CreateDirectory(OutputPath);
|
||||
}
|
||||
|
||||
var savePath = Path.Combine(OutputPath, imageName);
|
||||
|
||||
using (var fs = new FileStream(savePath, FileMode.Create))
|
||||
using (SKData d = bitmap.Encode(SKEncodedImageFormat.Jpeg, 100))
|
||||
{
|
||||
d.SaveTo(fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,11 +220,7 @@
|
||||
|
||||
pdf = pdf.Replace(".pdf", ".jpg");
|
||||
|
||||
if (File.Exists(pdf))
|
||||
{
|
||||
return SKImage.FromEncodedData(pdf);
|
||||
}
|
||||
return SKImage.FromEncodedData(pdf.Replace(".jpg", ".png"));
|
||||
return SKImage.FromEncodedData(pdf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,31 +85,6 @@
|
||||
d.SaveTo(fs);
|
||||
}
|
||||
}
|
||||
|
||||
using (var picture = document.GetPage<SKPicture>(pageNo))
|
||||
using (var image = SKImage.FromPicture(picture, size, ScaleMatrix))
|
||||
using (var bmp = SKBitmap.FromImage(image))
|
||||
using (var canvas = new SKCanvas(bmp))
|
||||
{
|
||||
Assert.NotNull(picture);
|
||||
|
||||
if (RenderGlyphRectangle)
|
||||
{
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
DrawRectangle(letter.GlyphRectangleLoose, canvas, redPaint, size.Height, Scale);
|
||||
}
|
||||
}
|
||||
|
||||
var imageName = $"{file}_{pageNo}_loose.png";
|
||||
var savePath = Path.Combine(OutputPath, imageName);
|
||||
|
||||
using (var fs = new FileStream(savePath, FileMode.Create))
|
||||
using (var d = bmp.Encode(SKEncodedImageFormat.Png, 100))
|
||||
{
|
||||
d.SaveTo(fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,8 +109,10 @@ public class FirstPassParserTests
|
||||
%%EOF
|
||||
""";
|
||||
|
||||
// Handle "\r\n" or "\n" in the sourcecode in the same way
|
||||
content = content.Replace("\r\n", "\n").Replace("\n", "\r\n");
|
||||
if (Environment.NewLine == "\n")
|
||||
{
|
||||
content = content.Replace("\n", "\r\n");
|
||||
}
|
||||
|
||||
var ib = StringBytesTestConverter.Convert(content, false);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
using System.Text;
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Encryption;
|
||||
using PdfPig.Parser.FileStructure;
|
||||
using PdfPig.Tokenization.Scanner;
|
||||
using PdfPig.Tokens;
|
||||
|
||||
@@ -721,12 +720,8 @@ endobj";
|
||||
{
|
||||
var input = StringBytesTestConverter.Convert(s, false);
|
||||
|
||||
return new PdfTokenScanner(input.Bytes,
|
||||
locationProvider ?? new TestObjectLocationProvider(),
|
||||
new TestFilterProvider(),
|
||||
NoOpEncryptionHandler.Instance,
|
||||
new FileHeaderOffset(0),
|
||||
useLenientParsing ? new ParsingOptions() : ParsingOptions.LenientParsingOff);
|
||||
return new PdfTokenScanner(input.Bytes, locationProvider ?? new TestObjectLocationProvider(),
|
||||
new TestFilterProvider(), NoOpEncryptionHandler.Instance, useLenientParsing ? new ParsingOptions() : ParsingOptions.LenientParsingOff);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<ObjectToken> ReadToEnd(PdfTokenScanner scanner)
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
|
||||
<RuntimeFrameworkVersion Condition="'$(TargetFramework)'=='netcoreapp2.1'">2.1.30</RuntimeFrameworkVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>annotations</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -40,7 +39,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Fonts\TrueType\*.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\Andada-Regular.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\google-simple-doc.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\issue-258-corrupt-name-table.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\PMingLiU.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\Roboto-Regular.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\Type1\AdobeUtopia.pfa" />
|
||||
<EmbeddedResource Remove="Fonts\Type1\CMBX10.pfa" />
|
||||
<EmbeddedResource Remove="Fonts\Type1\CMBX12.pfa" />
|
||||
@@ -58,12 +61,24 @@
|
||||
<Content Include="Fonts\CompactFontFormat\MinionPro.bin">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\*.ttf">
|
||||
<Content Include="Fonts\TrueType\Andada-Regular.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\google-simple-doc.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\issue-258-corrupt-name-table.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\PMingLiU.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\Roboto-Regular.GlyphData.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\Roboto-Regular.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\Type1\AdobeUtopia.pfa" />
|
||||
<Content Include="Fonts\Type1\CMBX10.pfa" />
|
||||
<Content Include="Fonts\Type1\CMBX12.pfa" />
|
||||
@@ -102,18 +117,9 @@
|
||||
<None Update="Dla\Documents\90 180 270 rotated.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Dla\Documents\caly-issues-56-1.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Dla\Documents\caly-issues-58-2.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Dla\Documents\complex rotated.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Dla\Documents\fseprd1102849.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Dla\Documents\no horizontal distance.pdf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Util
|
||||
{
|
||||
using PdfPig.Util;
|
||||
using System.Text;
|
||||
|
||||
public class MurmurHash3Tests
|
||||
{
|
||||
public static object[][] MurmurHashData = new object[][]
|
||||
{
|
||||
// https://murmurhash.shorelabs.com/
|
||||
["The quick brown fox jumps over the lazy dog", "2f1583c3ecee2c675d7bf66ce5e91d2c", "e34bbc7bbc071b6c7a433ca9c49a9347"],
|
||||
["MurmurHash3 was written by Austin Appleby, and is placed in the public", "6d3583489d9d1e5a898493af67e2ad10", "a91793d43f82cbabda2fb0c28c24799a"],
|
||||
["0", "0ab2409ea5eb34f8a5eb34f8a5eb34f8", "2ac9debed546a3803a8de9e53c875e09"],
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MurmurHashData))]
|
||||
public void x86x64Check(string sentence, string expectedX86, string expectedX64)
|
||||
{
|
||||
byte[] data = Encoding.UTF8.GetBytes(sentence);
|
||||
|
||||
var hash = MurmurHash3.Compute_x86_128(data, data.Length, 0);
|
||||
var actual = string.Concat(Array.ConvertAll(hash, x => x.ToString("x2")));
|
||||
Assert.Equal(expectedX86, actual);
|
||||
|
||||
hash = MurmurHash3.Compute_x64_128(data, data.Length, 0);
|
||||
actual = string.Concat(Array.ConvertAll(hash, x => x.ToString("x2")));
|
||||
Assert.Equal(expectedX64, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1154,7 +1154,7 @@
|
||||
{
|
||||
location += letter.Location.X;
|
||||
location += letter.Location.Y;
|
||||
location += letter.FontDetails.Name.Length;
|
||||
location += letter.Font.Name.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.13</Version>
|
||||
<Version>0.1.12</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.13</Version>
|
||||
<Version>0.1.12</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -46,12 +46,6 @@
|
||||
/// </summary>
|
||||
public PdfRectangle GlyphRectangle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The loose bounding box for the glyph. Contrary to the <see cref="GlyphRectangle"/>, the loose bounding box will be the same across all glyphes of the same font.
|
||||
/// It takes in account the font Ascent and Descent.
|
||||
/// </summary>
|
||||
public PdfRectangle GlyphRectangleLoose { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size as defined in the PDF file. This is not equivalent to font size in points but is relative to other font sizes on the page.
|
||||
/// </summary>
|
||||
@@ -60,20 +54,12 @@
|
||||
/// <summary>
|
||||
/// The name of the font.
|
||||
/// </summary>
|
||||
public string? FontName => FontDetails?.Name;
|
||||
public string? FontName => Font?.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Details about the font for this letter.
|
||||
/// </summary>
|
||||
public FontDetails FontDetails { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Details about the font for this letter.
|
||||
/// </summary>
|
||||
[Obsolete("Use FontDetails instead.")]
|
||||
public FontDetails Font => FontDetails;
|
||||
|
||||
private readonly IFont? _font;
|
||||
public FontDetails Font { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Text rendering mode that indicates whether we should draw this letter's strokes,
|
||||
@@ -114,58 +100,12 @@
|
||||
/// <summary>
|
||||
/// Create a new letter to represent some text drawn by the Tj operator.
|
||||
/// </summary>
|
||||
public Letter(string value,
|
||||
PdfRectangle glyphRectangle,
|
||||
PdfRectangle glyphRectangleLoose,
|
||||
public Letter(string value, PdfRectangle glyphRectangle,
|
||||
PdfPoint startBaseLine,
|
||||
PdfPoint endBaseLine,
|
||||
double width,
|
||||
double fontSize,
|
||||
IFont font,
|
||||
TextRenderingMode renderingMode,
|
||||
IColor strokeColor,
|
||||
IColor fillColor,
|
||||
double pointSize,
|
||||
int textSequence) :
|
||||
this(value, glyphRectangle, glyphRectangleLoose,
|
||||
startBaseLine, endBaseLine,
|
||||
width, fontSize, font.Details, font,
|
||||
renderingMode, strokeColor, fillColor,
|
||||
pointSize, textSequence)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new letter to represent some text drawn by the Tj operator.
|
||||
/// </summary>
|
||||
public Letter(string value,
|
||||
PdfRectangle glyphRectangle,
|
||||
PdfRectangle glyphRectangleLoose,
|
||||
PdfPoint startBaseLine,
|
||||
PdfPoint endBaseLine,
|
||||
double width,
|
||||
double fontSize,
|
||||
FontDetails fontDetails,
|
||||
TextRenderingMode renderingMode,
|
||||
IColor strokeColor,
|
||||
IColor fillColor,
|
||||
double pointSize,
|
||||
int textSequence):
|
||||
this(value, glyphRectangle, glyphRectangleLoose,
|
||||
startBaseLine, endBaseLine,
|
||||
width, fontSize, fontDetails, null,
|
||||
renderingMode, strokeColor, fillColor,
|
||||
pointSize, textSequence)
|
||||
{ }
|
||||
|
||||
private Letter(string value,
|
||||
PdfRectangle glyphRectangle,
|
||||
PdfRectangle glyphRectangleLoose,
|
||||
PdfPoint startBaseLine,
|
||||
PdfPoint endBaseLine,
|
||||
double width,
|
||||
double fontSize,
|
||||
FontDetails fontDetails,
|
||||
IFont? font,
|
||||
FontDetails font,
|
||||
TextRenderingMode renderingMode,
|
||||
IColor strokeColor,
|
||||
IColor fillColor,
|
||||
@@ -174,13 +114,11 @@
|
||||
{
|
||||
Value = value;
|
||||
GlyphRectangle = glyphRectangle;
|
||||
GlyphRectangleLoose = glyphRectangleLoose;
|
||||
StartBaseLine = startBaseLine;
|
||||
EndBaseLine = endBaseLine;
|
||||
Width = width;
|
||||
FontSize = fontSize;
|
||||
FontDetails = fontDetails;
|
||||
_font = font;
|
||||
Font = font;
|
||||
RenderingMode = renderingMode;
|
||||
if (renderingMode == TextRenderingMode.Stroke)
|
||||
{
|
||||
@@ -197,43 +135,6 @@
|
||||
TextOrientation = GetTextOrientation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Letter"/> instance with the same properties as the current instance,
|
||||
/// but with the font details set to bold.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A new <see cref="Letter"/> instance with bold font details.
|
||||
/// </returns>
|
||||
public Letter AsBold()
|
||||
{
|
||||
return new Letter(Value,
|
||||
GlyphRectangle,
|
||||
GlyphRectangleLoose,
|
||||
StartBaseLine,
|
||||
EndBaseLine,
|
||||
Width,
|
||||
FontSize,
|
||||
FontDetails.AsBold(),
|
||||
_font,
|
||||
RenderingMode,
|
||||
StrokeColor,
|
||||
FillColor,
|
||||
PointSize,
|
||||
TextSequence);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the font associated with this letter, if available.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The <see cref="IFont"/> instance representing the font used for this letter,
|
||||
/// or <c>null</c> if no font is associated.
|
||||
/// </returns>
|
||||
public IFont? GetFont()
|
||||
{
|
||||
return _font;
|
||||
}
|
||||
|
||||
private TextOrientation GetTextOrientation()
|
||||
{
|
||||
if (Math.Abs(StartBaseLine.Y - EndBaseLine.Y) < 10e-5)
|
||||
|
||||
@@ -59,10 +59,7 @@ namespace UglyToad.PdfPig.Content
|
||||
switch (Type)
|
||||
{
|
||||
case "OCG": // Optional content group dictionary
|
||||
// Name - Per spec this is required, but in practice some PDFs store layer names
|
||||
// at the document catalog level in OCProperties rather than in marked content Properties.
|
||||
// To avoid crashes, we make this optional and fall back to null or the tag name.
|
||||
|
||||
// Name - Required
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Name, pdfTokenScanner, out NameToken? name))
|
||||
{
|
||||
Name = name.Data;
|
||||
@@ -77,9 +74,7 @@ namespace UglyToad.PdfPig.Content
|
||||
}
|
||||
else
|
||||
{
|
||||
// Name not found in Properties - use tag as fallback or leave as null
|
||||
// This handles PDFs where layer names are stored at document catalog level
|
||||
Name = markedContentElement.Tag;
|
||||
throw new ArgumentException($"Cannot parse optional content's {nameof(Name)} from {nameof(markedContentElement.Properties)}. This is a required field.", nameof(markedContentElement.Properties));
|
||||
}
|
||||
|
||||
// Intent - Optional
|
||||
|
||||
@@ -182,15 +182,16 @@
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var factory in pageFactoryCache.Values)
|
||||
foreach (var key in pageFactoryCache.Keys)
|
||||
{
|
||||
var factory = pageFactoryCache[key];
|
||||
pageFactoryCache.Remove(key);
|
||||
|
||||
if (factory is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
pageFactoryCache.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
private readonly Dictionary<IndirectReference, IFont> loadedFonts = new Dictionary<IndirectReference, IFont>();
|
||||
private readonly Dictionary<NameToken, IFont> loadedDirectFonts = new Dictionary<NameToken, IFont>();
|
||||
private readonly StackDictionary<NameToken, IndirectReference> currentFontState = new StackDictionary<NameToken, IndirectReference>();
|
||||
private readonly StackDictionary<NameToken, IndirectReference> currentXObjectState = new StackDictionary<NameToken, IndirectReference>();
|
||||
private readonly StackDictionary<NameToken, IndirectReference> currentResourceState = new StackDictionary<NameToken, IndirectReference>();
|
||||
|
||||
private readonly Dictionary<NameToken, DictionaryToken> extendedGraphicsStates = new Dictionary<NameToken, DictionaryToken>();
|
||||
|
||||
@@ -54,8 +53,7 @@
|
||||
loadedNamedColorSpaceDetails.Clear();
|
||||
|
||||
namedColorSpaces.Push();
|
||||
currentFontState.Push();
|
||||
currentXObjectState.Push();
|
||||
currentResourceState.Push();
|
||||
|
||||
if (resourceDictionary.TryGet(NameToken.Font, out var fontBase))
|
||||
{
|
||||
@@ -80,7 +78,7 @@
|
||||
throw new InvalidOperationException($"Expected the XObject dictionary value for key /{pair.Key} to be an indirect reference, instead got: {pair.Value}.");
|
||||
}
|
||||
|
||||
currentXObjectState[NameToken.Create(pair.Key)] = reference.Data;
|
||||
currentResourceState[NameToken.Create(pair.Key)] = reference.Data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,8 +185,7 @@
|
||||
{
|
||||
lastLoadedFont = (null, null);
|
||||
loadedNamedColorSpaceDetails.Clear();
|
||||
currentFontState.Pop();
|
||||
currentXObjectState.Pop();
|
||||
currentResourceState.Pop();
|
||||
namedColorSpaces.Pop();
|
||||
}
|
||||
|
||||
@@ -202,7 +199,7 @@
|
||||
{
|
||||
var reference = objectKey.Data;
|
||||
|
||||
currentFontState[NameToken.Create(pair.Key)] = reference;
|
||||
currentResourceState[NameToken.Create(pair.Key)] = reference;
|
||||
|
||||
if (loadedFonts.ContainsKey(reference))
|
||||
{
|
||||
@@ -248,7 +245,7 @@
|
||||
}
|
||||
|
||||
IFont? font;
|
||||
if (currentFontState.TryGetValue(name, out var reference))
|
||||
if (currentResourceState.TryGetValue(name, out var reference))
|
||||
{
|
||||
loadedFonts.TryGetValue(reference, out font);
|
||||
}
|
||||
@@ -338,7 +335,7 @@
|
||||
public bool TryGetXObject(NameToken name, [NotNullWhen(true)] out StreamToken? stream)
|
||||
{
|
||||
stream = null;
|
||||
if (!currentXObjectState.TryGetValue(name, out var indirectReference))
|
||||
if (!currentResourceState.TryGetValue(name, out var indirectReference))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -56,16 +56,16 @@
|
||||
// add this and follow chain defined by 'Prev' keys
|
||||
xrefPartToBytePositionOrder.Add(firstCrossReferenceOffset);
|
||||
|
||||
// Get any streams that are tied to this table.
|
||||
var activePart = currentPart;
|
||||
var dependents = parts.Where(x => x.TiedToXrefAtOffset == activePart.Offset);
|
||||
foreach (var dependent in dependents)
|
||||
{
|
||||
xrefPartToBytePositionOrder.Add(dependent.Offset);
|
||||
}
|
||||
|
||||
while (currentPart.Dictionary != null)
|
||||
{
|
||||
// Get any streams that are tied to this table.
|
||||
var activePart = currentPart;
|
||||
var dependents = parts.Where(x => x.TiedToXrefAtOffset == activePart.Offset);
|
||||
foreach (var dependent in dependents)
|
||||
{
|
||||
xrefPartToBytePositionOrder.Add(dependent.Offset);
|
||||
}
|
||||
|
||||
long prevBytePos = currentPart.GetPreviousOffset();
|
||||
if (prevBytePos == -1)
|
||||
{
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Filters
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
internal sealed class Adler32ChecksumStream : Stream
|
||||
{
|
||||
private readonly Stream underlyingStream;
|
||||
|
||||
public Adler32ChecksumStream(Stream writeStream)
|
||||
{
|
||||
underlyingStream = writeStream ?? throw new ArgumentNullException(nameof(writeStream));
|
||||
}
|
||||
public override bool CanRead => underlyingStream.CanRead;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => underlyingStream.CanWrite;
|
||||
|
||||
public override long Length => underlyingStream.Length;
|
||||
|
||||
public override long Position { get => underlyingStream.Position; set => throw new NotImplementedException(); }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
underlyingStream.Flush();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int n = underlyingStream.Read(buffer, offset, count);
|
||||
|
||||
if (n > 0)
|
||||
{
|
||||
UpdateAdler(buffer.AsSpan(offset, n));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
underlyingStream.Write(buffer, offset, count);
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
UpdateAdler(buffer.AsSpan(offset, count));
|
||||
}
|
||||
}
|
||||
|
||||
public uint Checksum { get; private set; } = 1;
|
||||
|
||||
private void UpdateAdler(Span<byte> span)
|
||||
{
|
||||
const uint MOD_ADLER = 65521;
|
||||
uint a = Checksum & 0xFFFF;
|
||||
uint b = (Checksum >> 16) & 0xFFFF;
|
||||
|
||||
foreach (byte c in span)
|
||||
{
|
||||
a = (a + c) % MOD_ADLER;
|
||||
b = (b + a) % MOD_ADLER;
|
||||
}
|
||||
|
||||
Checksum = (b << 16) | a;
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
underlyingStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
{
|
||||
using Fonts;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using Tokens;
|
||||
@@ -44,7 +43,6 @@
|
||||
var colors = Math.Min(parameters.GetIntOrDefault(NameToken.Colors, DefaultColors), 32);
|
||||
var bitsPerComponent = parameters.GetIntOrDefault(NameToken.BitsPerComponent, DefaultBitsPerComponent);
|
||||
var columns = parameters.GetIntOrDefault(NameToken.Columns, DefaultColumns);
|
||||
|
||||
return Decompress(input, predictor, colors, bitsPerComponent, columns);
|
||||
}
|
||||
catch
|
||||
@@ -57,83 +55,29 @@
|
||||
|
||||
private static Memory<byte> Decompress(Memory<byte> input, int predictor, int colors, int bitsPerComponent, int columns)
|
||||
{
|
||||
#if NET
|
||||
using var memoryStream = MemoryHelper.AsReadOnlyMemoryStream(input);
|
||||
try
|
||||
using (var memoryStream = MemoryHelper.AsReadOnlyMemoryStream(input))
|
||||
{
|
||||
using (var zlib = new ZLibStream(memoryStream, CompressionMode.Decompress))
|
||||
using (var output = new MemoryStream((int)(input.Length * 1.5)))
|
||||
using (var f = PngPredictor.WrapPredictor(output, predictor, colors, bitsPerComponent, columns))
|
||||
// The first 2 bytes are the header which DeflateStream does not support.
|
||||
memoryStream.ReadByte();
|
||||
memoryStream.ReadByte();
|
||||
|
||||
try
|
||||
{
|
||||
zlib.CopyTo(f);
|
||||
f.Flush();
|
||||
|
||||
return output.AsMemory();
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
throw new CorruptCompressedDataException("Invalid Flate compressed stream encountered", ex);
|
||||
}
|
||||
#else
|
||||
// Ideally we would like to use the ZLibStream class but that is only available in .NET 5+.
|
||||
// We look at the raw data now
|
||||
// * First we have 2 bytes, specifying the type of compression
|
||||
// * Then we have the deflated data
|
||||
// * Then we have a 4 byte checksum (Adler32)
|
||||
|
||||
// Would be so nice to have zlib do the framing here... but the deflate stream already reads data from the stream that we need.
|
||||
|
||||
using var memoryStream = MemoryHelper.AsReadOnlyMemoryStream(input.Slice(2, input.Length - 2 /* Header */ - 4 /* Checksum */));
|
||||
// The first 2 bytes are the header which DeflateStream can't handle. After the s
|
||||
var adlerBytes = input.Slice(input.Length - 4, 4).Span;
|
||||
uint expected = BinaryPrimitives.ReadUInt32BigEndian(adlerBytes);
|
||||
uint altExpected = expected;
|
||||
|
||||
// Sometimes the data ends with "\r\n", "\r" or "\n" and we don't know if it is part of the zlib
|
||||
// Ideally this would have been removed by the caller from the provided length...
|
||||
if (adlerBytes[3] == '\n' || adlerBytes[3] == '\r')
|
||||
{
|
||||
if (adlerBytes[3] == '\n' && adlerBytes[2] == '\r')
|
||||
{
|
||||
// Now we don't know which value is the good one. The value could be ok, or padding.
|
||||
// Lets allow both values for now. Allowing two out of 2^32 is much better than allowing everything
|
||||
adlerBytes = input.Slice(input.Length - 6, 4).Span;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Same but now for just '\n' or '\r' instead of '\r\n'
|
||||
adlerBytes = input.Slice(input.Length - 5, 4).Span;
|
||||
}
|
||||
|
||||
altExpected = BinaryPrimitives.ReadUInt32BigEndian(adlerBytes);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
using (var deflate = new DeflateStream(memoryStream, CompressionMode.Decompress))
|
||||
using (var adlerStream = new Adler32ChecksumStream(deflate))
|
||||
using (var output = new MemoryStream((int)(input.Length * 1.5)))
|
||||
using (var f = PngPredictor.WrapPredictor(output, predictor, colors, bitsPerComponent, columns))
|
||||
{
|
||||
adlerStream.CopyTo(f);
|
||||
f.Flush();
|
||||
|
||||
uint actual = adlerStream.Checksum;
|
||||
if (expected != actual && altExpected != actual)
|
||||
using (var deflate = new DeflateStream(memoryStream, CompressionMode.Decompress))
|
||||
using (var output = new MemoryStream((int)(input.Length * 1.5)))
|
||||
using (var f = PngPredictor.WrapPredictor(output, predictor, colors, bitsPerComponent, columns))
|
||||
{
|
||||
throw new CorruptCompressedDataException("Flate stream has invalid checksum");
|
||||
}
|
||||
deflate.CopyTo(f);
|
||||
f.Flush();
|
||||
|
||||
return output.AsMemory();
|
||||
return output.AsMemory();
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
throw new CorruptCompressedDataException("Invalid Flate compressed stream encountered", ex);
|
||||
}
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
throw new CorruptCompressedDataException("Invalid Flate compressed stream encountered", ex);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -151,10 +95,9 @@
|
||||
|
||||
using (var compressStream = new MemoryStream())
|
||||
using (var compressor = new DeflateStream(compressStream, CompressionLevel.Fastest))
|
||||
using (var adlerStream = new Adler32ChecksumStream(compressor))
|
||||
{
|
||||
adlerStream.Write(data, 0, data.Length);
|
||||
adlerStream.Close();
|
||||
compressor.Write(data, 0, data.Length);
|
||||
compressor.Close();
|
||||
|
||||
var compressed = compressStream.ToArray();
|
||||
|
||||
@@ -168,7 +111,7 @@
|
||||
Array.Copy(compressed, 0, result, headerLength, compressed.Length);
|
||||
|
||||
// Write Checksum of raw data.
|
||||
var checksum = adlerStream.Checksum;
|
||||
var checksum = Adler32Checksum.Calculate(data);
|
||||
|
||||
var offset = headerLength + compressed.Length;
|
||||
|
||||
|
||||
@@ -563,8 +563,8 @@
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
// 2. Update current transformation matrix.
|
||||
ModifyCurrentTransformationMatrix(formMatrix);
|
||||
// 2. Update current transformation matrix.
|
||||
startState.CurrentTransformationMatrix = formMatrix.Multiply(startState.CurrentTransformationMatrix);
|
||||
|
||||
var contentStream = formStream.Decode(FilterProvider, PdfScanner);
|
||||
|
||||
@@ -576,8 +576,9 @@
|
||||
if (formStream.StreamDictionary.TryGet<ArrayToken>(NameToken.Bbox, PdfScanner, out var bboxToken))
|
||||
{
|
||||
var points = bboxToken.Data.OfType<NumericToken>().Select(x => x.Double).ToArray();
|
||||
PdfRectangle bbox = new PdfRectangle(points[0], points[1], points[2], points[3]).Normalise();
|
||||
ClipToRectangle(bbox, FillingRule.EvenOdd); // TODO - Check that Even Odd is valid
|
||||
PdfRectangle bbox = new PdfRectangle(points[0], points[1], points[2], points[3]);
|
||||
PdfRectangle transformedBox = startState.CurrentTransformationMatrix.Transform(bbox).Normalise();
|
||||
ClipToRectangle(transformedBox, FillingRule.EvenOdd); // TODO - Check that Even Odd is valid
|
||||
}
|
||||
|
||||
// 4. Paint the objects.
|
||||
@@ -957,10 +958,10 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void ModifyCurrentTransformationMatrix(TransformationMatrix value)
|
||||
public virtual void ModifyCurrentTransformationMatrix(double[] value)
|
||||
{
|
||||
var state = GetCurrentState();
|
||||
state.CurrentTransformationMatrix = value.Multiply(state.CurrentTransformationMatrix);
|
||||
var ctm = GetCurrentState().CurrentTransformationMatrix;
|
||||
GetCurrentState().CurrentTransformationMatrix = TransformationMatrix.FromArray(value).Multiply(ctm);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -100,6 +100,12 @@ namespace UglyToad.PdfPig.Graphics
|
||||
var transformedGlyphBounds = PerformantRectangleTransformer
|
||||
.Transform(renderingMatrix, textMatrix, transformationMatrix, characterBoundingBox.GlyphBounds);
|
||||
|
||||
var transformedPdfBounds = PerformantRectangleTransformer
|
||||
.Transform(renderingMatrix,
|
||||
textMatrix,
|
||||
transformationMatrix,
|
||||
new PdfRectangle(0, 0, characterBoundingBox.Width, UserSpaceUnit.PointMultiples));
|
||||
|
||||
if (ParsingOptions.ClipPaths)
|
||||
{
|
||||
var currentClipping = currentState.CurrentClippingPath;
|
||||
@@ -123,12 +129,11 @@ namespace UglyToad.PdfPig.Graphics
|
||||
letter = new Letter(
|
||||
newLetter,
|
||||
attachTo.GlyphRectangle,
|
||||
attachTo.GlyphRectangleLoose,
|
||||
attachTo.StartBaseLine,
|
||||
attachTo.EndBaseLine,
|
||||
attachTo.Width,
|
||||
attachTo.FontSize,
|
||||
attachTo.GetFont()!,
|
||||
attachTo.Font,
|
||||
attachTo.RenderingMode,
|
||||
attachTo.StrokeColor,
|
||||
attachTo.FillColor,
|
||||
@@ -146,30 +151,14 @@ namespace UglyToad.PdfPig.Graphics
|
||||
// If we did not create a letter for a combined diacritic, create one here.
|
||||
if (letter is null)
|
||||
{
|
||||
var transformedPdfBounds = PerformantRectangleTransformer
|
||||
.Transform(renderingMatrix,
|
||||
textMatrix,
|
||||
transformationMatrix,
|
||||
new PdfRectangle(0, 0, characterBoundingBox.Width, UserSpaceUnit.PointMultiples));
|
||||
|
||||
var looseBox = PerformantRectangleTransformer
|
||||
.Transform(renderingMatrix,
|
||||
textMatrix,
|
||||
transformationMatrix,
|
||||
new PdfRectangle(0,
|
||||
font.GetDescent(),
|
||||
characterBoundingBox.Width,
|
||||
font.GetAscent()));
|
||||
|
||||
letter = new Letter(
|
||||
unicode,
|
||||
isBboxValid ? transformedGlyphBounds : transformedPdfBounds,
|
||||
looseBox,
|
||||
transformedPdfBounds.BottomLeft,
|
||||
transformedPdfBounds.BottomRight,
|
||||
transformedPdfBounds.Width,
|
||||
fontSize,
|
||||
font,
|
||||
font.Details,
|
||||
currentState.FontState.TextRenderingMode,
|
||||
currentState.CurrentStrokingColor!,
|
||||
currentState.CurrentNonStrokingColor!,
|
||||
@@ -178,6 +167,7 @@ namespace UglyToad.PdfPig.Graphics
|
||||
}
|
||||
|
||||
letters.Add(letter);
|
||||
|
||||
markedContentStack.AddLetter(letter);
|
||||
}
|
||||
|
||||
@@ -452,7 +442,6 @@ namespace UglyToad.PdfPig.Graphics
|
||||
{
|
||||
// https://github.com/apache/pdfbox/blob/f4bfe47de37f6fe69e8f98b164c3546facfd5e91/pdfbox/src/main/java/org/apache/pdfbox/contentstream/PDFStreamEngine.java#L611
|
||||
var graphicsState = GetCurrentState();
|
||||
rectangle = graphicsState.CurrentTransformationMatrix.Transform(rectangle).Normalise();
|
||||
var clip = rectangle.ToPdfPath();
|
||||
clip.SetClipping(clippingRule);
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
/// <summary>
|
||||
/// Modify the current transformation matrix by concatenating the specified matrix.
|
||||
/// </summary>
|
||||
void ModifyCurrentTransformationMatrix(TransformationMatrix value);
|
||||
void ModifyCurrentTransformationMatrix(double[] value);
|
||||
|
||||
/// <summary>
|
||||
/// Set the character spacing to a number expressed in unscaled text space units.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using PdfPig.Core;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
@@ -44,7 +43,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.ModifyCurrentTransformationMatrix(TransformationMatrix.FromArray(Value));
|
||||
operationContext.ModifyCurrentTransformationMatrix(Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -39,16 +39,7 @@
|
||||
var strideWidth = decoded.Length / imageHeight / bytesPerPixel;
|
||||
if (strideWidth != imageWidth)
|
||||
{
|
||||
if (bytesPerPixel > 1 && imageWidth * imageHeight * bytesPerPixel < decoded.Length)
|
||||
{
|
||||
// Fixed thanks to / see discussion at https://github.com/UglyToad/PdfPig/issues/1183
|
||||
// Unclear what should be done here, we assume we can just remove the trailing bytes
|
||||
decoded = decoded.Slice(0, imageWidth * imageHeight * bytesPerPixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
decoded = RemoveStridePadding(decoded, strideWidth, imageWidth, imageHeight, bytesPerPixel);
|
||||
}
|
||||
decoded = RemoveStridePadding(decoded, strideWidth, imageWidth, imageHeight, bytesPerPixel);
|
||||
}
|
||||
|
||||
return details.Transform(decoded);
|
||||
|
||||
35
src/UglyToad.PdfPig/Images/Png/Adler32Checksum.cs
Normal file
35
src/UglyToad.PdfPig/Images/Png/Adler32Checksum.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace UglyToad.PdfPig.Images.Png
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Used to calculate the Adler-32 checksum used for ZLIB data in accordance with
|
||||
/// RFC 1950: ZLIB Compressed Data Format Specification.
|
||||
/// </summary>
|
||||
internal static class Adler32Checksum
|
||||
{
|
||||
// Both sums (s1 and s2) are done modulo 65521.
|
||||
private const int AdlerModulus = 65521;
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the Adler-32 checksum for some data.
|
||||
/// </summary>
|
||||
public static int Calculate(ReadOnlySpan<byte> data)
|
||||
{
|
||||
// s1 is the sum of all bytes.
|
||||
var s1 = 1;
|
||||
|
||||
// s2 is the sum of all s1 values.
|
||||
var s2 = 0;
|
||||
|
||||
foreach (var b in data)
|
||||
{
|
||||
s1 = (s1 + b) % AdlerModulus;
|
||||
s2 = (s1 + s2) % AdlerModulus;
|
||||
}
|
||||
|
||||
// The Adler-32 checksum is stored as s2*65536 + s1.
|
||||
return s2 * 65536 + s1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
namespace UglyToad.PdfPig.Images.Png
|
||||
{
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using UglyToad.PdfPig.Filters;
|
||||
|
||||
/// <summary>
|
||||
/// Used to construct PNG images. Call <see cref="Create"/> to make a new builder.
|
||||
@@ -123,10 +121,9 @@
|
||||
const int checksumLength = 4;
|
||||
using (var compressStream = new MemoryStream())
|
||||
using (var compressor = new DeflateStream(compressStream, CompressionLevel.Fastest, true))
|
||||
using (var adlerStream = new Adler32ChecksumStream(compressor))
|
||||
{
|
||||
adlerStream.Write(data, 0, data.Length);
|
||||
adlerStream.Close();
|
||||
compressor.Write(data, 0, data.Length);
|
||||
compressor.Close();
|
||||
|
||||
compressStream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
@@ -146,11 +143,15 @@
|
||||
}
|
||||
|
||||
// Write Checksum of raw data.
|
||||
var checksum = adlerStream.Checksum;
|
||||
var checksum = Adler32Checksum.Calculate(data);
|
||||
|
||||
var offset = headerLength + compressStream.Length;
|
||||
|
||||
BinaryPrimitives.WriteUInt32BigEndian(result.AsSpan((int)offset, 4), checksum);
|
||||
result[offset++] = (byte)(checksum >> 24);
|
||||
result[offset++] = (byte)(checksum >> 16);
|
||||
result[offset++] = (byte)(checksum >> 8);
|
||||
result[offset] = (byte)(checksum >> 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Images.Png
|
||||
namespace UglyToad.PdfPig.Images.Png
|
||||
{
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content;
|
||||
using Content;
|
||||
using Graphics.Colors;
|
||||
using UglyToad.PdfPig.Core;
|
||||
|
||||
internal static class PngFromPdfImageFactory
|
||||
internal static class PngFromPdfImageFactory
|
||||
{
|
||||
private static bool TryGenerateSoftMask(IPdfImage image, [NotNullWhen(true)] out ReadOnlySpan<byte> maskBytes)
|
||||
{
|
||||
@@ -26,9 +26,9 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mask.TryGetBytesAsMemory(out var maskMemory))
|
||||
{
|
||||
return false;
|
||||
if (!mask.TryGetBytesAsMemory(out var maskMemory))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -67,24 +67,24 @@
|
||||
bytesPure[actualSize - 2] == ReadHelper.AsciiCarriageReturn &&
|
||||
bytesPure[actualSize - 1] == ReadHelper.AsciiLineFeed);
|
||||
}
|
||||
|
||||
public static bool TryGenerate(IPdfImage image, [NotNullWhen(true)] out byte[]? bytes)
|
||||
{
|
||||
bytes = null;
|
||||
|
||||
var hasValidDetails = image.ColorSpaceDetails != null && !(image.ColorSpaceDetails is UnsupportedColorSpaceDetails);
|
||||
|
||||
|
||||
public static bool TryGenerate(IPdfImage image, [NotNullWhen(true)] out byte[]? bytes)
|
||||
{
|
||||
bytes = null;
|
||||
|
||||
var hasValidDetails = image.ColorSpaceDetails != null && !(image.ColorSpaceDetails is UnsupportedColorSpaceDetails);
|
||||
|
||||
var isColorSpaceSupported = hasValidDetails && image.ColorSpaceDetails!.BaseType != ColorSpace.Pattern;
|
||||
|
||||
if (!isColorSpaceSupported || !image.TryGetBytesAsMemory(out var imageMemory))
|
||||
{
|
||||
return false;
|
||||
|
||||
if (!isColorSpaceSupported || !image.TryGetBytesAsMemory(out var imageMemory))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bytesPure = imageMemory.Span;
|
||||
|
||||
try
|
||||
{
|
||||
var bytesPure = imageMemory.Span;
|
||||
|
||||
try
|
||||
{
|
||||
bytesPure = ColorSpaceDetailsByteConverter.Convert(image.ColorSpaceDetails!, bytesPure,
|
||||
image.BitsPerComponent, image.WidthInSamples, image.HeightInSamples);
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
var builder = PngBuilder.Create(image.WidthInSamples, image.HeightInSamples, hasMask);
|
||||
var builder = PngBuilder.Create(image.WidthInSamples, image.HeightInSamples, hasMask);
|
||||
|
||||
if (!IsCorrectlySized(image, bytesPure))
|
||||
{
|
||||
@@ -183,17 +183,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bytes = builder.Save();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
}
|
||||
|
||||
bytes = builder.Save();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored.
|
||||
}
|
||||
|
||||
return false;
|
||||
// ignored.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
/// How many bytes precede the "%PDF-" version header in the file. In some files this 'junk' can
|
||||
/// offset all following offset bytes.
|
||||
/// </summary>
|
||||
internal readonly record struct FileHeaderOffset(int Value) : IEquatable<FileHeaderOffset>
|
||||
internal readonly struct FileHeaderOffset(int value)
|
||||
{
|
||||
public override string ToString() => Value.ToString();
|
||||
|
||||
public bool Equals(FileHeaderOffset other)
|
||||
{
|
||||
return Value == other.Value;
|
||||
}
|
||||
public int Value => value;
|
||||
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
public override string ToString() => value.ToString();
|
||||
|
||||
public override bool Equals(object? obj) =>
|
||||
obj is FileHeaderOffset other && value == other.Value;
|
||||
|
||||
public override int GetHashCode() => value.GetHashCode();
|
||||
}
|
||||
@@ -61,9 +61,6 @@ internal static partial class FirstPassParser
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Code below commented to fix issue #1208, while not failing test Issue874
|
||||
* The logic would need to be reviewed before re-enabling.
|
||||
|
||||
// If we didn't brute force then use the previous position for ordering.
|
||||
foreach (var obj in streamsAndTables)
|
||||
{
|
||||
@@ -84,11 +81,6 @@ internal static partial class FirstPassParser
|
||||
orderedXrefs.Add(obj);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
orderedXrefs.AddRange(
|
||||
streamsAndTables
|
||||
.OrderBy(x => x.Offset));
|
||||
}
|
||||
|
||||
DictionaryToken? lastTrailer = null;
|
||||
@@ -161,23 +153,6 @@ internal static partial class FirstPassParser
|
||||
{
|
||||
results.Add(table);
|
||||
nextLocation = table.GetPrevious();
|
||||
|
||||
// Also add any optional associated Stream
|
||||
var xRefStm = table.GetXRefStm();
|
||||
if (xRefStm is long xRefStmValue)
|
||||
{
|
||||
var stream = GetXrefStreamOrTable(
|
||||
offset,
|
||||
input,
|
||||
scanner,
|
||||
xRefStmValue,
|
||||
log);
|
||||
|
||||
if (stream != null)
|
||||
{
|
||||
results.Add(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (streamOrTable is XrefStream stream)
|
||||
{
|
||||
|
||||
@@ -16,9 +16,6 @@ internal static class XrefBruteForcer
|
||||
{
|
||||
var results = new List<IXrefSection>();
|
||||
|
||||
// Guard against circular references; only read xref at each offset once
|
||||
var xrefOffsetSeen = new HashSet<long>();
|
||||
|
||||
var bruteForceObjPositions = new Dictionary<IndirectReference, long>();
|
||||
|
||||
DictionaryToken? trailer = null;
|
||||
@@ -134,14 +131,6 @@ internal static class XrefBruteForcer
|
||||
ClearQueues();
|
||||
|
||||
var potentialTableOffset = bytes.CurrentOffset - 4;
|
||||
|
||||
if (xrefOffsetSeen.Contains(potentialTableOffset))
|
||||
{
|
||||
log.Debug($"Skipping circular xref reference at {potentialTableOffset}");
|
||||
continue;
|
||||
}
|
||||
xrefOffsetSeen.Add(potentialTableOffset);
|
||||
|
||||
var table = XrefTableParser.TryReadTableAtOffset(
|
||||
new FileHeaderOffset(0),
|
||||
potentialTableOffset,
|
||||
@@ -163,22 +152,15 @@ internal static class XrefBruteForcer
|
||||
{
|
||||
ClearQueues();
|
||||
|
||||
if (lastObjPosition is not long offset)
|
||||
if (!lastObjPosition.HasValue)
|
||||
{
|
||||
log.Error("Found an /XRef without having encountered an object first");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (xrefOffsetSeen.Contains(offset))
|
||||
{
|
||||
log.Debug($"Skipping circular /XRef reference at {offset}");
|
||||
continue;
|
||||
}
|
||||
xrefOffsetSeen.Add(offset);
|
||||
|
||||
var stream = XrefStreamParser.TryReadStreamAtOffset(
|
||||
new FileHeaderOffset(0),
|
||||
offset,
|
||||
lastObjPosition.Value,
|
||||
bytes,
|
||||
scanner,
|
||||
log);
|
||||
|
||||
@@ -44,14 +44,4 @@ internal sealed class XrefTable : IXrefSection
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public long? GetXRefStm()
|
||||
{
|
||||
if (Dictionary != null && Dictionary.TryGet(NameToken.XrefStm, out NumericToken xRefStm))
|
||||
{
|
||||
return xRefStm.Long;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -22,13 +22,12 @@
|
||||
using PdfFonts.Parser.Parts;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.PdfFonts.Cmap;
|
||||
|
||||
internal static class PdfDocumentFactory
|
||||
{
|
||||
public static PdfDocument Open(ReadOnlyMemory<byte> memory, ParsingOptions? options = null)
|
||||
public static PdfDocument Open(byte[] fileBytes, ParsingOptions? options = null)
|
||||
{
|
||||
var inputBytes = new MemoryInputBytes(memory);
|
||||
var inputBytes = new MemoryInputBytes(fileBytes);
|
||||
|
||||
return Open(inputBytes, options);
|
||||
}
|
||||
@@ -45,23 +44,9 @@
|
||||
|
||||
internal static PdfDocument Open(Stream stream, ParsingOptions? options)
|
||||
{
|
||||
StreamInputBytes streamInput;
|
||||
long initialPosition;
|
||||
|
||||
if (stream is { CanRead: true, CanSeek: false })
|
||||
{
|
||||
// We need the stream to be seekable
|
||||
var ms = new MemoryStream();
|
||||
stream.CopyTo(ms); // Copy the non seekable stream in memory (seekable)
|
||||
ms.Position = 0;
|
||||
streamInput = new StreamInputBytes(ms, true); // The created memory stream will be disposed on document dispose
|
||||
initialPosition = ms.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
streamInput = new StreamInputBytes(stream, false);
|
||||
initialPosition = stream.Position;
|
||||
}
|
||||
var streamInput = new StreamInputBytes(stream, false);
|
||||
|
||||
var initialPosition = stream.Position;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -124,10 +109,8 @@
|
||||
|
||||
var version = FileHeaderParser.Parse(scanner, inputBytes, parsingOptions.UseLenientParsing, parsingOptions.Logger);
|
||||
|
||||
var fileHeaderOffset = new FileHeaderOffset((int)version.OffsetInFile);
|
||||
|
||||
var initialParse = FirstPassParser.Parse(
|
||||
fileHeaderOffset,
|
||||
new FileHeaderOffset((int)version.OffsetInFile),
|
||||
inputBytes,
|
||||
scanner,
|
||||
parsingOptions.Logger);
|
||||
@@ -145,7 +128,7 @@
|
||||
initialParse.BruteForceOffsets,
|
||||
inputBytes);
|
||||
|
||||
var pdfScanner = new PdfTokenScanner(inputBytes, locationProvider, filterProvider, NoOpEncryptionHandler.Instance, fileHeaderOffset, parsingOptions);
|
||||
var pdfScanner = new PdfTokenScanner(inputBytes, locationProvider, filterProvider, NoOpEncryptionHandler.Instance, parsingOptions);
|
||||
|
||||
var (rootReference, rootDictionary) = ParseTrailer(
|
||||
trailer,
|
||||
@@ -169,27 +152,22 @@
|
||||
|
||||
var encodingReader = new EncodingReader(pdfScanner);
|
||||
|
||||
var cmapCache = new CMapLocalCache(filterProvider, pdfScanner);
|
||||
|
||||
var type0Handler = new Type0FontHandler(
|
||||
cidFontFactory,
|
||||
filterProvider,
|
||||
pdfScanner,
|
||||
cmapCache,
|
||||
parsingOptions);
|
||||
|
||||
var type1Handler = new Type1FontHandler(
|
||||
pdfScanner,
|
||||
filterProvider,
|
||||
encodingReader,
|
||||
cmapCache,
|
||||
parsingOptions.UseLenientParsing);
|
||||
|
||||
var trueTypeHandler = new TrueTypeFontHandler(
|
||||
parsingOptions.Logger,
|
||||
var trueTypeHandler = new TrueTypeFontHandler(parsingOptions.Logger,
|
||||
pdfScanner,
|
||||
filterProvider,
|
||||
encodingReader,
|
||||
cmapCache,
|
||||
SystemFontFinder.Instance,
|
||||
type1Handler);
|
||||
|
||||
@@ -198,7 +176,7 @@
|
||||
type0Handler,
|
||||
trueTypeHandler,
|
||||
type1Handler,
|
||||
new Type3FontHandler(pdfScanner, encodingReader, cmapCache));
|
||||
new Type3FontHandler(pdfScanner, filterProvider, encodingReader));
|
||||
|
||||
var resourceContainer = new ResourceStore(pdfScanner, fontFactory, filterProvider, parsingOptions);
|
||||
|
||||
@@ -247,11 +225,6 @@
|
||||
|
||||
var rootDictionary = DirectObjectFinder.Get<DictionaryToken>(trailer.Root, pdfTokenScanner)!;
|
||||
|
||||
if (rootDictionary is null)
|
||||
{
|
||||
throw new PdfDocumentFormatException($"The root object in the trailer did not resolve to a readable dictionary.");
|
||||
}
|
||||
|
||||
if (!rootDictionary.ContainsKey(NameToken.Type) && isLenientParsing)
|
||||
{
|
||||
rootDictionary = rootDictionary.With(NameToken.Type, NameToken.Catalog);
|
||||
|
||||
@@ -102,14 +102,6 @@
|
||||
/// <returns>A <see cref="PdfDocument"/> providing access to the file contents.</returns>
|
||||
public static PdfDocument Open(byte[] fileBytes, ParsingOptions? options = null) => PdfDocumentFactory.Open(fileBytes, options);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="PdfDocument"/> for reading from the provided file bytes.
|
||||
/// </summary>
|
||||
/// <param name="memory">The bytes of the PDF file.</param>
|
||||
/// <param name="options">Optional parameters controlling parsing.</param>
|
||||
/// <returns>A <see cref="PdfDocument"/> providing access to the file contents.</returns>
|
||||
public static PdfDocument Open(ReadOnlyMemory<byte> memory, ParsingOptions? options = null) => PdfDocumentFactory.Open(memory, options);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a file and creates a <see cref="PdfDocument"/> for reading from the provided file path.
|
||||
/// </summary>
|
||||
@@ -120,14 +112,10 @@
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="PdfDocument"/> for reading from the provided stream.
|
||||
/// <para>
|
||||
/// If the stream provided is not seekable (<see cref="Stream.CanSeek"/> is <c>false</c>), the stream will be copied into a new <see cref="MemoryStream"/>.
|
||||
/// </para>
|
||||
/// The caller must manage disposing the stream. The created PdfDocument will not dispose the stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">
|
||||
/// A stream of the file contents, this must support reading and seeking.
|
||||
/// <para>If the stream provided is not seekable (<see cref="Stream.CanSeek"/> is <c>false</c>), the stream will be copied into a new <see cref="MemoryStream"/>.</para>
|
||||
/// The PdfDocument will not dispose of the provided stream.
|
||||
/// </param>
|
||||
/// <param name="options">Optional parameters controlling parsing.</param>
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Core;
|
||||
using Filters;
|
||||
using Parser.Parts;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for PDF types.
|
||||
@@ -64,7 +62,6 @@
|
||||
double totalMaxEstSize = stream.Data.Length * 100;
|
||||
|
||||
var transform = stream.Data;
|
||||
|
||||
for (var i = 0; i < filters.Count; i++)
|
||||
{
|
||||
var filter = filters[i];
|
||||
@@ -92,7 +89,6 @@
|
||||
double totalMaxEstSize = stream.Data.Length * 100;
|
||||
|
||||
var transform = stream.Data;
|
||||
|
||||
for (var i = 0; i < filters.Count; i++)
|
||||
{
|
||||
var filter = filters[i];
|
||||
@@ -157,23 +153,6 @@
|
||||
resolvedItems[NameToken.Create(kvp.Key)] = ResolveInternal(value, scanner, visited);
|
||||
}
|
||||
|
||||
if (resolvedItems.Count != dict.Data.Count)
|
||||
{
|
||||
if (resolvedItems.Count > dict.Data.Count)
|
||||
{
|
||||
throw new InvalidOperationException("Resolved more items than were present in the original dictionary. This should not be possible.");
|
||||
}
|
||||
|
||||
// We missed some due to cycles, try and resolve them now.
|
||||
foreach (var missing in dict.Data.Keys.Except(resolvedItems.Keys.Select(k => k.Data), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (dict.Data[missing] is IndirectReferenceToken reference)
|
||||
{
|
||||
resolvedItems[NameToken.Create(missing)] = ResolveInternal(reference, scanner, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new DictionaryToken(resolvedItems);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,10 +57,6 @@
|
||||
|
||||
TransformationMatrix GetFontMatrix(int characterIdentifier);
|
||||
|
||||
double GetDescent();
|
||||
|
||||
double GetAscent();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the glyph path for the given character code.
|
||||
/// </summary>
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
|
||||
bool TryGetBoundingAdvancedWidth(int characterIdentifier, out double width);
|
||||
|
||||
double? GetDescent();
|
||||
|
||||
double? GetAscent();
|
||||
|
||||
bool TryGetPath(int characterCode, [NotNullWhen(true)] out IReadOnlyList<PdfSubpath>? path);
|
||||
|
||||
bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, [NotNullWhen(true)] out IReadOnlyList<PdfSubpath>? path);
|
||||
|
||||
@@ -42,18 +42,6 @@
|
||||
|
||||
public PdfRectangle? GetCharacterBoundingBox(string characterName) => fontCollection.GetCharacterBoundingBox(characterName);
|
||||
|
||||
public double? GetDescent()
|
||||
{
|
||||
// BobLd: we don't support ascent / descent for cff for the moment
|
||||
return null;
|
||||
}
|
||||
|
||||
public double? GetAscent()
|
||||
{
|
||||
// BobLd: we don't support ascent / descent for cff for the moment
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryGetBoundingBox(int characterIdentifier, out PdfRectangle boundingBox)
|
||||
{
|
||||
boundingBox = new PdfRectangle(0, 0, 500, 0);
|
||||
|
||||
@@ -36,16 +36,6 @@
|
||||
|
||||
public int GetFontMatrixMultiplier() => font.GetUnitsPerEm();
|
||||
|
||||
public double? GetDescent()
|
||||
{
|
||||
return font.TableRegister.HorizontalHeaderTable.Descent;
|
||||
}
|
||||
|
||||
public double? GetAscent()
|
||||
{
|
||||
return font.TableRegister.HorizontalHeaderTable.Ascent;
|
||||
}
|
||||
|
||||
public bool TryGetFontMatrix(int characterCode, [NotNullWhen(true)] out TransformationMatrix? matrix)
|
||||
{
|
||||
// We don't have a matrix here
|
||||
|
||||
@@ -148,38 +148,6 @@
|
||||
return fontProgram.TryGetFontMatrix(characterIdentifier, out var m) ? m.Value : FontMatrix;
|
||||
}
|
||||
|
||||
public double GetDescent()
|
||||
{
|
||||
if (fontProgram is null)
|
||||
{
|
||||
return Descriptor.Descent;
|
||||
}
|
||||
|
||||
double? descent = fontProgram.GetDescent();
|
||||
if (descent.HasValue)
|
||||
{
|
||||
return descent.Value;
|
||||
}
|
||||
|
||||
return Descriptor.Descent;
|
||||
}
|
||||
|
||||
public double GetAscent()
|
||||
{
|
||||
if (fontProgram is null)
|
||||
{
|
||||
return Descriptor.Ascent;
|
||||
}
|
||||
|
||||
double? ascent = fontProgram.GetAscent();
|
||||
if (ascent.HasValue)
|
||||
{
|
||||
return ascent.Value;
|
||||
}
|
||||
|
||||
return Descriptor.Ascent;
|
||||
}
|
||||
|
||||
public bool TryGetPath(int characterCode, [NotNullWhen(true)] out IReadOnlyList<PdfSubpath>? path)
|
||||
{
|
||||
path = null;
|
||||
|
||||
@@ -132,38 +132,6 @@
|
||||
return FontMatrix;
|
||||
}
|
||||
|
||||
public double GetDescent()
|
||||
{
|
||||
if (fontProgram is null)
|
||||
{
|
||||
return Descriptor.Descent;
|
||||
}
|
||||
|
||||
double? descent = fontProgram.GetDescent();
|
||||
if (descent.HasValue)
|
||||
{
|
||||
return descent.Value;
|
||||
}
|
||||
|
||||
return Descriptor.Descent;
|
||||
}
|
||||
|
||||
public double GetAscent()
|
||||
{
|
||||
if (fontProgram is null)
|
||||
{
|
||||
return Descriptor.Ascent;
|
||||
}
|
||||
|
||||
double? ascent = fontProgram.GetAscent();
|
||||
if (ascent.HasValue)
|
||||
{
|
||||
return ascent.Value;
|
||||
}
|
||||
|
||||
return Descriptor.Ascent;
|
||||
}
|
||||
|
||||
public bool TryGetPath(int characterCode, [NotNullWhen(true)] out IReadOnlyList<PdfSubpath>? path) => TryGetPath(characterCode, cidToGid.GetGlyphIndex, out path);
|
||||
|
||||
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, [NotNullWhen(true)] out IReadOnlyList<PdfSubpath>? path)
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
|
||||
if (CMapParser.TryParseExternal(name, out result))
|
||||
{
|
||||
|
||||
Cache[name] = result;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -41,7 +43,9 @@
|
||||
throw new ArgumentNullException(nameof(bytes));
|
||||
}
|
||||
|
||||
return CMapParser.Parse(bytes);
|
||||
var result = CMapParser.Parse(bytes);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
namespace UglyToad.PdfPig.PdfFonts.Cmap
|
||||
{
|
||||
using Core;
|
||||
using Filters;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a local (per document) cache for CMap objects, allowing efficient retrieval and storage of CMap instances based on
|
||||
/// their names and unique identifiers.
|
||||
/// </summary>
|
||||
/// <remarks>This class is designed to cache CMap objects to improve performance by avoiding redundant
|
||||
/// parsing of CMap data. It uses a combination of CMap names and GUIDs derived from the CMap data to uniquely
|
||||
/// identify and store CMap instances.</remarks>
|
||||
internal sealed class CMapLocalCache
|
||||
{
|
||||
private static ReadOnlySpan<byte> cmapNameTag => @"/CMapName "u8;
|
||||
private readonly object cacheLock = new object();
|
||||
|
||||
private readonly Dictionary<string, Dictionary<Guid, CMap>> _cache = new();
|
||||
private readonly ILookupFilterProvider _filterProvider;
|
||||
private readonly IPdfTokenScanner _scanner;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a local (per document) cache for CMap objects, allowing efficient retrieval and storage of CMap instances based on
|
||||
/// their names and unique identifiers.
|
||||
/// </summary>
|
||||
/// <remarks>This class is designed to cache CMap objects to improve performance by avoiding redundant
|
||||
/// parsing of CMap data. It uses a combination of CMap names and GUIDs derived from the CMap data to uniquely
|
||||
/// identify and store CMap instances.</remarks>
|
||||
public CMapLocalCache(ILookupFilterProvider filterProvider, IPdfTokenScanner scanner)
|
||||
{
|
||||
_filterProvider = filterProvider;
|
||||
_scanner = scanner;
|
||||
}
|
||||
|
||||
public bool TryGet(string name, [NotNullWhen(true)] out CMap? result)
|
||||
{
|
||||
return CMapCache.TryGet(name, out result);
|
||||
}
|
||||
|
||||
private static Guid GetGuid(ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
// Assumes MurmurHash3 is good enough for hashing CMap data to create unique identifiers,
|
||||
// i.e. collisions should be extremely rare.
|
||||
return new Guid(MurmurHash3.Compute_x64_128(bytes));
|
||||
}
|
||||
|
||||
public bool TryGet(StreamToken token, [NotNullWhen(true)] out CMap? result)
|
||||
{
|
||||
if (token.Data.IsEmpty)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var decodedUnicodeCMap = token.Decode(_filterProvider, _scanner);
|
||||
|
||||
if (!TryGetNameFast(decodedUnicodeCMap.Span, out string? cmapName))
|
||||
{
|
||||
result = CMapCache.Parse(new MemoryInputBytes(decodedUnicodeCMap));
|
||||
return true;
|
||||
}
|
||||
|
||||
var guid = GetGuid(decodedUnicodeCMap.Span);
|
||||
|
||||
lock (cacheLock)
|
||||
{
|
||||
if (!_cache.TryGetValue(cmapName!, out var cMaps))
|
||||
{
|
||||
cMaps = new Dictionary<Guid, CMap>();
|
||||
_cache[cmapName!] = cMaps;
|
||||
}
|
||||
|
||||
if (cMaps.TryGetValue(guid, out result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
result = CMapCache.Parse(new MemoryInputBytes(decodedUnicodeCMap));
|
||||
cMaps[guid] = result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryGetNameFast(ReadOnlySpan<byte> bytes, out string? name)
|
||||
{
|
||||
name = null;
|
||||
int nameIndex = bytes.IndexOf(cmapNameTag);
|
||||
|
||||
if (nameIndex <= -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
nameIndex += cmapNameTag.Length;
|
||||
|
||||
int nameEndIndex = bytes.Slice(nameIndex).IndexOf("def"u8);
|
||||
|
||||
if (nameEndIndex <= -1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
name = Encoding.UTF8.GetString(bytes.Slice(nameIndex, nameEndIndex - 1));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,15 +81,11 @@ namespace UglyToad.PdfPig.PdfFonts.Cmap
|
||||
|
||||
public CMap Build()
|
||||
{
|
||||
#if NET
|
||||
BaseFontCharacterMap?.TrimExcess();
|
||||
#endif
|
||||
|
||||
return new CMap(GetCidSystemInfo(), Type, WMode, Name, Version,
|
||||
BaseFontCharacterMap ?? new Dictionary<int, string>(),
|
||||
CodespaceRanges ?? Array.Empty<CodespaceRange>(),
|
||||
CidRanges ?? Array.Empty<CidRange>(),
|
||||
CidCharacterMappings ?? Array.Empty<CidCharacterMapping>());
|
||||
CodespaceRanges ?? new CodespaceRange[0],
|
||||
CidRanges ?? new CidRange[0],
|
||||
CidCharacterMappings ?? new CidCharacterMapping[0]);
|
||||
}
|
||||
|
||||
private CharacterIdentifierSystemInfo GetCidSystemInfo()
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
= new Dictionary<int, CharacterBoundingBox>();
|
||||
|
||||
private readonly bool useLenientParsing;
|
||||
private readonly double ascent;
|
||||
private readonly double descent;
|
||||
|
||||
public NameToken Name => BaseFont;
|
||||
|
||||
@@ -59,30 +57,6 @@
|
||||
?? FontDetails.GetDefault(Name.Data);
|
||||
|
||||
useLenientParsing = parsingOptions.UseLenientParsing;
|
||||
ascent = ComputeAscent();
|
||||
descent = ComputeDescent();
|
||||
}
|
||||
|
||||
private double ComputeDescent()
|
||||
{
|
||||
double d = CidFont.GetDescent();
|
||||
if (Math.Abs(d) > double.Epsilon)
|
||||
{
|
||||
return GetFontMatrix().TransformY(d);
|
||||
}
|
||||
|
||||
return -0.25;
|
||||
}
|
||||
|
||||
private double ComputeAscent()
|
||||
{
|
||||
double a = CidFont.GetAscent();
|
||||
if (Math.Abs(a) > double.Epsilon)
|
||||
{
|
||||
return GetFontMatrix().TransformY(a);
|
||||
}
|
||||
|
||||
return 0.75;
|
||||
}
|
||||
|
||||
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)
|
||||
@@ -152,11 +126,12 @@
|
||||
// Get the bounding box in glyph space
|
||||
var boundingBox = CidFont.GetBoundingBox(characterIdentifier);
|
||||
|
||||
boundingBox = CidFont.GetFontMatrix(characterIdentifier).Transform(boundingBox);
|
||||
var fontMatrix = CidFont.GetFontMatrix(characterIdentifier);
|
||||
boundingBox = fontMatrix.Transform(boundingBox);
|
||||
|
||||
var width = CidFont.GetWidthFromFont(characterIdentifier);
|
||||
|
||||
var advanceWidth = GetFontMatrix().TransformX(width);
|
||||
var advanceWidth = fontMatrix.TransformX(width);
|
||||
|
||||
var result = new CharacterBoundingBox(boundingBox, advanceWidth);
|
||||
|
||||
@@ -170,16 +145,6 @@
|
||||
return CidFont.FontMatrix;
|
||||
}
|
||||
|
||||
public double GetDescent()
|
||||
{
|
||||
return descent;
|
||||
}
|
||||
|
||||
public double GetAscent()
|
||||
{
|
||||
return ascent;
|
||||
}
|
||||
|
||||
public PdfVector GetPositionVector(int characterCode)
|
||||
{
|
||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// <summary>
|
||||
/// Summary details of the font used to draw a glyph.
|
||||
/// </summary>
|
||||
public sealed class FontDetails
|
||||
public class FontDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// The normal weight for a font.
|
||||
@@ -35,8 +35,6 @@
|
||||
/// </summary>
|
||||
public bool IsItalic { get; }
|
||||
|
||||
private readonly Lazy<FontDetails> _bold;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="FontDetails"/>.
|
||||
/// </summary>
|
||||
@@ -46,17 +44,6 @@
|
||||
IsBold = isBold;
|
||||
Weight = weight;
|
||||
IsItalic = isItalic;
|
||||
|
||||
_bold = isBold ? new Lazy<FontDetails>(() => this) : new Lazy<FontDetails>(() => new FontDetails(Name, true, Weight, IsItalic));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An instance of <see cref="FontDetails"/> with the same properties as the current instance,
|
||||
/// but with the <see cref="IsBold"/> property set to <c>true</c>.
|
||||
/// </summary>
|
||||
public FontDetails AsBold()
|
||||
{
|
||||
return _bold.Value;
|
||||
}
|
||||
|
||||
internal static FontDetails GetDefault(string? name = null) => new FontDetails(name ?? string.Empty,
|
||||
@@ -64,7 +51,7 @@
|
||||
DefaultWeight,
|
||||
false);
|
||||
|
||||
internal FontDetails WithName(string? name) => name is not null
|
||||
internal FontDetails WithName(string name) => name != null
|
||||
? new FontDetails(name, IsBold, Weight, IsItalic)
|
||||
: this;
|
||||
|
||||
|
||||
@@ -45,24 +45,6 @@
|
||||
/// </summary>
|
||||
TransformationMatrix GetFontMatrix();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the descent value of the font, adjusted by the font matrix.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="double"/> representing the descent of the font,
|
||||
/// which is the distance from the baseline to the lowest point of the font's glyphs.
|
||||
/// </returns>
|
||||
double GetDescent();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the ascent value of the font, adjusted byt the font matrix.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="double"/> representing the ascent of the font,
|
||||
/// which is the distance from the baseline to the highest point of the font's glyphs.
|
||||
/// </returns>
|
||||
double GetAscent();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the glyph path for the given character code.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user