Skip to content

Commit 9da348f

Browse files
author
Matt Charters
committed
Reuse code from elsewhere and get rid of some duplicate stuff
1 parent a344ca2 commit 9da348f

3 files changed

Lines changed: 36 additions & 194 deletions

File tree

src/main/java/technology/tabula/Ruling.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@ public Ruling(float top, float left, float width, float height) {
2424

2525
public Ruling(Point2D p1, Point2D p2) {
2626
super(p1, p2);
27-
28-
// normalize almost vertical or almost horizontal lines
27+
this.normalize();
28+
}
29+
30+
/**
31+
* Normalize almost horizontal or almost vertical lines
32+
*/
33+
public void normalize() {
34+
2935
double angle = this.getAngle();
3036
if (Utils.within(angle, 0, 1) || Utils.within(angle, 180, 1)) { // almost horizontal
3137
this.setLine(this.x1, this.y1, this.x2, this.y1);

src/main/java/technology/tabula/detectors/NurminenDetectionAlgorithm.java

Lines changed: 26 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.apache.pdfbox.util.PDFOperator;
1010
import technology.tabula.*;
1111
import technology.tabula.Rectangle;
12+
import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
1213

1314
import java.awt.*;
1415
import java.awt.geom.Line2D;
@@ -132,7 +133,7 @@ public List<Rectangle> detect(Page page, File referenceDocument) {
132133
return new ArrayList<Rectangle>();
133134
}
134135

135-
List<Line2D.Float> horizontalRulings = this.getHorizontalRulings(image);
136+
List<Ruling> horizontalRulings = this.getHorizontalRulings(image);
136137

137138
// now check the page for vertical lines, but remove the text first to make things less confusing
138139
try {
@@ -142,9 +143,9 @@ public List<Rectangle> detect(Page page, File referenceDocument) {
142143
return new ArrayList<Rectangle>();
143144
}
144145

145-
List<Line2D.Float> verticalRulings = this.getVerticalRulings(image);
146+
List<Ruling> verticalRulings = this.getVerticalRulings(image);
146147

147-
List<Line2D.Float> allEdges = new ArrayList<Line2D.Float>(horizontalRulings);
148+
List<Ruling> allEdges = new ArrayList<Ruling>(horizontalRulings);
148149
allEdges.addAll(verticalRulings);
149150

150151
List<Rectangle> tableAreas = new ArrayList<Rectangle>();
@@ -154,15 +155,24 @@ public List<Rectangle> detect(Page page, File referenceDocument) {
154155
// now we need to snap edge endpoints to a grid
155156
Utils.snapPoints(allEdges, POINT_SNAP_DISTANCE_THRESHOLD, POINT_SNAP_DISTANCE_THRESHOLD);
156157

157-
// next get the crossing points of all the edges
158-
Set<Point2D.Float> crossingPoints = this.getCrossingPoints(horizontalRulings, verticalRulings);
158+
// normalize the rulings to make sure snapping didn't create any wacky non-horizontal/vertical rulings
159+
for (List<Ruling> rulings : Arrays.asList(horizontalRulings, verticalRulings)) {
160+
for (Iterator<Ruling> iterator = rulings.iterator(); iterator.hasNext();) {
161+
Ruling ruling = iterator.next();
162+
163+
ruling.normalize();
164+
if (ruling.oblique()) {
165+
iterator.remove();
166+
}
167+
}
168+
}
159169

160170
// merge the edge lines into rulings - this makes finding edges between crossing points in the next step easier
161-
horizontalRulings = this.mergeHorizontalEdges(horizontalRulings);
162-
verticalRulings = this.mergeVerticalEdges(verticalRulings);
171+
horizontalRulings = Ruling.collapseOrientedRulings(horizontalRulings);
172+
verticalRulings = Ruling.collapseOrientedRulings(verticalRulings);
163173

164174
// use the rulings and points to find cells
165-
List<Rectangle> cells = this.findRectangles(crossingPoints, horizontalRulings, verticalRulings);
175+
List<? extends Rectangle> cells = SpreadsheetExtractionAlgorithm.findCells(horizontalRulings, verticalRulings);
166176

167177
// then use those cells to make table areas
168178
tableAreas = this.getTableAreasFromCells(cells);
@@ -323,7 +333,7 @@ public int compare(Rectangle o1, Rectangle o2) {
323333
private Rectangle getTableFromText(List<Line> lines,
324334
List<TextEdge> relevantEdges,
325335
int relevantEdgeCount,
326-
List<Line2D.Float> horizontalRulings) {
336+
List<Ruling> horizontalRulings) {
327337

328338
Rectangle table = new Rectangle();
329339

@@ -653,95 +663,7 @@ private TextEdges getTextEdges(List<Line> lines) {
653663
return new TextEdges(leftTextEdges, midTextEdges, rightTextEdges);
654664
}
655665

656-
private List<Line2D.Float> mergeHorizontalEdges(List<Line2D.Float> horizontalEdges) {
657-
if (horizontalEdges.size() == 0) {
658-
return horizontalEdges;
659-
}
660-
661-
List<Line2D.Float> horizontalRulings = new ArrayList<Line2D.Float>();
662-
663-
// sort rulings by top-leftmost first
664-
Collections.sort(horizontalEdges, new Comparator<Line2D.Float>() {
665-
@Override
666-
public int compare(Line2D.Float o1, Line2D.Float o2) {
667-
if (o1.equals(o2)) {
668-
return 0;
669-
}
670-
671-
if (o1.y1 == o2.y1) {
672-
return Float.compare(o1.x1, o2.x1);
673-
} else {
674-
return Float.compare(o1.y1, o2.y1);
675-
}
676-
}
677-
});
678-
679-
Line2D.Float currentRuling = horizontalEdges.get(0);
680-
for (int i=1; i<horizontalEdges.size(); i++) {
681-
Line2D.Float nextEdge = horizontalEdges.get(i);
682-
683-
if (currentRuling.y1 == nextEdge.y1 &&
684-
nextEdge.x1 >= currentRuling.x1 &&
685-
nextEdge.x1 <= currentRuling.x2) {
686-
// this line segment can be part of the current line
687-
currentRuling.x2 = Math.max(nextEdge.x2, currentRuling.x2);
688-
} else {
689-
// store the complete line and continue
690-
horizontalRulings.add(currentRuling);
691-
currentRuling = nextEdge;
692-
}
693-
}
694-
695-
horizontalRulings.add(currentRuling);
696-
697-
return horizontalRulings;
698-
}
699-
700-
private List<Line2D.Float> mergeVerticalEdges(List<Line2D.Float> verticalEdges) {
701-
if (verticalEdges.size() == 0) {
702-
return verticalEdges;
703-
}
704-
705-
List<Line2D.Float> verticalRulings = new ArrayList<Line2D.Float>();
706-
707-
// sort by left-topmost first
708-
Collections.sort(verticalEdges, new Comparator<Line2D.Float>() {
709-
@Override
710-
public int compare(Line2D.Float o1, Line2D.Float o2) {
711-
if (o1.equals(o2)) {
712-
return 0;
713-
}
714-
715-
if (o1.x1 == o2.x1) {
716-
return Float.compare(o1.y1, o2.y1);
717-
} else {
718-
return Float.compare(o1.x1, o2.x1);
719-
}
720-
}
721-
});
722-
723-
Line2D.Float currentRuling = verticalEdges.get(0);
724-
for (int i=1; i<verticalEdges.size(); i++) {
725-
Line2D.Float nextEdge = verticalEdges.get(i);
726-
727-
if (currentRuling.x1 == nextEdge.x1 &&
728-
nextEdge.y1 >= currentRuling.y1 &&
729-
nextEdge.y1 <= currentRuling.y2) {
730-
// line segment is part of the current line
731-
currentRuling.y2 = Math.max(nextEdge.y2, currentRuling.y2);
732-
} else {
733-
// store the complete line and continue
734-
verticalRulings.add(currentRuling);
735-
currentRuling = nextEdge;
736-
}
737-
}
738-
739-
verticalRulings.add(currentRuling);
740-
741-
return verticalRulings;
742-
}
743-
744-
private List<Rectangle> getTableAreasFromCells(List<Rectangle> cells) {
666+
private List<Rectangle> getTableAreasFromCells(List<? extends Rectangle> cells) {
745667
List<List<Rectangle>> cellGroups = new ArrayList<List<Rectangle>>();
746668
for (Rectangle cell : cells) {
747669
boolean addedToGroup = false;
@@ -797,97 +719,11 @@ private List<Rectangle> getTableAreasFromCells(List<Rectangle> cells) {
797719
return tableAreas;
798720
}
799721

800-
private List<Rectangle> findRectangles(
801-
Set<Point2D.Float> crossingPoints,
802-
List<Line2D.Float> horizontalEdges,
803-
List<Line2D.Float> verticalEdges) {
804-
805-
List<Rectangle> foundRectangles = new ArrayList<Rectangle>();
806-
807-
ArrayList<Point2D.Float> sortedPoints = new ArrayList<Point2D.Float>(crossingPoints);
808-
809-
// sort all points by y value and then x value - this means that the first element
810-
// is always the top-leftmost point
811-
Collections.sort(sortedPoints, pointComparator);
812-
813-
for (int i=0; i<sortedPoints.size(); i++) {
814-
Point2D.Float topLeftPoint = sortedPoints.get(i);
815-
816-
ArrayList<Point2D.Float> pointsBelow = new ArrayList<Point2D.Float>();
817-
ArrayList<Point2D.Float> pointsRight = new ArrayList<Point2D.Float>();
818-
819-
for (int j=i+1; j<sortedPoints.size(); j++) {
820-
Point2D.Float checkPoint = sortedPoints.get(j);
821-
if (topLeftPoint.getX() == checkPoint.getX() && topLeftPoint.getY() < checkPoint.getY()) {
822-
pointsBelow.add(checkPoint);
823-
} else if (topLeftPoint.getY() == checkPoint.getY() && topLeftPoint.getX() < checkPoint.getX()) {
824-
pointsRight.add(checkPoint);
825-
}
826-
}
827-
828-
nextCrossingPoint:
829-
for (Point2D.Float belowPoint : pointsBelow) {
830-
if (!this.edgeExistsBetween(topLeftPoint, belowPoint, verticalEdges)) {
831-
break nextCrossingPoint;
832-
}
833-
834-
for (Point2D.Float rightPoint : pointsRight) {
835-
if (!this.edgeExistsBetween(topLeftPoint, rightPoint, horizontalEdges)) {
836-
break nextCrossingPoint;
837-
}
838-
839-
Point2D.Float bottomRightPoint = new Point2D.Float(rightPoint.x, belowPoint.y);
840-
841-
if (sortedPoints.contains(bottomRightPoint)
842-
&& this.edgeExistsBetween(belowPoint, bottomRightPoint, horizontalEdges)
843-
&& this.edgeExistsBetween(rightPoint, bottomRightPoint, verticalEdges)) {
844-
845-
foundRectangles.add(new Rectangle(
846-
topLeftPoint.y,
847-
topLeftPoint.x,
848-
bottomRightPoint.x - topLeftPoint.x,
849-
bottomRightPoint.y - topLeftPoint.y)
850-
);
851-
852-
break nextCrossingPoint;
853-
}
854-
}
855-
}
856-
}
857-
858-
return foundRectangles;
859-
}
860-
861-
private boolean edgeExistsBetween(Point2D.Float p1, Point2D.Float p2, List<Line2D.Float> edges) {
862-
for (Line2D.Float edge : edges) {
863-
if (p1.x >= edge.x1 && p1.x <= edge.x2 && p1.y >= edge.y1 && p1.y <= edge.y2
864-
&& p2.x >= edge.x1 && p2.x <= edge.x2 && p2.y >= edge.y1 && p2.y <= edge.y2) {
865-
return true;
866-
}
867-
}
868-
869-
return false;
870-
}
871-
872-
private Set<Point2D.Float> getCrossingPoints(List<Line2D.Float> horizontalEdges, List<Line2D.Float> verticalEdges) {
873-
Set<Point2D.Float> crossingPoints = new HashSet<Point2D.Float>();
874-
875-
for (Line2D.Float horizontalEdge : horizontalEdges) {
876-
for (Line2D.Float verticalEdge : verticalEdges) {
877-
if (horizontalEdge.intersectsLine(verticalEdge)) {
878-
crossingPoints.add(new Point2D.Float(verticalEdge.x1, horizontalEdge.y1));
879-
}
880-
}
881-
}
882-
883-
return crossingPoints;
884-
}
885-
886-
private List<Line2D.Float> getHorizontalRulings(BufferedImage image) {
722+
private List<Ruling> getHorizontalRulings(BufferedImage image) {
887723

888724
// get all horizontal edges, which we'll define as a change in grayscale colour
889725
// along a straight line of a certain length
890-
ArrayList<Line2D.Float> horizontalRulings = new ArrayList<Line2D.Float>();
726+
ArrayList<Ruling> horizontalRulings = new ArrayList<Ruling>();
891727

892728
Raster r = image.getRaster();
893729
int width = r.getWidth();
@@ -935,7 +771,7 @@ private List<Line2D.Float> getHorizontalRulings(BufferedImage image) {
935771
int endX = lineX - 1;
936772
int lineWidth = endX - x;
937773
if (lineWidth > HORIZONTAL_EDGE_WIDTH_MINIMUM) {
938-
horizontalRulings.add(new Line2D.Float(x, y, endX, y));
774+
horizontalRulings.add(new Ruling(new Point2D.Float(x, y), new Point2D.Float(endX, y)));
939775
}
940776
}
941777

@@ -946,11 +782,11 @@ private List<Line2D.Float> getHorizontalRulings(BufferedImage image) {
946782
return horizontalRulings;
947783
}
948784

949-
private List<Line2D.Float> getVerticalRulings(BufferedImage image) {
785+
private List<Ruling> getVerticalRulings(BufferedImage image) {
950786

951787
// get all vertical edges, which we'll define as a change in grayscale colour
952788
// along a straight line of a certain length
953-
ArrayList<Line2D.Float> verticalRulings = new ArrayList<Line2D.Float>();
789+
ArrayList<Ruling> verticalRulings = new ArrayList<Ruling>();
954790

955791
Raster r = image.getRaster();
956792
int width = r.getWidth();
@@ -998,7 +834,7 @@ private List<Line2D.Float> getVerticalRulings(BufferedImage image) {
998834
int endY = lineY - 1;
999835
int lineLength = endY - y;
1000836
if (lineLength > VERTICAL_EDGE_HEIGHT_MINIMUM) {
1001-
verticalRulings.add(new Line2D.Float(x, y, x, endY));
837+
verticalRulings.add(new Ruling(new Point2D.Float(x, y), new Point2D.Float(x, endY)));
1002838
}
1003839
}
1004840

src/main/java/technology/tabula/extractors/SpreadsheetExtractionAlgorithm.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ public static List<Cell> findCells(List<Ruling> horizontalRulingLines, List<Ruli
270270
return cellsFound;
271271
}
272272

273-
public List<Rectangle> findSpreadsheetsFromCells(List<? extends Rectangle> cells) {
273+
public static List<Rectangle> findSpreadsheetsFromCells(List<? extends Rectangle> cells) {
274274
// via: http://stackoverflow.com/questions/13746284/merging-multiple-adjacent-rectangles-into-one-polygon
275275
List<Rectangle> rectangles = new ArrayList<Rectangle>();
276276
Set<Point2D> pointSet = new HashSet<Point2D>();
@@ -386,7 +386,7 @@ private enum Direction {
386386
VERTICAL
387387
}
388388

389-
class PolygonVertex {
389+
static class PolygonVertex {
390390
Point2D point;
391391
Direction direction;
392392

0 commit comments

Comments
 (0)