From 285c6f0481b8395bc5a743ad96fa532812e74c61 Mon Sep 17 00:00:00 2001
From: BobLd <38405645+BobLd@users.noreply.github.com>
Date: Mon, 4 May 2026 09:52:51 +0100
Subject: [PATCH 1/7] Update NuGet packages and bump version
---
Tabula.Csv/Tabula.Csv.csproj | 52 ++++++++++++++++----------------
Tabula.Json/Tabula.Json.csproj | 52 ++++++++++++++++----------------
Tabula.Tests/Tabula.Tests.csproj | 4 +--
Tabula/Tabula.csproj | 52 ++++++++++++++++----------------
4 files changed, 80 insertions(+), 80 deletions(-)
diff --git a/Tabula.Csv/Tabula.Csv.csproj b/Tabula.Csv/Tabula.Csv.csproj
index 9f8ccae..603eaac 100644
--- a/Tabula.Csv/Tabula.Csv.csproj
+++ b/Tabula.Csv/Tabula.Csv.csproj
@@ -1,32 +1,32 @@
-
- netstandard2.0;net462;net471;net6.0;net8.0
- Extract tables from PDF files (port of tabula-java using PdfPig). Csv and Tsv writers.
- https://github.com/BobLd/tabula-sharp
- 0.1.5
- BobLd
- pdf, extract, table, tabula, pdfpig, parse, extraction, csv, tsv, excel, export
- MIT
- https://github.com/BobLd/tabula-sharp
- True
- True
- README.md
-
+
+ netstandard2.0;netstandard2.1;net462;net471;net6.0;net8.0;net9.0
+ Extract tables from PDF files (port of tabula-java using PdfPig). Csv and Tsv writers.
+ https://github.com/BobLd/tabula-sharp
+ 1.0.0
+ BobLd
+ pdf, extract, table, tabula, pdfpig, parse, extraction, csv, tsv, excel, export
+ MIT
+ https://github.com/BobLd/tabula-sharp
+ True
+ True
+ README.md
+
-
-
- True
- \
-
-
+
+
+ True
+ \
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
\ No newline at end of file
diff --git a/Tabula.Json/Tabula.Json.csproj b/Tabula.Json/Tabula.Json.csproj
index 06cb3aa..9e94d05 100644
--- a/Tabula.Json/Tabula.Json.csproj
+++ b/Tabula.Json/Tabula.Json.csproj
@@ -1,32 +1,32 @@
-
- netstandard2.0;net462;net471;net6.0;net8.0
- Extract tables from PDF files (port of tabula-java using PdfPig). Json writer.
- https://github.com/BobLd/tabula-sharp
- 0.1.5
- BobLd
- BobLd
- pdf, extract, table, tabula, pdfpig, parse, extraction, json, export
- MIT
- https://github.com/BobLd/tabula-sharp
- True
- README.md
-
+
+ netstandard2.0;netstandard2.1;net462;net471;net6.0;net8.0;net9.0
+ Extract tables from PDF files (port of tabula-java using PdfPig). Json writer.
+ https://github.com/BobLd/tabula-sharp
+ 1.0.0
+ BobLd
+ BobLd
+ pdf, extract, table, tabula, pdfpig, parse, extraction, json, export
+ MIT
+ https://github.com/BobLd/tabula-sharp
+ True
+ README.md
+
-
-
- True
- \
-
-
+
+
+ True
+ \
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
+
\ No newline at end of file
diff --git a/Tabula.Tests/Tabula.Tests.csproj b/Tabula.Tests/Tabula.Tests.csproj
index 5816e9b..97a5781 100644
--- a/Tabula.Tests/Tabula.Tests.csproj
+++ b/Tabula.Tests/Tabula.Tests.csproj
@@ -9,9 +9,9 @@
-
+
-
+
all
diff --git a/Tabula/Tabula.csproj b/Tabula/Tabula.csproj
index fa30e1e..6a3490e 100644
--- a/Tabula/Tabula.csproj
+++ b/Tabula/Tabula.csproj
@@ -1,31 +1,31 @@
-
- netstandard2.0;net462;net471;net6.0;net8.0
- Extract tables from PDF files (port of tabula-java using PdfPig).
- https://github.com/BobLd/tabula-sharp
- 0.1.5
- BobLd
- BobLd
- pdf, extract, table, tabula, pdfpig, parse, extraction, export
- MIT
- https://github.com/BobLd/tabula-sharp
- README.md
- True
- True
- True
- Tabula.snk
-
+
+ netstandard2.0;netstandard2.1;net462;net471;net6.0;net8.0;net9.0
+ Extract tables from PDF files (port of tabula-java using PdfPig).
+ https://github.com/BobLd/tabula-sharp
+ 1.0.0
+ BobLd
+ BobLd
+ pdf, extract, table, tabula, pdfpig, parse, extraction, export
+ MIT
+ https://github.com/BobLd/tabula-sharp
+ README.md
+ True
+ True
+ True
+ Tabula.snk
+
-
-
- True
- \
-
-
+
+
+ True
+ \
+
+
-
-
-
+
+
+
-
+
\ No newline at end of file
From 87aa14802165a32e8f7435c235328f0709b95638 Mon Sep 17 00:00:00 2001
From: BobLd <38405645+BobLd@users.noreply.github.com>
Date: Mon, 4 May 2026 09:59:52 +0100
Subject: [PATCH 2/7] Make TextElement's Letter property public and fix #54
---
Tabula/TextElement.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/Tabula/TextElement.cs b/Tabula/TextElement.cs
index ca4c806..833b9ec 100644
--- a/Tabula/TextElement.cs
+++ b/Tabula/TextElement.cs
@@ -39,6 +39,11 @@ public TextElement(PdfRectangle pdfRectangle, FontDetails font, double fontSize,
}
public string GetText() => text;
+ ///
+ /// Gets the PdfPig letter associated.
+ ///
+ public Letter Letter => letter;
+
///
/// The direction of the text (0, 90, 180, or 270). Can be any number with PdfPig.
///
From 77382c02530a1d0fa081598f446999073ad59cd7 Mon Sep 17 00:00:00 2001
From: BobLd <38405645+BobLd@users.noreply.github.com>
Date: Mon, 4 May 2026 10:27:47 +0100
Subject: [PATCH 3/7] Refine whitespace filter in TextStripper (ports
tabula-java 21a4932b) and prevent infinite loop in
SimpleNurminenDetectionAlgorithm, fix #30
---
Tabula.Tests/GitHubIssues.cs | 22 +++++++++++++++
Tabula.Tests/Resources/issue.pdf | Bin 0 -> 12114 bytes
Tabula.Tests/Tabula.Tests.csproj | 3 ++
.../SimpleNurminenDetectionAlgorithm.cs | 16 +++++------
Tabula/TableRectangle.cs | 26 ++++++++----------
Tabula/TextStripper.cs | 14 ++++++++--
6 files changed, 56 insertions(+), 25 deletions(-)
create mode 100644 Tabula.Tests/GitHubIssues.cs
create mode 100644 Tabula.Tests/Resources/issue.pdf
diff --git a/Tabula.Tests/GitHubIssues.cs b/Tabula.Tests/GitHubIssues.cs
new file mode 100644
index 0000000..c81a555
--- /dev/null
+++ b/Tabula.Tests/GitHubIssues.cs
@@ -0,0 +1,22 @@
+using Tabula.Detectors;
+using UglyToad.PdfPig;
+using Xunit;
+
+namespace Tabula.Tests
+{
+ public class GithubIssues
+ {
+ [Fact]
+ public void Issue30()
+ {
+ using (PdfDocument document = PdfDocument.Open("Resources/issue.pdf", new ParsingOptions() { ClipPaths = true }))
+ {
+ PageArea page = ObjectExtractor.Extract(document, 1);
+
+ SimpleNurminenDetectionAlgorithm detector = new SimpleNurminenDetectionAlgorithm();
+ var regions = detector.Detect(page);
+ Assert.Single(regions);
+ }
+ }
+ }
+}
diff --git a/Tabula.Tests/Resources/issue.pdf b/Tabula.Tests/Resources/issue.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..57b0cea899ab0ce968401cdfb8a724cd40f73fe8
GIT binary patch
literal 12114
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
literal 0
HcmV?d00001
diff --git a/Tabula.Tests/Tabula.Tests.csproj b/Tabula.Tests/Tabula.Tests.csproj
index 97a5781..59bbdbd 100644
--- a/Tabula.Tests/Tabula.Tests.csproj
+++ b/Tabula.Tests/Tabula.Tests.csproj
@@ -957,6 +957,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/Tabula/Detectors/SimpleNurminenDetectionAlgorithm.cs b/Tabula/Detectors/SimpleNurminenDetectionAlgorithm.cs
index e7bef85..9006338 100644
--- a/Tabula/Detectors/SimpleNurminenDetectionAlgorithm.cs
+++ b/Tabula/Detectors/SimpleNurminenDetectionAlgorithm.cs
@@ -110,7 +110,7 @@ public IReadOnlyList Detect(PageArea page)
List allEdges = new List(horizontalRulings);
allEdges.AddRange(verticalRulings);
- List tableAreas = new List();
+ HashSet tableAreas = new HashSet();
// if we found some edges, try to find some tables based on them
if (allEdges.Count > 0)
@@ -236,13 +236,14 @@ public IReadOnlyList Detect(PageArea page)
// get rid of any text lines contained within existing tables, this allows us to find more tables
//for (Iterator iterator = lines.iterator(); iterator.hasNext();)
- foreach (var textRow in lines.ToList())
+ for (int i = lines.Count - 1; i >= 0; i--)
{
- foreach (TableRectangle table in tableAreas)
+ var textRow = lines[i];
+ foreach (var table in tableAreas)
{
if (table.Contains(textRow))
{
- lines.Remove(textRow);
+ lines.RemoveAt(i);
break;
}
}
@@ -278,8 +279,7 @@ public IReadOnlyList Detect(PageArea page)
if (table != null)
{
- foundTable = true;
- tableAreas.Add(table);
+ foundTable = tableAreas.Add(table);
}
}
} while (foundTable);
@@ -725,7 +725,7 @@ private TextEdges getTextEdges(List lines)
return new TextEdges(leftTextEdges, midTextEdges, rightTextEdges);
}
- private List getTableAreasFromCells(List cells)
+ private HashSet getTableAreasFromCells(List cells)
{
List> cellGroups = new List>();
foreach (TableRectangle cell in cells)
@@ -764,7 +764,7 @@ private List getTableAreasFromCells(List cells)
}
// create table areas based on cell group
- List tableAreas = new List();
+ HashSet tableAreas = new HashSet();
foreach (List cellGroup in cellGroups)
{
// less than four cells should not make a table
diff --git a/Tabula/TableRectangle.cs b/Tabula/TableRectangle.cs
index e1e1245..7687277 100644
--- a/Tabula/TableRectangle.cs
+++ b/Tabula/TableRectangle.cs
@@ -11,7 +11,7 @@ namespace Tabula
///
/// A tabula rectangle.
///
- public class TableRectangle : IComparable
+ public class TableRectangle : IComparable, IEquatable
{
///
/// Sort top to bottom (as in reading order).
@@ -212,30 +212,26 @@ public void SetBottom(double bottom)
public override int GetHashCode()
{
- int prime = 31;
- int result = base.GetHashCode();
- // need to implement hash and equal in PdfPig's PdfRectangle
- result = prime * result + BoundingBox.BottomLeft.GetHashCode();
- result = prime * result + BoundingBox.TopLeft.GetHashCode();
- result = prime * result + BoundingBox.TopRight.GetHashCode();
- result = prime * result + BoundingBox.BottomRight.GetHashCode();
- result = prime * result + BitConverter.ToInt32(BitConverter.GetBytes(BoundingBox.Rotation), 0);
- return result;
+ return BoundingBox.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is TableRectangle other)
{
- if (!this.BoundingBox.BottomLeft.Equals(other.BoundingBox.BottomLeft)) return false;
- if (!this.BoundingBox.TopLeft.Equals(other.BoundingBox.TopLeft)) return false;
- if (!this.BoundingBox.TopRight.Equals(other.BoundingBox.TopRight)) return false;
- if (!this.BoundingBox.BottomRight.Equals(other.BoundingBox.BottomRight)) return false;
- return true;
+ return Equals(other);
}
+
return false;
}
+ public bool Equals(TableRectangle other)
+ {
+ if (other is null) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return BoundingBox.Equals(other.BoundingBox);
+ }
+
public override string ToString()
{
StringBuilder sb = new StringBuilder();
diff --git a/Tabula/TextStripper.cs b/Tabula/TextStripper.cs
index b449b0d..0d61e78 100644
--- a/Tabula/TextStripper.cs
+++ b/Tabula/TextStripper.cs
@@ -14,6 +14,8 @@ public sealed class TextStripper
{
private const string NBSP = "\u00A0";
private static float AVG_HEIGHT_MULT_THRESHOLD = 6.0f;
+ private static float MAX_BLANK_FONT_SIZE = 40.0f;
+ private static float MIN_BLANK_FONT_SIZE = 2.0f;
///
/// Process the page.
@@ -54,9 +56,17 @@ public static TextStripperResult Process(Page page)
totalHeight += Math.Max(te.Height, 1); // added by bobld: min height value to 1
double avgHeight = totalHeight / countHeight;
- if (avgHeight > 0 && te.Height >= (avgHeight * AVG_HEIGHT_MULT_THRESHOLD) && (te.GetText()?.Trim().Equals("") != false))
+ if (te.GetText()?.Trim().Equals("") != false)
{
- continue;
+ if (avgHeight > 0 && te.Height >= (avgHeight * AVG_HEIGHT_MULT_THRESHOLD))
+ {
+ continue;
+ }
+
+ if (letter.PointSize > MAX_BLANK_FONT_SIZE || letter.PointSize < MIN_BLANK_FONT_SIZE)
+ {
+ continue;
+ }
}
textElements.Add(te);
From 08892148661b39afe88fa9adcff89577869bf160 Mon Sep 17 00:00:00 2001
From: BobLd <38405645+BobLd@users.noreply.github.com>
Date: Mon, 4 May 2026 14:29:38 +0100
Subject: [PATCH 4/7] Attempt at fixing infinite loop in
SpreadsheetExtractionAlgorithm (#50)
---
.claude/settings.local.json | 8 ++++++++
.../SpreadsheetExtractionAlgorithm.cs | 20 +++++++++----------
2 files changed, 18 insertions(+), 10 deletions(-)
create mode 100644 .claude/settings.local.json
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..f9eb3d6
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,8 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(dotnet build *)",
+ "Bash(dotnet test *)"
+ ]
+ }
+}
diff --git a/Tabula/Extractors/SpreadsheetExtractionAlgorithm.cs b/Tabula/Extractors/SpreadsheetExtractionAlgorithm.cs
index a24e9df..4cca586 100644
--- a/Tabula/Extractors/SpreadsheetExtractionAlgorithm.cs
+++ b/Tabula/Extractors/SpreadsheetExtractionAlgorithm.cs
@@ -287,7 +287,6 @@ public static List FindSpreadsheetsFromCells(List pointSet = new HashSet();
Dictionary edgesH = new Dictionary();
Dictionary edgesV = new Dictionary();
- int i = 0;
cells = new List(new HashSet(cells));
@@ -316,26 +315,27 @@ public static List FindSpreadsheetsFromCells(List pointsSortY = new List(pointSet);
pointsSortY.Sort(new POINT_COMPARER());
- while (i < pointSet.Count)
+ // Pair consecutive points sharing a Y / X coordinate. Using a for-loop with an
+ // unconditional increment (rather than the upstream tabula-java nested while)
+ // prevents an infinite loop when a coordinate is NaN or Infinity, since Feq
+ // returns false for any NaN comparison and would otherwise leave i unchanged.
+ for (int i = 0; i + 1 < pointSet.Count; i++)
{
- float currY = (float)pointsSortY[i].Y;
- while (i < pointSet.Count && Utils.Feq(pointsSortY[i].Y, currY))
+ if (Utils.Feq(pointsSortY[i].Y, pointsSortY[i + 1].Y))
{
edgesH[pointsSortY[i]] = pointsSortY[i + 1];
edgesH[pointsSortY[i + 1]] = pointsSortY[i];
- i += 2;
+ i++;
}
}
- i = 0;
- while (i < pointSet.Count)
+ for (int i = 0; i + 1 < pointSet.Count; i++)
{
- float currX = (float)pointsSortX[i].X;
- while (i < pointSet.Count && Utils.Feq(pointsSortX[i].X, currX))
+ if (Utils.Feq(pointsSortX[i].X, pointsSortX[i + 1].X))
{
edgesV[pointsSortX[i]] = pointsSortX[i + 1];
edgesV[pointsSortX[i + 1]] = pointsSortX[i];
- i += 2;
+ i++;
}
}
From e13f43586e05aaf1b93643f5f17c4532db7cfd2a Mon Sep 17 00:00:00 2001
From: BobLd <38405645+BobLd@users.noreply.github.com>
Date: Mon, 4 May 2026 14:38:15 +0100
Subject: [PATCH 5/7] Remove obsolete PdfPig properties and misc
---
Tabula/ObjectExtractor.cs | 2 +-
Tabula/PageIterator.cs | 15 +--------------
Tabula/TextStripper.cs | 38 +++++++++++++++++++-------------------
3 files changed, 21 insertions(+), 34 deletions(-)
diff --git a/Tabula/ObjectExtractor.cs b/Tabula/ObjectExtractor.cs
index ab45c9e..7fd5dbb 100644
--- a/Tabula/ObjectExtractor.cs
+++ b/Tabula/ObjectExtractor.cs
@@ -75,7 +75,7 @@ public static PageArea ExtractPage(Page page)
// se.processPage(p);
var rulings = new List();
- foreach (var path in page.ExperimentalAccess.Paths)
+ foreach (var path in page.Paths)
{
if (!path.IsFilled && !path.IsStroked) continue; // strokeOrFillPath operator => filter stroke and filled
diff --git a/Tabula/PageIterator.cs b/Tabula/PageIterator.cs
index 9b3c0cd..334cfdb 100644
--- a/Tabula/PageIterator.cs
+++ b/Tabula/PageIterator.cs
@@ -28,20 +28,7 @@ public PageIterator(PdfDocument pdfDocument, IEnumerable pages) : base()
}
///
- public PageArea Current
- {
- get
- {
- try
- {
- return ObjectExtractor.ExtractPage(this.pdfDocument, pageIndexIterator.Current);
- }
- catch (Exception ex)
- {
- throw ex;
- }
- }
- }
+ public PageArea Current => ObjectExtractor.ExtractPage(this.pdfDocument, pageIndexIterator.Current);
///
object IEnumerator.Current => Current;
diff --git a/Tabula/TextStripper.cs b/Tabula/TextStripper.cs
index 0d61e78..1a528aa 100644
--- a/Tabula/TextStripper.cs
+++ b/Tabula/TextStripper.cs
@@ -44,7 +44,7 @@ public static TextStripperResult Process(Page page)
double wos = GetExpectedWhitespaceSize(letter);
- TextElement te = new TextElement(GetBbox(letter), letter.Font, letter.PointSize, c, wos, letter.GlyphRectangle.Rotation)
+ TextElement te = new TextElement(GetBbox(letter), letter.FontDetails, letter.PointSize, c, wos, letter.BoundingBox.Rotation)
{
letter = letter
};
@@ -90,10 +90,10 @@ private static bool IsPrintable(string s)
private static bool IsSpecial(char c)
{
-#if NETCOREAPP3_1
- return c >= System.Text.Unicode.UnicodeRanges.Specials.FirstCodePoint && c < (System.Text.Unicode.UnicodeRanges.Specials.FirstCodePoint + System.Text.Unicode.UnicodeRanges.Specials.Length);
-#else
+#if NETSTANDARD || NETFRAMEWORK
return c >= '\uFFF0' && c <= '\uFFFF';
+#else
+ return c >= System.Text.Unicode.UnicodeRanges.Specials.FirstCodePoint && c < (System.Text.Unicode.UnicodeRanges.Specials.FirstCodePoint + System.Text.Unicode.UnicodeRanges.Specials.Length);
#endif
}
@@ -112,39 +112,39 @@ private static PdfRectangle GetBbox(Letter letter)
{
case TextOrientation.Horizontal:
double add = 0;
- if (letter.GlyphRectangle.Height == 0)
+ if (letter.BoundingBox.Height == 0)
{
add = 1; // force minimum height to be 1
}
else if (letter.Value == " ")
{
- add = -(letter.GlyphRectangle.Height - 1); // force height of space to be 1
+ add = -(letter.BoundingBox.Height - 1); // force height of space to be 1
}
return new PdfRectangle(Utils.Round(letter.StartBaseLine.X, 2),
Utils.Round(letter.StartBaseLine.Y, 2),
- Utils.Round(letter.StartBaseLine.X + Math.Max(letter.Width, letter.GlyphRectangle.Width), 2),
- Utils.Round(letter.GlyphRectangle.TopLeft.Y + add, 2));
+ Utils.Round(letter.StartBaseLine.X + Math.Max(letter.Width, letter.BoundingBox.Width), 2),
+ Utils.Round(letter.BoundingBox.TopLeft.Y + add, 2));
case TextOrientation.Rotate180:
// need to force min height = 1 and height of space to be 1
return new PdfRectangle(Utils.Round(letter.StartBaseLine.X, 2),
Utils.Round(letter.StartBaseLine.Y, 2),
- Utils.Round(letter.StartBaseLine.X - Math.Max(letter.Width, letter.GlyphRectangle.Width), 2),
- Utils.Round(letter.GlyphRectangle.TopRight.Y, 2));
+ Utils.Round(letter.StartBaseLine.X - Math.Max(letter.Width, letter.BoundingBox.Width), 2),
+ Utils.Round(letter.BoundingBox.TopRight.Y, 2));
case TextOrientation.Rotate90:
// need to force min height = 1 and height of space to be 1
- return new PdfRectangle(new PdfPoint(Utils.Round(letter.StartBaseLine.X + letter.GlyphRectangle.Height, 2), Utils.Round(letter.GlyphRectangle.BottomLeft.Y, 2)),
- new PdfPoint(Utils.Round(letter.StartBaseLine.X + letter.GlyphRectangle.Height, 2), Utils.Round(letter.EndBaseLine.Y, 2)),
- new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.GlyphRectangle.BottomLeft.Y, 2)),
+ return new PdfRectangle(new PdfPoint(Utils.Round(letter.StartBaseLine.X + letter.BoundingBox.Height, 2), Utils.Round(letter.BoundingBox.BottomLeft.Y, 2)),
+ new PdfPoint(Utils.Round(letter.StartBaseLine.X + letter.BoundingBox.Height, 2), Utils.Round(letter.EndBaseLine.Y, 2)),
+ new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.BoundingBox.BottomLeft.Y, 2)),
new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.EndBaseLine.Y, 2)));
case TextOrientation.Rotate270:
// need to force min height = 1 and height of space to be 1
- return new PdfRectangle(new PdfPoint(Utils.Round(letter.StartBaseLine.X - letter.GlyphRectangle.Height, 2), Utils.Round(letter.StartBaseLine.Y, 2)),
- new PdfPoint(Utils.Round(letter.StartBaseLine.X - letter.GlyphRectangle.Height, 2), Utils.Round(letter.GlyphRectangle.BottomRight.Y, 2)),
+ return new PdfRectangle(new PdfPoint(Utils.Round(letter.StartBaseLine.X - letter.BoundingBox.Height, 2), Utils.Round(letter.StartBaseLine.Y, 2)),
+ new PdfPoint(Utils.Round(letter.StartBaseLine.X - letter.BoundingBox.Height, 2), Utils.Round(letter.BoundingBox.BottomRight.Y, 2)),
new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.StartBaseLine.Y, 2)),
- new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.GlyphRectangle.BottomRight.Y, 2)));
+ new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.BoundingBox.BottomRight.Y, 2)));
case TextOrientation.Other:
default:
@@ -161,8 +161,8 @@ private static PdfRectangle GetBoundingBoxOther(Letter letter)
{
letter.StartBaseLine,
letter.EndBaseLine,
- letter.GlyphRectangle.TopLeft,
- letter.GlyphRectangle.TopRight
+ letter.BoundingBox.TopLeft,
+ letter.BoundingBox.TopRight
};
// Candidates bounding boxes
@@ -174,7 +174,7 @@ private static PdfRectangle GetBoundingBoxOther(Letter letter)
// Find the orientation of the OBB, using the baseline angle
// Assumes line order is correct
- var baseLineAngle = Distances.BoundAngle180(Distances.Angle(letter.GlyphRectangle.BottomLeft, letter.GlyphRectangle.BottomRight));
+ var baseLineAngle = Distances.BoundAngle180(Distances.Angle(letter.BoundingBox.BottomLeft, letter.BoundingBox.BottomRight));
double deltaAngle = Math.Abs(Distances.BoundAngle180(obb.Rotation - baseLineAngle));
double deltaAngle1 = Math.Abs(Distances.BoundAngle180(obb1.Rotation - baseLineAngle));
From 94b8755e8101da3dc0862cb87494208d569d6547 Mon Sep 17 00:00:00 2001
From: BobLd <38405645+BobLd@users.noreply.github.com>
Date: Mon, 4 May 2026 14:40:12 +0100
Subject: [PATCH 6/7] Update README
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 7a56f0c..ecfd7b2 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@


