All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.openhtmltopdf.layout.FootnoteManager Maven / Gradle / Ivy

Go to download

Open HTML to PDF is a CSS 2.1 renderer written in Java. This artifact contains the core rendering and layout code.

The newest version!
package com.openhtmltopdf.layout;

import java.awt.Rectangle;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Element;

import com.openhtmltopdf.css.style.CalculatedStyle;
import com.openhtmltopdf.css.style.EmptyStyle;
import com.openhtmltopdf.render.BlockBox;
import com.openhtmltopdf.render.Box;
import com.openhtmltopdf.render.LineBox;
import com.openhtmltopdf.render.PageBox;
import com.openhtmltopdf.render.ViewportBox;
import com.openhtmltopdf.util.BoxUtil;
import com.openhtmltopdf.util.OpenUtil;

/**
 * The footnote manager which is only created if footnotes are detected in
 * the document. See {@link LayoutContext#getFootnoteManager()}
 */
public class FootnoteManager {

    private static class FootnoteArea {
        // The container for one or more footnotes belonging to a page.
        BlockBox footnoteArea;

        // The pages this footnote area covers, usually a singleton list
        // so may not be mutable.
        List pages;

        // The footnote area max-height or -1 if not provided
        // in footnote at-rule.
        public float maxHeight;
    }

    // Every page has zero or one footnote areas but a footnote area may go
    // over several pages.
    private final Map _footnoteAreas = new HashMap<>();

    // Map from a footnote body back to its footnote area.
    private final Map _containerMap = new HashMap<>();

    private FootnoteArea createFootnoteArea(LayoutContext c, PageBox page) {
        FootnoteArea area = new FootnoteArea();
        area.footnoteArea = createFootnoteAreaBlock(c, page);
        area.maxHeight = page.getFootnoteMaxHeight(c);
        return area;
    }

    /**
     * Sets up the footnote area.
     */
    private BlockBox createFootnoteAreaBlock(LayoutContext c, PageBox footnoteCallPage) {
        // Create style from @footnote page rules with absolute
        // positioning and block display.
        CalculatedStyle style = new EmptyStyle().deriveStyle(
                footnoteCallPage.getPageInfo().createFootnoteAreaStyle());

        Box rootBox = c.getRootLayer().getMaster();
        Element root = rootBox.getElement();

        // We get a NPE if our new BlockBox doesn't have an element
        // so just create one.
        Element me = root.getOwnerDocument().createElement("fs-footnote");
        Element body = BoxUtil.getBodyElementOrSomething(root);

        body.appendChild(me);

        Box bodyBox = BoxUtil.getBodyBoxOrSomething(c.getRootLayer().getMaster());
        int containingBlockWidth = OpenUtil.firstNonZero(
                         bodyBox.getContentWidth(), bodyBox.getWidth(),
                         rootBox.getContentWidth(), rootBox.getWidth(),
                         footnoteCallPage.getContentWidth(c), footnoteCallPage.getWidth(c));

        BlockBox footnoteArea = getFootNoteArea(me, containingBlockWidth, style);
        return footnoteArea;
    }

    private BlockBox getFootNoteArea(Element me, int containingBlockWidth, CalculatedStyle style) {
        BlockBox footnoteArea = new BlockBox();
        footnoteArea.setContainingBlock(new ViewportBox(new Rectangle(0, 0, containingBlockWidth, 0)));
        footnoteArea.setStyle(style);

        // For now we make sure all footnote bodies have block display.
        footnoteArea.setChildrenContentType(BlockBox.ContentType.BLOCK);
        footnoteArea.setElement(me);
        return footnoteArea;
    }

