aboutsummaryrefslogtreecommitdiffstats
path: root/src/org/apache/fop/area/AreaTree.java
blob: de097f7feb789d5762f6abc9b20d629eac2ac8f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
/*
 * $Id$
 * Copyright (C) 2002 The Apache Software Foundation. All rights reserved.
 * For details on use and redistribution please refer to the
 * LICENSE file included with these sources.
 */

package org.apache.fop.area;

import org.apache.fop.render.Renderer;

import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

/**
 * Area tree for formatting objects.
 *
 * Concepts:
 * The area tree is to be as small as possible. With minimal classes
 * and data to fully represent an area tree for formatting objects.
 * The area tree needs to be simple to render and follow the spec
 * closely.
 * This area tree has the concept of page sequences.
 * Where ever possible information is discarded or optimised to
 * keep memory use low. The data is also organised to make it
 * possible for renderers to minimise their output.
 * A page can be saved if not fully resolved and once rendered
 * a page contains only size and id reference information.
 * The area tree pages are organised in a model that depends on the
 * type of renderer.
 */
public class AreaTree {
    // allows for different models to deal with adding/rendering
    // in different situations
    private AreaTreeModel model;

    // hashmap of arraylists containing pages with id area
    private HashMap idLocations = new HashMap();
    // list of id's yet to be resolved and arraylists of pages
    private HashMap resolve = new HashMap();
    private ArrayList treeExtensions = new ArrayList();

    /**
     * Create a render pages area tree model.
     * @param rend the renderer that will be used
     * @return RenderPagesModel the new area tree model
     */
    public static RenderPagesModel createRenderPagesModel(Renderer rend) {
        return new RenderPagesModel(rend);
    }

    /**
     * Create a new store pages model.
     * @return StorePagesModel the new model
     */
    public static StorePagesModel createStorePagesModel() {
        return new StorePagesModel();
    }

    /**
     * Set the tree model to use for this area tree.
     * The different models can have different behaviour
     * when pages area added and other changes.
     * @param m the area tree model
     */
    public void setTreeModel(AreaTreeModel m) {
        model = m;
    }

    /**
     * Start a new page sequence.
     * This signals that a new page sequence has started in the document.
     * @param title the title of the new page sequence or null if no title
     */
    public void startPageSequence(Title title) {
        model.startPageSequence(title);
    }

    /**
     * Add a new page to the area tree.
     * @param page the page to add
     */
    public void addPage(PageViewport page) {
        model.addPage(page);
    }

    /**
     * Add an id reference pointing to a page viewport.
     * @param id the id of the reference
     * @param pv the page viewport that contains the id reference
     */
    public void addIDRef(String id, PageViewport pv) {
        List list = (List)idLocations.get(id);
        if (list == null) {
            list = new ArrayList();
            idLocations.put(id, list);
        }
        list.add(pv);

        HashSet todo = (HashSet)resolve.get(id);
        if (todo != null) {
            for (Iterator iter = todo.iterator(); iter.hasNext();) {
                Resolveable res = (Resolveable)iter.next();
                res.resolve(id, list);
            }
            resolve.remove(id);
        }
    }

    /**
     * Get the list of id references for an id.
     * @param id the id to lookup
     * @return the list of id references.
     */
    public List getIDReferences(String id) {
        return (List)idLocations.get(id);
    }

    /**
     * Add an unresolved object with a given id.
     * @param id the id reference that needs resolving
     * @param res the Resolveable object to resolve
     */
    public void addUnresolvedID(String id, Resolveable res) {
        HashSet todo = (HashSet)resolve.get(id);
        if (todo == null) {
            todo = new HashSet();
            resolve.put(id, todo);
        }
        todo.add(res);
    }

    /**
     * Add a tree extension.
     * This checks if the extension is resolveable and attempts
     * to resolve or add the resolveable ids for later resolution.
     * @param ext the tree extension to add.
     */
    public void addTreeExtension(TreeExt ext) {
        treeExtensions.add(ext);
        if (ext.isResolveable()) {
            Resolveable res = (Resolveable)ext;
            String[] ids = res.getIDs();
            for (int count = 0; count < ids.length; count++) {
                if (idLocations.containsKey(ids[count])) {
                    res.resolve(ids[count], (ArrayList)idLocations.get(ids[count]));
                } else {
                    HashSet todo = (HashSet)resolve.get(ids[count]);
                    if (todo == null) {
                        todo = new HashSet();
                        resolve.put(ids[count], todo);
                    }
                    todo.add(ext);
                }
            }
        } else {
            handleTreeExtension(ext, TreeExt.IMMEDIATELY);
        }
    }

