package technology.tabula; import java.awt.Shape; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.math.BigDecimal; import java.util.*; import org.apache.commons.cli.ParseException; /** * * @author manuel */ public class Utils { public static boolean within(double first, double second, double variance) { return second < first + variance && second > first - variance; } public static boolean overlap(double y1, double height1, double y2, double height2, double variance) { return within( y1, y2, variance) || (y2 <= y1 && y2 >= y1 - height1) || (y1 <= y2 && y1 >= y2-height2); } public static boolean overlap(double y1, double height1, double y2, double height2) { return overlap(y1, height1, y2, height2, 0.1f); } private final static float EPSILON = 0.01f; protected static boolean useQuickSort = useCustomQuickSort(); public static boolean feq(double f1, double f2) { return (Math.abs(f1 - f2) < EPSILON); } public static float round(double d, int decimalPlace) { BigDecimal bd = new BigDecimal(Double.toString(d)); bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP); return bd.floatValue(); } public static Rectangle bounds(Collection shapes) { if (shapes.isEmpty()) { throw new IllegalArgumentException("shapes can't be empty"); } Iterator iter = shapes.iterator(); Rectangle rv = new Rectangle(); rv.setRect(iter.next().getBounds2D()); do { Rectangle2D.union(iter.next().getBounds2D(), rv, rv); } while (iter.hasNext()); return rv; } // range iterator public static List range(final int begin, final int end) { return new AbstractList() { @Override public Integer get(int index) { return begin + index; } @Override public int size() { return end - begin; } }; } /* from apache.commons-lang */ public static boolean isNumeric(final CharSequence cs) { if (cs == null || cs.length() == 0) { return false; } final int sz = cs.length(); for (int i = 0; i < sz; i++) { if (!Character.isDigit(cs.charAt(i))) { return false; } } return true; } public static String join(String glue, String...s) { int k = s.length; if ( k == 0 ) { return null; } StringBuilder out = new StringBuilder(); out.append( s[0] ); for ( int x=1; x < k; ++x ) { out.append(glue).append(s[x]); } return out.toString(); } public static List> transpose(List> table) { List> ret = new ArrayList>(); final int N = table.get(0).size(); for (int i = 0; i < N; i++) { List col = new ArrayList(); for (List row : table) { col.add(row.get(i)); } ret.add(col); } return ret; } /** * Wrap Collections.sort so we can fallback to a non-stable quicksort * if we're running on JDK7+ */ public static > void sort(List list) { if (useQuickSort) { QuickSort.sort(list); } else { Collections.sort(list); } } private static boolean useCustomQuickSort() { // taken from PDFBOX: // check if we need to use the custom quicksort algorithm as a // workaround to the transitivity issue of TextPositionComparator: // https://issues.apache.org/jira/browse/PDFBOX-1512 String[] versionComponents = System.getProperty("java.version").split( "\\."); int javaMajorVersion = Integer.parseInt(versionComponents[0]); int javaMinorVersion = Integer.parseInt(versionComponents[1]); boolean is16orLess = javaMajorVersion == 1 && javaMinorVersion <= 6; String useLegacySort = System.getProperty("java.util.Arrays.useLegacyMergeSort"); return !is16orLess || (useLegacySort != null && useLegacySort.equals("true")); } public static List parsePagesOption(String pagesSpec) throws ParseException { if (pagesSpec.equals("all")) { return null; } List rv = new ArrayList(); String[] ranges = pagesSpec.split(","); for (int i = 0; i < ranges.length; i++) { String[] r = ranges[i].split("-"); if (r.length == 0 || !Utils.isNumeric(r[0]) || r.length > 1 && !Utils.isNumeric(r[1])) { throw new ParseException("Syntax error in page range specification"); } if (r.length < 2) { rv.add(Integer.parseInt(r[0])); } else { int t = Integer.parseInt(r[0]); int f = Integer.parseInt(r[1]); if (t > f) { throw new ParseException("Syntax error in page range specification"); } rv.addAll(Utils.range(t, f+1)); } } Collections.sort(rv); return rv; } public static void snapPoints(List rulings, float xThreshold, float yThreshold) { // collect points and keep a Line -> p1,p2 map Map linesToPoints = new HashMap(); List points = new ArrayList(); for (Line2D.Float r: rulings) { Point2D p1 = r.getP1(); Point2D p2 = r.getP2(); linesToPoints.put(r, new Point2D[] { p1, p2 }); points.add(p1); points.add(p2); } // snap by X Collections.sort(points, new Comparator() { @Override public int compare(Point2D arg0, Point2D arg1) { return java.lang.Double.compare(arg0.getX(), arg1.getX()); } }); List> groupedPoints = new ArrayList>(); groupedPoints.add(new ArrayList(Arrays.asList(new Point2D[] { points.get(0) }))); for (Point2D p: points.subList(1, points.size() - 1)) { List last = groupedPoints.get(groupedPoints.size() - 1); if (Math.abs(p.getX() - last.get(0).getX()) < xThreshold) { groupedPoints.get(groupedPoints.size() - 1).add(p); } else { groupedPoints.add(new ArrayList(Arrays.asList(new Point2D[] { p }))); } } for(List group: groupedPoints) { float avgLoc = 0; for(Point2D p: group) { avgLoc += p.getX(); } avgLoc /= group.size(); for(Point2D p: group) { p.setLocation(avgLoc, p.getY()); } } // --- // snap by Y Collections.sort(points, new Comparator() { @Override public int compare(Point2D arg0, Point2D arg1) { return java.lang.Double.compare(arg0.getY(), arg1.getY()); } }); groupedPoints = new ArrayList>(); groupedPoints.add(new ArrayList(Arrays.asList(new Point2D[] { points.get(0) }))); for (Point2D p: points.subList(1, points.size() - 1)) { List last = groupedPoints.get(groupedPoints.size() - 1); if (Math.abs(p.getY() - last.get(0).getY()) < yThreshold) { groupedPoints.get(groupedPoints.size() - 1).add(p); } else { groupedPoints.add(new ArrayList(Arrays.asList(new Point2D[] { p }))); } } for(List group: groupedPoints) { float avgLoc = 0; for(Point2D p: group) { avgLoc += p.getY(); } avgLoc /= group.size(); for(Point2D p: group) { p.setLocation(p.getX(), avgLoc); } } // --- // finally, modify lines for(Map.Entry ltp: linesToPoints.entrySet()) { Point2D[] p = ltp.getValue(); ltp.getKey().setLine(p[0], p[1]); } } }