- [ ] close pdf
- [v] add character
- [ ] add path
- - [ ] basic typesetting format
\ No newline at end of file
+ - [ ] basic typesetting format
+ - [v] text measuring width in pt
\ No newline at end of file
"license": "MIT",
"dependencies": {
"@pdf-lib/fontkit": "^1.1.1",
+ "harfbuzzjs": "^0.3.5",
"pdf-lib": "^1.17.1",
"typescript-parsec": "^0.3.4"
},
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
+ "node_modules/harfbuzzjs": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/harfbuzzjs/-/harfbuzzjs-0.3.5.tgz",
+ "integrity": "sha512-SbNxmVAyhlUJTHdaxgK5S6Uqy4mXIu80Vl6KDn8d+ctPAF6W3DY2yehB4BwIC24I/Tk5HGLjaQkyny5gY0r41Q=="
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
+ "harfbuzzjs": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/harfbuzzjs/-/harfbuzzjs-0.3.5.tgz",
+ "integrity": "sha512-SbNxmVAyhlUJTHdaxgK5S6Uqy4mXIu80Vl6KDn8d+ctPAF6W3DY2yehB4BwIC24I/Tk5HGLjaQkyny5gY0r41Q=="
+ },
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"license": "MIT",
"dependencies": {
"@pdf-lib/fontkit": "^1.1.1",
+ "harfbuzzjs": "^0.3.5",
"pdf-lib": "^1.17.1",
"typescript-parsec": "^0.3.4"
},
LISPS.setPattern((0, typescript_parsec_2.alt)((0, typescript_parsec_2.apply)((0, typescript_parsec_2.kmid)((0, typescript_parsec_2.seq)((0, typescript_parsec_2.str)("("), __), (0, typescript_parsec_2.rep_sc)(LISP), (0, typescript_parsec_2.str)(")")), applyList), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.str)("'"), (0, typescript_parsec_2.kmid)((0, typescript_parsec_2.seq)((0, typescript_parsec_2.str)("("), __), (0, typescript_parsec_2.rep_sc)(LISP), (0, typescript_parsec_2.str)(")"))), applyQuoted)));
CON_STR_INNER.setPattern((0, typescript_parsec_2.alt)((0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Id), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Int), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Flo), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Str), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.Other), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.tok)(TokenKind.SpaceNL), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.LParen)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.RParen)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.LBrack)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.RBrack)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.Apos)), tokenToStr), (0, typescript_parsec_2.apply)((0, typescript_parsec_2.kright)((0, typescript_parsec_2.tok)(TokenKind.BSlash), (0, typescript_parsec_2.tok)(TokenKind.BSlash)), bSlashTokenToStr), LISPS));
CON_STR.setPattern((0, typescript_parsec_2.apply)((0, typescript_parsec_2.kmid)((0, typescript_parsec_2.str)("["), (0, typescript_parsec_2.rep_sc)(CON_STR_INNER), (0, typescript_parsec_2.str)("]")), applyStrings));
+/**
+ * measuer the width of a test in px
+ * @param inputString the string to be measured
+ * @param fontFamily font family name
+ * @param fontSizePt font size in pt
+ * @returns the width in px
+ */
+async function measureWidthPx(inputString, fontFamily, fontSizePt) {
+ return await WebAssembly.instantiate(fs.readFileSync(__dirname + "/../3rdparty/harfbuzzjs/hb.wasm"))
+ .then(function (wsm) {
+ var hb = require('harfbuzzjs/hbjs');
+ hb = hb(wsm.instance);
+ let fontName = (0, child_process_1.spawnSync)('fc-match', ['--format=%{file}', fontFamily]);
+ const fontPath = fontName.stdout.toString();
+ let fontdata = fs.readFileSync(fontPath);
+ var blob = hb.createBlob(fontdata); // Load the font data into something Harfbuzz can use
+ var face = hb.createFace(blob, 0); // Select the first font in the file (there's normally only one!)
+ var font = hb.createFont(face); // Create a Harfbuzz font object from the face
+ font.setScale(fontSizePt * 4 / 3 * 1000, fontSizePt * 4 / 3 * 1000);
+ var buffer = hb.createBuffer(); // Make a buffer to hold some text
+ buffer.addText(inputString); // Fill it with some stuff
+ buffer.guessSegmentProperties(); // Set script, language and direction
+ hb.shape(font, buffer); // Shape the text, determining glyph IDs and positions
+ var output = buffer.json();
+ var totalX = 0;
+ for (var glyph of output) {
+ var xAdvance = glyph.ax;
+ totalX += xAdvance;
+ }
+ // Release memory
+ buffer.destroy();
+ font.destroy();
+ face.destroy();
+ blob.destroy();
+ return totalX / 1000;
+ });
+}
function astToString(ast, isInQuoted) {
if (Array.isArray(ast)) {
const ast2 = ast.map((x) => astToString(x, isInQuoted));
const path = fcMatch.stdout.toString();
pdfDoc.registerFontkit(fontkit_1.default);
const fontBytes = fs.readFileSync(path);
- console.log("A2A", (0, pdf_lib_1.rgb)(0, 0, 0));
const customFont = await pdfDoc.embedFont(fontBytes);
- console.log("A3A", (0, pdf_lib_1.rgb)(0, 0, 0));
const rgbColor = await hexColorToRGB(color);
- console.log("A4A", (0, pdf_lib_1.rgb)(0, 0, 0));
let a = await pdfDoc.getPage(0).drawText(text, {
x: x,
y: y,
await pdfDoc.save();
}
async function hexColorToRGB(hex) {
- let rgbHex = /[#]?(\d{2})(\d{2})(\d{2})/.exec(hex);
- let r = parseInt(rgbHex[1], 16) / 256.0;
- let g = parseInt(rgbHex[2], 16) / 256.0;
- let b = parseInt(rgbHex[3], 16) / 256.0;
+ let rgbHex = /[#]?([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})/.exec(hex);
+ let r = parseInt(rgbHex[1], 16) / 255.0;
+ let g = parseInt(rgbHex[2], 16) / 255.0;
+ let b = parseInt(rgbHex[3], 16) / 255.0;
return (0, pdf_lib_1.rgb)(r, g, b);
}
function listRef(l, i) {
};
}
}
+ // define manipulation
+ if (op.id === "define") {
+ const vari = prog[1];
+ const data = await interp(prog[2], env);
+ if (prog.length !== 3) {
+ throw invalidLengthException('define', 2);
+ }
+ else if (!isItem(vari) || !isItem(data)) {
+ throw new Error("the type of replace and variable should be the same.");
+ }
+ else if (env[vari.id] !== undefined) {
+ throw new Error("variable can't be duplicated defined.");
+ }
+ else {
+ env = extendEnv(env, vari.id, true, data);
+ return { type: ItemType.Unit };
+ }
+ }
/** let function */
else if (op.id === "let" || op.id === "letrec") {
const bindings = prog[1];
}
}
else {
- const argsMapped = await Promise.all(prog.slice(1).map(async (x) => {
- return interp(x, env);
- }));
+ let argsMapped = [];
+ for (var i = 1; i < prog.length; i++) {
+ argsMapped.push(await interp(prog[i], env));
+ }
+ /* const argsMapped = await Promise.all( prog.slice(1).map(async (x) => {
+ return interp(x, env);
+ })); */
// binary basic operator
if (op.id === "+") {
return interpBinary(add, argsMapped);
}
}
}
+ else if (op.id === "and") {
+ if (prog.length !== 3) {
+ throw invalidLengthException('and', 2);
+ }
+ else if (!argsMapped[0].hasOwnProperty('type') || argsMapped[0].type !== ItemType.Bool
+ || !argsMapped[1].hasOwnProperty('type') || argsMapped[1].type !== ItemType.Bool) {
+ throw new Error("the arg of 'and' is not valid boolean value");
+ }
+ else {
+ let ret = {
+ type: ItemType.Bool,
+ bool: argsMapped[0].bool && argsMapped[1].bool
+ };
+ return ret;
+ }
+ }
+ else if (op.id === "or") {
+ if (prog.length !== 3) {
+ throw invalidLengthException('or', 2);
+ }
+ else if (!argsMapped[0].hasOwnProperty('type') || argsMapped[0].type !== ItemType.Bool
+ || !argsMapped[1].hasOwnProperty('type') || argsMapped[1].type !== ItemType.Bool) {
+ throw new Error("the arg of 'or' is not valid boolean value");
+ }
+ else {
+ let ret = {
+ type: ItemType.Bool,
+ bool: argsMapped[0].bool || argsMapped[1].bool
+ };
+ return ret;
+ }
+ }
+ // measuring
+ else if (op.id === "measureWidthPx") {
+ if (prog.length !== 4) {
+ throw invalidLengthException('measureWidthPx', 3);
+ }
+ else {
+ let text = argsMapped[0].str;
+ let fontfamily = argsMapped[1].str;
+ let sizePt = argsMapped[2].flo;
+ let returnValue = await measureWidthPx(text, fontfamily, sizePt);
+ return {
+ type: ItemType.Flo,
+ flo: returnValue
+ };
+ }
+ }
+ else if (op.id === "isList") {
+ const arg = argsMapped[0];
+ if (prog.length !== 2) {
+ throw invalidLengthException('isList', 1);
+ }
+ else if (arg.type === ItemType.Ls) {
+ let a = {
+ type: ItemType.Bool,
+ bool: true,
+ };
+ return a;
+ }
+ else {
+ return {
+ type: ItemType.Bool,
+ bool: false,
+ };
+ }
+ }
else if (op.id === "car") {
const arg = argsMapped[0];
if (prog.length !== 2) {
else if (op.id === "cons") {
const arg = argsMapped;
if (prog.length !== 3) {
- throw invalidLengthException('cdr', 2);
+ throw invalidLengthException('cons', 2);
}
else if (!arg[1].hasOwnProperty('type') || arg[1].type !== ItemType.Ls) {
throw new Error("the 2nd arg of 'cons' is not a list.");
// set manipulations
else if (op.id === "set!") {
const vari = prog[1];
- const replacer = prog[2];
+ const replacer = await interp(prog[2], env);
if (prog.length !== 3) {
throw invalidLengthException('set!', 2);
}
tok,
opt,
} from "typescript-parsec";
-
+import {inspect} from "node:util";
/** input lisp file */
apply(kmid(str("["), rep_sc(CON_STR_INNER), str("]")), applyStrings)
);
+/**
+ * measuer the width of a test in px
+ * @param inputString the string to be measured
+ * @param fontFamily font family name
+ * @param fontSizePt font size in pt
+ * @returns the width in px
+ */
+async function measureWidthPx(inputString: string, fontFamily : string, fontSizePt: number): Promise<number>{
+ return await WebAssembly.instantiate(fs.readFileSync(__dirname+"/../3rdparty/harfbuzzjs/hb.wasm"))
+ .then(function (wsm) {
+ var hb = require('harfbuzzjs/hbjs');
+ hb = hb(wsm.instance);
+
+ let fontName = spawnSync('fc-match', ['--format=%{file}', fontFamily]);
+ const fontPath = fontName.stdout.toString();
+ let fontdata = fs.readFileSync(fontPath);
+
+
+ var blob = hb.createBlob(fontdata); // Load the font data into something Harfbuzz can use
+ var face = hb.createFace(blob, 0); // Select the first font in the file (there's normally only one!)
+ var font = hb.createFont(face); // Create a Harfbuzz font object from the face
+ font.setScale(fontSizePt * 4/3* 1000 , fontSizePt*4/3 * 1000 );
+ var buffer = hb.createBuffer(); // Make a buffer to hold some text
+ buffer.addText(inputString); // Fill it with some stuff
+ buffer.guessSegmentProperties(); // Set script, language and direction
+ hb.shape(font, buffer); // Shape the text, determining glyph IDs and positions
+ var output : Array<{g : number,
+ ax : number,
+ dx : number,
+ dy : number}> = buffer.json();
+
+ var totalX = 0;
+ for (var glyph of output) {
+ var xAdvance = glyph.ax;
+ totalX += xAdvance;
+
+ }
+
+ // Release memory
+ buffer.destroy();
+ font.destroy();
+ face.destroy();
+ blob.destroy();
+
+ return totalX / 1000;
+ });
+}
+
+
function astToString(ast: AST, isInQuoted? : boolean): string {
if (Array.isArray(ast)) {
const ast2 = ast.map((x)=>astToString(x, isInQuoted));
const path = fcMatch.stdout.toString();
pdfDoc.registerFontkit(fontkit);
const fontBytes = fs.readFileSync(path);
- console.log("A2A",rgb(0,0,0));
const customFont = await pdfDoc.embedFont(fontBytes);
- console.log("A3A",rgb(0,0,0));
const rgbColor = await hexColorToRGB(color);
- console.log("A4A",rgb(0,0,0));
let a = await pdfDoc.getPage(0).drawText(text, {
x: x,
async function hexColorToRGB(hex: string): Promise<RGB>{
- let rgbHex = /[#]?(\d{2})(\d{2})(\d{2})/.exec(hex);
- let r = parseInt((rgbHex as RegExpExecArray)[1], 16)/256.0;
- let g = parseInt((rgbHex as RegExpExecArray)[2], 16)/256.0;
- let b = parseInt((rgbHex as RegExpExecArray)[3], 16)/256.0;
+ let rgbHex = /[#]?([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})/.exec(hex);
+ let r = parseInt((rgbHex as RegExpExecArray)[1], 16)/255.0;
+ let g = parseInt((rgbHex as RegExpExecArray)[2], 16)/255.0;
+ let b = parseInt((rgbHex as RegExpExecArray)[3], 16)/255.0;
return rgb(r,g,b);
}
}
}
}
+ // define manipulation
+ if (op.id === "define") {
+ const vari : ItemId = prog[1] as ItemId;
+ const data = await interp(prog[2], env);
+ if (prog.length !== 3){
+ throw invalidLengthException('define', 2);
+ }else if (!isItem(vari) || !isItem(data)){
+ throw new Error("the type of replace and variable should be the same.")
+ }else if(env[vari.id] !== undefined){
+ throw new Error("variable can't be duplicated defined.")
+ }else {
+ env = extendEnv(env, vari.id, true, data);
+ return {type:ItemType.Unit};
+ }
+ }
+
/** let function */
else if (op.id === "let" || op.id === "letrec"){
const bindings = prog[1];
}
}
else{
+ let argsMapped = [];
+ for (var i=1;i<prog.length;i++){
+ argsMapped.push(await interp(prog[i], env));
+ }
+
+
- const argsMapped = await Promise.all( prog.slice(1).map(async (x) => {
+ /* const argsMapped = await Promise.all( prog.slice(1).map(async (x) => {
return interp(x, env);
- }));
+ })); */
// binary basic operator
if (op.id === "+") {
return interpBinary(add, argsMapped);
};
}
}
- } else if (op.id === "car") {
+ }else if (op.id === "and"){
+ if (prog.length !== 3){
+ throw invalidLengthException('and', 2);
+ }else if (!argsMapped[0].hasOwnProperty('type') || (argsMapped[0] as Item).type !== ItemType.Bool
+ || !argsMapped[1].hasOwnProperty('type') || (argsMapped[1] as Item).type !== ItemType.Bool){
+ throw new Error("the arg of 'and' is not valid boolean value")
+ }else{
+ let ret = {
+ type : ItemType.Bool,
+ bool: (argsMapped[0] as ItemBool).bool && (argsMapped[1] as ItemBool).bool
+ };
+ return ret as Item;
+ }
+ }else if (op.id === "or"){
+ if (prog.length !== 3){
+ throw invalidLengthException('or', 2);
+ }else if (!argsMapped[0].hasOwnProperty('type') || (argsMapped[0] as Item).type !== ItemType.Bool
+ || !argsMapped[1].hasOwnProperty('type') || (argsMapped[1] as Item).type !== ItemType.Bool){
+ throw new Error("the arg of 'or' is not valid boolean value")
+ }else{
+ let ret = {
+ type : ItemType.Bool,
+ bool: (argsMapped[0] as ItemBool).bool || (argsMapped[1] as ItemBool).bool
+ };
+ return ret as Item;
+ }
+ }
+
+ // measuring
+ else if (op.id === "measureWidthPx"){
+ if (prog.length !== 4){
+ throw invalidLengthException('measureWidthPx', 3);
+ }else{
+ let text = (argsMapped[0] as ItemStr).str;
+ let fontfamily = (argsMapped[1] as ItemStr).str;
+ let sizePt = (argsMapped[2] as ItemFlo).flo;
+ let returnValue = await measureWidthPx(text, fontfamily, sizePt);
+ return {
+ type: ItemType.Flo,
+ flo: returnValue
+ }
+ }
+ }
+
+ else if (op.id === "isList"){
+ const arg = argsMapped[0];
+ if (prog.length !== 2){
+ throw invalidLengthException('isList', 1);
+ }else if ((arg as Item).type === ItemType.Ls){
+
+ let a = {
+ type: ItemType.Bool,
+ bool: true as boolean,
+ };
+ return a as Item;
+ }else{
+ return {
+ type: ItemType.Bool,
+ bool: false as boolean,
+ };
+ }
+ }
+ else if (op.id === "car") {
const arg = argsMapped[0];
if (prog.length !== 2){
throw invalidLengthException('car', 1);
}else if (op.id === "cons") {
const arg = argsMapped;
if (prog.length !== 3){
- throw invalidLengthException('cdr', 2);
+ throw invalidLengthException('cons', 2);
}else if (!arg[1].hasOwnProperty('type') || (arg[1] as Item).type !== ItemType.Ls){
throw new Error("the 2nd arg of 'cons' is not a list.")
}else{
return subString(str, i as ItemInt, prog[3] as ItemInt);}
}
}
-
// set manipulations
else if (op.id === "set!") {
const vari : ItemId = prog[1] as ItemId;
- const replacer = prog[2];
+ const replacer = await interp(prog[2], env);
if (prog.length !== 3){
throw invalidLengthException('set!', 2);
}else if (!isItem(vari) || !isItem(replacer)
const prog = fs.readFileSync(filename, { encoding: 'utf8' });
console.log(await evaluate(prog));
+
const pdfBytes = await pdfDoc.save();
fs.writeFileSync(filename+'.pdf', pdfBytes, 'binary');
-(letrec (
- (defaultFontFormat
- '(("fontFamily" "Gentium")
-
- ("color" "#000000")
- ("size" 12)
- )
- )
- (map (lambda (f l)
- (if (!= l '())
- (cons (f (car l)) (map f (cdr l)))
- '())))
- (emptyDict '())
- (extendDict (lambda (dict var data) (cons (cons var (cons data '())) dict)))
- (dictRef (lambda (dict key)
- (if (= dict '()) false
- (if (= key (car (car dict))) (car (cdr (car dict))) (dictRef (cdr dict) key))
- )))
+(begin
+(define defaultFontFormat
+ '(("fontFamily" "Gentium")
+ ("color" "#ff0000")
+ ("fontSize" 12)
)
+)
+
+
+(define map (lambda (f l)
+ (if (!= l '())
+ (cons (f (car l)) (map f (cdr l)))
+ '())))
+(define emptyDict '())
+(define extendDict (lambda (dict var data) (cons (cons var (cons data '())) dict)))
+(define dictRef (lambda (dict key)
+ (if (= dict '()) false
+ (if (= key (car (car dict))) (car (cdr (car dict))) (dictRef (cdr dict) key))
+ )))
+(define setDictItem (lambda (dict key data)
+ (if (= (dictRef dict key) false)
+ false
+ (setDictItemAux dict '() key data)
+)))
+
+(define setDictItemAux (lambda (oldDict newDict key data)
+(if (= oldDict '()) newDict
+(if (= (car(car oldDict)) key)
+ (setDictItemAux (cdr oldDict) (cons (cons key (cons data '())) newDict) key data)
+ (setDictItemAux (cdr oldDict) (cons (car oldDict) newDict) key data)
+))))
-(begin
(addPDFPage '())
-(drawText
- (dictRef defaultFontFormat "fontFamily")
- (dictRef defaultFontFormat "size")
- (dictRef defaultFontFormat "color")
- 40.0
- 50.0
- "blah"
-)
(addPDFPage '())
+(define text2boxAux2 (lambda (format text)
+ (if (isList text)
+ (if (= (listRef text 0) "fontSize")
+ (let ((newFormat (setDictItem format "fontSize" (listRef text 1)))) (text2boxAux1 newFormat (listRef text 2)))
+ text)
+ (cons format (cons text '())))
+))
+
+(define text2boxAux1 (lambda (format txt)
+(if (isList txt)
+(map (lambda (x) (text2boxAux2 format x)) txt)
+(cons format (cons txt '()))
+)))
+
+(define text2box (lambda (txt) (text2boxAux1 defaultFontFormat txt)))
+
-(map (lambda (x) (+ x 2)) '(8 9 10))
-(let ((dict emptyDict))
- (let ((dictExtended
- (extendDict
- (extendDict emptyDict 1 2) 2 4)))
- (dictRef dictExtended 2)
-))))
\ No newline at end of file
+
+
+(drawText
+(dictRef defaultFontFormat "fontFamily")
+(dictRef defaultFontFormat "fontSize")
+(dictRef defaultFontFormat "color")
+40.0
+50.0
+"blah"
+)
+(define text '("abracabra" ("fontSize" 18 "貓") "foo"))
+(text2box text)
+(measureWidthPx "1314abc" "Gentium" 12.0)
+)
\ No newline at end of file
--- /dev/null
+(letrec (
+ (defaultFontFormat
+ '(("fontFamily" "Gentium")
+ ("color" "#ff0000")
+ ("size" 12)
+ )
+ )
+ (map (lambda (f l)
+ (if (!= l '())
+ (cons (f (car l)) (map f (cdr l)))
+ '())))
+ (emptyDict '())
+ (extendDict (lambda (dict var data) (cons (cons var (cons data '())) dict)))
+ (dictRef (lambda (dict key)
+ (if (= dict '()) false
+ (if (= key (car (car dict))) (car (cdr (car dict))) (dictRef (cdr dict) key))
+ )))
+ )
+
+(begin
+(addPDFPage '())
+(drawText
+ "Gentium"
+ "#ff0000"
+ "12"
+ 40.0
+ 50.0
+ "blah"
+)
+(addPDFPage '())
+
+(map (lambda (x) (+ x 2)) '(8 9 10))
+(let ((dict emptyDict))
+ (let ((dictExtended
+ (extendDict
+ (extendDict emptyDict 1 2) 2 4)))
+ (dictRef dictExtended 2)
+))))
\ No newline at end of file