    private void positionFootnoteArea(
            LayoutContext c, FootnoteArea area, PageBox firstPage, int lineHeight, boolean allowRepeat) {

        // We clear any page footnote area info as
        // they may have changed and are recreated.
        clearFootnoteAreaPages(area);

        int desiredHeight = area.footnoteArea.getBorderBoxHeight(c);
        int pageTop = firstPage.getTop();
        int pageBottom = firstPage.getBottom();

        int minFootnoteTop;

        if (area.maxHeight > 0) {
            // Respect max-height if they have left room for at least one line
            // of normal content.
            minFootnoteTop = Math.max(
                    pageTop + lineHeight,
                    ((int) (pageBottom - area.maxHeight)));
        } else {
            // Otherwise use a sensible default of 60%.
            minFootnoteTop = Math.max(
                    pageTop + lineHeight,
                    (int) (pageBottom - (firstPage.getContentHeight(c) * 0.6)));
        }

        if (minFootnoteTop > pageBottom) {
            // No room for even a single line.
            // Move to the next page.
            firstPage = c.getRootLayer().getFirstPage(c, minFootnoteTop);
        }

        int maxFootnoteHeight = firstPage.getBottom() - minFootnoteTop;

        int firstPageHeight = Math.min(
                maxFootnoteHeight,
                desiredHeight);

        firstPage.setFootnoteAreaHeight(firstPageHeight);
        _footnoteAreas.put(firstPage, area);

        boolean multiPage;

        if (firstPageHeight < desiredHeight) {
            multiPage = true;
            reserveSubsequentPagesForFootnoteArea(c, area, desiredHeight, minFootnoteTop, 1);
        } else {
            multiPage = false;
            area.pages = Collections.singletonList(firstPage);
        }

        int x = 0;
        Box body = BoxUtil.getBodyOrNull(c.getRootLayer().getMaster());

        if (body != null) {
            x = body.getMarginBorderPadding(c, CalculatedStyle.LEFT);
        }

        area.footnoteArea.setAbsX(x);
        area.footnoteArea.setAbsY(firstPage.getBottom() - firstPageHeight);

        if (multiPage && allowRepeat) {
            // For multi-page footnote areas we have to re-layout as
            // our simple positioning may have resulted in split line boxes
            // over two pages. Layout will take care to avoid this.
            area.footnoteArea.reset(c);
            area.footnoteArea.layout(c);
            area.footnoteArea.calcChildLocations();

            // The number of pages covered by this footnote area may have increased
            // so reserve additional pages as needed.
            int oldPagesCount = area.pages.size();
            int newDesiredHeight = area.footnoteArea.getBorderBoxHeight(c);

            reserveSubsequentPagesForFootnoteArea(c, area, newDesiredHeight, minFootnoteTop, oldPagesCount);
        } else {
            area.footnoteArea.calcChildLocations();
        }
    }

    private void reserveSubsequentPagesForFootnoteArea(
            LayoutContext c, FootnoteArea area, int desiredHeight, int footnoteTop, int startAt) {
        area.pages = c.getRootLayer().getPages(c, footnoteTop, footnoteTop + desiredHeight);

        // Reserve entire subsequent pages for footnotes.
        for (int i = startAt; i < area.pages.size(); i++) {
            PageBox page = area.pages.get(i);
            page.setFootnoteAreaHeight(page.getContentHeight(c));
            _footnoteAreas.put(page, area);
        }
    }

    private void clearFootnoteAreaPages(FootnoteArea area) {
        if (area.pages != null) {
            for (PageBox page : area.pages) {
                page.setFootnoteAreaHeight(0);
                _footnoteAreas.remove(page);
            }

            area.pages = null;
        }
    }

    /**
     * Adds a footnote body to the line box page, creating the footnote area as required.
     * 

* Important: This changes the page break point by expanding the footnote area. * It also reserves subsequent pages if required for the footnote area. *

* If the line box is moved to a different page, one must call * {@link #removeFootnoteBodies(LayoutContext, List, LineBox)} before the page change * and this method again after. **/ public void addFootnoteBody(LayoutContext c, BlockBox footnoteBody, LineBox line) { PageBox page = c.getRootLayer().getFirstPage(c, line); FootnoteArea footnote = _footnoteAreas.get(page); // We need to know that we are in the footnote area during layout so we // know which page bottom to use. c.setIsInFloatBottom(true); boolean createdArea = false; if (footnote == null) { footnote = createFootnoteArea(c, page); createdArea = true; } footnoteBody.setContainingBlock(footnote.footnoteArea.getContainingBlock()); footnote.footnoteArea.addChild(footnoteBody); _containerMap.put(footnoteBody, footnote); if (!createdArea) { footnote.footnoteArea.reset(c); } else { footnoteBody.reset(c); } // FIXME: Not very efficient to layout all footnotes again after one // is added. c.setIsPrintOverride(false); footnote.footnoteArea.layout(c); c.setIsPrintOverride(null); positionFootnoteArea(c, footnote, page, line.getHeight(), true); c.setIsInFloatBottom(false); } /** * Removes footnotes. This is used when a line is moved to a * new page. We remove from first page and add to the next. */ public void removeFootnoteBodies( LayoutContext c, List footnoteBodies, LineBox line) { for (BlockBox body : footnoteBodies) { FootnoteArea area = _containerMap.get(body); area.footnoteArea.removeChild(body); _containerMap.remove(body); if (area.footnoteArea.getChildCount() > 0) { c.setIsInFloatBottom(true); PageBox page = c.getRootLayer().getFirstPage(c, line); area.footnoteArea.reset(c); c.setIsPrintOverride(false); area.footnoteArea.layout(c); c.setIsPrintOverride(null); positionFootnoteArea(c, area, page, line.getHeight(), true); c.setIsInFloatBottom(false); } else { if (area.footnoteArea.getLayer() != null) { area.footnoteArea.getLayer().detach(); } clearFootnoteAreaPages(area); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy