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

com.openhtmltopdf.render.InlineBox 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!
/*
 * Copyright (c) 2006 Wisconsin Court System
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */
package com.openhtmltopdf.render;

import java.text.BreakIterator;

import org.w3c.dom.Element;
import com.openhtmltopdf.bidi.BidiSplitter;
import com.openhtmltopdf.css.constants.CSSName;
import com.openhtmltopdf.css.constants.IdentValue;
import com.openhtmltopdf.css.extend.ContentFunction;
import com.openhtmltopdf.css.parser.FSFunction;
import com.openhtmltopdf.css.style.CalculatedStyle;
import com.openhtmltopdf.extend.FSTextBreaker;
import com.openhtmltopdf.layout.Breaker;
import com.openhtmltopdf.layout.LayoutContext;
import com.openhtmltopdf.layout.Styleable;
import com.openhtmltopdf.layout.TextUtil;
import com.openhtmltopdf.layout.WhitespaceStripper;

/**
 * A class which represents a portion of an inline element. If an inline element
 * does not contain any nested elements, then a single InlineBox
 * object will contain the content for the entire element. Otherwise multiple
 * InlineBox objects will be created corresponding to each
 * discrete chunk of text appearing in the element. It is not rendered directly
 * (and hence does not extend from {@link Box}), but does play an important
 * role in layout (for example, when calculating min/max widths). Note that it
 * does not contain children. Inline content is stored as a flat list in the
 * layout tree. However, InlineBox does contain enough
 * information to reconstruct the original element nesting and this is, in fact,
 * done during inline layout.
 *
 * @see InlineLayoutBox
 */
public class InlineBox implements Styleable {
    private Element _element;

    private String _originalText;
    private String _text;
    private boolean _removableWhitespace;
    
    /**
     * See {@link #isStartsHere()}
     */
    private boolean _startsHere;
    
    /**
     * See {@link #isEndsHere()}
     */
    private boolean _endsHere;

    private CalculatedStyle _style;

    private ContentFunction _contentFunction;
    private FSFunction _function;

    private boolean _minMaxCalculated;
    private int _maxWidth;
    private int _minWidth;

    private int _firstLineWidth;

    private String _pseudoElementOrClass;

    public InlineBox(String text) {
        _text = text;
        _originalText = text;
    }

    private byte _textDirection;

    private BlockBox _footnoteBody;
    
    /**
     * @param direction either LTR or RTL from {@link BidiSplitter} interface.
     */
    public void setTextDirection(byte direction) {
    	this._textDirection = direction;
    }
    
    /**
     * @return either LTR or RTL from {@link BidiSplitter} interface.
     */
    public byte getTextDirection() {
    	return this._textDirection;
    }
    
    public String getText() {
        return _text;
    }

    public void setText(String text) {
        _text = text;
        _originalText = text;
    }

    public void applyTextTransform() {
        _text = _originalText;
        _text = TextUtil.transformText(_text, getStyle());
    }

    public boolean isRemovableWhitespace() {
        return _removableWhitespace;
    }

    public void setRemovableWhitespace(boolean removeableWhitespace) {
        _removableWhitespace = removeableWhitespace;
    }

    /**
     * The opposite of {@link #isStartsHere()}
     */
    public boolean isEndsHere() {
        return _endsHere;
    }

    /**
     * See {@link #isEndsHere()}
     */
    public void setEndsHere(boolean endsHere) {
        _endsHere = endsHere;
    }

    /**
     * Whether this is the first InlineBox for a box. For example:
     * 
     * [b]one[i]two[/i]three[/b]
     * 
     * will create three InlineBox objects and one and two will return
     * true for isStartsHere.
     * 
     * This is used for example to decide whether left margin needs to be applied.
     */
    public boolean isStartsHere() {
        return _startsHere;
    }

    /**
     * See {@link #isStartsHere()}
     */
    public void setStartsHere(boolean startsHere) {
        _startsHere = startsHere;
    }

    @Override
    public CalculatedStyle getStyle() {
        return _style;
    }

    @Override
    public void setStyle(CalculatedStyle style) {
        _style = style;
    }

    @Override
    public Element getElement() {
        return _element;
    }

    @Override
    public void setElement(Element element) {
        _element = element;
    }

    public ContentFunction getContentFunction() {
        return _contentFunction;
    }

    public void setContentFunction(ContentFunction contentFunction) {
        _contentFunction = contentFunction;
    }

    public boolean isDynamicFunction() {
        return _contentFunction != null;
    }

    private int getTextWidth(LayoutContext c, String s) {
        return c.getTextRenderer().getWidth(
                c.getFontContext(),
                c.getFont(getStyle().getFont(c)),
                s);
    }

