/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed 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 org.icepdf.core.pobjects.annotations; import org.icepdf.core.pobjects.*; import org.icepdf.core.pobjects.acroform.FieldDictionary; import org.icepdf.core.pobjects.acroform.TextFieldDictionary; import org.icepdf.core.pobjects.acroform.VariableTextFieldDictionary; import org.icepdf.core.pobjects.fonts.FontFile; import org.icepdf.core.pobjects.fonts.FontManager; import org.icepdf.core.util.Library; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.HashMap; /** * Text field (field type Text) is a box or space for text fill-in data typically * entered from a keyboard. The text may be restricted to a single line or may * be permitted to span multiple lines, depending on the setting of the Multi line * flag in the field dictionary’s Ff entry. Table 228 shows the flags pertaining * to this type of field. A text field shall have a field type of Text. A conforming * PDF file, and a conforming processor shall obey the usage guidelines as * defined by the big flags below. * * @since 5.1 */ public class TextWidgetAnnotation extends AbstractWidgetAnnotation<TextFieldDictionary> { protected FontFile fontFile; private TextFieldDictionary fieldDictionary; public TextWidgetAnnotation(Library l, HashMap h) { super(l, h); fieldDictionary = new TextFieldDictionary(library, entries); fontFile = fieldDictionary.getFont() != null ? fieldDictionary.getFont().getFont() : null; if (fontFile == null) { fontFile = FontManager.getInstance().initialize().getInstance( fieldDictionary.getFontName().toString(), 0); } } public void resetAppearanceStream(double dx, double dy, AffineTransform pageTransform) { // we won't touch password fields, we'll used the original display TextFieldDictionary.TextFieldType textFieldType = fieldDictionary.getTextFieldType(); if (textFieldType == TextFieldDictionary.TextFieldType.TEXT_PASSWORD) { // nothing to do, let the password comp handle the look. } else { // get at the original postscript as well alter the marked content Appearance appearance = appearances.get(currentAppearance); AppearanceState appearanceState = appearance.getSelectedAppearanceState(); Rectangle2D bbox = appearanceState.getBbox(); // putting in identity, as we a trump any cm in the annotation stream. AffineTransform matrix = new AffineTransform();//appearanceState.getMatrix(); String currentContentStream = appearanceState.getOriginalContentStream(); currentContentStream = buildTextWidgetContents(currentContentStream); // finally create the shapes from the altered stream. if (currentContentStream != null) { appearanceState.setContentStream(currentContentStream.getBytes()); } // some widgets don't have AP dictionaries in such a case we need to create the form object // and build out the default properties. Form appearanceStream = getOrGenerateAppearanceForm(); if (appearanceStream != null) { // update the content stream with the new stream data. appearanceStream.setRawBytes(currentContentStream.getBytes()); // add the appearance stream StateManager stateManager = library.getStateManager(); stateManager.addChange(new PObject(appearanceStream, appearanceStream.getPObjectReference())); // add an AP entry for the HashMap<Object, Object> appearanceRefs = new HashMap<Object, Object>(); appearanceRefs.put(APPEARANCE_STREAM_NORMAL_KEY, appearanceStream.getPObjectReference()); entries.put(APPEARANCE_STREAM_KEY, appearanceRefs); Rectangle2D formBbox = new Rectangle2D.Float( (float) bbox.getX(), (float) bbox.getY(), (float) bbox.getWidth(), (float) bbox.getHeight()); appearanceStream.setAppearance(null, matrix, formBbox); // add link to resources on forum, if no resources exist. if (library.getResources(appearanceStream.getEntries(), Form.RESOURCES_KEY) == null && library.getCatalog().getInteractiveForm().getResources() != null) { appearanceStream.getEntries().put(Form.RESOURCES_KEY, library.getCatalog().getInteractiveForm().getResources().getEntries()); } else { // need to find some resources, try adding the parent page. Page page = getPage(); if (page != null && page.getResources() != null) { appearanceStream.getEntries().put(Form.RESOURCES_KEY, page.getResources().getEntries()); } } // add the annotation as changed as T entry has also been updated to reflect teh changed content. stateManager.addChange(new PObject(this, this.getPObjectReference())); // compress the form object stream. if (compressAppearanceStream) { appearanceStream.getEntries().put(Stream.FILTER_KEY, new Name("FlateDecode")); } else { appearanceStream.getEntries().remove(Stream.FILTER_KEY); } appearanceStream.init(); } } } public String buildTextWidgetContents(String currentContentStream) { // text widgets can be null, in this case we setup the default so we can add our own data. if (currentContentStream == null || currentContentStream.equals("")) { currentContentStream = " /Tx BMC q BT ET Q EMC"; } String contents = (String) fieldDictionary.getFieldValue(); // int btStart = currentContentStream.indexOf("BT") + 2; // int etEnd = currentContentStream.lastIndexOf("ET"); int btStart = currentContentStream.indexOf("BMC") + 3; int etEnd = currentContentStream.lastIndexOf("EMC"); String preBt = ""; String postEt = ""; String markedContent = ""; if (btStart >= 0 && etEnd >= 0) { // grab the pre post marked content postscript. preBt = currentContentStream.substring(0, btStart) + " BT "; postEt = "ET " + currentContentStream.substring(etEnd); // marked content which we will use to try and find some data points. markedContent = currentContentStream.substring(btStart, etEnd); } else { preBt = "/Tx BMC q BT "; postEt = " ET Q EMC "; } // check for a bounding box definition Rectangle2D.Float bounds = findRectangle(preBt); boolean isfourthQuadrant = false; if (bounds != null && bounds.getHeight() < 0) { isfourthQuadrant = true; } // finally build out the new content stream StringBuilder content = new StringBuilder(); // calculate line light double lineHeight = getLineHeight(fieldDictionary.getDefaultAppearance()); // apply the default appearance. Page parentPage = getPage(); content.append(generateDefaultAppearance(markedContent, parentPage != null?parentPage.getResources():null, fieldDictionary)); if (fieldDictionary.getDefaultAppearance() == null) { lineHeight = getFontSize(markedContent); } // apply the text offset, 4 is just a generic padding. if (!isfourthQuadrant) { double height = getBbox().getHeight(); double size = fieldDictionary.getSize(); content.append(lineHeight).append(" TL "); // todo rework taking into account multi line height. double hOffset = Math.ceil(size + (height - size)); content.append(2).append(' ').append(hOffset).append(" Td "); } else { content.append(2).append(' ').append(2).append(" Td "); } // encode the text so it can be properly encoded in PDF string format // hex encode the text so that we better handle character codes > 127 content = encodeHexString(content, contents); // build the final content stream. currentContentStream = preBt + content + postEt; return currentContentStream; } public void reset() { // set the fields value (V) to the default value defined by the DV key. Object oldValue = fieldDictionary.getFieldValue(); Object tmp = fieldDictionary.getDefaultFieldValue(); if (tmp != null) { // apply the default value fieldDictionary.setFieldValue(fieldDictionary.getDefaultFieldValue(), getPObjectReference()); changeSupport.firePropertyChange("valueFieldReset", oldValue, fieldDictionary.getFieldValue()); } else { // otherwise we remove the key fieldDictionary.getEntries().remove(FieldDictionary.V_KEY); fieldDictionary.setFieldValue("", getPObjectReference()); if (changeSupport != null) { changeSupport.firePropertyChange("valueFieldReset", oldValue, ""); } } } @Override public TextFieldDictionary getFieldDictionary() { return fieldDictionary; } public String generateDefaultAppearance(String content, Resources resources, VariableTextFieldDictionary variableTextFieldDictionary) { if (variableTextFieldDictionary != null) { return variableTextFieldDictionary.generateDefaultAppearance(content, resources); } return null; } }