99import org .apache .pdfbox .util .PDFOperator ;
1010import technology .tabula .*;
1111import technology .tabula .Rectangle ;
12+ import technology .tabula .extractors .SpreadsheetExtractionAlgorithm ;
1213
1314import java .awt .*;
1415import 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
0 commit comments