    private int getMaxCharWidth(LayoutContext c, String s) {
        char[] chars = s.toCharArray();
        int result = 0;
        for (int i = 0; i < chars.length; i++) {
            int width = getTextWidth(c, Character.toString(chars[i]));
            if (width > result) {
                result = width;
            }
        }
        return result;
    }

    private void calcMaxWidthFromLineLength(LayoutContext c, int cbWidth, boolean trim) {
        int last = 0;
        int current = 0;

        while ( (current = _text.indexOf(WhitespaceStripper.EOL, last)) != -1) {
            String target = _text.substring(last, current);
            if (trim) {
                target = target.trim();
            }
            int length = getTextWidth(c, target);
            if (last == 0) {
                length += getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.LEFT);
            }
            if (length > _maxWidth) {
                _maxWidth = length;
            }
            if (last == 0) {
                _firstLineWidth = length;
            }
            last = current + 1;
        }

        String target = _text.substring(last);
        if (trim) {
            target = target.trim();
        }
        int length = getTextWidth(c, target);
        length += getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.RIGHT);
        if (length > _maxWidth) {
            _maxWidth = length;
        }
        if (last == 0) {
            _firstLineWidth = length;
        }
    }

    public int getSpaceWidth(LayoutContext c) {
        return c.getTextRenderer().getWidth(
                c.getFontContext(),
                getStyle().getFSFont(c),
                WhitespaceStripper.SPACE);

    }

    public int getTrailingSpaceWidth(LayoutContext c) {
        if (_text.length() > 0 && _text.charAt(_text.length()-1) == ' ') {
            return getSpaceWidth(c);
        } else {
            return 0;
        }
    }

    private int calcMinWidthFromWordLength(
            LayoutContext c, int cbWidth, boolean trimLeadingSpace, boolean includeWS) {
        int spaceWidth = -1;

        int last = 0;
        int current = 0;
        int maxWidth = 0;
        int spaceCount = 0;

        boolean haveFirstWord = false;
        int firstWord = 0;
        int lastWord = 0;
        
        CalculatedStyle style = getStyle();
        float letterSpacing = style.hasLetterSpacing()
                ? style.getFloatPropertyProportionalWidth(CSSName.LETTER_SPACING, 0, c)
                : 0f;

        String text = getText(trimLeadingSpace);
        FSTextBreaker breakIterator = Breaker.getLineBreakStream(text, c.getSharedContext());

        // Breaker should be used
        while ( (current = breakIterator.next()) != BreakIterator.DONE) {
            String currentWord = text.substring(last, current);
            
            if (currentWord.length() > 0 && currentWord.charAt(currentWord.length() - 1) == Breaker.SOFT_HYPHEN) {
                currentWord += '-';
            }
            
            int wordWidth = getTextWidth(c, currentWord);
            int minWordWidth;
            if (getStyle().getWordWrap() == IdentValue.BREAK_WORD) {
                minWordWidth = getMaxCharWidth(c, currentWord);
            } else {
                minWordWidth = (int) (wordWidth + (letterSpacing * currentWord.length()));
            }

            if (spaceCount > 0) {
                if (includeWS) {
                    for (int i = 0; i < spaceCount; i++) {
                        wordWidth += spaceWidth;
                        minWordWidth += spaceWidth;
                    }
                } else {
                    maxWidth += spaceWidth;
                }
                spaceCount = 0;
            }
            if (minWordWidth > 0) {
                if (! haveFirstWord) {
                    firstWord = minWordWidth;
                }
                lastWord = minWordWidth;
            }

            if (minWordWidth > _minWidth) {
                _minWidth = minWordWidth;
            }
            maxWidth += wordWidth;

            last = current;
            for (int i = current; i < text.length(); i++) {
                if (text.charAt(i) == ' ') {
                    spaceCount++;
                    last++;
                } else {
                    break;
                }
            }
            if (spaceCount > 0 && spaceWidth == -1) {
                spaceWidth = getSpaceWidth(c);
            }
        }

        String currentWord = text.substring(last);
        int wordWidth = getTextWidth(c, currentWord);
        int minWordWidth;
        if (getStyle().getWordWrap() == IdentValue.BREAK_WORD) {
            minWordWidth = getMaxCharWidth(c, currentWord);
        } else {
            minWordWidth = (int) (wordWidth + (letterSpacing * currentWord.length()));
        }
        if (spaceCount > 0) {
            if (includeWS) {
                for (int i = 0; i < spaceCount; i++) {
                    wordWidth += spaceWidth;
                    minWordWidth += spaceWidth;
                }
            } else {
                maxWidth += spaceWidth;
            }
            spaceCount = 0;
        }
        if (minWordWidth > 0) {
            if (! haveFirstWord) {
                firstWord = minWordWidth;
            }
            lastWord = minWordWidth;
        }
        if (minWordWidth > _minWidth) {
            _minWidth = minWordWidth;
        }
        maxWidth += wordWidth;

        if (isStartsHere()) {
            int leftMBP = getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.LEFT);
            if (firstWord + leftMBP > _minWidth) {
                _minWidth = firstWord + leftMBP;
            }
            maxWidth += leftMBP;
        }

        if (isEndsHere()) {
            int rightMBP = getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.RIGHT);
            if (lastWord + rightMBP > _minWidth) {
                _minWidth = lastWord + rightMBP;
            }
            maxWidth += rightMBP;
        }

        return maxWidth;
    }

    private String getText(boolean trimLeadingSpace) {
        if (! trimLeadingSpace) {
            return getText();
        } else {
            if (_text.length() > 0 && _text.charAt(0) == ' ') {
                return _text.substring(1);
            } else {
                return _text;
            }
        }
    }

    private int getInlineMBP(LayoutContext c, int cbWidth) {
        return getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.LEFT) +
            getStyle().getMarginBorderPadding(c, cbWidth, CalculatedStyle.RIGHT);
    }

    public void calcMinMaxWidth(LayoutContext c, int cbWidth, boolean trimLeadingSpace) {
        if (! _minMaxCalculated) {
            IdentValue whitespace = getStyle().getWhitespace();
            if (whitespace == IdentValue.NOWRAP) {
                _minWidth = _maxWidth = getInlineMBP(c, cbWidth) + getTextWidth(c, getText(trimLeadingSpace));
            } else if (whitespace == IdentValue.PRE) {
                calcMaxWidthFromLineLength(c, cbWidth, false);
                _minWidth = _maxWidth;
            } else if (whitespace == IdentValue.PRE_WRAP) {
                calcMinWidthFromWordLength(c, cbWidth, false, true);
                calcMaxWidthFromLineLength(c, cbWidth, false);
            } else if (whitespace == IdentValue.PRE_LINE) {
                calcMinWidthFromWordLength(c, cbWidth, trimLeadingSpace, false);
                calcMaxWidthFromLineLength(c, cbWidth, true);
            } else /* if (whitespace == IdentValue.NORMAL) */ {
                _maxWidth = calcMinWidthFromWordLength(c, cbWidth, trimLeadingSpace, false);
            }
            _minWidth = Math.min(_maxWidth, _minWidth);
            _minMaxCalculated = true;
        }
    }

    public int getMaxWidth() {
        return _maxWidth;
    }

    public int getMinWidth() {
        return _minWidth;
    }

    public int getFirstLineWidth() {
        return _firstLineWidth;
    }

    @Override
    public String getPseudoElementOrClass() {
        return _pseudoElementOrClass;
    }

    public void setPseudoElementOrClass(String pseudoElementOrClass) {
        _pseudoElementOrClass = pseudoElementOrClass;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        result.append("InlineBox: ");
        if (getElement() != null) {
            result.append("<");
            result.append(getElement().getNodeName());
            result.append("> ");
        } else {
            result.append("(anonymous) ");
        }
        if (getPseudoElementOrClass() != null) {
            result.append(':');
            result.append(getPseudoElementOrClass());
            result.append(' ');
        }
        if (isStartsHere() || isEndsHere()) {
            result.append("(");
            if (isStartsHere()) {
                result.append("S");
            }
            if (isEndsHere()) {
                result.append("E");
            }
            result.append(") ");
        }

        appendPositioningInfo(result);

        result.append("(");
        result.append(shortText());
        result.append(") ");
        return result.toString();
    }

    protected void appendPositioningInfo(StringBuilder result) {
        if (getStyle().isRelative()) {
            result.append("(relative) ");
        }
        if (getStyle().isFixed()) {
            result.append("(fixed) ");
        }
        if (getStyle().isAbsolute()) {
            result.append("(absolute) ");
        }
        if (getStyle().isFloated()) {
            result.append("(floated) ");
        }
    }

    private String shortText() {
        if (_text == null) {
            return null;
        } else {
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < _text.length() && i < 40; i++) {
                char c = _text.charAt(i);
                if (c == '\n') {
                    result.append(' ');
                } else {
                    result.append(c);
                }
            }
            if (result.length() == 40) {
                result.append("...");
            }
            return result.toString();
        }
    }

    public FSFunction getFunction() {
        return _function;
    }

    public void setFunction(FSFunction function) {
        _function = function;
    }

    public void truncateText() {
        _text = "";
        _originalText = "";
    }

    public void setFootnote(BlockBox footnoteBody) {
        _footnoteBody = footnoteBody;
    }

    public boolean hasFootnote() {
        return getFootnoteBody() != null;
    }

    public BlockBox getFootnoteBody() {
        return _footnoteBody;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy