org.jpedal.fonts.tt.conversion.CMAPWriter Maven / Gradle / Ivy
The newest version!
/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/java-pdf-library-support/
*
* (C) Copyright 1997-2013, IDRsolutions and Contributors.
*
* This file is part of JPedal
*
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---------------
* CMAPWriter.java
* ---------------
*/
package org.jpedal.fonts.tt.conversion;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.jpedal.fonts.PdfFont;
import org.jpedal.fonts.StandardFonts;
import org.jpedal.fonts.glyph.PdfJavaGlyphs;
import org.jpedal.fonts.tt.CMAP;
import org.jpedal.fonts.tt.FontFile2;
import org.jpedal.fonts.tt.Glyf;
import org.jpedal.fonts.tt.Hmtx;
import org.jpedal.fonts.tt.TTGlyphs;
import org.jpedal.utils.Sorts;
public class CMAPWriter extends CMAP implements FontTableWriter {
private static final long serialVersionUID = -923078471161905124L;
// set in code for use in OS2 table
int minCharCode = 65536;
int maxCharCode = 0;
String fontName;
private PdfFont originalFont;
public CMAPWriter(FontFile2 currentFontFile, int startPointer, Glyf currentGlyf) {
super(currentFontFile, startPointer, currentGlyf);
}
/**
* Creates format 0 subtable for true type fonts [only]
*
* @param glyphs PdfJavaGlyphs
* @return length of format 0 table [hardcoded to 262]
*/
private int createFormat0MapForTT(PdfJavaGlyphs glyphs) {
TTGlyphs ttGlyphs = (TTGlyphs) glyphs;
CMAP currentCmap = (CMAP) ttGlyphs.getTable(FontFile2.CMAP);
Map glyphMap = null;
if (currentCmap == null) {
glyphMap = new HashMap();
if (this.originalFont.getToUnicode() == null) {
int gCount = glyphs.getGlyphCount();
for (int z = 0; z < gCount; z++) {
glyphMap.put(z, z);
}
}
else {
for (int z = 0; z < 65536; z++) {
if (this.originalFont.getUnicodeMapping(z) != null) {
String str = this.originalFont.getUnicodeMapping(z);
int adjValue = getAdjustedUniValue(str);
if (adjValue >= 0) {
glyphMap.put(adjValue, z);
}
}
}
}
}
else {
glyphMap = currentCmap.buildCharStringTable();
}
for (int z = 0; z < 256; z++) {
this.glyphIndexToChar[1][z] = glyphMap.get(z) != null ? glyphMap.get(z) : 0;
}
return 262;
}
// this method should be combined to createFormat4Map subroutine in future
// @see createFormat4Map
private int createFormat4MapForTT(PdfJavaGlyphs glyphs) {
TTGlyphs ttGlyphs = (TTGlyphs) glyphs;
CMAP currentCmap = (CMAP) ttGlyphs.getTable(FontFile2.CMAP);
Map glyphMap = null;
if (currentCmap == null) {
glyphMap = new HashMap();
if (this.originalFont.getToUnicode() == null) {
int gCount = glyphs.getGlyphCount();
for (int z = 0; z < gCount; z++) {
glyphMap.put(z, z);
}
}
else {
for (int z = 0; z < 65536; z++) {
if (this.originalFont.getUnicodeMapping(z) != null) {
String str = this.originalFont.getUnicodeMapping(z);
int adjValue = getAdjustedUniValue(str);
if (adjValue >= 0) {
glyphMap.put(adjValue, z);
}
// System.out.println(z+" "+originalFont.getUnicodeMapping(z)+" "+adjValue);
}
}
// If there's no space character, find one
if (glyphMap.get(0x20) == null) {
Object[] keys = ttGlyphs.getCharStrings().keySet().toArray();
Hmtx hmtx = (Hmtx) ttGlyphs.getTable(FontFile2.HMTX);
int currentGlyph = 0;
// Ensure glyph is not already assigned and has a nonzero width
for (Object key : keys) {
while (currentGlyph < this.originalFont.getGlyphData().getGlyphCount() && hmtx.getUnscaledWidth(currentGlyph) == 0) {
currentGlyph++;
}
if (currentGlyph == (Integer) key) {
currentGlyph++;
}
}
// Place the glyph at 0x20 (unicode space)
glyphMap.put(0x20, currentGlyph);
}
}
}
else {
glyphMap = currentCmap.buildCharStringTable();
}
int segCount = 0;
HashMap uniToRawMap = new HashMap();
HashMap uniToCharCodeMap = new HashMap();
HashMap pointerCodeMap = new HashMap();// holds the byte array and relative pointers to access it in id
// range offset
ArrayList pointerList = new ArrayList();// to calculate the bytearray to be inserted after id range offset
for (Object key : glyphMap.keySet()) {
int k = (Integer) key;
int v = glyphMap.get(k);
int uniValue;
if (currentCmap == null || currentCmap.hasFormat4()) {
uniValue = k;
}
else {
uniValue = (this.originalFont.getUnicodeMapping(k) != null) ? this.originalFont.getUnicodeMapping(k).charAt(0) : k;
}
uniToRawMap.put(uniValue, k);
uniToCharCodeMap.put(uniValue, v);
// System.out.println(k+" == "+uniValue+" == >"+v);
}
Object[] uniToRawMapKeys = uniToRawMap.keySet().toArray();
Arrays.sort(uniToRawMapKeys);
segCount = uniToRawMap.size() + 1;
this.endCode = new int[segCount];
this.endCode[segCount - 1] = 0xffff;
for (int z = 0; z < segCount - 1; z++) {
this.endCode[z] = (Integer) uniToRawMapKeys[z];
}
this.startCode = new int[segCount];
this.startCode[segCount - 1] = 0xffff;
for (int z = 0; z < segCount - 1; z++) {
this.startCode[z] = (Integer) uniToRawMapKeys[z];
}
this.idDelta = new int[segCount];
this.idDelta[segCount - 1] = 1;
for (int z = 0; z < segCount - 1; z++) {
this.idDelta[z] = uniToCharCodeMap.get(this.startCode[z]) - this.startCode[z];
if (this.idDelta[z] >= 0) {// according to spec id delta should be zero if startcode is less than character code
this.idDelta[z] = 0;
pointerCodeMap.put(z, 2 * (segCount - (z)) + (2 * pointerList.size()));
pointerList.add(uniToCharCodeMap.get(this.startCode[z]));
}
}
this.idRangeOffset = new int[segCount];
this.idRangeOffset[segCount - 1] = 0;
for (int z = 0; z < segCount - 1; z++) {
this.idRangeOffset[z] = pointerCodeMap.get(z) != null ? pointerCodeMap.get(z) : 0;
}
int segX2 = segCount * 2;
int searchRange = (int) (2 * (Math.pow(2, Math.floor(Math.log(segCount) / Math.log(2)))));
int entrySelector = (int) (Math.log(searchRange / 2) / Math.log(2));
int rangeShift = 2 * segCount - searchRange;
this.CMAPreserved = new int[] { 0, 0, 0 };
this.CMAPsegCount = new int[] { segX2, 0, segX2 };
this.CMAPsearchRange = new int[] { searchRange, 0, searchRange };
this.CMAPentrySelector = new int[] { entrySelector, 0, entrySelector };
this.CMAPrangeShift = new int[] { rangeShift, 0, rangeShift };
if (pointerList.size() > 0) {
this.glyphIdArray = new int[pointerList.size()];
for (int z = 0; z < this.glyphIdArray.length; z++) {
this.glyphIdArray[z] = pointerList.get(z);
}
}
return 16 + (segCount * 8) + (pointerList.size() * 2);
}
private int createFormat4Map(PdfJavaGlyphs glyphs, boolean is1C, String[] glyphList) {
// create array and fill with dummy values
int[] unicodeToGlyph = new int[65536];
for (int i = 0; i < unicodeToGlyph.length; i++) {
unicodeToGlyph[i] = Integer.MAX_VALUE;
}
/**
* get mapped values to rebuild table using data we extracted from fonts
*/
if (this.originalFont.getFontType() == StandardFonts.TRUETYPE) { // TTF
int count = glyphList.length;
int ptr;
for (int ii = 0; ii < count; ii++) {
if (glyphList[ii] != null) {
ptr = Integer.parseInt(glyphList[ii]);
if (ptr > 0) {
unicodeToGlyph[ii] = ptr;
}
}
}
}
else { // PS version
getNonTTGlyphData(glyphs, is1C, glyphList, unicodeToGlyph);
}
// Find ranges of unicode characters with valid glyphs
int[] rangeStart = new int[40000];
int[] rangeEnd = new int[40000];
int segCount = 0;
boolean inRange = false;
for (int i = 0; i < 65536; i++) {
if (inRange && unicodeToGlyph[i] == Integer.MAX_VALUE) {
inRange = false;
rangeEnd[segCount] = i - 1;
segCount++;
}
else
if (!inRange && unicodeToGlyph[i] != Integer.MAX_VALUE) {
inRange = true;
rangeStart[segCount] = i;
}
}
// Handle unicode replacement character
if (unicodeToGlyph[0xFFFD] == Integer.MAX_VALUE) {
rangeStart[segCount] = 0xFFFD;
rangeEnd[segCount] = 0xFFFD;
unicodeToGlyph[0xFFFD] = 1;
segCount++;
}
// Deal with end character
if (unicodeToGlyph[0xFFFF] == Integer.MAX_VALUE) {
rangeStart[segCount] = 0xFFFF;
rangeEnd[segCount] = 0xFFFF;
unicodeToGlyph[0xFFFF] = 1;
segCount++;
}
// Initialise values
int segX2 = segCount * 2;
this.CMAPsegCount = new int[] { segX2, 0, segX2 };
int searchRange = 1;
while (searchRange * 2 <= segCount)
searchRange *= 2;
searchRange *= 2;
this.CMAPsearchRange = new int[] { searchRange, 0, searchRange };
int entrySelector = 0;
int working = searchRange / 2;
while (working > 1) {
working /= 2;
entrySelector++;
}
this.CMAPentrySelector = new int[] { entrySelector, 0, entrySelector };
int rangeShift = segX2 - searchRange;
this.CMAPrangeShift = new int[] { rangeShift, 0, rangeShift };
this.endCode = rangeEnd;
this.CMAPreserved = new int[] { 0, 0, 0 };
this.startCode = rangeStart;
this.idRangeOffset = new int[segCount];
// Check ranges are sequential in key and value and flag if not
int glyphIdArrayLength = 0;
for (int i = 0; i < segCount; i++) {
int diff = unicodeToGlyph[rangeStart[i]] - rangeStart[i];
for (int j = rangeStart[i] + 1; j <= rangeEnd[i]; j++) {
if (unicodeToGlyph[j] - j != diff) {
this.idRangeOffset[i] = -1;
glyphIdArrayLength += (rangeEnd[i] + 1) - rangeStart[i];
break;
}
}
}
// Deal with non-zero idRangeOffset values! (flagged as -1)
// WARNING! This is currently untested so results may vary!
this.glyphIdArray = new int[glyphIdArrayLength];
// Calculate deltas
this.idDelta = new int[segCount];
// Go through segments for deltas or offsets
int addressPointer = 16 + (segCount * 8);
int arrayPointer = 0;
for (int i = 0; i < this.idRangeOffset.length; i++) {
if (this.idRangeOffset[i] == 0) {
// Use a direct offset from the unicode values
this.idDelta[i] = unicodeToGlyph[rangeStart[i]] - rangeStart[i] - 1;
}
else {
// Use glyphIdArray
this.idRangeOffset[i] = addressPointer - (16 + (segCount * 6) + (i * 2));
for (int j = rangeStart[i]; j <= rangeEnd[i]; j++) {
this.glyphIdArray[arrayPointer] = unicodeToGlyph[j] - 1;
addressPointer += 2;
arrayPointer++;
}
}
}
// Return table length
return 16 + (segCount * 8) + (glyphIdArrayLength * 2);
}
private void getNonTTGlyphData(PdfJavaGlyphs glyphs, boolean is1C, String[] glyphList, int[] unicodeToGlyph) {
// Get glyphs present and put in unicode array
int cid = 0;
for (int i = 0; i < glyphs.getGlyphCount() + 1; i++) {
String val = null;
if (this.originalFont != null && this.originalFont.getGlyphData().isIdentity() && this.originalFont.hasToUnicode() && i > 1) {
val = this.originalFont.getUnicodeMapping(cid);
while (val == null && cid < 0xd800) {
cid++;
val = this.originalFont.getUnicodeMapping(cid);
}
if (val != null) {
unicodeToGlyph[val.charAt(0)] = i;
}
cid++;
}
else {
if (val == null && is1C) {
val = glyphs.getIndexForCharString(i);
}
else
if (val == null && i < glyphList.length) {
val = glyphList[i];
}
if (val != null) {
int uc = StandardFonts.getIDForGlyphName(this.fontName, val);
if (uc >= 0 && uc < unicodeToGlyph.length) {
if (is1C) unicodeToGlyph[uc] = i;
else unicodeToGlyph[uc] = i + 1;
}
}
}
}
}
/**
* used to turn Ps into OTF
*/
public CMAPWriter(String fontName, PdfFont currentFontData, PdfFont originalFont, PdfJavaGlyphs glyphs, String[] glyphList) {
this.fontName = fontName;
this.originalFont = originalFont;
/** make a list of glyfs from TT font */
// commented out because data manipulation is handled differently in TT fonts
// @see createFormat4MapForTT in CMAPWriter and
// @see buildCharStringTable() in CMAP
// if(glyphList==null && originalFont.getFontType()==StandardFonts.TRUETYPE){
// Map charStringsFoundInTTFont=originalFont.getGlyphData().getCharStrings();
//
// if(charStringsFoundInTTFont!=null){
// int size=charStringsFoundInTTFont.size();
// glyphList=new String[size];
// Iterator i=charStringsFoundInTTFont.keySet().iterator();
// int ptr=0;
// while(i.hasNext()){
// glyphList[ptr]=i.next().toString();
// ptr++;
// }
// }
// }
/**
* initialise the 3 tables we will need for out fonts in browser
*/
this.numberSubtables = 3;
this.CMAPformats = new int[] { 4, 0, 4 };
this.glyphIndexToChar = new int[3][256];
this.platformID = new int[] { 0, 1, 3 };
this.platformSpecificID = new int[] { 3, 0, 1 };
this.CMAPlang = new int[] { 0, 0, 0 };
// Initialise format 4 fields
int format4Length = 0;
if (originalFont.getFontType() == StandardFonts.TRUETYPE) {
format4Length = createFormat4MapForTT(glyphs);
createFormat0MapForTT(glyphs);
}
else { // for cff font handling
format4Length = createFormat4Map(glyphs, currentFontData.is1C(), glyphList);
int enc = StandardFonts.MAC;
StandardFonts.checkLoaded(enc);
// Get glyphs present and put in mac encoding array
for (int i = 0; i < glyphs.getGlyphCount() + 1; i++) {
String val = null;
if (currentFontData.is1C()) val = glyphs.getIndexForCharString(i);
else
if (i < glyphList.length) val = glyphList[i];
if (val != null) {
int id = StandardFonts.lookupCharacterIndex(val, StandardFonts.MAC);
int glyph = i;
if (currentFontData.is1C()) {
glyph -= 1;
if (i == 1) glyph = 1;
}
if (id >= 0 && id < 256) this.glyphIndexToChar[1][id] = glyph;
}
}
}
this.CMAPlength = new int[] { format4Length, 262, format4Length };
this.CMAPsubtables = new int[] { 28, 28 + (format4Length * 2), 28 + format4Length };
}
@Override
public byte[] writeTable() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
final boolean debug = false;
if (debug) System.out.println("write CMAP " + this);
// LogWriter.writeMethod("{readCMAPTable}", 0);
// read 'cmap' table
{
int numberSubtables = this.numberSubtables;
ArrayList tables = new ArrayList();
for (int i = 0; i < numberSubtables; i++) {
tables.add(i);
}
bos.write(FontWriter.setNextUint16(this.id));
bos.write(FontWriter.setNextUint16(numberSubtables));
for (int j = 0; j < numberSubtables; j++) {
int i = tables.get(j);
boolean isDuplicate = i < 0;
if (i < 0) i = -i;
if (isDuplicate) {
bos.write(FontWriter.setNextUint16(0));
bos.write(FontWriter.setNextUint16(3));
}
else {
bos.write(FontWriter.setNextUint16(this.platformID[i]));
bos.write(FontWriter.setNextUint16(this.platformSpecificID[i]));
}
bos.write(FontWriter.setNextUint32(this.CMAPsubtables[i]));
if (debug) System.out.println("platformID[i]=" + this.platformID[i] + " platformSpecificID[i]=" + this.platformSpecificID[i]
+ " CMAPsubtables[i]=" + this.CMAPsubtables[i]);
}
// work our correct order for subtables
int[] offset = new int[numberSubtables];
int[] order = new int[numberSubtables];
for (int j = 0; j < numberSubtables; j++) {
int i = tables.get(j);
if (i < 0) i = -i;
offset[i] = this.CMAPsubtables[i];
order[j] = i;
}
order = Sorts.quicksort(offset, order);
// now write back each subtable
for (int j = 0; j < numberSubtables; j++) {
int i = order[j];
// any padding
while (bos.size() < this.CMAPsubtables[i]) {
bos.write((byte) 0);
}
// }
// assume 16 bit format to start
bos.write(FontWriter.setNextUint16(this.CMAPformats[i]));
// length
bos.write(FontWriter.setNextUint16(this.CMAPlength[i]));
// lang
bos.write(FontWriter.setNextUint16(this.CMAPlang[i]));
// actual data
if (this.CMAPformats[i] == 0 && this.CMAPlength[i] == 262) {
for (int glyphNum = 0; glyphNum < 256; glyphNum++) {
bos.write(FontWriter.setNextUint8(this.glyphIndexToChar[i][glyphNum]));
}
}
else
if (this.CMAPformats[i] == 4) {
// @sam -works for TT.
// to make it work for OTF we need to setup the values for all
// the variables in the OTF COnstructor
// public CMAPWriter(PdfFont currentFontData,PdfJavaGlyphs glyphs)
// segcount
int segCount = this.CMAPsegCount[i] / 2;
bos.write(FontWriter.setNextUint16(this.CMAPsegCount[i]));
bos.write(FontWriter.setNextUint16(this.CMAPsearchRange[i]));
bos.write(FontWriter.setNextUint16(this.CMAPentrySelector[i]));
bos.write(FontWriter.setNextUint16(this.CMAPrangeShift[i]));
for (int jj = 0; jj < segCount; jj++)
bos.write(FontWriter.setNextUint16(this.endCode[jj]));
bos.write(FontWriter.setNextUint16(this.CMAPreserved[i]));
for (int jj = 0; jj < segCount; jj++)
bos.write(FontWriter.setNextUint16(this.startCode[jj]));
for (int jj = 0; jj < segCount; jj++)
bos.write(FontWriter.setNextUint16(this.idDelta[jj]));
for (int jj = 0; jj < segCount; jj++)
bos.write(FontWriter.setNextUint16(this.idRangeOffset[jj]));
if (this.glyphIdArray != null) {
for (int aGlyphIdArray : this.glyphIdArray)
bos.write(FontWriter.setNextUint16(aGlyphIdArray));
}
// }else if(CMAPformats[j]==6){
// int firstCode=currentFontFile.getNextUint16();
// int entryCount=currentFontFile.getNextUint16();
//
// f6glyphIdArray = new int[firstCode+entryCount];
// for(int jj=0;jj