Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
78d861b2cb
49
make.js
Executable file → Normal file
49
make.js
Executable file → Normal file
@ -104,12 +104,19 @@ target.generic = function() {
|
|||||||
target.web = function() {
|
target.web = function() {
|
||||||
target.generic();
|
target.generic();
|
||||||
target.extension();
|
target.extension();
|
||||||
target.pagesrepo();
|
|
||||||
|
|
||||||
cd(ROOT_DIR);
|
|
||||||
echo();
|
echo();
|
||||||
echo('### Creating web site');
|
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('-R', GENERIC_DIR + '/*', GH_PAGES_DIR);
|
||||||
cp(FIREFOX_BUILD_DIR + '/*.xpi', FIREFOX_BUILD_DIR + '/*.rdf',
|
cp(FIREFOX_BUILD_DIR + '/*.xpi', FIREFOX_BUILD_DIR + '/*.rdf',
|
||||||
GH_PAGES_DIR + EXTENSION_SRC_DIR + 'firefox/');
|
GH_PAGES_DIR + EXTENSION_SRC_DIR + 'firefox/');
|
||||||
@ -118,12 +125,14 @@ target.web = function() {
|
|||||||
cp('web/index.html.template', GH_PAGES_DIR + '/index.html');
|
cp('web/index.html.template', GH_PAGES_DIR + '/index.html');
|
||||||
|
|
||||||
cd(GH_PAGES_DIR);
|
cd(GH_PAGES_DIR);
|
||||||
|
exec('git init');
|
||||||
|
exec('git remote add origin ' + REPO);
|
||||||
exec('git add -A');
|
exec('git add -A');
|
||||||
|
exec('git commit -am "gh-pages site created via make.js script"');
|
||||||
|
exec('git branch -m gh-pages');
|
||||||
|
|
||||||
echo();
|
echo();
|
||||||
echo('Website built in ' + GH_PAGES_DIR);
|
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');
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
19
src/api.js
19
src/api.js
@ -575,24 +575,15 @@ var WorkerTransport = (function WorkerTransportClosure() {
|
|||||||
this.objs.resolve(id, imageData);
|
this.objs.resolve(id, imageData);
|
||||||
break;
|
break;
|
||||||
case 'Font':
|
case 'Font':
|
||||||
var name = data[2];
|
var exportedData = 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, only the font object is created but the font is
|
// 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`.
|
// not yet attached to the DOM. This is done in `FontLoader.bind`.
|
||||||
var font;
|
var font;
|
||||||
try {
|
if ('error' in exportedData)
|
||||||
font = new Font(name, file, properties);
|
font = new ErrorFont(exportedData.error);
|
||||||
} catch (e) {
|
else
|
||||||
font = new ErrorFont(e);
|
font = new Font(exportedData);
|
||||||
}
|
|
||||||
this.objs.resolve(id, font);
|
this.objs.resolve(id, font);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -171,31 +171,30 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||||||
|
|
||||||
++self.objIdCounter;
|
++self.objIdCounter;
|
||||||
if (!font.loadedName) {
|
if (!font.loadedName) {
|
||||||
font.translated = self.translateFont(font, xref, resources,
|
var translated = self.translateFont(font, xref, resources,
|
||||||
dependency);
|
dependency);
|
||||||
if (font.translated) {
|
if (translated) {
|
||||||
// keep track of each font we translated so the caller can
|
// keep track of each font we translated so the caller can
|
||||||
// load them asynchronously before calling display on a page
|
// load them asynchronously before calling display on a page
|
||||||
loadedName = 'font_' + uniquePrefix + self.objIdCounter;
|
loadedName = 'font_' + uniquePrefix + self.objIdCounter;
|
||||||
font.translated.properties.loadedName = loadedName;
|
translated.properties.loadedName = loadedName;
|
||||||
font.loadedName = loadedName;
|
font.loadedName = loadedName;
|
||||||
|
font.translated = translated;
|
||||||
|
|
||||||
var translated = font.translated;
|
var data;
|
||||||
// Convert the file to an ArrayBuffer which will be turned back into
|
try {
|
||||||
// a Stream in the main thread.
|
var fontObj = new Font(translated.name,
|
||||||
if (translated.file)
|
translated.file,
|
||||||
translated.file = translated.file.getBytes();
|
translated.properties);
|
||||||
if (translated.properties.file) {
|
data = fontObj.export();
|
||||||
translated.properties.file =
|
} catch (e) {
|
||||||
translated.properties.file.getBytes();
|
data = { error: e };
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.send('obj', [
|
handler.send('obj', [
|
||||||
loadedName,
|
loadedName,
|
||||||
'Font',
|
'Font',
|
||||||
translated.name,
|
data
|
||||||
translated.file,
|
|
||||||
translated.properties
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
src/fonts.js
18
src/fonts.js
@ -1526,6 +1526,15 @@ function fontCharsToUnicode(charCodes, fontProperties) {
|
|||||||
*/
|
*/
|
||||||
var Font = (function FontClosure() {
|
var Font = (function FontClosure() {
|
||||||
function Font(name, file, properties) {
|
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.name = name;
|
||||||
this.coded = properties.coded;
|
this.coded = properties.coded;
|
||||||
this.charProcOperatorList = properties.charProcOperatorList;
|
this.charProcOperatorList = properties.charProcOperatorList;
|
||||||
@ -2036,6 +2045,15 @@ var Font = (function FontClosure() {
|
|||||||
mimetype: null,
|
mimetype: null,
|
||||||
encoding: 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) {
|
checkAndRepair: function Font_checkAndRepair(name, font, properties) {
|
||||||
function readTableEntry(file) {
|
function readTableEntry(file) {
|
||||||
var tag = file.getBytes(4);
|
var tag = file.getBytes(4);
|
||||||
|
@ -61,6 +61,12 @@ var Pattern = (function PatternClosure() {
|
|||||||
|
|
||||||
var Shadings = {};
|
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
|
// Radial and axial shading have very similar implementations
|
||||||
// If needed, the implementations can be broken into two classes
|
// If needed, the implementations can be broken into two classes
|
||||||
Shadings.RadialAxial = (function RadialAxialClosure() {
|
Shadings.RadialAxial = (function RadialAxialClosure() {
|
||||||
@ -69,7 +75,6 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
|
|||||||
this.coordsArr = dict.get('Coords');
|
this.coordsArr = dict.get('Coords');
|
||||||
this.shadingType = dict.get('ShadingType');
|
this.shadingType = dict.get('ShadingType');
|
||||||
this.type = 'Pattern';
|
this.type = 'Pattern';
|
||||||
|
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
var cs = dict.get('ColorSpace', 'CS');
|
var cs = dict.get('ColorSpace', 'CS');
|
||||||
cs = ColorSpace.parse(cs, xref, res);
|
cs = ColorSpace.parse(cs, xref, res);
|
||||||
@ -87,7 +92,23 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
|
|||||||
var extendArr = dict.get('Extend');
|
var extendArr = dict.get('Extend');
|
||||||
extendStart = extendArr[0];
|
extendStart = extendArr[0];
|
||||||
extendEnd = extendArr[1];
|
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;
|
this.extendStart = extendStart;
|
||||||
@ -103,16 +124,43 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
|
|||||||
// 10 samples seems good enough for now, but probably won't work
|
// 10 samples seems good enough for now, but probably won't work
|
||||||
// if there are sharp color changes. Ideally, we would implement
|
// if there are sharp color changes. Ideally, we would implement
|
||||||
// the spec faithfully and add lossless optimizations.
|
// the spec faithfully and add lossless optimizations.
|
||||||
var step = (t1 - t0) / 10;
|
|
||||||
var diff = t1 - t0;
|
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) {
|
for (var i = t0; i <= t1; i += step) {
|
||||||
var rgbColor = cs.getRgb(fn([i]));
|
var rgbColor = cs.getRgb(fn([i]));
|
||||||
var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
|
var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
|
||||||
colorStops.push([(i - t0) / diff, cssColor]);
|
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;
|
this.colorStops = colorStops;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
// "firefox-bin: Fatal IO error 12 (Cannot allocate memory) on X server :1."
|
// "firefox-bin: Fatal IO error 12 (Cannot allocate memory) on X server :1."
|
||||||
// PDFJS.disableWorker = true;
|
// PDFJS.disableWorker = true;
|
||||||
|
|
||||||
var appPath, browser, canvas, currentTaskIdx, manifest, stdout;
|
var appPath, browser, canvas, dummyCanvas, currentTaskIdx, manifest, stdout;
|
||||||
var inFlightRequests = 0;
|
var inFlightRequests = 0;
|
||||||
|
|
||||||
function queryParams() {
|
function queryParams() {
|
||||||
@ -148,6 +148,46 @@ function canvasToDataURL() {
|
|||||||
return canvas.toDataURL('image/png');
|
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) {
|
function nextPage(task, loadError) {
|
||||||
var failure = loadError || '';
|
var failure = loadError || '';
|
||||||
|
|
||||||
@ -196,16 +236,21 @@ function nextPage(task, loadError) {
|
|||||||
canvas.height = viewport.height;
|
canvas.height = viewport.height;
|
||||||
clear(ctx);
|
clear(ctx);
|
||||||
|
|
||||||
// using the text layer builder that does nothing to test
|
var drawContext, textLayerBuilder;
|
||||||
// text layer creation operations
|
if (task.type == 'text') {
|
||||||
var textLayerBuilder = {
|
// using dummy canvas for pdf context drawing operations
|
||||||
beginLayout: function nullTextLayerBuilderBeginLayout() {},
|
if (!dummyCanvas) {
|
||||||
endLayout: function nullTextLayerBuilderEndLayout() {},
|
dummyCanvas = document.createElement('canvas');
|
||||||
appendText: function nullTextLayerBuilderAppendText(text, fontName,
|
}
|
||||||
fontSize) {}
|
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 = {
|
var renderContext = {
|
||||||
canvasContext: ctx,
|
canvasContext: drawContext,
|
||||||
textLayer: textLayerBuilder,
|
textLayer: textLayerBuilder,
|
||||||
viewport: viewport
|
viewport: viewport
|
||||||
};
|
};
|
||||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -34,3 +34,4 @@
|
|||||||
!gradientfill.pdf
|
!gradientfill.pdf
|
||||||
!basicapi.pdf
|
!basicapi.pdf
|
||||||
!mixedfonts.pdf
|
!mixedfonts.pdf
|
||||||
|
!shading_extend.pdf
|
||||||
|
BIN
test/pdfs/shading_extend.pdf
Normal file
BIN
test/pdfs/shading_extend.pdf
Normal file
Binary file not shown.
@ -514,7 +514,7 @@ def check(task, results, browser, masterMode):
|
|||||||
return
|
return
|
||||||
|
|
||||||
kind = task['type']
|
kind = task['type']
|
||||||
if 'eq' == kind:
|
if 'eq' == kind or 'text' == kind:
|
||||||
checkEq(task, results, browser, masterMode)
|
checkEq(task, results, browser, masterMode)
|
||||||
elif 'fbf' == kind:
|
elif 'fbf' == kind:
|
||||||
checkFBF(task, results, browser)
|
checkFBF(task, results, browser)
|
||||||
@ -528,6 +528,7 @@ def checkEq(task, results, browser, masterMode):
|
|||||||
pfx = os.path.join(REFDIR, sys.platform, browser, task['id'])
|
pfx = os.path.join(REFDIR, sys.platform, browser, task['id'])
|
||||||
results = results[0]
|
results = results[0]
|
||||||
taskId = task['id']
|
taskId = task['id']
|
||||||
|
taskType = task['type']
|
||||||
|
|
||||||
passed = True
|
passed = True
|
||||||
for page in xrange(len(results)):
|
for page in xrange(len(results)):
|
||||||
@ -547,7 +548,7 @@ def checkEq(task, results, browser, masterMode):
|
|||||||
|
|
||||||
eq = (ref == snapshot)
|
eq = (ref == snapshot)
|
||||||
if not eq:
|
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:
|
if not State.eqLog:
|
||||||
State.eqLog = open(EQLOG_FILE, 'w')
|
State.eqLog = open(EQLOG_FILE, 'w')
|
||||||
@ -576,7 +577,7 @@ def checkEq(task, results, browser, masterMode):
|
|||||||
of.close()
|
of.close()
|
||||||
|
|
||||||
if passed:
|
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):
|
def checkFBF(task, results, browser):
|
||||||
round0, round1 = results[0], results[1]
|
round0, round1 = results[0], results[1]
|
||||||
|
@ -11,6 +11,12 @@
|
|||||||
"rounds": 2,
|
"rounds": 2,
|
||||||
"type": "fbf"
|
"type": "fbf"
|
||||||
},
|
},
|
||||||
|
{ "id": "tracemonkey-text",
|
||||||
|
"file": "pdfs/tracemonkey.pdf",
|
||||||
|
"md5": "9a192d8b1a7dc652a19835f6f08098bd",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
{ "id": "html5-canvas-cheat-sheet-load",
|
{ "id": "html5-canvas-cheat-sheet-load",
|
||||||
"file": "pdfs/canvas.pdf",
|
"file": "pdfs/canvas.pdf",
|
||||||
"md5": "59510028561daf62e00bf9f6f066b033",
|
"md5": "59510028561daf62e00bf9f6f066b033",
|
||||||
@ -89,6 +95,12 @@
|
|||||||
"rounds": 1,
|
"rounds": 1,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
},
|
},
|
||||||
|
{ "id": "thuluthfont-text",
|
||||||
|
"file": "pdfs/ThuluthFeatures.pdf",
|
||||||
|
"md5": "b7e18bf7a3d6a9c82aefa12d721072fc",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
{ "id": "freeculture",
|
{ "id": "freeculture",
|
||||||
"file": "pdfs/freeculture.pdf",
|
"file": "pdfs/freeculture.pdf",
|
||||||
"md5": "dcdf3a8268e6a18938a42d5149efcfca",
|
"md5": "dcdf3a8268e6a18938a42d5149efcfca",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user