/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.tom_roush.pdfbox.pdmodel.interactive.form; import com.tom_roush.pdfbox.pdmodel.PDPageContentStream; import com.tom_roush.pdfbox.pdmodel.interactive.form.PlainText.Line; import com.tom_roush.pdfbox.pdmodel.interactive.form.PlainText.Paragraph; import com.tom_roush.pdfbox.pdmodel.interactive.form.PlainText.TextAttribute; import com.tom_roush.pdfbox.pdmodel.interactive.form.PlainText.Word; import java.io.IOException; import java.util.List; /** * TextFormatter to handle plain text formatting. * * The text formatter will take a single value or an array of values which * are treated as paragraphs. */ class PlainTextFormatter { enum TextAlign { LEFT(0), CENTER(1), RIGHT(2), JUSTIFY(4); private final int alignment; TextAlign(int alignment) { this.alignment = alignment; } int getTextAlign() { return alignment; } public static TextAlign valueOf(int alignment) { for (TextAlign textAlignment : TextAlign.values()) { if (textAlignment.getTextAlign() == alignment) { return textAlignment; } } return TextAlign.LEFT; } } private final AppearanceStyle appearanceStyle; private final boolean wrapLines; private final float width; private final PDPageContentStream contents; private final PlainText textContent; private final TextAlign textAlignment; private float horizontalOffset; private float verticalOffset; static class Builder { // required parameters private PDPageContentStream contents; // optional parameters private AppearanceStyle appearanceStyle; private boolean wrapLines = false; private float width = 0f; private PlainText textContent; private TextAlign textAlignment = TextAlign.LEFT; // initial offset from where to start the position of the first line private float horizontalOffset = 0f; private float verticalOffset = 0f; Builder(PDPageContentStream contents) { this.contents = contents; } Builder style(AppearanceStyle appearanceStyle) { this.appearanceStyle = appearanceStyle; return this; } Builder wrapLines(boolean wrapLines) { this.wrapLines = wrapLines; return this; } Builder width(float width) { this.width = width; return this; } Builder textAlign(int alignment) { this.textAlignment = TextAlign.valueOf(alignment); return this; } Builder textAlign(TextAlign alignment) { this.textAlignment = alignment; return this; } Builder text(PlainText textContent) { this.textContent = textContent; return this; } Builder initialOffset(float horizontalOffset, float verticalOffset) { this.horizontalOffset = horizontalOffset; this.verticalOffset = verticalOffset; return this; } PlainTextFormatter build() { return new PlainTextFormatter(this); } } private PlainTextFormatter(Builder builder) { appearanceStyle = builder.appearanceStyle; wrapLines = builder.wrapLines; width = builder.width; contents = builder.contents; textContent = builder.textContent; textAlignment = builder.textAlignment; horizontalOffset = builder.horizontalOffset; verticalOffset = builder.verticalOffset; } /** * Format the text block. * * @throws IOException if there is an error writing to the stream. */ public void format() throws IOException { if (textContent != null && !textContent.getParagraphs().isEmpty()) { for (Paragraph paragraph : textContent.getParagraphs()) { if (wrapLines) { List<Line> lines = paragraph.getLines( appearanceStyle.getFont(), appearanceStyle.getFontSize(), width ); processLines(lines); } else { float startOffset = 0f; float lineWidth = appearanceStyle.getFont().getStringWidth( paragraph.getText()) * appearanceStyle.getFontSize() / 1000f; if (lineWidth < width) { switch (textAlignment) { case CENTER: startOffset = (width - lineWidth) / 2; break; case RIGHT: startOffset = width - lineWidth; break; case JUSTIFY: default: startOffset = 0f; } } contents.newLineAtOffset(horizontalOffset + startOffset, verticalOffset); contents.showText(paragraph.getText()); } } } } /** * Process lines for output. * * Process lines for an individual paragraph and generate the * commands for the content stream to show the text. * * @param lines the lines to process. * @throws IOException if there is an error writing to the stream. */ private void processLines(List<Line> lines) throws IOException { float wordWidth = 0f; float lastPos = 0f; float startOffset = 0f; float interWordSpacing = 0f; for (Line line : lines) { switch (textAlignment) { case CENTER: startOffset = (width - line.getWidth())/2; break; case RIGHT: startOffset = width - line.getWidth(); break; case JUSTIFY: if (lines.indexOf(line) != lines.size() -1) { interWordSpacing = line.getInterWordSpacing(width); } break; default: startOffset = 0f; } float offset = -lastPos + startOffset + horizontalOffset; if (lines.indexOf(line) == 0) { contents.newLineAtOffset(offset, verticalOffset); // reset the initial verticalOffset verticalOffset = 0f; horizontalOffset = 0f; } else { // keep the last position verticalOffset = verticalOffset - appearanceStyle.getLeading(); contents.newLineAtOffset(offset, -appearanceStyle.getLeading()); } lastPos = startOffset; List<Word> words = line.getWords(); for (Word word : words) { contents.showText(word.getText()); wordWidth = (Float) word.getAttributes().getIterator().getAttribute(TextAttribute.WIDTH); if (words.indexOf(word) != words.size() -1) { contents.newLineAtOffset(wordWidth + interWordSpacing, 0f); lastPos = lastPos + wordWidth + interWordSpacing; } } } horizontalOffset = horizontalOffset - lastPos; } }