source: trunk/src/org/expeditee/io/flowlayout/XGroupItem.java@ 969

Last change on this file since 969 was 969, checked in by bln4, 9 years ago

Added a rule to deal with the combination of daisy chaining and multiple arrows heading into one box.

The issue: with multiple arrows going into the same box we have the possibility of creating infinite loops.

The ideal solution: only follow the second arrow into a box if we can detect that no loop occurs.

The current solution: we only follow the second arrow into a box if that box does not have a exiting arrow.

What this means:
(+) We can have multiple arrows going into a single box safely
(+) Daisy chaining cannot cause a infinite loop
(-) We cannot repeat a daisy chained set of boxes. In other words; if we have a series of boxes connected by arrows; even if they don't form a loop; only one arrow going into the first of the boxes is followed; others are ignored.

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