- adding custom ttf font
- print transparent text
- set raw transformation matrix
This commit is contained in:
Jindra Petřík
2021-02-18 22:12:42 +01:00
parent fd48d65e1d
commit edb4686ebe
11 changed files with 1103 additions and 209 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
build.xml.data.CRC32=fb275268
build.xml.script.CRC32=7438dbfb
build.xml.stylesheet.CRC32=8064a381@1.74.2.48
build.xml.stylesheet.CRC32=f85dc8f2@1.95.0.48
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=fb275268
nbproject/build-impl.xml.script.CRC32=1ec9d904
nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.74.2.48
nbproject/build-impl.xml.script.CRC32=c29906ba
nbproject/build-impl.xml.stylesheet.CRC32=f89f7d21@1.95.0.48

View File

@@ -19,8 +19,12 @@ build.test.results.dir=${build.dir}/test/results
#debug.transport=dt_socket
debug.classpath=\
${run.classpath}
debug.modulepath=\
${run.modulepath}
debug.test.classpath=\
${run.test.classpath}
debug.test.modulepath=\
${run.test.modulepath}
# Files in build.classes.dir which should be excluded from distribution jar
dist.archive.excludes=
# This directory is removed when the project is cleaned:
@@ -35,6 +39,8 @@ javac.classpath=
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
javac.modulepath=
javac.processormodulepath=
javac.processorpath=\
${javac.classpath}
javac.source=1.7
@@ -42,6 +48,8 @@ javac.target=1.7
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
javac.test.modulepath=\
${javac.modulepath}
javac.test.processorpath=\
${javac.test.classpath}
javadoc.additionalparam=
@@ -67,9 +75,13 @@ run.classpath=\
# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
# To set system properties for unit tests define test-sys-prop.name=value:
run.jvmargs=
run.modulepath=\
${javac.modulepath}
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
run.test.modulepath=\
${javac.test.modulepath}
source.encoding=UTF-8
src.dir=src
test.src.dir=test

View File

@@ -20,10 +20,20 @@
*/
package gnu.jpdf;
import java.awt.FontFormatException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <p>This class is the base of the PDF generator. A PDFDocument class is
@@ -249,7 +259,87 @@ public class PDFDocument implements Serializable
add(ft);
fonts.addElement(ft);
return ft;
}
}
public PDFFont getEmbeddedFont(String font, int style, File file) throws FileNotFoundException, IOException {
for (PDFFont ft : fonts) {
if (ft.equals("/TrueType", font, style)) {
return ft;
}
}
PDFStream fontFile2 = new PDFStream() {
@Override
public void write(OutputStream os) throws IOException {
writeStart(os);
os.write("/Length1 ".getBytes());
os.write(Integer.toString(buf.size()).getBytes());
os.write("\n".getBytes());
writeStream(os);
}
};
fontFile2.setDeflate(true);
OutputStream ff2Os = fontFile2.getOutputStream();
InputStream is = new FileInputStream(file);
byte buf[] = new byte[1024];
int cnt = 0;
while ((cnt = is.read(buf)) > 0) {
ff2Os.write(buf, 0, cnt);
}
is.close();
//ff2Os.write("AHOJ".getBytes());
add(fontFile2);
// the font wasn't found, so create it
fontid++;
TtfParser par = new TtfParser();
try {
par.loadFromTTF(file, 1024);
} catch (FontFormatException ex) {
Logger.getLogger(PDFDocument.class.getName()).log(Level.SEVERE, null, ex);
}
PDFFontDescriptor fontDescriptor = new PDFFontDescriptor(font, par.getAscent(), par.getDescent(), par.getCapHeight(), 80/*?*/, "" + fontFile2.getSerialID() + " 0 R");
add(fontDescriptor);
final List<Integer> widthsList = new ArrayList<>();
List<Integer> glyphAdvances = par.getAdvanceWidths();
Map<Integer, Integer> cmap = par.getCmap();
for (int i = 32; i <= 255; i++) {
if (cmap.containsKey(i)) {
widthsList.add(glyphAdvances.get(cmap.get(i)));
} else {
widthsList.add(0);
}
}
PDFObject widthStream = new PDFObject(null) {
@Override
public void write(OutputStream os) throws IOException {
os.write(Integer.toString(objser).getBytes());
os.write(" 0 obj\n".getBytes());
os.write("[ ".getBytes());
for (int i = 0; i < widthsList.size(); i++) {
if (i > 0) {
os.write(" ".getBytes());
}
os.write(("" + widthsList.get(i)).getBytes());
}
os.write(" ]\n".getBytes());
os.write("endobj\n".getBytes());
}
};
add(widthStream);
PDFFont ft = new PDFEmbeddedFont("/F" + fontid, font, style, "" + fontDescriptor.getSerialID() + " 0 R", widthStream.getSerialID() + " 0 R", 32, 255);
add(ft);
fonts.addElement(ft);
return ft;
}
/**
* Sets a unique name to a PDFImage

View File

@@ -0,0 +1,78 @@
package gnu.jpdf;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
/**
*
* @author JPEXS
*/
public class PDFEmbeddedFont extends PDFFont {
private final String descriptor;
private String name;
private String font;
private String type;
private final String widths;
private final int firstChar;
private final int lastChar;
public PDFEmbeddedFont(String name, String font, int style, String descriptor, String widths, int firstChar, int lastChar) {
super(name, "/TrueType", font, style);
this.descriptor = descriptor;
this.name = name;
this.font = font;
this.type = "/TrueType";
this.widths = widths;
this.firstChar = firstChar;
this.lastChar = lastChar;
}
@Override
public void write(OutputStream os) throws IOException {
// Write the object header
writeStart(os);
// now the objects body
os.write("/Subtype ".getBytes());
os.write(type.getBytes());
os.write("\n/Name ".getBytes());
os.write(name.getBytes());
os.write("\n/BaseFont ".getBytes());
os.write(font.getBytes());
os.write("\n".getBytes());
//os.write("/Encoding /WinAnsiEncoding\n".getBytes());
os.write(("/FirstChar " + firstChar + "\n").getBytes());
os.write(("/LastChar " + lastChar + "\n").getBytes());
os.write(("/Widths " + widths + "\n").getBytes());
/*int cnt = 300;
os.write(("/FirstChar 0\n").getBytes());
os.write(("/LastChar " + (cnt - 1) + "\n").getBytes());
os.write(("/Widths [").getBytes());
for (int i = 0; i < cnt; i++) {
os.write((" " + i).getBytes());
}
os.write(("]\n").getBytes());
*/
//os.write("/Widths [500 583 587 796]".getBytes());
os.write("/FontDescriptor ".getBytes());
os.write(descriptor.getBytes());
os.write("\n".getBytes());
/*os.write("/ToUnicode ".getBytes());
os.write(toUnicode.getBytes());
os.write("\n".getBytes());*/
// finish off with its footer
writeEnd(os);
}
}

