1 | /**
|
---|
2 | * XGroupItem.java
|
---|
3 | * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
|
---|
4 | *
|
---|
5 | * This program is free software: you can redistribute it and/or modify
|
---|
6 | * it under the terms of the GNU General Public License as published by
|
---|
7 | * the Free Software Foundation, either version 3 of the License, or
|
---|
8 | * (at your option) any later version.
|
---|
9 | *
|
---|
10 | * This program is distributed in the hope that it will be useful,
|
---|
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
13 | * GNU General Public License for more details.
|
---|
14 | *
|
---|
15 | * You should have received a copy of the GNU General Public License
|
---|
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
---|
17 | */
|
---|
18 |
|
---|
19 | package org.expeditee.io.flowlayout;
|
---|
20 |
|
---|
21 | import java.util.ArrayList;
|
---|
22 | import java.util.Collection;
|
---|
23 | import java.util.Collections;
|
---|
24 | import java.util.Comparator;
|
---|
25 | import java.util.HashSet;
|
---|
26 | import java.util.Iterator;
|
---|
27 | import java.util.LinkedList;
|
---|
28 | import java.util.List;
|
---|
29 |
|
---|
30 | import org.expeditee.core.Point;
|
---|
31 | import org.expeditee.core.bounds.AxisAlignedBoxBounds;
|
---|
32 | import org.expeditee.core.bounds.PolygonBounds;
|
---|
33 | import org.expeditee.gui.Frame;
|
---|
34 | import org.expeditee.gui.FrameUtils;
|
---|
35 | import org.expeditee.items.Item;
|
---|
36 | import org.expeditee.items.Line;
|
---|
37 | import org.expeditee.items.Text;
|
---|
38 |
|
---|
39 | public class XGroupItem extends XItem {
|
---|
40 | public static final Text GROUPSEP_END = new Text('2' + ""); // ASCII '2' is
|
---|
41 | // start of text
|
---|
42 |
|
---|
43 | public static final Text GROUPSEP_START = new Text('3' + ""); // ASCII '3'
|
---|
44 | // is end of
|
---|
45 | // text
|
---|
46 |
|
---|
47 | enum FlowType {
|
---|
48 | in_flow, out_of_flow_original, out_of_flow_faked_position
|
---|
49 | };
|
---|
50 |
|
---|
51 | public static boolean doImplicitBoxing = true;
|
---|
52 |
|
---|
53 | class MultiArrowHeadComparable implements Comparator<Item> {
|
---|
54 |
|
---|
55 | @Override
|
---|
56 | public int compare(Item o1, Item o2) {
|
---|
57 | int o1y = o1.getY();
|
---|
58 | int o2y = o2.getY();
|
---|
59 |
|
---|
60 | int order = 0; // default to assume they are identical
|
---|
61 |
|
---|
62 | if (o1y < o2y) {
|
---|
63 | order = -1; // done, o1 is higher up the frame than o2 => our
|
---|
64 | // definition of 'before'
|
---|
65 | } else if (o2y < o1y) {
|
---|
66 | order = +1; // also done, o1 is lower down the frame than o2 =>
|
---|
67 | // our definition of 'after'
|
---|
68 |
|
---|
69 | } else {
|
---|
70 | // have identical 'y' values, so look to 'x' to tie-break
|
---|
71 |
|
---|
72 | int o1x = o1.getX();
|
---|
73 | int o2x = o2.getX();
|
---|
74 |
|
---|
75 | if (o1x < o2x) {
|
---|
76 | order = -1; // done, o1 is to the left of o2 => our
|
---|
77 | // definition of 'before'
|
---|
78 | } else if (o2x < o1x) {
|
---|
79 | order = +1; // also done, o1 is to the right of o2 => our
|
---|
80 | // definition of 'after'
|
---|
81 | }
|
---|
82 | }
|
---|
83 |
|
---|
84 | return order;
|
---|
85 | }
|
---|
86 | }
|
---|
87 |
|
---|
88 | Frame frame;
|
---|
89 |
|
---|
90 | protected int y_span_height;
|
---|
91 | protected YOverlappingItemsSpan[] yitems_span_array;
|
---|
92 |
|
---|
93 | List<Text> raw_text_item_list;
|
---|
94 | List<XGroupItem> grouped_item_list;
|
---|
95 | List<Item> remaining_item_list;
|
---|
96 |
|
---|
97 | FlowType out_of_flow;
|
---|
98 |
|
---|
99 | protected XGroupItem() {
|
---|
100 | frame = null;
|
---|
101 |
|
---|
102 | y_span_height = 0;
|
---|
103 | yitems_span_array = null;
|
---|
104 |
|
---|
105 | raw_text_item_list = null;
|
---|
106 | grouped_item_list = null;
|
---|
107 | remaining_item_list = null;
|
---|
108 |
|
---|
109 | out_of_flow = FlowType.in_flow;
|
---|
110 | }
|
---|
111 |
|
---|
112 | public XGroupItem(Frame frame, List<Item> y_ordered_items, PolygonBounds enclosing_polygon) {
|
---|
113 | this.frame = frame;
|
---|
114 | this.out_of_flow = FlowType.in_flow;
|
---|
115 | ;
|
---|
116 |
|
---|
117 | if (enclosing_polygon == null) {
|
---|
118 | // e.g. when the top-level case, with no enclosing polygon at this
|
---|
119 | // stage
|
---|
120 | // => generate one based on the bounding box of the y_ordered_items
|
---|
121 | enclosing_polygon = DimensionExtent
|
---|
122 | .boundingBoxPolygon(y_ordered_items);
|
---|
123 | }
|
---|
124 |
|
---|
125 | AxisAlignedBoxBounds enclosing_bounding_rect = AxisAlignedBoxBounds.getEnclosing(enclosing_polygon);
|
---|
126 | initSpanArray(enclosing_bounding_rect);
|
---|
127 |
|
---|
128 | // Step 1: Separate out the raw-text-items, the grouped-items and
|
---|
129 | // 'remaining' (e.g. points and lines)
|
---|
130 |
|
---|
131 | raw_text_item_list = new ArrayList<Text>();
|
---|
132 | grouped_item_list = new ArrayList<XGroupItem>();
|
---|
133 | remaining_item_list = new ArrayList<Item>();
|
---|
134 |
|
---|
135 | separateYOverlappingItems(frame, y_ordered_items, enclosing_polygon, raw_text_item_list, grouped_item_list, remaining_item_list);
|
---|
136 |
|
---|
137 | // Step 2: Add in the raw-text items
|
---|
138 | for (Text text_item : raw_text_item_list) {
|
---|
139 |
|
---|
140 | XRawItem x_raw_item = new XRawItem(text_item, this);
|
---|
141 | // overspill can occur (and is acceptable) when raw-text item spills
|
---|
142 | // out of enclosing shape (such as a rectangle)
|
---|
143 | if(x_raw_item.bounding_rect == null) {
|
---|
144 | final StringBuilder errorMsg =
|
---|
145 | new StringBuilder("Was about to try mapInItem(XRawItem) but found a null bounding_rect. Item details: ");
|
---|
146 | final String nl = System.getProperty("line.separator");
|
---|
147 | errorMsg.append("\t Item parent: " + text_item.getParent() + nl);
|
---|
148 | errorMsg.append("\t Item position: " + text_item.getPosition() + nl);
|
---|
149 | errorMsg.append("\t Item text content: " + text_item.getText() + nl);
|
---|
150 | System.err.println(errorMsg.toString());
|
---|
151 | }
|
---|
152 | else mapInItem(x_raw_item);
|
---|
153 | }
|
---|
154 |
|
---|
155 | }
|
---|
156 |
|
---|
157 | public XGroupItem(Frame frame, List<Item> y_ordered_items) {
|
---|
158 | this(frame, y_ordered_items, null);
|
---|
159 | }
|
---|
160 |
|
---|
161 | protected XGroupItem(XGroupItem imprint, AxisAlignedBoxBounds copy_to_bounding_rect)
|
---|
162 | {
|
---|
163 | super();
|
---|
164 |
|
---|
165 | // Implement a shallow copy => share references to frame, and item list,
|
---|
166 | // and y-span
|
---|
167 | // Only the bounding box is changed => set to the
|
---|
168 | // 'copy_to_bounding_rect' passed in
|
---|
169 |
|
---|
170 | this.frame = imprint.frame;
|
---|
171 |
|
---|
172 | y_span_height = imprint.y_span_height;
|
---|
173 | yitems_span_array = imprint.yitems_span_array;
|
---|
174 |
|
---|
175 | this.raw_text_item_list = imprint.raw_text_item_list;
|
---|
176 | this.grouped_item_list = imprint.grouped_item_list;
|
---|
177 |
|
---|
178 | // int offX = imprint.getBoundingRect().x - copy_to_bounding_rect.x;
|
---|
179 | // int offY = imprint.getBoundingRect().y - copy_to_bounding_rect.y;
|
---|
180 | // this.grouped_item_list = new ArrayList<XGroupItem>();
|
---|
181 | // for(XGroupItem newChild : imprint.grouped_item_list) {
|
---|
182 | // Rectangle newRect = newChild.getBoundingRect();
|
---|
183 | // newRect.x -= offX;
|
---|
184 | // newRect.y -= offY;
|
---|
185 | // this.grouped_item_list.add(new XGroupItem(newChild, newRect));
|
---|
186 | // }
|
---|
187 | this.remaining_item_list = imprint.remaining_item_list;
|
---|
188 |
|
---|
189 | this.out_of_flow = imprint.out_of_flow; // Or perhaps set it to
|
---|
190 | // FlowType.out_of_flow_fake_position
|
---|
191 | // straight away?
|
---|
192 |
|
---|
193 | this.bounding_rect = new AxisAlignedBoxBounds(copy_to_bounding_rect); // deep copy
|
---|
194 | // to be on
|
---|
195 | // the safe
|
---|
196 | // side
|
---|
197 | }
|
---|
198 |
|
---|
199 | protected void initSpanArray(AxisAlignedBoxBounds bounding_rect) {
|
---|
200 | this.bounding_rect = bounding_rect;
|
---|
201 |
|
---|
202 | int y_top = getBoundingYTop();
|
---|
203 | int y_bot = getBoundingYBot();
|
---|
204 |
|
---|
205 | if (y_top <= y_bot) {
|
---|
206 | // => have non-trivial span
|
---|
207 |
|
---|
208 | y_span_height = (y_bot - y_top) + 2; // Polygon in Java excludes
|
---|
209 | // right and bottom sides so
|
---|
210 | // need extra "+1" in calc
|
---|
211 |
|
---|
212 | yitems_span_array = new YOverlappingItemsSpan[y_span_height];
|
---|
213 | } else {
|
---|
214 | y_span_height = 0;
|
---|
215 | yitems_span_array = null;
|
---|
216 | }
|
---|
217 | }
|
---|
218 |
|
---|
219 | public YOverlappingItemsSpan getSpanItemAt(int index) {
|
---|
220 | return yitems_span_array[index];
|
---|
221 | }
|
---|
222 |
|
---|
223 | public void setOutOfFlow(FlowType flow_type) {
|
---|
224 | out_of_flow = flow_type;
|
---|
225 | }
|
---|
226 |
|
---|
227 | public FlowType getFlowItem() {
|
---|
228 | return out_of_flow;
|
---|
229 | }
|
---|
230 |
|
---|
231 | public boolean isOriginalOutOfFlowItem() {
|
---|
232 | return out_of_flow == FlowType.out_of_flow_original;
|
---|
233 | }
|
---|
234 |
|
---|
235 | public boolean isFakedOutOfFlowItem() {
|
---|
236 | return out_of_flow == FlowType.out_of_flow_faked_position;
|
---|
237 | }
|
---|
238 |
|
---|
239 | public List<Text> getRawTextItemList() {
|
---|
240 | return raw_text_item_list;
|
---|
241 | }
|
---|
242 |
|
---|
243 | public List<Text[]> getRawTextLines() {
|
---|
244 | final List<Text> rawText = getRawTextItemList();
|
---|
245 | final List<Text> lineStarts = new LinkedList<Text>();
|
---|
246 | for (final YOverlappingItemsSpan span : this.yitems_span_array) {
|
---|
247 | if (span instanceof YOverlappingItemsTopEdge) {
|
---|
248 | final YOverlappingItemsTopEdge topEdge = (YOverlappingItemsTopEdge) span;
|
---|
249 | final List<XItem> xLine = topEdge.getXOrderedLine()
|
---|
250 | .getXItemList();
|
---|
251 | for (final XItem xitem : xLine) {
|
---|
252 | if (xitem instanceof XRawItem) {
|
---|
253 | final Text item = (Text) ((XRawItem) xitem).getItem();
|
---|
254 | lineStarts.add(item);
|
---|
255 | break;
|
---|
256 | }
|
---|
257 | }
|
---|
258 | }
|
---|
259 | }
|
---|
260 | final List<Integer> indexes = new LinkedList<Integer>();
|
---|
261 | for (final Text lineStart : lineStarts)
|
---|
262 | indexes.add(rawText.indexOf(lineStart));
|
---|
263 | final List<Text[]> ret = new LinkedList<Text[]>();
|
---|
264 | int rangeEnd = indexes.get(0);
|
---|
265 | int last = rangeEnd;
|
---|
266 | for (int i = 1; i < indexes.size(); i++) {
|
---|
267 | rangeEnd = indexes.get(i);
|
---|
268 | final List<Text> part = new LinkedList<Text>(rawText.subList(0,
|
---|
269 | rangeEnd - last));
|
---|
270 | last = rangeEnd;
|
---|
271 | rawText.removeAll(part);
|
---|
272 | part.toArray(new Text[] {});
|
---|
273 | ret.add(part.toArray(new Text[] {}));
|
---|
274 | }
|
---|
275 | ret.add(rawText.toArray(new Text[] {}));
|
---|
276 | return ret;
|
---|
277 | }
|
---|
278 |
|
---|
279 | public List<XGroupItem> getGroupedItemList() {
|
---|
280 | return grouped_item_list;
|
---|
281 | }
|
---|
282 |
|
---|
283 | public List<Item> getRemainingItemList() {
|
---|
284 | return remaining_item_list;
|
---|
285 | }
|
---|
286 |
|
---|
287 | public int getItemSpanLength() {
|
---|
288 | return yitems_span_array.length;
|
---|
289 | }
|
---|
290 |
|
---|
291 | public YOverlappingItemsSpan getYOverlappingItemsSpan(int y) {
|
---|
292 | int y_index = y - getBoundingYTop();
|
---|
293 |
|
---|
294 | if ((y_index < 0) || (y_index >= yitems_span_array.length)) {
|
---|
295 | int y_top = getBoundingYTop();
|
---|
296 | int y_bot = y_top + yitems_span_array.length - 1;
|
---|
297 |
|
---|
298 | System.err
|
---|
299 | .println("Error in getYOverlappingItemsSpan(): index out of bounds for value "
|
---|
300 | + y);
|
---|
301 | System.err.println(" => Operation mapped into local array: y-top="
|
---|
302 | + y_top + ", y-bot=" + y_bot);
|
---|
303 |
|
---|
304 | return null;
|
---|
305 | }
|
---|
306 | return yitems_span_array[y_index];
|
---|
307 | }
|
---|
308 |
|
---|
309 | protected int cropToTop(int y) {
|
---|
310 | int y_index = y - getBoundingYTop();
|
---|
311 |
|
---|
312 | if (y_index < 0) {
|
---|
313 | y = getBoundingYTop();
|
---|
314 | }
|
---|
315 |
|
---|
316 | return y;
|
---|
317 | }
|
---|
318 |
|
---|
319 | protected int cropToBot(int y) {
|
---|
320 | int y_index = y - getBoundingYTop();
|
---|
321 |
|
---|
322 | if (y_index >= yitems_span_array.length) {
|
---|
323 | y = getBoundingYBot();
|
---|
324 | }
|
---|
325 |
|
---|
326 | return y;
|
---|
327 | }
|
---|
328 |
|
---|
329 | public void setYOverlappingItemsSpan(int y,
|
---|
330 | YOverlappingItemsSpan yitems_span) {
|
---|
331 | int y_index = y - getBoundingYTop();
|
---|
332 |
|
---|
333 | if ((y_index < 0) || (y_index >= yitems_span_array.length)) {
|
---|
334 |
|
---|
335 | int y_top = getBoundingYTop();
|
---|
336 | int y_bot = y_top + yitems_span_array.length - 1;
|
---|
337 |
|
---|
338 | System.err
|
---|
339 | .println("Error in setYOverlappingItemsSpan(): index out of bounds for value "
|
---|
340 | + y);
|
---|
341 | System.err.println(" => Operation mapped into local array: y-top="
|
---|
342 | + y_top + ", y-bot=" + y_bot);
|
---|
343 |
|
---|
344 | return;
|
---|
345 | }
|
---|
346 | yitems_span_array[y_index] = yitems_span;
|
---|
347 | }
|
---|
348 |
|
---|
349 | protected List<Item> followLinesToArrowHeads(Collection<Item> visited,
|
---|
350 | Item anchor_item, List<Line> used_in_lines) {
|
---|
351 | List<Item> arrow_head_endpoints = new ArrayList<Item>();
|
---|
352 |
|
---|
353 | for (Line line : used_in_lines) {
|
---|
354 |
|
---|
355 | Item start_item = line.getStartItem();
|
---|
356 |
|
---|
357 | if (start_item == anchor_item) {
|
---|
358 | // the line we're considering is heading in the right direction
|
---|
359 |
|
---|
360 | Item end_item = line.getEndItem();
|
---|
361 |
|
---|
362 | if (!visited.contains(end_item)) {
|
---|
363 | // Needs processing
|
---|
364 | visited.add(end_item);
|
---|
365 |
|
---|
366 | List<Line> follow_lines = end_item.getLines();
|
---|
367 |
|
---|
368 | if (follow_lines.size() == 1) {
|
---|
369 | // reached an end-point
|
---|
370 | if (end_item.hasVisibleArrow()) {
|
---|
371 | arrow_head_endpoints.add(end_item);
|
---|
372 | }
|
---|
373 | } else if (follow_lines.size() > 1) {
|
---|
374 |
|
---|
375 | List<Item> followed_arrow_heads = followLinesToArrowHeads(
|
---|
376 | visited, end_item, follow_lines);
|
---|
377 | arrow_head_endpoints.addAll(followed_arrow_heads);
|
---|
378 | }
|
---|
379 | }
|
---|
380 | }
|
---|
381 |
|
---|
382 | }
|
---|
383 | return arrow_head_endpoints;
|
---|
384 | }
|
---|
385 |
|
---|
386 | protected XGroupItem innermostXGroup(Item arrow_head,
|
---|
387 | List<XGroupItem> grouped_item_list) {
|
---|
388 | XGroupItem innermost_item = null;
|
---|
389 |
|
---|
390 | for (XGroupItem xgroup_item : grouped_item_list) {
|
---|
391 | if (xgroup_item.containsItem(arrow_head)) {
|
---|
392 |
|
---|
393 | innermost_item = xgroup_item;
|
---|
394 |
|
---|
395 | // Now see if it is in any of the nested ones?
|
---|
396 |
|
---|
397 | List<XGroupItem> nested_group_item_list = xgroup_item.grouped_item_list;
|
---|
398 |
|
---|
399 | XGroupItem potentially_better_item = innermostXGroup(
|
---|
400 | arrow_head, nested_group_item_list);
|
---|
401 |
|
---|
402 | if (potentially_better_item != null) {
|
---|
403 | innermost_item = potentially_better_item;
|
---|
404 | }
|
---|
405 | break;
|
---|
406 | }
|
---|
407 |
|
---|
408 | }
|
---|
409 |
|
---|
410 | return innermost_item;
|
---|
411 | }
|
---|
412 |
|
---|
413 | protected void removeXGroupItem(XGroupItem remove_item,
|
---|
414 | List<XGroupItem> grouped_item_list) {
|
---|
415 |
|
---|
416 | for (XGroupItem xgroup_item : grouped_item_list) {
|
---|
417 |
|
---|
418 | if (xgroup_item == remove_item) {
|
---|
419 | grouped_item_list.remove(xgroup_item);
|
---|
420 | return;
|
---|
421 | } else {
|
---|
422 | List<XGroupItem> nested_group_item_list = xgroup_item.grouped_item_list;
|
---|
423 | removeXGroupItem(remove_item, nested_group_item_list);
|
---|
424 |
|
---|
425 | }
|
---|
426 | }
|
---|
427 | }
|
---|
428 |
|
---|
429 | protected boolean daisyChainContainsLoop(final XGroupItem toplevel_xgroup,
|
---|
430 | final Item exitingDot, Collection<XGroupItem> groupsSeen) {
|
---|
431 | final Item start_item = exitingDot;
|
---|
432 | final Item end_item = start_item.getLines().get(0).getEndItem();
|
---|
433 | final XGroupItem xgroup = innermostXGroup(end_item,
|
---|
434 | toplevel_xgroup.getGroupedItemList());
|
---|
435 |
|
---|
436 | if(xgroup == null) return false;
|
---|
437 |
|
---|
438 | if (groupsSeen.contains(xgroup))
|
---|
439 | return true;
|
---|
440 |
|
---|
441 | groupsSeen.add(xgroup);
|
---|
442 |
|
---|
443 | for (final Item i : xgroup.remaining_item_list) {
|
---|
444 | final List<Line> lines = i.getLines();
|
---|
445 | if (lines.size() == 1 && lines.get(0).getStartItem() == i) {
|
---|
446 | return daisyChainContainsLoop(toplevel_xgroup, i, groupsSeen);
|
---|
447 | }
|
---|
448 | }
|
---|
449 | return false;
|
---|
450 | }
|
---|
451 |
|
---|
452 | protected void repositionOutOfFlowGroupsFollowLine(
|
---|
453 | XGroupItem toplevel_xgroup, Item remaining_item,
|
---|
454 | Collection<XGroupItem> out_of_flow) {
|
---|
455 | // See if this item is the start of a line
|
---|
456 | // Follow it if it is
|
---|
457 | // For each end of the line (potentially a multi-split poly-line) that
|
---|
458 | // has an arrow head:
|
---|
459 | // See if the arrow-head falls inside an XGroup area
|
---|
460 | // => Ignore endings that are in the same XGroupItem as the line started
|
---|
461 | // in
|
---|
462 | List<Line> used_in_lines = remaining_item.getLines();
|
---|
463 | if (used_in_lines.size() == 1) {
|
---|
464 | // at the start (or end) of a line
|
---|
465 |
|
---|
466 | Item start_item = used_in_lines.get(0).getStartItem();
|
---|
467 |
|
---|
468 | if (remaining_item == start_item) {
|
---|
469 |
|
---|
470 | // found the start of a line
|
---|
471 |
|
---|
472 | Collection<Item> visited = new HashSet<Item>();
|
---|
473 | visited.add(remaining_item);
|
---|
474 |
|
---|
475 | List<Item> arrow_head_endpoints = followLinesToArrowHeads(
|
---|
476 | visited, remaining_item, used_in_lines);
|
---|
477 |
|
---|
478 | // System.out.println("**** For Xgroup " + this +
|
---|
479 | // " with dot starting at " + remaining_item + " has " +
|
---|
480 | // arrow_head_endpoints.size() + " arrow head endpoints");
|
---|
481 |
|
---|
482 | Collections.sort(arrow_head_endpoints,
|
---|
483 | new MultiArrowHeadComparable());
|
---|
484 |
|
---|
485 | for (Item arrow_head : arrow_head_endpoints) {
|
---|
486 | // find the inner-most group it falls within
|
---|
487 |
|
---|
488 | List<XGroupItem> toplevel_grouped_item_list = toplevel_xgroup
|
---|
489 | .getGroupedItemList();
|
---|
490 |
|
---|
491 | XGroupItem xgroup_item = innermostXGroup(arrow_head,
|
---|
492 | toplevel_grouped_item_list);
|
---|
493 |
|
---|
494 | if (xgroup_item != null) {
|
---|
495 |
|
---|
496 | // Ignore if the found 'xgroup_item' is 'this'
|
---|
497 | // (i.e. the found arrow head is at the same XGroupItem
|
---|
498 | // level we are currently processing)
|
---|
499 |
|
---|
500 | if (xgroup_item != this) {
|
---|
501 |
|
---|
502 | if (/* out_of_flow.contains(xgroup_item) && */daisyChainContainsLoop(
|
---|
503 | toplevel_xgroup, start_item,
|
---|
504 | new ArrayList<XGroupItem>())) {
|
---|
505 | System.err.println("#Found infinite loop; not following. On frame: " +
|
---|
506 | start_item.getParent().getName() + " At position: " +
|
---|
507 | start_item.getPosition());
|
---|
508 | continue;
|
---|
509 | }
|
---|
510 |
|
---|
511 | if (!out_of_flow.contains(xgroup_item)) {
|
---|
512 | for (Item remaining_item_deeper : xgroup_item
|
---|
513 | .getRemainingItemList()) {
|
---|
514 | xgroup_item
|
---|
515 | .repositionOutOfFlowGroupsFollowLine(
|
---|
516 | toplevel_xgroup,
|
---|
517 | remaining_item_deeper,
|
---|
518 | out_of_flow);
|
---|
519 | }
|
---|
520 | }
|
---|
521 |
|
---|
522 | out_of_flow.add(xgroup_item);
|
---|
523 |
|
---|
524 | // Can't delete here, as it causes a concurrent
|
---|
525 | // exception => add to 'out_of_flow' hashmap and
|
---|
526 | // perform removal later
|
---|
527 |
|
---|
528 | // System.out.println("**** innermost XGroupItem = "
|
---|
529 | // + xgroup_item);
|
---|
530 |
|
---|
531 | // Artificially rework its (x,y) org and dimension
|
---|
532 | // to make it appear where the start of the arrow is
|
---|
533 |
|
---|
534 | AxisAlignedBoxBounds start_rect = start_item.getBoundingBox();
|
---|
535 |
|
---|
536 | XGroupItem xgroup_item_shallow_copy = new XGroupItem(
|
---|
537 | xgroup_item, start_rect);
|
---|
538 |
|
---|
539 | // Perhaps the following two lines should be moved
|
---|
540 | // to inside the constructor??
|
---|
541 |
|
---|
542 | xgroup_item
|
---|
543 | .setOutOfFlow(FlowType.out_of_flow_original);
|
---|
544 | xgroup_item_shallow_copy
|
---|
545 | .setOutOfFlow(FlowType.out_of_flow_faked_position);
|
---|
546 |
|
---|
547 | // xgroup_item.setBoundingRect(start_rect);
|
---|
548 | // xgroup_item.setOutOfFlow();
|
---|
549 |
|
---|
550 | // Finally add it in
|
---|
551 | // mapInItem(xgroup_item);
|
---|
552 | mapInItem(xgroup_item_shallow_copy);
|
---|
553 | }
|
---|
554 | }
|
---|
555 | }
|
---|
556 | }
|
---|
557 |
|
---|
558 | }
|
---|
559 | }
|
---|
560 |
|
---|
561 | /**
|
---|
562 | * Look for any 'out-of-flow' XGroup boxes, signalled by the user drawing an
|
---|
563 | * arrow line to it => force an artificial change to such boxes so its
|
---|
564 | * dimensions becomes the starting point of the arrow line. This will make
|
---|
565 | * it sort to the desired spot in the YOverlapping span
|
---|
566 | */
|
---|
567 |
|
---|
568 | public void repositionOutOfFlowGroupsRecursive(XGroupItem toplevel_xgroup,
|
---|
569 | Collection<XGroupItem> out_of_flow) {
|
---|
570 | // Map in all the items in the given list:
|
---|
571 | for (Item remaining_item : remaining_item_list) {
|
---|
572 |
|
---|
573 | repositionOutOfFlowGroupsFollowLine(toplevel_xgroup,
|
---|
574 | remaining_item, out_of_flow);
|
---|
575 | }
|
---|
576 |
|
---|
577 | // Now recursively work through each item's nested x-groups
|
---|
578 | for (XGroupItem xgroup_item : grouped_item_list) {
|
---|
579 |
|
---|
580 | if (!out_of_flow.contains(xgroup_item)) {
|
---|
581 | xgroup_item.repositionOutOfFlowGroupsRecursive(toplevel_xgroup,
|
---|
582 | out_of_flow);
|
---|
583 | }
|
---|
584 | }
|
---|
585 | }
|
---|
586 |
|
---|
587 | public void repositionOutOfFlowGroups(XGroupItem toplevel_xgroup) {
|
---|
588 | // Collection<XGroupItem> out_of_flow = new HashSet<XGroupItem>();
|
---|
589 | Collection<XGroupItem> out_of_flow = new ArrayList<XGroupItem>();
|
---|
590 |
|
---|
591 | repositionOutOfFlowGroupsRecursive(toplevel_xgroup, out_of_flow);
|
---|
592 |
|
---|
593 | // List<XGroupItem >toplevel_grouped_item_list =
|
---|
594 | // toplevel_xgroup.getGroupedItemList();
|
---|
595 |
|
---|
596 | // ****
|
---|
597 |
|
---|
598 | // Want to remove the original "out-of-position" blocks that were found,
|
---|
599 | // as (for each arrow
|
---|
600 | // point to such an original) shallow-copies now exist with 'faked'
|
---|
601 | // positions that correspond
|
---|
602 | // to where the arrows start.
|
---|
603 |
|
---|
604 | // No longer remove them from the nested grouped structure, rather, rely
|
---|
605 | // on these items being
|
---|
606 | // tagged "isOutOfFLow()" to be skipped by subsequent traversal calls
|
---|
607 | // (e.g., to generate
|
---|
608 | // the normal "in-flow" nested blocks)
|
---|
609 |
|
---|
610 | // Structuring things this way, means that it is now possible to have
|
---|
611 | // multiple arrows
|
---|
612 | // pointing to the same block of code, and both "out-of-flow" arrows
|
---|
613 | // will be honoured,
|
---|
614 | // replicating the code at their respective start points
|
---|
615 |
|
---|
616 | /*
|
---|
617 | * for (XGroupItem xgroup_item: out_of_flow) {
|
---|
618 | * removeXGroupItem(xgroup_item,toplevel_grouped_item_list); }
|
---|
619 | */
|
---|
620 |
|
---|
621 | }
|
---|
622 |
|
---|
623 | /**
|
---|
624 | * Focusing only on enclosures that fit within the given polygon *and* have
|
---|
625 | * this item in it, the method finds the largest of these enclosures and
|
---|
626 | * returns all the items it contains
|
---|
627 | *
|
---|
628 | * @return
|
---|
629 | */
|
---|
630 | public Collection<Item> getItemsInNestedEnclosure(Item given_item, PolygonBounds outer_polygon)
|
---|
631 | {
|
---|
632 | Collection<Item> sameEnclosure = null;
|
---|
633 | Collection<Item> seen = new HashSet<Item>();
|
---|
634 |
|
---|
635 | double enclosureArea = 0.0;
|
---|
636 |
|
---|
637 | for (Item i : frame.getSortedItems()) {
|
---|
638 |
|
---|
639 | // Go through all the enclosures ...
|
---|
640 |
|
---|
641 | if (!seen.contains(i) && i.isEnclosed()) {
|
---|
642 |
|
---|
643 | Collection<Item> i_enclosing_dots_coll = i.getEnclosingDots();
|
---|
644 | List<Item> i_enclosing_dots = new ArrayList<Item>(
|
---|
645 | i_enclosing_dots_coll); // Change to List of items
|
---|
646 |
|
---|
647 | seen.addAll(i_enclosing_dots);
|
---|
648 |
|
---|
649 | PolygonBounds i_polygon = new PolygonBounds();
|
---|
650 | for (int di = 0; di < i_enclosing_dots.size(); di++) {
|
---|
651 | Item d = i_enclosing_dots.get(di);
|
---|
652 |
|
---|
653 | i_polygon.addPoint(d.getX(), d.getY());
|
---|
654 | }
|
---|
655 |
|
---|
656 | // ... looking for one that is completely contained in the
|
---|
657 | // 'outer_polygon' and ...
|
---|
658 | if (outer_polygon.completelyContains(i_polygon)) {
|
---|
659 |
|
---|
660 | Collection<Item> enclosed = i.getEnclosedItems();
|
---|
661 |
|
---|
662 | // ... includes this item
|
---|
663 | if (enclosed.contains(given_item)) {
|
---|
664 |
|
---|
665 | // Remember it if it is larger than anything we've
|
---|
666 | // encountered before
|
---|
667 | if ((i.getEnclosedArea() > enclosureArea)) {
|
---|
668 | enclosureArea = i.getEnclosedArea();
|
---|
669 | sameEnclosure = enclosed;
|
---|
670 | }
|
---|
671 | }
|
---|
672 | }
|
---|
673 |
|
---|
674 | }
|
---|
675 | }
|
---|
676 |
|
---|
677 | if (sameEnclosure == null)
|
---|
678 | return new LinkedList<Item>();
|
---|
679 |
|
---|
680 | return sameEnclosure;
|
---|
681 | }
|
---|
682 |
|
---|
683 | public void separateYOverlappingItems(Frame frame, List<Item> item_list,
|
---|
684 | PolygonBounds enclosing_polygon, List<Text> raw_text_item_list,
|
---|
685 | List<XGroupItem> grouped_item_list, List<Item> remaining_item_list) {
|
---|
686 | final List<Item> origonal_item_list = new ArrayList<Item>(item_list);
|
---|
687 | // raw_text_items_list => all the non-enclosed text items
|
---|
688 | // grouped_items_list => list of all enclosures containing text items
|
---|
689 | // remaining_item_list => whatever is left: mostly dots that form part
|
---|
690 | // of lines/polylines/polygons
|
---|
691 |
|
---|
692 | PolygonBounds area_enclosing_polygon = new PolygonBounds(enclosing_polygon);
|
---|
693 |
|
---|
694 | List<PolygonBounds> area_enclosed_polygon_list = new ArrayList<PolygonBounds>();
|
---|
695 |
|
---|
696 | while (item_list.size() > 0) {
|
---|
697 | Item item = item_list.remove(0); // shift
|
---|
698 |
|
---|
699 | if (item instanceof Text) {
|
---|
700 | Text text = (Text) item;
|
---|
701 |
|
---|
702 | Collection<Item> items_in_nested_enclosure_coll = getItemsInNestedEnclosure(
|
---|
703 | text, area_enclosing_polygon);
|
---|
704 | List<Item> items_in_nested_enclosure = new ArrayList<Item>(
|
---|
705 | items_in_nested_enclosure_coll); // Change to handling
|
---|
706 | // it as a list
|
---|
707 |
|
---|
708 | if (items_in_nested_enclosure.size() == 0) {
|
---|
709 | // if(text.getPixelBoundsUnion().x >=
|
---|
710 | // this.getBoundingXLeft() && text.getPixelBoundsUnion().y
|
---|
711 | // >= this.getBoundingYTop())
|
---|
712 | // raw_text_item_list.add(text);
|
---|
713 | // else remaining_item_list.add(text);
|
---|
714 | raw_text_item_list.add(text);
|
---|
715 | } else {
|
---|
716 |
|
---|
717 | // Something other than just this text-item is around
|
---|
718 |
|
---|
719 | while (items_in_nested_enclosure.size() > 0) {
|
---|
720 |
|
---|
721 | Item enclosure_item = items_in_nested_enclosure
|
---|
722 | .remove(0); // shift
|
---|
723 |
|
---|
724 | PolygonBounds enclosed_polygon = enclosure_item.getEnclosedShape();
|
---|
725 |
|
---|
726 | if (enclosed_polygon != null) {
|
---|
727 | // Got a group
|
---|
728 | // => Remove any items-in-nested-enclosure from:
|
---|
729 | //
|
---|
730 | // 'item_list' (so we don't waste our time
|
---|
731 | // discovering and processing again these related
|
---|
732 | // items)
|
---|
733 | // and
|
---|
734 | // 'raw_text_item_list' and 'remaining_item_lst' (as
|
---|
735 | // we now know they are part of the nested enclosed
|
---|
736 | // group)
|
---|
737 |
|
---|
738 | PolygonBounds area_enclosed_polygon = new PolygonBounds(enclosed_polygon);
|
---|
739 | area_enclosed_polygon_list.add(area_enclosed_polygon);
|
---|
740 |
|
---|
741 | item_list.remove(enclosure_item);
|
---|
742 | item_list.removeAll(items_in_nested_enclosure);
|
---|
743 |
|
---|
744 | raw_text_item_list.remove(enclosure_item);
|
---|
745 | raw_text_item_list
|
---|
746 | .removeAll(items_in_nested_enclosure);
|
---|
747 |
|
---|
748 | remaining_item_list.remove(enclosure_item);
|
---|
749 | remaining_item_list
|
---|
750 | .removeAll(items_in_nested_enclosure);
|
---|
751 |
|
---|
752 | // Now remove any of the just used enclosed-items if
|
---|
753 | // they formed part of the perimeter
|
---|
754 | List<Item> items_on_perimeter = new ArrayList<Item>();
|
---|
755 |
|
---|
756 | Iterator<Item> item_iterator = items_in_nested_enclosure
|
---|
757 | .iterator();
|
---|
758 | while (item_iterator.hasNext()) {
|
---|
759 | Item item_to_check = item_iterator.next();
|
---|
760 | Point pt_to_check = new Point(item_to_check.getX(), item_to_check.getY());
|
---|
761 |
|
---|
762 | if (area_enclosed_polygon.isVertex(pt_to_check)) {
|
---|
763 | items_on_perimeter.add(item_to_check);
|
---|
764 | }
|
---|
765 | }
|
---|
766 |
|
---|
767 | items_in_nested_enclosure
|
---|
768 | .removeAll(items_on_perimeter);
|
---|
769 | }
|
---|
770 |
|
---|
771 | else {
|
---|
772 | // Text or single point (with no enclosure)
|
---|
773 |
|
---|
774 | // This item doesn't feature at this level
|
---|
775 | // => will be subsequently capture by the group
|
---|
776 | // polygon below
|
---|
777 | // and processed by the recursive call
|
---|
778 |
|
---|
779 | item_list.remove(enclosure_item);
|
---|
780 | }
|
---|
781 | }
|
---|
782 |
|
---|
783 | }
|
---|
784 |
|
---|
785 | } // end of Text test
|
---|
786 | else {
|
---|
787 | // non-text item => add to remaining_item_list
|
---|
788 | remaining_item_list.add(item);
|
---|
789 | }
|
---|
790 |
|
---|
791 | }
|
---|
792 |
|
---|
793 | // Sort areas, smallest to largest
|
---|
794 | Collections.sort(area_enclosed_polygon_list,
|
---|
795 | new Comparator<PolygonBounds>() {
|
---|
796 |
|
---|
797 | public int compare(PolygonBounds ap1, PolygonBounds ap2) {
|
---|
798 | Double ap1_area = ap1.getArea();
|
---|
799 | Double ap2_area = ap2.getArea();
|
---|
800 |
|
---|
801 | return ap2_area.compareTo(ap1_area);
|
---|
802 | }
|
---|
803 | });
|
---|
804 |
|
---|
805 | // Remove any enclosed polygon areas that are completely contained in a
|
---|
806 | // larger one
|
---|
807 |
|
---|
808 | for (int ri = 0; ri < area_enclosed_polygon_list.size(); ri++) {
|
---|
809 | // ri = remove index pos
|
---|
810 |
|
---|
811 | PolygonBounds rpoly = area_enclosed_polygon_list.get(ri);
|
---|
812 |
|
---|
813 | for (int ci = ri + 1; ci < area_enclosed_polygon_list.size(); ci++) {
|
---|
814 | // ci = check index pos
|
---|
815 | PolygonBounds cpoly = area_enclosed_polygon_list.get(ci);
|
---|
816 | if (rpoly.completelyContains(cpoly)) {
|
---|
817 | area_enclosed_polygon_list.remove(ci);
|
---|
818 | ri--; // to offset to the outside loop increment
|
---|
819 | break;
|
---|
820 | }
|
---|
821 | }
|
---|
822 | }
|
---|
823 |
|
---|
824 | // By this point, guaranteed that the remaining polygons are the largest
|
---|
825 | // ones
|
---|
826 | // that capture groups of items
|
---|
827 | //
|
---|
828 | // There may be sub-groupings within them, which is the reason for the
|
---|
829 | // recursive call below
|
---|
830 |
|
---|
831 | for (PolygonBounds area_polygon : area_enclosed_polygon_list) {
|
---|
832 |
|
---|
833 | Collection<Item> enclosed_items = FrameUtils.getItemsEnclosedBy(frame, area_polygon);
|
---|
834 | List<Item> enclosed_item_list = new ArrayList<Item>(enclosed_items);
|
---|
835 |
|
---|
836 | int i = 0;
|
---|
837 | while (i < enclosed_item_list.size()) {
|
---|
838 | Item enclosed_item = enclosed_item_list.get(i);
|
---|
839 |
|
---|
840 | // Filter out enclosed-items points that are part of the
|
---|
841 | // polygon's perimeter
|
---|
842 | if (area_polygon.isVertex(enclosed_item.getPosition())) {
|
---|
843 | enclosed_item_list.remove(i);
|
---|
844 | // Don't include items the user hasn't asked us to.
|
---|
845 | }
|
---|
846 | // Only include items the user has asked to to include.
|
---|
847 | else if (!origonal_item_list.contains(enclosed_item))
|
---|
848 | enclosed_item_list.remove(i);
|
---|
849 | else {
|
---|
850 | i++;
|
---|
851 | }
|
---|
852 | }
|
---|
853 |
|
---|
854 | // Recursively work on the identified sub-group
|
---|
855 |
|
---|
856 | XGroupItem xgroup_item = new XGroupItem(frame, enclosed_item_list, area_polygon);
|
---|
857 |
|
---|
858 | grouped_item_list.add(xgroup_item);
|
---|
859 | }
|
---|
860 | }
|
---|
861 |
|
---|
862 | protected void castShadowIntoEmptySpace(
|
---|
863 | YOverlappingItemsTopEdge y_item_span_top_edge, int yt, int yb) {
|
---|
864 | // Assumes that all entries cast into are currently null
|
---|
865 | // => Use the more general castShadow() below, if this is not guaranteed
|
---|
866 | // to be the case
|
---|
867 |
|
---|
868 | YOverlappingItemsShadow y_span_shadow = new YOverlappingItemsShadow(
|
---|
869 | y_item_span_top_edge);
|
---|
870 |
|
---|
871 | for (int y = yt; y <= yb; y++) {
|
---|
872 |
|
---|
873 | setYOverlappingItemsSpan(y, y_span_shadow);
|
---|
874 | }
|
---|
875 | }
|
---|
876 |
|
---|
877 | protected void castShadow(YOverlappingItemsTopEdge y_item_span_top_edge,
|
---|
878 | int yt, int yb) {
|
---|
879 | // Cast shadows only in places where there are currently no shadow
|
---|
880 | // entries
|
---|
881 |
|
---|
882 | YOverlappingItemsShadow y_span_shadow = new YOverlappingItemsShadow(
|
---|
883 | y_item_span_top_edge);
|
---|
884 |
|
---|
885 | int y = yt;
|
---|
886 |
|
---|
887 | while (y <= yb) {
|
---|
888 | YOverlappingItemsSpan y_item_span = getYOverlappingItemsSpan(y);
|
---|
889 |
|
---|
890 | if (y_item_span == null) {
|
---|
891 | setYOverlappingItemsSpan(y, y_span_shadow);
|
---|
892 | y++;
|
---|
893 | } else if (y_item_span instanceof YOverlappingItemsTopEdge) {
|
---|
894 | // Our shadow has run into another top-edged zone
|
---|
895 | // => need to extend the shadow to include this zone as well
|
---|
896 | // (making this encountered top-edge obsolete)
|
---|
897 |
|
---|
898 | // Merge the newly encountered top-line in with the current one
|
---|
899 | // and change all its shadow references to our (dominant) one
|
---|
900 |
|
---|
901 | XOrderedLine dominant_x_line = y_item_span_top_edge
|
---|
902 | .getXOrderedLine();
|
---|
903 |
|
---|
904 | YOverlappingItemsTopEdge obsolete_top_edge = (YOverlappingItemsTopEdge) y_item_span;
|
---|
905 | XOrderedLine obsolete_x_line = obsolete_top_edge
|
---|
906 | .getXOrderedLine();
|
---|
907 |
|
---|
908 | for (XItem xitem : obsolete_x_line.getXItemList()) {
|
---|
909 |
|
---|
910 | dominant_x_line.orderedMergeItem(xitem);
|
---|
911 | }
|
---|
912 |
|
---|
913 | while (y_item_span != null) {
|
---|
914 |
|
---|
915 | setYOverlappingItemsSpan(y, y_span_shadow);
|
---|
916 |
|
---|
917 | y++;
|
---|
918 |
|
---|
919 | if (y <= yb) {
|
---|
920 | y_item_span = getYOverlappingItemsSpan(y);
|
---|
921 | } else {
|
---|
922 | y_item_span = null;
|
---|
923 | }
|
---|
924 | }
|
---|
925 | } else {
|
---|
926 | y++;
|
---|
927 | }
|
---|
928 | }
|
---|
929 | }
|
---|
930 |
|
---|
931 | protected int autocropToTop(XItem xitem) {
|
---|
932 | // top-edge over-spill can occur (and is acceptable) when (for example)
|
---|
933 | // the given xitem is a raw-text item
|
---|
934 | // that doesn't neatly fit in an enclosed area (i,e., is poking out the
|
---|
935 | // top of an enclosing rectangle)
|
---|
936 | // => cut down to the top of 'this' xgroupitem
|
---|
937 |
|
---|
938 | int yt = xitem.getBoundingYTop();
|
---|
939 |
|
---|
940 | yt = cropToTop(yt); // Only changes yt if in an over-spill situation
|
---|
941 |
|
---|
942 | return yt;
|
---|
943 |
|
---|
944 | }
|
---|
945 |
|
---|
946 | protected int autocropToBot(XItem xitem) {
|
---|
947 |
|
---|
948 | // similar to autocropToTop(), bottom-edge over-spill can occur (and is
|
---|
949 | // acceptable) with
|
---|
950 | // a less than precise placed raw-text item
|
---|
951 |
|
---|
952 | int yb = xitem.getBoundingYBot();
|
---|
953 |
|
---|
954 | yb = cropToBot(yb); // Only changes yb if in an over-spill situation
|
---|
955 |
|
---|
956 | return yb;
|
---|
957 | }
|
---|
958 |
|
---|
959 | protected boolean yExtentIntersection(XGroupItem xgroup_item1,
|
---|
960 | XGroupItem xgroup_item2) {
|
---|
961 | // Computation applied to y-extent of each rectangle
|
---|
962 |
|
---|
963 | // To intersect, either a point in item1 falls inside item2,
|
---|
964 | // or else item2 is completely within item1
|
---|
965 |
|
---|
966 | int yt1 = xgroup_item1.getBoundingYTop();
|
---|
967 | int yb1 = xgroup_item1.getBoundingYBot();
|
---|
968 |
|
---|
969 | int yt2 = xgroup_item2.getBoundingYTop();
|
---|
970 |
|
---|
971 | // yt1 insides item2?
|
---|
972 | if (xgroup_item2.containedInYExtent(yt1)) {
|
---|
973 | return true;
|
---|
974 | }
|
---|
975 |
|
---|
976 | // yb1 inside item2?
|
---|
977 | if (xgroup_item2.containedInYExtent(yb1)) {
|
---|
978 | return true;
|
---|
979 | }
|
---|
980 |
|
---|
981 | // item2 completely inside item1?
|
---|
982 | //
|
---|
983 | // With the previous testing, this can be determined
|
---|
984 | // by checking only one of the values is inside.
|
---|
985 | // Doesn't matter which => choose yt2
|
---|
986 |
|
---|
987 | if (xgroup_item1.containedInYExtent(yt2)) {
|
---|
988 | return true;
|
---|
989 | }
|
---|
990 |
|
---|
991 | // Get to here if there is no intersection
|
---|
992 | return false;
|
---|
993 |
|
---|
994 | }
|
---|
995 |
|
---|
996 | protected ArrayList<XGroupItem> calcXGroupYExtentIntersection(
|
---|
997 | XGroupItem xgroup_pivot_item, List<XGroupItem> xgroup_item_list) {
|
---|
998 | ArrayList<XGroupItem> intersect_list = new ArrayList<XGroupItem>();
|
---|
999 |
|
---|
1000 | for (XGroupItem xgroup_item : xgroup_item_list) {
|
---|
1001 |
|
---|
1002 | if (xgroup_item == xgroup_pivot_item) {
|
---|
1003 | // Don't bother working out the intersection with itself!
|
---|
1004 | continue;
|
---|
1005 | }
|
---|
1006 |
|
---|
1007 | if (yExtentIntersection(xgroup_pivot_item, xgroup_item)) {
|
---|
1008 | intersect_list.add(xgroup_item);
|
---|
1009 | }
|
---|
1010 | }
|
---|
1011 |
|
---|
1012 | return intersect_list;
|
---|
1013 | }
|
---|
1014 |
|
---|
1015 | protected ArrayList<YOverlappingItemsTopEdge> calcYSpanOverlap(XItem xitem) {
|
---|
1016 | int yt = autocropToTop(xitem);
|
---|
1017 | int yb = autocropToBot(xitem);
|
---|
1018 |
|
---|
1019 | ArrayList<YOverlappingItemsTopEdge> top_edge_list = new ArrayList<YOverlappingItemsTopEdge>();
|
---|
1020 |
|
---|
1021 | for (int y = yt; y <= yb; y++) {
|
---|
1022 |
|
---|
1023 | YOverlappingItemsSpan item_span = getYOverlappingItemsSpan(y);
|
---|
1024 |
|
---|
1025 | if (item_span != null) {
|
---|
1026 |
|
---|
1027 | if (item_span instanceof YOverlappingItemsTopEdge) {
|
---|
1028 |
|
---|
1029 | YOverlappingItemsTopEdge y_item_span_top_edge = (YOverlappingItemsTopEdge) item_span;
|
---|
1030 |
|
---|
1031 | top_edge_list.add(y_item_span_top_edge);
|
---|
1032 | }
|
---|
1033 | }
|
---|
1034 |
|
---|
1035 | }
|
---|
1036 |
|
---|
1037 | return top_edge_list;
|
---|
1038 | }
|
---|
1039 |
|
---|
1040 | protected boolean multipleYSpanOverlap(XItem xitem) {
|
---|
1041 | return calcYSpanOverlap(xitem).size() > 0;
|
---|
1042 | }
|
---|
1043 |
|
---|
1044 | public void mapInItem(XItem xitem) {
|
---|
1045 |
|
---|
1046 | int yt = autocropToTop(xitem);
|
---|
1047 | int yb = autocropToBot(xitem);
|
---|
1048 | /*
|
---|
1049 | * int yt = xitem.getBoundingYTop(); int yb = xitem.getBoundingYBot();
|
---|
1050 | *
|
---|
1051 | * // top-edge over-spill can occur (and is acceptable) when (for
|
---|
1052 | * example) the given xitem is a raw-text item // that doesn't neatly
|
---|
1053 | * fit in an enclosed area (i,e., is poking out the top of an enclosing
|
---|
1054 | * rectangle) yt = cropToTop(yt); // Only changes yt if in an over-spill
|
---|
1055 | * situation
|
---|
1056 | *
|
---|
1057 | * // similarly, bottom-edge over-spill can occur (and is acceptable)
|
---|
1058 | * with a less than precise placed raw-text item yb = cropToBot(yb); //
|
---|
1059 | * Only changes yb if in an over-spill situation
|
---|
1060 | */
|
---|
1061 |
|
---|
1062 | // Merge into 'items_span' checking for overlaps (based on xitem's
|
---|
1063 | // y-span)
|
---|
1064 |
|
---|
1065 | boolean merged_item = false;
|
---|
1066 | for (int y = yt; y <= yb; y++) {
|
---|
1067 |
|
---|
1068 | YOverlappingItemsSpan item_span = getYOverlappingItemsSpan(y);
|
---|
1069 |
|
---|
1070 | if (item_span != null) {
|
---|
1071 |
|
---|
1072 | if (item_span instanceof YOverlappingItemsTopEdge) {
|
---|
1073 |
|
---|
1074 | // Hit a top edge of an existing item
|
---|
1075 |
|
---|
1076 | // Need to:
|
---|
1077 | // 1. *Required* Insert new item into current x-ordered line
|
---|
1078 | // 2. *Conditionally* Move entire x-ordered line up to
|
---|
1079 | // higher 'y' top edge
|
---|
1080 | // 3. *Required* Cast shadow for new item
|
---|
1081 |
|
---|
1082 | // Note for Step 2:
|
---|
1083 | // i) No need to do the move if y == yt
|
---|
1084 | // (the case when the new top edge is exactly the same
|
---|
1085 | // height as the existing one)
|
---|
1086 | // ii) If moving top-edge (the case when y > yt) then no
|
---|
1087 | // need to recalculate the top edge for existing shadows, as
|
---|
1088 | // this 'connection' is stored as a reference
|
---|
1089 |
|
---|
1090 | // Step 1: insert into existing top-edge
|
---|
1091 |
|
---|
1092 | YOverlappingItemsTopEdge y_item_span_top_edge = (YOverlappingItemsTopEdge) item_span;
|
---|
1093 | XOrderedLine xitem_span = y_item_span_top_edge
|
---|
1094 | .getXOrderedLine();
|
---|
1095 |
|
---|
1096 | xitem_span
|
---|
1097 | .orderedMergeItem(xitem.getBoundingXLeft(), xitem);
|
---|
1098 |
|
---|
1099 | // Step 2: if our new top-edge is higher than the existing
|
---|
1100 | // one, then need to move existing top-edge
|
---|
1101 | if (y > yt) {
|
---|
1102 |
|
---|
1103 | // Move to new position
|
---|
1104 | setYOverlappingItemsSpan(yt, y_item_span_top_edge);
|
---|
1105 |
|
---|
1106 | // Old position needs to become a shadow reference
|
---|
1107 | YOverlappingItemsShadow y_span_shadow = new YOverlappingItemsShadow(
|
---|
1108 | y_item_span_top_edge);
|
---|
1109 | setYOverlappingItemsSpan(y, y_span_shadow);
|
---|
1110 | }
|
---|
1111 |
|
---|
1112 | // Step 3: Cast shadow
|
---|
1113 | castShadow(y_item_span_top_edge, yt + 1, yb);
|
---|
1114 |
|
---|
1115 | } else {
|
---|
1116 | // Top edge to our new item has hit a shadow entry (straight
|
---|
1117 | // off)
|
---|
1118 | // => Look up what the shadow references, and then add in to
|
---|
1119 | // that
|
---|
1120 |
|
---|
1121 | // Effectively after the shadow reference lookup this is the
|
---|
1122 | // same
|
---|
1123 | // as the above, without the need to worry about Step 2 (as
|
---|
1124 | // no move is needed)
|
---|
1125 |
|
---|
1126 | YOverlappingItemsShadow y_item_span_shadow = (YOverlappingItemsShadow) item_span;
|
---|
1127 | YOverlappingItemsTopEdge y_item_span_top_edge = y_item_span_shadow
|
---|
1128 | .getTopEdge();
|
---|
1129 |
|
---|
1130 | XOrderedLine xitem_span = y_item_span_top_edge
|
---|
1131 | .getXOrderedLine();
|
---|
1132 |
|
---|
1133 | // merge with this item list, preserving x ordering
|
---|
1134 | xitem_span
|
---|
1135 | .orderedMergeItem(xitem.getBoundingXLeft(), xitem);
|
---|
1136 |
|
---|
1137 | // Now Cast shadow
|
---|
1138 | castShadow(y_item_span_top_edge, yt + 1, yb);
|
---|
1139 | }
|
---|
1140 |
|
---|
1141 | merged_item = true;
|
---|
1142 | break;
|
---|
1143 |
|
---|
1144 | }
|
---|
1145 |
|
---|
1146 | }
|
---|
1147 |
|
---|
1148 | // Having checked all the y-location's ('yt' to 'yb') of this x-item, if
|
---|
1149 | // all y-span entries were found to be null
|
---|
1150 | // => 'merged_item' is still false
|
---|
1151 |
|
---|
1152 | if (!merged_item) {
|
---|
1153 | // xitem didn't intersect with any existing y-spans
|
---|
1154 | // => simple case for add (i.e. all entries affected by map are
|
---|
1155 | // currently null)
|
---|
1156 |
|
---|
1157 | // Start up a new x-ordered-line (consisting of only 'xitem'), add
|
---|
1158 | // in top edge and cast shadow
|
---|
1159 |
|
---|
1160 | XOrderedLine xitem_line = new XOrderedLine(xitem);
|
---|
1161 |
|
---|
1162 | YOverlappingItemsTopEdge y_item_span_top_edge = new YOverlappingItemsTopEdge(
|
---|
1163 | xitem_line);
|
---|
1164 |
|
---|
1165 | setYOverlappingItemsSpan(yt, y_item_span_top_edge);
|
---|
1166 |
|
---|
1167 | castShadowIntoEmptySpace(y_item_span_top_edge, yt + 1, yb);
|
---|
1168 | }
|
---|
1169 |
|
---|
1170 | }
|
---|
1171 |
|
---|
1172 | private ArrayList<DimensionExtent> calcXGroupXGaps(
|
---|
1173 | XGroupItem xgroup_pivot_item, List<XGroupItem> xgroup_item_list) {
|
---|
1174 | // 'xgroup_pivot_item' current not used!!
|
---|
1175 |
|
---|
1176 | int enclosing_xl = getBoundingXLeft();
|
---|
1177 | int enclosing_xr = getBoundingXRight();
|
---|
1178 |
|
---|
1179 | int enclosing_x_dim = enclosing_xr - enclosing_xl + 1;
|
---|
1180 | boolean is_x_shadow[] = new boolean[enclosing_x_dim]; // defaults all
|
---|
1181 | // values to
|
---|
1182 | // false
|
---|
1183 |
|
---|
1184 | ArrayList<DimensionExtent> x_gap_list = new ArrayList<DimensionExtent>();
|
---|
1185 |
|
---|
1186 | for (XGroupItem xgroup_item : xgroup_item_list) {
|
---|
1187 | int xl = xgroup_item.getBoundingXLeft();
|
---|
1188 | int xr = xgroup_item.getBoundingXRight();
|
---|
1189 |
|
---|
1190 | for (int x = xl; x <= xr; x++) {
|
---|
1191 | int x_offset = x - enclosing_xl;
|
---|
1192 | is_x_shadow[x_offset] = true;
|
---|
1193 | }
|
---|
1194 | }
|
---|
1195 |
|
---|
1196 | // New find where the contiguous runs of 'false' are, as they point to
|
---|
1197 | // the gaps
|
---|
1198 |
|
---|
1199 | int x = 1;
|
---|
1200 | int start_x_gap = enclosing_xl;
|
---|
1201 | boolean in_gap = true;
|
---|
1202 |
|
---|
1203 | while (x < is_x_shadow.length) {
|
---|
1204 | boolean prev_is_shadow = is_x_shadow[x - 1];
|
---|
1205 | boolean this_is_shadow = is_x_shadow[x];
|
---|
1206 |
|
---|
1207 | if (prev_is_shadow && !this_is_shadow) {
|
---|
1208 | // reached end of shadow, record start of a gap
|
---|
1209 | start_x_gap = enclosing_xl + x;
|
---|
1210 | in_gap = true;
|
---|
1211 | } else if (!prev_is_shadow && this_is_shadow) {
|
---|
1212 | // reached the end of a gap, record it
|
---|
1213 | int end_x_gap = enclosing_xl + x - 1;
|
---|
1214 | DimensionExtent de = new DimensionExtent(start_x_gap, end_x_gap);
|
---|
1215 | x_gap_list.add(de);
|
---|
1216 | in_gap = false;
|
---|
1217 | }
|
---|
1218 | x++;
|
---|
1219 | }
|
---|
1220 |
|
---|
1221 | if (in_gap) {
|
---|
1222 | int end_x_gap = enclosing_xl + x - 1;
|
---|
1223 | DimensionExtent de = new DimensionExtent(start_x_gap, end_x_gap);
|
---|
1224 | x_gap_list.add(de);
|
---|
1225 | }
|
---|
1226 |
|
---|
1227 | return x_gap_list;
|
---|
1228 | }
|
---|
1229 |
|
---|
1230 | protected void boxMultipleXRawItemsInGaps(XGroupItem xgroup_pivot_item,
|
---|
1231 | ArrayList<DimensionExtent> x_text_gap_list) {
|
---|
1232 | ArrayList<YOverlappingItemsTopEdge> y_span_overlap = calcYSpanOverlap(xgroup_pivot_item);
|
---|
1233 |
|
---|
1234 | // work out where continuous runs of raw-text items intersect with the
|
---|
1235 | // gaps occurring between boxed items
|
---|
1236 |
|
---|
1237 | // foreach x-gap,
|
---|
1238 | // foreach y-span's list of x-sorted objects,
|
---|
1239 | // => see how many raw-text items fit into it
|
---|
1240 |
|
---|
1241 | ArrayList<ArrayList<XRawItem>> implicit_box_list = new ArrayList<ArrayList<XRawItem>>();
|
---|
1242 |
|
---|
1243 | for (DimensionExtent de_gap : x_text_gap_list) {
|
---|
1244 |
|
---|
1245 | int deg_xl = de_gap.min;
|
---|
1246 | int deg_xr = de_gap.max;
|
---|
1247 |
|
---|
1248 | ArrayList<XRawItem> grouped_raw_text_list = new ArrayList<XRawItem>();
|
---|
1249 |
|
---|
1250 | int y_span_line_count = 0;
|
---|
1251 |
|
---|
1252 | for (YOverlappingItemsTopEdge y_top_edge : y_span_overlap) {
|
---|
1253 |
|
---|
1254 | List<XItem> x_item_list = y_top_edge.x_items.getXItemList();
|
---|
1255 |
|
---|
1256 | boolean used_x_raw_text = false;
|
---|
1257 |
|
---|
1258 | for (XItem x_item : x_item_list) {
|
---|
1259 |
|
---|
1260 | if (x_item instanceof XRawItem) {
|
---|
1261 | XRawItem x_raw_item = (XRawItem) x_item;
|
---|
1262 |
|
---|
1263 | int xri_xl = x_raw_item.getBoundingXLeft();
|
---|
1264 | int xri_xr = x_raw_item.getBoundingXRight();
|
---|
1265 |
|
---|
1266 | if ((xri_xl >= deg_xl) && (xri_xr <= deg_xr)) {
|
---|
1267 | grouped_raw_text_list.add(x_raw_item);
|
---|
1268 | used_x_raw_text = true;
|
---|
1269 | }
|
---|
1270 | }
|
---|
1271 | }
|
---|
1272 |
|
---|
1273 | if (used_x_raw_text) {
|
---|
1274 | y_span_line_count++;
|
---|
1275 | }
|
---|
1276 | }
|
---|
1277 |
|
---|
1278 | // Only interested in implicitly boxing if the formed group is used
|
---|
1279 | // over 2 or more y-span lines
|
---|
1280 | if (y_span_line_count >= 2) {
|
---|
1281 | implicit_box_list.add(grouped_raw_text_list);
|
---|
1282 | }
|
---|
1283 | }
|
---|
1284 |
|
---|
1285 | // System.err.println("*** Number of implicit groups found: " +
|
---|
1286 | // implicit_box_list.size());
|
---|
1287 |
|
---|
1288 | for (ArrayList<XRawItem> implicit_x_item_list : implicit_box_list) {
|
---|
1289 |
|
---|
1290 | for (YOverlappingItemsTopEdge y_top_edge : y_span_overlap) {
|
---|
1291 |
|
---|
1292 | List<XItem> yspan_x_item_list = y_top_edge.x_items
|
---|
1293 | .getXItemList();
|
---|
1294 |
|
---|
1295 | // Remove all the XRawItems from the current y-span
|
---|
1296 | yspan_x_item_list.removeAll(implicit_x_item_list);
|
---|
1297 |
|
---|
1298 | }
|
---|
1299 |
|
---|
1300 | // Create a list of original Text items
|
---|
1301 | // (OK that they are not y-ordered)
|
---|
1302 | ArrayList<Item> implicit_item_list = new ArrayList<Item>();
|
---|
1303 | for (XRawItem x_raw_item : implicit_x_item_list) {
|
---|
1304 | Item item = (Text) x_raw_item.item;
|
---|
1305 | implicit_item_list.add(item);
|
---|
1306 | }
|
---|
1307 |
|
---|
1308 | // Now insert them as their own boxed up items
|
---|
1309 | XGroupItem implicit_xgroup = new XGroupItem(frame,
|
---|
1310 | implicit_item_list);
|
---|
1311 | this.mapInItem(implicit_xgroup);
|
---|
1312 | }
|
---|
1313 |
|
---|
1314 | }
|
---|
1315 |
|
---|
1316 | protected void implicitBoxing(XGroupItem xgroup_pivot_item,
|
---|
1317 | List<XGroupItem> xgroup_item_list) {
|
---|
1318 |
|
---|
1319 | // Implicit boxing is needed if there are two or more raw-text items
|
---|
1320 | // that overlap in y-span-map of the given xgroup_item
|
---|
1321 |
|
---|
1322 | // First establish if there is any kind of multiple overlap
|
---|
1323 | if (multipleYSpanOverlap(xgroup_pivot_item)) {
|
---|
1324 |
|
---|
1325 | // Overlap could be with other boxed items, so need to investigate
|
---|
1326 | // further
|
---|
1327 |
|
---|
1328 | // Do this by working out if there are any raw-text items lurking
|
---|
1329 | // between the pivot xgroup_item
|
---|
1330 | // and the list of other xgroup_items
|
---|
1331 |
|
---|
1332 | // Step 1: Get list intersection (y-dim) of this group item, with
|
---|
1333 | // any
|
---|
1334 | // other group items in xgroup_item_list
|
---|
1335 | ArrayList<XGroupItem> y_overlapping_xgroup_item_list = calcXGroupYExtentIntersection(
|
---|
1336 | xgroup_pivot_item, xgroup_item_list);
|
---|
1337 |
|
---|
1338 | // Step 2: Work out where the gaps are between the y-overlapping
|
---|
1339 | // boxes in the x-dimension
|
---|
1340 | ArrayList<DimensionExtent> x_text_gap_list = calcXGroupXGaps(
|
---|
1341 | xgroup_pivot_item, y_overlapping_xgroup_item_list);
|
---|
1342 |
|
---|
1343 | // Step 3: Remove any sequences of raw-text items that fall in the
|
---|
1344 | // gaps from YSpan, box them up, and reinsert
|
---|
1345 | boxMultipleXRawItemsInGaps(xgroup_pivot_item, x_text_gap_list);
|
---|
1346 |
|
---|
1347 | }
|
---|
1348 | }
|
---|
1349 |
|
---|
1350 | public void mapInXGroupItemsRecursive(List<XGroupItem> xgroup_item_list) {
|
---|
1351 |
|
---|
1352 | // Map in all the items in the given list:
|
---|
1353 | for (XGroupItem xgroup_item : xgroup_item_list) {
|
---|
1354 | if (!xgroup_item.isOriginalOutOfFlowItem()) {
|
---|
1355 |
|
---|
1356 | // See if any implicit boxing of raw-text items is needed
|
---|
1357 | if (doImplicitBoxing) {
|
---|
1358 | implicitBoxing(xgroup_item, xgroup_item_list);
|
---|
1359 | }
|
---|
1360 | mapInItem(xgroup_item); // Map in this x-group-item
|
---|
1361 | }
|
---|
1362 | }
|
---|
1363 |
|
---|
1364 | // Now recursively work through each item's nested x-groups
|
---|
1365 | for (XGroupItem xgroup_item : xgroup_item_list) {
|
---|
1366 |
|
---|
1367 | List<XGroupItem> nested_xgroup_item_list = xgroup_item
|
---|
1368 | .getGroupedItemList();
|
---|
1369 |
|
---|
1370 | if (nested_xgroup_item_list.size() > 0) {
|
---|
1371 | xgroup_item.mapInXGroupItemsRecursive(nested_xgroup_item_list);
|
---|
1372 | }
|
---|
1373 | }
|
---|
1374 | }
|
---|
1375 |
|
---|
1376 | public List<Item[]> getYXOverlappingItemListLines() {
|
---|
1377 | final List<XRawItem> yxOverlappingXRawItemList = getYXOverlappingXRawItemList(true);
|
---|
1378 | final List<Item[]> lines = new ArrayList<Item[]>();
|
---|
1379 | final List<XRawItem> currentLineXItem = new ArrayList<XRawItem>();
|
---|
1380 | for (final XRawItem xitem : yxOverlappingXRawItemList) {
|
---|
1381 | if (xitem.getItem() == GROUPSEP_START)
|
---|
1382 | continue;
|
---|
1383 | else if (xitem.getItem() == GROUPSEP_END) {
|
---|
1384 | if (currentLineXItem.isEmpty())
|
---|
1385 | continue;
|
---|
1386 | else {
|
---|
1387 | final List<Item> ln = new ArrayList<Item>();
|
---|
1388 | for (final XRawItem xrawitem : currentLineXItem)
|
---|
1389 | ln.add(xrawitem.getItem());
|
---|
1390 | lines.add(ln.toArray(new Item[] {}));
|
---|
1391 | currentLineXItem.clear();
|
---|
1392 | }
|
---|
1393 | } else {
|
---|
1394 | if (currentLineXItem.isEmpty())
|
---|
1395 | currentLineXItem.add(xitem);
|
---|
1396 | else {
|
---|
1397 | final YOverlappingItemsSpan[] itemSpanArray = xitem
|
---|
1398 | .getGroup().yitems_span_array;
|
---|
1399 | final int index = xitem.getBoundingYTop()
|
---|
1400 | - xitem.getGroup().getBoundingYTop();
|
---|
1401 | final YOverlappingItemsSpan itemSpan = itemSpanArray[index];
|
---|
1402 | final List<XItem> xOrderedList = itemSpan instanceof YOverlappingItemsTopEdge ? ((YOverlappingItemsTopEdge) itemSpan)
|
---|
1403 | .getXOrderedLine().getXItemList()
|
---|
1404 | : ((YOverlappingItemsShadow) itemSpan).getTopEdge()
|
---|
1405 | .getXOrderedLine().getXItemList();
|
---|
1406 | if (!xOrderedList.contains(currentLineXItem.get(0))) {
|
---|
1407 | if (!currentLineXItem.isEmpty()) {
|
---|
1408 | final List<Item> ln = new ArrayList<Item>();
|
---|
1409 | for (final XRawItem xrawitem : currentLineXItem)
|
---|
1410 | ln.add(xrawitem.getItem());
|
---|
1411 | lines.add(ln.toArray(new Item[] {}));
|
---|
1412 | currentLineXItem.clear();
|
---|
1413 | }
|
---|
1414 | currentLineXItem.add(xitem);
|
---|
1415 | } else {
|
---|
1416 | currentLineXItem.add(xitem);
|
---|
1417 | }
|
---|
1418 | }
|
---|
1419 | }
|
---|
1420 | }
|
---|
1421 | if (!currentLineXItem.isEmpty()) {
|
---|
1422 | final List<Item> ln = new ArrayList<Item>();
|
---|
1423 | for (final XRawItem xrawitem : currentLineXItem)
|
---|
1424 | ln.add(xrawitem.getItem());
|
---|
1425 | lines.add(ln.toArray(new Item[] {}));
|
---|
1426 | currentLineXItem.clear();
|
---|
1427 | }
|
---|
1428 | return lines;
|
---|
1429 | }
|
---|
1430 |
|
---|
1431 | public ArrayList<Item> getYXOverlappingItemList(boolean separateGroups) {
|
---|
1432 | final List<XRawItem> yxOverlappingXItemList = getYXOverlappingXRawItemList(separateGroups);
|
---|
1433 | final ArrayList<Item> yxOverlappingItemList = new ArrayList<Item>();
|
---|
1434 | for (final XRawItem xitem : yxOverlappingXItemList)
|
---|
1435 | yxOverlappingItemList.add(xitem.getItem());
|
---|
1436 | return yxOverlappingItemList;
|
---|
1437 | }
|
---|
1438 |
|
---|
1439 | public List<XRawItem> getYXOverlappingXRawItemList(
|
---|
1440 | final boolean separateGroups) {
|
---|
1441 | ArrayList<XRawItem> overlapping_y_ordered_items = new ArrayList<XRawItem>();
|
---|
1442 |
|
---|
1443 | for (int y = 0; y < y_span_height; y++) {
|
---|
1444 |
|
---|
1445 | YOverlappingItemsSpan item_span = yitems_span_array[y];
|
---|
1446 |
|
---|
1447 | if (item_span != null) {
|
---|
1448 |
|
---|
1449 | if (item_span instanceof YOverlappingItemsTopEdge) {
|
---|
1450 |
|
---|
1451 | YOverlappingItemsTopEdge item_span_top_edge = (YOverlappingItemsTopEdge) item_span;
|
---|
1452 | XOrderedLine xitem_line = item_span_top_edge
|
---|
1453 | .getXOrderedLine();
|
---|
1454 |
|
---|
1455 | for (XItem xspan : xitem_line.getXItemList()) {
|
---|
1456 | if (xspan instanceof XRawItem) {
|
---|
1457 | XRawItem xitem_span = (XRawItem) xspan;
|
---|
1458 | // Item item = xitem_span.getItem();
|
---|
1459 | //
|
---|
1460 | // overlapping_y_ordered_items.add(item);
|
---|
1461 | overlapping_y_ordered_items.add(xitem_span);
|
---|
1462 | } else {
|
---|
1463 | // Must be an XGroupItem => recursive call on xspan
|
---|
1464 | // item
|
---|
1465 |
|
---|
1466 | XGroupItem nested_group_item = (XGroupItem) xspan;
|
---|
1467 | List<XRawItem> nested_overlapping_items = nested_group_item
|
---|
1468 | .getYXOverlappingXRawItemList(separateGroups);
|
---|
1469 | if (separateGroups) {
|
---|
1470 | overlapping_y_ordered_items.add(new XRawItem(
|
---|
1471 | GROUPSEP_START, this));
|
---|
1472 | }
|
---|
1473 | overlapping_y_ordered_items
|
---|
1474 | .addAll(nested_overlapping_items);
|
---|
1475 | if (separateGroups) {
|
---|
1476 | overlapping_y_ordered_items.add(new XRawItem(
|
---|
1477 | GROUPSEP_END, this));
|
---|
1478 | }
|
---|
1479 | }
|
---|
1480 | }
|
---|
1481 | }
|
---|
1482 | }
|
---|
1483 | }
|
---|
1484 |
|
---|
1485 | return overlapping_y_ordered_items;
|
---|
1486 | }
|
---|
1487 |
|
---|
1488 | public ArrayList<Item> getYXOverlappingItemList() {
|
---|
1489 | return getYXOverlappingItemList(false);
|
---|
1490 | }
|
---|
1491 |
|
---|
1492 | public String toString() {
|
---|
1493 | return "XGroupItem";
|
---|
1494 | }
|
---|
1495 | }
|
---|