/*
* $Id: CMapFormat4.java,v 1.3 2011-04-15 15:44:14 xphc Exp $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.sun.pdfview.font.ttf;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
import com.sun.pdfview.font.ttf.CMapFormat4.Segment;
/**
*
* @author jkaplan
*/
public class CMapFormat4 extends CMap {
/**
* The segments and associated data can be a char[] or an Integer
*/
public SortedMap<Segment,Object> segments;
/** Creates a new instance of CMapFormat0 */
protected CMapFormat4(short language) {
super((short) 4, language);
this.segments = Collections.synchronizedSortedMap(new TreeMap<Segment,Object>());
char[] map = new char[1];
map[0] = (char) 0;
addSegment((short) 0xffff, (short) 0xffff, map);
}
/**
* Add a segment with a map
*/
public void addSegment(short startCode, short endCode, char[] map) {
if (map.length != (endCode - startCode) + 1) {
throw new IllegalArgumentException("Wrong number of entries in map");
}
Segment s = new Segment(startCode, endCode, true);
// make sure we remove any old entries
this.segments.remove(s);
this.segments.put(s, map);
}
/**
* Add a segment with an idDelta
*/
public void addSegment(short startCode, short endCode, short idDelta) {
Segment s = new Segment(startCode, endCode, false);
// make sure we remove any old entries
this.segments.remove(s);
this.segments.put(s, Integer.valueOf(idDelta));
}
/**
* Remove a segment
*/
public void removeSegment(short startCode, short endCode) {
Segment s = new Segment(startCode, endCode, true);
this.segments.remove(s);
}
/**
* Get the length of this table
*/
@Override
public short getLength() {
// start with the size of the fixed header
short size = 16;
// add the size of each segment header
size += this.segments.size() * 8;
// add the total number of mappings times the size of a mapping
for (Iterator i = this.segments.keySet().iterator(); i.hasNext();) {
Segment s = (Segment) i.next();
// see if there's a map
if (s.hasMap) {
// if there is, add its size
char[] map = (char[]) this.segments.get(s);
size += map.length * 2;
}
}
return size;
}
/**
* Cannot map from a byte
*/
@Override
public byte map(byte src) {
char c = map((char) src);
if (c < Byte.MIN_VALUE || c > Byte.MAX_VALUE) {
// out of range
return 0;
}
return (byte) c;
}
/**
* Map from char
*/
@Override
public char map(char src) {
// find first segment with endcode > src
for (Iterator i = this.segments.keySet().iterator(); i.hasNext();) {
Segment s = (Segment) i.next();
if (s.endCode >= src) {
// are we within range?
if (s.startCode <= src) {
if (s.hasMap) {
// return the index of this character in
// the segment's map
char[] map = (char[]) this.segments.get(s);
return map[src - s.startCode];
} else {
// return the character code + idDelta
Integer idDelta = (Integer) this.segments.get(s);
return (char) (src + idDelta.intValue());
}
} else {
// undefined character
return (char) 0;
}
}
}
// shouldn't get here!
return (char) 0;
}
/**
* Get the src code which maps to the given glyphID
*/
@Override
public char reverseMap(short glyphID) {
// look at each segment
for (Iterator i = this.segments.keySet().iterator(); i.hasNext();) {
Segment s = (Segment) i.next();
// see if we have a map or a delta
if (s.hasMap) {
char[] map = (char[]) this.segments.get(s);
// if we have a map, we have to iterate through it
for (int c = 0; c < map.length; c++) {
if (map[c] == glyphID) {
return (char) (s.startCode + c);
}
}
} else {
Integer idDelta = (Integer) this.segments.get(s);
// we can do the math to see if we're in range
int start = s.startCode + idDelta.intValue();
int end = s.endCode + idDelta.intValue();
if (glyphID >= start && glyphID <= end) {
// we're in the range
return (char) (glyphID - idDelta.intValue());
}
}
}
// not found!
return (char) 0;
}
/**
* Get the data in this map as a ByteBuffer
*/
@Override
public void setData(int length, ByteBuffer data) {
// read the table size values
short segCount = (short) (data.getShort() / 2);
short searchRange = data.getShort();
short entrySelector = data.getShort();
short rangeShift = data.getShort();
// create arrays to store segment info
short[] endCodes = new short[segCount];
short[] startCodes = new short[segCount];
short[] idDeltas = new short[segCount];
short[] idRangeOffsets = new short[segCount];
// the start of the glyph array
int glyphArrayPos = 16 + (8 * segCount);
// read the endCodes
for (int i = 0; i < segCount; i++) {
endCodes[i] = data.getShort();
}
// read the pad
data.getShort();
// read the start codes
for (int i = 0; i < segCount; i++) {
startCodes[i] = data.getShort();
}
// read the idDeltas
for (int i = 0; i < segCount; i++) {
idDeltas[i] = data.getShort();
}
// read the id range offsets
for (int i = 0; i < segCount; i++) {
idRangeOffsets[i] = data.getShort();
// calculate the actual offset
if (idRangeOffsets[i] <= 0) {
// the easy way
addSegment(startCodes[i], endCodes[i], idDeltas[i]);
} else {
// find the start of the data segment
int offset = (data.position() - 2) + idRangeOffsets[i];
// get the number of entries in the map
int size = (endCodes[i] - startCodes[i]) + 1;
// allocate the actual map
char[] map = new char[size];
// remember our offset
data.mark();
// read the mappings
for (int c = 0; c < size; c++) {
data.position(offset + (c * 2));
map[c] = data.getChar();
}
// reset the position
data.reset();
addSegment(startCodes[i], endCodes[i], map);
}
}
}
/**
* Get the data in the map as a byte buffer
*/
@Override
public ByteBuffer getData() {
ByteBuffer buf = ByteBuffer.allocate(getLength());
// write the header
buf.putShort(getFormat());
buf.putShort(getLength());
buf.putShort(getLanguage());
// write the various values
buf.putShort((short) (getSegmentCount() * 2));
buf.putShort(getSearchRange());
buf.putShort(getEntrySelector());
buf.putShort(getRangeShift());
// write the endCodes
for (Iterator<Segment> i = this.segments.keySet().iterator(); i.hasNext();) {
Segment s = i.next();
buf.putShort((short) s.endCode);
}
// write the pad
buf.putShort((short) 0);
// write the startCodes
for (Iterator<Segment> i = this.segments.keySet().iterator(); i.hasNext();) {
Segment s = i.next();
buf.putShort((short) s.startCode);
}
// write the idDeltas for segments using deltas
for (Iterator<Segment> i = this.segments.keySet().iterator(); i.hasNext();) {
Segment s = i.next();
if (!s.hasMap) {
Integer idDelta = (Integer) this.segments.get(s);
buf.putShort(idDelta.shortValue());
} else {
buf.putShort((short) 0);
}
}
// the start of the glyph array
int glyphArrayOffset = 16 + (8 * getSegmentCount());
// write the idRangeOffsets and maps for segments using maps
for (Iterator<Segment> i = this.segments.keySet().iterator(); i.hasNext();) {
Segment s = i.next();
if (s.hasMap) {
// first set the offset, which is the number of bytes from the
// current position to the current offset
buf.putShort((short) (glyphArrayOffset - buf.position()));
// remember the current position
buf.mark();
// move the position to the offset
buf.position(glyphArrayOffset);
// now write the map
char[] map = (char[]) this.segments.get(s);
for (int c = 0; c < map.length; c++) {
buf.putChar(map[c]);
}
// reset the data pointer
buf.reset();
// update the offset
glyphArrayOffset += map.length * 2;
} else {
buf.putShort((short) 0);
}
}
// make sure we are at the end of the buffer before we flip
buf.position(glyphArrayOffset);
// reset the data pointer
buf.flip();
return buf;
}
/**
* Get the segment count
*/
public short getSegmentCount() {
return (short) this.segments.size();
}
/**
* Get the search range
*/
public short getSearchRange() {
double pow = Math.floor(Math.log(getSegmentCount()) / Math.log(2));
double pow2 = Math.pow(2, pow);
return (short) (2 * pow2);
}
/**
* Get the entry selector
*/
public short getEntrySelector() {
int sr2 = getSearchRange() / 2;
return (short) (Math.log(sr2) / Math.log(2));
}
/**
* Get the rangeShift()
*/
public short getRangeShift() {
return (short) ((2 * getSegmentCount()) - getSearchRange());
}
/** Get a pretty string */
@Override public String toString() {
StringBuffer buf = new StringBuffer();
String indent = " ";
buf.append(super.toString());
buf.append(indent + "SegmentCount : " + getSegmentCount() + "\n");
buf.append(indent + "SearchRange : " + getSearchRange() + "\n");
buf.append(indent + "EntrySelector: " + getEntrySelector() + "\n");
buf.append(indent + "RangeShift : " + getRangeShift() + "\n");
for (Iterator<Segment> i = this.segments.keySet().iterator(); i.hasNext();) {
Segment s = i.next();
buf.append(indent);
buf.append("Segment: " + Integer.toHexString(s.startCode));
buf.append("-" + Integer.toHexString(s.endCode) + " ");
buf.append("hasMap: " + s.hasMap + " ");
if (!s.hasMap) {
buf.append("delta: " + this.segments.get(s));
}
buf.append("\n");
}
return buf.toString();
}
static class Segment implements Comparable {
/** the end code (highest code in this segment) */
int endCode;
/** the start code (lowest code in this segment) */
int startCode;
/** whether it is a map or a delta */
boolean hasMap;
/** Create a new segment */
public Segment(short startCode, short endCode, boolean hasMap) {
// convert from unsigned short
this.endCode = (0xffff & endCode);
this.startCode = (0xffff & startCode);
this.hasMap = hasMap;
}
/** Equals based on compareTo (only compares endCode) */
@Override public boolean equals(Object o) {
return (compareTo(o) == 0);
}
/** Segments sort by increasing endCode */
@Override
public int compareTo(Object o) {
if (!(o instanceof Segment)) {
return -1;
}
Segment s = (Segment) o;
// if regions overlap at all, declare the segments equal,
// to avoid overlap in the segment list
if (((s.endCode >= this.startCode) && (s.endCode <= this.endCode)) ||
((s.startCode >= this.startCode) && (s.startCode <= this.endCode))) {
return 0;
} if (this.endCode > s.endCode) {
return 1;
} else if (this.endCode < s.endCode) {
return -1;
} else {
return 0;
}
}
}
}