package rst.pdfbox.layout.text; import java.awt.Color; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.pdfbox.pdmodel.edit.PDPageContentStream; import rst.pdfbox.layout.util.CompatibilityHelper; /** * A text of line containing only {@link StyledText}s. It may be terminated by a * {@link #getNewLine() new line}. */ public class TextLine implements TextSequence { /** * The font ascent. */ private static final String ASCENT = "ascent"; /** * The font height. */ private static final String HEIGHT = "height"; /** * The text width. */ private static final String WIDTH = "width"; private final List<StyledText> styledTextList = new ArrayList<StyledText>(); private NewLine newLine; private Map<String, Object> cache = new HashMap<String, Object>(); private void clearCache() { cache.clear(); } private void setCachedValue(final String key, Object value) { cache.put(key, value); } @SuppressWarnings("unchecked") private <T> T getCachedValue(final String key, Class<T> type) { return (T) cache.get(key); } /** * Adds a styled text. * * @param fragment * the fagment to add. */ public void add(final StyledText fragment) { styledTextList.add(fragment); clearCache(); } /** * Adds all styled texts of the given text line. * * @param textLine * the text line to add. */ public void add(final TextLine textLine) { for (StyledText fragment : textLine.getStyledTexts()) { add(fragment); } } /** * @return the terminating new line, may be <code>null</code>. */ public NewLine getNewLine() { return newLine; } /** * Sets the new line. * * @param newLine * the new line. */ public void setNewLine(NewLine newLine) { this.newLine = newLine; clearCache(); } /** * @return the styled texts building up this line. */ public List<StyledText> getStyledTexts() { return Collections.unmodifiableList(styledTextList); } @Override public Iterator<TextFragment> iterator() { return new TextLineIterator(styledTextList.iterator(), newLine); } /** * @return <code>true</code> if the line contains neither styled text nor a * new line. */ public boolean isEmpty() { return styledTextList.isEmpty() && newLine == null; } @Override public float getWidth() throws IOException { Float width = getCachedValue(WIDTH, Float.class); if (width == null) { width = 0f; for (TextFragment fragment : this) { width += fragment.getWidth(); } setCachedValue(WIDTH, width); } return width; } @Override public float getHeight() throws IOException { Float height = getCachedValue(HEIGHT, Float.class); if (height == null) { height = 0f; for (TextFragment fragment : this) { height = Math.max(height, fragment.getHeight()); } setCachedValue(HEIGHT, height); } return height; } /** * @return the (max) ascent of this line. * @throws IOException * by pdfbox. */ protected float getAscent() throws IOException { Float ascent = getCachedValue(ASCENT, Float.class); if (ascent == null) { ascent = 0f; for (TextFragment fragment : this) { float currentAscent = fragment.getFontDescriptor().getSize() * fragment.getFontDescriptor().getFont() .getFontDescriptor().getAscent() / 1000; ascent = Math.max(ascent, currentAscent); } setCachedValue(ASCENT, ascent); } return ascent; } @Override public void drawText(PDPageContentStream contentStream, Position upperLeft, Alignment alignment, DrawListener drawListener) throws IOException { drawAligned(contentStream, upperLeft, alignment, getWidth(), drawListener); } public void drawAligned(PDPageContentStream contentStream, Position upperLeft, Alignment alignment, float availableLineWidth, DrawListener drawListener) throws IOException { contentStream.saveGraphicsState(); contentStream.beginText(); float x = upperLeft.getX(); float y = upperLeft.getY() - getAscent(); // the baseline float offset = TextSequenceUtil.getOffset(this, availableLineWidth, alignment); x += offset; CompatibilityHelper.setTextTranslation(contentStream, x, y); float extraWordSpacing = 0; if (alignment == Alignment.Justify && (getNewLine() instanceof WrappingNewLine) ){ extraWordSpacing = (availableLineWidth - getWidth()) / (styledTextList.size()-1); } FontDescriptor lastFontDesc = null; float lastBaselineOffset = 0; Color lastColor = null; float gap = 0; for (StyledText styledText : styledTextList) { if (!styledText.getFontDescriptor().equals(lastFontDesc)) { lastFontDesc = styledText.getFontDescriptor(); contentStream.setFont(lastFontDesc.getFont(), lastFontDesc.getSize()); } if (!styledText.getColor().equals(lastColor)) { lastColor = styledText.getColor(); contentStream.setNonStrokingColor(lastColor); } if (styledText.getLeftMargin() > 0) { gap += styledText.getLeftMargin(); } boolean moveBaseline = styledText.getBaselineOffset() != lastBaselineOffset; if (moveBaseline || gap > 0) { float baselineDelta = lastBaselineOffset - styledText.getBaselineOffset(); lastBaselineOffset = styledText.getBaselineOffset(); CompatibilityHelper.moveTextPosition(contentStream, gap, baselineDelta); x += gap; } if (styledText.getText().length() > 0) { CompatibilityHelper.showText(contentStream, styledText.getText()); } if (drawListener != null) { float currentUpperLeft = y + styledText.getAsent(); drawListener.drawn(styledText, new Position(x, currentUpperLeft), styledText.getWidthWithoutMargin(), styledText.getHeight()); } x += styledText.getWidthWithoutMargin(); gap = extraWordSpacing; if (styledText.getRightMargin() > 0) { gap += styledText.getRightMargin(); } } contentStream.endText(); contentStream.restoreGraphicsState(); } @Override public String toString() { return "TextLine [styledText=" + styledTextList + ", newLine=" + newLine + "]"; } /** * An iterator for the text line. See {@link TextLine#iterator()}. */ private static class TextLineIterator implements Iterator<TextFragment> { private Iterator<StyledText> styledText; private NewLine newLine; /** * Creates an iterator of the given styled texts with an optional * trailing new line. * * @param styledText * the text fragments to iterate. * @param newLine * the optional trailing new line. */ public TextLineIterator(Iterator<StyledText> styledText, NewLine newLine) { super(); this.styledText = styledText; this.newLine = newLine; } @Override public boolean hasNext() { return styledText.hasNext() || newLine != null; } @Override public TextFragment next() { TextFragment next = null; if (styledText.hasNext()) { next = styledText.next(); } else if (newLine != null) { next = newLine; newLine = null; } return next; } @Override public void remove() { throw new UnsupportedOperationException(); } } }