/*
* Copyright 2010-2011 Øyvind Berg (elacin@gmail.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.elacin.pdfextract.geom;
import org.elacin.pdfextract.content.PhysicalContent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static org.elacin.pdfextract.Constants.RECTANGLE_COLLECTION_CACHE_ENABLED;
/**
* Created by IntelliJ IDEA. User: elacin Date: Nov 2, 2010 Time: 1:20:36 AM To change this template
* use File | Settings | File Templates.
*/
public class RectangleCollection extends PhysicalContent {
// ------------------------------ FIELDS ------------------------------
/*
* calculating all the intersections while searching is expensive, so keep this cached.
* will be pruned on update
*/
@NotNull
private final Map<Integer, List<PhysicalContent>> yCache = new HashMap<Integer,
List<PhysicalContent>>();
@NotNull
private final Map<Integer, List<PhysicalContent>> xCache = new HashMap<Integer,
List<PhysicalContent>>();
@NotNull
private final List<PhysicalContent> contents;
@Nullable
private final RectangleCollection parent;
// --------------------------- CONSTRUCTORS ---------------------------
public RectangleCollection(@NotNull final Collection<? extends PhysicalContent> newContents,
@Nullable final RectangleCollection parent) {
super(newContents);
this.parent = parent;
contents = new ArrayList<PhysicalContent>(newContents.size());
contents.addAll(newContents);
}
// ------------------------ INTERFACE METHODS ------------------------
// --------------------- Interface HasPosition ---------------------
public void calculatePos() {
setPos(MathUtils.findBoundsExcludingWhitespace(contents));
}
// --------------------- GETTER / SETTER METHODS ---------------------
@NotNull
public List<PhysicalContent> getContents() {
return contents;
}
@Nullable
public RectangleCollection getParent() {
return parent;
}
// -------------------------- PUBLIC METHODS --------------------------
public void addContent(final PhysicalContent content) {
contents.add(content);
clearCache();
}
public void addContents(Collection<? extends PhysicalContent> newContents) {
contents.addAll(newContents);
clearCache();
}
@SuppressWarnings({ "NumericCastThatLosesPrecision" })
public List<PhysicalContent> findContentAtXIndex(float x) {
return findContentAtXIndex((int) x);
}
public List<PhysicalContent> findContentAtXIndex(int x) {
if (!RECTANGLE_COLLECTION_CACHE_ENABLED ||!xCache.containsKey(x)) {
final Rectangle searchRectangle = new Rectangle((float) x, getPos().y, 1.0f,
getPos().height);
final List<PhysicalContent> result = findContentsIntersectingWith(searchRectangle);
Collections.sort(result, Sorting.sortByLowerY);
xCache.put(x, result);
}
return xCache.get(x);
}
@SuppressWarnings({ "NumericCastThatLosesPrecision" })
public List<PhysicalContent> findContentAtYIndex(float y) {
return findContentAtYIndex((int) y);
}
public List<PhysicalContent> findContentAtYIndex(int y) {
if (!RECTANGLE_COLLECTION_CACHE_ENABLED ||!yCache.containsKey(y)) {
final Rectangle searchRectangle = new Rectangle(getPos().x, (float) y, getPos().width,
1.0F);
final List<PhysicalContent> result = findContentsIntersectingWith(searchRectangle);
Collections.sort(result, Sorting.sortByLowerX);
yCache.put(y, result);
}
return yCache.get(y);
}
@NotNull
public List<PhysicalContent> findContentsIntersectingWith(@NotNull final HasPosition search) {
final List<PhysicalContent> ret = new ArrayList<PhysicalContent>(50);
for (PhysicalContent r : contents) {
if (search.getPos().intersectsWith(r.getPos())) {
ret.add(r);
}
}
return ret;
}
@NotNull
public List<PhysicalContent> findSurrounding(@NotNull final HasPosition content,
final int distance) {
final Rectangle bound = content.getPos();
Rectangle searchRectangle = new Rectangle(bound.x - (float) distance,
bound.y - (float) distance, bound.width + (float) distance,
bound.height + (float) distance);
final List<PhysicalContent> ret = findContentsIntersectingWith(searchRectangle);
if (ret.contains(content)) {
ret.remove(content);
}
return ret;
}
public void removeContent(PhysicalContent toRemove) {
if (!contents.remove(toRemove)) {
throw new RuntimeException("Region " + this + ": Could not remove " + toRemove);
}
clearCache();
}
public void removeContents(@NotNull Collection<PhysicalContent> listToRemove) {
contents.removeAll(listToRemove);
clearCache();
}
@NotNull
public List<PhysicalContent> searchInDirectionFromOrigin(@NotNull Direction dir,
@NotNull HasPosition origin, float distance) {
final Rectangle pos = origin.getPos();
final float x = pos.x + dir.xDiff * distance;
final float y = pos.y + dir.yDiff * distance;
final Rectangle search = new Rectangle(x, y, pos.width, pos.height);
final List<PhysicalContent> ret = findContentsIntersectingWith(search);
if (ret.contains(origin)) {
ret.remove(origin);
}
return ret;
}
// -------------------------- OTHER METHODS --------------------------
protected void clearCache() {
yCache.clear();
xCache.clear();
invalidatePos();
}
// -------------------------- ENUMERATIONS --------------------------
public enum Direction {
N(0, 1), NE(1, 1), E(1, 0), SE(1, -1), S(0, -1), SW(-1, -1), W(-1, 0), NW(-1, 1);
float xDiff;
float yDiff;
Direction(final float xDiff, final float yDiff) {
this.xDiff = xDiff;
this.yDiff = yDiff;
}
}
}