-- Supports netstandard2.0, net462, net471, net6.0, net8.0
+- Supports netstandard2.0 / 2.1, net462, net471, net6.0, net8.0 and net9.0
- No java bindings
NuGet packages available on the [releases](https://github.com/BobLd/tabula-sharp/releases) page and on www.nuget.org:
From cde6ec5f3b647367f7a6cd951c3c7ad80c5e72d1 Mon Sep 17 00:00:00 2001
From: BobLd <38405645+BobLd@users.noreply.github.com>
Date: Mon, 4 May 2026 14:43:09 +0100
Subject: [PATCH 7/7] Target net9 in tests and update .NET version from 8.0.x
to 9.0.x in test pipeline
---
.github/workflows/dotnet-linux.yml | 6 +-
.github/workflows/dotnet-mac.yml | 6 +-
.github/workflows/dotnet-windows.yml | 2 +-
Tabula.Tests/Tabula.Tests.csproj | 2092 +++++++++++++-------------
4 files changed, 1053 insertions(+), 1053 deletions(-)
diff --git a/.github/workflows/dotnet-linux.yml b/.github/workflows/dotnet-linux.yml
index 0fde9f2..06aee17 100644
--- a/.github/workflows/dotnet-linux.yml
+++ b/.github/workflows/dotnet-linux.yml
@@ -16,12 +16,12 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
- dotnet-version: 8.0.x
+ dotnet-version: 9.0.x
- name: Install mscorefonts
run: sudo apt-get -y install ttf-mscorefonts-installer
- name: Install dependencies
run: dotnet restore
- name: Build net8.0
- run: dotnet build --configuration Release --no-restore --framework net8.0
+ run: dotnet build --configuration Release --no-restore --framework net9.0
- name: Test net8.0
- run: dotnet test --no-restore --verbosity minimal --framework net8.0
+ run: dotnet test --no-restore --verbosity minimal --framework net9.0
diff --git a/.github/workflows/dotnet-mac.yml b/.github/workflows/dotnet-mac.yml
index 43a5d41..f2457c2 100644
--- a/.github/workflows/dotnet-mac.yml
+++ b/.github/workflows/dotnet-mac.yml
@@ -14,10 +14,10 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
- dotnet-version: 8.0.x
+ dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Build net8.0
- run: dotnet build --configuration Release --no-restore --framework net8.0
+ run: dotnet build --configuration Release --no-restore --framework net9.0
- name: Test net8.0
- run: dotnet test --no-restore --verbosity minimal --framework net8.0
+ run: dotnet test --no-restore --verbosity minimal --framework net9.0
diff --git a/.github/workflows/dotnet-windows.yml b/.github/workflows/dotnet-windows.yml
index e527dbc..bfc74d5 100644
--- a/.github/workflows/dotnet-windows.yml
+++ b/.github/workflows/dotnet-windows.yml
@@ -14,7 +14,7 @@ jobs:
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
- dotnet-version: 8.0.x
+ dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
diff --git a/Tabula.Tests/Tabula.Tests.csproj b/Tabula.Tests/Tabula.Tests.csproj
index 59bbdbd..c905667 100644
--- a/Tabula.Tests/Tabula.Tests.csproj
+++ b/Tabula.Tests/Tabula.Tests.csproj
@@ -1,1055 +1,1055 @@
-
- net8.0
+
+ net9.0
- false
+ false
- 0.1.2
-
+ 0.1.2
+
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
-
-
-
-
-
+
+
+
+
+
-
-
- Always
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- Always
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
-
+
+
+ Always
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ Always
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+