    /**
     * Handle a tree extension.
     * This sends the extension to the model for handling.
     * @param ext the tree extension to handle
     * @param when when the extension should be handled by the model
     */
    public void handleTreeExtension(TreeExt ext, int when) {
        // queue tree extension according to the when
        model.addExtension(ext, when);
    }

    /**
     * Signal end of document.
     * This indicates that the document is complete and any unresolved
     * reference can be dealt with.
     */
    public void endDocument() {
        for (Iterator iter = resolve.keySet().iterator(); iter.hasNext();) {
            String id = (String)iter.next();
            HashSet list = (HashSet)resolve.get(id);
            for (Iterator resIter = list.iterator(); resIter.hasNext();) {
                Resolveable res = (Resolveable)resIter.next();
                if (!res.isResolved()) {
                    res.resolve(id, null);
                }
            }
        }
        model.endDocument();
    }

    /**
     * This is the model for the area tree object.
     * The model implementation can handle the page sequence,
     * page and extensions.
     */
    public abstract static class AreaTreeModel {
        /**
         * Start a page sequence on this model.
         * @param title the title of the new page sequence
         */
        public abstract void startPageSequence(Title title);

        /**
         * Add a page to this moel.
         * @param page the page to add to the model.
         */
        public abstract void addPage(PageViewport page);

        /**
         * Add an extension to this model.
         * @param ext the extension to add
         * @param when when the extension should be handled
         */
        public abstract void addExtension(TreeExt ext, int when);

        /**
         * Signal the end of the document for any processing.
         */
        public abstract void endDocument();
    }

    /**
     * This class stores all the pages in the document
     * for interactive agents.
     * The pages are stored and can be retrieved in any order.
     */
    public static class StorePagesModel extends AreaTreeModel {
        private ArrayList pageSequence = null;
        private ArrayList titles = new ArrayList();
        private ArrayList currSequence;
        private ArrayList extensions = new ArrayList();

        /**
         * Create a new store pages model
         */
        public StorePagesModel() {
        }

        /**
         * Start a new page sequence.
         * This creates a new list for the pages in the new page sequence.
         * @param title the title of the page sequence.
         */
        public void startPageSequence(Title title) {
            titles.add(title);
            if (pageSequence == null) {
                pageSequence = new ArrayList();
            }
            currSequence = new ArrayList();
            pageSequence.add(currSequence);
        }

        /**
         * Add a page.
         * @param page the page to add to the current page sequence
         */
        public void addPage(PageViewport page) {
            currSequence.add(page);
        }

        /**
         * Get the page sequence count.
         * @return the number of page sequences in the document.
         */
        public int getPageSequenceCount() {
            return pageSequence.size();
        }

        /**
         * Get the title for a page sequence.
         * @param count the page sequence count
         * @return the title of the page sequence
         */
        public Title getTitle(int count) {
            return (Title) titles.get(count);
        }

        /**
         * Get the page count.
         * @param seq the page sequence to count.
         * @return returns the number of pages in a page sequence
         */
        public int getPageCount(int seq) {
            ArrayList sequence = (ArrayList) pageSequence.get(seq);
            return sequence.size();
        }

        /**
         * Get the page for a position in the document.
         * @param seq the page sequence number
         * @param count the page count in the sequence
         * @return the PageViewport for the particular page
         */
        public PageViewport getPage(int seq, int count) {
            ArrayList sequence = (ArrayList) pageSequence.get(seq);
            return (PageViewport) sequence.get(count);
        }

        /**
         * Add an extension to the store page model.
         * The extension is stored so that it can be retrieved in the
         * appropriate position.
         * @param ext the extension to add
         * @param when when the extension should be handled
         */
        public void addExtension(TreeExt ext, int when) {
            int seq, page;
            switch(when) {
                case TreeExt.IMMEDIATELY:
                    seq = pageSequence == null ? 0 : pageSequence.size();
                    page = currSequence == null ? 0 : currSequence.size();
                break;
                case TreeExt.AFTER_PAGE:
                break;
                case TreeExt.END_OF_DOC:
                break;
            }
            extensions.add(ext);
        }

