Add a the beggining of a Type1 font reader

This commit is contained in:
Vivien Nicolas 2011-05-31 20:22:05 +02:00
parent c76e2c5114
commit fe11af4b57
3 changed files with 260 additions and 3 deletions

226
PDFFont.js Normal file
View File

@ -0,0 +1,226 @@
var Type1Parser = function(aLexer) {
var lexer = aLexer;
/*
* The operand stack holds arbitrary PostScript objects that are the operands
* and results of PostScript operators being executed. The interpreter pushes
* objects on the operand stack when it encounters them as literal data in a
* program being executed. When an operator requires one or more operands, it
* obtains them by popping them off the top of the operand stack. When an
* operator returns one or more results, it does so by pushing them on the
* operand stack.
*/
var operandStack = [];
/*
* The dictionary stack holds only dictionary objects. The current set of
* dictionaries on the dictionary stack defines the environment for all
* implicit name searches, such as those that occur when the interpreter
* encounters an executable name. The role of the dictionary stack is
* introduced in Section 3.3, Data Types and Objects, and is further
* explained in Section 3.5, Execution. of the PostScript Language
* Reference.
*/
var systemDict = new Dict(),
globalDict = new Dict(),
userDict = new Dict();
var dictionaryStack = {
__innerStack__: [systemDict, globalDict],
push: function(aDictionary) {
this.__innerStack__.push(aDictionary);
},
pop: function() {
if (this.__innerStack__.length == 2)
return null;
return this.__innerStack__.pop();
},
peek: function() {
return this.__innerStack__[this.__innerStack__.length - 1];
}
}
var currentDict = dictionaryStack.peek();
/*
* The execution stack holds executable objects (mainly procedures and files)
* that are in intermediate stages of execution. At any point in the
* execution of a PostScript program, this stack represents the programs
* call stack. Whenever the interpreter suspends execution of an object to
* execute some other object, it pushes the new object on the execution
* stack. When the interpreter finishes executing an object, it pops that
* object off the execution stack and resumes executing the suspended object
* beneath it.
*/
var isExecutionStack = false;
var executionStack = [];
/* Stub to inhibit the logs */
this.getObj = function() {
var obj = lexer.getObj();
if (isExecutionStack && !IsCmd(obj, "}") && !IsCmd(obj, "]")) {
executionStack.push(obj);
this.getObj();
} else if (IsBool(obj) || IsInt(obj) || IsNum(obj) || IsString(obj)) {
log("Value: " + obj);
operandStack.push(obj);
this.getObj();
} else if (IsCmd(obj, "dup") || IsCmd(obj, "readonly") ||
IsCmd(obj, "currentdict") || IsCmd(obj, "currentfile")) {
// Do nothing for the moment
this.getObj();
} else if (IsName(obj)) {
log("Name: " + obj.name);
operandStack.push(obj.name);
this.getObj();
} else if (IsCmd(obj, "dict")) {
log("Dict: " + obj);
// XXX handling of dictionary is wrong here
var size = operandStack.pop();
var name = operandStack.pop();
if (!name) {
log ("Creating the global dict");
currentDict = dictionaryStack.peek();
} else {
var dict = new Dict();
log("Assign name: " + name + " for the dictionary");
currentDict.set(name, dict);
dictionaryStack.push(dict);
}
this.getObj();
} else if (IsCmd(obj, "begin")) {
log("begin a dictionary");
currentDict = dictionaryStack.peek();
this.getObj();
} else if (IsCmd(obj, "end")) {
log("Ending a dictionary");
dictionaryStack.pop();
currentDict = dictionaryStack.peek();
this.getObj();
} else if (IsCmd(obj, "def")) {
if (executionStack.length) {
var value = [];
while (executionStack.length)
value.push(executionStack.shift());
} else {
var value = operandStack.pop();
}
var key = operandStack.pop();
// XXX this happen because of a bad way to handle dictionary
if (key) {
log("def: " + key + " = " + value);
currentDict.set(key, value);
}
this.getObj();
} else if (IsCmd(obj, "{")) {
log("Start Proc: " + obj);
executionStack = [];
isExecutionStack = true;
this.getObj();
} else if (IsCmd(obj, "}")) {
log("End Proc: " + obj);
isExecutionStack = false;
this.getObj();
} else if (IsCmd(obj, "[")) {
isExecutionStack = true;
executionStack = [];
this.getObj();
log("Start array: " + obj);
} else if (IsCmd(obj, "]")) {
log("End array: " + obj);
isExecutionStack = false;
this.getObj();
} else if (IsCmd(obj, "eexec")) {
return; // end of the ASCII header
} else {
log("Getting an unknow token, adding it to the stack just in case");
log(obj);
operandStack.push(obj);
this.getObj();
}
return currentDict;
}
};
var hack = false;
var Type1Font = function(aFontName, aFontFile) {
// All type1 font program should begin with the comment %!
var validHeader = aFontFile.getByte() == 0x25 && aFontFile.getByte() == 0x21;
if (!validHeader)
error("Invalid file header");
var programType = "PS-AdobeFont";
for (var i = 0; i< programType.length; i++)
aFontFile.getChar();
// Ignore the '-' separator
aFontFile.getChar();
var version = parseFloat(aFontFile.getChar() + aFontFile.getChar() + aFontFile.getChar());
if (!hack) {
log(aFontName);
log("Version is: " + version);
var ASCIIStream = aFontFile.makeSubStream(0, aFontFile.dict.get("Length1"), aFontFile.dict);
this.parser = new Type1Parser(new Lexer(ASCIIStream));
var fontDictionary = this.parser.getObj();
log(fontDictionary + "\t" +
"fontInfo: " + fontDictionary.get("FontInfo") + "\t" +
"charStrings: " + fontDictionary.get("charStrings"));
var binaryStream = aFontFile.makeSubStream(aFontFile.dict.get("Length1"), aFontFile.dict.get("Length2"), aFontFile.dict);
function decrypt(aBinaryStream, aKey) {
var c1 = 52845, c2 = 22719;
var R = aKey;
var streamSize = aBinaryStream.length;
var decryptedString = [];
var value = null;
for (var i = 0; i < streamSize; i++) {
value = aBinaryStream.getByte();
decryptedString[i] = String.fromCharCode(value ^ (R >> 8));
R = ((value + R) * c1 + c2) & ((1 << 16) - 1);
}
return decryptedString.slice(4);
}
var eexecString = decrypt(binaryStream, 55665).join("");
log(eexecString);
TODO("decrypt charStrings data with the key 4330");
hack = true;
}
this.info = {};
this.name = aFontName;
this.encoding = [];
this.paintType = 0;
this.fontType = 0;
this.fontMatrix = [];
this.fontBBox = [];
this.uniqueID = 0;
this.metrics = {};
this.strokeWidth = 0.0;
this.private = {};
this.charStrings = {}
this.FID = 0;
};

