634 lines
22 KiB
Java
634 lines
22 KiB
Java
/**
|
|
* PDFobj.java
|
|
*
|
|
Copyright (c) 2018, Innovatics Inc.
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and / or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
package com.pdfjet;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
|
|
|
|
/**
|
|
* Used to create Java or .NET objects that represent the objects in PDF document.
|
|
* See the PDF specification for more information.
|
|
*
|
|
*/
|
|
public class PDFobj {
|
|
|
|
protected int number; // The object number
|
|
protected int offset; // The object offset
|
|
protected List<String> dict;
|
|
protected int stream_offset;
|
|
protected byte[] stream; // The compressed stream
|
|
protected byte[] data; // The decompressed data
|
|
protected int gsNumber = -1;
|
|
|
|
|
|
/**
|
|
* Used to create Java or .NET objects that represent the objects in PDF document.
|
|
* See the PDF specification for more information.
|
|
* Also see Example_19.
|
|
*
|
|
* @param offset the object offset in the offsets table.
|
|
*/
|
|
public PDFobj(int offset) {
|
|
this.offset = offset;
|
|
this.dict = new ArrayList<String>();
|
|
}
|
|
|
|
|
|
protected PDFobj() {
|
|
this.dict = new ArrayList<String>();
|
|
}
|
|
|
|
|
|
public int getNumber() {
|
|
return this.number;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the object dictionary.
|
|
*
|
|
* @return the object dictionary.
|
|
*/
|
|
public List<String> getDict() {
|
|
return this.dict;
|
|
}
|
|
|
|
|
|
public void setDict(List<String> dict) {
|
|
this.dict = dict;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the uncompressed stream data.
|
|
*
|
|
* @return the uncompressed stream data.
|
|
*/
|
|
public byte[] getData() {
|
|
return this.data;
|
|
}
|
|
|
|
|
|
protected void setStream(byte[] pdf, int length) {
|
|
stream = new byte[length];
|
|
System.arraycopy(pdf, this.stream_offset, stream, 0, length);
|
|
}
|
|
|
|
|
|
protected void setStream(byte[] stream) {
|
|
this.stream = stream;
|
|
}
|
|
|
|
|
|
protected void setNumber(int number) {
|
|
this.number = number;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the dictionary value for the specified key.
|
|
*
|
|
* @param key the specified key.
|
|
*
|
|
* @return the value.
|
|
*/
|
|
public String getValue(String key) {
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
String token = dict.get(i);
|
|
if (token.equals(key)) {
|
|
if (dict.get(i + 1).equals("<<")) {
|
|
StringBuilder buffer = new StringBuilder();
|
|
buffer.append("<<");
|
|
buffer.append(" ");
|
|
i += 2;
|
|
while (!dict.get(i).equals(">>")) {
|
|
buffer.append(dict.get(i));
|
|
buffer.append(" ");
|
|
i += 1;
|
|
}
|
|
buffer.append(">>");
|
|
return buffer.toString();
|
|
}
|
|
if (dict.get(i + 1).equals("[")) {
|
|
StringBuilder buffer = new StringBuilder();
|
|
buffer.append("[");
|
|
buffer.append(" ");
|
|
i += 2;
|
|
while (!dict.get(i).equals("]")) {
|
|
buffer.append(dict.get(i));
|
|
buffer.append(" ");
|
|
i += 1;
|
|
}
|
|
buffer.append("]");
|
|
return buffer.toString();
|
|
}
|
|
return dict.get(i + 1);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
|
|
protected List<Integer> getObjectNumbers(String key) {
|
|
List<Integer> numbers = new ArrayList<Integer>();
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
String token = dict.get(i);
|
|
if (token.equals(key)) {
|
|
String str = dict.get(++i);
|
|
if (str.equals("[")) {
|
|
while (true) {
|
|
str = dict.get(++i);
|
|
if (str.equals("]")) {
|
|
break;
|
|
}
|
|
numbers.add(Integer.valueOf(str));
|
|
++i; // 0
|
|
++i; // R
|
|
}
|
|
}
|
|
else {
|
|
numbers.add(Integer.valueOf(str));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return numbers;
|
|
}
|
|
|
|
|
|
public float[] getPageSize() {
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/MediaBox")) {
|
|
return new float[] {
|
|
Float.valueOf(dict.get(i + 4)),
|
|
Float.valueOf(dict.get(i + 5)) };
|
|
}
|
|
}
|
|
return Letter.PORTRAIT;
|
|
}
|
|
|
|
|
|
protected int getLength(List<PDFobj> objects) {
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
String token = dict.get(i);
|
|
if (token.equals("/Length")) {
|
|
int number = Integer.valueOf(dict.get(i + 1));
|
|
if (dict.get(i + 2).equals("0") &&
|
|
dict.get(i + 3).equals("R")) {
|
|
return getLength(objects, number);
|
|
}
|
|
else {
|
|
return number;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
protected int getLength(List<PDFobj> objects, int number) {
|
|
for (PDFobj obj : objects) {
|
|
if (obj.number == number) {
|
|
return Integer.valueOf(obj.dict.get(3));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
public PDFobj getContentsObject(Map<Integer, PDFobj> objects) {
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/Contents")) {
|
|
if (dict.get(i + 1).equals("[")) {
|
|
return objects.get(Integer.valueOf(dict.get(i + 2)));
|
|
}
|
|
return objects.get(Integer.valueOf(dict.get(i + 1)));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
public PDFobj getResourcesObject(Map<Integer, PDFobj> objects) {
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/Resources")) {
|
|
String token = dict.get(i + 1);
|
|
if (token.equals("<<")) {
|
|
PDFobj obj = new PDFobj();
|
|
obj.dict.add("0");
|
|
obj.dict.add("0");
|
|
obj.dict.add("obj");
|
|
obj.dict.add(token);
|
|
int level = 1;
|
|
i++;
|
|
while (i < dict.size() && level > 0) {
|
|
token = dict.get(i);
|
|
obj.dict.add(token);
|
|
if (token.equals("<<")) {
|
|
level++;
|
|
}
|
|
else if (token.equals(">>")) {
|
|
level--;
|
|
}
|
|
i++;
|
|
}
|
|
return obj;
|
|
}
|
|
return objects.get(Integer.valueOf(token));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
TODO: Test well this method and use instead of the method above.
|
|
public PDFobj getResourcesObject(Map<Integer, PDFobj> objects) {
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/Resources")) {
|
|
String token = dict.get(i + 1);
|
|
if (token.equals("<<")) {
|
|
return this;
|
|
}
|
|
return objects.get(Integer.valueOf(token));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
*/
|
|
|
|
public Font addResource(CoreFont coreFont, Map<Integer, PDFobj> objects) {
|
|
Font font = new Font(coreFont);
|
|
font.fontID = font.name.replace('-', '_').toUpperCase();
|
|
|
|
PDFobj obj = new PDFobj();
|
|
obj.number = Collections.max(objects.keySet()) + 1;
|
|
obj.dict.add("<<");
|
|
obj.dict.add("/Type");
|
|
obj.dict.add("/Font");
|
|
obj.dict.add("/Subtype");
|
|
obj.dict.add("/Type1");
|
|
obj.dict.add("/BaseFont");
|
|
obj.dict.add("/" + font.name);
|
|
if (!font.name.equals("Symbol") && !font.name.equals("ZapfDingbats")) {
|
|
obj.dict.add("/Encoding");
|
|
obj.dict.add("/WinAnsiEncoding");
|
|
}
|
|
obj.dict.add(">>");
|
|
|
|
objects.put(obj.number, obj);
|
|
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/Resources")) {
|
|
String token = dict.get(++i);
|
|
if (token.equals("<<")) { // Direct resources object
|
|
addFontResource(this, objects, font.fontID, obj.number);
|
|
}
|
|
else if (Character.isDigit(token.charAt(0))) { // Indirect resources object
|
|
addFontResource(objects.get(Integer.valueOf(token)), objects, font.fontID, obj.number);
|
|
}
|
|
}
|
|
}
|
|
|
|
return font;
|
|
}
|
|
|
|
|
|
private void addFontResource(
|
|
PDFobj obj, Map<Integer, PDFobj> objects, String fontID, int number) {
|
|
|
|
boolean fonts = false;
|
|
for (int i = 0; i < obj.dict.size(); i++) {
|
|
if (obj.dict.get(i).equals("/Font")) {
|
|
fonts = true;
|
|
}
|
|
}
|
|
if (!fonts) {
|
|
for (int i = 0; i < obj.dict.size(); i++) {
|
|
if (obj.dict.get(i).equals("/Resources")) {
|
|
obj.dict.add(i + 2, "/Font");
|
|
obj.dict.add(i + 3, "<<");
|
|
obj.dict.add(i + 4, ">>");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < obj.dict.size(); i++) {
|
|
if (obj.dict.get(i).equals("/Font")) {
|
|
String token = obj.dict.get(i + 1);
|
|
if (token.equals("<<")) {
|
|
obj.dict.add(i + 2, "/" + fontID);
|
|
obj.dict.add(i + 3, String.valueOf(number));
|
|
obj.dict.add(i + 4, "0");
|
|
obj.dict.add(i + 5, "R");
|
|
return;
|
|
}
|
|
else if (Character.isDigit(token.charAt(0))) {
|
|
PDFobj o2 = objects.get(Integer.valueOf(token));
|
|
for (int j = 0; j < o2.dict.size(); j++) {
|
|
if (o2.dict.get(j).equals("<<")) {
|
|
o2.dict.add(j + 1, "/" + fontID);
|
|
o2.dict.add(j + 2, String.valueOf(number));
|
|
o2.dict.add(j + 3, "0");
|
|
o2.dict.add(j + 4, "R");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private void insertNewObject(
|
|
List<String> dict, List<String> list, String type) {
|
|
for (String token : dict) {
|
|
if (token.equals(list.get(0))) {
|
|
return;
|
|
}
|
|
}
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals(type)) {
|
|
dict.addAll(i + 2, list);
|
|
return;
|
|
}
|
|
}
|
|
if (dict.get(3).equals("<<")) {
|
|
dict.addAll(4, list);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
private void addResource(
|
|
String type, PDFobj obj, Map<Integer, PDFobj> objects, int objNumber) {
|
|
String tag = type.equals("/Font") ? "/F" : "/Im";
|
|
String number = String.valueOf(objNumber);
|
|
List<String> list = Arrays.asList(tag + number, number, "0", "R");
|
|
for (int i = 0; i < obj.dict.size(); i++) {
|
|
if (obj.dict.get(i).equals(type)) {
|
|
String token = obj.dict.get(i + 1);
|
|
if (token.equals("<<")) {
|
|
insertNewObject(obj.dict, list, type);
|
|
}
|
|
else {
|
|
insertNewObject(objects.get(Integer.valueOf(token)).dict, list, type);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Handle the case where the page originally does not have any font resources.
|
|
list = Arrays.asList(type, "<<", tag + number, number, "0", "R", ">>");
|
|
for (int i = 0; i < obj.dict.size(); i++) {
|
|
if (obj.dict.get(i).equals("/Resources")) {
|
|
obj.dict.addAll(i + 2, list);
|
|
return;
|
|
}
|
|
}
|
|
for (int i = 0; i < obj.dict.size(); i++) {
|
|
if (obj.dict.get(i).equals("<<")) {
|
|
obj.dict.addAll(i + 1, list);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void addResource(Image image, Map<Integer, PDFobj> objects) {
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/Resources")) {
|
|
String token = dict.get(i + 1);
|
|
if (token.equals("<<")) { // Direct resources object
|
|
addResource("/XObject", this, objects, image.objNumber);
|
|
}
|
|
else { // Indirect resources object
|
|
addResource("/XObject", objects.get(Integer.valueOf(token)), objects, image.objNumber);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void addResource(Font font, Map<Integer, PDFobj> objects) {
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/Resources")) {
|
|
String token = dict.get(i + 1);
|
|
if (token.equals("<<")) { // Direct resources object
|
|
addResource("/Font", this, objects, font.objNumber);
|
|
}
|
|
else { // Indirect resources object
|
|
addResource("/Font", objects.get(Integer.valueOf(token)), objects, font.objNumber);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void addContent(byte[] content, Map<Integer, PDFobj> objects) {
|
|
PDFobj obj = new PDFobj();
|
|
obj.setNumber(Collections.max(objects.keySet()) + 1);
|
|
obj.setStream(content);
|
|
objects.put(obj.getNumber(), obj);
|
|
|
|
String objNumber = String.valueOf(obj.number);
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/Contents")) {
|
|
i += 1;
|
|
String token = dict.get(i);
|
|
if (token.equals("[")) {
|
|
// Array of content objects
|
|
while (true) {
|
|
i += 1;
|
|
token = dict.get(i);
|
|
if (token.equals("]")) {
|
|
dict.add(i, "R");
|
|
dict.add(i, "0");
|
|
dict.add(i, objNumber);
|
|
return;
|
|
}
|
|
i += 2; // Skip the 0 and R
|
|
}
|
|
}
|
|
else {
|
|
// Single content object
|
|
PDFobj obj2 = objects.get(Integer.valueOf(token));
|
|
if (obj2.data == null && obj2.stream == null) {
|
|
// This is not a stream object!
|
|
for (int j = 0; j < obj2.dict.size(); j++) {
|
|
if (obj2.dict.get(j).equals("]")) {
|
|
obj2.dict.add(j, "R");
|
|
obj2.dict.add(j, "0");
|
|
obj2.dict.add(j, objNumber);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
dict.add(i, "[");
|
|
dict.add(i + 4, "]");
|
|
dict.add(i + 4, "R");
|
|
dict.add(i + 4, "0");
|
|
dict.add(i + 4, objNumber);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Adds new content object before the existing content objects.
|
|
* The original code was provided by Stefan Ostermann author of ScribMaster and HandWrite Pro.
|
|
* Additional code to handle PDFs with indirect array of stream objects was written by EDragoev.
|
|
*
|
|
* @param content
|
|
* @param objects
|
|
*/
|
|
public void addPrefixContent(byte[] content, Map<Integer, PDFobj> objects) {
|
|
PDFobj obj = new PDFobj();
|
|
obj.setNumber(Collections.max(objects.keySet()) + 1);
|
|
obj.setStream(content);
|
|
objects.put(obj.getNumber(), obj);
|
|
|
|
String objNumber = String.valueOf(obj.number);
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/Contents")) {
|
|
i += 1;
|
|
String token = dict.get(i);
|
|
if (token.equals("[")) {
|
|
// Array of content object streams
|
|
i += 1;
|
|
dict.add(i, "R");
|
|
dict.add(i, "0");
|
|
dict.add(i, objNumber);
|
|
return;
|
|
}
|
|
else {
|
|
// Single content object
|
|
PDFobj obj2 = objects.get(Integer.valueOf(token));
|
|
if (obj2.data == null && obj2.stream == null) {
|
|
// This is not a stream object!
|
|
for (int j = 0; j < obj2.dict.size(); j++) {
|
|
if (obj2.dict.get(j).equals("[")) {
|
|
j += 1;
|
|
obj2.dict.add(j, "R");
|
|
obj2.dict.add(j, "0");
|
|
obj2.dict.add(j, objNumber);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
dict.add(i, "[");
|
|
dict.add(i + 4, "]");
|
|
i += 1;
|
|
dict.add(i, "R");
|
|
dict.add(i, "0");
|
|
dict.add(i, objNumber);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private int getMaxGSNumber(PDFobj obj) {
|
|
List<Integer> numbers = new ArrayList<Integer>();
|
|
for (String token : obj.dict) {
|
|
if (token.startsWith("/GS")) {
|
|
numbers.add(Integer.valueOf(token.substring(3)));
|
|
}
|
|
}
|
|
if (numbers.isEmpty()) {
|
|
return 0;
|
|
}
|
|
return Collections.max(numbers);
|
|
}
|
|
|
|
|
|
public void setGraphicsState(GraphicsState gs, Map<Integer, PDFobj> objects) {
|
|
PDFobj obj = null;
|
|
int index = -1;
|
|
for (int i = 0; i < dict.size(); i++) {
|
|
if (dict.get(i).equals("/Resources")) {
|
|
String token = dict.get(i + 1);
|
|
if (token.equals("<<")) {
|
|
obj = this;
|
|
index = i + 2;
|
|
}
|
|
else {
|
|
obj = objects.get(Integer.valueOf(token));
|
|
for (int j = 0; j < obj.dict.size(); j++) {
|
|
if (obj.dict.get(j).equals("<<")) {
|
|
index = j + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
gsNumber = getMaxGSNumber(obj);
|
|
if (gsNumber == 0) { // No existing ExtGState dictionary
|
|
obj.dict.add(index, "/ExtGState"); // Add ExtGState dictionary
|
|
obj.dict.add(++index, "<<");
|
|
}
|
|
else {
|
|
while (index < obj.dict.size()) {
|
|
String token = obj.dict.get(index);
|
|
if (token.equals("/ExtGState")) {
|
|
index += 1;
|
|
break;
|
|
}
|
|
index += 1;
|
|
}
|
|
}
|
|
obj.dict.add(++index, "/GS" + String.valueOf(gsNumber + 1));
|
|
obj.dict.add(++index, "<<");
|
|
obj.dict.add(++index, "/CA");
|
|
obj.dict.add(++index, String.valueOf(gs.get_CA()));
|
|
obj.dict.add(++index, "/ca");
|
|
obj.dict.add(++index, String.valueOf(gs.get_ca()));
|
|
obj.dict.add(++index, ">>");
|
|
if (gsNumber == 0) {
|
|
obj.dict.add(++index, ">>");
|
|
}
|
|
|
|
StringBuilder buf = new StringBuilder();
|
|
buf.append("q\n");
|
|
buf.append("/GS" + String.valueOf(gsNumber + 1) + " gs\n");
|
|
addPrefixContent(buf.toString().getBytes(), objects);
|
|
}
|
|
|
|
}
|