Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.lowagie.text.pdf.CJKFont Maven / Gradle / Ivy
/*
* $Id: CJKFont.java 4065 2009-09-16 23:09:11Z psoares33 $
*
* Copyright 2000, 2001, 2002 by Paulo Soares.
*
* The contents of this file are subject to the Mozilla Public License Version 1.1
* (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the License.
*
* The Original Code is 'iText, a free JAVA-PDF library'.
*
* The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
* the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
* All Rights Reserved.
* Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
* are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
*
* Contributor(s): all the names of the contributors are added in the source code
* where applicable.
*
* Alternatively, the contents of this file may be used under the terms of the
* LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
* provisions of LGPL are applicable instead of those above. If you wish to
* allow use of your version of this file only under the terms of the LGPL
* License and not to allow others to use your version of this file under
* the MPL, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the LGPL.
* If you do not delete the provisions above, a recipient may use your version
* of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MPL as stated above or under the terms of the GNU
* Library General Public License as published by the Free Software Foundation;
* either version 2 of the License, or 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 Library general Public License for more
* details.
*
* If you didn't download this code from the following link, you should check if
* you aren't using an obsolete version:
* https://github.com/LibrePDF/OpenPDF
*/
package com.lowagie.text.pdf;
import com.lowagie.text.DocumentException;
import com.lowagie.text.error_messages.MessageLocalization;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
/**
* Creates a CJK font compatible with the fonts in the Adobe Asian font Pack.
*
* @author Paulo Soares ([email protected] )
*/
class CJKFont extends BaseFont {
/**
* The encoding used in the PDF document for CJK fonts
*/
static final String CJK_ENCODING = "UnicodeBigUnmarked";
private static final int FIRST = 0;
private static final int BRACKET = 1;
private static final int SERIAL = 2;
private static final int V1Y = 880;
static Properties cjkFonts = new Properties();
static Properties cjkEncodings = new Properties();
Hashtable allCMaps = new Hashtable<>();
static ConcurrentHashMap> allFonts = new ConcurrentHashMap<>(
500, 0.85f, 64);
private static boolean propertiesLoaded = false;
private static Object initLock = new Object();
/** The font name */
private String fontName;
/** The style modifier */
private String style = "";
/** The CMap name associated with this font */
private String CMap;
private boolean cidDirect = false;
private char[] translationMap;
private IntHashtable vMetrics;
private IntHashtable hMetrics;
private HashMap fontDesc;
private boolean vertical = false;
private static void loadProperties() {
if (propertiesLoaded) {
return;
}
synchronized (initLock) {
if (propertiesLoaded) {
return;
}
try {
InputStream is = getResourceStream(RESOURCE_PATH
+ "cjkfonts.properties");
cjkFonts.load(is);
is.close();
is = getResourceStream(RESOURCE_PATH
+ "cjkencodings.properties");
cjkEncodings.load(is);
is.close();
} catch (Exception e) {
cjkFonts = new Properties();
cjkEncodings = new Properties();
}
propertiesLoaded = true;
}
}
/**
* Creates a CJK font.
*
* @param fontName
* the name of the font
* @param enc
* the encoding of the font
* @param emb
* always false
. CJK font and not embedded
* @throws DocumentException
* on error
*/
CJKFont(String fontName, String enc, boolean emb) throws DocumentException {
loadProperties();
fontType = FONT_TYPE_CJK;
String nameBase = getBaseName(fontName);
if (!isCJKFont(nameBase, enc)) {
throw new DocumentException(MessageLocalization.getComposedMessage(
"font.1.with.2.encoding.is.not.a.cjk.font", fontName, enc));
}
if (nameBase.length() < fontName.length()) {
style = fontName.substring(nameBase.length());
fontName = nameBase;
}
this.fontName = fontName;
encoding = CJK_ENCODING;
vertical = enc.endsWith("V");
CMap = enc;
if (enc.startsWith("Identity-")) {
cidDirect = true;
String s = cjkFonts.getProperty(fontName);
s = s.substring(0, s.indexOf('_'));
char[] c = allCMaps.get(s);
if (c == null) {
c = readCMap(s);
if (c == null) {
throw new DocumentException(
MessageLocalization.getComposedMessage(
"the.cmap.1.does.not.exist.as.a.resource",
s));
}
c[CID_NEWLINE] = '\n';
allCMaps.put(s, c);
}
translationMap = c;
} else {
char[] c = allCMaps.get(enc);
if (c == null) {
String s = cjkEncodings.getProperty(enc);
if (s == null) {
throw new DocumentException(
MessageLocalization
.getComposedMessage(
"the.resource.cjkencodings.properties.does.not.contain.the.encoding.1",
enc));
}
StringTokenizer tk = new StringTokenizer(s);
String nt = tk.nextToken();
c = allCMaps.get(nt);
if (c == null) {
c = readCMap(nt);
allCMaps.put(nt, c);
}
if (tk.hasMoreTokens()) {
String nt2 = tk.nextToken();
char[] m2 = readCMap(nt2);
for (int k = 0; k < 0x10000; ++k) {
if (m2[k] == 0) {
m2[k] = c[k];
}
}
allCMaps.put(enc, m2);
c = m2;
}
}
translationMap = c;
}
fontDesc = allFonts.get(fontName);
if (fontDesc == null) {
fontDesc = readFontProperties(fontName);
allFonts.putIfAbsent(fontName, fontDesc);
fontDesc = allFonts.get(fontName);
}
hMetrics = (IntHashtable) fontDesc.get("W");
vMetrics = (IntHashtable) fontDesc.get("W2");
}
/**
* Checks if its a valid CJK font.
*
* @param fontName
* the font name
* @param enc
* the encoding
* @return true
if it is CJK font
*/
public static boolean isCJKFont(String fontName, String enc) {
loadProperties();
String encodings = cjkFonts.getProperty(fontName);
return encodings != null
&& (enc.equals("Identity-H") || enc.equals("Identity-V") || encodings.contains("_" + enc + "_"));
}
/**
* Gets the width of a char
in normalized 1000 units.
*
* @param char1
* the unicode char
to get the width of
* @return the width in normalized 1000 units
*/
@Override
public int getWidth(int char1) {
int c = char1;
if (!cidDirect) {
c = translationMap[c];
}
int v;
if (vertical) {
v = vMetrics.get(c);
} else {
v = hMetrics.get(c);
}
if (v > 0) {
return v;
} else {
return 1000;
}
}
@Override
public int getWidth(String text) {
int total = 0;
for (int k = 0; k < text.length(); ++k) {
int c = text.charAt(k);
if (!cidDirect) {
c = translationMap[c];
}
int v;
if (vertical) {
v = vMetrics.get(c);
} else {
v = hMetrics.get(c);
}
if (v > 0) {
total += v;
} else {
total += 1000;
}
}
return total;
}
@Override
int getRawWidth(int c, String name) {
return 0;
}
@Override
public int getKerning(int char1, int char2) {
return 0;
}
private PdfDictionary getFontDescriptor() {
PdfDictionary dic = new PdfDictionary(PdfName.FONTDESCRIPTOR);
dic.put(PdfName.ASCENT, new PdfLiteral((String) fontDesc.get("Ascent")));
dic.put(PdfName.CAPHEIGHT,
new PdfLiteral((String) fontDesc.get("CapHeight")));
dic.put(PdfName.DESCENT,
new PdfLiteral((String) fontDesc.get("Descent")));
dic.put(PdfName.FLAGS, new PdfLiteral((String) fontDesc.get("Flags")));
dic.put(PdfName.FONTBBOX,
new PdfLiteral((String) fontDesc.get("FontBBox")));
dic.put(PdfName.FONTNAME, new PdfName(fontName + style));
dic.put(PdfName.ITALICANGLE,
new PdfLiteral((String) fontDesc.get("ItalicAngle")));
dic.put(PdfName.STEMV, new PdfLiteral((String) fontDesc.get("StemV")));
PdfDictionary pdic = new PdfDictionary();
pdic.put(PdfName.PANOSE, new PdfString((String) fontDesc.get("Panose"),
null));
dic.put(PdfName.STYLE, pdic);
return dic;
}
private PdfDictionary getCIDFont(PdfIndirectReference fontDescriptor,
IntHashtable cjkTag) {
PdfDictionary dic = new PdfDictionary(PdfName.FONT);
dic.put(PdfName.SUBTYPE, PdfName.CIDFONTTYPE0);
dic.put(PdfName.BASEFONT, new PdfName(fontName + style));
dic.put(PdfName.FONTDESCRIPTOR, fontDescriptor);
int[] keys = cjkTag.toOrderedKeys();
String w = convertToHCIDMetrics(keys, hMetrics);
if (w != null) {
dic.put(PdfName.W, new PdfLiteral(w));
}
if (vertical) {
w = convertToVCIDMetrics(keys, vMetrics, hMetrics);
if (w != null) {
dic.put(PdfName.W2, new PdfLiteral(w));
}
} else {
dic.put(PdfName.DW, new PdfNumber(1000));
}
PdfDictionary cdic = new PdfDictionary();
cdic.put(PdfName.REGISTRY,
new PdfString((String) fontDesc.get("Registry"), null));
cdic.put(PdfName.ORDERING,
new PdfString((String) fontDesc.get("Ordering"), null));
cdic.put(PdfName.SUPPLEMENT,
new PdfLiteral((String) fontDesc.get("Supplement")));
dic.put(PdfName.CIDSYSTEMINFO, cdic);
return dic;
}
private PdfDictionary getFontBaseType(PdfIndirectReference CIDFont) {
PdfDictionary dic = new PdfDictionary(PdfName.FONT);
dic.put(PdfName.SUBTYPE, PdfName.TYPE0);
String name = fontName;
if (style.length() > 0) {
name += "-" + style.substring(1);
}
name += "-" + CMap;
dic.put(PdfName.BASEFONT, new PdfName(name));
dic.put(PdfName.ENCODING, new PdfName(CMap));
dic.put(PdfName.DESCENDANTFONTS, new PdfArray(CIDFont));
return dic;
}
@Override
void writeFont(PdfWriter writer, PdfIndirectReference ref, Object[] params)
throws DocumentException, IOException {
IntHashtable cjkTag = (IntHashtable) params[0];
PdfIndirectReference ind_font = null;
PdfObject pobj = null;
PdfIndirectObject obj = null;
pobj = getFontDescriptor();
if (pobj != null) {
obj = writer.addToBody(pobj);
ind_font = obj.getIndirectReference();
}
pobj = getCIDFont(ind_font, cjkTag);
if (pobj != null) {
obj = writer.addToBody(pobj);
ind_font = obj.getIndirectReference();
}
pobj = getFontBaseType(ind_font);
writer.addToBody(pobj, ref);
}
/**
* You can't get the FontStream of a CJK font (CJK fonts are never
* embedded), so this method always returns null.
*
* @return null
* @since 2.1.3
*/
@Override
public PdfStream getFullFontStream() {
return null;
}
private float getDescNumber(String name) {
return Integer.parseInt((String) fontDesc.get(name));
}
private float getBBox(int idx) {
String s = (String) fontDesc.get("FontBBox");
StringTokenizer tk = new StringTokenizer(s, " []\r\n\t\f");
String ret = tk.nextToken();
for (int k = 0; k < idx; ++k) {
ret = tk.nextToken();
}
return Integer.parseInt(ret);
}
/**
* Gets the font parameter identified by key
. Valid values for
* key
are ASCENT
, CAPHEIGHT
,
* DESCENT
and ITALICANGLE
.
*
* @param key
* the parameter to be extracted
* @param fontSize
* the font size in points
* @return the parameter in points
*/
@Override
public float getFontDescriptor(int key, float fontSize) {
switch (key) {
case AWT_ASCENT:
case ASCENT:
return getDescNumber("Ascent") * fontSize / 1000;
case CAPHEIGHT:
return getDescNumber("CapHeight") * fontSize / 1000;
case AWT_DESCENT:
case DESCENT:
return getDescNumber("Descent") * fontSize / 1000;
case ITALICANGLE:
return getDescNumber("ItalicAngle");
case BBOXLLX:
return fontSize * getBBox(0) / 1000;
case BBOXLLY:
return fontSize * getBBox(1) / 1000;
case BBOXURX:
return fontSize * getBBox(2) / 1000;
case BBOXURY:
return fontSize * getBBox(3) / 1000;
case AWT_LEADING:
return 0;
case AWT_MAXADVANCE:
return fontSize * (getBBox(2) - getBBox(0)) / 1000;
}
return 0;
}
@Override
public String getPostscriptFontName() {
return fontName;
}
/**
* Gets the full name of the font. If it is a True Type font each array
* element will have {Platform ID, Platform Encoding ID, Language ID, font
* name}. The interpretation of this values can be found in the Open Type
* specification, chapter 2, in the 'name' table.
* For the other fonts the array has a single element with {"", "", "", font
* name}.
*
* @return the full name of the font
*/
@Override
public String[][] getFullFontName() {
return new String[][] { { "", "", "", fontName } };
}
/**
* Gets all the entries of the names-table. If it is a True Type font each
* array element will have {Name ID, Platform ID, Platform Encoding ID,
* Language ID, font name}. The interpretation of this values can be found
* in the Open Type specification, chapter 2, in the 'name' table.
* For the other fonts the array has a single element with {"4", "", "", "",
* font name}.
*
* @return the full name of the font
*/
@Override
public String[][] getAllNameEntries() {
return new String[][] { { "4", "", "", "", fontName } };
}
/**
* Gets the family name of the font. If it is a True Type font each array
* element will have {Platform ID, Platform Encoding ID, Language ID, font
* name}. The interpretation of this values can be found in the Open Type
* specification, chapter 2, in the 'name' table.
* For the other fonts the array has a single element with {"", "", "", font
* name}.
*
* @return the family name of the font
*/
@Override
public String[][] getFamilyFontName() {
return getFullFontName();
}
static char[] readCMap(String name) {
try {
name = name + ".cmap";
InputStream is = getResourceStream(RESOURCE_PATH + name);
char[] c = new char[0x10000];
for (int k = 0; k < 0x10000; ++k) {
c[k] = (char) ((is.read() << 8) + is.read());
}
is.close();
return c;
} catch (Exception e) {
// empty on purpose
}
return null;
}
static IntHashtable createMetric(String s) {
IntHashtable h = new IntHashtable();
StringTokenizer tk = new StringTokenizer(s);
while (tk.hasMoreTokens()) {
int n1 = Integer.parseInt(tk.nextToken());
h.put(n1, Integer.parseInt(tk.nextToken()));
}
return h;
}
static String convertToHCIDMetrics(int[] keys, IntHashtable h) {
if (keys.length == 0) {
return null;
}
int lastCid = 0;
int lastValue = 0;
int start;
for (start = 0; start < keys.length; ++start) {
lastCid = keys[start];
lastValue = h.get(lastCid);
if (lastValue != 0) {
++start;
break;
}
}
if (lastValue == 0) {
return null;
}
StringBuilder buf = new StringBuilder();
buf.append('[');
buf.append(lastCid);
int state = FIRST;
for (int k = start; k < keys.length; ++k) {
int cid = keys[k];
int value = h.get(cid);
if (value == 0) {
continue;
}
switch (state) {
case FIRST: {
if (cid == lastCid + 1 && value == lastValue) {
state = SERIAL;
} else if (cid == lastCid + 1) {
state = BRACKET;
buf.append('[').append(lastValue);
} else {
buf.append('[').append(lastValue).append(']').append(cid);
}
break;
}
case BRACKET: {
if (cid == lastCid + 1 && value == lastValue) {
state = SERIAL;
buf.append(']').append(lastCid);
} else if (cid == lastCid + 1) {
buf.append(' ').append(lastValue);
} else {
state = FIRST;
buf.append(' ').append(lastValue).append(']').append(cid);
}
break;
}
case SERIAL: {
if (cid != lastCid + 1 || value != lastValue) {
buf.append(' ').append(lastCid).append(' ')
.append(lastValue).append(' ').append(cid);
state = FIRST;
}
break;
}
}
lastValue = value;
lastCid = cid;
}
switch (state) {
case FIRST: {
buf.append('[').append(lastValue).append("]]");
break;
}
case BRACKET: {
buf.append(' ').append(lastValue).append("]]");
break;
}
case SERIAL: {
buf.append(' ').append(lastCid).append(' ').append(lastValue)
.append(']');
break;
}
}
return buf.toString();
}
static String convertToVCIDMetrics(int[] keys, IntHashtable v,
IntHashtable h) {
if (keys.length == 0) {
return null;
}
int lastCid = 0;
int lastValue = 0;
int lastHValue = 0;
int start;
for (start = 0; start < keys.length; ++start) {
lastCid = keys[start];
lastValue = v.get(lastCid);
if (lastValue != 0) {
++start;
break;
} else {
lastHValue = h.get(lastCid);
}
}
if (lastValue == 0) {
return null;
}
if (lastHValue == 0) {
lastHValue = 1000;
}
StringBuilder buf = new StringBuilder();
buf.append('[');
buf.append(lastCid);
int state = FIRST;
for (int k = start; k < keys.length; ++k) {
int cid = keys[k];
int value = v.get(cid);
if (value == 0) {
continue;
}
int hValue = h.get(lastCid);
if (hValue == 0) {
hValue = 1000;
}
switch (state) {
case FIRST: {
if (cid == lastCid + 1 && value == lastValue
&& hValue == lastHValue) {
state = SERIAL;
} else {
buf.append(' ').append(lastCid).append(' ')
.append(-lastValue).append(' ')
.append(lastHValue / 2).append(' ').append(V1Y)
.append(' ').append(cid);
}
break;
}
case SERIAL: {
if (cid != lastCid + 1 || value != lastValue
|| hValue != lastHValue) {
buf.append(' ').append(lastCid).append(' ')
.append(-lastValue).append(' ')
.append(lastHValue / 2).append(' ').append(V1Y)
.append(' ').append(cid);
state = FIRST;
}
break;
}
}
lastValue = value;
lastCid = cid;
lastHValue = hValue;
}
buf.append(' ').append(lastCid).append(' ').append(-lastValue)
.append(' ').append(lastHValue / 2).append(' ').append(V1Y)
.append(" ]");
return buf.toString();
}
static HashMap readFontProperties(String name) {
try {
name += ".properties";
InputStream is = getResourceStream(RESOURCE_PATH + name);
Properties p = new Properties();
p.load(is);
is.close();
IntHashtable W = createMetric(p.getProperty("W"));
p.remove("W");
IntHashtable W2 = createMetric(p.getProperty("W2"));
p.remove("W2");
HashMap map = new HashMap<>();
for (Enumeration e = p.keys(); e.hasMoreElements();) {
Object obj = e.nextElement();
map.put(obj, p.getProperty((String) obj));
}
map.put("W", W);
map.put("W2", W2);
return map;
} catch (Exception e) {
// empty on purpose
}
return null;
}
@Override
public int getUnicodeEquivalent(int c) {
if (cidDirect) {
return translationMap[c];
}
return c;
}
@Override
public int getCidCode(int c) {
if (cidDirect) {
return c;
}
return translationMap[c];
}
/**
* Checks if the font has any kerning pairs.
*
* @return always false
*/
@Override
public boolean hasKernPairs() {
return false;
}
/**
* Checks if a character exists in this font.
*
* @param c
* the character to check
* @return true
if the character has a glyph,
* false
otherwise
*/
@Override
public boolean charExists(int c) {
if (c >= translationMap.length) {
return false;
}
return translationMap[c] != 0;
}
/**
* Sets the character advance.
*
* @param c
* the character
* @param advance
* the character advance normalized to 1000 units
* @return true
if the advance was set, false
* otherwise. Will always return false
*/
@Override
public boolean setCharAdvance(int c, int advance) {
return false;
}
/**
* Sets the font name that will appear in the pdf font dictionary. Use with
* care as it can easily make a font unreadable if not embedded.
*
* @param name
* the new font name
*/
@Override
public void setPostscriptFontName(String name) {
fontName = name;
}
@Override
public boolean setKerning(int char1, int char2, int kern) {
return false;
}
@Override
public int[] getCharBBox(int c) {
return null;
}
@Override
protected int[] getRawCharBBox(int c, String name) {
return null;
}
}