Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: CI
on: [push, pull_request, workflow_dispatch]
on: [push, pull_request]
permissions:
contents: read

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: CodeQL
on: [push, pull_request, workflow_dispatch]
on: [push, pull_request]
permissions:
contents: read

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Code Coverage
on: [push, pull_request, workflow_dispatch]
on: [push, pull_request]
permissions:
contents: read

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Lint
on: [push, pull_request, workflow_dispatch]
on: [push, pull_request]
permissions:
contents: read

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/prefs_tests.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Prefs tests
on: [push, pull_request, workflow_dispatch]
on: [push, pull_request]
permissions:
contents: read

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/types_tests.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Types tests
on: [push, pull_request, workflow_dispatch]
on: [push, pull_request]
permissions:
contents: read

Expand Down
6 changes: 0 additions & 6 deletions .github/workflows/update_locales.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ on:
workflow_dispatch: # Allow manual triggering

permissions:
actions: write
contents: write
pull-requests: write

Expand Down Expand Up @@ -50,8 +49,3 @@ jobs:
--title "l10n: Update locale files" \
--body "Automated weekly update of locale files from mozilla-central." \
--label l10n || true
# GITHUB_TOKEN-initiated pushes/PRs don't trigger other workflows.
# Explicitly dispatch them so CI runs on the update-locales branch.
for workflow in ci.yml lint.yml codeql.yml; do
gh workflow run "$workflow" --ref update-locales
done
20 changes: 12 additions & 8 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -1104,14 +1104,18 @@ class Annotation {
}
} else if (borderStyle.has("Border")) {
const array = borderStyle.getArray("Border");
if (Array.isArray(array) && array.length >= 3) {
this.borderStyle.setHorizontalCornerRadius(array[0]);
this.borderStyle.setVerticalCornerRadius(array[1]);
this.borderStyle.setWidth(array[2], this.rectangle);

if (array.length === 4) {
// Dash array available
this.borderStyle.setDashArray(array[3], /* forceStyle = */ true);
if (Array.isArray(array)) {
if (array.length >= 3) {
this.borderStyle.setHorizontalCornerRadius(array[0]);
this.borderStyle.setVerticalCornerRadius(array[1]);
this.borderStyle.setWidth(array[2], this.rectangle);

if (array.length === 4) {
// Dash array available
this.borderStyle.setDashArray(array[3], /* forceStyle = */ true);
}
} else if (array.length === 0) {
this.borderStyle.setWidth(0);
}
}
} else {
Expand Down
16 changes: 15 additions & 1 deletion src/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2458,6 +2458,7 @@ class PartialEvaluator {
height: 0,
vertical: false,
prevTransform: null,
prevTextRise: 0,
textAdvanceScale: 0,
spaceInFlowMin: 0,
spaceInFlowMax: 0,
Expand Down Expand Up @@ -2906,7 +2907,19 @@ class PartialEvaluator {
return true;
}

if (Math.abs(advanceY) > textContentItem.height) {
// Compensate for a textRise change (e.g. superscript/subscript dropping
// back to baseline): textRise is baked into posY/lastPosY via tsm[5] in
// getCurrentTextTransform(), scaled by the Y component of the CTM×TM
// product, which equals currentTransform[3] / textState.fontSize.
// Without this correction a superscript whose textRise exceeds the line
// height triggers a spurious EOL when the rise returns to 0.
const textRiseDelta = textState.textRise - textContentItem.prevTextRise;
const advanceYCorrected =
textRiseDelta === 0
? advanceY
: advanceY -
(currentTransform[3] / textState.fontSize) * textRiseDelta;
if (Math.abs(advanceYCorrected) > textContentItem.height) {
appendEOL();
return true;
}
Expand Down Expand Up @@ -3068,6 +3081,7 @@ class PartialEvaluator {
if (scaledDim) {
// Save the position of the last visible character.
textChunk.prevTransform = getCurrentTextTransform();
textChunk.prevTextRise = textState.textRise;
}

const glyphUnicode = glyph.unicode;
Expand Down
70 changes: 70 additions & 0 deletions test/components/simple-viewer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!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-cmap_reader_factory": "../../src/display/cmap_reader_factory.js",
"display-standard_fontdata_factory": "../../src/display/standard_fontdata_factory.js",
"display-wasm_factory": "../../src/display/wasm_factory.js",
"display-fetch_stream": "../../src/display/fetch_stream.js",
"display-network": "../../src/display/network.js",
"display-node_stream": "../../src/display/stubs.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>
106 changes: 106 additions & 0 deletions test/components/simple-viewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* 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.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"
? "../../src/pdf.sandbox.js"
: "../../build/pdf.sandbox.mjs",
window.location
);

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,
});

const pdfViewer = new PDFViewer({
container,
eventBus,
l10n: new GenericL10n(navigator.language),
linkService: pdfLinkService,
findController: pdfFindController,
scriptingManager: pdfScriptingManager,
});
pdfLinkService.setViewer(pdfViewer);
pdfScriptingManager.setViewer(pdfViewer);

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);
1 change: 1 addition & 0 deletions test/integration/jasmine-boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ async function runTests(results) {
"reorganize_pages_spec.mjs",
"scripting_spec.mjs",
"signature_editor_spec.mjs",
"simple_viewer_spec.mjs",
"stamp_editor_spec.mjs",
"text_field_spec.mjs",
"text_layer_spec.mjs",
Expand Down
58 changes: 58 additions & 0 deletions test/integration/simple_viewer_spec.mjs
Original file line number Diff line number Diff line change
@@ -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.
*/

// Integration tests for the simple viewer (test/components/).

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);
})
);
});
});
});
1 change: 1 addition & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -890,3 +890,4 @@
!acroform_calculation_order.pdf
!extractPages_null_in_array.pdf
!issue20930.pdf
!text_rise_eol_bug.pdf
1 change: 1 addition & 0 deletions test/pdfs/issue20914.pdf.link
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://github.com/user-attachments/files/26097319/SiemensTest.PDF
Binary file added test/pdfs/text_rise_eol_bug.pdf
Binary file not shown.
8 changes: 8 additions & 0 deletions test/test_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14020,5 +14020,13 @@
"rounds": 1,
"link": true,
"type": "eq"
},
{
"id": "issue20914",
"file": "pdfs/issue20914.pdf",
"md5": "5740b4600e40c6fc920d38f96a74c6a5",
"rounds": 1,
"link": true,
"type": "eq"
}
]
Loading
Loading