View File

@@ -0,0 +1,48 @@
package gnu.jpdf;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
/**
*
* @author JPEXS
*/
public class PDFFontDescriptor extends PDFObject implements Serializable {
private final String fontName;
private final int ascent;
private final int descent;
private final int capheight;
private final int stemv;
private final String fontFile2;
public PDFFontDescriptor(String fontName, int ascent, int descent, int capheight, int stemv, String fontFile2) {
super("/FontDescriptor");
this.fontName = fontName;
this.ascent = ascent;
this.descent = descent;
this.capheight = capheight;
this.stemv = stemv;
this.fontFile2 = fontFile2;
}
@Override
public void write(OutputStream os) throws IOException {
writeStart(os);
os.write("/FontName ".getBytes());
os.write(fontName.getBytes());
os.write("\n".getBytes());
os.write("/Flags 4\n".getBytes());
os.write("/FontBBox [0 -16 725 863]\n".getBytes());
os.write("/ItalicAngle 0\n".getBytes());
os.write(("/Ascent " + ascent + "\n").getBytes());
os.write(("/Descent " + descent + "\n").getBytes());
os.write(("/CapHeight " + capheight + "\n").getBytes());
os.write(("/StemV " + stemv + "\n").getBytes());
//os.write("/MissingWidth 0\n".getBytes());
os.write(("/FontFile2 " + fontFile2 + "\n").getBytes());
writeEnd(os);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -50,6 +50,9 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable
*/
// Dimensions of the object.
private int objwidth;
private int objheight;
// Dimensions of the image.
private int width;
@@ -74,7 +77,7 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable
*/
public PDFImage(Image img) {
this();
setImage(img,img.getWidth(this), img.getHeight(this), this);
setImage(img, 0, 0, img.getWidth(this), img.getHeight(this), this);
}
/**
@@ -87,9 +90,11 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable
* @param h an <code>int</code> value
* @param obs an <code>ImageObserver</code> value
*/
public PDFImage(Image img,ImageObserver obs) {
public PDFImage(Image img,int x,int y,int w,int h,ImageObserver obs) {
this();
setImage(img, img.getWidth(this), img.getHeight(this), obs);
objwidth = w;
objheight = h;
setImage(img, x, y, img.getWidth(this), img.getHeight(this), obs);
}
@@ -154,7 +159,7 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable
* @param h an <code>int</code> value
* @param obs an <code>ImageObserver</code> value
*/
public void setImage(Image img,int w,int h,ImageObserver obs) {
public void setImage(Image img,int x,int y,int w,int h,ImageObserver obs) {
this.img = img;
width = w;
height = h;
@@ -400,6 +405,5 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable
//}
return false;
}
} // end class PDFImage

View File

@@ -235,9 +235,11 @@ public class PDFOutline extends PDFObject implements Serializable
// the number of outlines in this document
if(parent==null) {
// were the top level node, so all are open by default
os.write("/Count ".getBytes());
os.write(Integer.toString(outlines.size()).getBytes());
os.write("\n".getBytes());
if (outlines.size() > 0) {
os.write("/Count ".getBytes());
os.write(Integer.toString(outlines.size()).getBytes());
os.write("\n".getBytes());
}
} else {
// were a decendent, so by default we are closed. Find out how many
// entries are below us

View File

@@ -22,6 +22,7 @@ package gnu.jpdf;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.print.PageFormat;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
@@ -217,6 +218,30 @@ public class PDFPage extends PDFObject implements Serializable
return f;
}
public PDFFont getEmbeddedFont(String font, int style, File file) throws IOException {
// Search the fonts on this page, and return one that matches this
// font.
// This keeps the number of font definitions down to one per font/style
for (PDFFont ft : fonts) {
if (ft.equals("/TrueType", font, style)) {
return ft;
}
}
// Ok, the font isn't in the page, so create one.
// We need a procset if we are using fonts, so create it (if not
// already created, and add to our resources
if (fonts.size() == 0) {
addProcset();
procset.add("/Text");
}
// finally create and return the font
PDFFont f = pdfDocument.getEmbeddedFont(font, style, file);
fonts.addElement(f);
return f;
}
/**
* Returns the page's PageFormat.
* @return PageFormat describing the page size in device units (72dpi)

View File

@@ -0,0 +1,278 @@
package gnu.jpdf;
import java.awt.Font;
import java.awt.FontFormatException;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
*
* @author JPEXS
*/
public class TtfParser {
private int size = -1;
private long headOffset = -1;
private long os2Offset = -1;
private long hheaOffset = -1;
private long hmtxOffset = -1;
private long cmapOffset = -1;
private Font font;
private int ascent;
private int descent;
private int capHeight;
private float scale;
private int numOfHMetrics;
private List<Integer> advanceWidths = new ArrayList<>();
private Map<Integer, Integer> cmap = new TreeMap<>();
private int firstChar = -1;
private int lastChar = -1;
public void loadFromTTF(File file, int size) throws IOException, FontFormatException {
font = Font.createFont(Font.TRUETYPE_FONT, file);
RandomAccessFile input = new RandomAccessFile(file, "r");
this.size = size;
if (input == null) {
throw new IllegalArgumentException("input cannot be null.");
}
readTableDirectory(input);
if (headOffset == -1) {
throw new IOException("HEAD table not found.");
}
readHEAD(input);
readOS2(input);
readHHEA(input);
readHMTX(input);
readCMAP(input);
input.close();
}
public int getFirstChar() {
return firstChar;
}
public int getLastChar() {
return lastChar;
}
public List<Integer> getAdvanceWidths() {
return advanceWidths;
}
public Map<Integer, Integer> getCmap() {
return cmap;
}
private void readTableDirectory(RandomAccessFile input) throws IOException {
skip(input, 4);
int tableCount = readUnsignedShort(input);
skip(input, 6);
byte[] tagBytes = new byte[4];
long prevOffset = 0;
for (int i = 0; i < tableCount; i++) {
tagBytes[0] = readByte(input);
tagBytes[1] = readByte(input);
tagBytes[2] = readByte(input);
tagBytes[3] = readByte(input);
skip(input, 4);
long offset = readUnsignedLong(input);
long length = readUnsignedLong(input);
prevOffset = offset;
String tag = new String(tagBytes, "ISO-8859-1");
//System.err.println("tag " + tag + ", length: " + length);
if (tag.equals("head")) {
headOffset = offset;
}
if (tag.equals("OS/2")) {
os2Offset = offset;
}
if (tag.equals("hhea")) {
hheaOffset = offset;
}
if (tag.equals("hmtx")) {
hmtxOffset = offset;
}
if (tag.equals("cmap")) {
cmapOffset = offset;
}
}
}
private void readCMAP(RandomAccessFile input) throws IOException {
seek(input, cmapOffset);
int version = readUnsignedShort(input);
int numberSubtables = readUnsignedShort(input);
for (int i = 0; i < numberSubtables; i++) {
int platFormId = readUnsignedShort(input);
int platFormSpecificId = readUnsignedShort(input);
long offset = readUnsignedLong(input);
seek(input, cmapOffset + offset);
int format = readUnsignedShort(input);
switch (format) {
case 4:
int length = readUnsignedShort(input);
int languageCode = readUnsignedShort(input);
int segCountX2 = readUnsignedShort(input);
int segCount = segCountX2 / 2;
int searchRange = readUnsignedShort(input);
int entrySelector = readUnsignedShort(input);
int rangeShift = readUnsignedShort(input);
List<Integer> endCodes = new ArrayList<>();
List<Integer> startCodes = new ArrayList<>();
List<Integer> idDeltas = new ArrayList<>();
List<Integer> idRangeOffsets = new ArrayList<>();
for (int j = 0; j < segCount; j++) {
int endCode = readUnsignedShort(input);
endCodes.add(endCode);
}
readUnsignedShort(input);//pad
for (int j = 0; j < segCount; j++) {
int startCode = readUnsignedShort(input);
startCodes.add(startCode);
}
for (int j = 0; j < segCount; j++) {
int idDelta = readShort(input);
idDeltas.add(idDelta);
}
for (int j = 0; j < segCount; j++) {
int idRangeOffset = readUnsignedShort(input);
idRangeOffsets.add(idRangeOffset);
}
List<Integer> glyphIndices = new ArrayList<>();
long startA = input.getFilePointer();
;
long a = startA;
for (; a < cmapOffset + offset + length; a += 2) {
int glyphIndex = readUnsignedShort(input);
glyphIndices.add(glyphIndex);
}
for (int j = 0; j < segCount; j++) {
for (int k = startCodes.get(j); k <= endCodes.get(j); k++) {
if (k == 65535) {
continue;
}
if (idRangeOffsets.get(j) == 0) {
int glyph = (idDeltas.get(j) + k) % 65536;
cmap.put(k, glyph);
} else {
int glyphIndex = (idRangeOffsets.get(j) - 2 * (segCount - j)) / 2 + (k - startCodes.get(j));
int glyph = glyphIndices.get(glyphIndex);
cmap.put(k, glyph);
}
}
}
break;
}
break;
}
//System.err.println("format=" + format);
}
private void readHMTX(RandomAccessFile input) throws IOException {
seek(input, hmtxOffset);
for (int i = 0; i < numOfHMetrics; i++) {
int advanceWidth = readUnsignedShort(input);
advanceWidths.add((int) (scale * advanceWidth));
int leftSideBearing = readUnsignedShort(input);
}
}
private void readHHEA(RandomAccessFile input) throws IOException {
seek(input, hheaOffset + 4);
ascent = (int) (scale * readShort(input));
descent = (int) (scale * readShort(input));
capHeight = ascent;
seek(input, hheaOffset + 34);
numOfHMetrics = readUnsignedShort(input);
}
private void readHEAD(RandomAccessFile input) throws IOException {
seek(input, headOffset + 2 * 4 + 2 * 4 + 2);
int unitsPerEm = readUnsignedShort(input);
scale = (float) size / (float) unitsPerEm;
}
private void readOS2(RandomAccessFile input) throws IOException {
seek(input, os2Offset + 68);
//ascent = readShort(input);
//descent = readShort(input);
seek(input, os2Offset + 88);
//capHeight = readShort(input);
}
public int getAscent() {
return ascent;
}
public int getDescent() {
return descent;
}
public int getCapHeight() {
return capHeight;
}
private int readUnsignedByte(RandomAccessFile input) throws IOException {
int b = input.read();
if (b == -1) {
throw new EOFException("Unexpected end of file.");
}
return b;
}
private byte readByte(RandomAccessFile input) throws IOException {
return (byte) readUnsignedByte(input);
}
private int readUnsignedShort(RandomAccessFile input) throws IOException {
return (readUnsignedByte(input) << 8) + readUnsignedByte(input);
}
private short readShort(RandomAccessFile input) throws IOException {
return (short) readUnsignedShort(input);
}
private long readUnsignedLong(RandomAccessFile input) throws IOException {
long value = readUnsignedByte(input);
value = (value << 8) + readUnsignedByte(input);
value = (value << 8) + readUnsignedByte(input);
value = (value << 8) + readUnsignedByte(input);
return value;
}
private void skip(RandomAccessFile input, long skip) throws IOException {
input.seek(input.getFilePointer() + skip);
}
private void seek(RandomAccessFile input, long position) throws IOException {
//skip(input, position - bytePosition);
input.seek(position);
}
}