blob: c4e9cea1dd56e3998b31057a0d78bde131928b6d [file] [log] [blame]
/**
* 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 org.apache.pdfbox.jbig2;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.stream.ImageInputStream;
import org.apache.pdfbox.jbig2.io.SubInputStream;
import org.apache.pdfbox.jbig2.segments.EndOfStripe;
import org.apache.pdfbox.jbig2.segments.GenericRefinementRegion;
import org.apache.pdfbox.jbig2.segments.GenericRegion;
import org.apache.pdfbox.jbig2.segments.HalftoneRegion;
import org.apache.pdfbox.jbig2.segments.PageInformation;
import org.apache.pdfbox.jbig2.segments.PatternDictionary;
import org.apache.pdfbox.jbig2.segments.Profiles;
import org.apache.pdfbox.jbig2.segments.SymbolDictionary;
import org.apache.pdfbox.jbig2.segments.Table;
import org.apache.pdfbox.jbig2.segments.TextRegion;
import org.apache.pdfbox.jbig2.util.log.Logger;
import org.apache.pdfbox.jbig2.util.log.LoggerFactory;
/**
* The basic class for all JBIG2 segments.
*/
@SuppressWarnings("unchecked")
public class SegmentHeader {
private static final Logger log = LoggerFactory.getLogger(SegmentHeader.class);
private static final Map<Integer, Class<? extends SegmentData>> SEGMENT_TYPE_MAP = new HashMap<Integer, Class<? extends SegmentData>>();
static {
Object SEGMENT_TYPES[][] = {
{
0, SymbolDictionary.class
}, {
4, TextRegion.class
}, {
6, TextRegion.class
}, {
7, TextRegion.class
}, {
16, PatternDictionary.class
}, {
20, HalftoneRegion.class
}, {
22, HalftoneRegion.class
}, {
23, HalftoneRegion.class
}, {
36, GenericRegion.class
}, {
38, GenericRegion.class
}, {
39, GenericRegion.class
}, {
40, GenericRefinementRegion.class
}, {
42, GenericRefinementRegion.class
}, {
43, GenericRefinementRegion.class
}, {
48, PageInformation.class
}, {
50, EndOfStripe.class
}, {
52, Profiles.class
}, {
53, Table.class
},
};
for (int i = 0; i < SEGMENT_TYPES.length; i++) {
Object[] objects = SEGMENT_TYPES[i];
SEGMENT_TYPE_MAP.put((Integer) objects[0], (Class<? extends SegmentData>) objects[1]);
}
}
private int segmentNr;
private int segmentType;
private byte retainFlag;
private int pageAssociation;
private byte pageAssociationFieldSize;
private SegmentHeader[] rtSegments;
private long segmentHeaderLength;
private long segmentDataLength;
private long segmentDataStartOffset;
private final SubInputStream subInputStream;
private Reference<SegmentData> segmentData;
public SegmentHeader(JBIG2Document document, SubInputStream sis, long offset, int organisationType)
throws IOException {
this.subInputStream = sis;
parse(document, sis, offset, organisationType);
}
/**
*
*
* @param document
* @param subInputStream
* @param organisationType
* @param offset - The offset where the segment header starts
* @throws IOException
*/
private void parse(JBIG2Document document, ImageInputStream subInputStream, long offset, int organisationType)
throws IOException {
printDebugMessage("\n########################");
printDebugMessage("Segment parsing started.");
subInputStream.seek(offset);
printDebugMessage("|-Seeked to offset: " + offset);
/* 7.2.2 Segment number */
readSegmentNumber(subInputStream);
/* 7.2.3 Segment header flags */
readSegmentHeaderFlag(subInputStream);
/* 7.2.4 Amount of referred-to segments */
int countOfRTS = readAmountOfReferredToSegments(subInputStream);
/* 7.2.5 Referred-to segments numbers */
int[] rtsNumbers = readReferredToSegmentsNumbers(subInputStream, countOfRTS);
/* 7.2.6 Segment page association (Checks how big the page association field is.) */
readSegmentPageAssociation(document, subInputStream, countOfRTS, rtsNumbers);
/* 7.2.7 Segment data length (Contains the length of the data part (in bytes).) */
readSegmentDataLength(subInputStream);
readDataStartOffset(subInputStream, organisationType);
readSegmentHeaderLength(subInputStream, offset);
printDebugMessage("########################\n");
}
/**
* 7.2.2 Segment number
*
* @param subInputStream
* @throws IOException
*/
private void readSegmentNumber(ImageInputStream subInputStream) throws IOException {
segmentNr = (int) (subInputStream.readBits(32) & 0xffffffff);
printDebugMessage("|-Segment Nr: " + segmentNr);
}
/**
* 7.2.3 Segment header flags
*
* @param subInputStream
* @throws IOException
*/
private void readSegmentHeaderFlag(ImageInputStream subInputStream) throws IOException {
// Bit 7: Retain Flag, if 1, this segment is flagged as retained;
retainFlag = (byte) subInputStream.readBit();
printDebugMessage("|-Retain flag: " + retainFlag);
// Bit 6: Size of the page association field. One byte if 0, four bytes if 1;
pageAssociationFieldSize = (byte) subInputStream.readBit();
printDebugMessage("|-Page association field size=" + pageAssociationFieldSize);
// Bit 5-0: Contains the values (between 0 and 62 with gaps) for segment types, specified in 7.3
segmentType = (int) (subInputStream.readBits(6) & 0xff);
printDebugMessage("|-Segment type=" + segmentType);
}
/**
* 7.2.4 Amount of referred-to segments
*
* @param subInputStream
* @return The amount of referred-to segments.
* @throws IOException
*/
private int readAmountOfReferredToSegments(ImageInputStream subInputStream) throws IOException {
int countOfRTS = (int) (subInputStream.readBits(3) & 0xf);
printDebugMessage("|-RTS count: " + countOfRTS);
byte[] retainBit;
printDebugMessage(" |-Stream position before RTS: " + subInputStream.getStreamPosition());
if (countOfRTS <= 4) {
/* short format */
retainBit = new byte[5];
for (int i = 0; i <= 4; i++) {
retainBit[i] = (byte) subInputStream.readBit();
}
} else {
/* long format */
countOfRTS = (int) (subInputStream.readBits(29) & 0xffffffff);
int arrayLength = (countOfRTS + 8) >> 3;
retainBit = new byte[arrayLength <<= 3];
for (int i = 0; i < arrayLength; i++) {
retainBit[i] = (byte) subInputStream.readBit();
}
}
printDebugMessage(" |-Stream position after RTS: " + subInputStream.getStreamPosition());
return countOfRTS;
}
/**
* 7.2.5 Referred-to segments numbers
* <p>
* Gathers all segment numbers of referred-to segments. The segments itself are stored in the
* {@link #rtSegments} array.
*
* @param subInputStream - Wrapped source data input stream.
* @param countOfRTS - The amount of referred-to segments.
*
* @return An array with the segment number of all referred-to segments.
*
* @throws IOException
*/
private int[] readReferredToSegmentsNumbers(ImageInputStream subInputStream, int countOfRTS) throws IOException {
int[] rtsNumbers = new int[countOfRTS];
if (countOfRTS > 0) {
short rtsSize = 1;
if (segmentNr > 256) {
rtsSize = 2;
if (segmentNr > 65536) {
rtsSize = 4;
}
}
rtSegments = new SegmentHeader[countOfRTS];
printDebugMessage("|-Length of RT segments list: " + rtSegments.length);
for (int i = 0; i < countOfRTS; i++) {
rtsNumbers[i] = (int) (subInputStream.readBits(rtsSize << 3) & 0xffffffff);
}
}
return rtsNumbers;
}
/**
* 7.2.6 Segment page association
*
* @param document
* @param subInputStream
* @param countOfRTS
* @param rtsNumbers
* @throws IOException
*/
private void readSegmentPageAssociation(JBIG2Document document, ImageInputStream subInputStream, int countOfRTS,
int[] rtsNumbers) throws IOException {
if (pageAssociationFieldSize == 0) {
// Short format
pageAssociation = (short) (subInputStream.readBits(8) & 0xff);
} else {
// Long format
pageAssociation = (int) (subInputStream.readBits(32) & 0xffffffff);
}
if (countOfRTS > 0) {
final JBIG2Page page = document.getPage(pageAssociation);
for (int i = 0; i < countOfRTS; i++) {
rtSegments[i] = (null != page ? page.getSegment(rtsNumbers[i]) : document.getGlobalSegment(rtsNumbers[i]));
}
}
}
/**
* 7.2.7 Segment data length
* <p>
* Contains the length of the data part in bytes.
*
* @param subInputStream
* @throws IOException
*/
private void readSegmentDataLength(ImageInputStream subInputStream) throws IOException {
segmentDataLength = (subInputStream.readBits(32) & 0xffffffff);
printDebugMessage("|-Data length: " + segmentDataLength);
}
/**
* Sets the offset only if organization type is SEQUENTIAL. If random, data starts after segment
* headers and can be determined when all segment headers are parsed and allocated.
*
* @param subInputStream
* @param organisationType
* @throws IOException
*/
private void readDataStartOffset(ImageInputStream subInputStream, int organisationType) throws IOException {
if (organisationType == JBIG2Document.SEQUENTIAL) {
printDebugMessage("|-Organization is sequential.");
segmentDataStartOffset = subInputStream.getStreamPosition();
}
}
private void readSegmentHeaderLength(ImageInputStream subInputStream, long offset) throws IOException {
segmentHeaderLength = subInputStream.getStreamPosition() - offset;
printDebugMessage("|-Segment header length: " + segmentHeaderLength);
}
private void printDebugMessage(String message) {
log.debug(message);
}
public int getSegmentNr() {
return segmentNr;
}
public int getSegmentType() {
return segmentType;
}
public long getSegmentHeaderLength() {
return segmentHeaderLength;
}
public long getSegmentDataLength() {
return segmentDataLength;
}
public long getSegmentDataStartOffset() {
return segmentDataStartOffset;
}
public void setSegmentDataStartOffset(long segmentDataStartOffset) {
this.segmentDataStartOffset = segmentDataStartOffset;
}
public SegmentHeader[] getRtSegments() {
return rtSegments;
}
public int getPageAssociation() {
return pageAssociation;
}
public short getRetainFlag() {
return retainFlag;
}
/**
* Creates and returns a new {@link SubInputStream} that provides the data part of this segment.
* It is a clipped view of the source input stream.
*
* @return The {@link SubInputStream} that represents the data part of the segment.
*/
public SubInputStream getDataInputStream() {
return new SubInputStream(subInputStream, segmentDataStartOffset, segmentDataLength);
}
/**
* Retrieves the segments' data part.
*
* @return Retrieved {@link SegmentData} instance.
*/
public SegmentData getSegmentData() {
SegmentData segmentDataPart = null;
if (null != segmentData) {
segmentDataPart = segmentData.get();
}
if (null == segmentDataPart) {
try {
Class<? extends SegmentData> segmentClass = SEGMENT_TYPE_MAP.get(segmentType);
if (null == segmentClass) {
throw new IllegalArgumentException("No segment class for type " + segmentType);
}
segmentDataPart = segmentClass.newInstance();
segmentDataPart.init(this, getDataInputStream());
segmentData = new SoftReference<SegmentData>(segmentDataPart);
} catch (Exception e) {
throw new RuntimeException("Can't instantiate segment class", e);
}
}
return segmentDataPart;
}
public void cleanSegmentData() {
if (segmentData != null) {
segmentData = null;
}
}
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
if (rtSegments != null) {
for (SegmentHeader s : rtSegments) {
stringBuilder.append(s.segmentNr + " ");
}
} else {
stringBuilder.append("none");
}
return "\n#SegmentNr: " + segmentNr //
+ "\n SegmentType: " + segmentType //
+ "\n PageAssociation: " + pageAssociation //
+ "\n Referred-to segments: " + stringBuilder.toString() //
+ "\n"; //
}
}