From: Tan Kian-ting Date: Thu, 18 Apr 2024 17:25:07 +0000 (+0800) Subject: add text measurement X-Git-Url: https://git.kianting.info/?a=commitdiff_plain;h=90fd2d0d5cd08096bea0a6b60200db7a18d8b72b;p=anotherTypesetter add text measurement --- diff --git a/3rdparty/harfbuzzjs/hb.wasm b/3rdparty/harfbuzzjs/hb.wasm new file mode 100755 index 0000000..427871a Binary files /dev/null and b/3rdparty/harfbuzzjs/hb.wasm differ diff --git a/README.md b/README.md index 6801888..db3f025 100644 --- a/README.md +++ b/README.md @@ -14,4 +14,5 @@ - [ ] 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 diff --git a/package-lock.json b/package-lock.json index 5498402..f83f9f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@pdf-lib/fontkit": "^1.1.1", + "harfbuzzjs": "^0.3.5", "pdf-lib": "^1.17.1", "typescript-parsec": "^0.3.4" }, @@ -1021,6 +1022,11 @@ "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", @@ -2464,6 +2470,11 @@ "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", diff --git a/package.json b/package.json index f01ade4..f1cfa21 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "license": "MIT", "dependencies": { "@pdf-lib/fontkit": "^1.1.1", + "harfbuzzjs": "^0.3.5", "pdf-lib": "^1.17.1", "typescript-parsec": "^0.3.4" }, diff --git a/src/index.js b/src/index.js index 3b0d718..dae4421 100644 --- a/src/index.js +++ b/src/index.js @@ -157,6 +157,43 @@ SINGLE.setPattern((0, typescript_parsec_2.alt)((0, typescript_parsec_2.apply)((0 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)); @@ -369,11 +406,8 @@ async function drawText(pageIndex, fontFamily, textSize, color, x, y, text) { 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, @@ -384,10 +418,10 @@ async function drawText(pageIndex, fontFamily, textSize, color, x, y, text) { 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) { @@ -463,6 +497,24 @@ async function interp(prog, env) { }; } } + // 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]; @@ -531,9 +583,13 @@ async function interp(prog, env) { } } 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); @@ -604,6 +660,73 @@ async function interp(prog, env) { } } } + 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) { @@ -631,7 +754,7 @@ async function interp(prog, env) { 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."); @@ -693,7 +816,7 @@ async function interp(prog, env) { // 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); } diff --git a/src/index.ts b/src/index.ts index 2b266a3..9260f7f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ import { tok, opt, } from "typescript-parsec"; - +import {inspect} from "node:util"; /** input lisp file */ @@ -265,6 +265,55 @@ CON_STR.setPattern( 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{ + 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)); @@ -480,13 +529,10 @@ const fcMatch = await spawnSync('fc-match', ['--format=%{file}', fontFamily]); 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, @@ -501,10 +547,10 @@ const path = fcMatch.stdout.toString(); async function hexColorToRGB(hex: string): Promise{ - 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); } @@ -587,6 +633,22 @@ async function interp(prog: AST, env: Env): Promise { } } } + // 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]; @@ -647,10 +709,16 @@ async function interp(prog: AST, env: Env): Promise { } } else{ + let argsMapped = []; + for (var i=1;i { + /* 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); @@ -706,7 +774,69 @@ async function interp(prog: AST, env: Env): Promise { }; } } - } 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); @@ -728,7 +858,7 @@ async function interp(prog: AST, env: Env): Promise { }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{ @@ -778,11 +908,10 @@ async function interp(prog: AST, env: Env): Promise { 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) @@ -968,6 +1097,7 @@ async function run(){ const prog = fs.readFileSync(filename, { encoding: 'utf8' }); console.log(await evaluate(prog)); + const pdfBytes = await pdfDoc.save(); fs.writeFileSync(filename+'.pdf', pdfBytes, 'binary'); diff --git a/text.lisp b/text.lisp index 1d38465..3023dbd 100644 --- a/text.lisp +++ b/text.lisp @@ -1,39 +1,65 @@ -(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 diff --git a/text.lisp.pdf b/text.lisp.pdf index 08e0868..335d569 100644 Binary files a/text.lisp.pdf and b/text.lisp.pdf differ diff --git a/text2.lisp b/text2.lisp new file mode 100644 index 0000000..d222fea --- /dev/null +++ b/text2.lisp @@ -0,0 +1,38 @@ +(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