/**
 * @author Stanislav Lapitsky
 * @version 1.0
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;
import javax.swing.*;
import javax.swing.text.*;
import java.awt.geom.AffineTransform;
import javax.swing.event.*;

public class PageableEditorKit extends StyledEditorKit {

    PageableViewFactory factory = new PageableViewFactory();
    protected int pageWidth = 150;
    protected int pageHeight = 200;
    public static int DRAW_PAGE_INSET = 15;
    public static String PAGE_BREAK_ATTR_NAME="page_break_attribute";
    protected Insets pageMargins = new Insets(10, 10, 10, 10);

    protected JEditorPane header;
    protected JEditorPane footer;
    protected boolean isValidHF;
    public static int HF_SHIFT=3;
    boolean isPageBreakInsertion=false;
    DocumentListener relayoutListener=new DocumentListener() {
        public void insertUpdate(DocumentEvent e) {
            relayout();
        }

        public void removeUpdate(DocumentEvent e) {
            relayout();
        }

        public void changedUpdate(DocumentEvent e) {
            relayout();
        }

        protected void relayout() {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    JEditorPane parent=null;
                    int lastFooterHeight=0;
                    if (footer !=null && footer.getParent()!=null && footer.getParent() instanceof JEditorPane) {
                        parent=(JEditorPane)footer.getParent();
                        lastFooterHeight=footer.getHeight();
                    }
                    else if (header !=null && header.getParent()!=null && header.getParent() instanceof JEditorPane) {
                        parent=(JEditorPane)header.getParent();
                    }
                    isValidHF = false;
                    validateHF();
                    if (footer!=null && lastFooterHeight!=footer.getHeight()) {
                        int shift=lastFooterHeight-footer.getHeight();
                        footer.setLocation(footer.getX(),footer.getY()+shift);
                    }
                    ( (SectionView) parent.getUI().getRootView(parent).getView(0)).layout(0, Short.MAX_VALUE);
                    parent.repaint();
                }
            });
        }
    };

    /**
     * Constructs kit instance
     */
    public PageableEditorKit() {
    }

    /**
     * Sets page width
     * @param width int width value in pixels
     */
    public void setPageWidth(int width) {
        pageWidth = width;
        isValidHF=false;
    }

    /**
     * gets page width
     * @return int width in pixels
     */
    public int getPageWidth() {
        return pageWidth;
    }

    /**
     * Sets page height
     * @param height int height in pixels
     */
    public void setPageHeight(int height) {
        pageHeight = height;
    }

    /**
     * Gets page height
     * @return int height in pixels
     */
    public int getPageHeight() {
        return pageHeight;
    }

    /**
     * Sets page margins (distance between page content and page edge.
     * @param margins Insets margins.
     */
    public void setPageMargins(Insets margins) {
        pageMargins = margins;
        isValidHF=false;
    }

    public void setHeader(JEditorPane header) {
        this.header=header;
        header.getDocument().addDocumentListener(relayoutListener);
        isValidHF=false;
    }
    public void setFooter(JEditorPane footer) {
        this.footer=footer;
        footer.getDocument().addDocumentListener(relayoutListener);
        isValidHF=false;
    }
    public JEditorPane getHeader() {
        return header;
    }
    public JEditorPane getFooter() {
        return footer;
    }

    protected void calculateHFSizes() {
        int hfWidth=pageWidth-pageMargins.left-pageMargins.right-2*DRAW_PAGE_INSET;
        int maxHeight=(pageHeight-pageMargins.top-pageMargins.bottom-2*DRAW_PAGE_INSET)/2;

        if (header!=null) {
            header.setSize(hfWidth, pageHeight);
            int hHeight = Math.min(maxHeight, header.getPreferredSize().height);
            header.setSize(hfWidth, hHeight);
        }

        if (footer!=null) {
            footer.setSize(hfWidth, pageHeight);
            int fHeight = Math.min(maxHeight, footer.getPreferredSize().height);
            footer.setSize(hfWidth, fHeight);
        }
    }

    public void validateHF() {
        if (!isValidHF) {
            calculateHFSizes();

            isValidHF=true;
        }
    }
    /**
     * Collateral method to create application GUI
     * @return JFrame
     */
    public JFrame init() {
        JFrame frame = new JFrame("Pagination");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JEditorPane editor = new JEditorPane();
        editor.setEditorKit(this);
        editor.addCaretListener(new CaretListener() {
            public void caretUpdate(CaretEvent e) {
                if (!isPageBreakInsertion ) {
                    ( (StyledEditorKit) editor.getEditorKit()).getInputAttributes().removeAttribute(PAGE_BREAK_ATTR_NAME);
                }
            }
        });
        this.setHeader(createHeader());
        this.setFooter(createFooter());
        PageFormat pf = new PageFormat();
        pf.setPaper(new Paper());
        final PaginationPrinter pp = new PaginationPrinter(pf, editor);
        JScrollPane scroll = new JScrollPane(editor);
        frame.getContentPane().add(scroll);
        JToolBar tb=new JToolBar();
        JButton bPrint = new JButton("Print to default printer");
        bPrint.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                print(editor, pp);
            }
        });
        JButton bInsertPageBreak = new JButton("Insert page break");
        bInsertPageBreak.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                insertPageBreak(editor);
            }
        });
        tb.add(bPrint);
        tb.add(bInsertPageBreak);
        frame.getContentPane().add(tb, BorderLayout.NORTH);
        frame.setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds());

        return frame;
    }

    protected JEditorPane createHeader() {
        JEditorPane header=new JEditorPane();
        header.setEditorKit(new StyledEditorKit());
        try {
            header.getDocument().insertString(0, "header", new SimpleAttributeSet());
        }
        catch (BadLocationException ex) {
            //can't happen
        }

        return header;
    }

    protected JEditorPane createFooter() {
        JEditorPane footer=new JEditorPane();
        footer.setEditorKit(new StyledEditorKit());
        try {
            footer.getDocument().insertString(0, "footer", new SimpleAttributeSet());
        }
        catch (BadLocationException ex) {
            //can't happen
        }

        return footer;
    }

    /**
     * Prints pages content
     * @param editor JEditorPane pane with content.
     * @param pp PaginationPrinter Printable implementation.
     */
    protected void print(JEditorPane editor, PaginationPrinter pp) {
        PrinterJob pJob = PrinterJob.getPrinterJob();
        //by default paper is letter
        pJob.setPageable(pp);
        try {
            pJob.print();
        }
        catch (PrinterException ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new PageableEditorKit().init().setVisible(true);
    }

    /**
     * gets kit view factory.
     * @return ViewFactory
     */
    public ViewFactory getViewFactory() {
        return factory;
    }

    /**
     * inserts page break element in current caret location
     */
    public void insertPageBreak(final JEditorPane editor) {
        int caretPos=editor.getCaretPosition();
        final SimpleAttributeSet breakAttrs=new SimpleAttributeSet();
        breakAttrs.addAttribute(PAGE_BREAK_ATTR_NAME, Boolean.TRUE);
        DefaultStyledDocument doc=(DefaultStyledDocument)editor.getDocument();
        try {
            isPageBreakInsertion=true;
            char ch=doc.getText(caretPos,1).charAt(0);
            if (ch!='\n') {
                doc.insertString(caretPos, "\n", breakAttrs);
                caretPos++;
            }
            else {
                Element par=doc.getParagraphElement(caretPos);
                caretPos=par.getEndOffset()-1;
            }
            final int pos=caretPos;
            ( (StyledDocument) editor.getDocument()).setCharacterAttributes(pos, 1, breakAttrs, false);
            isPageBreakInsertion=false;
        }
        catch (BadLocationException ex) {
            //do nothing
        }
    }

    /**
     * The view factory class creates custom views for pagination
     * root view (SectionView class) and paragraph (PageableParagraphView class)
     *
     * @author Stanislav Lapitsky
     * @version 1.0
     */
    class PageableViewFactory implements ViewFactory {
        /**
         * Creates view for specified element.
         * @param elem Element parent element
         * @return View created view instance.
         */
        public View create(Element elem) {
            String kind = elem.getName();
            if (kind != null) {
                if (kind.equals(AbstractDocument.ContentElementName)) {
                    return new LabelView(elem);
                }
                else if (kind.equals(AbstractDocument.ParagraphElementName)) {
                    return new PageableParagraphView(elem);
                }
                else if (kind.equals(AbstractDocument.SectionElementName)) {
                    return new SectionView(elem, View.Y_AXIS);
                }
                else if (kind.equals(StyleConstants.ComponentElementName)) {
                    return new ComponentView(elem);
                }
                else if (kind.equals(StyleConstants.IconElementName)) {
                    return new IconView(elem);
                }
            }
            // default to text display
            return new LabelView(elem);
        }

    }

    /**
     * Root view which perform pagination and paints frame around pages.
     *
     * @author Stanislav Lapitsky
     * @version 1.0
     */
    class SectionView extends BoxView {
        int pageNumber = 0;

        /**
         * Creates view instace
         * @param elem Element
         * @param axis int
         */
        public SectionView(Element elem, int axis) {
            super(elem, axis);
        }

        /**
         * Gets amount of pages
         * @return int
         */
        public int getPageCount() {
            return pageNumber;
        }

        /**
         * Perform layout on the box
         *
         * @param width the width (inside of the insets) >= 0
         * @param height the height (inside of the insets) >= 0
         */
        public void layout(int width, int height) {
            width = pageWidth - 2 * DRAW_PAGE_INSET - pageMargins.left - pageMargins.right;
            this.setInsets( (short) (DRAW_PAGE_INSET + pageMargins.top), (short) (DRAW_PAGE_INSET + pageMargins.left), (short) (DRAW_PAGE_INSET + pageMargins.bottom),
                           (short) (DRAW_PAGE_INSET + pageMargins.right));
            super.layout(width, height);
        }

        /**
         * Determines the maximum span for this view along an
         * axis.
         *
         * overriddedn
         */
        public float getMaximumSpan(int axis) {
            return getPreferredSpan(axis);
        }

        /**
         * Determines the minimum span for this view along an
         * axis.
         *
         * overriddedn
         */
        public float getMinimumSpan(int axis) {
            return getPreferredSpan(axis);
        }

        /**
         * Determines the preferred span for this view along an
         * axis.
         * overriddedn
         */
        public float getPreferredSpan(int axis) {
            float span = 0;
            if (axis == View.X_AXIS) {
                span = pageWidth;
            }
            else {
                span = pageHeight * getPageCount();
            }
            return span;
        }

        /**
         * Performs layout along Y_AXIS with shifts for pages.
         *
         * @param targetSpan int
         * @param axis int
         * @param offsets int[]
         * @param spans int[]
         */
        protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
            super.layoutMajorAxis(targetSpan, axis, offsets, spans);
            //validate header and footer sizes if necessary
            validateHF();
            addHF();

            int n = offsets.length;
            pageNumber = 0;
            int headerHeight=header!=null ? header.getHeight() +HF_SHIFT:0;
            int footerHeight=footer!=null ? footer.getHeight() +HF_SHIFT:0;
            int totalOffset = headerHeight;
            for (int i = 0; i < n; i++) {
                offsets[i] = totalOffset;
                View v = getView(i);
                if (isPageBreak(v)) {
                    offsets[i] = pageNumber * pageHeight+headerHeight;
                    pageNumber++;
                }

                if (v instanceof MultiPageView) {
                    ( (MultiPageView) v).setBreakSpan(0);
                    ( (MultiPageView) v).setAdditionalSpace(0);
                }

                if ( (offsets[i] + spans[i]) > (pageNumber * pageHeight - DRAW_PAGE_INSET * 2 - pageMargins.top - pageMargins.bottom-footerHeight)) {
                    if ( (v instanceof MultiPageView) && (v.getViewCount() > 1)) {
                        MultiPageView multipageView = (MultiPageView) v;
                        int space = offsets[i] - (pageNumber - 1) * pageHeight;
                        int breakSpan = (pageNumber * pageHeight - DRAW_PAGE_INSET * 2 - pageMargins.top - pageMargins.bottom-footerHeight) - offsets[i];
                        multipageView.setBreakSpan(breakSpan);
                        multipageView.setPageOffset(space);
                        multipageView.setStartPageNumber(pageNumber);
                        multipageView.setEndPageNumber(pageNumber);
                        int height = (int) getHeight();

                        int width = ( (BoxView) v).getWidth();
                        if (v instanceof PageableParagraphView) {
                            PageableParagraphView parView = (PageableParagraphView) v;
                            parView.layout(width, height);
                        }

                        pageNumber = multipageView.getEndPageNumber();
                        spans[i] += multipageView.getAdditionalSpace();
                    }
                    else {
                        offsets[i] = pageNumber * pageHeight+headerHeight;
                        pageNumber++;
                    }
                }
                totalOffset = (int) Math.min( (long) offsets[i] + (long) spans[i], Integer.MAX_VALUE);
            }
        }

        protected boolean isPageBreak(View v) {
            AttributeSet attrs=v.getElement().getElement(v.getElement().getElementCount()-1).getAttributes();
            Boolean pb=(Boolean)attrs.getAttribute(PAGE_BREAK_ATTR_NAME);
            if (pb==null) {
                return false;
            }
            else {
                return pb.booleanValue();
            }
        }

        private void addHF() {
            JTextComponent text = (JTextComponent) getContainer();
            if (text!=null) {
                if (!isAdded(text,header)) {
                    text.add(header);
                    header.setLocation(DRAW_PAGE_INSET + pageMargins.left, DRAW_PAGE_INSET + pageMargins.top);
                }
                if (!isAdded(text,footer)) {
                    footer.setLocation(DRAW_PAGE_INSET + pageMargins.left, pageHeight - DRAW_PAGE_INSET - pageMargins.bottom - footer.getHeight());
                    text.add(footer);
                }
            }
        }

        protected boolean isAdded(JComponent text, JComponent c) {
            for (int i=0; i<text.getComponentCount(); i++) {
                if (text.getComponent(i)==c) {
                    return true;
                }
            }
            return false;
        }
        public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) {
            Point location=getClickedHFLocation(x,y);
            if (location!=null) {
                if (location.y % pageHeight < pageHeight / 2) {
                    //header
                    header.setLocation(location);
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            header.requestFocus();
                        }
                    });
                }
                else {
                    //footer
                    footer.setLocation(location);
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            footer.requestFocus();
                        }
                    });
                }
                return -1;
            }
            else {
                return super.viewToModel(x, y, a, bias);
            }
        }
        public Point getClickedHFLocation(float x, float y) {
            if (! (x >= DRAW_PAGE_INSET + pageMargins.left
                   && x <= pageWidth - DRAW_PAGE_INSET - pageMargins.right)) {
                return null;
            }
            int headerHeight=getHeader().getHeight();
            int footerHeight=getFooter().getHeight();
            int headerStartY=DRAW_PAGE_INSET + pageMargins.top;
            int footerStartY=pageHeight - DRAW_PAGE_INSET - pageMargins.bottom - footerHeight;
            int hfWidth=pageWidth-pageMargins.left-pageMargins.right-2*DRAW_PAGE_INSET;
            for (int i=0; i<getPageCount(); i++) {
                if (y<headerStartY) {
                    return null;
                }
                if (y<headerStartY+headerHeight) {
                    return new Point(DRAW_PAGE_INSET + pageMargins.left,headerStartY);
                }
                if (y>footerStartY && y<footerStartY+footerHeight) {
                    return new Point(DRAW_PAGE_INSET + pageMargins.left,footerStartY);
                }
                headerStartY+=pageHeight;
                footerStartY+=pageHeight;
            }
            return null;
        }

        /**
         * Paints view content and page frames.
         * @param g Graphics
         * @param a Shape
         */
        public void paint(Graphics g, Shape a) {
            Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a.getBounds();
            Shape baseClip = g.getClip().getBounds();
            int pageCount = getPageCount();
            Rectangle page = new Rectangle();
            page.x = alloc.x;
            page.y = alloc.y;
            page.height = pageHeight;
            page.width = pageWidth;
            String sC = Integer.toString(pageCount);
            for (int i = 0; i < pageCount; i++) {
                page.y = alloc.y + pageHeight * i;
                paintPageFrame(g, page, (Rectangle) baseClip);
                paintHeader(g, i, page);
                paintFooter(g, i, page);
                g.setColor(Color.blue);
                String sN = Integer.toString(i + 1);
                String pageStr = "Page: " + sN;
                pageStr += " of " + sC;
                g.drawString(pageStr,
                             page.x + page.width - 100,
                             page.y + page.height - 3);
            }
            super.paint(g, a);
            g.setColor(Color.gray);
            // Fills background of pages
            int currentWidth = (int) alloc.getWidth();
            int currentHeight = (int) alloc.getHeight();
            int x = page.x + DRAW_PAGE_INSET;
            int y = 0;
            int w = 0;
            int h = 0;
            if (pageWidth < currentWidth) {
                w = currentWidth;
                h = currentHeight;
                g.fillRect(page.x + page.width, alloc.y, w, h);
            }
            if (pageHeight * pageCount < currentHeight) {
                w = currentWidth;
                h = currentHeight;
                g.fillRect(page.x, alloc.y + page.height * pageCount, w, h);
            }
        }

        protected void paintHeader(Graphics g, int pageIndex, Rectangle page) {
            Graphics2D g2d=(Graphics2D)g;
            if (header!=null) {
                AffineTransform old=g2d.getTransform();
                g2d.translate(DRAW_PAGE_INSET+pageMargins.left,DRAW_PAGE_INSET+pageMargins.top+pageIndex*pageHeight);
                boolean isCaretVisible=header.getCaret().isVisible();
                boolean isCaretSelectionVisible=header.getCaret().isSelectionVisible();
                header.getCaret().setVisible(false);
                header.getCaret().setSelectionVisible(false);
                header.paint(g2d);
                header.getCaret().setVisible(isCaretVisible);
                header.getCaret().setSelectionVisible(isCaretSelectionVisible);
                g2d.setColor(Color.lightGray);
                g2d.draw(new Rectangle(-1,-1,header.getWidth()+1,header.getHeight()+1));
                g2d.setTransform(old);
            }
        }

        protected void paintFooter(Graphics g, int pageIndex, Rectangle page) {
            Graphics2D g2d=(Graphics2D)g;
            if (footer!=null) {
                AffineTransform old=g2d.getTransform();
                g2d.translate(DRAW_PAGE_INSET+pageMargins.left,(pageIndex+1)*pageHeight-DRAW_PAGE_INSET-pageMargins.bottom-footer.getHeight());
                boolean isCaretVisible=footer.getCaret().isVisible();
                boolean isCaretSelectionVisible=footer.getCaret().isSelectionVisible();
                footer.getCaret().setVisible(false);
                footer.getCaret().setSelectionVisible(false);
                footer.paint(g2d);
                footer.getCaret().setVisible(isCaretVisible);
                footer.getCaret().setSelectionVisible(isCaretSelectionVisible);
                g2d.setColor(Color.lightGray);
                g2d.draw(new Rectangle(-1,-1,footer.getWidth()+1,footer.getHeight()+1));
                g2d.setTransform(old);
            }
        }
        /**
         * Paints frame for specified page
         * @param g Graphics
         * @param page Shape page rectangle
         * @param container Rectangle
         */
        public void paintPageFrame(Graphics g, Shape page, Rectangle container) {
            Rectangle alloc = (page instanceof Rectangle) ? (Rectangle) page : page.getBounds();
            if (container.intersection(alloc).height <= 0)
                return;
            Color oldColor = g.getColor();

            //borders
            g.setColor(Color.gray);
            g.fillRect(alloc.x, alloc.y, alloc.width, DRAW_PAGE_INSET);
            g.fillRect(alloc.x, alloc.y, DRAW_PAGE_INSET, alloc.height);
            g.fillRect(alloc.x, alloc.y + alloc.height - DRAW_PAGE_INSET, alloc.width, DRAW_PAGE_INSET);
            g.fillRect(alloc.x + alloc.width - DRAW_PAGE_INSET, alloc.y, DRAW_PAGE_INSET, alloc.height);

            //frame
            g.setColor(Color.black);
            g.drawLine(alloc.x + DRAW_PAGE_INSET, alloc.y + DRAW_PAGE_INSET, alloc.x + alloc.width - DRAW_PAGE_INSET, alloc.y + DRAW_PAGE_INSET);
            g.drawLine(alloc.x + DRAW_PAGE_INSET, alloc.y + DRAW_PAGE_INSET, alloc.x + DRAW_PAGE_INSET, alloc.y + alloc.height - DRAW_PAGE_INSET);
            g.drawLine(alloc.x + DRAW_PAGE_INSET, alloc.y + alloc.height - DRAW_PAGE_INSET, alloc.x + alloc.width - DRAW_PAGE_INSET, alloc.y + alloc.height - DRAW_PAGE_INSET);
            g.drawLine(alloc.x + alloc.width - DRAW_PAGE_INSET, alloc.y + DRAW_PAGE_INSET, alloc.x + alloc.width - DRAW_PAGE_INSET, alloc.y + alloc.height - DRAW_PAGE_INSET);

            //shadow
            g.fillRect(alloc.x + alloc.width - DRAW_PAGE_INSET, alloc.y + DRAW_PAGE_INSET + 4, 4, alloc.height - 2 * DRAW_PAGE_INSET);
            g.fillRect(alloc.x + DRAW_PAGE_INSET + 4, alloc.y + alloc.height - DRAW_PAGE_INSET, alloc.width - 2 * DRAW_PAGE_INSET, 4);

            g.setColor(oldColor);
        }


    }

    /**
     * Represents multipage paragraph.
     *
     * @author Stanislav Lapitsky
     * @version 1.0
     */
    class PageableParagraphView extends ParagraphView implements MultiPageView {
        protected int additionalSpace = 0;
        protected int breakSpan = 0;
        protected int pageOffset = 0;
        protected int startPageNumber = 0;
        protected int endPageNumber = 0;

        public PageableParagraphView(Element elem) {
            super(elem);
        }

        public void layout(int width, int height) {
            super.layout(width, height);
        }

        protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
            super.layoutMajorAxis(targetSpan, axis, offsets, spans);
            performMultiPageLayout(targetSpan, axis, offsets, spans);
        }

        /**
         * Layout paragraph's content splitting between pages if needed.
         * Calculates shifts and breaks for parent view (SectionView)
         * @param targetSpan int
         * @param axis int
         * @param offsets int[]
         * @param spans int[]
         */
        public void performMultiPageLayout(int targetSpan, int axis, int[] offsets, int[] spans) {
            if (breakSpan == 0)
                return;
            int space = breakSpan;

            additionalSpace = 0;
            endPageNumber = startPageNumber;
            int topInset = this.getTopInset();
            int offs = 0;
            int headerHeight=getHeaderHeight();
            int footerHeight=getFooterHeight();
            for (int i = 0; i < offsets.length; i++) {
                if (offs + spans[i] + topInset > space) {
                    int newOffset = endPageNumber * pageHeight;
                    int addSpace = newOffset - (startPageNumber - 1) * pageHeight - pageOffset - offs - topInset-headerHeight;
                    additionalSpace += addSpace;
                    offs += addSpace;
                    for (int j = i; j < offsets.length; j++) {
                        offsets[j] += addSpace;
                    }
                    endPageNumber++;
                    space = (endPageNumber * pageHeight - 2 * DRAW_PAGE_INSET - pageMargins.top - pageMargins.bottom) - (startPageNumber - 1) * pageHeight - pageOffset-footerHeight;
                }
                offs += spans[i];
            }
        }

        protected int getHeaderHeight() {
            JTextComponent text = (JTextComponent)getContainer();
            if (text!=null && text instanceof JEditorPane && ((JEditorPane)text).getEditorKit() instanceof PageableEditorKit) {
                PageableEditorKit kit=(PageableEditorKit)((JEditorPane)text).getEditorKit();
                if (kit.getHeader()!=null) {
                    return kit.getHeader().getHeight()+PageableEditorKit.HF_SHIFT;
                }
            }
            return 0;
        }
        protected int getFooterHeight() {
            JTextComponent text = (JTextComponent)getContainer();
            if (text!=null && ((JEditorPane)text).getEditorKit() instanceof PageableEditorKit) {
                PageableEditorKit kit=(PageableEditorKit)((JEditorPane)text).getEditorKit();
                if (kit.getFooter()!=null) {
                    return kit.getFooter().getHeight()+PageableEditorKit.HF_SHIFT;
                }
            }
            return 0;
        }
        /**
         * Gets view's start page number
         * @return page number
         */
        public int getStartPageNumber() {
            return startPageNumber;
        }

        /**
         * Gets view's end page number
         * @return page number
         */
        public int getEndPageNumber() {
            return endPageNumber;
        }

        /**
         * Gets view's extra space (space between pages)
         * @return extra space
         */
        public int getAdditionalSpace() {
            return additionalSpace;
        }

        /**
         * Gets view's break span
         * @return break span
         */
        public int getBreakSpan() {
            return breakSpan;
        }

        /**
         * Gets view's offsets on the page
         * @return offset
         */
        public int getPageOffset() {
            return pageOffset;
        }

        /**
         * Sets view's start page number
         *
         * @param startPageNumber page number
         */
        public void setStartPageNumber(int startPageNumber) {
            this.startPageNumber = startPageNumber;
        }

        /**
         * Sets view's end page number
         *
         * @param endPageNumber page number
         */
        public void setEndPageNumber(int endPageNumber) {
            this.endPageNumber = endPageNumber;
        }

        /**
         * Sets extra space (space between pages)
         *
         * @param additionalSpace additional space
         */
        public void setAdditionalSpace(int additionalSpace) {
            this.additionalSpace = additionalSpace;
        }

        /**
         * Sets view's break span.
         *
         * @param breakSpan break span
         */
        public void setBreakSpan(int breakSpan) {
            this.breakSpan = breakSpan;
        }

        /**
         * Sets view's offset on the page
         *
         * @param pageOffset offset
         */
        public void setPageOffset(int pageOffset) {
            this.pageOffset = pageOffset;
        }
    }
}