36
pdf.js
View File

@ -1032,6 +1032,12 @@ var Dict = (function() {
},
set: function(key, value) {
this.map[key] = value;
},
toString: function() {
var keys = [];
for (var key in this.map)
keys.push(key);
return "Dict with " + keys.length + " keys: " + keys;
}
};
@ -1360,10 +1366,11 @@ var Lexer = (function() {
stream.skip();
return new Cmd(">>");
}
case "{":
case "}":
return new Cmd(ch);
// fall through
case ')':
case '{':
case '}':
error("Illegal character");
return Error;
}
@ -2258,11 +2265,34 @@ var CanvasGraphics = (function() {
},
setFont: function(fontRef, size) {
var font = this.res.get("Font").get(fontRef.name);
font = this.xref.fetchIfRef(font);
if (!font)
return;
var fontName = "Nimbus Roman No9 L";
var subtype = font.get("Subtype").name;
switch (subtype) {
case "Type1":
var fontDescriptor = font.get("FontDescriptor");
if (fontDescriptor.num) {
var fontDescriptor = this.xref.fetchIfRef(fontDescriptor);
var fontFile = this.xref.fetchIfRef(fontDescriptor.get("FontFile"));
font = new Type1Font(fontDescriptor.get("FontName").name, fontFile);
}
break;
case "Type3":
TODO("support Type3 font");
break;
default:
error("Unsupported font type: " + subtype);
break;
}
this.current.fontSize = size;
TODO("using hard-coded font for testing");
this.ctx.font = this.current.fontSize +'px "Nimbus Roman No9 L"';
this.ctx.font = this.current.fontSize +'px "' + fontName + '"';
},
moveText: function (x, y) {
this.current.x = this.current.lineX += x;

View File

@ -5,6 +5,7 @@
<script type="text/javascript" src="pdf.js"></script>
<script type="text/javascript" src="test.js"></script>
<script type="text/javascript" src="PDFFont.js"></script>
</head>
<body onload="load();">