fork from https://github.com/mozilla/pdf.js.git
This commit is contained in:
4
test/.gitignore
vendored
Normal file
4
test/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
ref/
|
||||
tmp/
|
||||
*.log
|
||||
test_snapshots/
|
||||
52
test/add_test.mjs
Normal file
52
test/add_test.mjs
Normal file
@@ -0,0 +1,52 @@
|
||||
import { calculateMD5 } from "./downloadutils.mjs";
|
||||
import { execSync } from "child_process";
|
||||
import fs from "fs";
|
||||
|
||||
const testManifest = "test/test_manifest.json";
|
||||
const pdfFolder = "test/pdfs/";
|
||||
const gitIgnore = "test/pdfs/.gitignore";
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
console.log("\nUsage: node add_test.js FILE\n");
|
||||
console.log(
|
||||
` Add a PDF as a reference test. FILE must be located in ${pdfFolder}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const file = process.argv[2];
|
||||
if (!file.startsWith(pdfFolder)) {
|
||||
throw new Error(`PDF file must be in '${pdfFolder}' directory.`);
|
||||
}
|
||||
if (!fs.existsSync(file)) {
|
||||
throw new Error(`PDF file does not exist '${file}'.`);
|
||||
}
|
||||
|
||||
function getRandomArbitrary(min, max) {
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
const md5 = await calculateMD5(file);
|
||||
|
||||
let contents = fs.readFileSync(gitIgnore, "utf8").split("\n");
|
||||
const randomLine = getRandomArbitrary(10, contents.length - 2);
|
||||
contents.splice(randomLine, 0, "!" + file.substring(file.lastIndexOf("/") + 1));
|
||||
fs.writeFileSync("test/pdfs/.gitignore", contents.join("\n"));
|
||||
|
||||
contents = fs.readFileSync(testManifest, "utf8");
|
||||
const pdf = file.substring(file.lastIndexOf("/") + 1, file.length - 4);
|
||||
const randomPoint = getRandomArbitrary(100, contents.length - 20);
|
||||
const bracket = contents.indexOf("},\n", randomPoint);
|
||||
const out =
|
||||
contents.substring(0, bracket) +
|
||||
"},\n" +
|
||||
` { "id": "${pdf}",\n` +
|
||||
` "file": "pdfs/${pdf}.pdf",\n` +
|
||||
` "md5": "${md5}",\n` +
|
||||
' "rounds": 1,\n' +
|
||||
' "type": "eq"\n' +
|
||||
" " +
|
||||
contents.substring(bracket);
|
||||
fs.writeFileSync("test/test_manifest.json", out);
|
||||
execSync(`git add ${testManifest} ${gitIgnore}`);
|
||||
execSync(`git add ${file}`);
|
||||
71
test/annotation_layer_builder_overrides.css
Normal file
71
test/annotation_layer_builder_overrides.css
Normal file
@@ -0,0 +1,71 @@
|
||||
/* Copyright 2017 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* Overrides to make the annotation layer visible for reference testing. */
|
||||
|
||||
.annotationLayer {
|
||||
position: absolute;
|
||||
|
||||
.wasCanvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.buttonWidgetAnnotation:is(.checkBox, .radioButton) input {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
:is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder) > a,
|
||||
.popupTriggerArea::after,
|
||||
.fileAttachmentAnnotation:not(.hasFillAlpha) .popupTriggerArea {
|
||||
opacity: 0.2;
|
||||
background: rgb(255 255 0);
|
||||
box-shadow: 0 2px 10px rgb(255 255 0);
|
||||
}
|
||||
.fileAttachmentAnnotation.hasFillAlpha {
|
||||
outline: 2px solid yellow;
|
||||
}
|
||||
|
||||
.hasClipPath::after {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.linkAnnotation.hasBorder {
|
||||
background-color: rgb(255 255 0 / 0.2);
|
||||
}
|
||||
|
||||
.popupTriggerArea::after {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.popup :is(h1, p) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.annotationTextContent {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.4;
|
||||
background-color: transparent;
|
||||
color: red;
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
96465
test/bidi/BidiCharacterTest.txt
Normal file
96465
test/bidi/BidiCharacterTest.txt
Normal file
File diff suppressed because it is too large
Load Diff
497591
test/bidi/BidiTest.txt
Normal file
497591
test/bidi/BidiTest.txt
Normal file
File diff suppressed because it is too large
Load Diff
455
test/chromium/test-telemetry.js
Normal file
455
test/chromium/test-telemetry.js
Normal file
@@ -0,0 +1,455 @@
|
||||
#!/usr/bin/env node
|
||||
/* Copyright 2016 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* eslint-disable no-var */
|
||||
|
||||
import assert from "assert";
|
||||
import fs from "fs";
|
||||
import vm from "vm";
|
||||
|
||||
var SRC_DIR = __dirname + "/../../";
|
||||
var telemetryJsPath = "extensions/chromium/telemetry.js";
|
||||
var telemetryJsSource = fs.readFileSync(SRC_DIR + telemetryJsPath);
|
||||
var telemetryScript = new vm.Script(telemetryJsSource, {
|
||||
filename: telemetryJsPath,
|
||||
displayErrors: true,
|
||||
});
|
||||
var LOG_URL = /LOG_URL = '([^']+)'/.exec(telemetryJsSource)[1];
|
||||
|
||||
// Create a minimal extension global that mocks the extension environment that
|
||||
// is used by telemetry.js
|
||||
function createExtensionGlobal() {
|
||||
var window = {};
|
||||
|
||||
// Whenever a "request" was made, the extra headers are appended to this list.
|
||||
var test_requests = (window.test_requests = []);
|
||||
|
||||
// Extension API mocks.
|
||||
window.window = window;
|
||||
window.chrome = {};
|
||||
window.chrome.extension = {};
|
||||
window.chrome.extension.inIncognitoContext = false;
|
||||
window.chrome.runtime = {};
|
||||
window.chrome.runtime.id = "oemmndcbldboiebfnladdacbdfmadadm";
|
||||
window.chrome.runtime.getManifest = function () {
|
||||
return { version: "1.0.0" };
|
||||
};
|
||||
|
||||
function createStorageAPI() {
|
||||
var storageArea = {};
|
||||
storageArea.get = function (key, callback) {
|
||||
assert.equal(key, "disableTelemetry");
|
||||
// chrome.storage.*. is async, but we make it synchronous to ease testing.
|
||||
callback(storageArea.mock_data);
|
||||
};
|
||||
return storageArea;
|
||||
}
|
||||
window.chrome.storage = {};
|
||||
window.chrome.storage.managed = createStorageAPI();
|
||||
window.chrome.storage.local = createStorageAPI();
|
||||
window.chrome.storage.sync = createStorageAPI();
|
||||
|
||||
// Other DOM.
|
||||
window.navigator = {};
|
||||
window.navigator.onLine = true;
|
||||
window.navigator.userAgent =
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " +
|
||||
"Chrome/50.0.2661.94 Safari/537.36";
|
||||
window.localStorage = {};
|
||||
|
||||
var getRandomValues_state = 0;
|
||||
window.crypto = {};
|
||||
window.crypto.getRandomValues = function (buf) {
|
||||
var state = getRandomValues_state++;
|
||||
for (var i = 0; i < buf.length; ++i) {
|
||||
// Totally random byte ;)
|
||||
buf[i] = 0x42 + state;
|
||||
}
|
||||
return buf;
|
||||
};
|
||||
|
||||
// Network-related mocks.
|
||||
window.Request = {};
|
||||
window.Request.prototype = {
|
||||
get mode() {
|
||||
throw new TypeError("Illegal invocation");
|
||||
},
|
||||
};
|
||||
window.fetch = function (url, options) {
|
||||
assert.equal(url, LOG_URL);
|
||||
assert.equal(options.method, "POST");
|
||||
assert.equal(options.mode, "cors");
|
||||
assert.ok(!options.body);
|
||||
test_requests.push(options.headers);
|
||||
};
|
||||
window.Headers = function (headers) {
|
||||
headers = JSON.parse(JSON.stringify(headers)); // Clone.
|
||||
Object.keys(headers).forEach(function (k) {
|
||||
headers[k] = String(headers[k]);
|
||||
});
|
||||
return headers;
|
||||
};
|
||||
window.XMLHttpRequest = function () {
|
||||
var invoked = {
|
||||
open: false,
|
||||
send: false,
|
||||
};
|
||||
var headers = {};
|
||||
return {
|
||||
open(method, url) {
|
||||
assert.equal(invoked.open, false);
|
||||
invoked.open = true;
|
||||
assert.equal(method, "POST");
|
||||
assert.equal(url, LOG_URL);
|
||||
},
|
||||
setRequestHeader(k, v) {
|
||||
assert.equal(invoked.open, true);
|
||||
headers[k] = String(v);
|
||||
},
|
||||
send(body) {
|
||||
assert.equal(invoked.open, true);
|
||||
assert.equal(invoked.send, false);
|
||||
invoked.send = true;
|
||||
assert.ok(!body);
|
||||
test_requests.push(headers);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Time-related logic.
|
||||
var timers = [];
|
||||
window.setInterval = function (callback, ms) {
|
||||
assert.equal(typeof callback, "function");
|
||||
timers.push(callback);
|
||||
};
|
||||
window.Date = {
|
||||
test_now_value: Date.now(),
|
||||
now() {
|
||||
return window.Date.test_now_value;
|
||||
},
|
||||
};
|
||||
window.test_fireTimers = function () {
|
||||
assert.ok(timers.length);
|
||||
timers.forEach(function (timer) {
|
||||
timer();
|
||||
});
|
||||
};
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
// Simulate an update of the browser.
|
||||
function updateBrowser(window) {
|
||||
window.navigator.userAgent = window.navigator.userAgent.replace(
|
||||
/Chrome\/(\d+)/,
|
||||
function (_, v) {
|
||||
return "Chrome/" + (parseInt(v, 10) + 1);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var tests = [
|
||||
function test_first_run() {
|
||||
// Default settings, run extension for the first time.
|
||||
var window = createExtensionGlobal();
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_first_run_incognito() {
|
||||
// The extension should not send any requests when in incognito mode.
|
||||
var window = createExtensionGlobal();
|
||||
window.chrome.extension.inIncognitoContext = true;
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, []);
|
||||
},
|
||||
|
||||
function test_storage_managed_unavailable() {
|
||||
var window = createExtensionGlobal();
|
||||
delete window.chrome.storage.managed;
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_managed_pref() {
|
||||
var window = createExtensionGlobal();
|
||||
window.chrome.storage.managed.mock_data = {
|
||||
disableTelemetry: true,
|
||||
};
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, []);
|
||||
},
|
||||
|
||||
function test_local_pref() {
|
||||
var window = createExtensionGlobal();
|
||||
window.chrome.storage.local.mock_data = {
|
||||
disableTelemetry: true,
|
||||
};
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, []);
|
||||
},
|
||||
|
||||
function test_managed_pref_is_overridden() {
|
||||
var window = createExtensionGlobal();
|
||||
window.chrome.storage.managed.mock_data = {
|
||||
disableTelemetry: true,
|
||||
};
|
||||
window.chrome.storage.sync.mock_data = {
|
||||
disableTelemetry: false,
|
||||
};
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_run_extension_again() {
|
||||
var window = createExtensionGlobal();
|
||||
telemetryScript.runInNewContext(window);
|
||||
telemetryScript.runInNewContext(window);
|
||||
// Only one request should be sent because of rate-limiting.
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
|
||||
// Simulate that quite some hours passed, but it's still rate-limited.
|
||||
window.Date.test_now_value += 11 * 36e5;
|
||||
telemetryScript.runInNewContext(window);
|
||||
// Only one request should be sent because of rate-limiting.
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
|
||||
// Another hour passes and the request should not be rate-limited any more.
|
||||
window.Date.test_now_value += 1 * 36e5;
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_running_for_a_while() {
|
||||
var window = createExtensionGlobal();
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
|
||||
// Simulate that the timer fired 11 hours since the last ping. The request
|
||||
// should still be rate-limited.
|
||||
window.Date.test_now_value += 11 * 36e5;
|
||||
window.test_fireTimers();
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
|
||||
// Another hour passes and the request should not be rate-limited any more.
|
||||
window.Date.test_now_value += 1 * 36e5;
|
||||
window.test_fireTimers();
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_browser_update() {
|
||||
var window = createExtensionGlobal();
|
||||
telemetryScript.runInNewContext(window);
|
||||
updateBrowser(window);
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
{
|
||||
// Generate a new ID for better privacy.
|
||||
"Deduplication-Id": "4343434343",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_browser_update_between_pref_toggle() {
|
||||
var window = createExtensionGlobal();
|
||||
telemetryScript.runInNewContext(window);
|
||||
window.chrome.storage.local.mock_data = {
|
||||
disableTelemetry: true,
|
||||
};
|
||||
updateBrowser(window);
|
||||
telemetryScript.runInNewContext(window);
|
||||
window.chrome.storage.local.mock_data = {
|
||||
disableTelemetry: false,
|
||||
};
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
{
|
||||
// Generate a new ID for better privacy, even if the update happened
|
||||
// while telemetry was disabled.
|
||||
"Deduplication-Id": "4343434343",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_extension_update() {
|
||||
var window = createExtensionGlobal();
|
||||
telemetryScript.runInNewContext(window);
|
||||
window.chrome.runtime.getManifest = function () {
|
||||
return { version: "1.0.1" };
|
||||
};
|
||||
window.Date.test_now_value += 12 * 36e5;
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
{
|
||||
// The ID did not change because the browser version did not change.
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.1",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_unofficial_build() {
|
||||
var window = createExtensionGlobal();
|
||||
var didWarn = false;
|
||||
window.console = {};
|
||||
window.console.warn = function () {
|
||||
didWarn = true;
|
||||
};
|
||||
window.chrome.runtime.id = "abcdefghijklmnopabcdefghijklmnop";
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, []);
|
||||
assert.ok(didWarn);
|
||||
},
|
||||
|
||||
function test_fetch_is_supported() {
|
||||
var window = createExtensionGlobal();
|
||||
// XMLHttpRequest should not be called when fetch is available. So removing
|
||||
// the XMLHttpRequest API should not change behavior.
|
||||
delete window.XMLHttpRequest;
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_fetch_not_supported() {
|
||||
var window = createExtensionGlobal();
|
||||
delete window.fetch;
|
||||
delete window.Request;
|
||||
delete window.Headers;
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_fetch_mode_not_supported() {
|
||||
var window = createExtensionGlobal();
|
||||
delete window.Request.prototype.mode;
|
||||
window.fetch = function () {
|
||||
throw new Error("Unexpected call to fetch!");
|
||||
};
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
function test_network_offline() {
|
||||
var window = createExtensionGlobal();
|
||||
// Simulate that the network is down for sure.
|
||||
window.navigator.onLine = false;
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, []);
|
||||
|
||||
// Simulate that the network might be up.
|
||||
window.navigator.onLine = true;
|
||||
telemetryScript.runInNewContext(window);
|
||||
assert.deepEqual(window.test_requests, [
|
||||
{
|
||||
"Deduplication-Id": "4242424242",
|
||||
"Extension-Version": "1.0.0",
|
||||
},
|
||||
]);
|
||||
},
|
||||
];
|
||||
var test_i = 0;
|
||||
|
||||
(function next() {
|
||||
var test = tests[test_i++];
|
||||
if (!test) {
|
||||
console.log("All tests completed.");
|
||||
return;
|
||||
}
|
||||
console.log("Running test " + test_i + "/" + tests.length + ": " + test.name);
|
||||
test();
|
||||
process.nextTick(next);
|
||||
})();
|
||||
31
test/color_utils.mjs
Normal file
31
test/color_utils.mjs
Normal file
@@ -0,0 +1,31 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import kleur from "kleur";
|
||||
|
||||
kleur.enabled =
|
||||
!process.env.NO_COLOR &&
|
||||
(!!process.stdout.isTTY ||
|
||||
!!process.env.FORCE_COLOR ||
|
||||
process.env.GITHUB_ACTIONS === "true");
|
||||
|
||||
const TEST_PASSED = kleur.green("TEST-PASS");
|
||||
const TEST_UNEXPECTED_FAIL = kleur.red().bold("TEST-UNEXPECTED-FAIL");
|
||||
|
||||
function colorBrowser(name) {
|
||||
return kleur.cyan(name);
|
||||
}
|
||||
|
||||
export { colorBrowser, TEST_PASSED, TEST_UNEXPECTED_FAIL };
|
||||
66
test/components/simple-viewer.html
Normal file
66
test/components/simple-viewer.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2026 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<html dir="ltr" mozdisallowselectionprint lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
|
||||
<title>PDF.js — Simple viewer</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #808080;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#viewerContainer {
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="resource" type="application/l10n" href="../../web/locale/locale.json" />
|
||||
<link rel="stylesheet" href="../../web/pdf_viewer.css" />
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"pdfjs/": "../../src/",
|
||||
"pdfjs-lib": "../../src/pdf.js",
|
||||
|
||||
"display-binary_data_factory": "../../src/display/binary_data_factory.js",
|
||||
"display-network_stream": "../../src/display/network_stream.js",
|
||||
"display-node_utils": "../../src/display/stubs.js",
|
||||
|
||||
"fluent-bundle": "../../node_modules/@fluent/bundle/esm/index.js",
|
||||
"fluent-dom": "../../node_modules/@fluent/dom/esm/index.js",
|
||||
"cached-iterable": "../../node_modules/cached-iterable/src/index.mjs",
|
||||
|
||||
"web-null_l10n": "../../web/genericl10n.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="simple-viewer.js" type="module"></script>
|
||||
</head>
|
||||
|
||||
<body tabindex="1">
|
||||
<div id="viewerContainer">
|
||||
<div id="viewer" class="pdfViewer"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
113
test/components/simple-viewer.js
Normal file
113
test/components/simple-viewer.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { getDocument, GlobalWorkerOptions } from "pdfjs-lib";
|
||||
import { EventBus } from "../../web/event_utils.js";
|
||||
import { GenericL10n } from "../../web/genericl10n.js";
|
||||
import { PDFFindController } from "../../web/pdf_find_controller.js";
|
||||
import { PDFLinkService } from "../../web/pdf_link_service.js";
|
||||
import { PDFScriptingManager } from "../../web/pdf_scripting_manager.component.js";
|
||||
import { PDFViewer } from "../../web/pdf_viewer.js";
|
||||
|
||||
// The workerSrc property shall be specified.
|
||||
//
|
||||
GlobalWorkerOptions.workerSrc =
|
||||
typeof PDFJSDev === "undefined"
|
||||
? "../../src/pdf.worker.js"
|
||||
: "../../build/pdf.worker.mjs";
|
||||
|
||||
// Some PDFs need external cmaps.
|
||||
//
|
||||
const CMAP_URL =
|
||||
typeof PDFJSDev === "undefined"
|
||||
? "../../external/bcmaps/"
|
||||
: "../../web/cmaps/";
|
||||
|
||||
const DEFAULT_URL = "../../web/compressed.tracemonkey-pldi-09.pdf";
|
||||
|
||||
const ENABLE_XFA = true;
|
||||
const SEARCH_FOR = ""; // try "Mozilla";
|
||||
|
||||
const SANDBOX_BUNDLE_SRC = new URL(
|
||||
typeof PDFJSDev === "undefined"
|
||||
? "../../build/generic/build/pdf.sandbox.mjs"
|
||||
: "../../build/pdf.sandbox.mjs",
|
||||
window.location
|
||||
);
|
||||
|
||||
const WASM_URL =
|
||||
typeof PDFJSDev === "undefined"
|
||||
? "../../build/generic/web/wasm/"
|
||||
: "../../build/wasm/";
|
||||
|
||||
const fileUrl = new URLSearchParams(location.search).get("file") ?? DEFAULT_URL;
|
||||
|
||||
const container = document.getElementById("viewerContainer");
|
||||
|
||||
const eventBus = new EventBus();
|
||||
|
||||
// (Optionally) enable hyperlinks within PDF files.
|
||||
const pdfLinkService = new PDFLinkService({
|
||||
eventBus,
|
||||
});
|
||||
|
||||
// (Optionally) enable find controller.
|
||||
const pdfFindController = new PDFFindController({
|
||||
eventBus,
|
||||
linkService: pdfLinkService,
|
||||
});
|
||||
|
||||
// (Optionally) enable scripting support.
|
||||
const pdfScriptingManager = new PDFScriptingManager({
|
||||
eventBus,
|
||||
sandboxBundleSrc: SANDBOX_BUNDLE_SRC,
|
||||
wasmUrl: WASM_URL,
|
||||
});
|
||||
|
||||
const pdfViewer = new PDFViewer({
|
||||
container,
|
||||
eventBus,
|
||||
l10n: new GenericL10n(navigator.language),
|
||||
linkService: pdfLinkService,
|
||||
findController: pdfFindController,
|
||||
scriptingManager: pdfScriptingManager,
|
||||
});
|
||||
pdfLinkService.setViewer(pdfViewer);
|
||||
pdfScriptingManager.setViewer(pdfViewer);
|
||||
window.pdfScriptingManager = pdfScriptingManager;
|
||||
|
||||
eventBus.on("pagesinit", function () {
|
||||
// We can use pdfViewer now, e.g. let's change default scale.
|
||||
pdfViewer.currentScaleValue = "page-width";
|
||||
|
||||
// We can try searching for things.
|
||||
if (SEARCH_FOR) {
|
||||
eventBus.dispatch("find", { type: "", query: SEARCH_FOR });
|
||||
}
|
||||
});
|
||||
|
||||
// Loading document.
|
||||
const loadingTask = getDocument({
|
||||
url: fileUrl,
|
||||
cMapUrl: CMAP_URL,
|
||||
enableXfa: ENABLE_XFA,
|
||||
});
|
||||
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
// Document loaded, specifying document for the viewer and
|
||||
// the (optional) linkService.
|
||||
pdfViewer.setDocument(pdfDocument);
|
||||
|
||||
pdfLinkService.setDocument(pdfDocument, null);
|
||||
58
test/coverage_utils.js
Normal file
58
test/coverage_utils.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Istanbul coverage objects use s (statements), b (branches), and f (functions)
|
||||
// as shorthand keys for the hit-count maps.
|
||||
function mergeCoverageIntoGlobal(coverage) {
|
||||
if (!coverage || Object.keys(coverage).length === 0) {
|
||||
return;
|
||||
}
|
||||
globalThis.__coverage__ ??= {};
|
||||
for (const [key, fileCoverage] of Object.entries(coverage)) {
|
||||
const existing = globalThis.__coverage__[key];
|
||||
if (!existing) {
|
||||
globalThis.__coverage__[key] = fileCoverage;
|
||||
continue;
|
||||
}
|
||||
for (const id of Object.keys(fileCoverage.s)) {
|
||||
existing.s[id] = (existing.s[id] ?? 0) + fileCoverage.s[id];
|
||||
}
|
||||
for (const id of Object.keys(fileCoverage.b)) {
|
||||
existing.b[id] = fileCoverage.b[id].map(
|
||||
(c, i) => (existing.b[id]?.[i] ?? 0) + c
|
||||
);
|
||||
}
|
||||
for (const id of Object.keys(fileCoverage.f)) {
|
||||
existing.f[id] = (existing.f[id] ?? 0) + fileCoverage.f[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAndMergeWorkerCoverage(pdfWorker) {
|
||||
if (!pdfWorker) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const coverage = await pdfWorker.messageHandler.sendWithPromise(
|
||||
"GetWorkerCoverage",
|
||||
null
|
||||
);
|
||||
mergeCoverageIntoGlobal(coverage);
|
||||
} catch (e) {
|
||||
console.warn(`Failed to collect worker coverage: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
export { fetchAndMergeWorkerCoverage, mergeCoverageIntoGlobal };
|
||||
129
test/downloadutils.mjs
Normal file
129
test/downloadutils.mjs
Normal file
@@ -0,0 +1,129 @@
|
||||
/* Copyright 2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import crypto from "crypto";
|
||||
import fs from "fs";
|
||||
|
||||
function rewriteWebArchiveUrl(url) {
|
||||
// Web Archive URLs need to be transformed to add `if_` after the ID.
|
||||
// Without this, an HTML page containing an iframe with the PDF file
|
||||
// will be served instead (issue 8920).
|
||||
const webArchiveRegex =
|
||||
/(^https?:\/\/web\.archive\.org\/web\/)(\d+)(\/https?:\/\/.+)/;
|
||||
const urlParts = webArchiveRegex.exec(url);
|
||||
if (urlParts) {
|
||||
return `${urlParts[1]}${urlParts[2]}if_${urlParts[3]}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
async function downloadFile(file, url) {
|
||||
url = rewriteWebArchiveUrl(url);
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return fs.promises.writeFile(file, response.body);
|
||||
}
|
||||
|
||||
async function downloadManifestFiles(manifest) {
|
||||
// Keep track of file identifiers to remove any duplicates,
|
||||
// since multiple test-cases may use the same PDF.
|
||||
const seenFiles = new Set();
|
||||
|
||||
const links = new Map(
|
||||
manifest
|
||||
.filter(({ link, file }) => {
|
||||
if (!link || seenFiles.has(file)) {
|
||||
return false;
|
||||
}
|
||||
seenFiles.add(file);
|
||||
return !fs.existsSync(file);
|
||||
})
|
||||
.map(({ file }) => {
|
||||
const url = fs.readFileSync(`${file}.link`).toString().trimEnd();
|
||||
return [file, url];
|
||||
})
|
||||
);
|
||||
seenFiles.clear();
|
||||
|
||||
for (const [file, url] of links) {
|
||||
console.log(`Downloading ${url} to ${file}...`);
|
||||
try {
|
||||
await downloadFile(file, url);
|
||||
} catch (ex) {
|
||||
console.error(`Error during downloading of ${url}:`, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function calculateMD5(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash("md5");
|
||||
const stream = fs.createReadStream(file);
|
||||
stream.on("data", data => hash.update(data));
|
||||
stream.on("error", error => reject(error));
|
||||
stream.on("end", () => resolve(hash.digest("hex")));
|
||||
});
|
||||
}
|
||||
|
||||
async function verifyManifestFiles(manifest) {
|
||||
let error = false;
|
||||
|
||||
for (const item of manifest) {
|
||||
if (fs.existsSync(`${item.file}.error`)) {
|
||||
console.error(
|
||||
`WARNING: "${item.file}" was not downloaded; see "${item.file}.error" file.`
|
||||
);
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.link && !fs.existsSync(`${item.file}.link`)) {
|
||||
console.error(
|
||||
`WARNING: Unneeded \`"link": true\`-entry for the "${item.id}" test.`
|
||||
);
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const md5 = await calculateMD5(item.file);
|
||||
if (!item.md5) {
|
||||
console.error(
|
||||
`WARNING: MD5 hash missing for "${item.file}" (computed "${md5}").`
|
||||
);
|
||||
error = true;
|
||||
} else if (md5 !== item.md5) {
|
||||
console.error(
|
||||
`WARNING: MD5 hash mismatch for "${item.file}" (expected "${item.md5}", computed "${md5}").`
|
||||
);
|
||||
error = true;
|
||||
}
|
||||
} catch (ex) {
|
||||
console.log(
|
||||
`WARNING: MD5 hash calculation failed for "${item.file}" ("${ex}").`
|
||||
);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new Error("Manifest validation failed");
|
||||
}
|
||||
}
|
||||
|
||||
export { calculateMD5, downloadManifestFiles, verifyManifestFiles };
|
||||
39
test/draw_layer_test.css
Normal file
39
test/draw_layer_test.css
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Copyright 2023 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* Used in 'highlight' tests */
|
||||
|
||||
.highlight {
|
||||
position: absolute;
|
||||
mix-blend-mode: multiply;
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
.highlightOutline {
|
||||
position: absolute;
|
||||
mix-blend-mode: normal;
|
||||
fill-rule: evenodd;
|
||||
fill: none;
|
||||
|
||||
.mainOutline {
|
||||
stroke: white;
|
||||
stroke-width: 4px;
|
||||
}
|
||||
|
||||
.secondaryOutline {
|
||||
stroke: blue;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
1615
test/driver.js
Normal file
1615
test/driver.js
Normal file
File diff suppressed because it is too large
Load Diff
36
test/font/README.md
Normal file
36
test/font/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Font tests
|
||||
|
||||
The font tests check if PDF.js can read font data correctly. For validation
|
||||
the `ttx` tool (from the Python `fonttools` library) is used that can convert
|
||||
font data to an XML format that we can easily use for assertions in the tests.
|
||||
In the font tests we let PDF.js read font data and pass the PDF.js-interpreted
|
||||
font data through `ttx` to check its correctness. The font tests are successful
|
||||
if PDF.js can successfully read the font data and `ttx` can successfully read
|
||||
the PDF.js-interpreted font data back, proving that PDF.js does not apply any
|
||||
transformations that break the font data.
|
||||
|
||||
## Running the font tests
|
||||
|
||||
The font tests are run on GitHub Actions using the workflow defined in
|
||||
`.github/workflows/font_tests.yml`, but it is also possible to run the font
|
||||
tests locally. The current stable versions of the following dependencies are
|
||||
required to be installed on the system:
|
||||
|
||||
- Python 3
|
||||
- `fonttools` (see https://pypi.org/project/fonttools and https://github.com/fonttools/fonttools)
|
||||
|
||||
The recommended way of installing `fonttools` is using `pip` in a virtual
|
||||
environment because it avoids having to do a system-wide installation and
|
||||
therefore improves isolation, but any other way of installing `fonttools`
|
||||
that makes `ttx` available in the `PATH` environment variable also works.
|
||||
|
||||
Using the virtual environment approach the font tests can be run locally by
|
||||
creating and sourcing a virtual environment with `fonttools` installed in
|
||||
it before running the font tests:
|
||||
|
||||
```
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install fonttools
|
||||
npx gulp fonttest
|
||||
```
|
||||
18
test/font/font_core_spec.js
Normal file
18
test/font/font_core_spec.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ttx, verifyTtxOutput } from "./fontutils.js";
|
||||
|
||||
describe("font1", function () {
|
||||
const font1_1 = Uint8Array.fromBase64(
|
||||
// eslint-disable-next-line max-len
|
||||
"T1RUTwAJAIAAAwAQQ0ZGIP/t0rAAAACcAAADKU9TLzJDxycMAAADyAAAAGBjbWFwwFIBcgAABCgAAABUaGVhZKsnTJ4AAAR8AAAANmhoZWEDHvxTAAAEtAAAACRobXR4AAAAAAAABNgAAAA4bWF4cAAOUAAAAAUQAAAABm5hbWX8Fq+xAAAFGAAAAfhwb3N0AAMAAAAABxAAAAAgAQAEAgABAQEMS0hQRkxFK01UU1kAAQEBOfgeAPgfAfggAvghA/gXBIv+Tvqn+bAFHQAAAMgPHQAAAL0QHQAAANsRHQAAACcdAAADARL4IAwWAAcBAQgUGx5TV19yYWRpY2FsY2lyY2xlY29weXJ0c2ltaWxhcjEuMUNvcHlyaWdodCAoQykgMTk5MiwgMTk5MyBUaGUgVGVYcGxvcmF0b3JzIENvcnBvcmF0aW9uTVRTWU1hdGhUaW1lAAAAAAkAAg0YQ0RmZ3AAAKYAqAGIAYkADAAeAFwAXgGHAAoCAAEAAwAWAFoAtgDxARcBNgGKAd4CDiAO93W9Ad/4+AP5TPd1Fb38+FkHDvfslp/3PtH3Pp8B9xjR9zDQ9zDRFPz4P/eAFfd193UFRQb7UvtS+1L3UgVFBvd1+3X7dvt1BdIG91L3UvdS+1IF0gYO+MT7ZbP5vLMBw7P5vLMD+kT3fxX3iPtc91z7iPuI+1z7XPuI+4j3XPtc94j3iPdc91z3iB78UPwoFft0+0f3SPd093T3R/dI93T3dPdI+0j7dPt0+0j7SPt0Hw73Zb33Br0Bw/kwA/ln+C8VT3o8Lz8hMvc4+xYbP0E/WncfQIwH3KLi0Mb3AuL7OPcUG9nc272ZH9IHDjig97O997SfAfgBvQP5aPd1Fb37yffIWfvI+8lZ98n7yL33yAcO9MP3JsMBw/kwA/lo98cVw/0wUwf5MPteFcP9MFMHDkX7SaD4JJ/4JJ8B9yXVA/dv9w0V0n6yPZwejQfZnZiy0hr3PAfQn7HSmx6WByRNd/sLH/tGB0t7bEZ5HtB4m2xLGvtFB/sMyXfyHpYHRJt3sdAaDkX7SaD4JJ/4JJ8B9yvVA/d19xwVy5uq0J4eRp17qssa90UH9wxNnyQegAfSe59lRhr7PAdEmGTZeh6JBz15fmREGvs8B0Z3ZUR7HoAH8smf9wsfDvgq/k6g99/k+LCfAcD5yAP4Kf5OFZUG+F76fQVWBvwe/fT7cffE+yz7KJp23dsFDnie+GWenJD3K54G+2WiBx4KBI8MCb0KvQufqQwMqZ8MDfmgFPhMFQAAAAAAAwIkAfQABQAAAooCuwAAAIwCigK7AAAB3wAxAQIAAAAABgAAAAAAAAAAAAABEAAAAAAAAAAAAAAAKjIxKgAAAEPgBwMc/EYAZAMcA7oAAAAAAAAAAAAAAAAAAABDAAMAAAABAAMAAQAAAAwABABIAAAACgAIAAIAAgBEAGcAcOAH//8AAABDAGYAcOAA////wv+h/5kAAAABAAAAAAAAAAQAAAABAAEAAgACAAMAAwAEAAQAAQAAAAAQAAAAAABfDzz1AAAD6AAAAACeC34nAAAAAJ4LficAAPxGD/8DHAAAABEAAAAAAAAAAAABAAADHPxGAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAOAAAAAAAUAPYAAQAAAAAAAAAQAAAAAQAAAAAAAQALABAAAQAAAAAAAgAHABsAAQAAAAAAAwAIACIAAQAAAAAABAALACoAAQAAAAAABQAMADUAAQAAAAAABgAAAEEAAQAAAAAABwAHAEEAAQAAAAAACAAHAEgAAQAAAAAACQAHAE8AAwABBAkAAAAgAFYAAwABBAkAAQAWAHYAAwABBAkAAgAOAIwAAwABBAkAAwAQAJoAAwABBAkABAAWAKoAAwABBAkABQAYAMAAAwABBAkABgAAANgAAwABBAkABwAOANgAAwABBAkACAAOAOYAAwABBAkACQAOAPRPcmlnaW5hbCBsaWNlbmNlS0hQRkxFK01UU1lVbmtub3dudW5pcXVlSURLSFBGTEUrTVRTWVZlcnNpb24gMC4xMVVua25vd25Vbmtub3duVW5rbm93bgBPAHIAaQBnAGkAbgBhAGwAIABsAGkAYwBlAG4AYwBlAEsASABQAEYATABFACsATQBUAFMAWQBVAG4AawBuAG8AdwBuAHUAbgBpAHEAdQBlAEkARABLAEgAUABGAEwARQArAE0AVABTAFkAVgBlAHIAcwBpAG8AbgAgADAALgAxADEAVQBuAGsAbgBvAHcAbgBVAG4AawBuAG8AdwBuAFUAbgBrAG4AbwB3AG4AAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
|
||||
);
|
||||
|
||||
describe("test harness testing", function () {
|
||||
it("returns output", async function () {
|
||||
const output = await ttx(font1_1);
|
||||
|
||||
verifyTtxOutput(output);
|
||||
expect(/<ttFont /.test(output)).toEqual(true);
|
||||
expect(/<\/ttFont>/.test(output)).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
45
test/font/font_fpgm_spec.js
Normal file
45
test/font/font_fpgm_spec.js
Normal file
File diff suppressed because one or more lines are too long
141
test/font/font_glyf_spec.js
Normal file
141
test/font/font_glyf_spec.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ttx, verifyTtxOutput } from "./fontutils.js";
|
||||
import { Font } from "../../src/core/fonts.js";
|
||||
import { Stream } from "../../src/core/stream.js";
|
||||
import { ToUnicodeMap } from "../../src/core/to_unicode_map.js";
|
||||
|
||||
// Minimal TrueType font: 4 glyphs (.notdef, space, A, B), OS/2 v1 / 86 bytes,
|
||||
// no hinting tables.
|
||||
const baseFont = Uint8Array.fromBase64(
|
||||
"AAEAAAAKAIAAAwAgT1MvMkTeRDYAAAEoAAAAVmNtYXAAdQBcAAABjAAAADxnbHlmmNLJuAAAAdQAAABKaGVhZC3Q8mwAAACsAAAANmhoZWEFFgH2AAAA5AAAACRobXR4AlgAAAAAAYAAAAAKbG9jYQAyACYAAAHIAAAACm1heHAABgAGAAABCAAAACBuYW1lAJlcyAAAAiAAAAA8cG9zdAAuACQAAAJcAAAAKgABAAAAAQAAfM/c718PPPUAAQPoAAAAAOYyVzYAAAAA5jJXNgAAAAACWAMgAAAAAwACAAAAAAAAAAEAAAMg/zgAAAJYAAAAZAH0AAEAAAAAAAAAAAAAAAAAAAABAAEAAAAEAAQAAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAQJYAZAABQAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAPz8/PwAAACAAQgMg/zgAAAMgAMgAAAAAAAAAAAAAAlgAAAAAAAAAAAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAAoAAAABgAEAAEAAgAgAEL//wAAACAAQf///+H/wQABAAAAAAAAAAAADQANABkAJQAAAAEAZAAAAlgDIAADAAAzIREhZAH0/gwDIAAAAQAAAAAB9AK8AAMAADEhESEB9P4MArwAAQAAAAAB9AK8AAMAADEhESEB9P4MArwAAAAAAAQANgABAAAAAAABAAEAAAABAAAAAAACAAEAAQADAAEECQABAAIAAgADAAEECQACAAIABFRSAFQAUgACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAADACQAJQAA"
|
||||
);
|
||||
|
||||
function clone(buf) {
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
|
||||
function readUint16(buf, pos) {
|
||||
return (buf[pos] << 8) | buf[pos + 1];
|
||||
}
|
||||
|
||||
function readUint32(buf, pos) {
|
||||
return (
|
||||
buf[pos] * 0x1000000 +
|
||||
((buf[pos + 1] << 16) | (buf[pos + 2] << 8) | buf[pos + 3])
|
||||
);
|
||||
}
|
||||
|
||||
function getTables(buf) {
|
||||
const tables = Object.create(null);
|
||||
const numTables = readUint16(buf, 4);
|
||||
for (let i = 0; i < numTables; i++) {
|
||||
const off = 12 + i * 16;
|
||||
const tag = String.fromCharCode(
|
||||
buf[off],
|
||||
buf[off + 1],
|
||||
buf[off + 2],
|
||||
buf[off + 3]
|
||||
);
|
||||
tables[tag] = {
|
||||
offset: readUint32(buf, off + 8),
|
||||
length: readUint32(buf, off + 12),
|
||||
};
|
||||
}
|
||||
return tables;
|
||||
}
|
||||
|
||||
function makeProperties(toUnicode) {
|
||||
return {
|
||||
loadedName: "font",
|
||||
type: "TrueType",
|
||||
differences: [],
|
||||
defaultEncoding: [],
|
||||
toUnicode,
|
||||
xHeight: 0,
|
||||
capHeight: 0,
|
||||
italicAngle: 0,
|
||||
firstChar: 0,
|
||||
lastChar: 255,
|
||||
};
|
||||
}
|
||||
|
||||
describe("font_glyf", function () {
|
||||
describe("Cyclic composite glyph 0", function () {
|
||||
it("removes a self-referencing composite glyph 0 (issue 21298)", async function () {
|
||||
const buggy = clone(baseFont);
|
||||
const tables = getTables(buggy);
|
||||
const headOff = tables.head.offset;
|
||||
const indexToLocFormat = readUint16(buggy, headOff + 50);
|
||||
const locaOff = tables.loca.offset;
|
||||
const glyf0 =
|
||||
indexToLocFormat === 0
|
||||
? readUint16(buggy, locaOff) * 2
|
||||
: readUint32(buggy, locaOff);
|
||||
const glyf0End =
|
||||
indexToLocFormat === 0
|
||||
? readUint16(buggy, locaOff + 2) * 2
|
||||
: readUint32(buggy, locaOff + 4);
|
||||
const pos = tables.glyf.offset + glyf0;
|
||||
buggy.fill(0, pos, tables.glyf.offset + glyf0End);
|
||||
buggy[pos] = 0xff;
|
||||
buggy[pos + 1] = 0xff;
|
||||
buggy[pos + 11] = 0x02;
|
||||
|
||||
const font = new Font(
|
||||
"font",
|
||||
new Stream(buggy),
|
||||
makeProperties(new ToUnicodeMap([])),
|
||||
{}
|
||||
);
|
||||
const output = await ttx(font.data);
|
||||
verifyTtxOutput(output);
|
||||
const notdef =
|
||||
/<TTGlyph[^>]*name="\.notdef"[^>]*\/>|<TTGlyph[^>]*name="\.notdef"[^>]*>([\s\S]*?)<\/TTGlyph>/.exec(
|
||||
output
|
||||
);
|
||||
expect(notdef).not.toBeNull();
|
||||
expect(notdef[1] || "").not.toMatch(
|
||||
/<component\b[^>]+glyphName="\.notdef"/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("OS/2 table length validation", function () {
|
||||
it("rewrites the OS/2 table when its length doesn't match the declared version", async function () {
|
||||
const buggy = clone(baseFont);
|
||||
const tables = getTables(buggy);
|
||||
const os2 = tables["OS/2"].offset;
|
||||
buggy[os2 + 62] = 0x00;
|
||||
buggy[os2 + 63] = 0x40;
|
||||
buggy[os2 + 1] = 0x03;
|
||||
|
||||
const font = new Font(
|
||||
"font",
|
||||
new Stream(buggy),
|
||||
makeProperties(new ToUnicodeMap([])),
|
||||
{}
|
||||
);
|
||||
const output = await ttx(font.data);
|
||||
verifyTtxOutput(output);
|
||||
expect(
|
||||
/<OS_2>\s*(?:<!--[\s\S]*?-->\s*)?<version value="3"\/>/.test(output)
|
||||
).toEqual(true);
|
||||
expect(/<sCapHeight\b/.test(output)).toEqual(true);
|
||||
expect(/<usMaxContext\b/.test(output)).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
71
test/font/font_os2_spec.js
Normal file
71
test/font/font_os2_spec.js
Normal file
File diff suppressed because one or more lines are too long
93
test/font/font_post_spec.js
Normal file
93
test/font/font_post_spec.js
Normal file
File diff suppressed because one or more lines are too long
24
test/font/font_test.html
Normal file
24
test/font/font_test.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>PDF.js font tests</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../../node_modules/jasmine-core/lib/jasmine-core/jasmine.css" />
|
||||
|
||||
<script src="../../node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
|
||||
<script src="../../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
|
||||
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"pdfjs/": "../../src/",
|
||||
"pdfjs-lib": "../../src/pdf.js",
|
||||
"pdfjs-web/": "../../web/",
|
||||
"pdfjs-test/": "../"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="jasmine-boot.js" type="module"></script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
35
test/font/fontutils.js
Normal file
35
test/font/fontutils.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/* Copyright 2013 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
async function ttx(data) {
|
||||
const response = await fetch("/ttx", {
|
||||
method: "POST",
|
||||
body: data.toBase64(),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.text();
|
||||
}
|
||||
|
||||
function verifyTtxOutput(output) {
|
||||
const m = /^<error>(.*?)<\/error>/.exec(output);
|
||||
if (m) {
|
||||
throw m[1];
|
||||
}
|
||||
}
|
||||
|
||||
export { ttx, verifyTtxOutput };
|
||||
108
test/font/jasmine-boot.js
Normal file
108
test/font/jasmine-boot.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/* Copyright 2016 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/*
|
||||
Copyright (c) 2008-2016 Pivotal Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
/* globals jasmineRequire */
|
||||
|
||||
// Modified jasmine's boot.js file to load PDF.js libraries async.
|
||||
|
||||
"use strict";
|
||||
|
||||
import { TestReporter } from "../reporter.js";
|
||||
|
||||
async function initializePDFJS(callback) {
|
||||
await Promise.all(
|
||||
[
|
||||
"pdfjs-test/font/font_core_spec.js",
|
||||
"pdfjs-test/font/font_glyf_spec.js",
|
||||
"pdfjs-test/font/font_os2_spec.js",
|
||||
"pdfjs-test/font/font_post_spec.js",
|
||||
"pdfjs-test/font/font_fpgm_spec.js",
|
||||
].map(moduleName => import(moduleName)) // eslint-disable-line no-unsanitized/method
|
||||
);
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
(function () {
|
||||
window.jasmine = jasmineRequire.core(jasmineRequire);
|
||||
|
||||
jasmineRequire.html(jasmine);
|
||||
|
||||
const env = jasmine.getEnv();
|
||||
|
||||
const jasmineInterface = jasmineRequire.interface(jasmine, env);
|
||||
extend(window, jasmineInterface);
|
||||
|
||||
// Runner Parameters
|
||||
const urls = new jasmine.HtmlReporterV2Urls();
|
||||
|
||||
env.configure(urls.configFromCurrentUrl());
|
||||
|
||||
// Reporters
|
||||
const htmlReporter = new jasmine.HtmlReporterV2({ env, urls });
|
||||
|
||||
env.addReporter(htmlReporter);
|
||||
|
||||
if (urls.queryString.getParam("browser")) {
|
||||
const testReporter = new TestReporter(urls.queryString.getParam("browser"));
|
||||
env.addReporter(testReporter);
|
||||
}
|
||||
|
||||
// Sets longer timeout.
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
|
||||
|
||||
function extend(destination, source) {
|
||||
for (const property in source) {
|
||||
destination[property] = source[property];
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
function fontTestInit() {
|
||||
initializePDFJS(function () {
|
||||
env.execute();
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
document.readyState === "interactive" ||
|
||||
document.readyState === "complete"
|
||||
) {
|
||||
fontTestInit();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", fontTestInit, true);
|
||||
}
|
||||
})();
|
||||
70
test/font/ttxdriver.mjs
Normal file
70
test/font/ttxdriver.mjs
Normal file
@@ -0,0 +1,70 @@
|
||||
/* Copyright 2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { spawn } from "child_process";
|
||||
|
||||
let ttxTaskId = Date.now();
|
||||
|
||||
function runTtx(fontPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ttx = spawn("ttx", [fontPath], { stdio: "ignore" });
|
||||
ttx.on("error", () => {
|
||||
reject(
|
||||
new Error(
|
||||
"Unable to execute `ttx`; make sure the `fonttools` dependency is installed"
|
||||
)
|
||||
);
|
||||
});
|
||||
ttx.on("close", () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function translateFont(content) {
|
||||
const buffer = Buffer.from(content, "base64");
|
||||
const taskId = (ttxTaskId++).toString();
|
||||
const fontPath = path.join(os.tmpdir(), `pdfjs-font-test-${taskId}.otf`);
|
||||
const resultPath = path.join(os.tmpdir(), `pdfjs-font-test-${taskId}.ttx`);
|
||||
|
||||
// Write the font data to a temporary file on disk (because TTX only accepts
|
||||
// files as input).
|
||||
fs.writeFileSync(fontPath, buffer);
|
||||
|
||||
// Run TTX on the temporary font file.
|
||||
let ttxError;
|
||||
try {
|
||||
await runTtx(fontPath);
|
||||
} catch (error) {
|
||||
ttxError = error;
|
||||
}
|
||||
|
||||
// Remove the temporary font/result files and report on the outcome.
|
||||
fs.unlinkSync(fontPath);
|
||||
if (ttxError) {
|
||||
throw ttxError;
|
||||
}
|
||||
if (!fs.existsSync(resultPath)) {
|
||||
throw new Error("TTX did not generate output");
|
||||
}
|
||||
const xml = fs.readFileSync(resultPath);
|
||||
fs.unlinkSync(resultPath);
|
||||
return xml;
|
||||
}
|
||||
|
||||
export { translateFont };
|
||||
BIN
test/images/firefox_logo.png
Normal file
BIN
test/images/firefox_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 169 KiB |
1
test/images/firefox_logo.svg
Normal file
1
test/images/firefox_logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 10 KiB |
BIN
test/images/red.png
Normal file
BIN
test/images/red.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
BIN
test/images/samplesignature.png
Normal file
BIN
test/images/samplesignature.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
610
test/integration/accessibility_spec.mjs
Normal file
610
test/integration/accessibility_spec.mjs
Normal file
@@ -0,0 +1,610 @@
|
||||
/* Copyright 2021 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
awaitPromise,
|
||||
closePages,
|
||||
loadAndWait,
|
||||
waitForPageRendered,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
const isStructTreeVisible = async page => {
|
||||
await page.waitForSelector(".structTree");
|
||||
return page.evaluate(() => {
|
||||
let elem = document.querySelector(".structTree");
|
||||
while (elem) {
|
||||
if (elem.getAttribute("aria-hidden") === "true") {
|
||||
return false;
|
||||
}
|
||||
elem = elem.parentElement;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
describe("accessibility", () => {
|
||||
describe("structure tree", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("structure_simple.pdf", ".structTree");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must build structure that maps to text layer", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
expect(await isStructTreeVisible(page))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeTrue();
|
||||
|
||||
// Check the headings match up.
|
||||
const head1 = await page.$eval(
|
||||
".structTree [role='heading'][aria-level='1'] span",
|
||||
el =>
|
||||
document.getElementById(el.getAttribute("aria-owns")).textContent
|
||||
);
|
||||
expect(head1).withContext(`In ${browserName}`).toEqual("Heading 1");
|
||||
const head2 = await page.$eval(
|
||||
".structTree [role='heading'][aria-level='2'] span",
|
||||
el =>
|
||||
document.getElementById(el.getAttribute("aria-owns")).textContent
|
||||
);
|
||||
expect(head2).withContext(`In ${browserName}`).toEqual("Heading 2");
|
||||
|
||||
// Check the order of the content.
|
||||
const texts = await page.$$eval(".structTree [aria-owns]", nodes =>
|
||||
nodes.map(
|
||||
el =>
|
||||
document.getElementById(el.getAttribute("aria-owns"))
|
||||
.textContent
|
||||
)
|
||||
);
|
||||
expect(texts)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual([
|
||||
"Heading 1",
|
||||
"This paragraph 1.",
|
||||
"Heading 2",
|
||||
"This paragraph 2.",
|
||||
]);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the struct tree is still there after zooming", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
expect(await isStructTreeVisible(page))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeTrue();
|
||||
|
||||
const handle = await waitForPageRendered(page);
|
||||
await page.click(`#zoom${i < 4 ? "In" : "Out"}Button`);
|
||||
await awaitPromise(handle);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Annotation", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey_a11y.pdf",
|
||||
".textLayer .endOfContent"
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
function getSpans(page) {
|
||||
return page.evaluate(() => {
|
||||
const elements = document.querySelectorAll(
|
||||
`.textLayer span[aria-owns]:not([role="presentation"])`
|
||||
);
|
||||
const results = [];
|
||||
for (const element of elements) {
|
||||
results.push(element.innerText);
|
||||
}
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
it("must check that some spans are linked to some annotations thanks to aria-owns", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const spanContents = await getSpans(page);
|
||||
|
||||
expect(spanContents)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(["Languages", "@intel.com", "Abstract", "Introduction"]);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Annotations order", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("fields_order.pdf", ".annotationLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the text fields are in the visual order", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const ids = await page.evaluate(() => {
|
||||
const elements = document.querySelectorAll(
|
||||
".annotationLayer .textWidgetAnnotation"
|
||||
);
|
||||
const results = [];
|
||||
for (const element of elements) {
|
||||
results.push(element.getAttribute("data-annotation-id"));
|
||||
}
|
||||
return results;
|
||||
});
|
||||
|
||||
expect(ids)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(["32R", "30R", "31R", "34R", "29R", "33R"]);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Stamp annotation accessibility", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tagged_stamp.pdf",
|
||||
".annotationLayer #pdfjs_internal_id_21R"
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check the id in aria-controls", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.waitForSelector(".annotationLayer");
|
||||
const stampId = "pdfjs_internal_id_20R";
|
||||
await page.click(`#${stampId}`);
|
||||
|
||||
const controlledId = await page.$eval(
|
||||
"#pdfjs_internal_id_21R",
|
||||
el => document.getElementById(el.getAttribute("aria-controls")).id
|
||||
);
|
||||
expect(controlledId)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(stampId);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check the aria-label linked to the stamp annotation", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.waitForSelector(".annotationLayer");
|
||||
|
||||
const ariaLabel = await page.$eval(
|
||||
".annotationLayer section[role='img']",
|
||||
el => el.getAttribute("aria-label")
|
||||
);
|
||||
expect(ariaLabel)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("Secondary text for stamp");
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the stamp annotation is linked to the struct tree", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.waitForSelector(".structTree");
|
||||
|
||||
const isLinkedToStampAnnotation = await page.$eval(
|
||||
".structTree [role='figure']",
|
||||
el =>
|
||||
document
|
||||
.getElementById(el.getAttribute("aria-owns"))
|
||||
.classList.contains("stampAnnotation")
|
||||
);
|
||||
expect(isLinkedToStampAnnotation)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Figure in the content stream", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug1708040.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that an image is correctly inserted in the text layer", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
expect(await isStructTreeVisible(page))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeTrue();
|
||||
|
||||
const spanId = await page.evaluate(() => {
|
||||
const el = document.querySelector(
|
||||
`.structTree span[role="figure"]`
|
||||
);
|
||||
return el.getAttribute("aria-owns") || null;
|
||||
});
|
||||
|
||||
expect(spanId).withContext(`In ${browserName}`).not.toBeNull();
|
||||
|
||||
const ariaLabel = await page.evaluate(id => {
|
||||
const img = document.querySelector(`#${id} > span[role="img"]`);
|
||||
return img.getAttribute("aria-label");
|
||||
}, spanId);
|
||||
|
||||
expect(ariaLabel)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("A logo of a fox and a globe");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("No undefined id", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("issue20102.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that span hasn't an 'undefined' id", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const id = await page.$eval("span.markedContent", span => span.id);
|
||||
expect(id).withContext(`In ${browserName}`).toBe("");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MathML in AF entry from LaTeX", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug1937438_af_from_latex.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the MathML is correctly inserted", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const mathML = await page.$eval(
|
||||
"span.structTree span[aria-owns='p58R_mc13'] > math",
|
||||
el => el?.innerHTML ?? ""
|
||||
);
|
||||
expect(mathML)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(
|
||||
` <msqrt><msup><mi>x</mi><mn>2</mn></msup></msqrt> <mo>=</mo> <mrow intent="absolute-value($x)"><mo>|</mo><mi arg="x">x</mi><mo>|</mo></mrow> `
|
||||
);
|
||||
|
||||
// Check that the math corresponding element is hidden in the text
|
||||
// layer.
|
||||
const ariaHidden = await page.$eval("span#p58R_mc13", el =>
|
||||
el.getAttribute("aria-hidden")
|
||||
);
|
||||
expect(ariaHidden).withContext(`In ${browserName}`).toEqual("true");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MathML with some attributes in AF entry from LaTeX", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug1997343.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the MathML is correctly inserted", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const mathML = await page.$eval(
|
||||
"span.structTree span[aria-owns='p21R_mc64']",
|
||||
el => el?.innerHTML ?? ""
|
||||
);
|
||||
expect(mathML)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(
|
||||
'<math display="block"> <msup> <mi>𝑛</mi> <mi>𝑝</mi> </msup> <mo lspace="0.278em" rspace="0.278em">=</mo> <mi>𝑛</mi> <mspace width="1.000em"></mspace> <mi> mod </mi> <mspace width="0.167em"></mspace> <mspace width="0.167em"></mspace> <mi>𝑝</mi> </math>'
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MathML tags in the struct tree", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug1937438_mml_from_latex.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the MathML is correctly inserted", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const mathML = await page.$eval(
|
||||
"span.structTree span[role='group'] span[role='group']:last-child > span math",
|
||||
el => el?.innerHTML ?? ""
|
||||
);
|
||||
expect(mathML)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(
|
||||
`<mi aria-owns="p76R_mc16">𝑐</mi><mo aria-owns="p76R_mc17">=</mo><msqrt><mrow><msup><mi aria-owns="p76R_mc18">𝑎</mi><mn aria-owns="p76R_mc19">2</mn></msup><mo aria-owns="p76R_mc20">+</mo><msup><mi aria-owns="p76R_mc21">𝑏</mi><mn aria-owns="p76R_mc22">2</mn></msup></mrow></msqrt>`
|
||||
);
|
||||
const ariaHidden = await page.$eval("span#p76R_mc16", el =>
|
||||
el.getAttribute("aria-hidden")
|
||||
);
|
||||
expect(ariaHidden).withContext(`In ${browserName}`).toEqual("true");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Artifacts must be aria-hidden", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug1937438_mml_from_latex.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that some artifacts are aria-hidden", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const parentSquareRootHidden = await page.evaluate(() => {
|
||||
for (const span of document.querySelectorAll(".textLayer span")) {
|
||||
if (span.textContent === "√") {
|
||||
return span.parentElement.getAttribute("aria-hidden");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
expect(parentSquareRootHidden)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("true");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("No alt-text with MathML", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug2004951.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that there's no alt-text on the MathML node", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const ariaLabel = await page.$eval(
|
||||
"span[aria-owns='p3R_mc2']",
|
||||
el => el.getAttribute("aria-label") || ""
|
||||
);
|
||||
expect(ariaLabel).withContext(`In ${browserName}`).toEqual("");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Text elements must be aria-hidden when there's MathML and annotations", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug2009627.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the text in text layer is aria-hidden", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const ariaHidden = await page.evaluate(() =>
|
||||
Array.from(
|
||||
document.querySelectorAll(".structTree :has(> math)")
|
||||
).map(el =>
|
||||
document
|
||||
.getElementById(el.getAttribute("aria-owns"))
|
||||
.getAttribute("aria-hidden")
|
||||
)
|
||||
);
|
||||
expect(ariaHidden)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(["true", "true", "true"]);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MathML in AF entry with struct tree children must not be duplicated", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug2025674.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the MathML is not duplicated in the struct tree", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
// The Formula node has both AF MathML and struct tree children.
|
||||
// When AF MathML is present, children must not be walked to avoid
|
||||
// rendering the math content twice in the accessibility tree.
|
||||
const mathCount = await page.evaluate(
|
||||
() => document.querySelectorAll(".structTree math").length
|
||||
);
|
||||
expect(mathCount).withContext(`In ${browserName}`).toBe(1);
|
||||
|
||||
// All text layer elements referenced by the formula subtree must
|
||||
// be aria-hidden so screen readers don't read both the MathML and
|
||||
// the underlying text content.
|
||||
const allHidden = await page.evaluate(() => {
|
||||
const ids = [];
|
||||
for (const el of document.querySelectorAll(
|
||||
".structTree [aria-owns]"
|
||||
)) {
|
||||
if (el.closest("math")) {
|
||||
ids.push(el.getAttribute("aria-owns"));
|
||||
}
|
||||
}
|
||||
// Also collect ids from the formula span itself.
|
||||
for (const el of document.querySelectorAll(
|
||||
".structTree span:has(> math)"
|
||||
)) {
|
||||
const owned = el.getAttribute("aria-owns");
|
||||
if (owned) {
|
||||
ids.push(owned);
|
||||
}
|
||||
}
|
||||
return ids.every(
|
||||
id =>
|
||||
document.getElementById(id)?.getAttribute("aria-hidden") ===
|
||||
"true"
|
||||
);
|
||||
});
|
||||
expect(allHidden).withContext(`In ${browserName}`).toBeTrue();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("A TH in a TR itself in a TBody is rowheader", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug2014080.pdf", ".textLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the table has the right structure", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
let elementRole = await page.evaluate(() =>
|
||||
Array.from(
|
||||
document.querySelector(".structTree [role='table']").children
|
||||
).map(child => child.getAttribute("role"))
|
||||
);
|
||||
|
||||
// THeader and TBody must be rowgroup.
|
||||
expect(elementRole)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(["rowgroup", "rowgroup"]);
|
||||
|
||||
elementRole = await page.evaluate(() =>
|
||||
Array.from(
|
||||
document.querySelector(
|
||||
".structTree [role='table'] > [role='rowgroup'] > [role='row']"
|
||||
).children
|
||||
).map(child => child.getAttribute("role"))
|
||||
);
|
||||
|
||||
// THeader has 3 columnheader.
|
||||
expect(elementRole)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(["columnheader", "columnheader", "columnheader"]);
|
||||
|
||||
elementRole = await page.evaluate(() =>
|
||||
Array.from(
|
||||
document.querySelector(
|
||||
".structTree [role='table'] > [role='rowgroup']:nth-child(2)"
|
||||
).children
|
||||
).map(child => child.getAttribute("role"))
|
||||
);
|
||||
|
||||
// TBody has 5 rows.
|
||||
expect(elementRole)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(["row", "row", "row", "row", "row"]);
|
||||
|
||||
elementRole = await page.evaluate(() =>
|
||||
Array.from(
|
||||
document.querySelector(
|
||||
".structTree [role='table'] > [role='rowgroup']:nth-child(2) > [role='row']:first-child"
|
||||
).children
|
||||
).map(child => child.getAttribute("role"))
|
||||
);
|
||||
// First row has a rowheader and 2 cells.
|
||||
expect(elementRole)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(["rowheader", "cell", "cell"]);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
969
test/integration/annotation_spec.mjs
Normal file
969
test/integration/annotation_spec.mjs
Normal file
@@ -0,0 +1,969 @@
|
||||
/* Copyright 2020 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
closePages,
|
||||
getAnnotationSelector,
|
||||
getQuerySelector,
|
||||
getRect,
|
||||
getSelector,
|
||||
loadAndWait,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
describe("Annotation highlight", () => {
|
||||
describe("annotation-highlight.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"annotation-highlight.pdf",
|
||||
getAnnotationSelector("19R")
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check the popup position in the DOM", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const highlightSelector = getAnnotationSelector("19R");
|
||||
const popupSelector = getAnnotationSelector("21R");
|
||||
const areSiblings = await page.evaluate(
|
||||
(highlightSel, popupSel) => {
|
||||
const highlight = document.querySelector(highlightSel);
|
||||
const popup = document.querySelector(popupSel);
|
||||
return highlight.nextElementSibling === popup;
|
||||
},
|
||||
highlightSelector,
|
||||
popupSelector
|
||||
);
|
||||
expect(areSiblings).withContext(`In ${browserName}`).toEqual(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must show a popup on mouseover", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
let hidden = await page.$eval(
|
||||
getAnnotationSelector("21R"),
|
||||
el => el.hidden
|
||||
);
|
||||
expect(hidden).withContext(`In ${browserName}`).toEqual(true);
|
||||
await page.hover(getAnnotationSelector("19R"));
|
||||
await page.waitForSelector(getAnnotationSelector("21R"), {
|
||||
visible: true,
|
||||
timeout: 0,
|
||||
});
|
||||
hidden = await page.$eval(
|
||||
getAnnotationSelector("21R"),
|
||||
el => el.hidden
|
||||
);
|
||||
expect(hidden).withContext(`In ${browserName}`).toEqual(false);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Check that widget annotations are in front of highlight ones", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug1883609.pdf", getAnnotationSelector("23R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must click on widget annotations", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
for (const i of [23, 22, 14]) {
|
||||
await page.click(getAnnotationSelector(`${i}R`));
|
||||
await page.waitForSelector(`#pdfjs_internal_id_${i}R:focus-within`);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Checkbox annotation", () => {
|
||||
describe("issue12706.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("issue12706.pdf", getAnnotationSelector("63R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must let checkboxes with the same name behave like radio buttons", async () => {
|
||||
const selectors = [63, 70, 79].map(n => getAnnotationSelector(`${n}R`));
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
for (const selector of selectors) {
|
||||
await page.click(selector);
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector} > :first-child').checked`
|
||||
);
|
||||
|
||||
for (const otherSelector of selectors) {
|
||||
const checked = await page.$eval(
|
||||
`${otherSelector} > :first-child`,
|
||||
el => el.checked
|
||||
);
|
||||
expect(checked)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(selector === otherSelector);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("issue15597.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("issue15597.pdf", getAnnotationSelector("7R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check the checkbox", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const selector = getAnnotationSelector("7R");
|
||||
await page.click(selector);
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector} > :first-child').checked`
|
||||
);
|
||||
expect(true).withContext(`In ${browserName}`).toEqual(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bug1847733.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug1847733.pdf", getAnnotationSelector("18R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check the checkbox", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const selectors = [18, 30, 42, 54].map(id =>
|
||||
getAnnotationSelector(`${id}R`)
|
||||
);
|
||||
for (const selector of selectors) {
|
||||
await page.click(selector);
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector} > :first-child').checked`
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Text widget", () => {
|
||||
describe("issue13271.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("issue13271.pdf", getAnnotationSelector("24R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must update all the fields with the same value", async () => {
|
||||
const base = "hello world";
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.type(getSelector("25R"), base);
|
||||
await page.waitForFunction(`${getQuerySelector("24R")}.value !== ""`);
|
||||
await page.waitForFunction(`${getQuerySelector("26R")}.value !== ""`);
|
||||
|
||||
let text = await page.$eval(getSelector("24R"), el => el.value);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual(base);
|
||||
|
||||
text = await page.$eval(getSelector("26R"), el => el.value);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual(base);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("issue16473.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("issue16473.pdf", getAnnotationSelector("22R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must reset a formatted value after a change", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.type(getSelector("22R"), "a");
|
||||
await page.keyboard.press("Tab");
|
||||
await page.waitForFunction(
|
||||
`${getQuerySelector("22R")}.value !== "Hello world"`
|
||||
);
|
||||
|
||||
const text = await page.$eval(getSelector("22R"), el => el.value);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual("aHello World");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Link annotations with internal destinations", () => {
|
||||
describe("bug1708041.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"bug1708041.pdf",
|
||||
".page[data-page-number='1'] .annotationLayer"
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must click on a link and check if it navigates to the correct page", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const pageOneSelector = ".page[data-page-number='1']";
|
||||
const linkSelector = `${pageOneSelector} #pdfjs_internal_id_42R`;
|
||||
await page.waitForSelector(linkSelector);
|
||||
const linkTitle = await page.$eval(linkSelector, el => el.title);
|
||||
expect(linkTitle)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("Go to the last page");
|
||||
await page.click(linkSelector);
|
||||
const pageSixTextLayerSelector =
|
||||
".page[data-page-number='6'] .textLayer";
|
||||
await page.waitForSelector(pageSixTextLayerSelector, {
|
||||
visible: true,
|
||||
});
|
||||
await page.waitForFunction(
|
||||
sel => {
|
||||
const textLayer = document.querySelector(sel);
|
||||
return document.activeElement === textLayer;
|
||||
},
|
||||
{},
|
||||
pageSixTextLayerSelector
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Annotation and storage", () => {
|
||||
describe("issue14023.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("issue14023.pdf", getAnnotationSelector("64R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must let checkboxes with the same name behave like radio buttons", async () => {
|
||||
const text1 = "hello world!";
|
||||
const text2 = "!dlrow olleh";
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
// Text field.
|
||||
await page.type(getSelector("64R"), text1);
|
||||
// Checkbox.
|
||||
await page.click(getAnnotationSelector("65R"));
|
||||
// Radio.
|
||||
await page.click(getAnnotationSelector("67R"));
|
||||
|
||||
for (const [pageNumber, textId, checkId, radio1Id, radio2Id] of [
|
||||
[2, "18R", "19R", "21R", "20R"],
|
||||
[5, "23R", "24R", "22R", "25R"],
|
||||
]) {
|
||||
await page.evaluate(n => {
|
||||
window.document
|
||||
.querySelectorAll(`[data-page-number="${n}"][class="page"]`)[0]
|
||||
.scrollIntoView();
|
||||
}, pageNumber);
|
||||
|
||||
// Need to wait to have a displayed text input.
|
||||
await page.waitForSelector(getSelector(textId), {
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
const text = await page.$eval(getSelector(textId), el => el.value);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual(text1);
|
||||
|
||||
let checked = await page.$eval(
|
||||
getSelector(checkId),
|
||||
el => el.checked
|
||||
);
|
||||
expect(checked).toEqual(true);
|
||||
|
||||
checked = await page.$eval(getSelector(radio1Id), el => el.checked);
|
||||
expect(checked).toEqual(false);
|
||||
|
||||
checked = await page.$eval(getSelector(radio2Id), el => el.checked);
|
||||
expect(checked).toEqual(false);
|
||||
}
|
||||
|
||||
// Change data on page 5 and check that other pages changed.
|
||||
// Text field.
|
||||
await page.type(getSelector("23R"), text2);
|
||||
// Checkbox.
|
||||
await page.click(getAnnotationSelector("24R"));
|
||||
// Radio.
|
||||
await page.click(getAnnotationSelector("25R"));
|
||||
|
||||
for (const [pageNumber, textId, checkId, radio1Id, radio2Id] of [
|
||||
[1, "64R", "65R", "67R", "68R"],
|
||||
[2, "18R", "19R", "21R", "20R"],
|
||||
]) {
|
||||
await page.evaluate(n => {
|
||||
window.document
|
||||
.querySelectorAll(`[data-page-number="${n}"][class="page"]`)[0]
|
||||
.scrollIntoView();
|
||||
}, pageNumber);
|
||||
|
||||
// Need to wait to have a displayed text input.
|
||||
await page.waitForSelector(getSelector(textId), {
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
const text = await page.$eval(getSelector(textId), el => el.value);
|
||||
expect(text)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(text2 + text1);
|
||||
|
||||
let checked = await page.$eval(
|
||||
getSelector(checkId),
|
||||
el => el.checked
|
||||
);
|
||||
expect(checked).toEqual(false);
|
||||
|
||||
checked = await page.$eval(getSelector(radio1Id), el => el.checked);
|
||||
expect(checked).toEqual(false);
|
||||
|
||||
checked = await page.$eval(getSelector(radio2Id), el => el.checked);
|
||||
expect(checked).toEqual(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ResetForm action", () => {
|
||||
describe("resetform.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("resetform.pdf", getAnnotationSelector("63R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must reset all fields", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const base = "hello world";
|
||||
for (let i = 63; i <= 67; i++) {
|
||||
await page.type(getSelector(`${i}R`), base);
|
||||
}
|
||||
|
||||
const selectors = [69, 71, 75].map(n =>
|
||||
getAnnotationSelector(`${n}R`)
|
||||
);
|
||||
for (const selector of selectors) {
|
||||
await page.click(selector);
|
||||
}
|
||||
|
||||
await page.select(getSelector("78R"), "b");
|
||||
await page.select(getSelector("81R"), "f");
|
||||
|
||||
await page.click(getAnnotationSelector("82R"));
|
||||
await page.waitForFunction(`${getQuerySelector("63R")}.value === ""`);
|
||||
|
||||
for (let i = 63; i <= 68; i++) {
|
||||
const text = await page.$eval(getSelector(`${i}R`), el => el.value);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual("");
|
||||
}
|
||||
|
||||
const ids = [69, 71, 72, 73, 74, 75, 76, 77];
|
||||
for (const id of ids) {
|
||||
const checked = await page.$eval(
|
||||
getSelector(`${id}R`),
|
||||
el => el.checked
|
||||
);
|
||||
expect(checked).withContext(`In ${browserName}`).toEqual(false);
|
||||
}
|
||||
|
||||
let selected = await page.$eval(
|
||||
`${getSelector("78R")} [value="a"]`,
|
||||
el => el.selected
|
||||
);
|
||||
expect(selected).withContext(`In ${browserName}`).toEqual(true);
|
||||
|
||||
selected = await page.$eval(
|
||||
`${getSelector("81R")} [value="d"]`,
|
||||
el => el.selected
|
||||
);
|
||||
expect(selected).withContext(`In ${browserName}`).toEqual(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must reset some fields", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const base = "hello world";
|
||||
for (let i = 63; i <= 68; i++) {
|
||||
await page.type(getSelector(`${i}R`), base);
|
||||
}
|
||||
|
||||
const selectors = [69, 71, 72, 73, 75].map(n =>
|
||||
getAnnotationSelector(`${n}R`)
|
||||
);
|
||||
for (const selector of selectors) {
|
||||
await page.click(selector);
|
||||
}
|
||||
|
||||
await page.select(getSelector("78R"), "b");
|
||||
await page.select(getSelector("81R"), "f");
|
||||
|
||||
await page.click(getAnnotationSelector("84R"));
|
||||
await page.waitForFunction(`${getQuerySelector("63R")}.value === ""`);
|
||||
|
||||
for (let i = 63; i <= 68; i++) {
|
||||
const expected = (i - 3) % 2 === 0 ? "" : base;
|
||||
const text = await page.$eval(getSelector(`${i}R`), el => el.value);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual(expected);
|
||||
}
|
||||
|
||||
let ids = [69, 72, 73, 74, 76, 77];
|
||||
for (const id of ids) {
|
||||
const checked = await page.$eval(
|
||||
getSelector(`${id}R`),
|
||||
el => el.checked
|
||||
);
|
||||
expect(checked)
|
||||
.withContext(`In ${browserName + id}`)
|
||||
.toEqual(false);
|
||||
}
|
||||
|
||||
ids = [71, 75];
|
||||
for (const id of ids) {
|
||||
const checked = await page.$eval(
|
||||
getSelector(`${id}R`),
|
||||
el => el.checked
|
||||
);
|
||||
expect(checked).withContext(`In ${browserName}`).toEqual(true);
|
||||
}
|
||||
|
||||
let selected = await page.$eval(
|
||||
`${getSelector("78R")} [value="a"]`,
|
||||
el => el.selected
|
||||
);
|
||||
expect(selected).withContext(`In ${browserName}`).toEqual(true);
|
||||
|
||||
selected = await page.$eval(
|
||||
`${getSelector("81R")} [value="f"]`,
|
||||
el => el.selected
|
||||
);
|
||||
expect(selected).withContext(`In ${browserName}`).toEqual(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("FreeText widget", () => {
|
||||
describe("issue14438.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"issue14438.pdf",
|
||||
getAnnotationSelector("10R")
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the FreeText annotation has a popup", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const selector = getAnnotationSelector("10R");
|
||||
await page.click(selector);
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector}').hidden === false`
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ink widget and its popup after editing", () => {
|
||||
describe("annotation-caret-ink.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"annotation-caret-ink.pdf",
|
||||
getAnnotationSelector("25R")
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the Ink annotation has a popup", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const selector = getAnnotationSelector("25R");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector}').hidden === false`
|
||||
);
|
||||
await page.click("#editorFreeText");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector}').hidden === true`
|
||||
);
|
||||
await page.click("#editorFreeText");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector}').hidden === false`
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Don't use AP when /NeedAppearances is true", () => {
|
||||
describe("bug1844583.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"bug1844583.pdf",
|
||||
getAnnotationSelector("8R")
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check the content of the text field", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const text = await page.$eval(getSelector("8R"), el => el.value);
|
||||
expect(text)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("Hello World");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Toggle popup with keyboard", () => {
|
||||
describe("tagged_stamp.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tagged_stamp.pdf",
|
||||
getAnnotationSelector("20R")
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the popup has the correct visibility", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const selector = getAnnotationSelector("21R");
|
||||
let hidden = await page.$eval(selector, el => el.hidden);
|
||||
expect(hidden).withContext(`In ${browserName}`).toEqual(true);
|
||||
|
||||
await page.focus(getAnnotationSelector("20R"));
|
||||
await page.keyboard.press("Enter");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector}').hidden !== true`
|
||||
);
|
||||
hidden = await page.$eval(selector, el => el.hidden);
|
||||
expect(hidden).withContext(`In ${browserName}`).toEqual(false);
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector}').hidden !== false`
|
||||
);
|
||||
hidden = await page.$eval(selector, el => el.hidden);
|
||||
expect(hidden).withContext(`In ${browserName}`).toEqual(true);
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector}').hidden !== true`
|
||||
);
|
||||
hidden = await page.$eval(selector, el => el.hidden);
|
||||
expect(hidden).withContext(`In ${browserName}`).toEqual(false);
|
||||
|
||||
await page.keyboard.press("Escape");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${selector}').hidden !== false`
|
||||
);
|
||||
hidden = await page.$eval(selector, el => el.hidden);
|
||||
expect(hidden).withContext(`In ${browserName}`).toEqual(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Annotation with empty popup and aria", () => {
|
||||
describe("issue14438.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"highlights.pdf",
|
||||
getAnnotationSelector("693R")
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the highlight annotation has no popup and no aria-haspopup attribute", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const highlightSelector = getAnnotationSelector("693R");
|
||||
const popupSelector = getAnnotationSelector("694R");
|
||||
await page.waitForFunction(
|
||||
// No aria-haspopup attribute,
|
||||
`document.querySelector('${highlightSelector}').ariaHasPopup === null ` +
|
||||
// and no popup.
|
||||
`&& document.querySelector('${popupSelector}') === null`
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Rotated annotation and its clickable area", () => {
|
||||
describe("rotated_ink.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"rotated_ink.pdf",
|
||||
getAnnotationSelector("18R")
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the clickable area has been rotated", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const rect = await getRect(page, getAnnotationSelector("18R"));
|
||||
const promisePopup = page.waitForSelector(
|
||||
getAnnotationSelector("19R"),
|
||||
{ visible: true }
|
||||
);
|
||||
await page.mouse.move(
|
||||
rect.x + rect.width * 0.1,
|
||||
rect.y + rect.height * 0.9
|
||||
);
|
||||
await promisePopup;
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Text under some annotations", () => {
|
||||
describe("bug1885505.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"bug1885505.pdf",
|
||||
":is(" +
|
||||
[56, 58, 60, 65]
|
||||
.map(id => getAnnotationSelector(`${id}R`))
|
||||
.join(", ") +
|
||||
")"
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the text under a highlight annotation exist in the DOM", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const text = await page.$eval(
|
||||
`${getAnnotationSelector("56R")} mark`,
|
||||
el => el.textContent
|
||||
);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual("Languages");
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the text under an underline annotation exist in the DOM", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const text = await page.$eval(
|
||||
`${getAnnotationSelector("58R")} u`,
|
||||
el => el.textContent
|
||||
);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual("machine");
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the text under a squiggly annotation exist in the DOM", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const text = await page.$eval(
|
||||
`${getAnnotationSelector("60R")} u`,
|
||||
el => el.textContent
|
||||
);
|
||||
expect(text).withContext(`In ${browserName}`)
|
||||
.toEqual(`paths through nested loops. We have implemented
|
||||
a dynamic compiler for JavaScript based on our`);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the text under a strikeout annotation exist in the DOM", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const text = await page.$eval(
|
||||
`${getAnnotationSelector("65R")} s`,
|
||||
el => el.textContent
|
||||
);
|
||||
expect(text)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("Experimentation,");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Annotation without popup and enableComment set to true", () => {
|
||||
describe("annotation-text-without-popup.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"annotation-text-without-popup.pdf",
|
||||
getAnnotationSelector("4R"),
|
||||
"page-fit",
|
||||
null,
|
||||
{ enableComment: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the popup is shown", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const rect = await getRect(page, getAnnotationSelector("4R"));
|
||||
|
||||
// Hover the annotation, the popup should be visible.
|
||||
let promisePopup = page.waitForSelector("#commentPopup", {
|
||||
visible: true,
|
||||
});
|
||||
await page.mouse.move(
|
||||
rect.x + rect.width / 2,
|
||||
rect.y + rect.height / 2
|
||||
);
|
||||
await promisePopup;
|
||||
|
||||
// Move the mouse away, the popup should be hidden.
|
||||
promisePopup = page.waitForSelector("#commentPopup", {
|
||||
visible: false,
|
||||
});
|
||||
await page.mouse.move(
|
||||
rect.x - rect.width / 2,
|
||||
rect.y - rect.height / 2
|
||||
);
|
||||
await promisePopup;
|
||||
|
||||
// Click the annotation, the popup should be visible.
|
||||
promisePopup = page.waitForSelector("#commentPopup", {
|
||||
visible: true,
|
||||
});
|
||||
await page.mouse.click(
|
||||
rect.x + rect.width / 2,
|
||||
rect.y + rect.height / 2
|
||||
);
|
||||
await promisePopup;
|
||||
|
||||
// Click again, the popup should be hidden.
|
||||
promisePopup = page.waitForSelector("#commentPopup", {
|
||||
visible: false,
|
||||
});
|
||||
await page.mouse.click(
|
||||
rect.x + rect.width / 2,
|
||||
rect.y + rect.height / 2
|
||||
);
|
||||
await promisePopup;
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Annotation order in the DOM", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"comments.pdf",
|
||||
".page[data-page-number='1'] .annotationLayer #pdfjs_internal_id_661R"
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that annotations are in the visual order", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const sectionIds = await page.evaluate(() =>
|
||||
[
|
||||
...document.querySelectorAll(
|
||||
".page[data-page-number='1'] .annotationLayer > section:not(.popupAnnotation)"
|
||||
),
|
||||
].map(el => el.id.split("_").pop())
|
||||
);
|
||||
expect(sectionIds)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual([
|
||||
"612R",
|
||||
"693R",
|
||||
"687R",
|
||||
"690R",
|
||||
"713R",
|
||||
"673R",
|
||||
"613R",
|
||||
"680R",
|
||||
"661R",
|
||||
]);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bug 2026037", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("bug2026037.pdf", getAnnotationSelector("22R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that spaces in a choice option display value are preserved", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
// The option's displayValue contains multiple consecutive spaces
|
||||
// ("A B"). Browsers collapse spaces in textContent, so the
|
||||
// fix stores the original in a "display-value" attribute and uses
|
||||
// non-breaking spaces (\u00A0) in textContent.
|
||||
const displayAttr = await page.$eval(
|
||||
`${getSelector("22R")} option`,
|
||||
el => el.getAttribute("display-value")
|
||||
);
|
||||
expect(displayAttr)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("A B");
|
||||
|
||||
const textContent = await page.$eval(
|
||||
`${getSelector("22R")} option`,
|
||||
el => el.textContent
|
||||
);
|
||||
expect(textContent)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("A\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0B");
|
||||
|
||||
const exportValue = await page.$eval(
|
||||
`${getSelector("22R")} option`,
|
||||
el => el.value
|
||||
);
|
||||
expect(exportValue)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("a b");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
262
test/integration/autolinker_spec.mjs
Normal file
262
test/integration/autolinker_spec.mjs
Normal file
@@ -0,0 +1,262 @@
|
||||
/* Copyright 2025 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
awaitPromise,
|
||||
closePages,
|
||||
createPromise,
|
||||
loadAndWait,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
function waitForLinkAnnotations(page, pageNumber) {
|
||||
return page.evaluateHandle(
|
||||
number => [
|
||||
new Promise(resolve => {
|
||||
const { eventBus } = window.PDFViewerApplication;
|
||||
eventBus.on("linkannotationsadded", function listener(e) {
|
||||
if (number === undefined || e.pageNumber === number) {
|
||||
resolve();
|
||||
eventBus.off("linkannotationsadded", listener);
|
||||
}
|
||||
});
|
||||
}),
|
||||
],
|
||||
pageNumber
|
||||
);
|
||||
}
|
||||
|
||||
function recordInitialLinkAnnotationsEvent(eventBus) {
|
||||
globalThis.initialLinkAnnotationsEventFired = false;
|
||||
eventBus.on(
|
||||
"linkannotationsadded",
|
||||
() => {
|
||||
globalThis.initialLinkAnnotationsEventFired = true;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
function waitForInitialLinkAnnotations(page) {
|
||||
return createPromise(page, resolve => {
|
||||
if (globalThis.initialLinkAnnotationsEventFired) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
window.PDFViewerApplication.eventBus.on("linkannotationsadded", resolve, {
|
||||
once: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("autolinker", function () {
|
||||
describe("bug1019475_2.pdf", function () {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"bug1019475_2.pdf",
|
||||
".annotationLayer",
|
||||
null,
|
||||
null,
|
||||
{
|
||||
enableAutoLinking: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must appropriately add link annotations when relevant", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await waitForLinkAnnotations(page);
|
||||
const url = await page.$$eval(
|
||||
".annotationLayer > .linkAnnotation > a",
|
||||
annotations => annotations.map(a => a.href)
|
||||
);
|
||||
expect(url.length).withContext(`In ${browserName}`).toEqual(1);
|
||||
expect(url[0])
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("http://www.mozilla.org/");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bug1019475_1.pdf", function () {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"bug1019475_1.pdf",
|
||||
".annotationLayer",
|
||||
null,
|
||||
null,
|
||||
{
|
||||
enableAutoLinking: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must not add links when unnecessary", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await waitForLinkAnnotations(page);
|
||||
const linkIds = await page.$$eval(
|
||||
".annotationLayer > .linkAnnotation > a",
|
||||
annotations =>
|
||||
annotations.map(a => a.getAttribute("data-element-id"))
|
||||
);
|
||||
expect(linkIds.length).withContext(`In ${browserName}`).toEqual(3);
|
||||
linkIds.forEach(id =>
|
||||
expect(id)
|
||||
.withContext(`In ${browserName}`)
|
||||
.not.toContain("inferred_link_")
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pr19449.pdf", function () {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("pr19449.pdf", ".annotationLayer", null, null, {
|
||||
docBaseUrl: "http://example.com",
|
||||
enableAutoLinking: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must not add links that overlap even if the URLs are different", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await waitForLinkAnnotations(page);
|
||||
const linkIds = await page.$$eval(
|
||||
".annotationLayer > .linkAnnotation > a",
|
||||
annotations =>
|
||||
annotations.map(a => a.getAttribute("data-element-id"))
|
||||
);
|
||||
expect(linkIds.length).withContext(`In ${browserName}`).toEqual(1);
|
||||
linkIds.forEach(id =>
|
||||
expect(id)
|
||||
.withContext(`In ${browserName}`)
|
||||
.not.toContain("inferred_link_")
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PR 19470", function () {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"bug1019475_2.pdf",
|
||||
".annotationLayer",
|
||||
null,
|
||||
null,
|
||||
{
|
||||
enableAutoLinking: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must not repeatedly add link annotations redundantly", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await waitForLinkAnnotations(page);
|
||||
let url = await page.$$eval(
|
||||
".annotationLayer > .linkAnnotation > a",
|
||||
annotations => annotations.map(a => a.href)
|
||||
);
|
||||
expect(url.length).withContext(`In ${browserName}`).toEqual(1);
|
||||
|
||||
await page.evaluate(() =>
|
||||
window.PDFViewerApplication.pdfViewer.updateScale({
|
||||
drawingDelay: -1,
|
||||
scaleFactor: 2,
|
||||
})
|
||||
);
|
||||
await waitForLinkAnnotations(page);
|
||||
url = await page.$$eval(
|
||||
".annotationLayer > .linkAnnotation > a",
|
||||
annotations => annotations.map(a => a.href)
|
||||
);
|
||||
expect(url.length).withContext(`In ${browserName}`).toEqual(1);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when highlighting search results", function () {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"issue3115r.pdf",
|
||||
".annotationLayer",
|
||||
null,
|
||||
{ eventBusSetup: recordInitialLinkAnnotationsEvent },
|
||||
{ enableAutoLinking: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must find links that overlap with search results", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await awaitPromise(await waitForInitialLinkAnnotations(page));
|
||||
|
||||
const linkAnnotationsPromise = await waitForLinkAnnotations(page, 36);
|
||||
|
||||
// Search for "rich.edu"
|
||||
await page.click("#viewFindButton");
|
||||
await page.waitForSelector("#viewFindButton", { hidden: false });
|
||||
await page.type("#findInput", "rich.edu");
|
||||
await page.waitForSelector(".textLayer .highlight");
|
||||
|
||||
await awaitPromise(linkAnnotationsPromise);
|
||||
|
||||
const urls = await page.$$eval(
|
||||
".page[data-page-number='36'] > .annotationLayer > .linkAnnotation > a",
|
||||
annotations => annotations.map(a => a.href)
|
||||
);
|
||||
|
||||
expect(urls)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toContain(jasmine.stringContaining("rich.edu"));
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
94
test/integration/caret_browsing_spec.mjs
Normal file
94
test/integration/caret_browsing_spec.mjs
Normal file
@@ -0,0 +1,94 @@
|
||||
/* Copyright 2021 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
closePages,
|
||||
getRect,
|
||||
loadAndWait,
|
||||
waitForSelectionChange,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
describe("Caret browsing", () => {
|
||||
describe("Selection", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", ".textLayer .endOfContent");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must move the caret down and check the selection", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const spanRect = await getRect(
|
||||
page,
|
||||
`.page[data-page-number="1"] > .textLayer > span`
|
||||
);
|
||||
await page.mouse.click(
|
||||
spanRect.x + 1,
|
||||
spanRect.y + spanRect.height / 2,
|
||||
{ count: 2 }
|
||||
);
|
||||
await page.keyboard.down("Shift");
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await page.keyboard.press("ArrowRight");
|
||||
}
|
||||
await page.keyboard.up("Shift");
|
||||
await waitForSelectionChange(page, "Trace-based");
|
||||
|
||||
await page.keyboard.down("Shift");
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.keyboard.up("Shift");
|
||||
|
||||
// The caret is just before Languages.
|
||||
await waitForSelectionChange(
|
||||
page,
|
||||
"Trace-based Just-in-Time Type Specialization for Dynamic\n"
|
||||
);
|
||||
|
||||
await page.keyboard.down("Shift");
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.keyboard.up("Shift");
|
||||
|
||||
// The caret is just before Mike Shaver.
|
||||
await waitForSelectionChange(
|
||||
page,
|
||||
"Trace-based Just-in-Time Type Specialization for Dynamic\nLanguages\nAndreas Gal∗+, Brendan Eich∗, "
|
||||
);
|
||||
|
||||
await page.keyboard.down("Shift");
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await page.keyboard.up("Shift");
|
||||
|
||||
// The caret is just before Languages.
|
||||
await waitForSelectionChange(
|
||||
page,
|
||||
"Trace-based Just-in-Time Type Specialization for Dynamic\n"
|
||||
);
|
||||
|
||||
await page.keyboard.down("Shift");
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await page.keyboard.up("Shift");
|
||||
|
||||
// The caret is in the middle of Time.
|
||||
await waitForSelectionChange(page, "Trace-based Just-in-Tim");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
1270
test/integration/comment_spec.mjs
Normal file
1270
test/integration/comment_spec.mjs
Normal file
File diff suppressed because it is too large
Load Diff
181
test/integration/copy_paste_spec.mjs
Normal file
181
test/integration/copy_paste_spec.mjs
Normal file
@@ -0,0 +1,181 @@
|
||||
/* Copyright 2023 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
closePages,
|
||||
copy,
|
||||
kbSelectAll,
|
||||
loadAndWait,
|
||||
mockClipboard,
|
||||
waitForEvent,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
const selectAll = async page => {
|
||||
await waitForEvent({
|
||||
page,
|
||||
eventName: "selectionchange",
|
||||
action: () => kbSelectAll(page),
|
||||
});
|
||||
|
||||
await page.waitForFunction(() => {
|
||||
const selection = document.getSelection();
|
||||
const hiddenCopyElement = document.getElementById("hiddenCopyElement");
|
||||
return selection.containsNode(hiddenCopyElement);
|
||||
});
|
||||
};
|
||||
|
||||
describe("Copy and paste", () => {
|
||||
describe("all text", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#hiddenCopyElement", 100);
|
||||
await mockClipboard(pages);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that we've all the contents on copy/paste", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.waitForSelector(
|
||||
".page[data-page-number='1'] .textLayer .endOfContent"
|
||||
);
|
||||
await selectAll(page);
|
||||
|
||||
await copy(page);
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('#viewerContainer').style.cursor !== "wait"`
|
||||
);
|
||||
|
||||
await page.waitForFunction(
|
||||
async () =>
|
||||
!!(await navigator.clipboard.readText())?.includes(
|
||||
"Dynamic languages such as JavaScript"
|
||||
)
|
||||
);
|
||||
const text = await page.evaluate(() =>
|
||||
navigator.clipboard.readText()
|
||||
);
|
||||
|
||||
expect(
|
||||
text.includes("This section provides an overview of our system")
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
expect(
|
||||
text.includes(
|
||||
"are represented by function calls. This makes the LIR used by"
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
expect(
|
||||
text.includes("When compiling loops, we consult the oracle before")
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
expect(text.includes("Nested Trace Tree Formation"))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
expect(
|
||||
text.includes(
|
||||
"An important detail is that the call to the inner trace"
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
expect(text.includes("When trace recording is completed, nanojit"))
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
expect(
|
||||
text.includes(
|
||||
"SpiderMonkey, like many VMs, needs to preempt the user program"
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
expect(
|
||||
text.includes(
|
||||
"Using similar computations, we find that trace recording takes"
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
expect(
|
||||
text.includes(
|
||||
"specialization algorithm. We also described our trace compiler"
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
expect(
|
||||
text.includes(
|
||||
"dynamic optimization system. In Proceedings of the ACM SIGPLAN"
|
||||
)
|
||||
)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("Copy/paste and ligatures", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"copy_paste_ligatures.pdf",
|
||||
"#hiddenCopyElement",
|
||||
100
|
||||
);
|
||||
await mockClipboard(pages);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the ligatures have been removed when the text has been copied", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.waitForSelector(
|
||||
".page[data-page-number='1'] .textLayer .endOfContent"
|
||||
);
|
||||
await selectAll(page);
|
||||
|
||||
await copy(page);
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('#viewerContainer').style.cursor !== "wait"`
|
||||
);
|
||||
|
||||
await page.waitForFunction(
|
||||
async () => !!(await navigator.clipboard.readText())
|
||||
);
|
||||
|
||||
const text = await page.evaluate(() =>
|
||||
navigator.clipboard.readText()
|
||||
);
|
||||
|
||||
expect(text)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual("abcdeffffiflffifflſtstghijklmno");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
134
test/integration/cursor_tools_spec.mjs
Normal file
134
test/integration/cursor_tools_spec.mjs
Normal file
@@ -0,0 +1,134 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
closePages,
|
||||
getRect,
|
||||
loadAndWait,
|
||||
waitForSelectionChange,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
async function enableSelectTool(page) {
|
||||
await page.click("#secondaryToolbarToggleButton");
|
||||
await page.waitForSelector("#secondaryToolbar", { hidden: false });
|
||||
|
||||
await page.click("#cursorSelectTool");
|
||||
await page.waitForFunction(
|
||||
"window.PDFViewerApplication.pdfCursorTools.activeTool === 0"
|
||||
);
|
||||
}
|
||||
|
||||
async function enableHandTool(page) {
|
||||
await page.click("#secondaryToolbarToggleButton");
|
||||
await page.waitForSelector("#secondaryToolbar", { hidden: false });
|
||||
|
||||
await page.click("#cursorHandTool");
|
||||
await page.waitForFunction(
|
||||
"window.PDFViewerApplication.pdfCursorTools.activeTool === 1"
|
||||
);
|
||||
}
|
||||
|
||||
describe("Cursor tools", () => {
|
||||
describe("Text selection", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
".textLayer .endOfContent",
|
||||
100
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that text selection works", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await enableSelectTool(page);
|
||||
|
||||
const spanRect = await getRect(
|
||||
page,
|
||||
`.page[data-page-number="1"] > .textLayer > span`
|
||||
);
|
||||
const x = spanRect.x + 1,
|
||||
y = spanRect.y + spanRect.height / 2;
|
||||
|
||||
await page.mouse.click(x, y, { count: 3 });
|
||||
await waitForSelectionChange(
|
||||
page,
|
||||
"Trace-based Just-in-Time Type Specialization for Dynamic"
|
||||
);
|
||||
|
||||
// Remove the selection.
|
||||
await page.mouse.click(x, y);
|
||||
await waitForSelectionChange(page, "");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Hand tool", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
".textLayer .endOfContent",
|
||||
100
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that hand tool scrolling works", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await enableHandTool(page);
|
||||
|
||||
const pageHeight = await page.evaluate(
|
||||
() =>
|
||||
document.querySelector(`.page[data-page-number="1"]`).offsetHeight
|
||||
);
|
||||
const steps = 10,
|
||||
delta = Math.floor(pageHeight / steps);
|
||||
|
||||
const spanRect = await getRect(
|
||||
page,
|
||||
`.page[data-page-number="1"] > .textLayer > span`
|
||||
);
|
||||
const x = spanRect.x + 1,
|
||||
y = spanRect.y + spanRect.height / 2;
|
||||
|
||||
for (let i = 0; i < steps; i++) {
|
||||
await page.mouse.move(x, y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(x, y - delta);
|
||||
await page.mouse.up();
|
||||
}
|
||||
// Ensure that the second page is visible.
|
||||
await page.waitForFunction("window.PDFViewerApplication.page === 2");
|
||||
|
||||
// Finally, disable the hand tool.
|
||||
await enableSelectTool(page);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
429
test/integration/document_properties_spec.mjs
Normal file
429
test/integration/document_properties_spec.mjs
Normal file
@@ -0,0 +1,429 @@
|
||||
/* Copyright 2024 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { closePages, FSI, loadAndWait, PDI } from "./test_utils.mjs";
|
||||
import fs from "fs/promises";
|
||||
|
||||
const FIELDS = [
|
||||
"fileName",
|
||||
"fileSize",
|
||||
"title",
|
||||
"author",
|
||||
"subject",
|
||||
"keywords",
|
||||
"creationDate",
|
||||
"modificationDate",
|
||||
"creator",
|
||||
"producer",
|
||||
"version",
|
||||
"pageCount",
|
||||
"pageSize",
|
||||
"linearized",
|
||||
];
|
||||
|
||||
async function openDocumentProperties(page) {
|
||||
await page.click("#secondaryToolbarToggleButton");
|
||||
await page.waitForSelector("#secondaryToolbar", { hidden: false });
|
||||
|
||||
await page.click("#documentProperties");
|
||||
await page.waitForSelector("#documentPropertiesDialog", {
|
||||
hidden: false,
|
||||
});
|
||||
}
|
||||
|
||||
async function closeDocumentProperties(page) {
|
||||
await page.click("#documentPropertiesClose");
|
||||
await page.waitForSelector("#documentPropertiesDialog", {
|
||||
hidden: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function checkFieldProperties(page, expectedProps) {
|
||||
await page.waitForFunction(
|
||||
`document.getElementById("fileSizeField").textContent !== "-"`
|
||||
);
|
||||
const promises = [];
|
||||
|
||||
for (const name of FIELDS) {
|
||||
promises.push(
|
||||
page.evaluate(
|
||||
n => [n, document.getElementById(`${n}Field`).textContent],
|
||||
name
|
||||
)
|
||||
);
|
||||
}
|
||||
const props = Object.fromEntries(await Promise.all(promises));
|
||||
expect(props).toEqual(expectedProps);
|
||||
}
|
||||
|
||||
function getFieldDataLastUpdated(page) {
|
||||
return page.evaluate(
|
||||
() =>
|
||||
document.getElementById("documentPropertiesDialog").dataset
|
||||
.fieldDataLastUpdated
|
||||
);
|
||||
}
|
||||
|
||||
describe("PDFDocumentProperties", () => {
|
||||
describe("Document with both /Info and /Metadata", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("basicapi.pdf", ".textLayer .endOfContent");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that the document properties dialog has the correct information", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await openDocumentProperties(page);
|
||||
|
||||
await checkFieldProperties(page, {
|
||||
fileName: "basicapi.pdf",
|
||||
fileSize: `${FSI}103${PDI} KB (${FSI}105,779${PDI} bytes)`,
|
||||
title: "Basic API Test",
|
||||
author: "Brendan Dahl",
|
||||
subject: "-",
|
||||
keywords: "TCPDF",
|
||||
creationDate: "4/10/12, 7:30:26 AM",
|
||||
modificationDate: "4/10/12, 7:30:26 AM",
|
||||
creator: "TCPDF",
|
||||
producer: "TCPDF 5.9.133 (http://www.tcpdf.org)",
|
||||
version: "1.7",
|
||||
pageCount: "3",
|
||||
pageSize: `${FSI}8.27${PDI} × ${FSI}11.69${PDI} ${FSI}in${PDI} (${FSI}A4${PDI}, ${FSI}portrait${PDI})`,
|
||||
linearized: "No",
|
||||
});
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Document with approximately A4-sized page", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"arial_unicode_en_cidfont.pdf",
|
||||
".textLayer .endOfContent"
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that the document properties dialog has the correct information", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await openDocumentProperties(page);
|
||||
|
||||
await checkFieldProperties(page, {
|
||||
fileName: "arial_unicode_en_cidfont.pdf",
|
||||
fileSize: `${FSI}15.4${PDI} KB (${FSI}15,779${PDI} bytes)`,
|
||||
title: "-",
|
||||
author: "Adil Allawi",
|
||||
subject: "-",
|
||||
keywords: "-",
|
||||
creationDate: "7/10/11, 7:17:28 PM",
|
||||
modificationDate: "-",
|
||||
creator: "Writer",
|
||||
producer: "NeoOffice 3.2 Beta",
|
||||
version: "1.4",
|
||||
pageCount: "1",
|
||||
pageSize: `${FSI}8.27${PDI} × ${FSI}11.69${PDI} ${FSI}in${PDI} (${FSI}A4${PDI}, ${FSI}portrait${PDI})`,
|
||||
linearized: "No",
|
||||
});
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Document without contentLength", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".textLayer .endOfContent");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that the document properties dialog has the correct information", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
// Open a binary PDF document, such that `contentLength` is undefined.
|
||||
const base64 = await fs.readFile("./pdfs/clippath.pdf", {
|
||||
encoding: "base64",
|
||||
});
|
||||
|
||||
await page.evaluate(async b64 => {
|
||||
await window.PDFViewerApplication.open({
|
||||
data: Uint8Array.fromBase64(b64),
|
||||
});
|
||||
}, base64);
|
||||
|
||||
await openDocumentProperties(page);
|
||||
|
||||
await checkFieldProperties(page, {
|
||||
fileName: "document.pdf",
|
||||
fileSize: `${FSI}0.448${PDI} KB (${FSI}459${PDI} bytes)`,
|
||||
title: "-",
|
||||
author: "-",
|
||||
subject: "-",
|
||||
keywords: "-",
|
||||
creationDate: "-",
|
||||
modificationDate: "-",
|
||||
creator: "-",
|
||||
producer: "-",
|
||||
version: "1.1",
|
||||
pageCount: "1",
|
||||
pageSize: `${FSI}2.78${PDI} × ${FSI}1.39${PDI} ${FSI}in${PDI} (${FSI}landscape${PDI})`,
|
||||
linearized: "No",
|
||||
});
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Document with multiple pages, and changed viewer page/rotation", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("basicapi.pdf", ".textLayer .endOfContent");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that the document properties dialog has the correct information", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await openDocumentProperties(page);
|
||||
|
||||
await checkFieldProperties(page, {
|
||||
fileName: "basicapi.pdf",
|
||||
fileSize: `${FSI}103${PDI} KB (${FSI}105,779${PDI} bytes)`,
|
||||
title: "Basic API Test",
|
||||
author: "Brendan Dahl",
|
||||
subject: "-",
|
||||
keywords: "TCPDF",
|
||||
creationDate: "4/10/12, 7:30:26 AM",
|
||||
modificationDate: "4/10/12, 7:30:26 AM",
|
||||
creator: "TCPDF",
|
||||
producer: "TCPDF 5.9.133 (http://www.tcpdf.org)",
|
||||
version: "1.7",
|
||||
pageCount: "3",
|
||||
pageSize: `${FSI}8.27${PDI} × ${FSI}11.69${PDI} ${FSI}in${PDI} (${FSI}A4${PDI}, ${FSI}portrait${PDI})`,
|
||||
linearized: "No",
|
||||
});
|
||||
const fieldDataLastUpdated = await getFieldDataLastUpdated(page);
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
|
||||
// Ensure that immediately re-opening the dialog doesn't cause
|
||||
// the field-data to be fetched and parsed again.
|
||||
await openDocumentProperties(page);
|
||||
|
||||
expect(await getFieldDataLastUpdated(page)).toEqual(
|
||||
fieldDataLastUpdated
|
||||
);
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
|
||||
// Goto the second page, and rotate the document.
|
||||
await page.click("#next");
|
||||
await page.waitForFunction(
|
||||
() => window.PDFViewerApplication.page === 2
|
||||
);
|
||||
await page.keyboard.press("r");
|
||||
await page.waitForFunction(
|
||||
() => window.PDFViewerApplication.pdfViewer.pagesRotation === 90
|
||||
);
|
||||
|
||||
await openDocumentProperties(page);
|
||||
|
||||
await checkFieldProperties(page, {
|
||||
fileName: "basicapi.pdf",
|
||||
fileSize: `${FSI}103${PDI} KB (${FSI}105,779${PDI} bytes)`,
|
||||
title: "Basic API Test",
|
||||
author: "Brendan Dahl",
|
||||
subject: "-",
|
||||
keywords: "TCPDF",
|
||||
creationDate: "4/10/12, 7:30:26 AM",
|
||||
modificationDate: "4/10/12, 7:30:26 AM",
|
||||
creator: "TCPDF",
|
||||
producer: "TCPDF 5.9.133 (http://www.tcpdf.org)",
|
||||
version: "1.7",
|
||||
pageCount: "3",
|
||||
pageSize: `${FSI}11.69${PDI} × ${FSI}8.27${PDI} ${FSI}in${PDI} (${FSI}A4${PDI}, ${FSI}landscape${PDI})`,
|
||||
linearized: "No",
|
||||
});
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Document with different page sizes", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("sizes.pdf", ".textLayer .endOfContent");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that the document properties dialog has the correct information", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await openDocumentProperties(page);
|
||||
|
||||
await checkFieldProperties(page, {
|
||||
fileName: "sizes.pdf",
|
||||
fileSize: `${FSI}13.4${PDI} KB (${FSI}13,739${PDI} bytes)`,
|
||||
title: "-",
|
||||
author: "Yury ",
|
||||
subject: "-",
|
||||
keywords: "-",
|
||||
creationDate: "6/26/11, 1:26:03 PM",
|
||||
modificationDate: "-",
|
||||
creator: "Writer",
|
||||
producer: "OpenOffice.org 3.3",
|
||||
version: "1.4",
|
||||
pageCount: "3",
|
||||
pageSize: `${FSI}8.5${PDI} × ${FSI}11${PDI} ${FSI}in${PDI} (${FSI}Letter${PDI}, ${FSI}portrait${PDI})`,
|
||||
linearized: "No",
|
||||
});
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
|
||||
// Goto the second page.
|
||||
await page.click("#next");
|
||||
await page.waitForFunction(
|
||||
() => window.PDFViewerApplication.page === 2
|
||||
);
|
||||
|
||||
await openDocumentProperties(page);
|
||||
|
||||
await checkFieldProperties(page, {
|
||||
fileName: "sizes.pdf",
|
||||
fileSize: `${FSI}13.4${PDI} KB (${FSI}13,739${PDI} bytes)`,
|
||||
title: "-",
|
||||
author: "Yury ",
|
||||
subject: "-",
|
||||
keywords: "-",
|
||||
creationDate: "6/26/11, 1:26:03 PM",
|
||||
modificationDate: "-",
|
||||
creator: "Writer",
|
||||
producer: "OpenOffice.org 3.3",
|
||||
version: "1.4",
|
||||
pageCount: "3",
|
||||
pageSize: `${FSI}9.01${PDI} × ${FSI}4.49${PDI} ${FSI}in${PDI} (${FSI}landscape${PDI})`,
|
||||
linearized: "No",
|
||||
});
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Document with corrupt page", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"Pages-tree-refs.pdf",
|
||||
".textLayer .endOfContent",
|
||||
null,
|
||||
null,
|
||||
{ page: 2 }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that the document properties dialog has the correct information", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await openDocumentProperties(page);
|
||||
|
||||
await checkFieldProperties(page, {
|
||||
fileName: "Pages-tree-refs.pdf",
|
||||
fileSize: `${FSI}1.07${PDI} KB (${FSI}1,098${PDI} bytes)`,
|
||||
title: "-",
|
||||
author: "-",
|
||||
subject: "-",
|
||||
keywords: "-",
|
||||
creationDate: "-",
|
||||
modificationDate: "-",
|
||||
creator: "-",
|
||||
producer: "-",
|
||||
version: "1.7",
|
||||
pageCount: "2",
|
||||
pageSize: "-",
|
||||
linearized: "No",
|
||||
});
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
|
||||
// Goto the first page (which is *not* corrupt).
|
||||
await page.click("#previous");
|
||||
await page.waitForFunction(
|
||||
() => window.PDFViewerApplication.page === 1
|
||||
);
|
||||
|
||||
await openDocumentProperties(page);
|
||||
|
||||
await checkFieldProperties(page, {
|
||||
fileName: "Pages-tree-refs.pdf",
|
||||
fileSize: `${FSI}1.07${PDI} KB (${FSI}1,098${PDI} bytes)`,
|
||||
title: "-",
|
||||
author: "-",
|
||||
subject: "-",
|
||||
keywords: "-",
|
||||
creationDate: "-",
|
||||
modificationDate: "-",
|
||||
creator: "-",
|
||||
producer: "-",
|
||||
version: "1.7",
|
||||
pageCount: "2",
|
||||
pageSize: `${FSI}8.27${PDI} × ${FSI}11.69${PDI} ${FSI}in${PDI} (${FSI}A4${PDI}, ${FSI}portrait${PDI})`,
|
||||
linearized: "No",
|
||||
});
|
||||
|
||||
await closeDocumentProperties(page);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
234
test/integration/find_spec.mjs
Normal file
234
test/integration/find_spec.mjs
Normal file
@@ -0,0 +1,234 @@
|
||||
/* Copyright 2021 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
closePages,
|
||||
FSI,
|
||||
loadAndWait,
|
||||
PDI,
|
||||
waitForTextToBe,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
function fuzzyMatch(a, b, browserName, pixelFuzz = 3) {
|
||||
expect(a)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeLessThan(b + pixelFuzz);
|
||||
expect(a)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBeGreaterThan(b - pixelFuzz);
|
||||
}
|
||||
|
||||
describe("find bar", () => {
|
||||
describe("highlight all", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("find_all.pdf", ".textLayer", 100);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must highlight text in the right position", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
// Highlight all occurrences of the letter A (case insensitive).
|
||||
await page.click("#viewFindButton");
|
||||
await page.waitForSelector("#findInput", { visible: true });
|
||||
await page.type("#findInput", "a");
|
||||
await page.click("#findHighlightAll + label");
|
||||
await page.waitForSelector(".textLayer .highlight");
|
||||
|
||||
// The PDF file contains the text 'AB BA' in a monospace font on a
|
||||
// single line. Check if the two occurrences of A are highlighted.
|
||||
const highlights = await page.$$(".textLayer .highlight");
|
||||
expect(highlights.length).withContext(`In ${browserName}`).toEqual(2);
|
||||
|
||||
// Normalize the highlight's height. The font data in the PDF sets the
|
||||
// size of the glyphs (and therefore the size of the highlights), but
|
||||
// the viewer applies extra padding to them. For the comparison we
|
||||
// therefore use the unpadded, glyph-sized parent element's height.
|
||||
const parentSpan = (await highlights[0].$$("xpath/.."))[0];
|
||||
const parentBox = await parentSpan.boundingBox();
|
||||
const firstA = await highlights[0].boundingBox();
|
||||
const secondA = await highlights[1].boundingBox();
|
||||
firstA.height = parentBox.height;
|
||||
secondA.height = parentBox.height;
|
||||
|
||||
// Check if the vertical position of the highlights is correct. Both
|
||||
// should be on a single line.
|
||||
expect(firstA.y).withContext(`In ${browserName}`).toEqual(secondA.y);
|
||||
|
||||
// Check if the height of the two highlights is correct. Both should
|
||||
// match the font size.
|
||||
const fontSize = 26.66; // From the PDF.
|
||||
fuzzyMatch(firstA.height, fontSize, browserName);
|
||||
fuzzyMatch(secondA.height, fontSize, browserName);
|
||||
|
||||
// Check if the horizontal position of the highlights is correct. The
|
||||
// second occurrence should be four glyph widths (three letters and
|
||||
// one space) away from the first occurrence.
|
||||
const pageDiv = await page.$(".page canvas");
|
||||
const pageBox = await pageDiv.boundingBox();
|
||||
const expectedFirstAX = 30; // From the PDF.
|
||||
const glyphWidth = 15.98; // From the PDF.
|
||||
fuzzyMatch(firstA.x, pageBox.x + expectedFirstAX, browserName);
|
||||
fuzzyMatch(secondA.x, firstA.x + glyphWidth * 4, browserName);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("highlight all (XFA)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("xfa_imm5257e.pdf", ".xfaLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must search xfa correctly", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.click("#viewFindButton");
|
||||
await page.waitForSelector("#findInput", { visible: true });
|
||||
await page.type("#findInput", "preferences");
|
||||
await page.waitForSelector("#findInput[data-status='']");
|
||||
await page.waitForSelector(".xfaLayer .highlight");
|
||||
await waitForTextToBe(
|
||||
page,
|
||||
"#findResultsCount",
|
||||
`${FSI}1${PDI} of ${FSI}1${PDI} match`
|
||||
);
|
||||
const selectedElement = await page.waitForSelector(
|
||||
".highlight.selected"
|
||||
);
|
||||
const selectedText = await selectedElement.evaluate(
|
||||
el => el.textContent
|
||||
);
|
||||
expect(selectedText).toEqual("Preferences");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("issue19207.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("issue19207.pdf", ".textLayer", 200);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must scroll to the search result text", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
// Search for "40"
|
||||
await page.click("#viewFindButton");
|
||||
await page.waitForSelector("#findInput", { visible: true });
|
||||
await page.type("#findInput", "40");
|
||||
|
||||
const highlight = await page.waitForSelector(".textLayer .highlight");
|
||||
|
||||
expect(await highlight.isIntersectingViewport()).toBeTrue();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("scrolls to the search result text for smaller viewports", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", ".textLayer", 100);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must scroll to the search result text", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
// Set a smaller viewport to simulate a mobile device
|
||||
await page.setViewport({ width: 350, height: 600 });
|
||||
await page.click("#viewFindButton");
|
||||
await page.waitForSelector("#findInput", { visible: true });
|
||||
await page.type("#findInput", "productivity");
|
||||
|
||||
const highlight = await page.waitForSelector(".textLayer .highlight");
|
||||
|
||||
expect(await highlight.isIntersectingViewport()).toBeTrue();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Check that the search results are correctly visible in rotated PDFs (bug 2021392)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"hello_world_rotated.pdf",
|
||||
".textLayer",
|
||||
"page-fit"
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must scroll each match into the viewport when navigating search results", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.click("#viewFindButton");
|
||||
await page.waitForSelector("#findInput", { visible: true });
|
||||
await page.type("#findInput", "hello");
|
||||
await page.waitForSelector("#findInput[data-status='']");
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
if (i > 0) {
|
||||
await page.click("#findNextButton");
|
||||
await page.waitForSelector("#findInput[data-status='']");
|
||||
}
|
||||
|
||||
// Verify we are on the expected match number.
|
||||
await waitForTextToBe(
|
||||
page,
|
||||
"#findResultsCount",
|
||||
`${FSI}${i + 1}${PDI} of ${FSI}5${PDI} matches`
|
||||
);
|
||||
|
||||
// The selected highlight must be visible in the viewport.
|
||||
const selected = await page.waitForSelector(
|
||||
".textLayer .highlight.selected"
|
||||
);
|
||||
expect(await selected.isIntersectingViewport())
|
||||
.withContext(`In ${browserName}, match ${i + 1}`)
|
||||
.toBeTrue();
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
3682
test/integration/freetext_editor_spec.mjs
Normal file
3682
test/integration/freetext_editor_spec.mjs
Normal file
File diff suppressed because it is too large
Load Diff
2851
test/integration/highlight_editor_spec.mjs
Normal file
2851
test/integration/highlight_editor_spec.mjs
Normal file
File diff suppressed because it is too large
Load Diff
1350
test/integration/ink_editor_spec.mjs
Normal file
1350
test/integration/ink_editor_spec.mjs
Normal file
File diff suppressed because it is too large
Load Diff
115
test/integration/jasmine-boot.js
Normal file
115
test/integration/jasmine-boot.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/* Copyright 2020 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { TEST_PASSED, TEST_UNEXPECTED_FAIL } from "../color_utils.mjs";
|
||||
import Jasmine from "jasmine";
|
||||
|
||||
async function runTests(results) {
|
||||
const jasmine = new Jasmine();
|
||||
jasmine.exitOnCompletion = false;
|
||||
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
|
||||
|
||||
jasmine.loadConfig({
|
||||
random: true,
|
||||
spec_dir: "integration",
|
||||
spec_files: [
|
||||
"accessibility_spec.mjs",
|
||||
"annotation_spec.mjs",
|
||||
"autolinker_spec.mjs",
|
||||
"caret_browsing_spec.mjs",
|
||||
"comment_spec.mjs",
|
||||
"copy_paste_spec.mjs",
|
||||
"cursor_tools_spec.mjs",
|
||||
"document_properties_spec.mjs",
|
||||
"find_spec.mjs",
|
||||
"freetext_editor_spec.mjs",
|
||||
"highlight_editor_spec.mjs",
|
||||
"ink_editor_spec.mjs",
|
||||
"presentation_mode_spec.mjs",
|
||||
"reorganize_pages_spec.mjs",
|
||||
"scripting_spec.mjs",
|
||||
"signature_editor_spec.mjs",
|
||||
"simple_viewer_spec.mjs",
|
||||
"stamp_editor_spec.mjs",
|
||||
"text_extractor_spec.mjs",
|
||||
"text_field_spec.mjs",
|
||||
"text_layer_spec.mjs",
|
||||
"text_layer_images_spec.mjs",
|
||||
"thumbnail_view_spec.mjs",
|
||||
"viewer_spec.mjs",
|
||||
],
|
||||
});
|
||||
|
||||
function failureError(result) {
|
||||
return result.failedExpectations
|
||||
?.map(item => item.message)
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
jasmine.addReporter({
|
||||
jasmineDone(suiteInfo) {},
|
||||
jasmineStarted(suiteInfo) {},
|
||||
specDone(result) {
|
||||
// Ignore excluded (fit/xit) or skipped (pending) tests.
|
||||
if (["excluded", "pending"].includes(result.status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Report on passed or failed tests.
|
||||
++results.runs;
|
||||
if (result.status === "passed") {
|
||||
console.log(`${TEST_PASSED} | ${result.description}`);
|
||||
} else {
|
||||
++results.failures;
|
||||
const error = failureError(result);
|
||||
results.failureList?.push({
|
||||
description: result.description,
|
||||
error,
|
||||
});
|
||||
console.log(
|
||||
`${TEST_UNEXPECTED_FAIL} | ${result.description}${error ? ` | ${error}` : ""}`
|
||||
);
|
||||
}
|
||||
},
|
||||
specStarted(result) {},
|
||||
suiteDone(result) {
|
||||
// Ignore excluded (fdescribe/xdescribe) or skipped (pending) suites.
|
||||
if (["excluded", "pending"].includes(result.status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Report on failed suites only (indicates problems in setup/teardown).
|
||||
if (result.status === "failed") {
|
||||
++results.failures;
|
||||
const error = failureError(result);
|
||||
results.failureList?.push({
|
||||
description: result.description,
|
||||
error,
|
||||
});
|
||||
console.log(
|
||||
`${TEST_UNEXPECTED_FAIL} | ${result.description}${error ? ` | ${error}` : ""}`
|
||||
);
|
||||
}
|
||||
},
|
||||
suiteStarted(result) {},
|
||||
});
|
||||
|
||||
return jasmine.execute();
|
||||
}
|
||||
|
||||
export { runTests };
|
||||
196
test/integration/presentation_mode_spec.mjs
Normal file
196
test/integration/presentation_mode_spec.mjs
Normal file
@@ -0,0 +1,196 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
awaitPromise,
|
||||
closePages,
|
||||
createPromise,
|
||||
loadAndWait,
|
||||
waitForTimeout,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
async function enterPresentationMode(page) {
|
||||
await page.click("#secondaryToolbarToggleButton");
|
||||
await page.waitForSelector("#secondaryToolbar", { hidden: false });
|
||||
|
||||
const handlePresentationModeChanged = await createPromise(page, resolve => {
|
||||
window.PDFViewerApplication.eventBus.on(
|
||||
"presentationmodechanged",
|
||||
resolve,
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
await page.click("#presentationMode");
|
||||
await awaitPromise(handlePresentationModeChanged);
|
||||
|
||||
// Check that presentation mode is active and that the toolbar is
|
||||
// invisible; the latter differentiates between proper presentation
|
||||
// mode and pressing F11 to only hide the browser's UI elements.
|
||||
await page.waitForFunction(`document.fullscreenElement !== null`);
|
||||
await page.waitForSelector("#viewerContainer.pdfPresentationMode", {
|
||||
visible: true,
|
||||
});
|
||||
await page.waitForSelector("#toolbarContainer", { visible: false });
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 1`
|
||||
);
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentScaleValue === "page-fit"`
|
||||
);
|
||||
}
|
||||
|
||||
async function exitPresentationMode(page, browserName) {
|
||||
// Note that in Chrome pressing Escape does not work to exit full screen mode
|
||||
// in the Puppeteer scope, so there we exit full screen mode programmatically
|
||||
// instead, which is equivalent to what happens if Escape is pressed.
|
||||
const handlePresentationModeChanged = await createPromise(page, resolve => {
|
||||
window.PDFViewerApplication.eventBus.on(
|
||||
"presentationmodechanged",
|
||||
resolve,
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
await (browserName === "chrome"
|
||||
? page.evaluate(() => document.exitFullscreen())
|
||||
: page.keyboard.press("Escape"));
|
||||
await awaitPromise(handlePresentationModeChanged);
|
||||
|
||||
// Check that presentation mode is not active anymore and the toolbar
|
||||
// is visible again.
|
||||
await page.waitForFunction(`document.fullscreenElement === null`);
|
||||
await page.waitForSelector("#viewerContainer:not(.pdfPresentationMode)", {
|
||||
visible: true,
|
||||
});
|
||||
await page.waitForSelector("#toolbarContainer", { visible: true });
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 1`
|
||||
);
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentScaleValue !== "page-fit"`
|
||||
);
|
||||
}
|
||||
|
||||
describe("PDFPresentationMode", () => {
|
||||
describe("Changing pages", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"basicapi.pdf",
|
||||
".textLayer .endOfContent",
|
||||
100
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that changing pages using arrow keys works in presentation mode", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await enterPresentationMode(page);
|
||||
|
||||
// Go to the next page.
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 2`
|
||||
);
|
||||
|
||||
// Go to the previous page.
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 1`
|
||||
);
|
||||
|
||||
await exitPresentationMode(page, browserName);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that changing pages using mouse wheel works in presentation mode", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await enterPresentationMode(page);
|
||||
|
||||
// Go to the next page.
|
||||
await page.mouse.wheel({ deltaY: 100 });
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 2`
|
||||
);
|
||||
|
||||
// Wait until the viewer accepts a new mouse scroll event; see
|
||||
// `MOUSE_SCROLL_COOLDOWN_TIME` in `web/pdf_presentation_mode.js`.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
await waitForTimeout(50);
|
||||
|
||||
// Go to the previous page.
|
||||
await page.mouse.wheel({ deltaY: -100 });
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 1`
|
||||
);
|
||||
|
||||
await exitPresentationMode(page, browserName);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that changing pages using mouse click works in presentation mode", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await enterPresentationMode(page);
|
||||
|
||||
// Go to the next page.
|
||||
await page.click(".page[data-page-number='1']");
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 2`
|
||||
);
|
||||
|
||||
// Go to the previous page.
|
||||
await page.keyboard.down("Shift");
|
||||
await page.click(".page[data-page-number='2']");
|
||||
await page.keyboard.up("Shift");
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 1`
|
||||
);
|
||||
|
||||
await exitPresentationMode(page, browserName);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("check that clicking on internal links work in presentation mode", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await enterPresentationMode(page);
|
||||
|
||||
// Go to the last page.
|
||||
await page.click("#pdfjs_internal_id_12R");
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 3`
|
||||
);
|
||||
|
||||
// Go to the first page.
|
||||
await page.keyboard.press("Home");
|
||||
await page.waitForFunction(
|
||||
`window.PDFViewerApplication.pdfViewer.currentPageNumber === 1`
|
||||
);
|
||||
|
||||
await exitPresentationMode(page, browserName);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
3725
test/integration/reorganize_pages_spec.mjs
Normal file
3725
test/integration/reorganize_pages_spec.mjs
Normal file
File diff suppressed because it is too large
Load Diff
2699
test/integration/scripting_spec.mjs
Normal file
2699
test/integration/scripting_spec.mjs
Normal file
File diff suppressed because it is too large
Load Diff
774
test/integration/signature_editor_spec.mjs
Normal file
774
test/integration/signature_editor_spec.mjs
Normal file
@@ -0,0 +1,774 @@
|
||||
/* Copyright 2025 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
awaitPromise,
|
||||
closePages,
|
||||
copy,
|
||||
FSI,
|
||||
getEditorSelector,
|
||||
getRect,
|
||||
loadAndWait,
|
||||
paste,
|
||||
PDI,
|
||||
switchToEditor,
|
||||
waitForPointerUp,
|
||||
waitForTimeout,
|
||||
} from "./test_utils.mjs";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { PNG } from "pngjs";
|
||||
|
||||
const __dirname = import.meta.dirname;
|
||||
|
||||
const switchToSignature = switchToEditor.bind(null, "Signature");
|
||||
|
||||
describe("Signature Editor", () => {
|
||||
const descriptionInputSelector = "#addSignatureDescription > input";
|
||||
const addButtonSelector = "#addSignatureAddButton";
|
||||
|
||||
describe("Basic operations", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the editor has been removed when the dialog is cancelled", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([_, page]) => {
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// An invisible editor is created but invisible.
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await page.waitForSelector(editorSelector, { visible: false });
|
||||
|
||||
await page.click("#addSignatureCancelButton");
|
||||
|
||||
// The editor should have been removed.
|
||||
await page.waitForSelector(`:not(${editorSelector})`);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check that the basic and common elements are working as expected", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
await page.waitForSelector(
|
||||
"#addSignatureTypeButton[aria-selected=true]"
|
||||
);
|
||||
await page.click("#addSignatureTypeInput");
|
||||
await page.waitForSelector(
|
||||
"#addSignatureSaveContainer > input:disabled"
|
||||
);
|
||||
let description = await page.$eval(
|
||||
descriptionInputSelector,
|
||||
el => el.value
|
||||
);
|
||||
expect(description).withContext(browserName).toEqual("");
|
||||
await page.waitForSelector(`${addButtonSelector}:disabled`);
|
||||
await page.waitForSelector("#addSignatureDescInput:disabled");
|
||||
|
||||
await page.type("#addSignatureTypeInput", "PDF.js");
|
||||
await page.waitForSelector(`${addButtonSelector}:not(:disabled)`);
|
||||
await page.waitForSelector("#addSignatureDescInput:not(:disabled)");
|
||||
|
||||
// The save button should be enabled now.
|
||||
await page.waitForSelector(
|
||||
"#addSignatureSaveContainer > input:not(:disabled)"
|
||||
);
|
||||
await page.waitForSelector("#addSignatureSaveCheckbox:checked");
|
||||
|
||||
// The description has been filled in automatically.
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("${descriptionInputSelector}").value !== ""`
|
||||
);
|
||||
description = await page.$eval(
|
||||
descriptionInputSelector,
|
||||
el => el.value
|
||||
);
|
||||
expect(description).withContext(browserName).toEqual("PDF.js");
|
||||
|
||||
// Clear the description.
|
||||
await page.click("#addSignatureDescription > button");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("${descriptionInputSelector}").value === ""`
|
||||
);
|
||||
|
||||
// Clear the text for the signature.
|
||||
await page.click("#clearSignatureButton");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("#addSignatureTypeInput").value === ""`
|
||||
);
|
||||
// The save button should be disabled now.
|
||||
await page.waitForSelector(
|
||||
"#addSignatureSaveContainer > input:disabled"
|
||||
);
|
||||
await page.waitForSelector(`${addButtonSelector}:disabled`);
|
||||
|
||||
await page.type("#addSignatureTypeInput", "PDF.js");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("${descriptionInputSelector}").value !== ""`
|
||||
);
|
||||
|
||||
// Clearing the signature type should clear the description.
|
||||
await page.click("#clearSignatureButton");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("#addSignatureTypeInput").value === ""`
|
||||
);
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("${descriptionInputSelector}").value === ""`
|
||||
);
|
||||
|
||||
// Add a signature and change the description.
|
||||
await page.type("#addSignatureTypeInput", "PDF.js");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("${descriptionInputSelector}").value !== ""`
|
||||
);
|
||||
await page.click("#addSignatureDescription > button");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("${descriptionInputSelector}").value === ""`
|
||||
);
|
||||
await page.type(descriptionInputSelector, "Hello World");
|
||||
await page.type("#addSignatureTypeInput", "Hello");
|
||||
|
||||
// The description mustn't be changed.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
await waitForTimeout(100);
|
||||
description = await page.$eval(
|
||||
descriptionInputSelector,
|
||||
el => el.value
|
||||
);
|
||||
expect(description).withContext(browserName).toEqual("Hello World");
|
||||
|
||||
await page.click("#addSignatureAddButton");
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: false,
|
||||
});
|
||||
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await page.waitForSelector(editorSelector, { visible: true });
|
||||
await page.waitForSelector(
|
||||
`.canvasWrapper > svg use[href="#path_0"]`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
await page.waitForFunction(
|
||||
`document.getElementById("viewer-alert").textContent === "Signature added"`
|
||||
);
|
||||
|
||||
// Check the tooltip.
|
||||
await page.waitForSelector(
|
||||
`.altText.editDescription[title="Hello World"]`
|
||||
);
|
||||
|
||||
// Check the aria description.
|
||||
await page.waitForSelector(
|
||||
`${editorSelector}[aria-description="Signature editor: ${FSI}Hello World${PDI}"]`
|
||||
);
|
||||
|
||||
// Edit the description.
|
||||
await page.click(`.altText.editDescription`);
|
||||
|
||||
await page.waitForSelector("#editSignatureDescriptionDialog", {
|
||||
visible: true,
|
||||
});
|
||||
await page.waitForSelector("#editSignatureUpdateButton:disabled");
|
||||
await page.waitForSelector(
|
||||
`#editSignatureDescriptionDialog svg[aria-label="Hello World"]`
|
||||
);
|
||||
const editDescriptionInput = "#editSignatureDescription > input";
|
||||
description = await page.$eval(editDescriptionInput, el => el.value);
|
||||
expect(description).withContext(browserName).toEqual("Hello World");
|
||||
await page.click("#editSignatureDescription > button");
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("${editDescriptionInput}").value === ""`
|
||||
);
|
||||
await page.waitForSelector(
|
||||
"#editSignatureUpdateButton:not(:disabled)"
|
||||
);
|
||||
await page.type(editDescriptionInput, "Hello PDF.js World");
|
||||
await page.waitForSelector(
|
||||
"#editSignatureUpdateButton:not(:disabled)"
|
||||
);
|
||||
|
||||
await page.click("#editSignatureUpdateButton");
|
||||
|
||||
// Check the tooltip.
|
||||
await page.waitForSelector(
|
||||
`.altText.editDescription[title="Hello PDF.js World"]`
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check drawing with the mouse", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
await page.click("#addSignatureDrawButton");
|
||||
const drawSelector = "#addSignatureDraw";
|
||||
await page.waitForSelector(drawSelector, { visible: true });
|
||||
|
||||
let description = await page.$eval(
|
||||
descriptionInputSelector,
|
||||
el => el.value
|
||||
);
|
||||
expect(description).withContext(browserName).toEqual("");
|
||||
await page.waitForSelector(`${addButtonSelector}:disabled`);
|
||||
|
||||
const { x, y, width, height } = await getRect(page, drawSelector);
|
||||
const clickHandle = await waitForPointerUp(page);
|
||||
await page.mouse.move(x + 0.1 * width, y + 0.1 * height);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(x + 0.3 * width, y + 0.3 * height);
|
||||
await page.mouse.up();
|
||||
await awaitPromise(clickHandle);
|
||||
await page.waitForSelector(`${addButtonSelector}:not(:disabled)`);
|
||||
|
||||
// The save button should be enabled now.
|
||||
await page.waitForSelector(
|
||||
"#addSignatureSaveContainer > input:not(:disabled)"
|
||||
);
|
||||
await page.waitForSelector("#addSignatureSaveCheckbox:checked");
|
||||
|
||||
// The description has been filled in automatically.
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("${descriptionInputSelector}").value !== ""`
|
||||
);
|
||||
description = await page.$eval(
|
||||
descriptionInputSelector,
|
||||
el => el.value
|
||||
);
|
||||
expect(description).withContext(browserName).toEqual("Signature");
|
||||
|
||||
await page.click("#addSignatureAddButton");
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: false,
|
||||
});
|
||||
|
||||
await page.waitForSelector(
|
||||
".canvasWrapper > svg use[href='#path_0']"
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check adding an image", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
await page.click("#addSignatureImageButton");
|
||||
await page.waitForSelector("#addSignatureImagePlaceholder", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
let description = await page.$eval(
|
||||
descriptionInputSelector,
|
||||
el => el.value
|
||||
);
|
||||
expect(description).withContext(browserName).toEqual("");
|
||||
await page.waitForSelector(`${addButtonSelector}:disabled`);
|
||||
|
||||
const input = await page.$("#addSignatureFilePicker");
|
||||
await input.uploadFile(
|
||||
`${path.join(__dirname, "../images/firefox_logo.png")}`
|
||||
);
|
||||
await page.waitForSelector(`#addSignatureImage > path:not([d=""])`);
|
||||
|
||||
// The save button should be enabled now.
|
||||
await page.waitForSelector(
|
||||
"#addSignatureSaveContainer > input:not(:disabled)"
|
||||
);
|
||||
await page.waitForSelector("#addSignatureSaveCheckbox:checked");
|
||||
|
||||
// The description has been filled in automatically.
|
||||
await page.waitForFunction(
|
||||
`document.querySelector("${descriptionInputSelector}").value !== ""`
|
||||
);
|
||||
description = await page.$eval(
|
||||
descriptionInputSelector,
|
||||
el => el.value
|
||||
);
|
||||
expect(description)
|
||||
.withContext(browserName)
|
||||
.toEqual("firefox_logo.png");
|
||||
|
||||
await page.click("#addSignatureAddButton");
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: false,
|
||||
});
|
||||
|
||||
await page.waitForSelector(
|
||||
".canvasWrapper > svg use[href='#path_0']"
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must check copy and paste", async () => {
|
||||
// Run sequentially to avoid clipboard issues.
|
||||
for (const [browserName, page] of pages) {
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
await page.type("#addSignatureTypeInput", "Hello");
|
||||
await page.waitForSelector(`${addButtonSelector}:not(:disabled)`);
|
||||
await page.click("#addSignatureAddButton");
|
||||
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await page.waitForSelector(editorSelector, { visible: true });
|
||||
const originalRect = await getRect(page, editorSelector);
|
||||
const originalDescription = await page.$eval(
|
||||
`${editorSelector} .altText.editDescription`,
|
||||
el => el.title
|
||||
);
|
||||
const originalL10nParameter = await page.$eval(editorSelector, el =>
|
||||
el.getAttribute("data-l10n-args")
|
||||
);
|
||||
|
||||
await copy(page);
|
||||
await paste(page);
|
||||
|
||||
const pastedEditorSelector = getEditorSelector(1);
|
||||
await page.waitForSelector(pastedEditorSelector, { visible: true });
|
||||
const pastedRect = await getRect(page, pastedEditorSelector);
|
||||
const pastedDescription = await page.$eval(
|
||||
`${pastedEditorSelector} .altText.editDescription`,
|
||||
el => el.title
|
||||
);
|
||||
const pastedL10nParameter = await page.$eval(pastedEditorSelector, el =>
|
||||
el.getAttribute("data-l10n-args")
|
||||
);
|
||||
|
||||
expect(pastedRect)
|
||||
.withContext(`In ${browserName}`)
|
||||
.not.toEqual(originalRect);
|
||||
expect(pastedDescription)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(originalDescription);
|
||||
expect(pastedL10nParameter)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toEqual(originalL10nParameter);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bug 1948741", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the editor isn't too large", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([_, page]) => {
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
await page.type(
|
||||
"#addSignatureTypeInput",
|
||||
"[18:50:03] asset pdf.scripting.mjs 105 KiB [emitted] [javascript module] (name: main)"
|
||||
);
|
||||
await page.waitForSelector(`${addButtonSelector}:not(:disabled)`);
|
||||
await page.click("#addSignatureAddButton");
|
||||
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await page.waitForSelector(editorSelector, { visible: true });
|
||||
await page.waitForSelector(
|
||||
`.canvasWrapper > svg use[href="#path_0"]`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
const { width } = await getRect(page, editorSelector);
|
||||
const { width: pageWidth } = await getRect(page, ".page");
|
||||
expect(width).toBeLessThanOrEqual(pageWidth);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bug 1949201", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the error panel is correctly removed", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([_, page]) => {
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
await page.click("#addSignatureImageButton");
|
||||
await page.waitForSelector("#addSignatureImagePlaceholder", {
|
||||
visible: true,
|
||||
});
|
||||
const input = await page.$("#addSignatureFilePicker");
|
||||
await input.uploadFile(
|
||||
`${path.join(__dirname, "./signature_editor_spec.mjs")}`
|
||||
);
|
||||
await page.waitForSelector("#addSignatureError", { visible: true });
|
||||
await page.click("#addSignatureErrorCloseButton");
|
||||
await page.waitForSelector("#addSignatureError", { visible: false });
|
||||
|
||||
await input.uploadFile(
|
||||
`${path.join(__dirname, "./stamp_editor_spec.mjs")}`
|
||||
);
|
||||
await page.waitForSelector("#addSignatureError", { visible: true });
|
||||
|
||||
await page.click("#addSignatureTypeButton");
|
||||
await page.waitForSelector(
|
||||
"#addSignatureTypeButton[aria-selected=true]"
|
||||
);
|
||||
await page.waitForSelector("#addSignatureError", { visible: false });
|
||||
await page.click("#addSignatureCancelButton");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("viewerCssTheme (light)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"empty.pdf",
|
||||
".annotationEditorLayer",
|
||||
null,
|
||||
null,
|
||||
{ viewerCssTheme: "1" }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the signature has the correct color with the light theme", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([_, page]) => {
|
||||
const colorTheme = await page.evaluate(() => {
|
||||
const html = document.querySelector("html");
|
||||
const style = getComputedStyle(html);
|
||||
return style.getPropertyValue("color-scheme");
|
||||
});
|
||||
expect(colorTheme).toEqual("light");
|
||||
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
await page.type("#addSignatureTypeInput", "Should be black.");
|
||||
await page.waitForSelector(`${addButtonSelector}:not(:disabled)`);
|
||||
await page.click("#addSignatureAddButton");
|
||||
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await page.waitForSelector(editorSelector, { visible: true });
|
||||
await page.waitForSelector(
|
||||
`.canvasWrapper > svg use[href="#path_0"]`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
const color = await page.evaluate(() => {
|
||||
const use = document.querySelector(
|
||||
`.canvasWrapper > svg use[href="#path_0"]`
|
||||
);
|
||||
return use.parentNode.getAttribute("fill");
|
||||
});
|
||||
expect(color).toEqual("#000000");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("viewerCssTheme (dark)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"empty.pdf",
|
||||
".annotationEditorLayer",
|
||||
null,
|
||||
null,
|
||||
{ viewerCssTheme: "2" }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the signature has the correct color with the dark theme", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([_, page]) => {
|
||||
const colorTheme = await page.evaluate(() => {
|
||||
const html = document.querySelector("html");
|
||||
const style = getComputedStyle(html);
|
||||
return style.getPropertyValue("color-scheme");
|
||||
});
|
||||
expect(colorTheme).toEqual("dark");
|
||||
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
await page.type("#addSignatureTypeInput", "Should be black.");
|
||||
await page.waitForSelector(`${addButtonSelector}:not(:disabled)`);
|
||||
await page.click("#addSignatureAddButton");
|
||||
|
||||
const editorSelector = getEditorSelector(0);
|
||||
await page.waitForSelector(editorSelector, { visible: true });
|
||||
await page.waitForSelector(
|
||||
`.canvasWrapper > svg use[href="#path_0"]`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
const color = await page.evaluate(() => {
|
||||
const use = document.querySelector(
|
||||
`.canvasWrapper > svg use[href="#path_0"]`
|
||||
);
|
||||
return use.parentNode.getAttribute("fill");
|
||||
});
|
||||
expect(color).toEqual("#000000");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Check the aspect ratio (bug 1962819)", () => {
|
||||
let pages, contentWidth, contentHeight;
|
||||
|
||||
function getContentAspectRatio(png) {
|
||||
const { width, height } = png;
|
||||
const buffer = new Uint32Array(png.data.buffer);
|
||||
let x0 = width;
|
||||
let y0 = height;
|
||||
let x1 = 0;
|
||||
let y1 = 0;
|
||||
for (let i = 0; i < height; i++) {
|
||||
for (let j = 0; j < width; j++) {
|
||||
if (buffer[width * i + j] !== 0) {
|
||||
x0 = Math.min(x0, j);
|
||||
y0 = Math.min(y0, i);
|
||||
x1 = Math.max(x1, j);
|
||||
y1 = Math.max(y1, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentWidth = x1 - x0;
|
||||
contentHeight = y1 - y0;
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
const data = fs.readFileSync(
|
||||
path.join(__dirname, "../images/samplesignature.png")
|
||||
);
|
||||
const png = PNG.sync.read(data);
|
||||
getContentAspectRatio(png);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the signature has the correct aspect ratio", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToSignature(page);
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
await page.click("#addSignatureImageButton");
|
||||
await page.waitForSelector("#addSignatureImagePlaceholder", {
|
||||
visible: true,
|
||||
});
|
||||
await page.waitForSelector(`${addButtonSelector}:disabled`);
|
||||
|
||||
const input = await page.$("#addSignatureFilePicker");
|
||||
await input.uploadFile(
|
||||
`${path.join(__dirname, "../images/samplesignature.png")}`
|
||||
);
|
||||
await page.waitForSelector(`#addSignatureImage > path:not([d=""])`);
|
||||
|
||||
// The save button should be enabled now.
|
||||
await page.waitForSelector(
|
||||
"#addSignatureSaveContainer > input:not(:disabled)"
|
||||
);
|
||||
await page.click("#addSignatureAddButton");
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: false,
|
||||
});
|
||||
const { width, height } = await getRect(
|
||||
page,
|
||||
".canvasWrapper > svg use[href='#path_0']"
|
||||
);
|
||||
|
||||
expect(Math.abs(contentWidth / width - contentHeight / height))
|
||||
.withContext(
|
||||
`In ${browserName} (${contentWidth}x${contentHeight} vs ${width}x${height})`
|
||||
)
|
||||
.toBeLessThan(0.25);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bug 1974257", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the signature save checkbox is disabled if storage is full", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([_, page]) => {
|
||||
await switchToSignature(page);
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
await page.click("#addSignatureTypeInput");
|
||||
await page.type("#addSignatureTypeInput", `PDF.js ${i}`);
|
||||
if (i === 5) {
|
||||
await page.waitForSelector(
|
||||
"#addSignatureSaveCheckbox:not(checked)"
|
||||
);
|
||||
await page.waitForSelector("#addSignatureSaveCheckbox:disabled");
|
||||
} else {
|
||||
await page.waitForSelector("#addSignatureSaveCheckbox:checked");
|
||||
await page.waitForSelector(
|
||||
"#addSignatureSaveCheckbox:not(:disabled)"
|
||||
);
|
||||
}
|
||||
await page.click("#addSignatureAddButton");
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: false,
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bug 1975719", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that an error is displayed with a monochrome image", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([_, page]) => {
|
||||
await switchToSignature(page);
|
||||
|
||||
await page.click("#editorSignatureAddSignature");
|
||||
|
||||
await page.waitForSelector("#addSignatureDialog", {
|
||||
visible: true,
|
||||
});
|
||||
await page.click("#addSignatureImageButton");
|
||||
await page.waitForSelector("#addSignatureImagePlaceholder", {
|
||||
visible: true,
|
||||
});
|
||||
const input = await page.$("#addSignatureFilePicker");
|
||||
await input.uploadFile(
|
||||
`${path.join(__dirname, "../images/red.png")}`
|
||||
);
|
||||
await page.waitForSelector("#addSignatureError", { visible: true });
|
||||
await page.waitForSelector(
|
||||
"#addSignatureErrorTitle[data-l10n-id='pdfjs-editor-add-signature-image-no-data-error-title']"
|
||||
);
|
||||
await page.waitForSelector(
|
||||
"#addSignatureErrorDescription[data-l10n-id='pdfjs-editor-add-signature-image-no-data-error-description']"
|
||||
);
|
||||
await page.click("#addSignatureErrorCloseButton");
|
||||
await page.waitForSelector("#addSignatureError", { visible: false });
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
107
test/integration/simple_viewer_spec.mjs
Normal file
107
test/integration/simple_viewer_spec.mjs
Normal file
@@ -0,0 +1,107 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Integration tests for the simple viewer (test/components/).
|
||||
|
||||
function getSelector(id) {
|
||||
return `[data-element-id="${id}"]`;
|
||||
}
|
||||
|
||||
function getAnnotationSelector(id) {
|
||||
return `[data-annotation-id="${id}"]`;
|
||||
}
|
||||
|
||||
describe("Simple viewer", () => {
|
||||
describe("TextLayerBuilder without abortSignal", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
const origin = new URL(global.integrationBaseUrl).origin;
|
||||
pages = await Promise.all(
|
||||
global.integrationSessions.map(async session => {
|
||||
const page = await session.browser.newPage();
|
||||
await page.goto(
|
||||
`${origin}/test/components/simple-viewer.html` +
|
||||
`?file=/test/pdfs/tracemonkey.pdf`
|
||||
);
|
||||
await page.bringToFront();
|
||||
await page.waitForSelector(
|
||||
"[data-page-number='1'] .textLayer .endOfContent"
|
||||
);
|
||||
await page.waitForSelector(
|
||||
"[data-page-number='2'] .textLayer .endOfContent"
|
||||
);
|
||||
return [session.name, page];
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(pages.map(([, page]) => page.close()));
|
||||
});
|
||||
|
||||
it("must produce text spans in the text layer", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const count = await page.evaluate(
|
||||
() => document.querySelectorAll(".textLayer span").length
|
||||
);
|
||||
expect(count).withContext(`In ${browserName}`).toBeGreaterThan(0);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Scripting support with evaljs.pdf", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
const origin = new URL(global.integrationBaseUrl).origin;
|
||||
pages = await Promise.all(
|
||||
global.integrationSessions.map(async session => {
|
||||
const page = await session.browser.newPage();
|
||||
await page.goto(
|
||||
`${origin}/test/components/simple-viewer.html` +
|
||||
`?file=/test/pdfs/evaljs.pdf`
|
||||
);
|
||||
await page.bringToFront();
|
||||
await page.waitForSelector(getSelector("55R"));
|
||||
await page.waitForFunction(
|
||||
"window.pdfScriptingManager?.ready === true"
|
||||
);
|
||||
return [session.name, page];
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(pages.map(([, page]) => page.close()));
|
||||
});
|
||||
|
||||
it("must evaluate JavaScript entered in the input field", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.type(getSelector("55R"), "1 + 1");
|
||||
await page.click(getAnnotationSelector("57R"));
|
||||
await page.waitForFunction(
|
||||
`document.querySelector('${getSelector("56R")}').value !== ""`
|
||||
);
|
||||
const text = await page.$eval(getSelector("56R"), el => el.value);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual("2");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
1891
test/integration/stamp_editor_spec.mjs
Normal file
1891
test/integration/stamp_editor_spec.mjs
Normal file
File diff suppressed because it is too large
Load Diff
1209
test/integration/test_utils.mjs
Normal file
1209
test/integration/test_utils.mjs
Normal file
File diff suppressed because it is too large
Load Diff
119
test/integration/text_extractor_spec.mjs
Normal file
119
test/integration/text_extractor_spec.mjs
Normal file
@@ -0,0 +1,119 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { closePages, loadAndWait } from "./test_utils.mjs";
|
||||
|
||||
async function dispatchRequestTextContent(page, id) {
|
||||
return page.evaluate(requestId => {
|
||||
const event = new CustomEvent("requestTextContent", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: { requestId },
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}, id);
|
||||
}
|
||||
|
||||
async function getReportTextData(page) {
|
||||
await page.waitForFunction(() => window._reportTextData !== undefined);
|
||||
return page.evaluate(() => {
|
||||
const data = window._reportTextData;
|
||||
delete window._reportTextData;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
describe("PdfTextExtractor", () => {
|
||||
describe("Simple multi-page document", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("basicapi.pdf", ".textLayer .endOfContent");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that all text is extracted", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await dispatchRequestTextContent(page, 1);
|
||||
|
||||
const { text, requestId } = await getReportTextData(page);
|
||||
|
||||
expect(text).toEqual(
|
||||
[
|
||||
"Table Of Content",
|
||||
"Chapter 1 .......................................................... 2",
|
||||
"Paragraph 1.1 ...................................................... 3",
|
||||
"page 1 / 3",
|
||||
"Chapter 1",
|
||||
"page 2 / 3",
|
||||
"Paragraph 1.1",
|
||||
"Powered by TCPDF (www.tcpdf.org)",
|
||||
"page 3 / 3",
|
||||
].join("\n")
|
||||
);
|
||||
expect(requestId).toEqual(1);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Multi-page document, with disableAutoFetch=true set", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
".textLayer .endOfContent",
|
||||
null,
|
||||
null,
|
||||
{
|
||||
disableAutoFetch: true,
|
||||
disableStream: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("check that all text is extracted", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await dispatchRequestTextContent(page, 2);
|
||||
|
||||
const { text, requestId } = await getReportTextData(page);
|
||||
|
||||
expect(
|
||||
text.startsWith(
|
||||
"Trace-based Just-in-Time Type Specialization for Dynamic\nLanguages"
|
||||
)
|
||||
).toBeTrue();
|
||||
expect(
|
||||
text.endsWith(
|
||||
"Conference on Virtual Execution Environments, pages 83–93. ACM\nPress, 2007."
|
||||
)
|
||||
).toBeTrue();
|
||||
expect(text.length).toEqual(82804);
|
||||
expect(requestId).toEqual(2);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
39
test/integration/text_field_spec.mjs
Normal file
39
test/integration/text_field_spec.mjs
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Copyright 2024 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { closePages, getSelector, loadAndWait } from "./test_utils.mjs";
|
||||
|
||||
describe("Text field", () => {
|
||||
describe("Empty text field", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("file_pdfjs_form.pdf", getSelector("7R"));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must check that the field is empty although its appearance contains a white space", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const text = await page.$eval(getSelector("7R"), el => el.value);
|
||||
expect(text).withContext(`In ${browserName}`).toEqual("");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
399
test/integration/text_layer_images_spec.mjs
Normal file
399
test/integration/text_layer_images_spec.mjs
Normal file
@@ -0,0 +1,399 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
awaitPromise,
|
||||
closePages,
|
||||
getEditorSelector,
|
||||
getRect,
|
||||
loadAndWait,
|
||||
switchToEditor,
|
||||
waitForPointerUp,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
const switchToHighlight = switchToEditor.bind(null, "Highlight");
|
||||
|
||||
describe("Text layer images", () => {
|
||||
describe("basic", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"images.pdf",
|
||||
`.page[data-page-number = "1"] .endOfContent`,
|
||||
undefined,
|
||||
{
|
||||
// When running Firefox with Puppeteer, setting the
|
||||
// devicePixelRatio Puppeteer option does not properly set
|
||||
// the `window.devicePixelRatio` value. Set it manually.
|
||||
earlySetup: `() => { window.devicePixelRatio = 1 }`,
|
||||
},
|
||||
{ imagesRightClickMinSize: 16 },
|
||||
{ width: 800, height: 600, devicePixelRatio: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should render images in the text layer", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const images = await page.$$eval(
|
||||
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`,
|
||||
els => els.map(el => JSON.stringify(el.getBoundingClientRect()))
|
||||
);
|
||||
|
||||
expect(images.length).withContext(`In ${browserName}`).toEqual(5);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("when right-clicking an image it should get the contents", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const imageCanvas = await page.$(
|
||||
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
|
||||
);
|
||||
|
||||
expect(await page.evaluate(el => el.width, imageCanvas))
|
||||
.withContext(`Initial width, in ${browserName}`)
|
||||
.toBe(0);
|
||||
expect(await page.evaluate(el => el.height, imageCanvas))
|
||||
.withContext(`Initial height, in ${browserName}`)
|
||||
.toBe(0);
|
||||
|
||||
await imageCanvas.click({ button: "right" });
|
||||
|
||||
expect(await page.evaluate(el => el.width, imageCanvas))
|
||||
.withContext(`Final width, in ${browserName}`)
|
||||
.toBeGreaterThan(0);
|
||||
expect(await page.evaluate(el => el.height, imageCanvas))
|
||||
.withContext(`Final height, in ${browserName}`)
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
expect(
|
||||
await page.evaluate(el => {
|
||||
const ctx = el.getContext("2d");
|
||||
const imageData = ctx.getImageData(0, 0, el.width, el.height);
|
||||
const pixels = new Uint32Array(imageData.data.buffer);
|
||||
const firstPixel = pixels[0];
|
||||
return pixels.some(pixel => pixel !== firstPixel);
|
||||
}, imageCanvas)
|
||||
)
|
||||
.withContext(`Image is not all the same pixel, in ${browserName}`)
|
||||
.toBe(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("transforms", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"images.pdf",
|
||||
`.page[data-page-number = "1"] .endOfContent`,
|
||||
undefined,
|
||||
{
|
||||
// When running Firefox with Puppeteer, setting the
|
||||
// devicePixelRatio Puppeteer option does not properly set
|
||||
// the `window.devicePixelRatio` value. Set it manually.
|
||||
earlySetup: `() => { window.devicePixelRatio = 1 }`,
|
||||
},
|
||||
{ imagesRightClickMinSize: 16 },
|
||||
{ width: 800, height: 600, devicePixelRatio: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("the three copies of the PDF.js logo have different rotations", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const getRotation = async nth =>
|
||||
page.evaluate(n => {
|
||||
const canvas = document.querySelectorAll(
|
||||
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
|
||||
)[n];
|
||||
const cssTransform = getComputedStyle(canvas).transform;
|
||||
if (cssTransform && cssTransform !== "none") {
|
||||
const matrixValues = cssTransform
|
||||
.slice(7, -1)
|
||||
.split(", ")
|
||||
.map(parseFloat);
|
||||
return (
|
||||
Math.atan2(matrixValues[1], matrixValues[0]) * (180 / Math.PI)
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}, nth);
|
||||
|
||||
const rotation1 = await getRotation(1);
|
||||
const rotation2 = await getRotation(2);
|
||||
const rotation4 = await getRotation(4);
|
||||
|
||||
expect(Math.abs(rotation1 - rotation2))
|
||||
.withContext(`Rotation between 1 and 2, in ${browserName}`)
|
||||
.toBeGreaterThan(10);
|
||||
expect(Math.abs(rotation1 - rotation4))
|
||||
.withContext(`Rotation between 1 and 4, in ${browserName}`)
|
||||
.toBeGreaterThan(10);
|
||||
expect(Math.abs(rotation2 - rotation4))
|
||||
.withContext(`Rotation between 2 and 4, in ${browserName}`)
|
||||
.toBeGreaterThan(10);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("the three copies of the PDF.js logo have the same size", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const getSize = async nth =>
|
||||
page.evaluate(n => {
|
||||
const canvas = document.querySelectorAll(
|
||||
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
|
||||
)[n];
|
||||
return { width: canvas.width, height: canvas.height };
|
||||
}, nth);
|
||||
|
||||
const size1 = await getSize(1);
|
||||
const size2 = await getSize(2);
|
||||
const size4 = await getSize(4);
|
||||
|
||||
const EPSILON = 3;
|
||||
|
||||
expect(size1.width)
|
||||
.withContext(`1-2 width, in ${browserName}`)
|
||||
.toBeCloseTo(size2.width, EPSILON);
|
||||
expect(size1.height)
|
||||
.withContext(`1-2 height, in ${browserName}`)
|
||||
.toBeCloseTo(size2.height, EPSILON);
|
||||
|
||||
expect(size1.width)
|
||||
.withContext(`1-4 width, in ${browserName}`)
|
||||
.toBeCloseTo(size4.width, EPSILON);
|
||||
expect(size1.height)
|
||||
.withContext(`1-4 height, in ${browserName}`)
|
||||
.toBeCloseTo(size4.height, EPSILON);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("trimming", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"bug_jpx.pdf",
|
||||
`.page[data-page-number = "1"] .endOfContent`,
|
||||
undefined,
|
||||
{
|
||||
// When running Firefox with Puppeteer, setting the
|
||||
// devicePixelRatio Puppeteer option does not properly set
|
||||
// the `window.devicePixelRatio` value. Set it manually.
|
||||
earlySetup: `() => { window.devicePixelRatio = 1 }`,
|
||||
},
|
||||
{ imagesRightClickMinSize: 16 },
|
||||
{ width: 800, height: 600, devicePixelRatio: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("no white border around black image", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const canvasHandle = await page.$(
|
||||
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
|
||||
);
|
||||
|
||||
await canvasHandle.click({ button: "right" });
|
||||
|
||||
expect(
|
||||
await page.evaluate(el => {
|
||||
const ctx = el.getContext("2d");
|
||||
const imageData = ctx.getImageData(0, 0, el.width, el.height);
|
||||
const pixels = new Uint32Array(imageData.data.buffer);
|
||||
return Array.from(pixels.filter(pixel => pixel !== 0xff000000));
|
||||
}, canvasHandle)
|
||||
)
|
||||
.withContext(`Image is all black, in ${browserName}`)
|
||||
.toEqual([]);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("trimming after rotation", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"image-rotated-black-white-ratio.pdf",
|
||||
`.page[data-page-number = "1"] .endOfContent`,
|
||||
undefined,
|
||||
{
|
||||
// When running Firefox with Puppeteer, setting the
|
||||
// devicePixelRatio Puppeteer option does not properly set
|
||||
// the `window.devicePixelRatio` value. Set it manually.
|
||||
earlySetup: `() => { window.devicePixelRatio = 1 }`,
|
||||
},
|
||||
{ imagesRightClickMinSize: 16 },
|
||||
{ width: 800, height: 600, devicePixelRatio: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("no white extra white around rotated image", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
const canvasHandle = await page.$(
|
||||
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
|
||||
);
|
||||
|
||||
await canvasHandle.click({ button: "right" });
|
||||
|
||||
expect(
|
||||
await page.evaluate(el => {
|
||||
const ctx = el.getContext("2d");
|
||||
const imageData = ctx.getImageData(0, 0, el.width, el.height);
|
||||
const pixels = new Uint32Array(imageData.data.buffer);
|
||||
const blackPixels = pixels.filter(
|
||||
pixel => pixel === 0xff000000
|
||||
).length;
|
||||
const whitePixels = pixels.filter(
|
||||
pixel => pixel === 0xffffffff
|
||||
).length;
|
||||
return blackPixels / (blackPixels + whitePixels);
|
||||
}, canvasHandle)
|
||||
)
|
||||
.withContext(`Image is 75% black, in ${browserName}`)
|
||||
.toBeCloseTo(0.75);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("free-highlighting on top of an image placeholder", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"images.pdf",
|
||||
`.page[data-page-number = "1"] .endOfContent`,
|
||||
undefined,
|
||||
{
|
||||
earlySetup: `() => { window.devicePixelRatio = 1 }`,
|
||||
},
|
||||
{
|
||||
imagesRightClickMinSize: 16,
|
||||
highlightEditorColors: "yellow=#FFFF00",
|
||||
},
|
||||
{ width: 800, height: 600, devicePixelRatio: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must create a free highlight when dragging on an image placeholder (bug 2034980)", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToHighlight(page);
|
||||
|
||||
const rect = await getRect(
|
||||
page,
|
||||
`.page[data-page-number="1"] > .textLayer > .textLayerImages > canvas`
|
||||
);
|
||||
|
||||
const x1 = rect.x + rect.width / 4;
|
||||
const y1 = rect.y + rect.height / 4;
|
||||
const x2 = rect.x + (3 * rect.width) / 4;
|
||||
const y2 = rect.y + (3 * rect.height) / 4;
|
||||
|
||||
const clickHandle = await waitForPointerUp(page);
|
||||
await page.mouse.move(x1, y1);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(x2, y2);
|
||||
await page.mouse.up();
|
||||
await awaitPromise(clickHandle);
|
||||
|
||||
await page.waitForSelector(getEditorSelector(0));
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("free-highlighting on a page with no images", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"empty.pdf",
|
||||
`.page[data-page-number = "1"] .textLayerImages`,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
imagesRightClickMinSize: 16,
|
||||
highlightEditorColors: "yellow=#FFFF00",
|
||||
},
|
||||
{ width: 800, height: 600, devicePixelRatio: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must create a free highlight when dragging on the empty image container", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await switchToHighlight(page);
|
||||
|
||||
const rect = await getRect(
|
||||
page,
|
||||
`.page[data-page-number="1"] > .textLayer > .textLayerImages`
|
||||
);
|
||||
|
||||
const x1 = rect.x + rect.width / 4;
|
||||
const y1 = rect.y + rect.height / 4;
|
||||
const x2 = rect.x + (3 * rect.width) / 4;
|
||||
const y2 = rect.y + (3 * rect.height) / 4;
|
||||
|
||||
const clickHandle = await waitForPointerUp(page);
|
||||
await page.mouse.move(x1, y1);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(x2, y2);
|
||||
await page.mouse.up();
|
||||
await awaitPromise(clickHandle);
|
||||
|
||||
await page.waitForSelector(getEditorSelector(0));
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
1357
test/integration/text_layer_spec.mjs
Normal file
1357
test/integration/text_layer_spec.mjs
Normal file
File diff suppressed because it is too large
Load Diff
547
test/integration/thumbnail_view_spec.mjs
Normal file
547
test/integration/thumbnail_view_spec.mjs
Normal file
@@ -0,0 +1,547 @@
|
||||
/* Copyright 2026 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
awaitPromise,
|
||||
closePages,
|
||||
FSI,
|
||||
getThumbnailSelector,
|
||||
kbFocusNext,
|
||||
loadAndWait,
|
||||
PDI,
|
||||
showViewsManager,
|
||||
} from "./test_utils.mjs";
|
||||
|
||||
function waitForThumbnailVisible(page, pageNum) {
|
||||
return page.waitForSelector(getThumbnailSelector(pageNum), { visible: true });
|
||||
}
|
||||
|
||||
async function waitForMenu(page, buttonSelector, visible = true) {
|
||||
return page.waitForFunction(
|
||||
(selector, vis) => {
|
||||
const button = document.querySelector(selector);
|
||||
if (!button) {
|
||||
return false;
|
||||
}
|
||||
return button.getAttribute("aria-expanded") === (vis ? "true" : "false");
|
||||
},
|
||||
{},
|
||||
buttonSelector,
|
||||
visible
|
||||
);
|
||||
}
|
||||
|
||||
describe("PDF Thumbnail View", () => {
|
||||
describe("Works without errors", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should render thumbnails without errors", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
|
||||
const thumbSelector =
|
||||
"#thumbnailsView .thumbnailImageContainer > img";
|
||||
await page.waitForSelector(thumbSelector, { visible: true });
|
||||
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
await page.waitForSelector(`${thumbSelector}[src^="blob:http:"]`, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
const title = await page.$eval(
|
||||
getThumbnailSelector(1),
|
||||
el => el.title
|
||||
);
|
||||
expect(title)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(`Page ${FSI}1${PDI} of ${FSI}14${PDI}`);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should have accessible label on resizer", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
|
||||
const ariaLabel = await page.$eval("#viewsManagerResizer", el =>
|
||||
el.getAttribute("aria-label")
|
||||
);
|
||||
expect(ariaLabel)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe("Sidebar resizer");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("The view is scrolled correctly", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
async function goToPage(page, number) {
|
||||
const handle = await page.evaluateHandle(
|
||||
num => [
|
||||
new Promise(resolve => {
|
||||
const container = document.getElementById("viewsManagerContent");
|
||||
container.addEventListener("scrollend", resolve, { once: true });
|
||||
// eslint-disable-next-line no-undef
|
||||
PDFViewerApplication.pdfLinkService.goToPage(num);
|
||||
}),
|
||||
],
|
||||
number
|
||||
);
|
||||
return awaitPromise(handle);
|
||||
}
|
||||
|
||||
it("should scroll the view", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
for (const pageNum of [14, 1, 13, 2]) {
|
||||
await goToPage(page, pageNum);
|
||||
const thumbSelector = getThumbnailSelector(pageNum);
|
||||
await page.waitForSelector(
|
||||
`.thumbnail ${thumbSelector}[aria-current="page"]`,
|
||||
{ visible: true }
|
||||
);
|
||||
await page.waitForSelector(
|
||||
`${thumbSelector} > img[src^="blob:http:"]`,
|
||||
{
|
||||
visible: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("The view is accessible with the keyboard", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#viewsManagerToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should navigate with the keyboard", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
await waitForThumbnailVisible(page, 2);
|
||||
await waitForThumbnailVisible(page, 3);
|
||||
|
||||
await kbFocusNext(page, "#viewsManagerSelectorButton");
|
||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
||||
await kbFocusNext(page, `#thumbnailsView ${getThumbnailSelector(1)}`);
|
||||
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.waitForSelector(
|
||||
`#thumbnailsView ${getThumbnailSelector(2)}:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await page.waitForSelector(`${getThumbnailSelector(1)}:focus`, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.waitForSelector(
|
||||
`#thumbnailsView ${getThumbnailSelector(3)}:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
const currentPage = await page.$eval(
|
||||
"#pageNumber",
|
||||
el => el.valueAsNumber
|
||||
);
|
||||
expect(currentPage).withContext(`In ${browserName}`).toBe(3);
|
||||
|
||||
await page.keyboard.press("End");
|
||||
await page.waitForSelector(
|
||||
`#thumbnailsView ${getThumbnailSelector(14)}:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
await page.keyboard.press("Home");
|
||||
await page.waitForSelector(
|
||||
`#thumbnailsView ${getThumbnailSelector(1)}:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("must navigate when a synthetic click is dispatched on the thumbnail image (bug 2034568)", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 3);
|
||||
|
||||
// Simulate a screen reader (e.g. NVDA) firing a synthetic click on
|
||||
// the <img> child rather than the thumbnailImageContainer button.
|
||||
await page.evaluate(() => {
|
||||
const img = document.querySelector(
|
||||
`.thumbnail[page-number="3"] .thumbnailImageContainer img`
|
||||
);
|
||||
img.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
|
||||
const currentPage = await page.$eval(
|
||||
"#pageNumber",
|
||||
el => el.valueAsNumber
|
||||
);
|
||||
expect(currentPage).withContext(`In ${browserName}`).toBe(3);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("The manage dropdown menu", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
async function enableMenuItems(page) {
|
||||
await page.evaluate(() => {
|
||||
document
|
||||
.querySelectorAll("#viewsManagerStatusActionOptions button")
|
||||
.forEach(button => {
|
||||
button.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it("should open with Enter key and remain open", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
await enableMenuItems(page);
|
||||
|
||||
// Focus the manage button
|
||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Press Enter to open the menu
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Verify first menu item can be focused
|
||||
await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Close menu with Escape
|
||||
await page.keyboard.press("Escape");
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton", false);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should open with Space key and remain open", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
await enableMenuItems(page);
|
||||
|
||||
// Focus the manage button
|
||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Press Space to open the menu
|
||||
await page.keyboard.press(" ");
|
||||
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Verify first menu item can be focused
|
||||
await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Navigate menu items with arrow keys
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.waitForSelector("#viewsManagerStatusActionCut:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Menu should still be open
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Close menu with Escape
|
||||
await page.keyboard.press("Escape");
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton", false);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Checkbox accessibility", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should have a title on the checkbox", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
const title = await page.$eval(
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]`,
|
||||
el => el.title
|
||||
);
|
||||
expect(title)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toBe(`Select page ${FSI}1${PDI}`);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Menu keyboard navigation with multi-character keys (bug 2016212)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"page_with_number_and_link.pdf",
|
||||
"#viewsManagerSelectorButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must navigate menus with ArrowDown and Tab keys", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
// Focus the views manager selector button
|
||||
await page.waitForSelector("#viewsManagerSelectorButton", {
|
||||
visible: true,
|
||||
});
|
||||
await page.focus("#viewsManagerSelectorButton");
|
||||
|
||||
// Open menu with Enter key
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
// Wait for menu to be expanded
|
||||
await waitForMenu(page, "#viewsManagerSelectorButton");
|
||||
|
||||
// Check that focus moved to the first menu button (pages)
|
||||
await page.waitForSelector("#thumbnailsViewMenu:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Press ArrowDown to navigate to second item
|
||||
await page.keyboard.press("ArrowDown");
|
||||
|
||||
// Should now be on outlines button
|
||||
await page.waitForSelector("#outlinesViewMenu:focus", {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Press Tab to move to the manage button (should close views menu)
|
||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
||||
|
||||
// Wait for views manager menu to be collapsed
|
||||
await waitForMenu(page, "#viewsManagerSelectorButton", false);
|
||||
|
||||
// Open manage menu with Space key
|
||||
await page.keyboard.press(" ");
|
||||
|
||||
// Wait for manage menu to be expanded
|
||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Views manager status visibility (bug 2016656)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"page_with_number_and_link.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should show the manage button in thumbnail view and hide it in outline view", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
// The status bar (Select pages + Manage button) must be visible in
|
||||
// thumbnail view.
|
||||
await page.waitForSelector("#viewsManagerStatus", { visible: true });
|
||||
|
||||
// Switch to outline view.
|
||||
await page.click("#viewsManagerSelectorButton");
|
||||
await page.waitForSelector("#outlinesViewMenu", { visible: true });
|
||||
await page.click("#outlinesViewMenu");
|
||||
await page.waitForSelector("#outlinesView", { visible: true });
|
||||
|
||||
// The status bar must no longer be visible in outline view.
|
||||
await page.waitForSelector("#viewsManagerStatus", { hidden: true });
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Checkbox keyboard navigation", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"tracemonkey.pdf",
|
||||
"#viewsManagerToggleButton",
|
||||
null,
|
||||
null,
|
||||
{ enableSplitMerge: true }
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should focus checkboxes with Tab key", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
|
||||
// Focus the first thumbnail button
|
||||
await kbFocusNext(page, getThumbnailSelector(1));
|
||||
|
||||
// Verify we're on the first thumbnail
|
||||
await page.waitForSelector(`${getThumbnailSelector(1)}:focus`, {
|
||||
visible: true,
|
||||
});
|
||||
|
||||
// Tab to checkbox
|
||||
await kbFocusNext(
|
||||
page,
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]`
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should navigate checkboxes with arrow keys", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await showViewsManager(page);
|
||||
await waitForThumbnailVisible(page, 1);
|
||||
await waitForThumbnailVisible(page, 2);
|
||||
|
||||
// Navigate to first checkbox
|
||||
await kbFocusNext(
|
||||
page,
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]`
|
||||
);
|
||||
|
||||
// Verify first checkbox is focused
|
||||
await page.waitForSelector(
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
// Navigate to next checkbox with ArrowDown
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await page.waitForSelector(
|
||||
`.thumbnail[page-number="2"] input[type="checkbox"]:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
|
||||
// Navigate back with ArrowUp
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await page.waitForSelector(
|
||||
`.thumbnail[page-number="1"] input[type="checkbox"]:focus`,
|
||||
{ visible: true }
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
2069
test/integration/viewer_spec.mjs
Normal file
2069
test/integration/viewer_spec.mjs
Normal file
File diff suppressed because it is too large
Load Diff
1
test/pdfs/.gitattributes
vendored
Normal file
1
test/pdfs/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.pdf binary
|
||||
933
test/pdfs/.gitignore
vendored
Normal file
933
test/pdfs/.gitignore
vendored
Normal file
@@ -0,0 +1,933 @@
|
||||
*.pdf
|
||||
*.error
|
||||
|
||||
!boundingBox_invalid.pdf
|
||||
!pdkids.pdf
|
||||
!tracemonkey.pdf
|
||||
!TrueType_without_cmap.pdf
|
||||
!franz.pdf
|
||||
!franz_2.pdf
|
||||
!fraction-highlight.pdf
|
||||
!german-umlaut-r.pdf
|
||||
!issue13269.pdf
|
||||
!xref_command_missing.pdf
|
||||
!issue1155r.pdf
|
||||
!issue2017r.pdf
|
||||
!bug1727053.pdf
|
||||
!issue18408_reduced.pdf
|
||||
!bug1907000_reduced.pdf
|
||||
!knockout_blend_multiply.pdf
|
||||
!knockout_inner_backdrop.pdf
|
||||
!knockout_isolated_overlap.pdf
|
||||
!knockout_nested.pdf
|
||||
!knockout_nested_group_alpha.pdf
|
||||
!knockout_nonisolated_sparse.pdf
|
||||
!knockout_smask.pdf
|
||||
!SimFang-variant.pdf
|
||||
!bug1953099.pdf
|
||||
!issue11913.pdf
|
||||
!issue2391-1.pdf
|
||||
!issue2391-2.pdf
|
||||
!issue14046.pdf
|
||||
!issue7891_bc1.pdf
|
||||
!issue14881.pdf
|
||||
!issue3214.pdf
|
||||
!issue4665.pdf
|
||||
!checkbox-bad-appearance.pdf
|
||||
!issue4684.pdf
|
||||
!issue8092.pdf
|
||||
!issue19802.pdf
|
||||
!issue5256.pdf
|
||||
!issue5801.pdf
|
||||
!issue5946.pdf
|
||||
!issue5972.pdf
|
||||
!issue5874.pdf
|
||||
!issue5808.pdf
|
||||
!issue6179_reduced.pdf
|
||||
!issue6204.pdf
|
||||
!issue6342.pdf
|
||||
!issue6652.pdf
|
||||
!issue6782.pdf
|
||||
!issue6901.pdf
|
||||
!issue6961.pdf
|
||||
!issue6962.pdf
|
||||
!issue7020.pdf
|
||||
!issue7101.pdf
|
||||
!issue7115.pdf
|
||||
!issue7180.pdf
|
||||
!issue7769.pdf
|
||||
!issue7200.pdf
|
||||
!issue7229.pdf
|
||||
!issue7403.pdf
|
||||
!issue7406.pdf
|
||||
!issue7426.pdf
|
||||
!issue7439.pdf
|
||||
!issue7847_radial.pdf
|
||||
!issue8844.pdf
|
||||
!issue17056.pdf
|
||||
!issue17679.pdf
|
||||
!issue17679_2.pdf
|
||||
!issue18030.pdf
|
||||
!issue18042.pdf
|
||||
!issue18059.pdf
|
||||
!issue14953.pdf
|
||||
!issue15367.pdf
|
||||
!issue15372.pdf
|
||||
!issue7446.pdf
|
||||
!issue7492.pdf
|
||||
!issue7544.pdf
|
||||
!issue7507.pdf
|
||||
!issue6931_reduced.pdf
|
||||
!issue14847.pdf
|
||||
!issue17846.pdf
|
||||
!doc_actions.pdf
|
||||
!issue7580.pdf
|
||||
!issue7598.pdf
|
||||
!issue12750.pdf
|
||||
!issue7665.pdf
|
||||
!issue7696.pdf
|
||||
!issue7835.pdf
|
||||
!issue11922_reduced.pdf
|
||||
!issue7855.pdf
|
||||
!XiaoBiaoSong.pdf
|
||||
!issue11144_reduced.pdf
|
||||
!issue7872.pdf
|
||||
!issue7901.pdf
|
||||
!issue8061.pdf
|
||||
!bug1721218_reduced.pdf
|
||||
!issue8088.pdf
|
||||
!issue8125.pdf
|
||||
!issue8229.pdf
|
||||
!issue8276_reduced.pdf
|
||||
!issue8372.pdf
|
||||
!issue9713.pdf
|
||||
!xfa_filled_imm1344e.pdf
|
||||
!issue8424.pdf
|
||||
!issue8480.pdf
|
||||
!bug1650302_reduced.pdf
|
||||
!issue18816.pdf
|
||||
!issue8570.pdf
|
||||
!issue8697.pdf
|
||||
!issue8702.pdf
|
||||
!structure_simple.pdf
|
||||
!issue12823.pdf
|
||||
!issue8707.pdf
|
||||
!issue8798r.pdf
|
||||
!issue8823.pdf
|
||||
!issue9084.pdf
|
||||
!issue12963.pdf
|
||||
!issue9105_reduced.pdf
|
||||
!issue19484_2.pdf
|
||||
!issue9105_other.pdf
|
||||
!issue9252.pdf
|
||||
!issue9262_reduced.pdf
|
||||
!issue9291.pdf
|
||||
!issue9418.pdf
|
||||
!issue9458.pdf
|
||||
!issue9655_reduced.pdf
|
||||
!issue9915_reduced.pdf
|
||||
!bug854315.pdf
|
||||
!issue9940.pdf
|
||||
!issue10388_reduced.pdf
|
||||
!issue10438_reduced.pdf
|
||||
!issue10529.pdf
|
||||
!issue10542_reduced.pdf
|
||||
!issue10665_reduced.pdf
|
||||
!issue11016_reduced.pdf
|
||||
!issue19532.pdf
|
||||
!issue15516_reduced.pdf
|
||||
!issue11045.pdf
|
||||
!bug1057544.pdf
|
||||
!issue11150_reduced.pdf
|
||||
!issue6127.pdf
|
||||
!issue7891_bc0.pdf
|
||||
!issue11242_reduced.pdf
|
||||
!issue16176.pdf
|
||||
!issue16287.pdf
|
||||
!issue17064_readonly.pdf
|
||||
!issue11279.pdf
|
||||
!issue11362.pdf
|
||||
!issue13325_reduced.pdf
|
||||
!issue11578_reduced.pdf
|
||||
!issue11651.pdf
|
||||
!issue11878_reduced.pdf
|
||||
!issue13916.pdf
|
||||
!issue14023.pdf
|
||||
!issue14438.pdf
|
||||
!issue14999_reduced.pdf
|
||||
!bad-PageLabels.pdf
|
||||
!decodeACSuccessive.pdf
|
||||
!issue13003.pdf
|
||||
!filled-background.pdf
|
||||
!ArabicCIDTrueType.pdf
|
||||
!ThuluthFeatures.pdf
|
||||
!arial_unicode_ab_cidfont.pdf
|
||||
!arial_unicode_en_cidfont.pdf
|
||||
!asciihexdecode.pdf
|
||||
!bug766086.pdf
|
||||
!bug793632.pdf
|
||||
!issue14821.pdf
|
||||
!bug1020858.pdf
|
||||
!prefilled_f1040.pdf
|
||||
!bug1050040.pdf
|
||||
!issue18986.pdf
|
||||
!bug1200096.pdf
|
||||
!bug1068432.pdf
|
||||
!issue12295.pdf
|
||||
!bug1146106.pdf
|
||||
!issue13447.pdf
|
||||
!bug1245391_reduced.pdf
|
||||
!bug1252420.pdf
|
||||
!bug1513120_reduced.pdf
|
||||
!bug1538111.pdf
|
||||
!bug1552113.pdf
|
||||
!issue6132.pdf
|
||||
!issue9949.pdf
|
||||
!bug1308536.pdf
|
||||
!bug1337429.pdf
|
||||
!bug1606566.pdf
|
||||
!issue5564_reduced.pdf
|
||||
!canvas.pdf
|
||||
!bug1132849.pdf
|
||||
!issue6894.pdf
|
||||
!issue5804.pdf
|
||||
!issue11131_reduced.pdf
|
||||
!Pages-tree-refs.pdf
|
||||
!ShowText-ShadingPattern.pdf
|
||||
!complex_ttf_font.pdf
|
||||
!issue3694_reduced.pdf
|
||||
!extgstate.pdf
|
||||
!issue4706.pdf
|
||||
!rotation.pdf
|
||||
!simpletype3font.pdf
|
||||
!Type3WordSpacing.pdf
|
||||
!IndexedCS_negative_and_high.pdf
|
||||
!sizes.pdf
|
||||
!javauninstall-7r.pdf
|
||||
!file_url_link.pdf
|
||||
!multiple-filters-length-zero.pdf
|
||||
!non-embedded-NuptialScript.pdf
|
||||
!issue3205r.pdf
|
||||
!issue3207r.pdf
|
||||
!issue3263r.pdf
|
||||
!issue3879r.pdf
|
||||
!issue5686.pdf
|
||||
!issue3928.pdf
|
||||
!issue8565.pdf
|
||||
!clippath.pdf
|
||||
!issue19800.pdf
|
||||
!issue8795_reduced.pdf
|
||||
!bug1755507.pdf
|
||||
!close-path-bug.pdf
|
||||
!issue6019.pdf
|
||||
!issue6621.pdf
|
||||
!issue6286.pdf
|
||||
!issue13107_reduced.pdf
|
||||
!issue1055r.pdf
|
||||
!issue11713.pdf
|
||||
!issue1293r.pdf
|
||||
!issue11931.pdf
|
||||
!issue1655r.pdf
|
||||
!issue6541.pdf
|
||||
!issue10640.pdf
|
||||
!issue2948.pdf
|
||||
!issue6231_1.pdf
|
||||
!issue10402.pdf
|
||||
!issue7074_reduced.pdf
|
||||
!issue6413.pdf
|
||||
!issue4630.pdf
|
||||
!issue4909.pdf
|
||||
!scorecard_reduced.pdf
|
||||
!issue5084.pdf
|
||||
!issue8960_reduced.pdf
|
||||
!issue5202.pdf
|
||||
!images_1bit_grayscale.pdf
|
||||
!issue5280.pdf
|
||||
!issue12399_reduced.pdf
|
||||
!annotation-ink-without-appearance.pdf
|
||||
!issue5677.pdf
|
||||
!issue5954.pdf
|
||||
!issue6612.pdf
|
||||
!alphatrans.pdf
|
||||
!issue14200.pdf
|
||||
!pattern_text_embedded_font.pdf
|
||||
!devicen.pdf
|
||||
!cmykjpeg.pdf
|
||||
!issue840.pdf
|
||||
!160F-2019.pdf
|
||||
!issue4402_reduced.pdf
|
||||
!issue16263.pdf
|
||||
!issue845r.pdf
|
||||
!issue3405r.pdf
|
||||
!issue14130.pdf
|
||||
!issue7339_reduced.pdf
|
||||
!issue3438.pdf
|
||||
!issue11403_reduced.pdf
|
||||
!ContentStreamNoCycleType3insideType3.pdf
|
||||
!ContentStreamCycleType3insideType3.pdf
|
||||
!issue2074.pdf
|
||||
!issue18117.pdf
|
||||
!scan-bad.pdf
|
||||
!issue13561_reduced.pdf
|
||||
!bug847420.pdf
|
||||
!bug860632.pdf
|
||||
!bug894572.pdf
|
||||
!bug911034.pdf
|
||||
!issue19695.pdf
|
||||
!bug1108301.pdf
|
||||
!issue10301.pdf
|
||||
!bug1157493.pdf
|
||||
!issue15910.pdf
|
||||
!issue4260_reduced.pdf
|
||||
!bug1250079.pdf
|
||||
!bug1473809.pdf
|
||||
!issue12120_reduced.pdf
|
||||
!pdfjsbad1586.pdf
|
||||
!standard_fonts.pdf
|
||||
!freeculture.pdf
|
||||
!issue14802.pdf
|
||||
!issue6006.pdf
|
||||
!pdfkit_compressed.pdf
|
||||
!TAMReview.pdf
|
||||
!pr4922.pdf
|
||||
!pr6531_1.pdf
|
||||
!pr6531_2.pdf
|
||||
!pr7352.pdf
|
||||
!pr19449.pdf
|
||||
!bug900822.pdf
|
||||
!bug1392647.pdf
|
||||
!issue918.pdf
|
||||
!bug920426.pdf
|
||||
!issue1905.pdf
|
||||
!issue2833.pdf
|
||||
!issue2931.pdf
|
||||
!issue3323.pdf
|
||||
!issue4304.pdf
|
||||
!issue9017_reduced.pdf
|
||||
!issue4379.pdf
|
||||
!issue4550.pdf
|
||||
!issue13316_reduced.pdf
|
||||
!issue15977_reduced.pdf
|
||||
!issue4575.pdf
|
||||
!colorspace_atan.pdf
|
||||
!bug1011159.pdf
|
||||
!issue5734.pdf
|
||||
!issue4875.pdf
|
||||
!issue11740_reduced.pdf
|
||||
!issue12705.pdf
|
||||
!issue4881.pdf
|
||||
!issue5994.pdf
|
||||
!issue6151.pdf
|
||||
!rotated.pdf
|
||||
!issue1249.pdf
|
||||
!issue1171.pdf
|
||||
!smaskdim.pdf
|
||||
!endchar.pdf
|
||||
!type4psfunc.pdf
|
||||
!issue1350.pdf
|
||||
!S2.pdf
|
||||
!glyph_accent.pdf
|
||||
!personwithdog.pdf
|
||||
!find_all.pdf
|
||||
!helloworld-bad.pdf
|
||||
!zerowidthline.pdf
|
||||
!colorspace_cos.pdf
|
||||
!issue17554.pdf
|
||||
!issue13242.pdf
|
||||
!js-colors.pdf
|
||||
!annotation-line-without-appearance-empty-Rect.pdf
|
||||
!issue12841_reduced.pdf
|
||||
!bug868745.pdf
|
||||
!mmtype1.pdf
|
||||
!issue4436r.pdf
|
||||
!issue5704.pdf
|
||||
!issue5751.pdf
|
||||
!bug893730.pdf
|
||||
!bug864847.pdf
|
||||
!issue15629.pdf
|
||||
!issue1002.pdf
|
||||
!issue925.pdf
|
||||
!issue2840.pdf
|
||||
!issue4061.pdf
|
||||
!issue4668.pdf
|
||||
!issue13226.pdf
|
||||
!PDFJS-7562-reduced.pdf
|
||||
!issue11768_reduced.pdf
|
||||
!issue5039.pdf
|
||||
!issue14117.pdf
|
||||
!issue5070.pdf
|
||||
!issue5238.pdf
|
||||
!jp2k-resetprob.pdf
|
||||
!issue5244.pdf
|
||||
!issue5291.pdf
|
||||
!issue4398.pdf
|
||||
!issue5421.pdf
|
||||
!issue5470.pdf
|
||||
!issue5501.pdf
|
||||
!issue5599.pdf
|
||||
!issue17147.pdf
|
||||
!issue5747.pdf
|
||||
!issue6099.pdf
|
||||
!issue6336.pdf
|
||||
!issue6387.pdf
|
||||
!issue6410.pdf
|
||||
!issue11124.pdf
|
||||
!issue8586.pdf
|
||||
!jbig2_symbol_offset.pdf
|
||||
!gradientfill.pdf
|
||||
!bug903856.pdf
|
||||
!issue14618.pdf
|
||||
!bug850854.pdf
|
||||
!issue12810.pdf
|
||||
!bug866395.pdf
|
||||
!issue12010_reduced.pdf
|
||||
!issue10572.pdf
|
||||
!issue11718_reduced.pdf
|
||||
!bug1027533.pdf
|
||||
!bug1028735.pdf
|
||||
!bug1046314.pdf
|
||||
!bug1065245.pdf
|
||||
!issue6769.pdf
|
||||
!bug1151216.pdf
|
||||
!issue8111.pdf
|
||||
!bug1175962.pdf
|
||||
!bug1020226.pdf
|
||||
!issue9534_reduced.pdf
|
||||
!attachment.pdf
|
||||
!basicapi.pdf
|
||||
!issue15590.pdf
|
||||
!issue15594_reduced.pdf
|
||||
!issue2884_reduced.pdf
|
||||
!mixedfonts.pdf
|
||||
!shading_extend.pdf
|
||||
!noembed-identity.pdf
|
||||
!noembed-identity-2.pdf
|
||||
!noembed-jis7.pdf
|
||||
!issue12504.pdf
|
||||
!noembed-eucjp.pdf
|
||||
!bug1627427_reduced.pdf
|
||||
!noembed-sjis.pdf
|
||||
!vertical.pdf
|
||||
!issue13343.pdf
|
||||
!ZapfDingbats.pdf
|
||||
!bug878026.pdf
|
||||
!issue1045.pdf
|
||||
!issue5010.pdf
|
||||
!issue10339_reduced.pdf
|
||||
!issue15557.pdf
|
||||
!issue4934.pdf
|
||||
!issue4650.pdf
|
||||
!issue6721_reduced.pdf
|
||||
!issue3025.pdf
|
||||
!bug1365930.pdf
|
||||
!french_diacritics.pdf
|
||||
!issue2099-1.pdf
|
||||
!issue3371.pdf
|
||||
!issue2956.pdf
|
||||
!issue2537r.pdf
|
||||
!issue269_1.pdf
|
||||
!bug946506.pdf
|
||||
!issue3885.pdf
|
||||
!issue11697_reduced.pdf
|
||||
!bug859204.pdf
|
||||
!annotation-tx.pdf
|
||||
!annotation-tx2.pdf
|
||||
!annotation-tx3.pdf
|
||||
!coons-allflags-withfunction.pdf
|
||||
!tensor-allflags-withfunction.pdf
|
||||
!issue10084_reduced.pdf
|
||||
!issue4246.pdf
|
||||
!issue11915.pdf
|
||||
!js-authors.pdf
|
||||
!issue4461.pdf
|
||||
!issue4573.pdf
|
||||
!issue4722.pdf
|
||||
!bug1811668_reduced.pdf
|
||||
!issue4800.pdf
|
||||
!issue9243.pdf
|
||||
!issue13147.pdf
|
||||
!issue11477_reduced.pdf
|
||||
!text_clip_cff_cid.pdf
|
||||
!issue4801.pdf
|
||||
!issue5334.pdf
|
||||
!annotation-caret-ink.pdf
|
||||
!bug1186827.pdf
|
||||
!issue12706.pdf
|
||||
!issue215.pdf
|
||||
!issue5044.pdf
|
||||
!issue1512r.pdf
|
||||
!issue2128r.pdf
|
||||
!bug1703683_page2_reduced.pdf
|
||||
!issue5540.pdf
|
||||
!issue15893_reduced.pdf
|
||||
!issue5549.pdf
|
||||
!visibility_expressions.pdf
|
||||
!issue5475.pdf
|
||||
!issue10519_reduced.pdf
|
||||
!annotation-border-styles.pdf
|
||||
!colorspace_sin.pdf
|
||||
!IdentityToUnicodeMap_charCodeOf.pdf
|
||||
!PDFJS-9279-reduced.pdf
|
||||
!issue5481.pdf
|
||||
!resetform.pdf
|
||||
!issue5567.pdf
|
||||
!issue5701.pdf
|
||||
!issue6769_no_matrix.pdf
|
||||
!issue12007_reduced.pdf
|
||||
!issue5896.pdf
|
||||
!issue6010_1.pdf
|
||||
!issue6010_2.pdf
|
||||
!issue6068.pdf
|
||||
!issue6081.pdf
|
||||
!issue6069.pdf
|
||||
!issue6106.pdf
|
||||
!issue6296.pdf
|
||||
!issue19848.pdf
|
||||
!bug852992_reduced.pdf
|
||||
!issue13271.pdf
|
||||
!issue6298.pdf
|
||||
!issue6889.pdf
|
||||
!issue11473.pdf
|
||||
!bug1001080.pdf
|
||||
!issue15716.pdf
|
||||
!bug1671312_reduced.pdf
|
||||
!bug1671312_ArialNarrow.pdf
|
||||
!issue17848.pdf
|
||||
!issue6108.pdf
|
||||
!issue6113.pdf
|
||||
!openoffice.pdf
|
||||
!js-buttons.pdf
|
||||
!issue7014.pdf
|
||||
!issue19484_1.pdf
|
||||
!issue8187.pdf
|
||||
!annotation-link-text-popup.pdf
|
||||
!issue9278.pdf
|
||||
!annotation-text-without-popup.pdf
|
||||
!annotation-underline.pdf
|
||||
!issue13193.pdf
|
||||
!annotation-underline-without-appearance.pdf
|
||||
!issue269_2.pdf
|
||||
!issue13372.pdf
|
||||
!annotation-strikeout.pdf
|
||||
!annotation-strikeout-without-appearance.pdf
|
||||
!annotation-squiggly.pdf
|
||||
!issue14256.pdf
|
||||
!annotation-squiggly-without-appearance.pdf
|
||||
!annotation-highlight.pdf
|
||||
!annotation-highlight-without-appearance.pdf
|
||||
!issue12418_reduced.pdf
|
||||
!annotation-freetext.pdf
|
||||
!annotation-line.pdf
|
||||
!evaljs.pdf
|
||||
!issue12798_page1_reduced.pdf
|
||||
!annotation-line-without-appearance.pdf
|
||||
!bug1669099.pdf
|
||||
!annotation-square-circle.pdf
|
||||
!bug1942064.pdf
|
||||
!annotation-square-circle-without-appearance.pdf
|
||||
!annotation-stamp.pdf
|
||||
!issue14048.pdf
|
||||
!issue11656.pdf
|
||||
!annotation-fileattachment.pdf
|
||||
!named_dest_collision_for_editor.pdf
|
||||
!annotation-text-widget.pdf
|
||||
!issue7454.pdf
|
||||
!issue15443.pdf
|
||||
!annotation-choice-widget.pdf
|
||||
!issue10900.pdf
|
||||
!annotation-button-widget.pdf
|
||||
!annotation-polyline-polygon.pdf
|
||||
!annotation-polyline-polygon-without-appearance.pdf
|
||||
!zero_descent.pdf
|
||||
!operator-in-TJ-array.pdf
|
||||
!issue7878.pdf
|
||||
!font_ascent_descent.pdf
|
||||
!listbox_actions.pdf
|
||||
!issue11442_reduced.pdf
|
||||
!issue11549_reduced.pdf
|
||||
!issue8097_reduced.pdf
|
||||
!issue15262.pdf
|
||||
!issue17904.pdf
|
||||
!bug1743245.pdf
|
||||
!quadpoints.pdf
|
||||
!transparent.pdf
|
||||
!issue19326.pdf
|
||||
!issue13931.pdf
|
||||
!issue19474.pdf
|
||||
!xobject-image.pdf
|
||||
!issue15441.pdf
|
||||
!issue6605.pdf
|
||||
!ccitt_EndOfBlock_false.pdf
|
||||
!issue9972-1.pdf
|
||||
!issue9972-2.pdf
|
||||
!issue9972-3.pdf
|
||||
!tiling-pattern-box.pdf
|
||||
!tiling-pattern-large-steps.pdf
|
||||
!issue13201.pdf
|
||||
!issue14462_reduced.pdf
|
||||
!issue11555.pdf
|
||||
!issue12337.pdf
|
||||
!pr12564.pdf
|
||||
!pr12828.pdf
|
||||
!secHandler.pdf
|
||||
!issue14297.pdf
|
||||
!rc_annotation.pdf
|
||||
!issue14267.pdf
|
||||
!PDFBOX-4352-0.pdf
|
||||
!REDHAT-1531897-0.pdf
|
||||
!xfa_issue14315.pdf
|
||||
!poppler-67295-0.pdf
|
||||
!poppler-85140-0.pdf
|
||||
!issue15012.pdf
|
||||
!issue19176.pdf
|
||||
!bug1947248_text.pdf
|
||||
!bug1947248_forms.pdf
|
||||
!issue15150.pdf
|
||||
!poppler-395-0-fuzzed.pdf
|
||||
!issue14165.pdf
|
||||
!GHOSTSCRIPT-698804-1-fuzzed.pdf
|
||||
!issue14814.pdf
|
||||
!poppler-91414-0-53.pdf
|
||||
!poppler-91414-0-54.pdf
|
||||
!poppler-742-0-fuzzed.pdf
|
||||
!poppler-937-0-fuzzed.pdf
|
||||
!PDFBOX-3148-2-fuzzed.pdf
|
||||
!poppler-90-0-fuzzed.pdf
|
||||
!issue14415.pdf
|
||||
!issue14307.pdf
|
||||
!issue18645.pdf
|
||||
!issue14497.pdf
|
||||
!bug1799927.pdf
|
||||
!issue14502.pdf
|
||||
!issue13211.pdf
|
||||
!issue14627.pdf
|
||||
!issue14862.pdf
|
||||
!issue14705.pdf
|
||||
!bug1771477.pdf
|
||||
!bug1724918.pdf
|
||||
!issue15053.pdf
|
||||
!bug1675139.pdf
|
||||
!issue15092.pdf
|
||||
!bug1782186.pdf
|
||||
!tracemonkey_a11y.pdf
|
||||
!bug1782564.pdf
|
||||
!issue15340.pdf
|
||||
!bug1795263.pdf
|
||||
!issue15597.pdf
|
||||
!bug1796741.pdf
|
||||
!textfields.pdf
|
||||
!freetext_no_appearance.pdf
|
||||
!issue15690.pdf
|
||||
!bug1802888.pdf
|
||||
!issue15759.pdf
|
||||
!issue18823.pdf
|
||||
!pr20043.pdf
|
||||
!issue15753.pdf
|
||||
!issue15789.pdf
|
||||
!fields_order.pdf
|
||||
!issue15815.pdf
|
||||
!issue15818.pdf
|
||||
!autoprint.pdf
|
||||
!bug1811694.pdf
|
||||
!bug1811510.pdf
|
||||
!issue20324.pdf
|
||||
!bug1815476.pdf
|
||||
!issue16021.pdf
|
||||
!bug1770750.pdf
|
||||
!issue19971.pdf
|
||||
!issue16063.pdf
|
||||
!issue19389.pdf
|
||||
!issue16067.pdf
|
||||
!bug1820909.1.pdf
|
||||
!issue16221.pdf
|
||||
!issue16224.pdf
|
||||
!issue16278.pdf
|
||||
!copy_paste_ligatures.pdf
|
||||
!issue16316.pdf
|
||||
!issue14565.pdf
|
||||
!multiline.pdf
|
||||
!bug1825002.pdf
|
||||
!issue14755.pdf
|
||||
!issue16473.pdf
|
||||
!bug1529502.pdf
|
||||
!issue16500.pdf
|
||||
!issue16538.pdf
|
||||
!freetexts.pdf
|
||||
!issue16553.pdf
|
||||
!empty.pdf
|
||||
!rotated_freetexts.pdf
|
||||
!issue16633.pdf
|
||||
!bug1844576.pdf
|
||||
!bug1844583.pdf
|
||||
!annotation_hidden_print.pdf
|
||||
!annotation_hidden_noview.pdf
|
||||
!widget_hidden_print.pdf
|
||||
!empty_protected.pdf
|
||||
!tagged_stamp.pdf
|
||||
!bug1851498.pdf
|
||||
!issue17065.pdf
|
||||
!issue17069.pdf
|
||||
!issue17215.pdf
|
||||
!bug1863910.pdf
|
||||
!bug1865341.pdf
|
||||
!bug1872721.pdf
|
||||
!bug1871353.pdf
|
||||
!bug1871353.1.pdf
|
||||
!file_pdfjs_form.pdf
|
||||
!issue17492.pdf
|
||||
!issue17540.pdf
|
||||
!bug1669097.pdf
|
||||
!issue17671.pdf
|
||||
!bug1868759.pdf
|
||||
!issue17730.pdf
|
||||
!bug1883609.pdf
|
||||
!issue19550.pdf
|
||||
!issue20453.pdf
|
||||
!issue17808.pdf
|
||||
!issue17871_bottom_right.pdf
|
||||
!issue17871_top_right.pdf
|
||||
!bug1889122.pdf
|
||||
!issue17929.pdf
|
||||
!issue12213.pdf
|
||||
!tracemonkey_freetext.pdf
|
||||
!issue17998.pdf
|
||||
!pdfjs_wikipedia.pdf
|
||||
!bug1539074.pdf
|
||||
!bug1539074.1.pdf
|
||||
!issue18305.pdf
|
||||
!issue18360.pdf
|
||||
!issue18099_reduced.pdf
|
||||
!file_pdfjs_test.pdf
|
||||
!issue18536.pdf
|
||||
!issue18561.pdf
|
||||
!highlights.pdf
|
||||
!highlight.pdf
|
||||
!bug1708040.pdf
|
||||
!issue18694.pdf
|
||||
!issue18693.pdf
|
||||
!bug1918115.pdf
|
||||
!bug1919513.pdf
|
||||
!issue16038.pdf
|
||||
!highlight_popup.pdf
|
||||
!issue18072.pdf
|
||||
!stamps.pdf
|
||||
!issue15096.pdf
|
||||
!issue18036.pdf
|
||||
!issue18894.pdf
|
||||
!bug1922766.pdf
|
||||
!issue18956.pdf
|
||||
!issue19083.pdf
|
||||
!issue19120.pdf
|
||||
!bug1934157.pdf
|
||||
!rotated_ink.pdf
|
||||
!inks.pdf
|
||||
!inks_basic.pdf
|
||||
!issue19182.pdf
|
||||
!issue18911.pdf
|
||||
!issue19207.pdf
|
||||
!issue19239.pdf
|
||||
!issue19360.pdf
|
||||
!bug1019475_1.pdf
|
||||
!bug1019475_2.pdf
|
||||
!issue19505.pdf
|
||||
!colors.pdf
|
||||
!red_stamp.pdf
|
||||
!issue19633.pdf
|
||||
!issue19424.pdf
|
||||
!issue18529.pdf
|
||||
!issue16742.pdf
|
||||
!chrome-text-selection-markedContent.pdf
|
||||
!bug1963407.pdf
|
||||
!issue19517.pdf
|
||||
!empty#hash.pdf
|
||||
!bug1885505.pdf
|
||||
!bug1974436.pdf
|
||||
!firefox_logo.pdf
|
||||
!firefox_stamp.pdf
|
||||
!issue20062.pdf
|
||||
!issue20102.pdf
|
||||
!issue20065.pdf
|
||||
!bug1708041.pdf
|
||||
!bug1978317.pdf
|
||||
!dates.pdf
|
||||
!dates_save.pdf
|
||||
!print_protection.pdf
|
||||
!tracemonkey_with_annotations.pdf
|
||||
!tracemonkey_with_editable_annotations.pdf
|
||||
!bug1980958.pdf
|
||||
!tracemonkey_annotation_on_page_8.pdf
|
||||
!issue20232.pdf
|
||||
!bug1989304.pdf
|
||||
!comments.pdf
|
||||
!issue20319_1.pdf
|
||||
!issue20319_2.pdf
|
||||
!issue20439.pdf
|
||||
!bug1992868.pdf
|
||||
!bug1937438_af_from_latex.pdf
|
||||
!bug1937438_from_word.pdf
|
||||
!bug1937438_mml_from_latex.pdf
|
||||
!bug1997343.pdf
|
||||
!doc_1_3_pages.pdf
|
||||
!doc_2_3_pages.pdf
|
||||
!doc_3_3_pages.pdf
|
||||
!labelled_pages.pdf
|
||||
!extract_link.pdf
|
||||
!two_paragraphs.pdf
|
||||
!paragraph_and_link.pdf
|
||||
!issue20225.pdf
|
||||
!issue20513.pdf
|
||||
!issue20516.pdf
|
||||
!issue20489.pdf
|
||||
!bug2004951.pdf
|
||||
!bitmap-composite-and-xnor-halftone.pdf
|
||||
!bitmap-composite-and-xnor-refine.pdf
|
||||
!bitmap-composite-and-xnor-text.pdf
|
||||
!bitmap-composite-and-xnor.pdf
|
||||
!bitmap-composite-or-xor-replace-halftone.pdf
|
||||
!bitmap-composite-or-xor-replace-refine.pdf
|
||||
!bitmap-composite-or-xor-replace-text.pdf
|
||||
!bitmap-composite-or-xor-replace.pdf
|
||||
!bitmap-customat-tpgdon.pdf
|
||||
!bitmap-customat.pdf
|
||||
!bitmap-halftone-10bpp-mmr.pdf
|
||||
!bitmap-halftone-10bpp.pdf
|
||||
!bitmap-halftone-composite.pdf
|
||||
!bitmap-halftone-grid.pdf
|
||||
!bitmap-halftone-refine.pdf
|
||||
!bitmap-halftone-skip-dummy.pdf
|
||||
!bitmap-halftone-skip-grid-template1.pdf
|
||||
!bitmap-halftone-skip-grid-template2.pdf
|
||||
!bitmap-halftone-skip-grid-template3.pdf
|
||||
!bitmap-halftone-skip-grid.pdf
|
||||
!bitmap-halftone-template1.pdf
|
||||
!bitmap-halftone-template2.pdf
|
||||
!bitmap-halftone-template3.pdf
|
||||
!bitmap-halftone.pdf
|
||||
!bitmap-initially-unknown-size.pdf
|
||||
!bitmap-mmr.pdf
|
||||
!bitmap-p32-eof.pdf
|
||||
!bitmap-randomaccess.pdf
|
||||
!bitmap-refine-customat-tpgron.pdf
|
||||
!bitmap-refine-customat.pdf
|
||||
!bitmap-refine-lossless.pdf
|
||||
!bitmap-refine-page-subrect.pdf
|
||||
!bitmap-refine-page.pdf
|
||||
!bitmap-refine-refine.pdf
|
||||
!bitmap-refine-template1-tpgron.pdf
|
||||
!bitmap-refine-template1.pdf
|
||||
!bitmap-refine-tpgron.pdf
|
||||
!bitmap-refine.pdf
|
||||
!bitmap-stripe-initially-unknown-height.pdf
|
||||
!bitmap-stripe-last-implicit.pdf
|
||||
!bitmap-stripe-single-no-end-of-stripe.pdf
|
||||
!bitmap-stripe-single.pdf
|
||||
!bitmap-stripe.pdf
|
||||
!bitmap-symbol-big-segmentid.pdf
|
||||
!bitmap-symbol-context-reuse.pdf
|
||||
!bitmap-symbol-empty.pdf
|
||||
!bitmap-symbol-negative-sbdsoffset.pdf
|
||||
!bitmap-symbol-refine.pdf
|
||||
!bitmap-symbol-symbolrefine-textrefine.pdf
|
||||
!bitmap-symbol-symbolrefineone-customat.pdf
|
||||
!bitmap-symbol-symbolrefineone-template1.pdf
|
||||
!bitmap-symbol-symbolrefineone.pdf
|
||||
!bitmap-symbol-symbolrefineseveral.pdf
|
||||
!bitmap-symbol-symhuff-texthuff.pdf
|
||||
!bitmap-symbol-symhuff-texthuffB10B13.pdf
|
||||
!bitmap-symbol-symhuffB5B3-texthuffB7B9B12.pdf
|
||||
!bitmap-symbol-symhuffcustom-texthuffcustom.pdf
|
||||
!bitmap-symbol-symhuffrefine-textrefine.pdf
|
||||
!bitmap-symbol-symhuffrefineone.pdf
|
||||
!bitmap-symbol-symhuffrefineseveral.pdf
|
||||
!bitmap-symbol-symhuffuncompressed-texthuff.pdf
|
||||
!bitmap-symbol-textbottomleft.pdf
|
||||
!bitmap-symbol-textbottomlefttranspose.pdf
|
||||
!bitmap-symbol-textbottomright.pdf
|
||||
!bitmap-symbol-textbottomrighttranspose.pdf
|
||||
!bitmap-symbol-textcomposite.pdf
|
||||
!bitmap-symbol-texthuffrefine.pdf
|
||||
!bitmap-symbol-texthuffrefineB15.pdf
|
||||
!bitmap-symbol-texthuffrefinecustom.pdf
|
||||
!bitmap-symbol-texthuffrefinecustomdims.pdf
|
||||
!bitmap-symbol-texthuffrefinecustompos.pdf
|
||||
!bitmap-symbol-texthuffrefinecustomposdims.pdf
|
||||
!bitmap-symbol-texthuffrefinecustomsize.pdf
|
||||
!bitmap-symbol-textrefine-customat.pdf
|
||||
!bitmap-symbol-textrefine-negative-delta-width.pdf
|
||||
!bitmap-symbol-textrefine.pdf
|
||||
!bitmap-symbol-texttopright.pdf
|
||||
!bitmap-symbol-texttoprighttranspose.pdf
|
||||
!bitmap-symbol-texttranspose.pdf
|
||||
!bitmap-symbol.pdf
|
||||
!bitmap-template1-customat-tpgdon.pdf
|
||||
!bitmap-template1-customat.pdf
|
||||
!bitmap-template1-tpgdon.pdf
|
||||
!bitmap-template1.pdf
|
||||
!bitmap-template2-customat-tpgdon.pdf
|
||||
!bitmap-template2-customat.pdf
|
||||
!bitmap-template2-tpgdon.pdf
|
||||
!bitmap-template2.pdf
|
||||
!bitmap-template3-customat-tpgdon.pdf
|
||||
!bitmap-template3-customat.pdf
|
||||
!bitmap-template3-tpgdon.pdf
|
||||
!bitmap-template3.pdf
|
||||
!bitmap-tpgdon.pdf
|
||||
!bitmap-trailing-7fff-stripped-harder-refine.pdf
|
||||
!bitmap-trailing-7fff-stripped-harder.pdf
|
||||
!bitmap-trailing-7fff-stripped.pdf
|
||||
!bitmap.pdf
|
||||
!bomb_giant.pdf
|
||||
!bug2009627.pdf
|
||||
!page_with_number.pdf
|
||||
!page_with_number_and_link.pdf
|
||||
!Brotli-Prototype-FileA.pdf
|
||||
!images.pdf
|
||||
!bug_jpx.pdf
|
||||
!image-rotated-black-white-ratio.pdf
|
||||
!bug2013793.pdf
|
||||
!bug2014080.pdf
|
||||
!two_pages.pdf
|
||||
!sci-notation.pdf
|
||||
!nested_outline.pdf
|
||||
!form_two_pages.pdf
|
||||
!outlines_se.pdf
|
||||
!radial_gradients.pdf
|
||||
!outlines_for_editor.pdf
|
||||
!outline_goto_action.pdf
|
||||
!mesh_shading_empty.pdf
|
||||
!acroform_calculation_order.pdf
|
||||
!extractPages_null_in_array.pdf
|
||||
!issue20930.pdf
|
||||
!text_rise_eol_bug.pdf
|
||||
!hello_world_rotated.pdf
|
||||
!bug2025674.pdf
|
||||
!bug2026037.pdf
|
||||
!tiling_patterns_variations.pdf
|
||||
!function_based_shading.pdf
|
||||
!issue16091.pdf
|
||||
!issue7821.pdf
|
||||
!issue21068.pdf
|
||||
!recursiveCompositGlyf.pdf
|
||||
!issue19634.pdf
|
||||
!three_pages_with_number.pdf
|
||||
!issue13520.pdf
|
||||
!22060_A1_01_Plans.pdf
|
||||
!issue21126.pdf
|
||||
!bug2035197_1.pdf
|
||||
!bug2035197_2.pdf
|
||||
!smask_alpha_oob.pdf
|
||||
!smask_alpha_oob_transfer.pdf
|
||||
!smask_alpha_bc.pdf
|
||||
!smask_luminosity_oob_transfer.pdf
|
||||
!operator_list_cycle.pdf
|
||||
!knockout_groups_test.pdf
|
||||
!issue18032.pdf
|
||||
!encrypted-attachment.pdf
|
||||
!auth-event-ef-open.pdf
|
||||
!Embedded_font.pdf
|
||||
!issue18548_reduced.pdf
|
||||
!issue_cff_unsigned_bbox.pdf
|
||||
!90ms_rksj_h_sample.pdf
|
||||
!issue21346.pdf
|
||||
!cidfont_cmap_overflow.pdf
|
||||
!jbig2_file_header.pdf
|
||||
BIN
test/pdfs/160F-2019.pdf
Normal file
BIN
test/pdfs/160F-2019.pdf
Normal file
Binary file not shown.
1
test/pdfs/20130226130259.pdf.link
Normal file
1
test/pdfs/20130226130259.pdf.link
Normal file
@@ -0,0 +1 @@
|
||||
https://web.archive.org/web/20160111221513/http://www.courts.go.jp/app/files/hanrei_jp/014/083014_hanrei.pdf
|
||||
BIN
test/pdfs/22060_A1_01_Plans.pdf
Normal file
BIN
test/pdfs/22060_A1_01_Plans.pdf
Normal file
Binary file not shown.
67
test/pdfs/90ms_rksj_h_sample.pdf
Normal file
67
test/pdfs/90ms_rksj_h_sample.pdf
Normal file
@@ -0,0 +1,67 @@
|
||||
%PDF-1.4
|
||||
%âãÏÓ
|
||||
|
||||
1 0 obj
|
||||
<< /Type /Catalog /Pages 2 0 R >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
|
||||
/Contents 4 0 R
|
||||
/Resources << /Font << /F0 5 0 R /F1 7 0 R >> >> >>
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Length 92 >>
|
||||
stream
|
||||
BT
|
||||
/F0 14 Tf
|
||||
72 720 Td
|
||||
(Hello ASCII) Tj
|
||||
0 -28 Td
|
||||
/F1 14 Tf
|
||||
<93FA967B8CEA836583588367> Tj
|
||||
ET
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Type /Font /Subtype /Type1
|
||||
/BaseFont /Helvetica
|
||||
/Encoding /WinAnsiEncoding >>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Font /Subtype /CIDFontType2
|
||||
/BaseFont /HeiseiMin-W3
|
||||
/CIDSystemInfo << /Registry (Adobe) /Ordering (Japan1) /Supplement 2 >>
|
||||
/DW 1000 >>
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
<< /Type /Font /Subtype /Type0
|
||||
/BaseFont /HeiseiMin-W3
|
||||
/Encoding /90ms-RKSJ-H
|
||||
/DescendantFonts [6 0 R] >>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 8
|
||||
0000000000 65535 f
|
||||
0000000016 00000 n
|
||||
0000000066 00000 n
|
||||
0000000124 00000 n
|
||||
0000000267 00000 n
|
||||
0000000410 00000 n
|
||||
0000000514 00000 n
|
||||
0000000685 00000 n
|
||||
trailer
|
||||
<< /Size 8 /Root 1 0 R >>
|
||||
startxref
|
||||
816
|
||||
%%EOF
|
||||
BIN
test/pdfs/ArabicCIDTrueType.pdf
Normal file
BIN
test/pdfs/ArabicCIDTrueType.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/Brotli-Prototype-FileA.pdf
Normal file
BIN
test/pdfs/Brotli-Prototype-FileA.pdf
Normal file
Binary file not shown.
122
test/pdfs/ContentStreamCycleType3insideType3.pdf
Normal file
122
test/pdfs/ContentStreamCycleType3insideType3.pdf
Normal file
@@ -0,0 +1,122 @@
|
||||
%PDF-1.6
|
||||
%µ¶
|
||||
|
||||
1 0 obj
|
||||
<</Type/Catalog/Outlines 2 0 R/Pages 3 0 R>>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<</Type/Outlines/Count 0>>
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<</Type/Pages/Kids[4 0 R]/Count 1>>
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<</Type/Page/Parent 3 0 R/MediaBox[0 0 600 840]/Contents 5 0 R/Resources<</Font<</FType3A 6 0 R>>>>>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<</Length 123>>
|
||||
stream
|
||||
BT
|
||||
1 0 0 1 100 500 Tm
|
||||
2 Tr % Fill then Stroke Text render mode
|
||||
/FType3A 20 Tf
|
||||
20 20 Td
|
||||
50 Tw
|
||||
(ab) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<</Type/Font/Subtype/Type3/Name/FType3A/Widths[1000 1000]/FontBBox[0 0 750 750]/FontMatrix[.01 0 0 .01 0 0]/Encoding<</Type/Encoding/Differences[97/rect/triangle]>>/Resources<</Font<</FType3B 9 0 R>>>>/CharProcs<</rect 7 0 R/triangle 8 0 R>>/FirstChar 97/LastChar 98>>
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
<</Length 179>>
|
||||
stream
|
||||
%%% FType3A stroked red 'rect' = 97 or 'a'
|
||||
1000 0 d0
|
||||
20 w
|
||||
1 0 0 RG
|
||||
0 0 750 750 re s
|
||||
BT % This is where a Type3 calls into another Type 3!
|
||||
/FType3B 50 Tf
|
||||
(ccc) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
8 0 obj
|
||||
<</Length 99>>
|
||||
stream
|
||||
%%% FType3A stroked green 'triangle' = 98 'b'
|
||||
1000 0 d0
|
||||
20 w
|
||||
0 1 0 RG
|
||||
0 0 m 375 750 l 750 0 l s
|
||||
endstream
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
<</Type/Font/Subtype/Type3/Name/FType3B/FontBBox[0 0 750 750]/FontMatrix[.004 0 0 .004 0 0]/Encoding<</Type/Encoding/Differences[99/fontinside]>>/Widths[900]/Resources<</Font<</F1 11 0 R>>/Pattern<</P1 12 0 R>>>>/CharProcs<</fontinside 10 0 R>>/FirstChar 99/LastChar 99>>
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<</Length 265>>
|
||||
stream
|
||||
900 0 d0
|
||||
%%% FType3B Helv text for 'c'
|
||||
2 Tr % Fill then Stroke Text render mode
|
||||
10 w % stroke width 10
|
||||
0 0 1 RG % Blue stroke
|
||||
/Pattern cs /P1 scn % Set /P1 pattern as fill (magenta text)
|
||||
BT
|
||||
/F1 60 Tf
|
||||
30 0 0 30 15 15 Tm -3 Tc 0 Tw
|
||||
(ab) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
11 0 obj
|
||||
<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>
|
||||
endobj
|
||||
|
||||
12 0 obj
|
||||
<</Type/Pattern/PaintType 1/PatternType 1/TilingType 2/Matrix[0.9 0 0 0.9 0 0]/XStep 55/YStep 32/BBox[0 0 60 60]/Resources<</Font<</CyclicFont 6 0 R>>>>/Length 100>>
|
||||
stream
|
||||
BT
|
||||
/CyclicFont 4 Tf
|
||||
10 0 0 10 1 1 Tm
|
||||
0 Tc
|
||||
0 Tw
|
||||
1 0 1 rg % Magenta fill
|
||||
(ba) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 13
|
||||
0000000000 65536 f
|
||||
0000000016 00000 n
|
||||
0000000077 00000 n
|
||||
0000000120 00000 n
|
||||
0000000172 00000 n
|
||||
0000000290 00000 n
|
||||
0000000464 00000 n
|
||||
0000000749 00000 n
|
||||
0000000978 00000 n
|
||||
0000001127 00000 n
|
||||
0000001419 00000 n
|
||||
0000001736 00000 n
|
||||
0000001802 00000 n
|
||||
trailer
|
||||
<</Size 13/Root 1 0 R>>
|
||||
startxref
|
||||
2111
|
||||
%%EOF
|
||||
122
test/pdfs/ContentStreamNoCycleType3insideType3.pdf
Normal file
122
test/pdfs/ContentStreamNoCycleType3insideType3.pdf
Normal file
@@ -0,0 +1,122 @@
|
||||
%PDF-1.6
|
||||
%µ¶
|
||||
|
||||
1 0 obj
|
||||
<</Type/Catalog/Outlines 2 0 R/Pages 3 0 R>>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<</Type/Outlines/Count 0>>
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<</Type/Pages/Kids[4 0 R]/Count 1>>
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<</Type/Page/Parent 3 0 R/MediaBox[0 0 600 840]/Contents 5 0 R/Resources<</Font<</FType3A 6 0 R>>>>>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<</Length 123>>
|
||||
stream
|
||||
BT
|
||||
1 0 0 1 100 500 Tm
|
||||
2 Tr % Fill then Stroke Text render mode
|
||||
/FType3A 20 Tf
|
||||
20 20 Td
|
||||
50 Tw
|
||||
(ab) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<</Type/Font/Subtype/Type3/Name/FType3A/Widths[1000 1000]/FontBBox[0 0 750 750]/FontMatrix[.01 0 0 .01 0 0]/Encoding<</Type/Encoding/Differences[97/rect/triangle]>>/Resources<</Font<</FType3B 9 0 R>>>>/CharProcs<</rect 7 0 R/triangle 8 0 R>>/FirstChar 97/LastChar 98>>
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
<</Length 179>>
|
||||
stream
|
||||
%%% FType3A stroked red 'rect' = 97 or 'a'
|
||||
1000 0 d0
|
||||
20 w
|
||||
1 0 0 RG
|
||||
0 0 750 750 re s
|
||||
BT % This is where a Type3 calls into another Type 3!
|
||||
/FType3B 50 Tf
|
||||
(ccc) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
8 0 obj
|
||||
<</Length 99>>
|
||||
stream
|
||||
%%% FType3A stroked green 'triangle' = 98 'b'
|
||||
1000 0 d0
|
||||
20 w
|
||||
0 1 0 RG
|
||||
0 0 m 375 750 l 750 0 l s
|
||||
endstream
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
<</Type/Font/Subtype/Type3/Name/FType3B/FontBBox[0 0 750 750]/FontMatrix[.004 0 0 .004 0 0]/Encoding<</Type/Encoding/Differences[99/fontinside]>>/Widths[900]/Resources<</Font<</F1 11 0 R>>/Pattern<</P1 12 0 R>>>>/CharProcs<</fontinside 10 0 R>>/FirstChar 99/LastChar 99>>
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<</Length 265>>
|
||||
stream
|
||||
900 0 d0
|
||||
%%% FType3B Helv text for 'c'
|
||||
2 Tr % Fill then Stroke Text render mode
|
||||
10 w % stroke width 10
|
||||
0 0 1 RG % Blue stroke
|
||||
/Pattern cs /P1 scn % Set /P1 pattern as fill (magenta text)
|
||||
BT
|
||||
/F1 60 Tf
|
||||
30 0 0 30 15 15 Tm -3 Tc 0 Tw
|
||||
(ab) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
11 0 obj
|
||||
<</Type/Font/Subtype/Type1/BaseFont/Helvetica>>
|
||||
endobj
|
||||
|
||||
12 0 obj
|
||||
<</Type/Pattern/PaintType 1/PatternType 1/TilingType 2/Matrix[0.9 0 0 0.9 0 0]/XStep 55/YStep 32/BBox[0 0 60 60]/Resources<</Font<</CyclicFont 11 0 R>>>>/Length 100>>
|
||||
stream
|
||||
BT
|
||||
/CyclicFont 4 Tf
|
||||
10 0 0 10 1 1 Tm
|
||||
0 Tc
|
||||
0 Tw
|
||||
1 0 1 rg % Magenta fill
|
||||
(ba) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 13
|
||||
0000000000 65536 f
|
||||
0000000016 00000 n
|
||||
0000000077 00000 n
|
||||
0000000120 00000 n
|
||||
0000000172 00000 n
|
||||
0000000290 00000 n
|
||||
0000000464 00000 n
|
||||
0000000749 00000 n
|
||||
0000000978 00000 n
|
||||
0000001127 00000 n
|
||||
0000001419 00000 n
|
||||
0000001736 00000 n
|
||||
0000001802 00000 n
|
||||
trailer
|
||||
<</Size 13/Root 1 0 R>>
|
||||
startxref
|
||||
2111
|
||||
%%EOF
|
||||
BIN
test/pdfs/Embedded_font.pdf
Normal file
BIN
test/pdfs/Embedded_font.pdf
Normal file
Binary file not shown.
69
test/pdfs/GHOSTSCRIPT-698804-1-fuzzed.pdf
Normal file
69
test/pdfs/GHOSTSCRIPT-698804-1-fuzzed.pdf
Normal file
@@ -0,0 +1,69 @@
|
||||
%PDF-1.4
|
||||
%âãÏÓ
|
||||
|
||||
1 0 obj
|
||||
<<
|
||||
/Type /Catalog
|
||||
/Outline 2 0 R
|
||||
/Pages 3 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<<
|
||||
/Type /Outlines
|
||||
/Count 0
|
||||
>>
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<<
|
||||
/Type /Pages
|
||||
/Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
>>
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<<
|
||||
/Type /Page
|
||||
/Parent 3 0 R
|
||||
/MediaBox [ 0 0 612 792 ]
|
||||
/Contents 5 0 R
|
||||
/Resources <<
|
||||
/ProcSet 6 0 R
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<<
|
||||
/Length 0
|
||||
>>
|
||||
stream
|
||||
endstream
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
[ /PDF ]
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 2
|
||||
0000000000 65536 f
|
||||
0000000016 00000 n
|
||||
00000004294967296 3
|
||||
0000000138 00000 n
|
||||
0000000204 00000 n
|
||||
0000000342 00000 n
|
||||
|
||||
|
||||
trailer
|
||||
<<
|
||||
/Size 7
|
||||
/Root 1 0 R
|
||||
>>
|
||||
|
||||
startxref
|
||||
418
|
||||
%%EOF
|
||||
1
test/pdfs/High-Pressure-Measurement-WP-001287.pdf.link
Normal file
1
test/pdfs/High-Pressure-Measurement-WP-001287.pdf.link
Normal file
@@ -0,0 +1 @@
|
||||
https://web.archive.org/web/20150927202748/http://www2.emersonprocess.com/siteadmincenter/PM%20Micro%20Motion%20Documents/High-Pressure-Measurement-WP-001287.pdf
|
||||
68
test/pdfs/IdentityToUnicodeMap_charCodeOf.pdf
Normal file
68
test/pdfs/IdentityToUnicodeMap_charCodeOf.pdf
Normal file
@@ -0,0 +1,68 @@
|
||||
%PDF-1.7
|
||||
%âãÏÓ
|
||||
1 0 obj
|
||||
<<
|
||||
/Pages 2 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/Kids [3 0 R]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Parent 2 0 R
|
||||
/MediaBox [0 0 200 50]
|
||||
/Resources
|
||||
<<
|
||||
/Font
|
||||
<<
|
||||
/F1 4 0 R
|
||||
>>
|
||||
>>
|
||||
/Contents 5 0 R
|
||||
/Type /Page
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/BaseFont /Times-Roman
|
||||
/Subtype /Type1
|
||||
/ToUnicode /Identity-H
|
||||
/Encoding /WinAnsiEncoding
|
||||
/Type /Font
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Length 37
|
||||
>>
|
||||
stream
|
||||
BT
|
||||
10 20 TD
|
||||
/F1 20 Tf
|
||||
(ABCdef) Tj
|
||||
ET
|
||||
|
||||
endstream
|
||||
endobj xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000066 00000 n
|
||||
0000000125 00000 n
|
||||
0000000254 00000 n
|
||||
0000000378 00000 n
|
||||
trailer
|
||||
|
||||
<<
|
||||
/Root 1 0 R
|
||||
/Size 6
|
||||
>>
|
||||
startxref
|
||||
467
|
||||
%%EOF
|
||||
87
test/pdfs/IndexedCS_negative_and_high.pdf
Normal file
87
test/pdfs/IndexedCS_negative_and_high.pdf
Normal file
@@ -0,0 +1,87 @@
|
||||
%PDF-1.7
|
||||
%âãÏÓ
|
||||
|
||||
1 0 obj
|
||||
<<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<<
|
||||
/Type /Pages
|
||||
/Kids [ 3 0 R ]
|
||||
/Count 1
|
||||
>>
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/MediaBox [ 0 0 500 100 ]
|
||||
/Contents 5 0 R
|
||||
/Resources <<
|
||||
/ColorSpace << /Cs1 4 0 R >>
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
[
|
||||
/Indexed /DeviceRGB
|
||||
7 % "hival"
|
||||
< 008000 FF0000 00FF00 0000FF 00FFFF FF00FF FFFF00 F380FF > % indices 0-7 inclusive
|
||||
]
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<<
|
||||
/Length 1037
|
||||
>>
|
||||
stream
|
||||
% Reference
|
||||
0 0.5 0 rg 5 10 20 20 re f % 008000
|
||||
0 0.5 0 rg 30 10 20 20 re f % 008000
|
||||
1 0 0 rg 55 10 20 20 re f % FF0000
|
||||
0 1 0 rg 80 10 20 20 re f % 00FF00
|
||||
0 0 1 rg 105 10 20 20 re f % 0000FF
|
||||
0 1 1 rg 130 10 20 20 re f % 00FFFF
|
||||
1 0 1 rg 155 10 20 20 re f % FF00FF
|
||||
1 1 0 rg 180 10 20 20 re f % FFFF00
|
||||
0.95 0.5 1 rg 205 10 20 20 re f % F380FF
|
||||
0.95 0.5 1 rg 230 10 20 20 re f % F380FF
|
||||
0.95 0.5 1 rg 255 10 20 20 re f % F380FF
|
||||
|
||||
% Test patches
|
||||
/Cs1 cs
|
||||
-17 sc 5 50 20 20 re f % Below 0 --> snap to 0
|
||||
0 sc 30 50 20 20 re f
|
||||
1 sc 55 50 20 20 re f
|
||||
2 sc 80 50 20 20 re f
|
||||
3 sc 105 50 20 20 re f
|
||||
4 sc 130 50 20 20 re f
|
||||
5 sc 155 50 20 20 re f
|
||||
6 sc 180 50 20 20 re f
|
||||
7 sc 205 50 20 20 re f % Same as "Hival" = OK
|
||||
6.5 sc 230 50 20 20 re f % Needs to round up --> snap to "hival"
|
||||
17 sc 255 50 20 20 re f % Clearly out of range! --> snap to "hival"
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000019 00000 n
|
||||
0000000083 00000 n
|
||||
0000000162 00000 n
|
||||
0000000332 00000 n
|
||||
0000000503 00000 n
|
||||
trailer
|
||||
<<
|
||||
/Root 1 0 R
|
||||
/Size 6
|
||||
>>
|
||||
startxref
|
||||
1600
|
||||
%%EOF
|
||||
1
test/pdfs/JBIG2Globals.pdf.link
Normal file
1
test/pdfs/JBIG2Globals.pdf.link
Normal file
@@ -0,0 +1 @@
|
||||
https://github.com/pdfminer/pdfminer.six/files/8399364/64.pdf
|
||||
1
test/pdfs/JST2007-5.pdf.link
Normal file
1
test/pdfs/JST2007-5.pdf.link
Normal file
@@ -0,0 +1 @@
|
||||
https://web.archive.org/web/20090612045731/http://kanji.zinbun.kyoto-u.ac.jp/~yasuoka/publications/JST2007-5.pdf
|
||||
1
test/pdfs/P020121130574743273239.pdf.link
Normal file
1
test/pdfs/P020121130574743273239.pdf.link
Normal file
@@ -0,0 +1 @@
|
||||
https://web.archive.org/web/20150329232430/http://image.haier.com/manual/japan/wash_machine/201211/P020121130574743273239.pdf
|
||||
3574
test/pdfs/PDFBOX-3148-2-fuzzed.pdf
Normal file
3574
test/pdfs/PDFBOX-3148-2-fuzzed.pdf
Normal file
File diff suppressed because it is too large
Load Diff
BIN
test/pdfs/PDFBOX-4352-0.pdf
Normal file
BIN
test/pdfs/PDFBOX-4352-0.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/PDFJS-7562-reduced.pdf
Normal file
BIN
test/pdfs/PDFJS-7562-reduced.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/PDFJS-9279-reduced.pdf
Normal file
BIN
test/pdfs/PDFJS-9279-reduced.pdf
Normal file
Binary file not shown.
84
test/pdfs/Pages-tree-refs.pdf
Normal file
84
test/pdfs/Pages-tree-refs.pdf
Normal file
@@ -0,0 +1,84 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Type /Pages
|
||||
/Kids [6 0 R 3 0 R]
|
||||
/Count 2
|
||||
/MediaBox [0 0 595 842]
|
||||
>>
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<< /Type /Pages
|
||||
/Kids [4 0 R]
|
||||
/Count 1
|
||||
/MediaBox [0 0 595 842]
|
||||
>>
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Type /Pages
|
||||
/Kids [5 0 R]
|
||||
/Count 1
|
||||
/MediaBox [0 0 595 842]
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Type /Pages
|
||||
/Kids [3 0 R]
|
||||
/Count 1
|
||||
/MediaBox [0 0 595 842]
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Page
|
||||
/Parent 2 0 R
|
||||
/Resources
|
||||
<< /Font
|
||||
<< /F1
|
||||
<< /Type /Font
|
||||
/Subtype /Type1
|
||||
/BaseFont /Courier
|
||||
>>
|
||||
>>
|
||||
>>
|
||||
/Contents [7 0 R]
|
||||
>>
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
<< /Length 69 >>
|
||||
stream
|
||||
BT
|
||||
/F1 22 Tf
|
||||
30 800 Td
|
||||
(Testcase: 'Pages loop') Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 8
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000069 00000 n
|
||||
0000000176 00000 n
|
||||
0000000277 00000 n
|
||||
0000000378 00000 n
|
||||
0000000479 00000 n
|
||||
0000000744 00000 n
|
||||
trailer
|
||||
<< /Root 1 0 R
|
||||
/Size 8
|
||||
>>
|
||||
startxref
|
||||
866
|
||||
%%EOF
|
||||
BIN
test/pdfs/REDHAT-1531897-0.pdf
Normal file
BIN
test/pdfs/REDHAT-1531897-0.pdf
Normal file
Binary file not shown.
2549
test/pdfs/S2.pdf
Normal file
2549
test/pdfs/S2.pdf
Normal file
File diff suppressed because one or more lines are too long
1
test/pdfs/SFAA_Japanese.pdf.link
Normal file
1
test/pdfs/SFAA_Japanese.pdf.link
Normal file
@@ -0,0 +1 @@
|
||||
https://web.archive.org/web/20150307061027/http://www.project2061.org/publications/sfaa/SFAA_Japanese.pdf
|
||||
BIN
test/pdfs/ShowText-ShadingPattern.pdf
Normal file
BIN
test/pdfs/ShowText-ShadingPattern.pdf
Normal file
Binary file not shown.
175
test/pdfs/SimFang-variant.pdf
Normal file
175
test/pdfs/SimFang-variant.pdf
Normal file
@@ -0,0 +1,175 @@
|
||||
%PDF-1.4
|
||||
%âãÏÓ
|
||||
4 0 obj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /TrueType
|
||||
/Name /F5
|
||||
/BaseFont /#BA#DA#CC#E5
|
||||
/Encoding /WinAnsiEncoding
|
||||
/FirstChar 0
|
||||
/LastChar 255
|
||||
/Widths [500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500]
|
||||
/FontDescriptor <<
|
||||
/MissingWidth 1000
|
||||
/Ascent 1000
|
||||
/CapHeight 1000
|
||||
/Descent -200
|
||||
/Flags 4
|
||||
/FontBBox [0 -200 1000 1000]
|
||||
/FontName <D0A1B1EACBCE>
|
||||
/ItalicAngle 0
|
||||
/StemV 100
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/ProcSet [/PDF]
|
||||
>>
|
||||
endobj
|
||||
7 0 obj
|
||||
<<
|
||||
/Type /Encoding
|
||||
/Differences [65 /G065]
|
||||
>>
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Length 103
|
||||
/Filter /FlateDecode
|
||||
>>
|
||||
stream
|
||||
xœE<EFBFBD>»
€0DûHÙáFÀø¼¢‚ý[âÄ<C3A2>ÊOwOg7¸áÜjÑfx&8îZä@㺀VE!˜´„¹‰€<»L-v<>$|3©J~W3Wž^¿´Š9ÕMòÿK‡«–Žh#Þ
|
||||
|
||||
endstream
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/G065 9 0 R
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Name /X0
|
||||
/Type /Font
|
||||
/Subtype /Type3
|
||||
/Resources 6 0 R
|
||||
/FontBBox [0 0 96 96]
|
||||
/FontMatrix [0.00999 0 0 0.00999 0 0]
|
||||
/FirstChar 65
|
||||
/LastChar 65
|
||||
/Encoding 7 0 R
|
||||
/CharProcs 8 0 R
|
||||
/Widths [96]
|
||||
>>
|
||||
endobj
|
||||
10 0 obj
|
||||
<<
|
||||
/Filter /FlateDecode
|
||||
/Length 179
|
||||
>>
|
||||
stream
|
||||
xœm<EFBFBD>9
|
||||
1E{ŸBeÒh$ï!àõîÓd<C393>”sÿ"ž<18>Oÿ‹<C3BF>½3°|<01>¶
Xip†å%ÞÂ12lMIB Ú+tûª[{uclbj<62>€%’gh}¥gTZ<54>žìè)nɇ(éí+Ø"¹Õ¾ñ<C2BE>hÄc.n¨/¹õ²†Ï&ד3j§TH·hõ‰‡á™RƱu?QÔŸšzü}§OÎíºK¸®’ÒÄ"£Sj
|
||||
endstream
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Filter [/ASCII85Decode /LZWDecode]
|
||||
/Length 488
|
||||
>>
|
||||
stream
|
||||
J,g]g+e/h_!_gCtO=0f)$P%cIi8Zdfc5&3j_8$7g.@L`YKUJNGBP\poR=_;Dl'P(T
|
||||
(7Boo^^S:71(MN]ZQX/+Cbu.lK"p74pe1T%s.DY%&\1TdJhr54.M9au6>79n6`Q:4
|
||||
PbLSZTLEE(8E@'*1mg_*eTnN*;*'V3+gm-EEetX%;Bo$ur2ss*N`.-!.kG_q6GDD'
|
||||
dKoL!8Ka#EV,@V!\j8ZFbp6EE<9cn=N6j<M8Q?[#7"dq'1>0nf;(&;QU6bUD')c@\
|
||||
9-d\DA=cZ0Q>gIM$$;cd2O@&a;X,Nn_a<?V-PVE%?Sf]idH6WRZqHGq]Zm<uCi"]?
|
||||
Stg(<gV-H9NB<SA\T=sN)Il%(BDIak7/H&mV!kmDUo4X;8;]V>P(]I1aRc(K1^ue>
|
||||
gF/(+GaKo$qneLWDrQ#;5\S(\$q'4Q,85`-8;S(=Z"WSBOV*FM)4,?B],R<gbPN=#
|
||||
OmIK<a:\o8+ioO-!W~>
|
||||
endstream
|
||||
endobj
|
||||
12 0 obj
|
||||
[/Indexed /DeviceRGB 255 13 0 R]
|
||||
endobj
|
||||
11 0 obj
|
||||
<<
|
||||
/Filter [/ASCII85Decode /LZWDecode]
|
||||
/Width 57
|
||||
/Height 78
|
||||
/ColorSpace 12 0 R
|
||||
/BitsPerComponent 8
|
||||
/Length 384
|
||||
>>
|
||||
stream
|
||||
J2Q'--mT93"b]2#:mbcG*<g4EU+e!c7O8k.7O$6IjDI*qL<*fo3>@cD)N)[K'"I;.
|
||||
4!p=d-;nQ"Sm*b8:1ggMjN`/[Ra>[].D6LH8"Rq<PH#W]NCfpFBGB`C)OEOb<b`@a
|
||||
V-^c;YZC)_b9^1P8Qo;7<kDqO`Eok*/Cl>J*p%UNg$CZ.V<7;f^0kYVlm7t`LS%r?
|
||||
fVo&_*Iu4?k89/RNu<Y^j7?Cn^.0]GKkGF/m+qt9Vi>@I>!6JU=0'h`14!3.a'5n%
|
||||
hY,]@cIXe_g1bdN.=VR7_qX^*[LPd=NOn$>H!L6M2_iM9oXQcLa"t-9l?Bb/\mKZf
|
||||
j5bKu?;CNUX6oP+\fW#jld]qKHTqGC[ZgF$^97Y<]0@k[!W~>
|
||||
endstream
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Type /Page
|
||||
/CropBox [0 0 462.5 625.9]
|
||||
/MediaBox [0 0 462.5 625.9]
|
||||
/Resources <<
|
||||
/Font <<
|
||||
/TT0 4 0 R
|
||||
/T3_0 5 0 R
|
||||
>>
|
||||
>>
|
||||
/Contents 10 0 R
|
||||
/Thumb 11 0 R
|
||||
/Parent 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/Type /Pages
|
||||
/Kids [3 0 R]
|
||||
/Count 1
|
||||
>>
|
||||
endobj
|
||||
1 0 obj
|
||||
<<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
14 0 obj
|
||||
<<
|
||||
/ModDate (D:20260303075300Z)
|
||||
/Producer (Pdftools SDK)
|
||||
>>
|
||||
endobj
|
||||
xref
|
||||
0 15
|
||||
0000000000 65535 f
|
||||
0000003528 00000 n
|
||||
0000003471 00000 n
|
||||
0000003286 00000 n
|
||||
0000000015 00000 n
|
||||
0000001668 00000 n
|
||||
0000001362 00000 n
|
||||
0000001399 00000 n
|
||||
0000001635 00000 n
|
||||
0000001460 00000 n
|
||||
0000001877 00000 n
|
||||
0000002754 00000 n
|
||||
0000002705 00000 n
|
||||
0000002129 00000 n
|
||||
0000003577 00000 n
|
||||
trailer
|
||||
<<
|
||||
/Size 15
|
||||
/Root 1 0 R
|
||||
/Info 14 0 R
|
||||
/ID [<42230467D173E23BC1D16DD8BFFDB792> <5275A2D429D61242E799751CBE8A1B51>]
|
||||
>>
|
||||
startxref
|
||||
3653
|
||||
%%EOF
|
||||
BIN
test/pdfs/TAMReview.pdf
Normal file
BIN
test/pdfs/TAMReview.pdf
Normal file
Binary file not shown.
1
test/pdfs/TaroUTR50SortedList112.pdf.link
Normal file
1
test/pdfs/TaroUTR50SortedList112.pdf.link
Normal file
@@ -0,0 +1 @@
|
||||
https://web.archive.org/web/20150805202858/http://blogs.adobe.com/CCJKType/files/2012/07/TaroUTR50SortedList112.pdf
|
||||
BIN
test/pdfs/ThuluthFeatures.pdf
Normal file
BIN
test/pdfs/ThuluthFeatures.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/TrueType_without_cmap.pdf
Normal file
BIN
test/pdfs/TrueType_without_cmap.pdf
Normal file
Binary file not shown.
139
test/pdfs/Type3WordSpacing.pdf
Normal file
139
test/pdfs/Type3WordSpacing.pdf
Normal file
@@ -0,0 +1,139 @@
|
||||
%PDF-1.7
|
||||
%âãÏÓ
|
||||
|
||||
1 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Type /Pages
|
||||
/Kids [ 3 0 R ]
|
||||
/Count 1
|
||||
>>
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<< /Type /Page
|
||||
/Parent 2 0 R
|
||||
/MediaBox [ 0 0 300 80 ]
|
||||
/Contents 4 0 R
|
||||
/Resources <<
|
||||
/Font << /FTyp3 5 0 R >>
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<<
|
||||
/Length 715
|
||||
>>
|
||||
stream
|
||||
0 0 1 rg % Blue non-stroking (fill)
|
||||
1 0 0 RG % Red stroking
|
||||
|
||||
BT
|
||||
/FTyp3 10 Tf % Select Type 3 font
|
||||
0 Tr % Text render mode: 0=fill, 1=stroke, 2=fill then stroke
|
||||
|
||||
0 10 TD
|
||||
50 Tw % Adjust this to: 0, 50, etc
|
||||
( ab ba abba) Tj
|
||||
|
||||
T*
|
||||
40 Tw % Adjust this to: 0, 50, etc
|
||||
( ab ba abba) Tj
|
||||
|
||||
T*
|
||||
30 Tw % Adjust this to: 0, 50, etc
|
||||
( ab ba abba) Tj
|
||||
|
||||
T*
|
||||
20 Tw % Adjust this to: 0, 50, etc
|
||||
( ab ba abba) Tj
|
||||
|
||||
T*
|
||||
10 Tw % Adjust this to: 0, 50, etc
|
||||
( ab ba abba) Tj
|
||||
|
||||
T*
|
||||
0 Tw % Adjust this to: 0, 50, etc
|
||||
( ab ba abba) Tj
|
||||
ET
|
||||
endstream
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Type /Font
|
||||
/Subtype /Type3
|
||||
/FontBBox [ 0 0 750 750 ]
|
||||
/FontMatrix [ 0.001 0 0 0.001 0 0 ]
|
||||
/CharProcs 7 0 R
|
||||
/Encoding 6 0 R
|
||||
/FirstChar 97 % "a"
|
||||
/LastChar 98 % "b"
|
||||
/Widths [ 1000 1000 ]
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Encoding
|
||||
/Differences [ 97 /square /triangle ]
|
||||
>>
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
<< /square 8 0 R
|
||||
/triangle 9 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
% Type 3 SQUARE glyph description = "a"
|
||||
8 0 obj
|
||||
<<
|
||||
/Length 273
|
||||
>>
|
||||
stream
|
||||
1000 0 0 0 750 750 d1 % NO explicit colour
|
||||
50 w % Explicitly set line width as depend on it for stroking
|
||||
[ 150 150 ] 0 d % Explicitly set dashing as depend on it for stroking
|
||||
30 30 720 720 re S % Stroke (only) with inherited colour
|
||||
endstream
|
||||
endobj
|
||||
|
||||
% Type 3 TRIANGLE glyph description = "b"
|
||||
9 0 obj
|
||||
<<
|
||||
/Length 316
|
||||
>>
|
||||
stream
|
||||
1000 0 0 0 750 750 d1 % NO explicit colour
|
||||
20 w % Explicitly set line width as depend on it for stroking
|
||||
[ 100 100 ] 0 d % Explicitly set dashing as depend on it for stroking
|
||||
20 20 m
|
||||
350 730 l
|
||||
730 20 l
|
||||
b* % Close then Fill then Stroke with inherited colours
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 10
|
||||
0000000000 65535 f
|
||||
0000000019 00000 n
|
||||
0000000087 00000 n
|
||||
0000000168 00000 n
|
||||
0000000353 00000 n
|
||||
0000001130 00000 n
|
||||
0000001439 00000 n
|
||||
0000001539 00000 n
|
||||
0000001657 00000 n
|
||||
0000002035 00000 n
|
||||
trailer
|
||||
<<
|
||||
/Root 1 0 R
|
||||
/Size 10
|
||||
>>
|
||||
startxref
|
||||
2413
|
||||
%%EOF
|
||||
BIN
test/pdfs/XiaoBiaoSong.pdf
Normal file
BIN
test/pdfs/XiaoBiaoSong.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/ZapfDingbats.pdf
Normal file
BIN
test/pdfs/ZapfDingbats.pdf
Normal file
Binary file not shown.
1
test/pdfs/aboutstacks.pdf.link
Normal file
1
test/pdfs/aboutstacks.pdf.link
Normal file
@@ -0,0 +1 @@
|
||||
https://web.archive.org/web/20160112115354/http://www.fao.org/fileadmin/user_upload/tci/docs/2_About%20Stacks.pdf
|
||||
48
test/pdfs/acroform_calculation_order.pdf
Normal file
48
test/pdfs/acroform_calculation_order.pdf
Normal file
@@ -0,0 +1,48 @@
|
||||
%PDF-1.7
|
||||
1 0 obj
|
||||
<< /Type /Catalog /Pages 2 0 R /AcroForm 5 0 R >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Resources << /Font << /F1 9 0 R >> >> /Contents 4 0 R /Annots [7 0 R 8 0 R] >>
|
||||
endobj
|
||||
4 0 obj
|
||||
<< /Length 36 >>
|
||||
stream
|
||||
BT /F1 12 Tf 10 100 Td (Hello) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
<< /Fields [6 0 R] /CO [6 0 R] /DA (/F1 12 Tf 0 g) >>
|
||||
endobj
|
||||
6 0 obj
|
||||
<< /FT /Tx /T (group) /Kids [7 0 R 8 0 R] /DA (/F1 12 Tf 0 g) >>
|
||||
endobj
|
||||
7 0 obj
|
||||
<< /Type /Annot /Subtype /Widget /Parent 6 0 R /Rect [10 150 90 170] /P 3 0 R >>
|
||||
endobj
|
||||
8 0 obj
|
||||
<< /Type /Annot /Subtype /Widget /Parent 6 0 R /Rect [10 120 90 140] /P 3 0 R >>
|
||||
endobj
|
||||
9 0 obj
|
||||
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
|
||||
endobj
|
||||
xref
|
||||
0 10
|
||||
0000000000 65535 f
|
||||
0000000009 00000 n
|
||||
0000000074 00000 n
|
||||
0000000131 00000 n
|
||||
0000000279 00000 n
|
||||
0000000365 00000 n
|
||||
0000000434 00000 n
|
||||
0000000514 00000 n
|
||||
0000000610 00000 n
|
||||
0000000706 00000 n
|
||||
trailer
|
||||
<< /Size 10 /Root 1 0 R >>
|
||||
startxref
|
||||
776
|
||||
%%EOF
|
||||
BIN
test/pdfs/alphatrans.pdf
Normal file
BIN
test/pdfs/alphatrans.pdf
Normal file
Binary file not shown.
1
test/pdfs/annotation-as.pdf.link
Normal file
1
test/pdfs/annotation-as.pdf.link
Normal file
@@ -0,0 +1 @@
|
||||
https://bugzilla.mozilla.org/attachment.cgi?id=611315
|
||||
BIN
test/pdfs/annotation-border-styles.pdf
Normal file
BIN
test/pdfs/annotation-border-styles.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/annotation-button-widget.pdf
Normal file
BIN
test/pdfs/annotation-button-widget.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/annotation-caret-ink.pdf
Normal file
BIN
test/pdfs/annotation-caret-ink.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/annotation-choice-widget.pdf
Normal file
BIN
test/pdfs/annotation-choice-widget.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/annotation-fileattachment.pdf
Normal file
BIN
test/pdfs/annotation-fileattachment.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/annotation-freetext.pdf
Normal file
BIN
test/pdfs/annotation-freetext.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/annotation-highlight-without-appearance.pdf
Normal file
BIN
test/pdfs/annotation-highlight-without-appearance.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/annotation-highlight.pdf
Normal file
BIN
test/pdfs/annotation-highlight.pdf
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user