        /**
         * Get the list of extensions that apply at a particular
         * position in the document.
         * @param seq the page sequence number
         * @param count the page count in the sequence
         * @return the list of extensions
         */
        public List getExtensions(int seq, int count) {
            return null;
        }

        /**
         * Get the end of document extensions for this stroe pages model.
         * @return the list of end extensions
         */
        public List getEndExtensions() {
            return extensions;
        }

        /**
         * End document, do nothing.
         */
        public void endDocument() {
        }
    }

    /**
     * This uses the store pages model to store the pages
     * each page is either rendered if ready or prepared
     * for later rendering.
     * Once a page is rendered it is cleared to release the
     * contents but the PageViewport is retained.
     */
    public static class RenderPagesModel extends StorePagesModel {
        /**
         * The renderer that will render the pages.
         */
        protected Renderer renderer;
        /**
         * Pages that have been prepared but not rendered yet.
         */
        protected ArrayList prepared = new ArrayList();
        private ArrayList pendingExt = new ArrayList();
        private ArrayList endDocExt = new ArrayList();

        /**
         * Create a new render pages model with the given renderer.
         * @param rend the renderer to render pages to
         */
        public RenderPagesModel(Renderer rend) {
            renderer = rend;
        }

        /**
         * Start a new page sequence.
         * This tells the renderer that a new page sequence has
         * started with the given title.
         * @param title the title of the new page sequence
         */
        public void startPageSequence(Title title) {
            super.startPageSequence(title);
            renderer.startPageSequence(title);
        }

        /**
         * Add a page to the render page model.
         * If the page is finished it can be rendered immediately.
         * If the page needs resolving then if the renderer supports
         * out of order rendering it can prepare the page. Otherwise
         * the page is added to a queue.
         * @param page the page to add to the model
         */
        public void addPage(PageViewport page) {
            super.addPage(page);

            // check prepared pages
            boolean cont = checkPreparedPages();

            // if page finished
            if (cont && page.isResolved()) {
                try {
                    renderer.renderPage(page);
                } catch (Exception e) {
                    // use error handler to handle this FOP or IO Exception
                }
                page.clear();
            } else {
                preparePage(page);
            }

            renderExtensions(pendingExt);
            pendingExt.clear();
        }

        /**
         * Check prepared pages
         * @return true if the current page should be rendered
         *         false if the renderer doesn't support out of order
         *         rendering and there are pending pages
         */
        protected boolean checkPreparedPages() {
            for (Iterator iter = prepared.iterator(); iter.hasNext();) {
                PageViewport p = (PageViewport)iter.next();
                if (p.isResolved()) {
                    try {
                        renderer.renderPage(p);
                    } catch (Exception e) {
                        // use error handler to handle this FOP or IO Exception
                    }
                    p.clear();
                    iter.remove();
                } else {
                    // if keeping order then stop at first page not resolved
                    if (!renderer.supportsOutOfOrder()) {
                        break;
                    }
                }
            }
            return renderer.supportsOutOfOrder() || prepared.isEmpty();
        }

        /**
         * Prepare a page.
         * An unresolved page can be prepared if the renderer supports
         * it and the page will be rendered later.
         * @param page the page to prepare
         */
        protected void preparePage(PageViewport page) {
            if (renderer.supportsOutOfOrder()) {
                renderer.preparePage(page);
            }
            prepared.add(page);
        }

        /**
         * Add an extension to this model.
         * If handle immediately then send directly to the renderer.
         * The after page ones are handled after the next page is added.
         * End of document extensions are added to a list to be
         * handled at the end.
         * @param ext the extension
         * @param when when to render the extension
         */
        public void addExtension(TreeExt ext, int when) {
            switch(when) {
                case TreeExt.IMMEDIATELY:
                    renderer.renderExtension(ext);
                break;
                case TreeExt.AFTER_PAGE:
                    pendingExt.add(ext);
                break;
                case TreeExt.END_OF_DOC:
                    endDocExt.add(ext);
                break;
            }
        }        

        private void renderExtensions(ArrayList list) {
            for (int count = 0; count < list.size(); count++) {
                TreeExt ext = (TreeExt)list.get(count);
                renderer.renderExtension(ext);
            }
        }

        /**
         * End the document. Render any end document extensions.
         */
        public void endDocument() {
            // render any pages that had unresolved ids
            checkPreparedPages();
            renderExtensions(endDocExt);
        }
    }

}