From 8f3b198c230deaeef7100535ed46cebff5079972 Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Sun, 19 Feb 2012 20:12:57 -0600 Subject: [PATCH 01/15] Check if glyph are stored outside the glyf table --- src/fonts.js | 10 +++++++++- test/pdfs/.gitignore | 1 + test/pdfs/issue1249.pdf | Bin 0 -> 68783 bytes test/test_manifest.json | 6 ++++++ 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/pdfs/issue1249.pdf diff --git a/src/fonts.js b/src/fonts.js index 0f9b6f9d0..23a08c0c7 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -1636,12 +1636,20 @@ var Font = (function FontClosure() { var locaData = loca.data; // removing the invalid glyphs var oldGlyfData = glyf.data; - var newGlyfData = new Uint8Array(oldGlyfData.length); + var oldGlyfDataLength = oldGlyfData.length; + var newGlyfData = new Uint8Array(oldGlyfDataLength); var startOffset = itemDecode(locaData, 0); var writeOffset = 0; itemEncode(locaData, 0, writeOffset); for (var i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) { var endOffset = itemDecode(locaData, j); + if (endOffset > oldGlyfDataLength) { + // glyph end offset points outside glyf data, rejecting the glyph + itemEncode(locaData, j, writeOffset); + startOffset = endOffset; + continue; + } + var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset, newGlyfData, writeOffset); writeOffset += newLength; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index e0926492b..7e79f90f0 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -20,6 +20,7 @@ !scan-bad.pdf !freeculture.pdf !issue918.pdf +!issue1249.pdf !smaskdim.pdf !type4psfunc.pdf !S2.pdf diff --git a/test/pdfs/issue1249.pdf b/test/pdfs/issue1249.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f7bacda0241f7fdf7f069037999e0fda4580a942 GIT binary patch literal 68783 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®GP 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�g@>&#DiaIx7OMH84Os%O0THK^yKu+T<yFW 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 literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index e09f16da9..19d25317a 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -417,6 +417,12 @@ "link": true, "type": "eq" }, + { "id": "issue1249-load", + "file": "pdfs/issue1249.pdf", + "md5": "4f81339fa09422a7db980f34ea963609", + "rounds": 1, + "type": "load" + }, { "id": "liveprogramming", "file": "pdfs/liveprogramming.pdf", "md5": "7bd4dad1188232ef597d36fd72c33e52", From 0ea87068ed9683c7483abd45b110e264cfa4fee9 Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Wed, 22 Feb 2012 21:52:29 -0600 Subject: [PATCH 02/15] Skipping HTTP POST requests in the addon --- extensions/firefox/components/PdfStreamConverter.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index 06db4e2d3..c6dab9ba7 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -124,6 +124,19 @@ PdfStreamConverter.prototype = { asyncConvertData: function(aFromType, aToType, aListener, aCtxt) { if (!Services.prefs.getBoolPref('extensions.pdf.js.active')) throw Cr.NS_ERROR_NOT_IMPLEMENTED; + + // Ignoring HTTP POST requests -- pdf.js has to repeat the request. + var skipConversion = false; + try { + var request = aCtxt; + request.QueryInterface(Ci.nsIHttpChannel); + skipConversion = (request.requestMethod === 'POST'); + } catch (e) { + // Non-HTTP request... continue normally. + } + if (skipConversion) + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + // Store the listener passed to us this.listener = aListener; }, From d0143cc2894307a7d0e652e190f9d2a67874dfb2 Mon Sep 17 00:00:00 2001 From: Brendan Dahl <brendan.dahl@gmail.com> Date: Thu, 23 Feb 2012 09:21:35 -0800 Subject: [PATCH 03/15] Fix missing bidi for extension. --- src/bidi.js | 2 +- web/viewer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bidi.js b/src/bidi.js index a3308fa2c..a6e3e429f 100644 --- a/src/bidi.js +++ b/src/bidi.js @@ -3,7 +3,7 @@ 'use strict'; -var bidi = (function bidiClosure() { +var bidi = PDFJS.bidi = (function bidiClosure() { // Character types for symbols from 0000 to 00FF. var baseTypes = [ 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'S', 'B', 'S', 'WS', diff --git a/web/viewer.js b/web/viewer.js index 0a1ad7508..9f477f3a7 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -1097,7 +1097,7 @@ var TextLayerBuilder = function textLayerBuilder(textLayerDiv) { textDiv.style.fontSize = fontHeight + 'px'; textDiv.style.left = text.geom.x + 'px'; textDiv.style.top = (text.geom.y - fontHeight) + 'px'; - textDiv.textContent = bidi(text, -1); + textDiv.textContent = PDFJS.bidi(text, -1); textDiv.dir = text.direction; textDiv.dataset.textLength = text.length; this.textDivs.push(textDiv); From 2cc89735aea412870c5b3cc0ace446d8b621916c Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Wed, 29 Feb 2012 18:31:03 -0600 Subject: [PATCH 04/15] Fixing cap1 statement --- src/fonts.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index 542c33f55..9d11d81cc 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -1863,6 +1863,13 @@ var Font = (function FontClosure() { var hasShortCmap = !!cmapTable.hasShortCmap; var toFontChar = this.toFontChar; + if (hasShortCmap && ids.length == numGlyphs) { + // Fixes the short cmap tables -- some generators use incorrect + // glyph id. + for (var i = 0, ii = ids.length; i < ii; i++) + ids[i] = i; + } + if (toFontChar && toFontChar.length > 0) { // checking if cmap is just identity map var isIdentity = true; @@ -1906,11 +1913,14 @@ var Font = (function FontClosure() { // copying all characters to private use area, all mapping all known // glyphs to the unicodes. The glyphs and ids arrays will grow. var usedUnicodes = []; + var glyphNames = properties.glyphNames || []; for (var i = 0, ii = glyphs.length; i < ii; i++) { var code = glyphs[i].unicode; + var gid = ids[i]; glyphs[i].unicode += kCmapGlyphOffset; + toFontChar[code] = glyphs[i].unicode; - var glyphName = properties.baseEncoding[code]; + var glyphName = glyphNames[gid] || properties.baseEncoding[code]; if (glyphName in GlyphsUnicode) { var unicode = GlyphsUnicode[glyphName]; if (unicode in usedUnicodes) @@ -1921,9 +1931,11 @@ var Font = (function FontClosure() { unicode: unicode, code: glyphs[i].code }); - ids.push(ids[i]); + ids.push(gid); + toFontChar[code] = unicode; } } + this.useToFontChar = true; } // Moving all symbolic font glyphs into 0xF000 - 0xF0FF range. From a6b9efc06bc7ef12806c26cbcefc1feebbb7467f Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Wed, 29 Feb 2012 22:11:32 -0600 Subject: [PATCH 05/15] Re-encode cmap based on post table or current encoding; fix GlyphsUnicode table entries --- src/fonts.js | 58 ++++++++++++++++++++++++++++++++-- src/glyphlist.js | 81 ------------------------------------------------ 2 files changed, 55 insertions(+), 84 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index 9d11d81cc..d04fd1a4e 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -1870,6 +1870,9 @@ var Font = (function FontClosure() { ids[i] = i; } + var unusedUnicode = kCmapGlyphOffset; + var glyphNames = properties.glyphNames || []; + var encoding = properties.baseEncoding; if (toFontChar && toFontChar.length > 0) { // checking if cmap is just identity map var isIdentity = true; @@ -1892,7 +1895,6 @@ var Font = (function FontClosure() { glyphs[i].unicode = unicode; usedUnicodes[unicode] = true; } - var unusedUnicode = kCmapGlyphOffset; for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) { var i = unassignedUnicodeItems[j]; while (unusedUnicode in usedUnicodes) @@ -1913,14 +1915,13 @@ var Font = (function FontClosure() { // copying all characters to private use area, all mapping all known // glyphs to the unicodes. The glyphs and ids arrays will grow. var usedUnicodes = []; - var glyphNames = properties.glyphNames || []; for (var i = 0, ii = glyphs.length; i < ii; i++) { var code = glyphs[i].unicode; var gid = ids[i]; glyphs[i].unicode += kCmapGlyphOffset; toFontChar[code] = glyphs[i].unicode; - var glyphName = glyphNames[gid] || properties.baseEncoding[code]; + var glyphName = glyphNames[gid] || encoding[code]; if (glyphName in GlyphsUnicode) { var unicode = GlyphsUnicode[glyphName]; if (unicode in usedUnicodes) @@ -1936,6 +1937,57 @@ var Font = (function FontClosure() { } } this.useToFontChar = true; + } else if (!this.isSymbolicFont && + (this.hasEncoding || properties.glyphNames)) { + // Re-encode cmap encoding to unicode, based on the 'post' table data + // or base encoding + var reverseMap = []; + for (var i = 0, ii = glyphs.length; i < ii; i++) + reverseMap[glyphs[i].unicode] = i; + + for (var i = 0, ii = glyphs.length; i < ii; i++) { + var code = glyphs[i].unicode; + var gid = ids[i] + + var glyphName = glyphNames[gid] || encoding[code]; + if (glyphName in GlyphsUnicode) { + var unicode = GlyphsUnicode[glyphName]; + if (!unicode || reverseMap[unicode] === i) + continue; // unknown glyph name or in its own place + + var destination = reverseMap[unicode]; + var j = i; + // Flipping unicodes while next destination unicode has assigned + // glyph and future glyph can be assigned to unicode. + while (typeof destination === 'number') { + glyphs[j].unicode = unicode; + reverseMap[unicode] = j; + + code = glyphs[destination].unicode; + gid = ids[destination]; + glyphName = glyphNames[gid] || encoding[code]; + + unicode = GlyphsUnicode[glyphName]; + if (!unicode || reverseMap[unicode] === j) { + unicode = 0; + break; // unknown glyph name or in its own place + } + + j = destination; + destination = reverseMap[unicode]; + } + + if (!unicode) { + // Future glyph cannot be assigned to unicode, generate new one. + while (reverseMap[unusedUnicode]) + unusedUnicode++; + unicode = unusedUnicode++; + } + + glyphs[j].unicode = unicode; + reverseMap[unicode] = j; + } + } } // Moving all symbolic font glyphs into 0xF000 - 0xF0FF range. diff --git a/src/glyphlist.js b/src/glyphlist.js index 01b94442a..694134840 100644 --- a/src/glyphlist.js +++ b/src/glyphlist.js @@ -1508,27 +1508,7 @@ var GlyphsUnicode = { dalet: 0x05D3, daletdagesh: 0xFB33, daletdageshhebrew: 0xFB33, - dalethatafpatah: 0x05D305B2, - dalethatafpatahhebrew: 0x05D305B2, - dalethatafsegol: 0x05D305B1, - dalethatafsegolhebrew: 0x05D305B1, dalethebrew: 0x05D3, - dalethiriq: 0x05D305B4, - dalethiriqhebrew: 0x05D305B4, - daletholam: 0x05D305B9, - daletholamhebrew: 0x05D305B9, - daletpatah: 0x05D305B7, - daletpatahhebrew: 0x05D305B7, - daletqamats: 0x05D305B8, - daletqamatshebrew: 0x05D305B8, - daletqubuts: 0x05D305BB, - daletqubutshebrew: 0x05D305BB, - daletsegol: 0x05D305B6, - daletsegolhebrew: 0x05D305B6, - daletsheva: 0x05D305B0, - daletshevahebrew: 0x05D305B0, - dalettsere: 0x05D305B5, - dalettserehebrew: 0x05D305B5, dalfinalarabic: 0xFEAA, dammaarabic: 0x064F, dammalowarabic: 0x064F, @@ -1845,10 +1825,6 @@ var GlyphsUnicode = { finalkafdagesh: 0xFB3A, finalkafdageshhebrew: 0xFB3A, finalkafhebrew: 0x05DA, - finalkafqamats: 0x05DA05B8, - finalkafqamatshebrew: 0x05DA05B8, - finalkafsheva: 0x05DA05B0, - finalkafshevahebrew: 0x05DA05B0, finalmem: 0x05DD, finalmemhebrew: 0x05DD, finalnun: 0x05DF, @@ -2037,14 +2013,7 @@ var GlyphsUnicode = { hakatakanahalfwidth: 0xFF8A, halantgurmukhi: 0x0A4D, hamzaarabic: 0x0621, - hamzadammaarabic: 0x0621064F, - hamzadammatanarabic: 0x0621064C, - hamzafathaarabic: 0x0621064E, - hamzafathatanarabic: 0x0621064B, hamzalowarabic: 0x0621, - hamzalowkasraarabic: 0x06210650, - hamzalowkasratanarabic: 0x0621064D, - hamzasukunarabic: 0x06210652, hangulfiller: 0x3164, hardsigncyrillic: 0x044A, harpoonleftbarbup: 0x21BC, @@ -2476,10 +2445,6 @@ var GlyphsUnicode = { lameddagesh: 0xFB3C, lameddageshhebrew: 0xFB3C, lamedhebrew: 0x05DC, - lamedholam: 0x05DC05B9, - lamedholamdagesh: '05DC 05B9 05BC', - lamedholamdageshhebrew: '05DC 05B9 05BC', - lamedholamhebrew: 0x05DC05B9, lamfinalarabic: 0xFEDE, lamhahinitialarabic: 0xFCCA, laminitialarabic: 0xFEDF, @@ -2489,8 +2454,6 @@ var GlyphsUnicode = { lammedialarabic: 0xFEE0, lammeemhahinitialarabic: 0xFD88, lammeeminitialarabic: 0xFCCC, - lammeemjeeminitialarabic: 'FEDF FEE4 FEA0', - lammeemkhahinitialarabic: 'FEDF FEE4 FEA8', largecircle: 0x25EF, lbar: 0x019A, lbelt: 0x026C, @@ -2787,7 +2750,6 @@ var GlyphsUnicode = { noonfinalarabic: 0xFEE6, noonghunnaarabic: 0x06BA, noonghunnafinalarabic: 0xFB9F, - noonhehinitialarabic: 0xFEE7FEEC, nooninitialarabic: 0xFEE7, noonjeeminitialarabic: 0xFCD2, noonjeemisolatedarabic: 0xFC4B, @@ -3159,27 +3121,7 @@ var GlyphsUnicode = { qof: 0x05E7, qofdagesh: 0xFB47, qofdageshhebrew: 0xFB47, - qofhatafpatah: 0x05E705B2, - qofhatafpatahhebrew: 0x05E705B2, - qofhatafsegol: 0x05E705B1, - qofhatafsegolhebrew: 0x05E705B1, qofhebrew: 0x05E7, - qofhiriq: 0x05E705B4, - qofhiriqhebrew: 0x05E705B4, - qofholam: 0x05E705B9, - qofholamhebrew: 0x05E705B9, - qofpatah: 0x05E705B7, - qofpatahhebrew: 0x05E705B7, - qofqamats: 0x05E705B8, - qofqamatshebrew: 0x05E705B8, - qofqubuts: 0x05E705BB, - qofqubutshebrew: 0x05E705BB, - qofsegol: 0x05E705B6, - qofsegolhebrew: 0x05E705B6, - qofsheva: 0x05E705B0, - qofshevahebrew: 0x05E705B0, - qoftsere: 0x05E705B5, - qoftserehebrew: 0x05E705B5, qparen: 0x24AC, quarternote: 0x2669, qubuts: 0x05BB, @@ -3253,32 +3195,11 @@ var GlyphsUnicode = { reharmenian: 0x0580, rehfinalarabic: 0xFEAE, rehiragana: 0x308C, - rehyehaleflamarabic: '0631 FEF3 FE8E 0644', rekatakana: 0x30EC, rekatakanahalfwidth: 0xFF9A, resh: 0x05E8, reshdageshhebrew: 0xFB48, - reshhatafpatah: 0x05E805B2, - reshhatafpatahhebrew: 0x05E805B2, - reshhatafsegol: 0x05E805B1, - reshhatafsegolhebrew: 0x05E805B1, reshhebrew: 0x05E8, - reshhiriq: 0x05E805B4, - reshhiriqhebrew: 0x05E805B4, - reshholam: 0x05E805B9, - reshholamhebrew: 0x05E805B9, - reshpatah: 0x05E805B7, - reshpatahhebrew: 0x05E805B7, - reshqamats: 0x05E805B8, - reshqamatshebrew: 0x05E805B8, - reshqubuts: 0x05E805BB, - reshqubutshebrew: 0x05E805BB, - reshsegol: 0x05E805B6, - reshsegolhebrew: 0x05E805B6, - reshsheva: 0x05E805B0, - reshshevahebrew: 0x05E805B0, - reshtsere: 0x05E805B5, - reshtserehebrew: 0x05E805B5, reversedtilde: 0x223D, reviahebrew: 0x0597, reviamugrashhebrew: 0x0597, @@ -3477,7 +3398,6 @@ var GlyphsUnicode = { shaddadammaarabic: 0xFC61, shaddadammatanarabic: 0xFC5E, shaddafathaarabic: 0xFC60, - shaddafathatanarabic: 0x0651064B, shaddakasraarabic: 0xFC62, shaddakasratanarabic: 0xFC5F, shade: 0x2592, @@ -3674,7 +3594,6 @@ var GlyphsUnicode = { tchehfinalarabic: 0xFB7B, tchehinitialarabic: 0xFB7C, tchehmedialarabic: 0xFB7D, - tchehmeeminitialarabic: 0xFB7CFEE4, tcircle: 0x24E3, tcircumflexbelow: 0x1E71, tcommaaccent: 0x0163, From 8cb8de3092c90e1ceb6753372ef6ab4b0b33bd57 Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Wed, 29 Feb 2012 22:57:54 -0600 Subject: [PATCH 06/15] Lint error --- src/fonts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fonts.js b/src/fonts.js index d04fd1a4e..1992c0c7d 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -1947,7 +1947,7 @@ var Font = (function FontClosure() { for (var i = 0, ii = glyphs.length; i < ii; i++) { var code = glyphs[i].unicode; - var gid = ids[i] + var gid = ids[i]; var glyphName = glyphNames[gid] || encoding[code]; if (glyphName in GlyphsUnicode) { From 447098936cde2ceeb62d080edfd7418fceabc91d Mon Sep 17 00:00:00 2001 From: Artur Adib <arturadib@gmail.com> Date: Thu, 1 Mar 2012 12:31:53 -0500 Subject: [PATCH 07/15] dummy README - testing bot's gh-pages update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33bb30b66..c9631df61 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # PDF.JS - + pdf.js is an HTML5 technology experiment that explores building a faithful and efficient Portable Document Format (PDF) renderer without native code From fb1276c00f232fadbc269f04102fcf251c6f21f9 Mon Sep 17 00:00:00 2001 From: Artur Adib <arturadib@gmail.com> Date: Thu, 1 Mar 2012 12:35:38 -0500 Subject: [PATCH 08/15] testing bot --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c9631df61..33bb30b66 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # PDF.JS - + pdf.js is an HTML5 technology experiment that explores building a faithful and efficient Portable Document Format (PDF) renderer without native code From 65811a7bfe5e730c770a1d368e51995f4040943f Mon Sep 17 00:00:00 2001 From: Artur Adib <arturadib@gmail.com> Date: Thu, 1 Mar 2012 12:41:06 -0500 Subject: [PATCH 09/15] another bot test --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33bb30b66..c9631df61 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # PDF.JS - + pdf.js is an HTML5 technology experiment that explores building a faithful and efficient Portable Document Format (PDF) renderer without native code From 38e3f32557a8a96fba2fea3851a0e2d6b0563598 Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Thu, 1 Mar 2012 21:23:36 -0600 Subject: [PATCH 10/15] Add and fix pdfkit_compressed.pdf --- src/fonts.js | 30 +++++++++++++++++++++++++----- test/pdfs/.gitignore | 1 + test/pdfs/pdfkit_compressed.pdf | Bin 0 -> 8286 bytes test/test_manifest.json | 6 ++++++ 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 test/pdfs/pdfkit_compressed.pdf diff --git a/src/fonts.js b/src/fonts.js index 261a907f2..0d0e12fec 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -1881,6 +1881,7 @@ var Font = (function FontClosure() { var unusedUnicode = kCmapGlyphOffset; var glyphNames = properties.glyphNames || []; var encoding = properties.baseEncoding; + var differences = properties.differences; if (toFontChar && toFontChar.length > 0) { // checking if cmap is just identity map var isIdentity = true; @@ -1945,35 +1946,51 @@ var Font = (function FontClosure() { } } this.useToFontChar = true; - } else if (!this.isSymbolicFont && - (this.hasEncoding || properties.glyphNames)) { + } else if (!this.isSymbolicFont && (this.hasEncoding || + properties.glyphNames || differences.length > 0)) { // Re-encode cmap encoding to unicode, based on the 'post' table data - // or base encoding + // diffrence array or base encoding var reverseMap = []; for (var i = 0, ii = glyphs.length; i < ii; i++) reverseMap[glyphs[i].unicode] = i; for (var i = 0, ii = glyphs.length; i < ii; i++) { var code = glyphs[i].unicode; + var changeCode = false; var gid = ids[i]; - var glyphName = glyphNames[gid] || encoding[code]; + var glyphName = glyphNames[gid]; + if (!glyphName) { + glyphName = differences[code] || encoding[code]; + changeCode = true; + } if (glyphName in GlyphsUnicode) { var unicode = GlyphsUnicode[glyphName]; if (!unicode || reverseMap[unicode] === i) continue; // unknown glyph name or in its own place var destination = reverseMap[unicode]; + if (typeof destination === 'number' && destination > i) + continue; + var j = i; // Flipping unicodes while next destination unicode has assigned // glyph and future glyph can be assigned to unicode. while (typeof destination === 'number') { glyphs[j].unicode = unicode; reverseMap[unicode] = j; + if (changeCode) { + toFontChar[code] = unicode; + changeCode = false; + } code = glyphs[destination].unicode; gid = ids[destination]; - glyphName = glyphNames[gid] || encoding[code]; + glyphName = glyphNames[gid]; + if (!glyphName) { + glyphName = differences[code] || encoding[code]; + changeCode = true; + } unicode = GlyphsUnicode[glyphName]; if (!unicode || reverseMap[unicode] === j) { @@ -1994,7 +2011,10 @@ var Font = (function FontClosure() { glyphs[j].unicode = unicode; reverseMap[unicode] = j; + if (changeCode) + toFontChar[code] = unicode; } + this.useToFontChar = true; } } diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 81b63290d..f14236860 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -19,6 +19,7 @@ !issue840.pdf !scan-bad.pdf !freeculture.pdf +!pdfkit_compressed.pdf !issue918.pdf !issue1249.pdf !smaskdim.pdf diff --git a/test/pdfs/pdfkit_compressed.pdf b/test/pdfs/pdfkit_compressed.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f3e25216d231f4b18c14e33b00bfc7877650c30c GIT binary patch literal 8286 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 NVEHN=o07Kj{{RcE)-wPA literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index c2cbaa415..16d924151 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -466,6 +466,12 @@ "link": true, "type": "eq" }, + { "id": "pdfkit_compressed", + "file": "pdfs/pdfkit_compressed.pdf", + "md5": "ffe9c571d0a1572e234253e6c7cdee6c", + "rounds": 1, + "type": "eq" + }, { "id": "issue925", "file": "pdfs/issue925.pdf", "md5": "f58fe943090aff89dcc8e771bc0db4c2", From ab3107e8e0df30f2006fca6881aebb3730f5fd63 Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Thu, 1 Mar 2012 22:01:39 -0600 Subject: [PATCH 11/15] Optimization --- src/fonts.js | 48 ++++-------------------------------------------- 1 file changed, 4 insertions(+), 44 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index 0d0e12fec..df0acbbc5 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -1966,51 +1966,11 @@ var Font = (function FontClosure() { } if (glyphName in GlyphsUnicode) { var unicode = GlyphsUnicode[glyphName]; - if (!unicode || reverseMap[unicode] === i) - continue; // unknown glyph name or in its own place + if (!unicode || (unicode in reverseMap)) + continue; // unknown glyph name or its place is taken - var destination = reverseMap[unicode]; - if (typeof destination === 'number' && destination > i) - continue; - - var j = i; - // Flipping unicodes while next destination unicode has assigned - // glyph and future glyph can be assigned to unicode. - while (typeof destination === 'number') { - glyphs[j].unicode = unicode; - reverseMap[unicode] = j; - if (changeCode) { - toFontChar[code] = unicode; - changeCode = false; - } - - code = glyphs[destination].unicode; - gid = ids[destination]; - glyphName = glyphNames[gid]; - if (!glyphName) { - glyphName = differences[code] || encoding[code]; - changeCode = true; - } - - unicode = GlyphsUnicode[glyphName]; - if (!unicode || reverseMap[unicode] === j) { - unicode = 0; - break; // unknown glyph name or in its own place - } - - j = destination; - destination = reverseMap[unicode]; - } - - if (!unicode) { - // Future glyph cannot be assigned to unicode, generate new one. - while (reverseMap[unusedUnicode]) - unusedUnicode++; - unicode = unusedUnicode++; - } - - glyphs[j].unicode = unicode; - reverseMap[unicode] = j; + glyphs[i].unicode = unicode; + reverseMap[unicode] = i; if (changeCode) toFontChar[code] = unicode; } From b870cbad0f38235d1bc19efd8f331f85aec34e76 Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Fri, 2 Mar 2012 07:11:24 -0600 Subject: [PATCH 12/15] Move custom style --- src/util.js | 51 --------------------------------------------------- web/viewer.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/util.js b/src/util.js index b0739ec3a..93bd36b55 100644 --- a/src/util.js +++ b/src/util.js @@ -174,57 +174,6 @@ var Util = (function UtilClosure() { return Util; })(); -// optimised CSS custom property getter/setter -var CustomStyle = (function CustomStyleClosure() { - - // As noted on: http://www.zachstronaut.com/posts/2009/02/17/ - // animate-css-transforms-firefox-webkit.html - // in some versions of IE9 it is critical that ms appear in this list - // before Moz - var prefixes = ['ms', 'Moz', 'Webkit', 'O']; - var _cache = { }; - - function CustomStyle() { - } - - CustomStyle.getProp = function get(propName, element) { - // check cache only when no element is given - if (arguments.length == 1 && typeof _cache[propName] == 'string') { - return _cache[propName]; - } - - element = element || document.documentElement; - var style = element.style, prefixed, uPropName; - - // test standard property first - if (typeof style[propName] == 'string') { - return (_cache[propName] = propName); - } - - // capitalize - uPropName = propName.charAt(0).toUpperCase() + propName.slice(1); - - // test vendor specific properties - for (var i = 0, l = prefixes.length; i < l; i++) { - prefixed = prefixes[i] + uPropName; - if (typeof style[prefixed] == 'string') { - return (_cache[propName] = prefixed); - } - } - - //if all fails then set to undefined - return (_cache[propName] = 'undefined'); - } - - CustomStyle.setProp = function set(propName, element, str) { - var prop = this.getProp(propName); - if (prop != 'undefined') - element.style[prop] = str; - } - - return CustomStyle; -})(); - var PDFStringTranslateTable = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, diff --git a/web/viewer.js b/web/viewer.js index 9f477f3a7..77bbc2c0e 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -1022,6 +1022,57 @@ var DocumentOutlineView = function documentOutlineView(outline) { } }; +// optimised CSS custom property getter/setter +var CustomStyle = (function CustomStyleClosure() { + + // As noted on: http://www.zachstronaut.com/posts/2009/02/17/ + // animate-css-transforms-firefox-webkit.html + // in some versions of IE9 it is critical that ms appear in this list + // before Moz + var prefixes = ['ms', 'Moz', 'Webkit', 'O']; + var _cache = { }; + + function CustomStyle() { + } + + CustomStyle.getProp = function get(propName, element) { + // check cache only when no element is given + if (arguments.length == 1 && typeof _cache[propName] == 'string') { + return _cache[propName]; + } + + element = element || document.documentElement; + var style = element.style, prefixed, uPropName; + + // test standard property first + if (typeof style[propName] == 'string') { + return (_cache[propName] = propName); + } + + // capitalize + uPropName = propName.charAt(0).toUpperCase() + propName.slice(1); + + // test vendor specific properties + for (var i = 0, l = prefixes.length; i < l; i++) { + prefixed = prefixes[i] + uPropName; + if (typeof style[prefixed] == 'string') { + return (_cache[propName] = prefixed); + } + } + + //if all fails then set to undefined + return (_cache[propName] = 'undefined'); + } + + CustomStyle.setProp = function set(propName, element, str) { + var prop = this.getProp(propName); + if (prop != 'undefined') + element.style[prop] = str; + } + + return CustomStyle; +})(); + var TextLayerBuilder = function textLayerBuilder(textLayerDiv) { this.textLayerDiv = textLayerDiv; From f6ba3843bd59337a6760e8df85aedd53685d237e Mon Sep 17 00:00:00 2001 From: Artur Adib <arturadib@gmail.com> Date: Sat, 3 Mar 2012 14:01:31 -0500 Subject: [PATCH 13/15] Using ShellJS --- external/shelljs/LICENSE | 26 + external/shelljs/README.md | 316 +++++++++ external/shelljs/global.js | 3 + external/shelljs/make.js | 46 ++ external/shelljs/package.json | 12 + external/shelljs/shell.js | 1207 +++++++++++++++++++++++++++++++++ make.js | 401 +++++++++++ 7 files changed, 2011 insertions(+) create mode 100644 external/shelljs/LICENSE create mode 100644 external/shelljs/README.md create mode 100644 external/shelljs/global.js create mode 100644 external/shelljs/make.js create mode 100644 external/shelljs/package.json create mode 100644 external/shelljs/shell.js create mode 100755 make.js diff --git a/external/shelljs/LICENSE b/external/shelljs/LICENSE new file mode 100644 index 000000000..1b35ee9fb --- /dev/null +++ b/external/shelljs/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2012, Artur Adib <aadib@mozilla.com> +All rights reserved. + +You may use this project under the terms of the New BSD license as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Artur Adib nor the + names of the contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/external/shelljs/README.md b/external/shelljs/README.md new file mode 100644 index 000000000..01b99a5f8 --- /dev/null +++ b/external/shelljs/README.md @@ -0,0 +1,316 @@ +# ShellJS - Unix shell commands for Node.js [](http://travis-ci.org/arturadib/shelljs) + +ShellJS is a **portable** (Windows included) implementation of Unix shell commands on top of the Node.js API. You can use it to eliminate your shell script's dependency on Unix while still keeping its familiar and powerful commands. + +The project is both [unit-tested](http://travis-ci.org/arturadib/shelljs) and battle-tested at Mozilla's [pdf.js](http://github.com/mozilla/pdf.js). + + +### Example + +```javascript +require('shelljs/global'); + +// Copy files to release dir +mkdir('-p', 'out/Release'); +cp('-R', 'lib/*.js', 'out/Release'); + +// Replace macros in each .js file +cd('lib'); +for (file in ls('*.js')) { + sed('-i', 'BUILD_VERSION', 'v0.1.2', file); + sed('-i', /.*REMOVE_THIS_LINE.*\n/, '', file); + sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, cat('macro.js'), file); +} +cd('..'); + +// Run external tool synchronously +if (exec('git commit -am "Auto-commit"').code !== 0) { + echo('Error: Git commit failed'); + exit(1); +} +``` + +### Global vs. Local + +The example above uses the convenience script `shelljs/global` to reduce verbosity. If polluting your global namespace is not desirable, simply require `shelljs`. + +Example: + +```javascript +var shell = require('shelljs'); +shell.echo('hello world'); +``` + +### Make tool + +A convenience script `shelljs/make` is also provided to mimic the behavior of a Unix Makefile. In this case all shell objects are global, and command line arguments will cause the script to execute only the corresponding function in the global `target` object. To avoid redundant calls, target functions are executed only once per script. + +Example: + +```javascript +// +// Example file: make.js +// +require('shelljs/make'); + +target.all = function() { + target.bundle(); + target.docs(); +} + +// Bundle JS files +target.bundle = function() { + cd(__dirname); + mkdir('build'); + cd('lib'); + cat('*.js').to('../build/output.js'); +} + +// Generate docs +target.docs = function() { + cd(__dirname); + mkdir('docs'); + cd('lib'); + for (file in ls('*.js')) { + var text = grep('//@', file); // extract special comments + text.replace('//@', ''); // remove comment tags + text.to('docs/my_docs.md'); + } +} +``` + +To run the target `all`, call the above script without arguments: `$ node make`. To run the target `docs`: `$ node make docs`, and so on. + +### Installing + +Via npm: + +```bash +$ npm install shelljs +``` + +Or simply copy `shell.js` into your project's directory, and `require()` accordingly. + + +<!-- + + DO NOT MODIFY BEYOND THIS POINT - IT'S AUTOMATICALLY GENERATED + +--> + + +# Command reference + + +All commands run synchronously, unless otherwise stated. + + +#### cd('dir') +Changes to directory `dir` for the duration of the script + +#### pwd() +Returns the current directory. + +#### ls([options ,] path [,path ...]) +#### ls([options ,] path_array) +Available options: + ++ `-R`: recursive ++ `-a`: all files (include files beginning with `.`) + +Examples: + +```javascript +ls('projs/*.js'); +ls('-R', '/users/me', '/tmp'); +ls('-R', ['/users/me', '/tmp']); // same as above +``` + +Returns list of files in the given path, or in current directory if no path provided. +For convenient iteration via `for (file in ls())`, the format returned is a hash object: +`{ 'file1':null, 'dir1/file2':null, ...}`. + +#### cp('[options ,] source [,source ...], dest') +#### cp('[options ,] source_array, dest') +Available options: + ++ `-f`: force ++ `-r, -R`: recursive + +Examples: + +```javascript +cp('file1', 'dir1'); +cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp'); +cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above +``` + +Copies files. The wildcard `*` is accepted. + +#### rm([options ,] file [, file ...]) +#### rm([options ,] file_array) +Available options: + ++ `-f`: force ++ `-r, -R`: recursive + +Examples: + +```javascript +rm('-rf', '/tmp/*'); +rm('some_file.txt', 'another_file.txt'); +rm(['some_file.txt', 'another_file.txt']); // same as above +``` + +Removes files. The wildcard `*` is accepted. + +#### mv(source [, source ...], dest') +#### mv(source_array, dest') +Available options: + ++ `f`: force + +Examples: + +```javascript +mv('-f', 'file', 'dir/'); +mv('file1', 'file2', 'dir/'); +mv(['file1', 'file2'], 'dir/'); // same as above +``` + +Moves files. The wildcard `*` is accepted. + +#### mkdir([options ,] dir [, dir ...]) +#### mkdir([options ,] dir_array) +Available options: + ++ `p`: full path (will create intermediate dirs if necessary) + +Examples: + +```javascript +mkdir('-p', '/tmp/a/b/c/d', '/tmp/e/f/g'); +mkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above +``` + +Creates directories. + +#### cat(file [, file ...]) +#### cat(file_array) + +Examples: + +```javascript +var str = cat('file*.txt'); +var str = cat('file1', 'file2'); +var str = cat(['file1', 'file2']); // same as above +``` + +Returns a string containing the given file, or a concatenated string +containing the files if more than one file is given (a new line character is +introduced between each file). Wildcard `*` accepted. + +#### 'string'.to(file) + +Examples: + +```javascript +cat('input.txt').to('output.txt'); +``` + +Analogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as +those returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_ + +#### sed([options ,] search_regex, replace_str, file) +Available options: + ++ `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_ + +Examples: + +```javascript +sed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js'); +sed(/.*DELETE_THIS_LINE.*\n/, '', 'source.js'); +``` + +Reads an input string from `file` and performs a JavaScript `replace()` on the input +using the given search regex and replacement string. Returns the new string after replacement. + +#### grep(regex_filter, file [, file ...]) +#### grep(regex_filter, file_array) + +Examples: + +```javascript +grep('GLOBAL_VARIABLE', '*.js'); +``` + +Reads input string from given files and returns a string containing all lines of the +file that match the given `regex_filter`. Wildcard `*` accepted. + +#### which(command) + +Examples: + +```javascript +var nodeExec = which('node'); +``` + +Searches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions. +Returns string containing the absolute path to the command. + +#### echo(string [,string ...]) + +Examples: + +```javascript +echo('hello world'); +var str = echo('hello world'); +``` + +Prints string to stdout, and returns string with additional utility methods +like `.to()`. + +#### exit(code) +Exits the current process with the given exit code. + +#### env['VAR_NAME'] +Object containing environment variables (both getter and setter). Shortcut to process.env. + +#### exec(command [, options] [, callback]) +Available options (all `false` by default): + ++ `async`: Asynchronous execution. Needs callback. ++ `silent`: Do not echo program output to console. + +Examples: + +```javascript +var version = exec('node --version', {silent:true}).output; +``` + +Executes the given `command` _synchronously_, unless otherwise specified. +When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's +`output` (stdout + stderr) and its exit `code`. Otherwise the `callback` gets the +arguments `(code, output)`. + +## Non-Unix commands + + +#### tempdir() +Searches and returns string containing a writeable, platform-dependent temporary directory. +Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir). + +#### exists(path [, path ...]) +#### exists(path_array) +Returns true if all the given paths exist. + +#### error() +Tests if error occurred in the last command. Returns `null` if no error occurred, +otherwise returns string explaining the error + +#### verbose() +Enables all output (default) + +#### silent() +Suppresses all output, except for explict `echo()` calls diff --git a/external/shelljs/global.js b/external/shelljs/global.js new file mode 100644 index 000000000..83a27e69a --- /dev/null +++ b/external/shelljs/global.js @@ -0,0 +1,3 @@ +var shell = require('./shell.js'); +for (cmd in shell) + global[cmd] = shell[cmd]; diff --git a/external/shelljs/make.js b/external/shelljs/make.js new file mode 100644 index 000000000..9c92dadc8 --- /dev/null +++ b/external/shelljs/make.js @@ -0,0 +1,46 @@ +require('./global'); + +global.target = {}; + +// This ensures we only execute the script targets after the entire script has +// been evaluated +var args = process.argv.slice(2); +setTimeout(function() { + + if (args.length === 1 && args[0] === '--help') { + console.log('Available targets:'); + for (t in target) + console.log(' ' + t); + return; + } + + // Wrap targets to prevent duplicate execution + for (t in target) { + (function(t, oldTarget){ + + // Wrap it + target[t] = function(force) { + if (oldTarget.done && !force) + return; + oldTarget.done = true; + return oldTarget(arguments); + } + + })(t, target[t]); + } + + // Execute desired targets + if (args.length > 0) { + args.forEach(function(arg) { + if (arg in target) + target[arg](); + else { + console.log('no such target: ' + arg); + exit(1); + } + }); + } else if ('all' in target) { + target.all(); + } + +}, 0); diff --git a/external/shelljs/package.json b/external/shelljs/package.json new file mode 100644 index 000000000..9499680e9 --- /dev/null +++ b/external/shelljs/package.json @@ -0,0 +1,12 @@ +{ "name": "shelljs" +, "version": "0.0.2pre1" +, "author": "Artur Adib <aadib@mozilla.com>" +, "description": "Portable Unix shell commands for Node.js" +, "keywords": ["unix", "shell", "makefile", "make", "jake", "synchronous"] +, "repository": "git://github.com/arturadib/shelljs" +, "homepage": "http://github.com/arturadib/shelljs" +, "main": "./shell.js" +, "scripts": { + "test": "node scripts/run-tests" + } +} diff --git a/external/shelljs/shell.js b/external/shelljs/shell.js new file mode 100644 index 000000000..2d9a5a787 --- /dev/null +++ b/external/shelljs/shell.js @@ -0,0 +1,1207 @@ +// +// ShellJS +// Unix shell commands on top of Node's API +// +// Copyright (c) 2012 Artur Adib +// http://github.com/arturadib/shelljs +// + +var fs = require('fs'), + path = require('path'), + util = require('util'), + vm = require('vm'), + child = require('child_process'), + os = require('os'); + +// Node shims for < v0.7 +fs.existsSync = fs.existsSync || path.existsSync; + +var state = { + error: null, + fatal: false, + silent: false, + currentCmd: 'shell.js', + tempDir: null + }, + platform = os.type().match(/^Win/) ? 'win' : 'unix'; + + +//@ +//@ All commands run synchronously, unless otherwise stated. +//@ + + +//@ +//@ #### cd('dir') +//@ Changes to directory `dir` for the duration of the script +function _cd(options, dir) { + if (!dir) + error('directory not specified'); + + if (!fs.existsSync(dir)) + error('no such file or directory: ' + dir); + + if (fs.existsSync(dir) && !fs.statSync(dir).isDirectory()) + error('not a directory: ' + dir); + + process.chdir(dir); +}; +exports.cd = wrap('cd', _cd); + +//@ +//@ #### pwd() +//@ Returns the current directory. +function _pwd(options) { + var pwd = path.resolve(process.cwd()); + return ShellString(pwd); +}; +exports.pwd = wrap('pwd', _pwd); + +//@ +//@ #### ls([options ,] path [,path ...]) +//@ #### ls([options ,] path_array) +//@ Available options: +//@ +//@ + `-R`: recursive +//@ + `-a`: all files (include files beginning with `.`) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ ls('projs/*.js'); +//@ ls('-R', '/users/me', '/tmp'); +//@ ls('-R', ['/users/me', '/tmp']); // same as above +//@ ``` +//@ +//@ Returns list of files in the given path, or in current directory if no path provided. +//@ For convenient iteration via `for (file in ls())`, the format returned is a hash object: +//@ `{ 'file1':null, 'dir1/file2':null, ...}`. +function _ls(options, paths) { + options = parseOptions(options, { + 'R': 'recursive', + 'a': 'all' + }); + + if (!paths) + paths = ['.']; + else if (typeof paths === 'object') + paths = paths; // assume array + else if (typeof paths === 'string') + paths = [].slice.call(arguments, 1); + + var hash = {}; + + function pushHash(file, query) { + // hidden file? + if (path.basename(file)[0] === '.') { + // not explicitly asking for hidden files? + if (!options.all && !(path.basename(query)[0] === '.' && path.basename(query).length > 1)) + return; + } + + hash[file] = null; + } + + paths.forEach(function(p) { + if (fs.existsSync(p)) { + // Simple file? + if (fs.statSync(p).isFile()) { + pushHash(p, p); + return; // continue + } + + // Simple dir? + if (fs.statSync(p).isDirectory()) { + // Iterate over p contents + fs.readdirSync(p).forEach(function(file) { + pushHash(file, p); + + // Recursive + var oldDir = _pwd(); + _cd('', p); + if (fs.statSync(file).isDirectory() && options.recursive) + hash = extend(hash, _ls('-R', file+'/*')); + _cd('', oldDir); + }); + return; // continue + } + } + + // p does not exist - possible wildcard present + + var basename = path.basename(p); + var dirname = path.dirname(p); + // Wildcard present on an existing dir? (e.g. '/tmp/*.js') + if (basename.search(/\*/) > -1 && fs.existsSync(dirname) && fs.statSync(dirname).isDirectory) { + // Escape special regular expression chars + var regexp = basename.replace(/(\^|\$|\(|\)|\<|\>|\[|\]|\{|\}|\.|\+|\?)/g, '\\$1'); + // Translates wildcard into regex + regexp = '^' + regexp.replace(/\*/g, '.*'); + // Iterate over directory contents + fs.readdirSync(dirname).forEach(function(file) { + if (file.match(new RegExp(regexp))) { + pushHash(path.normalize(dirname+'/'+file), basename); + + // Recursive + var pp = dirname + '/' + file; + if (fs.statSync(pp).isDirectory() && options.recursive) + hash = extend(hash, _ls('-R', pp+'/*')); + } + }); // forEach + return; + } + + error('no such file or directory: ' + p, true); + }); + + return hash; +}; +exports.ls = wrap('ls', _ls); + + +//@ +//@ #### cp('[options ,] source [,source ...], dest') +//@ #### cp('[options ,] source_array, dest') +//@ Available options: +//@ +//@ + `-f`: force +//@ + `-r, -R`: recursive +//@ +//@ Examples: +//@ +//@ ```javascript +//@ cp('file1', 'dir1'); +//@ cp('-Rf', '/tmp/*', '/usr/local/*', '/home/tmp'); +//@ cp('-Rf', ['/tmp/*', '/usr/local/*'], '/home/tmp'); // same as above +//@ ``` +//@ +//@ Copies files. The wildcard `*` is accepted. +function _cp(options, sources, dest) { + options = parseOptions(options, { + 'f': 'force', + 'R': 'recursive', + 'r': 'recursive' + }); + + // Get sources, dest + if (arguments.length < 3) { + error('missing <source> and/or <dest>'); + } else if (arguments.length > 3) { + sources = [].slice.call(arguments, 1, arguments.length - 1); + dest = arguments[arguments.length - 1]; + } else if (typeof sources === 'string') { + sources = [sources]; + } else if ('length' in sources) { + sources = sources; // no-op for array + } else { + error('invalid arguments'); + } + + // Dest is not existing dir, but multiple sources given + if ((!fs.existsSync(dest) || !fs.statSync(dest).isDirectory()) && sources.length > 1) + error('dest is not a directory (too many sources)'); + + // Dest is an existing file, but no -f given + if (fs.existsSync(dest) && fs.statSync(dest).isFile() && !options.force) + error('dest file already exists: ' + dest); + + sources = expand(sources); + + sources.forEach(function(src) { + if (!fs.existsSync(src)) { + error('no such file or directory: '+src, true); + return; // skip file + } + + // If here, src exists + + if (fs.statSync(src).isDirectory()) { + if (!options.recursive) { + // Non-Recursive + log(src + ' is a directory (not copied)'); + } else { + // Recursive + // 'cp /a/source dest' should create 'source' in 'dest' + var newDest = dest+'/'+path.basename(src), + checkDir = fs.statSync(src); + try { + fs.mkdirSync(newDest, checkDir.mode); + } catch (e) { + //if the directory already exists, that's okay + if (e.code !== 'EEXIST') throw e; + } + cpdirSyncRecursive(src, newDest, {force: options.force}); + } + return; // done with dir + } + + // If here, src is a file + + // When copying to '/path/dir': + // thisDest = '/path/dir/file1' + var thisDest = dest; + if (fs.existsSync(dest) && fs.statSync(dest).isDirectory()) + thisDest = path.normalize(dest + '/' + path.basename(src)); + + if (fs.existsSync(thisDest) && !options.force) { + error('dest file already exists: ' + thisDest, true); + return; // skip file + } + + copyFileSync(src, thisDest); + }); // forEach(src) +}; // cp +exports.cp = wrap('cp', _cp); + +//@ +//@ #### rm([options ,] file [, file ...]) +//@ #### rm([options ,] file_array) +//@ Available options: +//@ +//@ + `-f`: force +//@ + `-r, -R`: recursive +//@ +//@ Examples: +//@ +//@ ```javascript +//@ rm('-rf', '/tmp/*'); +//@ rm('some_file.txt', 'another_file.txt'); +//@ rm(['some_file.txt', 'another_file.txt']); // same as above +//@ ``` +//@ +//@ Removes files. The wildcard `*` is accepted. +function _rm(options, files) { + options = parseOptions(options, { + 'f': 'force', + 'r': 'recursive', + 'R': 'recursive' + }); + if (!files) + error('no paths given'); + + if (typeof files === 'string') + files = [].slice.call(arguments, 1); + // if it's array leave it as it is + + files = expand(files); + + files.forEach(function(file) { + if (!fs.existsSync(file)) { + // Path does not exist, no force flag given + if (!options.force) + error('no such file or directory: '+file, true); + + return; // skip file + } + + // If here, path exists + + // Remove simple file + if (fs.statSync(file).isFile()) { + fs.unlinkSync(file); + return; + } + + // Path is an existing directory, but no -r flag given + if (fs.statSync(file).isDirectory() && !options.recursive) { + error('path is a directory', true); + return; // skip path + } + + // Recursively remove existing directory + if (fs.statSync(file).isDirectory() && options.recursive) { + rmdirSyncRecursive(file); + } + }); // forEach(file) +}; // rm +exports.rm = wrap('rm', _rm); + +//@ +//@ #### mv(source [, source ...], dest') +//@ #### mv(source_array, dest') +//@ Available options: +//@ +//@ + `f`: force +//@ +//@ Examples: +//@ +//@ ```javascript +//@ mv('-f', 'file', 'dir/'); +//@ mv('file1', 'file2', 'dir/'); +//@ mv(['file1', 'file2'], 'dir/'); // same as above +//@ ``` +//@ +//@ Moves files. The wildcard `*` is accepted. +function _mv(options, sources, dest) { + options = parseOptions(options, { + 'f': 'force' + }); + + // Get sources, dest + if (arguments.length < 3) { + error('missing <source> and/or <dest>'); + } else if (arguments.length > 3) { + sources = [].slice.call(arguments, 1, arguments.length - 1); + dest = arguments[arguments.length - 1]; + } else if (typeof sources === 'string') { + sources = [sources]; + } else if ('length' in sources) { + sources = sources; // no-op for array + } else { + error('invalid arguments'); + } + + sources = expand(sources); + + // Dest is not existing dir, but multiple sources given + if ((!fs.existsSync(dest) || !fs.statSync(dest).isDirectory()) && sources.length > 1) + error('dest is not a directory (too many sources)'); + + // Dest is an existing file, but no -f given + if (fs.existsSync(dest) && fs.statSync(dest).isFile() && !options.force) + error('dest file already exists: ' + dest); + + sources.forEach(function(src) { + if (!fs.existsSync(src)) { + error('no such file or directory: '+src, true); + return; // skip file + } + + // If here, src exists + + // When copying to '/path/dir': + // thisDest = '/path/dir/file1' + var thisDest = dest; + if (fs.existsSync(dest) && fs.statSync(dest).isDirectory()) + thisDest = path.normalize(dest + '/' + path.basename(src)); + + if (fs.existsSync(thisDest) && !options.force) { + error('dest file already exists: ' + thisDest, true); + return; // skip file + } + + if (path.resolve(src) === path.dirname(path.resolve(thisDest))) { + error('cannot move to self: '+src, true); + return; // skip file + } + + fs.renameSync(src, thisDest); + }); // forEach(src) +}; // mv +exports.mv = wrap('mv', _mv); + +//@ +//@ #### mkdir([options ,] dir [, dir ...]) +//@ #### mkdir([options ,] dir_array) +//@ Available options: +//@ +//@ + `p`: full path (will create intermediate dirs if necessary) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ mkdir('-p', '/tmp/a/b/c/d', '/tmp/e/f/g'); +//@ mkdir('-p', ['/tmp/a/b/c/d', '/tmp/e/f/g']); // same as above +//@ ``` +//@ +//@ Creates directories. +function _mkdir(options, dirs) { + options = parseOptions(options, { + 'p': 'fullpath' + }); + if (!dirs) + error('no paths given'); + + if (typeof dirs === 'string') + dirs = [].slice.call(arguments, 1); + // if it's array leave it as it is + + dirs.forEach(function(dir) { + if (fs.existsSync(dir)) { + if (!options.fullpath) + error('path already exists: ' + dir, true); + return; // skip dir + } + + // Base dir does not exist, and no -p option given + var baseDir = path.dirname(dir); + if (!fs.existsSync(baseDir) && !options.fullpath) { + error('no such file or directory: ' + baseDir, true); + return; // skip dir + } + + if (options.fullpath) + mkdirSyncRecursive(dir); + else + fs.mkdirSync(dir, 0777); + }); +}; // mkdir +exports.mkdir = wrap('mkdir', _mkdir); + +//@ +//@ #### cat(file [, file ...]) +//@ #### cat(file_array) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ var str = cat('file*.txt'); +//@ var str = cat('file1', 'file2'); +//@ var str = cat(['file1', 'file2']); // same as above +//@ ``` +//@ +//@ Returns a string containing the given file, or a concatenated string +//@ containing the files if more than one file is given (a new line character is +//@ introduced between each file). Wildcard `*` accepted. +function _cat(options, files) { + var cat = ''; + + if (!files) + error('no paths given'); + + if (typeof files === 'string') + files = [].slice.call(arguments, 1); + // if it's array leave it as it is + + files = expand(files); + + files.forEach(function(file) { + if (!fs.existsSync(file)) + error('no such file or directory: ' + file); + + cat += fs.readFileSync(file, 'utf8') + '\n'; + }); + + if (cat[cat.length-1] === '\n') + cat = cat.substring(0, cat.length-1); + + return ShellString(cat); +}; +exports.cat = wrap('cat', _cat); + +//@ +//@ #### 'string'.to(file) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ cat('input.txt').to('output.txt'); +//@ ``` +//@ +//@ Analogous to the redirection operator `>` in Unix, but works with JavaScript strings (such as +//@ those returned by `cat`, `grep`, etc). _Like Unix redirections, `to()` will overwrite any existing file!_ +function _to(options, file) { + if (!file) + error('wrong arguments'); + + if (!fs.existsSync( path.dirname(file) )) + error('no such file or directory: ' + path.dirname(file)); + + fs.writeFileSync(file, this.toString(), 'utf8'); +}; +// In the future, when Proxies are default, we can add methods like `.to()` to primitive strings. +// For now, this is a dummy function to bookmark places we need such strings +function ShellString(str) { + return str; +} +String.prototype.to = wrap('to', _to); + +//@ +//@ #### sed([options ,] search_regex, replace_str, file) +//@ Available options: +//@ +//@ + `-i`: Replace contents of 'file' in-place. _Note that no backups will be created!_ +//@ +//@ Examples: +//@ +//@ ```javascript +//@ sed('-i', 'PROGRAM_VERSION', 'v0.1.3', 'source.js'); +//@ sed(/.*DELETE_THIS_LINE.*\n/, '', 'source.js'); +//@ ``` +//@ +//@ Reads an input string from `file` and performs a JavaScript `replace()` on the input +//@ using the given search regex and replacement string. Returns the new string after replacement. +function _sed(options, regex, replacement, file) { + options = parseOptions(options, { + 'i': 'inplace' + }); + + if (typeof replacement === 'string') + replacement = replacement; // no-op + else if (typeof replacement === 'number') + replacement = replacement.toString(); // fallback + else + error('invalid replacement string'); + + if (!file) + error('no file given'); + + if (!fs.existsSync(file)) + error('no such file or directory: ' + file); + + var result = fs.readFileSync(file, 'utf8').replace(regex, replacement); + if (options.inplace) + fs.writeFileSync(file, result, 'utf8'); + + return ShellString(result); +}; +exports.sed = wrap('sed', _sed); + +//@ +//@ #### grep(regex_filter, file [, file ...]) +//@ #### grep(regex_filter, file_array) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ grep('GLOBAL_VARIABLE', '*.js'); +//@ ``` +//@ +//@ Reads input string from given files and returns a string containing all lines of the +//@ file that match the given `regex_filter`. Wildcard `*` accepted. +function _grep(options, regex, files) { + if (!files) + error('no paths given'); + + if (typeof files === 'string') + files = [].slice.call(arguments, 2); + // if it's array leave it as it is + + files = expand(files); + + var grep = ''; + files.forEach(function(file) { + if (!fs.existsSync(file)) { + error('no such file or directory: ' + file, true); + return; + } + + var contents = fs.readFileSync(file, 'utf8'), + lines = contents.split(/\r*\n/); + lines.forEach(function(line) { + if (line.match(regex)) + grep += line + '\n'; + }); + }); + + return ShellString(grep); +}; +exports.grep = wrap('grep', _grep); + + +//@ +//@ #### which(command) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ var nodeExec = which('node'); +//@ ``` +//@ +//@ Searches for `command` in the system's PATH. On Windows looks for `.exe`, `.cmd`, and `.bat` extensions. +//@ Returns string containing the absolute path to the command. +function _which(options, cmd) { + if (!cmd) + error('must specify command'); + + var pathEnv = process.env.path || process.env.Path || process.env.PATH, + pathArray = splitPath(pathEnv), + where = null; + + // No relative/absolute paths provided? + if (cmd.search(/\//) === -1) { + // Search for command in PATH + pathArray.forEach(function(dir) { + if (where) + return; // already found it + + var attempt = path.resolve(dir + '/' + cmd); + if (fs.existsSync(attempt)) { + where = attempt; + return; + } + + if (platform === 'win') { + var baseAttempt = attempt; + attempt = baseAttempt + '.exe'; + if (fs.existsSync(attempt)) { + where = attempt; + return; + } + attempt = baseAttempt + '.cmd'; + if (fs.existsSync(attempt)) { + where = attempt; + return; + } + attempt = baseAttempt + '.bat'; + if (fs.existsSync(attempt)) { + where = attempt; + return; + } + } // if 'win' + }); + } + + // Command not found anywhere? + if (!fs.existsSync(cmd) && !where) + return null; + + where = where || path.resolve(cmd); + + return ShellString(where); +}; +exports.which = wrap('which', _which); + +//@ +//@ #### echo(string [,string ...]) +//@ +//@ Examples: +//@ +//@ ```javascript +//@ echo('hello world'); +//@ var str = echo('hello world'); +//@ ``` +//@ +//@ Prints string to stdout, and returns string with additional utility methods +//@ like `.to()`. +function _echo(options) { + var messages = [].slice.call(arguments, 1); + log.apply(this, messages); + return ShellString(messages.join(' ')); +}; +exports.echo = wrap('echo', _echo); + +//@ +//@ #### exit(code) +//@ Exits the current process with the given exit code. +exports.exit = process.exit; + +//@ +//@ #### env['VAR_NAME'] +//@ Object containing environment variables (both getter and setter). Shortcut to process.env. +exports.env = process.env; + +//@ +//@ #### exec(command [, options] [, callback]) +//@ Available options (all `false` by default): +//@ +//@ + `async`: Asynchronous execution. Needs callback. +//@ + `silent`: Do not echo program output to console. +//@ +//@ Examples: +//@ +//@ ```javascript +//@ var version = exec('node --version', {silent:true}).output; +//@ ``` +//@ +//@ Executes the given `command` _synchronously_, unless otherwise specified. +//@ When in synchronous mode returns the object `{ code:..., output:... }`, containing the program's +//@ `output` (stdout + stderr) and its exit `code`. Otherwise the `callback` gets the +//@ arguments `(code, output)`. +function _exec(command, options, callback) { + if (!command) + error('must specify command'); + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + options = extend({ + silent: false, + async: false + }, options); + + if (options.async) + execAsync(command, options, callback); + else + return execSync(command, options); +}; +exports.exec = wrap('exec', _exec, {notUnix:true}); + + + + +//@ +//@ ## Non-Unix commands +//@ + + + + + + +//@ +//@ #### tempdir() +//@ Searches and returns string containing a writeable, platform-dependent temporary directory. +//@ Follows Python's [tempfile algorithm](http://docs.python.org/library/tempfile.html#tempfile.tempdir). +exports.tempdir = wrap('tempdir', tempDir); + +//@ +//@ #### exists(path [, path ...]) +//@ #### exists(path_array) +//@ Returns true if all the given paths exist. +function _exists(options, paths) { + if (!paths) + error('no paths given'); + + if (typeof paths === 'string') + paths = [].slice.call(arguments, 1); + // if it's array leave it as it is + + var exists = true; + paths.forEach(function(p) { + if (!fs.existsSync(p)) + exists = false; + }); + + return exists; +}; +exports.exists = wrap('exists', _exists); + +//@ +//@ #### error() +//@ Tests if error occurred in the last command. Returns `null` if no error occurred, +//@ otherwise returns string explaining the error +exports.error = function() { + return state.error; +} + +//@ +//@ #### verbose() +//@ Enables all output (default) +exports.verbose = function() { + state.silent = false; +} + +//@ +//@ #### silent() +//@ Suppresses all output, except for explict `echo()` calls +exports.silent = function() { + state.silent = true; +} + + + + + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////////////////////// +// +// Auxiliary functions (internal use only) +// + +function log() { + if (!state.silent) + console.log.apply(this, arguments); +} + +function write(msg) { + if (!state.silent) + process.stdout.write(msg); +} + +// Shows error message. Throws unless '_continue = true'. +function error(msg, _continue) { + if (state.error === null) + state.error = ''; + state.error += state.currentCmd + ': ' + msg + '\n'; + + log(state.error); + + if (!_continue) + throw ''; +} + +// Returns {'alice': true, 'bob': false} when passed: +// parseOptions('-a', {'a':'alice', 'b':'bob'}); +function parseOptions(str, map) { + if (!map) + error('parseOptions() internal error: no map given'); + + // All options are false by default + var options = {}; + for (letter in map) + options[map[letter]] = false; + + if (!str) + return options; // defaults + + if (typeof str !== 'string') + error('parseOptions() internal error: wrong str'); + + // e.g. match[1] = 'Rf' for str = '-Rf' + var match = str.match(/^\-(.+)/); + if (!match) + return options; + + // e.g. chars = ['R', 'f'] + var chars = match[1].split(''); + + chars.forEach(function(char) { + if (char in map) + options[map[char]] = true; + else + error('option not recognized: '+char); + }); + + return options; +} + +// Common wrapper for all Unix-like commands +function wrap(cmd, fn, options) { + return function() { + var retValue = null; + + state.currentCmd = cmd; + state.error = null; + + try { + var args = [].slice.call(arguments, 0); + + if (options && options.notUnix) { + retValue = fn.apply(this, args); + } else { + if (args.length === 0 || typeof args[0] !== 'string' || args[0][0] !== '-') + args.unshift(''); // only add dummy option if '-option' not already present + retValue = fn.apply(this, args); + } + } catch (e) { + if (!state.error) { + // If state.error hasn't been set it's an error thrown by Node, not us - probably a bug... + console.log('maker.js: internal error'); + console.log(e.stack || e); + process.exit(1); + } + if (state.fatal) + throw e; + } + + state.currentCmd = 'maker.js'; + return retValue; + } +} // wrap + +// Buffered file copy, synchronous +// (Using readFileSync() + writeFileSync() could easily cause a memory overflow +// with large files) +function copyFileSync(srcFile, destFile) { + if (!fs.existsSync(srcFile)) + error('copyFileSync: no such file or directory: ' + srcFile); + + var BUF_LENGTH = 64*1024, + buf = new Buffer(BUF_LENGTH), + fdr = fs.openSync(srcFile, 'r'), + fdw = fs.openSync(destFile, 'w'), + bytesRead = BUF_LENGTH, + pos = 0; + + while (bytesRead === BUF_LENGTH) { + bytesRead = fs.readSync(fdr, buf, 0, BUF_LENGTH, pos); + fs.writeSync(fdw, buf, 0, bytesRead); + pos += bytesRead; + } + + fs.closeSync(fdr); + fs.closeSync(fdw); +} + +// Recursively copies 'sourceDir' into 'destDir' +// Adapted from https://github.com/ryanmcgrath/wrench-js +// +// Copyright (c) 2010 Ryan McGrath +// Copyright (c) 2012 Artur Adib +// +// Licensed under the MIT License +// http://www.opensource.org/licenses/mit-license.php +function cpdirSyncRecursive(sourceDir, destDir, opts) { + if (!opts) opts = {}; + + /* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */ + var checkDir = fs.statSync(sourceDir); + try { + fs.mkdirSync(destDir, checkDir.mode); + } catch (e) { + //if the directory already exists, that's okay + if (e.code !== 'EEXIST') throw e; + } + + var files = fs.readdirSync(sourceDir); + + for(var i = 0; i < files.length; i++) { + var currFile = fs.lstatSync(sourceDir + "/" + files[i]); + + if (currFile.isDirectory()) { + /* recursion this thing right on back. */ + cpdirSyncRecursive(sourceDir + "/" + files[i], destDir + "/" + files[i], opts); + } else if (currFile.isSymbolicLink()) { + var symlinkFull = fs.readlinkSync(sourceDir + "/" + files[i]); + fs.symlinkSync(symlinkFull, destDir + "/" + files[i]); + } else { + /* At this point, we've hit a file actually worth copying... so copy it on over. */ + if (fs.existsSync(destDir + "/" + files[i]) && !opts.force) { + log('skipping existing file: ' + files[i]); + } else { + copyFileSync(sourceDir + "/" + files[i], destDir + "/" + files[i]); + } + } + + } // for files +}; // cpdirSyncRecursive + +// Recursively removes 'dir' +// Adapted from https://github.com/ryanmcgrath/wrench-js +// +// Copyright (c) 2010 Ryan McGrath +// Copyright (c) 2012 Artur Adib +// +// Licensed under the MIT License +// http://www.opensource.org/licenses/mit-license.php +function rmdirSyncRecursive(dir) { + var files; + + files = fs.readdirSync(dir); + + // Loop through and delete everything in the sub-tree after checking it + for(var i = 0; i < files.length; i++) { + var currFile = fs.lstatSync(dir + "/" + files[i]); + + if(currFile.isDirectory()) // Recursive function back to the beginning + rmdirSyncRecursive(dir + "/" + files[i]); + + else if(currFile.isSymbolicLink()) // Unlink symlinks + fs.unlinkSync(dir + "/" + files[i]); + + else // Assume it's a file - perhaps a try/catch belongs here? + fs.unlinkSync(dir + "/" + files[i]); + } + + // Now that we know everything in the sub-tree has been deleted, we can delete the main directory. + // Huzzah for the shopkeep. + return fs.rmdirSync(dir); +}; // rmdirSyncRecursive + +// Recursively creates 'dir' +function mkdirSyncRecursive(dir) { + var baseDir = path.dirname(dir); + + // Base dir exists, no recursion necessary + if (fs.existsSync(baseDir)) { + fs.mkdirSync(dir, 0777); + return; + } + + // Base dir does not exist, go recursive + mkdirSyncRecursive(baseDir); + + // Base dir created, can create dir + fs.mkdirSync(dir, 0777); +}; + +// e.g. 'makerjs_a5f185d0443ca...' +function randomFileName() { + function randomHash(count) { + if (count === 1) + return parseInt(16*Math.random()).toString(16); + else { + var hash = ''; + for (var i=0; i<count; i++) + hash += randomHash(1); + return hash; + } + } + + return 'makerjs_'+randomHash(20); +} + +// Returns false if 'dir' is not a writeable directory, 'dir' otherwise +function writeableDir(dir) { + if (!dir || !fs.existsSync(dir)) + return false; + + if (!fs.statSync(dir).isDirectory()) + return false; + + var testFile = dir+'/'+randomFileName(); + try { + fs.writeFileSync(testFile, ' '); + fs.unlinkSync(testFile); + return dir; + } catch (e) { + return false; + } +} + +// Cross-platform method for getting an available temporary directory. +// Follows the algorithm of Python's tempfile.tempdir +// http://docs.python.org/library/tempfile.html#tempfile.tempdir +function tempDir() { + if (state.tempDir) + return state.tempDir; // from cache + + state.tempDir = writeableDir(process.env['TMPDIR']) || + writeableDir(process.env['TEMP']) || + writeableDir(process.env['TMP']) || + writeableDir(process.env['Wimp$ScrapDir']) || // RiscOS + writeableDir('C:\\TEMP') || // Windows + writeableDir('C:\\TMP') || // Windows + writeableDir('\\TEMP') || // Windows + writeableDir('\\TMP') || // Windows + writeableDir('/tmp') || + writeableDir('/var/tmp') || + writeableDir('/usr/tmp') || + writeableDir('.'); // last resort + + return state.tempDir; +} + +// Wrapper around exec() to enable echoing output to console in real time +function execAsync(cmd, opts, callback) { + var output = ''; + + var c = child.exec(cmd, {env: process.env}, function(err) { + if (callback) + callback(err ? err.code : 0, output); + }); + + c.stdout.on('data', function(data) { + output += data; + if (!opts.silent) + write(data); + }); + + c.stderr.on('data', function(data) { + output += data; + if (!opts.silent) + write(data); + }); +} + +// Hack to run child_process.exec() synchronously (sync avoids callback hell) +// Uses a custom wait loop that checks for a flag file, created when the child process is done. +// (Can't do a wait loop that checks for internal Node variables/messages as +// Node is single-threaded; callbacks and other internal state changes are done in the +// event loop). +function execSync(cmd, opts) { + var stdoutFile = path.resolve(tempDir()+'/'+randomFileName()), + codeFile = path.resolve(tempDir()+'/'+randomFileName()), + scriptFile = path.resolve(tempDir()+'/'+randomFileName()); + + var options = extend({ + silent: false + }, opts); + + var previousStdoutContent = ''; + // Echoes stdout changes from running process, if not silent + function updateStdout() { + if (state.silent || options.silent || !fs.existsSync(stdoutFile)) + return; + + var stdoutContent = fs.readFileSync(stdoutFile, 'utf8'); + // No changes since last time? + if (stdoutContent.length <= previousStdoutContent.length) + return; + + process.stdout.write(stdoutContent.substr(previousStdoutContent.length)); + previousStdoutContent = stdoutContent; + } + + function escape(str) { + str = str.replace(/\'/g, '"'); + str = str.replace(/\\/g, '\\\\'); + return str; + } + + cmd += ' > '+stdoutFile+' 2>&1'; // works on both win/unix + + var script = + "var child = require('child_process'), \ + fs = require('fs'); \ + child.exec('"+escape(cmd)+"', {env: process.env}, function(err) { \ + fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0'); \ + });"; + + if (fs.existsSync(scriptFile)) fs.unlinkSync(scriptFile); + if (fs.existsSync(stdoutFile)) fs.unlinkSync(stdoutFile); + if (fs.existsSync(codeFile)) fs.unlinkSync(codeFile); + + fs.writeFileSync(scriptFile, script); + child.exec('node '+scriptFile, { + env: process.env, + cwd: exports.pwd() + }); + + // The wait loop + while (!fs.existsSync(codeFile)) { updateStdout(); }; + while (!fs.existsSync(stdoutFile)) { updateStdout(); }; + + // At this point codeFile exists, but it's not necessarily flushed yet. + // Keep reading it until it is. + var code = parseInt(''); + while (isNaN(code)) + code = parseInt(fs.readFileSync(codeFile, 'utf8')); + + var stdout = fs.readFileSync(stdoutFile, 'utf8'); + + fs.unlinkSync(scriptFile); + fs.unlinkSync(stdoutFile); + fs.unlinkSync(codeFile); + + // True if successful, false if not + var obj = { + code: code, + output: stdout + }; + return obj; +} // execSync() + +// Expands wildcards with matching file names. For a given array of file names 'list', returns +// another array containing all file names as per ls(list[i]). +// For example: expand(['file*.js']) = ['file1.js', 'file2.js', ...] +// (if the files 'file1.js', 'file2.js', etc, exist in the current dir) +function expand(list) { + var expanded = []; + list.forEach(function(listEl) { + // Wildcard present? + if (listEl.search(/\*/) > -1) { + for (file in _ls('', listEl)) + expanded.push(file); + } else { + expanded.push(listEl); + } + }); + return expanded; +} + +// Cross-platform method for splitting environment PATH variables +function splitPath(p) { + if (!p) + return []; + + if (platform === 'win') + return p.split(';'); + else + return p.split(':'); +} + +// extend(target_obj, source_obj1 [, source_obj2 ...]) +// Shallow extend, e.g.: +// aux.extend({a:1}, {b:2}, {c:3}) +// returns {a:1, b:2, c:3} +function extend(target) { + var sources = [].slice.call(arguments, 1); + sources.forEach(function(source) { + for (key in source) + target[key] = source[key]; + }); + + return target; +} diff --git a/make.js b/make.js new file mode 100755 index 000000000..873e14d74 --- /dev/null +++ b/make.js @@ -0,0 +1,401 @@ +#!/usr/bin/env node +require('./external/shelljs/make'); + +var ROOT_DIR = __dirname+'/', // absolute path to project's root + BUILD_DIR = 'build/', + BUILD_TARGET = BUILD_DIR+'pdf.js', + FIREFOX_BUILD_DIR = BUILD_DIR+'/firefox/', + EXTENSION_SRC_DIR = 'extensions/', + GH_PAGES_DIR = BUILD_DIR+'gh-pages/', + REPO = 'git@github.com:mozilla/pdf.js.git', + PYTHON_BIN = 'python2.7'; + +// +// make all +// +target.all = function() { + // Don't do anything by default + echo('Please specify a target. Available targets:'); + for (t in target) + if (t !== 'all') echo(' ' + t); +} + + + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Production stuff +// + +// +// make web +// Generates the website for the project, by checking out the gh-pages branch underneath +// the build directory, and then moving the various viewer files into place. +// +target.web = function() { + target.production(); + target.extension(); + target.pagesrepo(); + + cd(ROOT_DIR); + echo(); + echo('### Creating web site'); + + cp(BUILD_TARGET, GH_PAGES_DIR+BUILD_TARGET); + cp('-R', 'web/*', GH_PAGES_DIR+'/web'); + cp(FIREFOX_BUILD_DIR+'/*.xpi', FIREFOX_BUILD_DIR+'/*.rdf', GH_PAGES_DIR+EXTENSION_SRC_DIR+'firefox/'); + cp(GH_PAGES_DIR+'/web/index.html.template', GH_PAGES_DIR+'/index.html'); + mv('-f', GH_PAGES_DIR+'/web/viewer-production.html', GH_PAGES_DIR+'/web/viewer.html'); + cd(GH_PAGES_DIR); + exec('git add -A'); + + echo(); + echo("Website built in "+GH_PAGES_DIR); + echo("Don't forget to cd into "+GH_PAGES_DIR+" and issue 'git commit' to push changes."); +} + +// +// make production +// Creates production output (pdf.js, and corresponding changes to web/ files) +// +target.production = function() { + target.bundle(); + target.viewer(); +} + +// +// make bundle +// Bundles all source files into one wrapper 'pdf.js' file, in the given order. +// + +target.bundle = function() { + cd(ROOT_DIR); + echo(); + echo('### Bundling files into '+BUILD_TARGET); + + // File order matters + var SRC_FILES = + ['core.js', + 'util.js', + 'canvas.js', + 'obj.js', + 'function.js', + 'charsets.js', + 'cidmaps.js', + 'colorspace.js', + 'crypto.js', + 'evaluator.js', + 'fonts.js', + 'glyphlist.js', + 'image.js', + 'metrics.js', + 'parser.js', + 'pattern.js', + 'stream.js', + 'worker.js', + '../external/jpgjs/jpg.js', + 'jpx.js', + 'bidi.js']; + + if (!exists(BUILD_DIR)) + mkdir(BUILD_DIR); + + cd('src'); + var bundle = cat(SRC_FILES), + bundleVersion = exec('git log --format="%h" -n 1', {silent:true}).output.replace('\n', ''); + + sed(/.*PDFJSSCRIPT_INCLUDE_ALL.*\n/, bundle, 'pdf.js').to(ROOT_DIR+BUILD_TARGET); + sed('-i', 'PDFJSSCRIPT_BUNDLE_VER', bundleVersion, ROOT_DIR+BUILD_TARGET); +} + +// +// make viewer +// Changes development <script> tags in our web viewer to use only 'pdf.js'. +// Produces 'viewer-production.html' +// +target.viewer = function() { + cd(ROOT_DIR); + echo(); + echo('### Generating production-level viewer'); + + cd('web'); + // Remove development lines + sed(/.*PDFJSSCRIPT_REMOVE_CORE.*\n/g, '', 'viewer.html').to('viewer-production.html'); + // Introduce snippet + sed('-i', /.*PDFJSSCRIPT_INCLUDE_BUILD.*\n/g, cat('viewer-snippet.html'), 'viewer-production.html'); +} + +// +// make pagesrepo +// +// This target clones the gh-pages repo into the build directory. It deletes the current contents +// of the repo, since we overwrite everything with data from the master repo. The 'make web' target +// then uses 'git add -A' to track additions, modifications, moves, and deletions. +target.pagesrepo = function() { + cd(ROOT_DIR); + echo(); + echo('### Creating fresh clone of gh-pages'); + + if (!exists(BUILD_DIR)) + mkdir(BUILD_DIR); + + if (!exists(GH_PAGES_DIR)) { + echo(); + echo('Cloning project repo...'); + echo('(This operation can take a while, depending on network conditions)'); + exec('git clone -b gh-pages --depth=1 '+REPO+' '+GH_PAGES_DIR, {silent:true}); + echo('Done.'); + } + + rm('-rf', GH_PAGES_DIR+'/*'); + mkdir('-p', GH_PAGES_DIR+'/web'); + mkdir('-p', GH_PAGES_DIR+'/web/images'); + mkdir('-p', GH_PAGES_DIR+BUILD_DIR); + mkdir('-p', GH_PAGES_DIR+EXTENSION_SRC_DIR+'/firefox'); +} + + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Extension stuff +// + +var EXTENSION_WEB_FILES = + ['web/images', + 'web/viewer.css', + 'web/viewer.js', + 'web/viewer.html', + 'web/viewer-production.html'], + EXTENSION_BASE_VERSION = '4bb289ec499013de66eb421737a4dbb4a9273eda', + EXTENSION_BUILD_NUMBER; + +// +// make extension +// +target.extension = function() { + cd(ROOT_DIR); + echo(); + echo('### Building extensions'); + + target.production(); + target.firefox(); + target.chrome(); +} + +target.buildnumber = function() { + cd(ROOT_DIR); + echo(); + echo('### Getting extension build number'); + + // Build number is the number of commits since base version + EXTENSION_BUILD_NUMBER = exec('git log --format=oneline '+EXTENSION_BASE_VERSION+'..', {silent:true}) + .output.match(/\n/g).length; // get # of lines in git output + + echo('Extension build number: ' + EXTENSION_BUILD_NUMBER); +} + +// +// make firefox +// +target.firefox = function() { + cd(ROOT_DIR); + echo(); + echo('### Building Firefox extension'); + + var FIREFOX_BUILD_CONTENT_DIR = FIREFOX_BUILD_DIR+'/content/', + FIREFOX_CONTENT_DIR = EXTENSION_SRC_DIR+'/firefox/content/', + FIREFOX_EXTENSION_FILES_TO_COPY = + ['*.js', + '*.rdf', + 'components']; + FIREFOX_EXTENSION_FILES = + ['content', + '*.js', + 'install.rdf', + 'components', + 'content']; + FIREFOX_EXTENSION_NAME = 'pdf.js.xpi', + FIREFOX_AMO_EXTENSION_NAME = 'pdf.js.amo.xpi'; + + target.production(); + target.buildnumber(); + cd(ROOT_DIR); + + // Clear out everything in the firefox extension build directory + rm('-rf', FIREFOX_BUILD_DIR); + mkdir('-p', FIREFOX_BUILD_CONTENT_DIR); + mkdir('-p', FIREFOX_BUILD_CONTENT_DIR+BUILD_DIR); + mkdir('-p', FIREFOX_BUILD_CONTENT_DIR+'/web'); + + // Copy extension files + cd('extensions/firefox'); + cp('-R', FIREFOX_EXTENSION_FILES_TO_COPY, ROOT_DIR+FIREFOX_BUILD_DIR); + cd(ROOT_DIR); + + // Copy a standalone version of pdf.js inside the content directory + cp(BUILD_TARGET, FIREFOX_BUILD_CONTENT_DIR+BUILD_DIR); + cp('-R', EXTENSION_WEB_FILES, FIREFOX_BUILD_CONTENT_DIR+'/web'); + rm(FIREFOX_BUILD_CONTENT_DIR+'/web/viewer-production.html'); + + // Copy over the firefox extension snippet so we can inline pdf.js in it + cp('web/viewer-snippet-firefox-extension.html', FIREFOX_BUILD_CONTENT_DIR+'/web'); + + // Modify the viewer so it does all the extension-only stuff. + cd(FIREFOX_BUILD_CONTENT_DIR+'/web'); + sed('-i', /.*PDFJSSCRIPT_INCLUDE_BUNDLE.*\n/, cat(ROOT_DIR+BUILD_TARGET), 'viewer-snippet-firefox-extension.html'); + sed('-i', /.*PDFJSSCRIPT_REMOVE_CORE.*\n/g, '', 'viewer.html'); + sed('-i', /.*PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION.*\n/g, '', 'viewer.html'); + sed('-i', /.*PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION.*\n/, cat('viewer-snippet-firefox-extension.html'), 'viewer.html'); + cd(ROOT_DIR); + + // We don't need pdf.js anymore since its inlined + rm('-Rf', FIREFOX_BUILD_CONTENT_DIR+BUILD_DIR); + + // Update the build version number + sed('-i', /PDFJSSCRIPT_BUILD/, EXTENSION_BUILD_NUMBER, FIREFOX_BUILD_DIR+'/install.rdf'); + sed('-i', /PDFJSSCRIPT_BUILD/, EXTENSION_BUILD_NUMBER, FIREFOX_BUILD_DIR+'/update.rdf'); + + // Create the xpi + cd(FIREFOX_BUILD_DIR); + exec('zip -r '+FIREFOX_EXTENSION_NAME+' '+FIREFOX_EXTENSION_FILES.join(' ')); + echo('extension created: ' + FIREFOX_EXTENSION_NAME); + cd(ROOT_DIR); + + // Build the amo extension too (remove the updateUrl) + cd(FIREFOX_BUILD_DIR); + sed('-i', /.*updateURL.*\n/, '', 'install.rdf'); + exec('zip -r '+FIREFOX_AMO_EXTENSION_NAME+' '+FIREFOX_EXTENSION_FILES.join(' ')); + echo('AMO extension created: ' + FIREFOX_AMO_EXTENSION_NAME); + cd(ROOT_DIR); +} + +// +// make chrome +// +target.chrome = function() { + cd(ROOT_DIR); + echo(); + echo('### Building Chrome extension'); + + var CHROME_BUILD_DIR = BUILD_DIR+'/chrome/', + CHROME_CONTENT_DIR = EXTENSION_SRC_DIR+'/chrome/content/', + CHROME_BUILD_CONTENT_DIR = CHROME_BUILD_DIR+'/content/', + CHROME_EXTENSION_FILES = + ['extensions/chrome/*.json', + 'extensions/chrome/*.html']; + + target.production(); + target.buildnumber(); + cd(ROOT_DIR); + + // Clear out everything in the chrome extension build directory + rm('-Rf', CHROME_BUILD_DIR); + mkdir('-p', CHROME_BUILD_CONTENT_DIR); + mkdir('-p', CHROME_BUILD_CONTENT_DIR+BUILD_DIR); + mkdir('-p', CHROME_BUILD_CONTENT_DIR+'/web'); + + // Copy extension files + cp('-R', CHROME_EXTENSION_FILES, CHROME_BUILD_DIR); + + // Copy a standalone version of pdf.js inside the content directory + cp(BUILD_TARGET, CHROME_BUILD_CONTENT_DIR+BUILD_DIR); + cp('-R', EXTENSION_WEB_FILES, CHROME_BUILD_CONTENT_DIR+'/web'); + mv('-f', CHROME_BUILD_CONTENT_DIR+'/web/viewer-production.html', CHROME_BUILD_CONTENT_DIR+'/web/viewer.html'); +} + + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Test stuff +// + +// +// make test +// +target.test = function() { + target.browsertest(); + target.unittest(); +} + +// +// make browsertest +// +target.browsertest = function() { + cd(ROOT_DIR); + echo(); + echo('### Running browser tests'); + + var PDF_TEST = env['PDF_TEST'] || 'test_manifest.json', + PDF_BROWSERS = env['PDF_BROWSERS'] || 'resources/browser_manifests/browser_manifest.json'; + + if (!exists('test/'+PDF_BROWSERS)) { + echo('Browser manifest file test/'+PDF_BROWSERS+' does not exist.'); + echo('Try copying one of the examples in test/resources/browser_manifests/'); + exit(1); + } + + cd('test'); + exec(PYTHON_BIN+' test.py --reftest --browserManifestFile='+PDF_BROWSERS+' --manifestFile='+PDF_TEST, {async:true}); +} + +// +// make unittest +// +target.unittest = function() { + cd(ROOT_DIR); + echo(); + echo('### Running unit tests'); + + cd('test/unit'); + exec('make', {async:true}); +} + + + + +/////////////////////////////////////////////////////////////////////////////////////////// +// +// Other +// + +// +// make server +// +target.server = function() { + cd(ROOT_DIR); + echo(); + echo('### Starting local server'); + + cd('test'); + exec(PYTHON_BIN+' -u test.py --port=8888', {async:true}); +} + +// +// make lint +// +target.lint = function() { + cd(ROOT_DIR); + echo(); + echo('### Linting JS files (this can take a while!)'); + + var LINT_FILES = 'src/*.js \ + web/*.js \ + test/*.js \ + test/unit/*.js \ + extensions/firefox/*.js \ + extensions/firefox/components/*.js \ + extensions/chrome/*.js'; + + exec('gjslint --nojsdoc '+LINT_FILES); +} + +// +// make clean +// +target.clean = function() { + cd(ROOT_DIR); + echo(); + echo('### Cleaning up project builds'); + + rm('-rf', BUILD_DIR); +} From 30888e94bd10423de3112d8569aeec6ac3ff3cf3 Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Sun, 4 Mar 2012 20:26:57 -0600 Subject: [PATCH 14/15] Exclude make.js from the linting; fixes few lint make.js errors --- Makefile | 4 +- make.js | 176 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 94 insertions(+), 86 deletions(-) diff --git a/Makefile b/Makefile index b11b015b6..d32c1a252 100644 --- a/Makefile +++ b/Makefile @@ -144,9 +144,9 @@ browser-test: # To install gjslint, see: # # <http://code.google.com/closure/utilities/docs/linter_howto.html> -SRC_DIRS := . src utils web test examples/helloworld extensions/firefox \ +SRC_DIRS := src utils web test examples/helloworld extensions/firefox \ extensions/firefox/components extensions/chrome test/unit -GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js)) +GJSLINT_FILES = $(foreach DIR, $(SRC_DIRS), $(wildcard $(DIR)/*.js)) lint: gjslint --nojsdoc $(GJSLINT_FILES) diff --git a/make.js b/make.js index 873e14d74..7c514a34a 100755 --- a/make.js +++ b/make.js @@ -1,25 +1,24 @@ #!/usr/bin/env node require('./external/shelljs/make'); -var ROOT_DIR = __dirname+'/', // absolute path to project's root +var ROOT_DIR = __dirname + '/', // absolute path to project's root BUILD_DIR = 'build/', - BUILD_TARGET = BUILD_DIR+'pdf.js', - FIREFOX_BUILD_DIR = BUILD_DIR+'/firefox/', + BUILD_TARGET = BUILD_DIR + 'pdf.js', + FIREFOX_BUILD_DIR = BUILD_DIR + '/firefox/', EXTENSION_SRC_DIR = 'extensions/', - GH_PAGES_DIR = BUILD_DIR+'gh-pages/', + GH_PAGES_DIR = BUILD_DIR + 'gh-pages/', REPO = 'git@github.com:mozilla/pdf.js.git', PYTHON_BIN = 'python2.7'; // // make all // -target.all = function() { +target.all = function() { // Don't do anything by default echo('Please specify a target. Available targets:'); for (t in target) if (t !== 'all') echo(' ' + t); -} - +}; /////////////////////////////////////////////////////////////////////////////////////////// @@ -29,30 +28,33 @@ target.all = function() { // // make web -// Generates the website for the project, by checking out the gh-pages branch underneath +// Generates the website for the project, by checking out the gh-pages branch underneath // the build directory, and then moving the various viewer files into place. // target.web = function() { target.production(); target.extension(); target.pagesrepo(); - + cd(ROOT_DIR); echo(); echo('### Creating web site'); - cp(BUILD_TARGET, GH_PAGES_DIR+BUILD_TARGET); - cp('-R', 'web/*', GH_PAGES_DIR+'/web'); - cp(FIREFOX_BUILD_DIR+'/*.xpi', FIREFOX_BUILD_DIR+'/*.rdf', GH_PAGES_DIR+EXTENSION_SRC_DIR+'firefox/'); - cp(GH_PAGES_DIR+'/web/index.html.template', GH_PAGES_DIR+'/index.html'); - mv('-f', GH_PAGES_DIR+'/web/viewer-production.html', GH_PAGES_DIR+'/web/viewer.html'); + cp(BUILD_TARGET, GH_PAGES_DIR + BUILD_TARGET); + cp('-R', 'web/*', GH_PAGES_DIR + '/web'); + cp(FIREFOX_BUILD_DIR + '/*.xpi', FIREFOX_BUILD_DIR + '/*.rdf', + GH_PAGES_DIR + EXTENSION_SRC_DIR + 'firefox/'); + cp(GH_PAGES_DIR + '/web/index.html.template', GH_PAGES_DIR + '/index.html'); + mv('-f', GH_PAGES_DIR + '/web/viewer-production.html', + GH_PAGES_DIR + '/web/viewer.html'); cd(GH_PAGES_DIR); exec('git add -A'); - + echo(); - echo("Website built in "+GH_PAGES_DIR); - echo("Don't forget to cd into "+GH_PAGES_DIR+" and issue 'git commit' to push changes."); -} + echo("Website built in " + GH_PAGES_DIR); + echo("Don't forget to cd into " + GH_PAGES_DIR + + " and issue 'git commit' to push changes."); +}; // // make production @@ -61,7 +63,7 @@ target.web = function() { target.production = function() { target.bundle(); target.viewer(); -} +}; // // make bundle @@ -71,10 +73,10 @@ target.production = function() { target.bundle = function() { cd(ROOT_DIR); echo(); - echo('### Bundling files into '+BUILD_TARGET); + echo('### Bundling files into ' + BUILD_TARGET); // File order matters - var SRC_FILES = + var SRC_FILES = ['core.js', 'util.js', 'canvas.js', @@ -102,11 +104,13 @@ target.bundle = function() { cd('src'); var bundle = cat(SRC_FILES), - bundleVersion = exec('git log --format="%h" -n 1', {silent:true}).output.replace('\n', ''); + bundleVersion = exec('git log --format="%h" -n 1', + {silent: true}).output.replace('\n', ''); - sed(/.*PDFJSSCRIPT_INCLUDE_ALL.*\n/, bundle, 'pdf.js').to(ROOT_DIR+BUILD_TARGET); - sed('-i', 'PDFJSSCRIPT_BUNDLE_VER', bundleVersion, ROOT_DIR+BUILD_TARGET); -} + sed(/.*PDFJSSCRIPT_INCLUDE_ALL.*\n/, bundle, 'pdf.js') + .to(ROOT_DIR + BUILD_TARGET); + sed('-i', 'PDFJSSCRIPT_BUNDLE_VER', bundleVersion, ROOT_DIR + BUILD_TARGET); +}; // // make viewer @@ -120,15 +124,17 @@ target.viewer = function() { cd('web'); // Remove development lines - sed(/.*PDFJSSCRIPT_REMOVE_CORE.*\n/g, '', 'viewer.html').to('viewer-production.html'); + sed(/.*PDFJSSCRIPT_REMOVE_CORE.*\n/g, '', 'viewer.html') + .to('viewer-production.html'); // Introduce snippet - sed('-i', /.*PDFJSSCRIPT_INCLUDE_BUILD.*\n/g, cat('viewer-snippet.html'), 'viewer-production.html'); -} + sed('-i', /.*PDFJSSCRIPT_INCLUDE_BUILD.*\n/g, cat('viewer-snippet.html'), + 'viewer-production.html'); +}; // // make pagesrepo // -// This target clones the gh-pages repo into the build directory. It deletes the current contents +// This target clones the gh-pages repo into the build directory. It deletes the current contents // of the repo, since we overwrite everything with data from the master repo. The 'make web' target // then uses 'git add -A' to track additions, modifications, moves, and deletions. target.pagesrepo = function() { @@ -143,16 +149,17 @@ target.pagesrepo = function() { echo(); echo('Cloning project repo...'); echo('(This operation can take a while, depending on network conditions)'); - exec('git clone -b gh-pages --depth=1 '+REPO+' '+GH_PAGES_DIR, {silent:true}); + exec('git clone -b gh-pages --depth=1 ' + REPO + ' ' + ßGH_PAGES_DIR, + {silent: true}); echo('Done.'); } - rm('-rf', GH_PAGES_DIR+'/*'); - mkdir('-p', GH_PAGES_DIR+'/web'); - mkdir('-p', GH_PAGES_DIR+'/web/images'); - mkdir('-p', GH_PAGES_DIR+BUILD_DIR); - mkdir('-p', GH_PAGES_DIR+EXTENSION_SRC_DIR+'/firefox'); -} + rm('-rf', GH_PAGES_DIR + '/*'); + mkdir('-p', GH_PAGES_DIR + '/web'); + mkdir('-p', GH_PAGES_DIR + '/web/images'); + mkdir('-p', GH_PAGES_DIR + BUILD_DIR); + mkdir('-p', GH_PAGES_DIR + EXTENSION_SRC_DIR + '/firefox'); +}; /////////////////////////////////////////////////////////////////////////////////////////// @@ -180,7 +187,7 @@ target.extension = function() { target.production(); target.firefox(); target.chrome(); -} +}; target.buildnumber = function() { cd(ROOT_DIR); @@ -188,11 +195,12 @@ target.buildnumber = function() { echo('### Getting extension build number'); // Build number is the number of commits since base version - EXTENSION_BUILD_NUMBER = exec('git log --format=oneline '+EXTENSION_BASE_VERSION+'..', {silent:true}) + EXTENSION_BUILD_NUMBER = exec('git log --format=oneline ' + + EXTENSION_BASE_VERSION + '..', {silent: true}) .output.match(/\n/g).length; // get # of lines in git output - - echo('Extension build number: ' + EXTENSION_BUILD_NUMBER); -} + + echo('Extension build number: ' + EXTENSION_BUILD_NUMBER); +}; // // make firefox @@ -202,8 +210,8 @@ target.firefox = function() { echo(); echo('### Building Firefox extension'); - var FIREFOX_BUILD_CONTENT_DIR = FIREFOX_BUILD_DIR+'/content/', - FIREFOX_CONTENT_DIR = EXTENSION_SRC_DIR+'/firefox/content/', + var FIREFOX_BUILD_CONTENT_DIR = FIREFOX_BUILD_DIR + '/content/', + FIREFOX_CONTENT_DIR = EXTENSION_SRC_DIR + '/firefox/content/', FIREFOX_EXTENSION_FILES_TO_COPY = ['*.js', '*.rdf', @@ -224,50 +232,50 @@ target.firefox = function() { // Clear out everything in the firefox extension build directory rm('-rf', FIREFOX_BUILD_DIR); mkdir('-p', FIREFOX_BUILD_CONTENT_DIR); - mkdir('-p', FIREFOX_BUILD_CONTENT_DIR+BUILD_DIR); - mkdir('-p', FIREFOX_BUILD_CONTENT_DIR+'/web'); - - // Copy extension files + mkdir('-p', FIREFOX_BUILD_CONTENT_DIR + BUILD_DIR); + mkdir('-p', FIREFOX_BUILD_CONTENT_DIR + '/web'); + + // Copy extension files cd('extensions/firefox'); - cp('-R', FIREFOX_EXTENSION_FILES_TO_COPY, ROOT_DIR+FIREFOX_BUILD_DIR); + cp('-R', FIREFOX_EXTENSION_FILES_TO_COPY, ROOT_DIR + FIREFOX_BUILD_DIR); cd(ROOT_DIR); // Copy a standalone version of pdf.js inside the content directory - cp(BUILD_TARGET, FIREFOX_BUILD_CONTENT_DIR+BUILD_DIR); - cp('-R', EXTENSION_WEB_FILES, FIREFOX_BUILD_CONTENT_DIR+'/web'); - rm(FIREFOX_BUILD_CONTENT_DIR+'/web/viewer-production.html'); + cp(BUILD_TARGET, FIREFOX_BUILD_CONTENT_DIR + BUILD_DIR); + cp('-R', EXTENSION_WEB_FILES, FIREFOX_BUILD_CONTENT_DIR + '/web'); + rm(FIREFOX_BUILD_CONTENT_DIR + '/web/viewer-production.html'); // Copy over the firefox extension snippet so we can inline pdf.js in it - cp('web/viewer-snippet-firefox-extension.html', FIREFOX_BUILD_CONTENT_DIR+'/web'); + cp('web/viewer-snippet-firefox-extension.html', FIREFOX_BUILD_CONTENT_DIR + '/web'); // Modify the viewer so it does all the extension-only stuff. - cd(FIREFOX_BUILD_CONTENT_DIR+'/web'); - sed('-i', /.*PDFJSSCRIPT_INCLUDE_BUNDLE.*\n/, cat(ROOT_DIR+BUILD_TARGET), 'viewer-snippet-firefox-extension.html'); + cd(FIREFOX_BUILD_CONTENT_DIR + '/web'); + sed('-i', /.*PDFJSSCRIPT_INCLUDE_BUNDLE.*\n/, cat(ROOT_DIR + BUILD_TARGET), 'viewer-snippet-firefox-extension.html'); sed('-i', /.*PDFJSSCRIPT_REMOVE_CORE.*\n/g, '', 'viewer.html'); sed('-i', /.*PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION.*\n/g, '', 'viewer.html'); sed('-i', /.*PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION.*\n/, cat('viewer-snippet-firefox-extension.html'), 'viewer.html'); cd(ROOT_DIR); // We don't need pdf.js anymore since its inlined - rm('-Rf', FIREFOX_BUILD_CONTENT_DIR+BUILD_DIR); + rm('-Rf', FIREFOX_BUILD_CONTENT_DIR + BUILD_DIR); // Update the build version number - sed('-i', /PDFJSSCRIPT_BUILD/, EXTENSION_BUILD_NUMBER, FIREFOX_BUILD_DIR+'/install.rdf'); - sed('-i', /PDFJSSCRIPT_BUILD/, EXTENSION_BUILD_NUMBER, FIREFOX_BUILD_DIR+'/update.rdf'); + sed('-i', /PDFJSSCRIPT_BUILD/, EXTENSION_BUILD_NUMBER, FIREFOX_BUILD_DIR + '/install.rdf'); + sed('-i', /PDFJSSCRIPT_BUILD/, EXTENSION_BUILD_NUMBER, FIREFOX_BUILD_DIR + '/update.rdf'); // Create the xpi cd(FIREFOX_BUILD_DIR); - exec('zip -r '+FIREFOX_EXTENSION_NAME+' '+FIREFOX_EXTENSION_FILES.join(' ')); + exec('zip -r ' + FIREFOX_EXTENSION_NAME + ' ' + FIREFOX_EXTENSION_FILES.join(' ')); echo('extension created: ' + FIREFOX_EXTENSION_NAME); cd(ROOT_DIR); // Build the amo extension too (remove the updateUrl) cd(FIREFOX_BUILD_DIR); sed('-i', /.*updateURL.*\n/, '', 'install.rdf'); - exec('zip -r '+FIREFOX_AMO_EXTENSION_NAME+' '+FIREFOX_EXTENSION_FILES.join(' ')); + exec('zip -r ' + FIREFOX_AMO_EXTENSION_NAME + ' ' + FIREFOX_EXTENSION_FILES.join(' ')); echo('AMO extension created: ' + FIREFOX_AMO_EXTENSION_NAME); cd(ROOT_DIR); -} +}; // // make chrome @@ -277,9 +285,9 @@ target.chrome = function() { echo(); echo('### Building Chrome extension'); - var CHROME_BUILD_DIR = BUILD_DIR+'/chrome/', - CHROME_CONTENT_DIR = EXTENSION_SRC_DIR+'/chrome/content/', - CHROME_BUILD_CONTENT_DIR = CHROME_BUILD_DIR+'/content/', + var CHROME_BUILD_DIR = BUILD_DIR + '/chrome/', + CHROME_CONTENT_DIR = EXTENSION_SRC_DIR + '/chrome/content/', + CHROME_BUILD_CONTENT_DIR = CHROME_BUILD_DIR + '/content/', CHROME_EXTENSION_FILES = ['extensions/chrome/*.json', 'extensions/chrome/*.html']; @@ -291,17 +299,18 @@ target.chrome = function() { // Clear out everything in the chrome extension build directory rm('-Rf', CHROME_BUILD_DIR); mkdir('-p', CHROME_BUILD_CONTENT_DIR); - mkdir('-p', CHROME_BUILD_CONTENT_DIR+BUILD_DIR); - mkdir('-p', CHROME_BUILD_CONTENT_DIR+'/web'); + mkdir('-p', CHROME_BUILD_CONTENT_DIR + BUILD_DIR); + mkdir('-p', CHROME_BUILD_CONTENT_DIR + '/web'); - // Copy extension files + // Copy extension files cp('-R', CHROME_EXTENSION_FILES, CHROME_BUILD_DIR); // Copy a standalone version of pdf.js inside the content directory - cp(BUILD_TARGET, CHROME_BUILD_CONTENT_DIR+BUILD_DIR); - cp('-R', EXTENSION_WEB_FILES, CHROME_BUILD_CONTENT_DIR+'/web'); - mv('-f', CHROME_BUILD_CONTENT_DIR+'/web/viewer-production.html', CHROME_BUILD_CONTENT_DIR+'/web/viewer.html'); -} + cp(BUILD_TARGET, CHROME_BUILD_CONTENT_DIR + BUILD_DIR); + cp('-R', EXTENSION_WEB_FILES, CHROME_BUILD_CONTENT_DIR + '/web'); + mv('-f', CHROME_BUILD_CONTENT_DIR + '/web/viewer-production.html', + CHROME_BUILD_CONTENT_DIR + '/web/viewer.html'); +}; /////////////////////////////////////////////////////////////////////////////////////////// @@ -315,7 +324,7 @@ target.chrome = function() { target.test = function() { target.browsertest(); target.unittest(); -} +}; // // make browsertest @@ -328,15 +337,16 @@ target.browsertest = function() { var PDF_TEST = env['PDF_TEST'] || 'test_manifest.json', PDF_BROWSERS = env['PDF_BROWSERS'] || 'resources/browser_manifests/browser_manifest.json'; - if (!exists('test/'+PDF_BROWSERS)) { - echo('Browser manifest file test/'+PDF_BROWSERS+' does not exist.'); + if (!exists('test/' + PDF_BROWSERS)) { + echo('Browser manifest file test/' + PDF_BROWSERS + ' does not exist.'); echo('Try copying one of the examples in test/resources/browser_manifests/'); exit(1); } cd('test'); - exec(PYTHON_BIN+' test.py --reftest --browserManifestFile='+PDF_BROWSERS+' --manifestFile='+PDF_TEST, {async:true}); -} + exec(PYTHON_BIN + ' test.py --reftest --browserManifestFile=' + PDF_BROWSERS + + ' --manifestFile=' + PDF_TEST, {async: true}); +}; // // make unittest @@ -347,10 +357,8 @@ target.unittest = function() { echo('### Running unit tests'); cd('test/unit'); - exec('make', {async:true}); -} - - + exec('make', {async: true}); +}; /////////////////////////////////////////////////////////////////////////////////////////// @@ -367,8 +375,8 @@ target.server = function() { echo('### Starting local server'); cd('test'); - exec(PYTHON_BIN+' -u test.py --port=8888', {async:true}); -} + exec(PYTHON_BIN + ' -u test.py --port=8888', {async: true}); +}; // // make lint @@ -386,8 +394,8 @@ target.lint = function() { extensions/firefox/components/*.js \ extensions/chrome/*.js'; - exec('gjslint --nojsdoc '+LINT_FILES); -} + exec('gjslint --nojsdoc ' + LINT_FILES); +}; // // make clean @@ -398,4 +406,4 @@ target.clean = function() { echo('### Cleaning up project builds'); rm('-rf', BUILD_DIR); -} +}; From 02711c761062c544aebadd235cd1d3b937373d0b Mon Sep 17 00:00:00 2001 From: notmasteryet <async.processingjs@yahoo.com> Date: Wed, 7 Mar 2012 19:12:05 -0600 Subject: [PATCH 15/15] Fixes unnecessary style attributes in svg --- web/images/zoom-in.svg | 4 ++-- web/images/zoom-out.svg | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/images/zoom-in.svg b/web/images/zoom-in.svg index db28218a8..48ee42dd9 100644 --- a/web/images/zoom-in.svg +++ b/web/images/zoom-in.svg @@ -419,12 +419,12 @@ d="M 33.278212 34.94062 A 10.31934 2.320194 0 1 1 12.639532,34.94062 A 10.31934 2.320194 0 1 1 33.278212 34.94062 z" transform="matrix(1.550487,0,0,1.978714,-12.4813,-32.49103)" /> <path - style="font-size:59.901077px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125.00000%;writing-mode:lr-tb;text-anchor:start;fill:#75a1d0;fill-opacity:1.0000000;stroke:#3465a4;stroke-width:1.0000004px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Bitstream Vera Sans" + style="fill:#75a1d0;fill-opacity:1.0000000;stroke:#3465a4;stroke-width:1.0000004px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" d="M 27.514356,37.542682 L 27.514356,28.515722 L 37.492820,28.475543 L 37.492820,21.480219 L 27.523285,21.480219 L 27.514356,11.520049 L 20.498082,11.531210 L 20.502546,21.462362 L 10.512920,21.536022 L 10.477206,28.504561 L 20.511475,28.475543 L 20.518171,37.515896 L 27.514356,37.542682 z " id="text1314" sodipodi:nodetypes="ccccccccccccc" /> <path - style="font-size:59.901077px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125.00000%;writing-mode:lr-tb;text-anchor:start;opacity:0.40860215;fill:url(#linearGradient4975);fill-opacity:1.0000000;stroke:url(#linearGradient7922);stroke-width:1.0000006px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Bitstream Vera Sans" + style="opacity:0.40860215;fill:url(#linearGradient4975);fill-opacity:1.0000000;stroke:url(#linearGradient7922);stroke-width:1.0000006px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" d="M 26.498702,36.533920 L 26.498702,27.499738 L 36.501304,27.499738 L 36.494607,22.475309 L 26.507630,22.475309 L 26.507630,12.480335 L 21.512796,12.498193 L 21.521725,22.475309 L 11.495536,22.493166 L 11.468750,27.466256 L 21.533143,27.475185 L 21.519750,36.502670 L 26.498702,36.533920 z " id="path7076" sodipodi:nodetypes="ccccccccccccc" /> diff --git a/web/images/zoom-out.svg b/web/images/zoom-out.svg index c58d9c4c6..eb13b60e3 100644 --- a/web/images/zoom-out.svg +++ b/web/images/zoom-out.svg @@ -407,12 +407,12 @@ inkscape:label="Layer 1" inkscape:groupmode="layer"> <path - style="font-size:59.901077px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125.00000%;writing-mode:lr-tb;text-anchor:start;fill:#75a1d0;fill-opacity:1.0000000;stroke:#3465a4;stroke-width:1.0000004px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Bitstream Vera Sans" + style="fill:#75a1d0;fill-opacity:1.0000000;stroke:#3465a4;stroke-width:1.0000004px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" d="M 27.514356,28.359472 L 39.633445,28.475543 L 39.633445,21.480219 L 27.523285,21.480219 L 20.502546,21.462362 L 8.5441705,21.489147 L 8.5084565,28.457686 L 20.511475,28.475543 L 27.514356,28.359472 z " id="text1314" sodipodi:nodetypes="ccccccccc" /> <path - style="font-size:59.901077px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125.00000%;writing-mode:lr-tb;text-anchor:start;opacity:0.40860215;fill:url(#linearGradient4975);fill-opacity:1.0000000;stroke:url(#linearGradient7922);stroke-width:1.0000006px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Bitstream Vera Sans" + style="opacity:0.40860215;fill:url(#linearGradient4975);fill-opacity:1.0000000;stroke:url(#linearGradient7922);stroke-width:1.0000006px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" d="M 38.579429,27.484113 L 38.588357,22.475309 L 9.5267863,22.493166 L 9.5000003,27.466256 L 38.579429,27.484113 z " id="path7076" sodipodi:nodetypes="ccccc" />