diff --git a/make.js b/make.js old mode 100755 new mode 100644 index e33a597e0..27afbef25 --- a/make.js +++ b/make.js @@ -104,12 +104,19 @@ target.generic = function() { target.web = function() { target.generic(); target.extension(); - target.pagesrepo(); - cd(ROOT_DIR); echo(); echo('### Creating web site'); + if (test('-d', GH_PAGES_DIR)) + 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'); + mkdir('-p', GH_PAGES_DIR + EXTENSION_SRC_DIR + '/chrome'); + cp('-R', GENERIC_DIR + '/*', GH_PAGES_DIR); cp(FIREFOX_BUILD_DIR + '/*.xpi', FIREFOX_BUILD_DIR + '/*.rdf', GH_PAGES_DIR + EXTENSION_SRC_DIR + 'firefox/'); @@ -118,12 +125,14 @@ target.web = function() { cp('web/index.html.template', GH_PAGES_DIR + '/index.html'); cd(GH_PAGES_DIR); + exec('git init'); + exec('git remote add origin ' + REPO); exec('git add -A'); + exec('git commit -am "gh-pages site created via make.js script"'); + exec('git branch -m gh-pages'); 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.'); }; // @@ -245,38 +254,6 @@ target.bundle = function() { }; -// -// 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 (!test('-d', BUILD_DIR)) - mkdir(BUILD_DIR); - - if (!test('-d', 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'); - mkdir('-p', GH_PAGES_DIR + EXTENSION_SRC_DIR + '/chrome'); -}; - /////////////////////////////////////////////////////////////////////////////////////////// // diff --git a/src/api.js b/src/api.js index d6d13a095..169faea0d 100644 --- a/src/api.js +++ b/src/api.js @@ -575,24 +575,15 @@ var WorkerTransport = (function WorkerTransportClosure() { this.objs.resolve(id, imageData); break; case 'Font': - var name = data[2]; - var file = data[3]; - var properties = data[4]; - - if (file) { - // Rewrap the ArrayBuffer in a stream. - var fontFileDict = new Dict(); - file = new Stream(file, 0, file.length, fontFileDict); - } + var exportedData = data[2]; // At this point, only the font object is created but the font is // not yet attached to the DOM. This is done in `FontLoader.bind`. var font; - try { - font = new Font(name, file, properties); - } catch (e) { - font = new ErrorFont(e); - } + if ('error' in exportedData) + font = new ErrorFont(exportedData.error); + else + font = new Font(exportedData); this.objs.resolve(id, font); break; default: diff --git a/src/evaluator.js b/src/evaluator.js index 621dd613f..704091149 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -171,31 +171,30 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { ++self.objIdCounter; if (!font.loadedName) { - font.translated = self.translateFont(font, xref, resources, - dependency); - if (font.translated) { + var translated = self.translateFont(font, xref, resources, + dependency); + if (translated) { // keep track of each font we translated so the caller can // load them asynchronously before calling display on a page loadedName = 'font_' + uniquePrefix + self.objIdCounter; - font.translated.properties.loadedName = loadedName; + translated.properties.loadedName = loadedName; font.loadedName = loadedName; + font.translated = translated; - var translated = font.translated; - // Convert the file to an ArrayBuffer which will be turned back into - // a Stream in the main thread. - if (translated.file) - translated.file = translated.file.getBytes(); - if (translated.properties.file) { - translated.properties.file = - translated.properties.file.getBytes(); + var data; + try { + var fontObj = new Font(translated.name, + translated.file, + translated.properties); + data = fontObj.export(); + } catch (e) { + data = { error: e }; } handler.send('obj', [ loadedName, 'Font', - translated.name, - translated.file, - translated.properties + data ]); } } diff --git a/src/fonts.js b/src/fonts.js index 15af2fa39..820ada8a0 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -1526,6 +1526,15 @@ function fontCharsToUnicode(charCodes, fontProperties) { */ var Font = (function FontClosure() { function Font(name, file, properties) { + if (arguments.length === 1) { + // importing translated data + var data = arguments[0]; + for (var i in data) { + this[i] = data[i]; + } + return; + } + this.name = name; this.coded = properties.coded; this.charProcOperatorList = properties.charProcOperatorList; @@ -2036,6 +2045,15 @@ var Font = (function FontClosure() { mimetype: null, encoding: null, + export: function Font_export() { + var data = {}; + for (var i in this) { + if (this.hasOwnProperty(i)) + data[i] = this[i]; + } + return data; + }, + checkAndRepair: function Font_checkAndRepair(name, font, properties) { function readTableEntry(file) { var tag = file.getBytes(4); diff --git a/src/pattern.js b/src/pattern.js index 2f53c5c80..45c4a3279 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -61,6 +61,12 @@ var Pattern = (function PatternClosure() { var Shadings = {}; +// A small number to offset the first/last color stops so we can insert ones to +// support extend. Number.MIN_VALUE appears to be too small and breaks the +// extend. 1e-7 works in FF but chrome seems to use an even smaller sized number +// internally so we have to go bigger. +Shadings.SMALL_NUMBER = 1e-2; + // Radial and axial shading have very similar implementations // If needed, the implementations can be broken into two classes Shadings.RadialAxial = (function RadialAxialClosure() { @@ -69,7 +75,6 @@ Shadings.RadialAxial = (function RadialAxialClosure() { this.coordsArr = dict.get('Coords'); this.shadingType = dict.get('ShadingType'); this.type = 'Pattern'; - this.ctx = ctx; var cs = dict.get('ColorSpace', 'CS'); cs = ColorSpace.parse(cs, xref, res); @@ -87,7 +92,23 @@ Shadings.RadialAxial = (function RadialAxialClosure() { var extendArr = dict.get('Extend'); extendStart = extendArr[0]; extendEnd = extendArr[1]; - TODO('Support extend'); + } + + if (this.shadingType === PatternType.RADIAL && + (!extendStart || !extendEnd)) { + // Radial gradient only currently works if either circle is fully within + // the other circle. + var x1 = this.coordsArr[0]; + var y1 = this.coordsArr[1]; + var r1 = this.coordsArr[2]; + var x2 = this.coordsArr[3]; + var y2 = this.coordsArr[4]; + var r2 = this.coordsArr[5]; + var distance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + if (r1 <= r2 + distance && + r2 <= r1 + distance) { + warn('Unsupported radial gradient.'); + } } this.extendStart = extendStart; @@ -103,16 +124,43 @@ Shadings.RadialAxial = (function RadialAxialClosure() { // 10 samples seems good enough for now, but probably won't work // if there are sharp color changes. Ideally, we would implement // the spec faithfully and add lossless optimizations. - var step = (t1 - t0) / 10; var diff = t1 - t0; + var step = diff / 10; + + var colorStops = this.colorStops = []; + + // Protect against bad domains so we don't end up in an infinte loop below. + if (t0 >= t1 || step <= 0) { + // Acrobat doesn't seem to handle these cases so we'll ignore for + // now. + info('Bad shading domain.'); + return; + } - var colorStops = []; for (var i = t0; i <= t1; i += step) { var rgbColor = cs.getRgb(fn([i])); var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]); colorStops.push([(i - t0) / diff, cssColor]); } + var background = 'transparent'; + if (dict.has('Background')) { + var rgbColor = cs.getRgb(dict.get('Background')); + background = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]); + } + + if (!extendStart) { + // Insert a color stop at the front and offset the first real color stop + // so it doesn't conflict with the one we insert. + colorStops.unshift([0, background]); + colorStops[1][0] += Shadings.SMALL_NUMBER; + } + if (!extendEnd) { + // Same idea as above in extendStart but for the end. + colorStops[colorStops.length - 1][0] -= Shadings.SMALL_NUMBER; + colorStops.push([1, background]); + } + this.colorStops = colorStops; } diff --git a/test/driver.js b/test/driver.js index 54179f6cb..d1a8a17a7 100644 --- a/test/driver.js +++ b/test/driver.js @@ -26,7 +26,7 @@ // "firefox-bin: Fatal IO error 12 (Cannot allocate memory) on X server :1." // PDFJS.disableWorker = true; -var appPath, browser, canvas, currentTaskIdx, manifest, stdout; +var appPath, browser, canvas, dummyCanvas, currentTaskIdx, manifest, stdout; var inFlightRequests = 0; function queryParams() { @@ -148,6 +148,46 @@ function canvasToDataURL() { return canvas.toDataURL('image/png'); } +function NullTextLayerBuilder() { +} +NullTextLayerBuilder.prototype = { + beginLayout: function NullTextLayerBuilder_BeginLayout() {}, + endLayout: function NullTextLayerBuilder_EndLayout() {}, + appendText: function NullTextLayerBuilder_AppendText() {} +}; + +function SimpleTextLayerBuilder(ctx, viewport) { + this.ctx = ctx; + this.viewport = viewport; +} +SimpleTextLayerBuilder.prototype = { + beginLayout: function SimpleTextLayerBuilder_BeginLayout() { + this.ctx.save(); + }, + endLayout: function SimpleTextLayerBuilder_EndLayout() { + this.ctx.restore(); + }, + appendText: function SimpleTextLayerBuilder_AppendText(text, fontName, + fontSize) { + var ctx = this.ctx, viewport = this.viewport; + // vScale and hScale already contain the scaling to pixel units + var fontHeight = fontSize * text.geom.vScale; + ctx.beginPath(); + ctx.strokeStyle = 'red'; + ctx.fillStyle = 'yellow'; + ctx.rect(text.geom.x, text.geom.y - fontHeight, + text.canvasWidth * text.geom.hScale, fontHeight); + ctx.stroke(); + ctx.fill(); + + var textContent = bidi(text, -1); + ctx.font = fontHeight + 'px sans-serif'; + ctx.fillStyle = 'black'; + ctx.fillText(textContent, text.geom.x, text.geom.y); + } +}; + + function nextPage(task, loadError) { var failure = loadError || ''; @@ -196,16 +236,21 @@ function nextPage(task, loadError) { canvas.height = viewport.height; clear(ctx); - // using the text layer builder that does nothing to test - // text layer creation operations - var textLayerBuilder = { - beginLayout: function nullTextLayerBuilderBeginLayout() {}, - endLayout: function nullTextLayerBuilderEndLayout() {}, - appendText: function nullTextLayerBuilderAppendText(text, fontName, - fontSize) {} - }; + var drawContext, textLayerBuilder; + if (task.type == 'text') { + // using dummy canvas for pdf context drawing operations + if (!dummyCanvas) { + dummyCanvas = document.createElement('canvas'); + } + drawContext = dummyCanvas.getContext('2d'); + // ... text builder will draw its content on the test canvas + textLayerBuilder = new SimpleTextLayerBuilder(ctx, viewport); + } else { + drawContext = ctx; + textLayerBuilder = new NullTextLayerBuilder(); + } var renderContext = { - canvasContext: ctx, + canvasContext: drawContext, textLayer: textLayerBuilder, viewport: viewport }; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 7f2911983..60793fa31 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -34,3 +34,4 @@ !gradientfill.pdf !basicapi.pdf !mixedfonts.pdf +!shading_extend.pdf diff --git a/test/pdfs/shading_extend.pdf b/test/pdfs/shading_extend.pdf new file mode 100644 index 000000000..0c8c151da Binary files /dev/null and b/test/pdfs/shading_extend.pdf differ diff --git a/test/test.py b/test/test.py index b5e3241b9..0e62aa9fe 100644 --- a/test/test.py +++ b/test/test.py @@ -514,7 +514,7 @@ def check(task, results, browser, masterMode): return kind = task['type'] - if 'eq' == kind: + if 'eq' == kind or 'text' == kind: checkEq(task, results, browser, masterMode) elif 'fbf' == kind: checkFBF(task, results, browser) @@ -528,6 +528,7 @@ def checkEq(task, results, browser, masterMode): pfx = os.path.join(REFDIR, sys.platform, browser, task['id']) results = results[0] taskId = task['id'] + taskType = task['type'] passed = True for page in xrange(len(results)): @@ -547,7 +548,7 @@ def checkEq(task, results, browser, masterMode): eq = (ref == snapshot) if not eq: - print 'TEST-UNEXPECTED-FAIL | eq', taskId, '| in', browser, '| rendering of page', page + 1, '!= reference rendering' + print 'TEST-UNEXPECTED-FAIL | ', taskType, taskId, '| in', browser, '| rendering of page', page + 1, '!= reference rendering' if not State.eqLog: State.eqLog = open(EQLOG_FILE, 'w') @@ -576,7 +577,7 @@ def checkEq(task, results, browser, masterMode): of.close() if passed: - print 'TEST-PASS | eq test', task['id'], '| in', browser + print 'TEST-PASS | ', taskType, ' test', task['id'], '| in', browser def checkFBF(task, results, browser): round0, round1 = results[0], results[1] diff --git a/test/test_manifest.json b/test/test_manifest.json index d19633947..e7575f41f 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -11,6 +11,12 @@ "rounds": 2, "type": "fbf" }, + { "id": "tracemonkey-text", + "file": "pdfs/tracemonkey.pdf", + "md5": "9a192d8b1a7dc652a19835f6f08098bd", + "rounds": 1, + "type": "text" + }, { "id": "html5-canvas-cheat-sheet-load", "file": "pdfs/canvas.pdf", "md5": "59510028561daf62e00bf9f6f066b033", @@ -89,6 +95,12 @@ "rounds": 1, "type": "eq" }, + { "id": "thuluthfont-text", + "file": "pdfs/ThuluthFeatures.pdf", + "md5": "b7e18bf7a3d6a9c82aefa12d721072fc", + "rounds": 1, + "type": "text" + }, { "id": "freeculture", "file": "pdfs/freeculture.pdf", "md5": "dcdf3a8268e6a18938a42d5149efcfca",