/*
* 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;
import org.icepdf.core.pobjects.fonts.Font;
import org.icepdf.core.pobjects.fonts.FontFile;
import org.icepdf.core.pobjects.fonts.ofont.OFont;
import org.icepdf.core.pobjects.security.SecurityManager;
import org.icepdf.core.util.Utils;
/**
* <p>This class represents a PDF Literal String Object. Literal String
* objects are written as a sequence of literal characters enclosed in
* parentheses ().</p>
*
* @since 2.0
*/
public class LiteralStringObject implements StringObject {
// core data used to represent the literal string information
private StringBuilder stringData;
private static char[] hexChar = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f'};
// Reference is need for standard encryption
Reference reference;
/**
* <p>Creates a new literal string object so that it represents the same
* sequence of bytes as in the bytes argument. In other words, the
* initial content of the literal string is the characters represented
* by the byte data.</p>
*
* @param bytes array of bytes which will be interpreted as literal
* character data.
*/
public LiteralStringObject(byte[] bytes) {
this(new StringBuilder(bytes.length).append(new String(bytes)));
}
public LiteralStringObject(StringBuilder chars, boolean dif) {
stringData = chars;
}
/**
* <p>Creates a new literal string object so that it represents the same
* sequence of character data specifed by the argument.</p>
*
* @param string the initial contents of the literal string object
*/
public LiteralStringObject(String string) {
// append string data
// escape the string for any special characters.
// \( - left parenthesis
// \) - right parenthesis
// \\ - backslash
stringData = new StringBuilder(string.replaceAll("(?=[()\\\\])", "\\\\"));
}
/**
* <p>Creates a new literal string object so that it represents the same
* sequence of character data specified by the arguments. The string
* value is assumed to be unencrypted and will be encrypted. The
* method #LiteralStringObject(String string) should be used if the string
* is all ready encrypted. This method is used for creating new
* LiteralStringObject's that are created post document parse, like annotation
* property values. </p>
*
* @param string the initial contents of the literal string object,
* unencrypted.
* @param reference of parent PObject
* @param securityManager security manager used ot encrypt the string.
*/
public LiteralStringObject(String string, Reference reference,
SecurityManager securityManager) {
// append string data
this.reference = reference;
// convert string to octal encoded.
string = Utils.convertStringToOctal(string);
// decrypt the string.
stringData = new StringBuilder(
encryption(string, false, securityManager));
}
/**
* <p>Creates a new literal string object so that it represents the same
* sequence of character data specifed by the argument. The first and last
* characters of the StringBuffer are removed. This constructor should
* only be used in the context of the parser which has leading and ending
* parentheses which are removed by this method.</p>
*
* @param stringBuffer the initial contents of the literal string object
*/
public LiteralStringObject(StringBuilder stringBuffer) {
// remove parentheses, passed in by parser
stringBuffer.deleteCharAt(0);
stringBuffer.deleteCharAt(stringBuffer.length() - 1);
// append string data
stringData = new StringBuilder(stringBuffer.length());
stringData.append(stringBuffer.toString());
}
/**
* Gets the integer value of the hexidecimal data specified by the start and
* offset parameters.
*
* @param start the begining index, inclusive
* @param offset the length of bytes to process
* @return unsigned integer value of the specifed data range
*/
public int getUnsignedInt(int start, int offset) {
if (start < 0 || stringData.length() < (start + offset))
return stringData.charAt(0);
if (offset == 1) {
return stringData.charAt(start);
}
if (offset == 2) {
return ((stringData.charAt(start) & 0xFF) << 8) |
((stringData.charAt(start + 1)) & 0xFF);
} else if (offset == 4) {
return ((stringData.charAt(start) & 0xFF) << 24) |
((stringData.charAt(start + 1) & 0xFF) << 16) |
((stringData.charAt(start + 2) & 0xFF) << 8) |
((stringData.charAt(start + 3)) & 0xFF);
} else {
return 0;
}
}
/**
* <p>Returns a string representation of the object.</p>
*
* @return a string representing the object.
*/
public String toString() {
return stringData.toString();
}
/**
* <p>Gets a hexadecimal String representation of this object's data, which
* is converted to hexadecimal form.</p>
*
* @return a String representation of the objects data.
*/
public String getHexString() {
return stringToHex(stringData).toString();
}
/**
* <p>Gets a hexadecimal StringBuffer representation of this object's data,
* which is converted to hexadecimal form.</p>
*
* @return a StringBufffer representation of the object's data in hexadecimal
* notation.
*/
public StringBuilder getHexStringBuffer() {
return stringToHex(stringData);
}
/**
* <p>Gets a literal StringBuffer representation of this object's data
* which is in fact, the raw data contained in this object.</p>
*
* @return a StringBuffer representation of the object's data.
*/
public StringBuilder getLiteralStringBuffer() {
return stringData;
}
/**
* <p>Gets a literal String representation of this object's data,
* which is in fact, the raw data contained in this object.</p>
*
* @return a String representation of the object's data.
*/
public String getLiteralString() {
return stringData.toString();
}
/**
* <p>Gets a literal String representation of this object's data using the
* specifed font and format. The font is used to verify that the
* specific character codes can be rendered; if they cannot they may be
* removed or combined with the next character code to get a displayable
* character code.
*
* @param fontFormat the type of pdf font which will be used to display
* the text. Valid values are CID_FORMAT and SIMPLE_FORMAT for Adobe
* Composite and Simple font types respectively
* @param font font used to render the the literal string data.
* @return StringBuffer which contains all renderable characters for the
* given font.
*/
public StringBuilder getLiteralStringBuffer(final int fontFormat, FontFile font) {
if (fontFormat == Font.SIMPLE_FORMAT
|| (font.getByteEncoding() == FontFile.ByteEncoding.ONE_BYTE && !(font instanceof OFont))) {
return stringData;
} else if (fontFormat == Font.CID_FORMAT) {
int length = getLength();
int charValue;
StringBuilder tmp = new StringBuilder(length);
if (font.getByteEncoding() == FontFile.ByteEncoding.MIXED_BYTE) {
int charOffset = 1;
for (int i = 0; i < length; i += charOffset) {
// check range for possible 2 byte char.
charValue = getUnsignedInt(i, 1);
if (font.canDisplayEchar((char) charValue)) {
tmp.append((char) charValue);
} else {
int charValue2 = getUnsignedInt(i, 2);
if (font.canDisplayEchar((char) charValue2)) {
tmp.append((char) charValue2);
i += 1;
}
}
}
} else {
// we have default 2bytes.
int charOffset = 2;
for (int i = 0; i < length; i += charOffset) {
int charValue2 = getUnsignedInt(i, 2);
if (font.canDisplayEchar((char) charValue2)) {
tmp.append((char) charValue2);
}
}
}
return tmp;
}
return null;
}
/**
* The length of the the underlying object's data.
*
* @return length of objcts data.
*/
public int getLength() {
return stringData.length();
}
/**
* Utility method for converting literal strings to hexadecimal.
*
* @param string StringBuffer in literal form
* @return StringBuffer in hexadecial form
*/
private StringBuilder stringToHex(StringBuilder string) {
StringBuilder hh = new StringBuilder(string.length() * 2);
int charCode;
for (int i = 0, max = string.length(); i < max; i++) {
charCode = string.charAt(i);
hh.append(hexChar[(charCode & 0xf0) >>> 4]);
hh.append(hexChar[charCode & 0x0f]);
}
return hh;
}
/**
* Sets the parent PDF object's reference.
*
* @param reference parent object reference.
*/
public void setReference(Reference reference) {
this.reference = reference;
}
/**
* Sets the parent PDF object's reference.
*
* @return returns the reference used for encryption.
*/
public Reference getReference() {
return reference;
}
/**
* Gets the decrypted literal string value of the data using the key provided by the
* security manager.
*
* @param securityManager security manager associated with parent document.
*/
public String getDecryptedLiteralString(SecurityManager securityManager) {
return encryption(stringData.toString(), true, securityManager);
}
/**
* Decrypts or encrypts a string.
*
* @param string string to encrypt or decrypt
* @param decrypt true to decrypt string, false otherwise;
* @param securityManager security manager for document.
* @return encrypted or decrypted string, depends on value of decrypt param.
*/
public String encryption(String string, boolean decrypt,
SecurityManager securityManager) {
// get the security manager instance
if (securityManager != null && reference != null) {
// get the key
byte[] key = securityManager.getDecryptionKey();
// convert string to bytes.
byte[] textBytes =
Utils.convertByteCharSequenceToByteArray(string);
// Decrypt String
if (decrypt) {
textBytes = securityManager.decrypt(reference,
key,
textBytes);
} else {
textBytes = securityManager.encrypt(reference,
key,
textBytes);
}
// convert back to a string
return Utils.convertByteArrayToByteString(textBytes);
}
return string;
}
}