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

Last change on this file since 904 was 904, checked in by davidb, 10 years ago

Some error checks added in to array bound access in y-span

  • Property svn:executable set to *
File size: 27.7 KB
Line 
1package org.expeditee.io.flowlayout;
2
3import java.awt.Point;
4import java.awt.Polygon;
5import java.awt.Rectangle;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.Collections;
9import java.util.Comparator;
10import java.util.HashSet;
11import java.util.Iterator;
12import java.util.LinkedList;
13import java.util.List;
14
15import org.expeditee.gui.Frame;
16import org.expeditee.gui.FrameUtils;
17import org.expeditee.items.Item;
18import org.expeditee.items.Line;
19import org.expeditee.items.Text;
20
21
22public class XGroupItem extends XItem
23{
24 enum FlowType { in_flow, out_of_flow_original, out_of_flow_faked_position };
25
26 class MultiArrowHeadComparable implements Comparator<Item>{
27
28 @Override
29 public int compare(Item o1, Item o2)
30 {
31 int o1y = o1.getY();
32 int o2y = o2.getY();
33
34 int order = 0; // default to assume they are identical
35
36 if (o1y<o2y) {
37 order = -1; // done, o1 is higher up the frame than o2 => our definition of 'before'
38 }
39 else if (o2y<o1y) {
40 order = +1; // also done, o1 is lower down the frame than o2 => our definition of 'after'
41
42 }
43 else {
44 // have identical 'y' values, so look to 'x' to tie-break
45
46 int o1x = o1.getX();
47 int o2x = o2.getX();
48
49 if (o1x<o2x) {
50 order = -1; // done, o1 is to the left of o2 => our definition of 'before'
51 }
52 else if (o2x<o1x) {
53 order = +1; // also done, o1 is to the right of o2 => our definition of 'after'
54 }
55 }
56
57 return order;
58 }
59 }
60
61 Frame frame;
62
63 protected int y_span_height;
64 protected YOverlappingItemsSpan[] yitems_span_array;
65
66 List<Text> raw_text_item_list;
67 List<XGroupItem> grouped_item_list;
68 List<Item> remaining_item_list;
69
70 FlowType out_of_flow;
71
72 protected XGroupItem()
73 {
74 frame = null;
75
76 y_span_height = 0;
77 yitems_span_array = null;
78
79 raw_text_item_list = null;
80 grouped_item_list = null;
81 remaining_item_list = null;
82
83 out_of_flow = FlowType.in_flow;
84 }
85
86 public XGroupItem(Frame frame, List<Item> y_ordered_items, Polygon enclosing_polygon)
87 {
88 this.frame = frame;
89 this.out_of_flow = FlowType.in_flow;;
90
91 if (enclosing_polygon == null) {
92 // e.g. when the top-level case, with no enclosing polygon at this stage
93 // => generate one based on the bounding box of the y_ordered_items
94 enclosing_polygon = DimensionExtent.boundingBoxPolygon(y_ordered_items);
95 }
96
97 Rectangle enclosing_bounding_rect = enclosing_polygon.getBounds();
98 initSpanArray(enclosing_bounding_rect);
99
100 // Step 1: Separate out the raw-text-items, the grouped-items and 'remaining' (e.g. points and lines)
101
102 raw_text_item_list = new ArrayList<Text>();
103 grouped_item_list = new ArrayList<XGroupItem>();
104 remaining_item_list = new ArrayList<Item>();
105
106 separateYOverlappingItems(frame, y_ordered_items, enclosing_polygon, raw_text_item_list, grouped_item_list, remaining_item_list);
107
108
109 // Step 2: Add in the raw-text items
110 for (Text text_item : raw_text_item_list) {
111
112 XRawItem x_raw_item = new XRawItem(text_item);
113 mapInItem(x_raw_item);
114 }
115
116 }
117
118 public XGroupItem(Frame frame, List<Item> y_ordered_items)
119 {
120 this(frame,y_ordered_items,null);
121 }
122
123 protected XGroupItem(XGroupItem imprint, Rectangle copy_to_bounding_rect)
124 {
125 super();
126
127 // Implement a shallow copy => share references to frame, and item list, and y-span
128 // Only the bounding box is changed => set to the 'copy_to_bounding_rect' passed in
129
130 this.frame = imprint.frame;
131
132 y_span_height = imprint.y_span_height;
133 yitems_span_array = imprint.yitems_span_array;
134
135 this.raw_text_item_list = imprint.raw_text_item_list;
136 this.grouped_item_list = imprint.grouped_item_list;
137
138// int offX = imprint.getBoundingRect().x - copy_to_bounding_rect.x;
139// int offY = imprint.getBoundingRect().y - copy_to_bounding_rect.y;
140// this.grouped_item_list = new ArrayList<XGroupItem>();
141// for(XGroupItem newChild : imprint.grouped_item_list) {
142// Rectangle newRect = newChild.getBoundingRect();
143// newRect.x -= offX;
144// newRect.y -= offY;
145// this.grouped_item_list.add(new XGroupItem(newChild, newRect));
146// }
147 this.remaining_item_list = imprint.remaining_item_list;
148
149 this.out_of_flow = imprint.out_of_flow; // Or perhaps set it to FlowType.out_of_flow_fake_position straight away?
150
151 this.bounding_rect = new Rectangle(copy_to_bounding_rect); // deep copy to be on the safe side
152 }
153
154
155 protected void initSpanArray(Rectangle bounding_rect)
156 {
157 this.bounding_rect = bounding_rect;
158
159 int y_top = getBoundingYTop();
160 int y_bot = getBoundingYBot();
161
162 if (y_top<=y_bot) {
163 // => have non-trivial span
164
165 y_span_height = (y_bot - y_top) + 2; // Polygon in Java excludes right and bottom sides so need extra "+1" in calc
166
167 yitems_span_array = new YOverlappingItemsSpan[y_span_height];
168 }
169 else {
170 y_span_height = 0;
171 yitems_span_array = null;
172 }
173 }
174
175 public YOverlappingItemsSpan getSpanItemAt(int index) {
176 return yitems_span_array[index];
177 }
178
179 public void setOutOfFlow(FlowType flow_type)
180 {
181 out_of_flow = flow_type;
182 }
183
184 public FlowType getFlowItem()
185 {
186 return out_of_flow;
187 }
188
189 public boolean isOriginalOutOfFlowItem()
190 {
191 return out_of_flow == FlowType.out_of_flow_original;
192 }
193
194 public boolean isFakedOutOfFlowItem()
195 {
196 return out_of_flow == FlowType.out_of_flow_faked_position;
197 }
198
199
200 public List<Text> getRawTextItemList()
201 {
202 return raw_text_item_list;
203 }
204
205 public List<XGroupItem> getGroupedItemList()
206 {
207 return grouped_item_list;
208 }
209
210 public List<Item> getRemainingItemList()
211 {
212 return remaining_item_list;
213 }
214
215 public YOverlappingItemsSpan getYOverlappingItemsSpan(int y)
216 {
217 int y_index = y - getBoundingYTop();
218
219 if ((y_index<0) || (y_index>=yitems_span_array.length)) {
220 int y_top = getBoundingYTop();
221 int y_bot = y_top + yitems_span_array.length -1;
222 System.err.println("Error in getYOverlappingItemsSpan(): index out of bounds for value " + y);
223 System.err.println(" => Operation mapped into local array: y-top=" + y_top + ", y-bot=" + y_bot);
224 return null;
225 }
226 return yitems_span_array[y_index];
227 }
228
229 public void setYOverlappingItemsSpan(int y,YOverlappingItemsSpan yitems_span)
230 {
231 int y_index = y - getBoundingYTop();
232
233 if ((y_index<0) || (y_index>=yitems_span_array.length)) {
234 int y_top = getBoundingYTop();
235 int y_bot = y_top + yitems_span_array.length -1;
236 System.err.println("Error in setYOverlappingItemsSpan(): index out of bounds for value " + y);
237 System.err.println(" => Operation mapped into local array: y-top=" + y_top + ", y-bot=" + y_bot);
238 return;
239 }
240 yitems_span_array[y_index] = yitems_span;
241 }
242
243
244 protected List<Item> followLinesToArrowHeads(Collection<Item> visited, Item anchor_item, List<Line>used_in_lines)
245 {
246 List<Item> arrow_head_endpoints = new ArrayList<Item>();
247
248 for (Line line: used_in_lines) {
249
250 Item start_item = line.getStartItem();
251
252 if (start_item == anchor_item) {
253 // the line we're considering is heading in the right direction
254
255 Item end_item = line.getEndItem();
256
257 if (!visited.contains(end_item)) {
258 // Needs processing
259 visited.add(end_item);
260
261 List<Line> follow_lines = end_item.getLines();
262
263 if (follow_lines.size()==1) {
264 // reached an end-point
265 if (end_item.hasVisibleArrow()) {
266 arrow_head_endpoints.add(end_item);
267 }
268 }
269 else if (follow_lines.size()>1) {
270
271 List<Item> followed_arrow_heads = followLinesToArrowHeads(visited,end_item,follow_lines);
272 arrow_head_endpoints.addAll(followed_arrow_heads);
273 }
274 }
275 }
276
277 }
278 return arrow_head_endpoints;
279 }
280
281
282 protected XGroupItem innermostXGroup(Item arrow_head, List<XGroupItem> grouped_item_list)
283 {
284 XGroupItem innermost_item = null;
285
286 for (XGroupItem xgroup_item: grouped_item_list) {
287 if (xgroup_item.containsItem(arrow_head)) {
288
289 innermost_item = xgroup_item;
290
291 // Now see if it is in any of the nested ones?
292
293 List<XGroupItem> nested_group_item_list = xgroup_item.grouped_item_list;
294
295 XGroupItem potentially_better_item = innermostXGroup(arrow_head,nested_group_item_list);
296
297 if (potentially_better_item != null) {
298 innermost_item = potentially_better_item;
299 }
300 break;
301 }
302
303 }
304
305 return innermost_item;
306 }
307
308 protected void removeXGroupItem(XGroupItem remove_item, List<XGroupItem> grouped_item_list)
309 {
310
311 for (XGroupItem xgroup_item: grouped_item_list) {
312
313 if (xgroup_item == remove_item) {
314 grouped_item_list.remove(xgroup_item);
315 return;
316 }
317 else {
318 List<XGroupItem> nested_group_item_list = xgroup_item.grouped_item_list;
319 removeXGroupItem(remove_item,nested_group_item_list);
320
321 }
322 }
323 }
324
325
326
327 protected void repositionOutOfFlowGroupsFollowLine(XGroupItem toplevel_xgroup, Item remaining_item, Collection<XGroupItem> out_of_flow)
328 {
329 // See if this item is the start of a line
330 // Follow it if it is
331 // For each end of the line (potentially a multi-split poly-line) that has an arrow head:
332 // See if the arrow-head falls inside an XGroup area
333 // => Ignore endings that are in the same XGroupItem as the line started in
334
335 List<Line> used_in_lines = remaining_item.getLines();
336 if (used_in_lines.size()==1) {
337 // at the start (or end) of a line
338
339 Item start_item = used_in_lines.get(0).getStartItem();
340
341 if (remaining_item == start_item) {
342
343 // found the start of a line
344
345 Collection<Item> visited = new HashSet<Item>();
346 visited.add(remaining_item);
347
348 List<Item> arrow_head_endpoints = followLinesToArrowHeads(visited,remaining_item,used_in_lines);
349
350 //System.out.println("**** For Xgroup " + this + " with dot starting at " + remaining_item + " has " + arrow_head_endpoints.size() + " arrow head endpoints");
351
352 Collections.sort(arrow_head_endpoints, new MultiArrowHeadComparable());
353
354 for (Item arrow_head: arrow_head_endpoints) {
355 // find the inner-most group it falls within
356
357 List<XGroupItem> toplevel_grouped_item_list = toplevel_xgroup.getGroupedItemList();
358
359 XGroupItem xgroup_item = innermostXGroup(arrow_head,toplevel_grouped_item_list);
360
361 if (xgroup_item != null) {
362
363 // Ignore if the found 'xgroup_item' is 'this'
364 // (i.e. the found arrow head is at the same XGroupItem level we are currently processing)
365
366 if (xgroup_item != this) {
367
368 out_of_flow.add(xgroup_item);
369 // Can't delete here, as it causes a concurrent exception => add to 'out_of_flow' hashmap and perform removal later
370
371 //System.out.println("**** innermost XGroupItem = " + xgroup_item);
372
373 // Artificially rework its (x,y) org and dimension to make it appear where the start of the arrow is
374
375 Rectangle start_rect = start_item.getArea().getBounds();
376
377
378 XGroupItem xgroup_item_shallow_copy = new XGroupItem(xgroup_item,start_rect);
379
380 // Perhaps the following two lines should be moved to inside the constructor??
381
382 xgroup_item.setOutOfFlow(FlowType.out_of_flow_original);
383 xgroup_item_shallow_copy.setOutOfFlow(FlowType.out_of_flow_faked_position);
384
385
386 //xgroup_item.setBoundingRect(start_rect);
387 //xgroup_item.setOutOfFlow();
388
389 // Finally add it in
390 //mapInItem(xgroup_item);
391 mapInItem(xgroup_item_shallow_copy);
392 }
393 }
394 }
395 }
396
397 }
398 }
399
400 /**
401 * Look for any 'out-of-flow' XGroup boxes, signalled by the user drawing an arrow line to it
402 * => force an artificial change to such boxes so its dimensions become point becomes the starting point
403 * of the arrow line. This will make it sort to the desired spot in the YOverlapping span
404 */
405
406 public void repositionOutOfFlowGroupsRecursive(XGroupItem toplevel_xgroup, Collection<XGroupItem> out_of_flow)
407 {
408 // Map in all the items in the given list:
409 for (Item remaining_item: remaining_item_list) {
410
411 repositionOutOfFlowGroupsFollowLine(toplevel_xgroup,remaining_item,out_of_flow);
412 }
413
414 // Now recursively work through each item's nested x-groups
415 for (XGroupItem xgroup_item: grouped_item_list) {
416
417 if (!out_of_flow.contains(xgroup_item)) {
418 xgroup_item.repositionOutOfFlowGroupsRecursive(toplevel_xgroup,out_of_flow);
419 }
420 }
421 }
422
423
424 public void repositionOutOfFlowGroups(XGroupItem toplevel_xgroup)
425 {
426 Collection<XGroupItem> out_of_flow = new HashSet<XGroupItem>();
427
428 repositionOutOfFlowGroupsRecursive(toplevel_xgroup,out_of_flow);
429
430 List<XGroupItem >toplevel_grouped_item_list = toplevel_xgroup.getGroupedItemList();
431
432 // ****
433
434 // Want to remove the original "out-of-position" blocks that were found, as (for each arrow
435 // point to such an original) shallow-copies now exist with 'faked' positions that correspond
436 // to where the arrows start.
437
438
439 // No longer remove them from the nested grouped structure, rather, rely on these items being
440 // tagged "isOutOfFLow()" to be skipped by subsequent traversal calls (e.g., to generate
441 // the normal "in-flow" nested blocks)
442
443 // Structuring things this way, means that it is now possible to have multiple arrows
444 // pointing to the same block of code, and both "out-of-flow" arrows will be honoured,
445 // replicating the code at their respective start points
446
447 /*
448 for (XGroupItem xgroup_item: out_of_flow) {
449 removeXGroupItem(xgroup_item,toplevel_grouped_item_list);
450 }
451
452 */
453
454 }
455
456 /**
457 * Focusing only on enclosures that fit within the given polygon *and* have this item
458 * in it, the method finds the largest of these enclosures and
459 * returns all the items it contains
460 *
461 * @return
462 */
463 public Collection<Item> getItemsInNestedEnclosure(Item given_item, AreaPolygon outer_polygon)
464 {
465 Collection<Item> sameEnclosure = null;
466 Collection<Item> seen = new HashSet<Item>();
467
468 double enclosureArea = 0.0;
469
470 for (Item i : frame.getItems()) {
471
472 // Go through all the enclosures ...
473
474 if (!seen.contains(i) && i.isEnclosed()) {
475
476 Collection<Item> i_enclosing_dots_coll = i.getEnclosingDots();
477 List<Item> i_enclosing_dots = new ArrayList<Item>(i_enclosing_dots_coll); // Change to List of items
478
479 seen.addAll(i_enclosing_dots);
480
481 Polygon i_polygon = new Polygon();
482 for (int di=0; di<i_enclosing_dots.size(); di++) {
483 Item d = i_enclosing_dots.get(di);
484
485 i_polygon.addPoint(d.getX(), d.getY());
486 }
487
488 // ... looking for one that is completely contained in the 'outer_polygon' and ...
489 if (outer_polygon.completelyContains(i_polygon)) {
490
491 Collection<Item> enclosed = i.getEnclosedItems();
492
493 // ... includes this item
494 if (enclosed.contains(given_item)) {
495
496 // Remember it if it is larger than anything we've encountered before
497 if ((i.getEnclosedArea() > enclosureArea)) {
498 sameEnclosure = enclosed;
499 }
500 }
501 }
502
503 }
504 }
505
506 if (sameEnclosure == null)
507 return new LinkedList<Item>();
508
509 return sameEnclosure;
510 }
511
512
513 public void separateYOverlappingItems(Frame frame, List<Item> item_list, Polygon enclosing_polygon,
514 List<Text> raw_text_item_list, List<XGroupItem> grouped_item_list, List<Item> remaining_item_list)
515 {
516 // raw_text_items_list => all the non-enclosed text items
517 // grouped_items_list => list of all enclosures containing text items
518 // remaining_item_list => whatever is left: mostly dots that form part of lines/polylines/polygons
519
520 AreaPolygon area_enclosing_polygon = new AreaPolygon(enclosing_polygon);
521
522 List<AreaPolygon> area_enclosed_polygon_list = new ArrayList<AreaPolygon>();
523
524 while (item_list.size()>0) {
525 Item item = item_list.remove(0); // shift
526
527 if (item instanceof Text) {
528 Text text = (Text)item;
529
530 Collection<Item> items_in_nested_enclosure_coll = getItemsInNestedEnclosure(text,area_enclosing_polygon);
531 List<Item> items_in_nested_enclosure = new ArrayList<Item>(items_in_nested_enclosure_coll); // Change to handling it as a list
532
533 if (items_in_nested_enclosure.size()==0) {
534
535 raw_text_item_list.add(text);
536 }
537 else {
538
539 // Something other than just this text-item is around
540
541 while (items_in_nested_enclosure.size()>0) {
542
543 Item enclosure_item = items_in_nested_enclosure.remove(0); // shift
544
545 Polygon enclosed_polygon = enclosure_item.getEnclosedShape();
546
547 if (enclosed_polygon!=null) {
548 // Got a group
549 // => Remove any items-in-nested-enclosure from:
550 //
551 // 'item_list' (so we don't waste our time discovering and processing again these related items)
552 // and
553 // 'raw_text_item_list' and 'remaining_item_lst' (as we now know they are part of the nested enclosed group)
554
555 AreaPolygon area_enclosed_polygon = new AreaPolygon(enclosed_polygon);
556 area_enclosed_polygon_list.add(area_enclosed_polygon);
557
558 item_list.remove(enclosure_item);
559 item_list.removeAll(items_in_nested_enclosure);
560
561 raw_text_item_list.remove(enclosure_item);
562 raw_text_item_list.removeAll(items_in_nested_enclosure);
563
564 remaining_item_list.remove(enclosure_item);
565 remaining_item_list.removeAll(items_in_nested_enclosure);
566
567 // Now remove any of the just used enclosed-items if they formed part of the perimeter
568 List<Item> items_on_perimeter = new ArrayList<Item>();
569
570 Iterator<Item> item_iterator = items_in_nested_enclosure.iterator();
571 while (item_iterator.hasNext()) {
572 Item item_to_check = item_iterator.next();
573 Point pt_to_check = new Point(item_to_check.getX(),item_to_check.getY());
574
575 if (area_enclosed_polygon.isPerimieterPoint(pt_to_check)) {
576 items_on_perimeter.add(item_to_check);
577 }
578 }
579
580 items_in_nested_enclosure.removeAll(items_on_perimeter);
581 }
582
583 else {
584 // Text or single point (with no enclosure)
585
586 // This item doesn't feature at this level
587 // => will be subsequently capture by the group polygon below
588 // and processed by the recursive call
589
590 item_list.remove(enclosure_item);
591 }
592 }
593
594 }
595
596 } // end of Text test
597 else {
598 // non-text item => add to remaining_item_list
599 remaining_item_list.add(item);
600 }
601
602 }
603
604 // Sort areas, smallest to largest
605 Collections.sort(area_enclosed_polygon_list, new Comparator<AreaPolygon>() {
606
607 public int compare(AreaPolygon ap1, AreaPolygon ap2) {
608 Double ap1_area = ap1.getArea();
609 Double ap2_area = ap2.getArea();
610
611 return ap2_area.compareTo(ap1_area);
612 }
613 });
614
615 // Remove any enclosed polygon areas that are completely contained in a larger one
616
617 for (int ri=0; ri<area_enclosed_polygon_list.size(); ri++) {
618 // ri = remove index pos
619
620 AreaPolygon rpoly = area_enclosed_polygon_list.get(ri);
621
622 for (int ci=ri+1; ci<area_enclosed_polygon_list.size(); ci++) {
623 // ci = check index pos
624 AreaPolygon cpoly = area_enclosed_polygon_list.get(ci);
625 if (rpoly.completelyContains(cpoly)) {
626 area_enclosed_polygon_list.remove(ci);
627 ri--; // to offset to the outside loop increment
628 break;
629 }
630 }
631 }
632
633
634 // By this point, guaranteed that the remaining polygons are the largest ones
635 // that capture groups of items
636 //
637 // There may be sub-groupings within them, which is the reason for the recursive call below
638
639
640 for (AreaPolygon area_polygon: area_enclosed_polygon_list) {
641
642 Collection<Item> enclosed_items = FrameUtils.getItemsEnclosedBy(frame, area_polygon);
643 List<Item> enclosed_item_list = new ArrayList<Item>(enclosed_items);
644
645 int i=0;
646 while (i<enclosed_item_list.size()) {
647 Item enclosed_item = enclosed_item_list.get(i);
648
649 // Filter out enclosed-items points that are part of the polygon's perimeter
650 if (area_polygon.isPerimieterPoint(enclosed_item.getPosition())) {
651 enclosed_item_list.remove(i);
652 }
653 else {
654 i++;
655 }
656 }
657
658 // Recursively work on the identified sub-group
659
660 XGroupItem xgroup_item = new XGroupItem(frame, enclosed_item_list, area_polygon);
661
662 grouped_item_list.add(xgroup_item);
663 }
664 }
665
666
667
668 protected void castShadowIntoEmptySpace(YOverlappingItemsTopEdge y_item_span_top_edge, int yt, int yb)
669 {
670 // Assumes that all entries cast into are currently null
671 // => Use the more general castShadow() below, if this is not guaranteed to be the case
672
673 YOverlappingItemsShadow y_span_shadow = new YOverlappingItemsShadow(y_item_span_top_edge);
674
675 for (int y=yt; y<=yb; y++) {
676 setYOverlappingItemsSpan(y,y_span_shadow);
677 }
678 }
679
680 protected void castShadow(YOverlappingItemsTopEdge y_item_span_top_edge, int yt, int yb)
681 {
682 // Cast shadows only in places where there are currently no shadow entries
683
684 YOverlappingItemsShadow y_span_shadow = new YOverlappingItemsShadow(y_item_span_top_edge);
685
686 int y=yt;
687
688 while (y<=yb) {
689 YOverlappingItemsSpan y_item_span = getYOverlappingItemsSpan(y);
690
691 if (y_item_span==null) {
692 setYOverlappingItemsSpan(y,y_span_shadow);
693 y++;
694 }
695 else if (y_item_span instanceof YOverlappingItemsTopEdge) {
696 // Our shadow has run into another top-edged zone
697 // => need to extend the shadow to include this zone as well
698 // (making this encountered top-edge obsolete
699
700 // Merge the newly encountered top-line in with the current one
701 // and change all its shadow references to our (dominant) one
702
703 XOrderedLine dominant_x_line = y_item_span_top_edge.getXOrderedLine();
704
705 YOverlappingItemsTopEdge obsolete_top_edge = (YOverlappingItemsTopEdge)y_item_span;
706 XOrderedLine obsolete_x_line = obsolete_top_edge.getXOrderedLine();
707
708 for (XItem xitem: obsolete_x_line.getXItemList()) {
709
710 dominant_x_line.orderedMergeItem(xitem);
711 }
712
713 while (y_item_span != null) {
714
715 setYOverlappingItemsSpan(y,y_span_shadow);
716
717 y++;
718
719 if (y<=yb) {
720 y_item_span = getYOverlappingItemsSpan(y);
721 }
722 else {
723 y_item_span = null;
724 }
725 }
726 }
727 else {
728 y++;
729 }
730 }
731 }
732
733 public void mapInItem(XItem xitem)
734 {
735
736 int yt = xitem.getBoundingYTop();
737 int yb = xitem.getBoundingYBot();
738
739 // Merge into 'items_span' checking for overlaps (based on xitem's y-span)
740
741 boolean merged_item = false;
742 for (int y=yt; y<=yb; y++) {
743
744 YOverlappingItemsSpan item_span = getYOverlappingItemsSpan(y);
745
746 if (item_span != null) {
747
748 if (item_span instanceof YOverlappingItemsTopEdge) {
749
750 // Hit a top edge of an existing item
751
752 // Need to:
753 // 1. *Required* Insert new item into current x-ordered line
754 // 2. *Conditionally* Move entire x-ordered line up to higher 'y' top edge
755 // 3. *Required* Cast shadow for new item
756
757 // Note for Step 2:
758 // i) No need to do the move if y == yt
759 // (the case when the new top edge is exactly the same height as the existing one)
760 // ii) If moving top-edge (the case when y > yt) then no need to recalculate the top edge for existing shadows, as
761 // this 'connection' is stored as a reference
762
763
764 // Step 1: insert into existing top-edge
765
766 YOverlappingItemsTopEdge y_item_span_top_edge = (YOverlappingItemsTopEdge)item_span;
767 XOrderedLine xitem_span = y_item_span_top_edge.getXOrderedLine();
768
769 xitem_span.orderedMergeItem(xitem.getBoundingXLeft(),xitem);
770
771 // Step 2: if our new top-edge is higher than the existing one, then need to move existing top-edge
772 if (y>yt) {
773
774 // Move to new position
775 setYOverlappingItemsSpan(yt, y_item_span_top_edge);
776
777 // Old position needs to become a shadow reference
778 YOverlappingItemsShadow y_span_shadow = new YOverlappingItemsShadow(y_item_span_top_edge);
779 setYOverlappingItemsSpan(y,y_span_shadow);
780 }
781
782
783 // Step 3: Cast shadow
784 castShadow(y_item_span_top_edge, yt+1, yb);
785
786 }
787 else {
788 // Top edge to our new item has hit a shadow entry (straight off)
789 // => Look up what the shadow references, and then add in to that
790
791 // Effectively after the shadow reference lookup this is the same
792 // as the above, with the need to worry about Step 2 (as no move is needed)
793
794 YOverlappingItemsShadow y_item_span_shadow = (YOverlappingItemsShadow)item_span;
795 YOverlappingItemsTopEdge y_item_span_top_edge = y_item_span_shadow.getTopEdge();
796
797 XOrderedLine xitem_span = y_item_span_top_edge.getXOrderedLine();
798
799 // merge with this item list, preserving x ordering
800
801 xitem_span.orderedMergeItem(xitem.getBoundingXLeft(),xitem);
802
803 // Now Cast shadow
804 castShadow(y_item_span_top_edge, yt+1, yb);
805 }
806
807 merged_item = true;
808 break;
809
810 }
811
812 }
813
814 if (!merged_item) {
815 // xitem didn't intersect with any existing y-spans
816 // => simple case for add (i.e. all entries affected by map are currently null)
817
818 // Start up a new x-ordered-line (consisting of only 'xitem'), add in top edge and cast shadow
819
820 XOrderedLine xitem_line = new XOrderedLine(xitem);
821
822 YOverlappingItemsTopEdge y_item_span_top_edge = new YOverlappingItemsTopEdge(xitem_line);
823 setYOverlappingItemsSpan(yt,y_item_span_top_edge);
824
825 castShadowIntoEmptySpace(y_item_span_top_edge, yt+1, yb);
826
827 }
828
829
830 }
831
832 public void mapInXGroupItemsRecursive(List<XGroupItem> xgroup_item_list)
833 {
834
835 // Map in all the items in the given list:
836 for (XGroupItem xgroup_item: xgroup_item_list) {
837 if (!xgroup_item.isOriginalOutOfFlowItem()) {
838 mapInItem(xgroup_item); // Map in this x-group-item
839 }
840 }
841
842 // Now recursively work through each item's nested x-groups
843 for (XGroupItem xgroup_item: xgroup_item_list) {
844
845 if (!xgroup_item.isOriginalOutOfFlowItem()) {
846 List<XGroupItem> nested_xgroup_item_list = xgroup_item.getGroupedItemList();
847
848 if (nested_xgroup_item_list.size() >0) {
849 xgroup_item.mapInXGroupItemsRecursive(nested_xgroup_item_list);
850 }
851 }
852 }
853 }
854
855 public ArrayList<Item> getYXOverlappingItemList(boolean separateGroups)
856 {
857 ArrayList<Item> overlapping_y_ordered_items = new ArrayList<Item>();
858
859 for (int y=0; y<y_span_height; y++) {
860
861 YOverlappingItemsSpan item_span = yitems_span_array[y];
862
863 if (item_span != null) {
864
865 if (item_span instanceof YOverlappingItemsTopEdge) {
866
867 YOverlappingItemsTopEdge item_span_top_edge = (YOverlappingItemsTopEdge)item_span;
868 XOrderedLine xitem_line = item_span_top_edge.getXOrderedLine();
869
870 for (XItem xspan : xitem_line.getXItemList()) {
871 if (xspan instanceof XRawItem) {
872 XRawItem xitem_span = (XRawItem)xspan;
873 Item item = xitem_span.getItem();
874
875 overlapping_y_ordered_items.add(item);
876 }
877 else {
878 // Must be an XGroupItem => recursive call on xspan item
879
880 XGroupItem nested_group_item = (XGroupItem)xspan;
881 ArrayList<Item> nested_overlapping_items = nested_group_item.getYXOverlappingItemList(separateGroups);
882 if(separateGroups) {
883 overlapping_y_ordered_items.add(new Text("{"));
884 }
885 overlapping_y_ordered_items.addAll(nested_overlapping_items);
886 if(separateGroups) {
887 overlapping_y_ordered_items.add(new Text("}"));
888 }
889 }
890 }
891 }
892 }
893 }
894
895 return overlapping_y_ordered_items;
896 }
897
898 public ArrayList<Item> getYXOverlappingItemList() {
899 return getYXOverlappingItemList(false);
900 }
901}
902
Note: See TracBrowser for help on using the repository browser.