Compare commits

...

12 Commits

Author SHA1 Message Date
Tim van der Meij
e7661983f7
Merge pull request #21333 from calixteman/fix_test_timeout
Recover the browser session on test timeout to keep running subsequent tests
2026-05-25 18:27:31 +02:00
Tim van der Meij
d1c85f87f7
Merge pull request #21330 from calixteman/fix_regex
Enable 'eslint-plugin-regexp' and fix existing findings
2026-05-25 18:22:21 +02:00
Calixte Denizet
91ca580f31 Recover the browser session on test timeout to keep running subsequent tests
When a reftest hangs and trips the per-browser timeout, the session was
closed, which left every remaining task in the per-browserType queue with
no consumer and effectively skipped the rest of the manifest. Instead,
mark the in-flight task(s) as failed, reload the page so the driver can
reconnect and request the next task, and only fall back to closing the
session if the reload itself fails.
2026-05-25 16:50:55 +02:00
calixteman
f82382e010
Merge pull request #21331 from calixteman/fix_cjk_file
Load the predefined CMap for composite fonts that omit the FontDescriptor
2026-05-25 16:40:11 +02:00
Jonas Jenwald
f156b8190b
Merge pull request #21332 from Snuffleupagus/direct-return
Avoid a temporary variable and return results directly in a couple of functions
2026-05-25 16:14:15 +02:00
Jonas Jenwald
48a12ac225 Avoid a temporary variable and return results directly in a couple of functions 2026-05-25 15:33:39 +02:00
Calixte Denizet
8f85e3f20b Load the predefined CMap for composite fonts that omit the FontDescriptor
and add font substitutions for the standard Acrobat CJK families.
2026-05-25 14:44:48 +02:00
calixteman
164ffb9042
Merge pull request #21321 from danyalahmed1995/fix/issue-21259-text-layer-spacing
Prevent inherited spacing from affecting text layer
2026-05-25 14:36:22 +02:00
Calixte Denizet
7bda0fc97c Enable 'eslint-plugin-regexp' and fix existing findings
Enable the recommended preset and fix or per-line-disable the 78
findings it surfaces. Most are equivalent rewrites, intentional
patterns (control chars, the whatwg email regex, autolinker URL regex)
keep their behavior via targeted disables.
2026-05-25 14:31:55 +02:00
Tim van der Meij
87b436ddd4
Merge pull request #21329 from calixteman/fix_selection_color
Force the text selection color to be in light mode
2026-05-25 13:59:13 +02:00
Calixte Denizet
f7af3ea9ac Force the text selection color to be in light mode
Most of the pdfs have a white background, so it makes sense to use the colors for light mode.
2026-05-25 12:59:54 +02:00
Danyal Ahmed
4ee9c39fc8 Prevent inherited spacing from affecting text layer
Fixes #21259.

Reset letter-spacing and word-spacing on the text layer and hidden measurement canvas so inherited page styles do not affect text layer alignment. Add an integration regression test for inherited spacing.
2026-05-24 23:56:23 +05:00
49 changed files with 629 additions and 78 deletions

View File

@ -7,6 +7,7 @@ import noUnsanitized from "eslint-plugin-no-unsanitized";
import perfectionist from "eslint-plugin-perfectionist";
import preferMathClamp from "./external/eslint_plugins/prefer-math-clamp.mjs";
import prettierRecommended from "eslint-plugin-prettier/recommended";
import regexpPlugin from "eslint-plugin-regexp";
import unicorn from "eslint-plugin-unicorn";
const jsFiles = folder => {
@ -55,6 +56,14 @@ export default [
\* ======================================================================== */
prettierRecommended,
{
files: jsFiles("."),
plugins: regexpPlugin.configs["flat/recommended"].plugins,
rules: {
...regexpPlugin.configs["flat/recommended"].rules,
"regexp/no-legacy-features": "off",
},
},
{
files: ["**/*.json", "**/.*.json"],
language: "json/json",

View File

@ -45,7 +45,7 @@ function watchObjectOrEmbed(elem) {
// <embed src> <object data>
var srcAttribute = "src" in elem ? "src" : "data";
var path = elem[srcAttribute];
if (!mimeType && !/\.pdf($|[?#])/i.test(path)) {
if (!mimeType && !/\.pdf(?:$|[?#])/i.test(path)) {
return;
}

View File

@ -132,7 +132,7 @@ function preprocess(inFilename, outFilename, defines) {
}
}
function expand(line) {
line = line.replaceAll(/__[\w]+__/g, function (variable) {
line = line.replaceAll(/__\w+__/g, function (variable) {
variable = variable.substring(2, variable.length - 2);
if (variable in defines) {
return defines[variable];
@ -158,6 +158,7 @@ function preprocess(inFilename, outFilename, defines) {
let state = STATE_NONE;
const stack = [];
const control =
// eslint-disable-next-line regexp/no-super-linear-backtracking
/^(?:\/\/|\s*\/\*|\s*<!--)\s*#(if|elif|else|endif|expand|include|error)\b(?:\s+(.*?)(?:\*\/|-->)?$)?/;
while ((line = readLine()) !== null) {

View File

@ -43,7 +43,7 @@ function extractFtlIds(ftlPath) {
const lines = readFileSync(ftlPath, "utf8").split("\n");
const ids = [];
for (const line of lines) {
const match = line.match(/^([a-zA-Z][a-zA-Z0-9-]*)\s*=/);
const match = line.match(/^([a-z][a-z0-9-]*)\s*=/i);
if (match) {
ids.push(match[1]);
}

View File

@ -14,7 +14,7 @@
*/
function parseAdobeCMap(content) {
let m = /(\bbegincmap\b[\s\S]*?)\bendcmap\b/.exec(content);
let m = /(\bbegincmap\b[\s\S]+?)\bendcmap\b/.exec(content);
if (!m) {
throw new Error("cmap was not found");
}
@ -37,7 +37,7 @@ function parseAdobeCMap(content) {
result.usecmap = m[1];
}
const re =
/(\d+)\s+(begincodespacerange|beginnotdefrange|begincidchar|begincidrange|beginbfchar|beginbfrange)\n([\s\S]*?)\n(endcodespacerange|endnotdefrange|endcidchar|endcidrange|endbfchar|endbfrange)/g;
/(\d+)\s+(begincodespacerange|beginnotdefrange|begincidchar|begincidrange|beginbfchar|beginbfrange)\n([\s\S]*?)\n(?:endcodespacerange|endnotdefrange|endcidchar|endcidrange|endbfchar|endbfrange)/g;
while ((m = re.exec(body))) {
const lines = m[3].toLowerCase().split("\n");

View File

@ -1135,7 +1135,7 @@ gulp.task("locale", function () {
if (!checkDir(dirPath)) {
continue;
}
if (!/^[a-z][a-z]([a-z])?(-[A-Z][A-Z])?$/.test(locale)) {
if (!/^[a-z]{2,3}(?:-[A-Z]{2})?$/.test(locale)) {
console.log("Skipping invalid locale: " + locale);
continue;
}

75
package-lock.json generated
View File

@ -31,6 +31,7 @@
"eslint-plugin-no-unsanitized": "^4.1.5",
"eslint-plugin-perfectionist": "^5.9.0",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-regexp": "^3.1.0",
"eslint-plugin-unicorn": "^64.0.0",
"globals": "^17.6.0",
"gulp": "^5.0.1",
@ -5462,6 +5463,28 @@
}
}
},
"node_modules/eslint-plugin-regexp": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-3.1.0.tgz",
"integrity": "sha512-qGXIC3DIKZHcK1H9A9+Byz9gmndY6TTSRkSMTZpNXdyCw2ObSehRgccJv35n9AdUakEjQp5VFNLas6BMXizCZg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.11.0",
"comment-parser": "^1.4.0",
"jsdoc-type-pratt-parser": "^7.0.0",
"refa": "^0.12.1",
"regexp-ast-analysis": "^0.7.1",
"scslre": "^0.3.0"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"peerDependencies": {
"eslint": ">=9.38.0"
}
},
"node_modules/eslint-plugin-unicorn": {
"version": "64.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-64.0.0.tgz",
@ -7378,6 +7401,16 @@
"node": ">=12.0.0"
}
},
"node_modules/jsdoc-type-pratt-parser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.2.0.tgz",
"integrity": "sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/jsdoc/node_modules/escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
@ -9092,6 +9125,19 @@
"node": ">= 10.13.0"
}
},
"node_modules/refa": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz",
"integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.8.0"
},
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@ -9112,6 +9158,20 @@
"node": ">=4"
}
},
"node_modules/regexp-ast-analysis": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz",
"integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.8.0",
"refa": "^0.12.1"
},
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/regexp-tree": {
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
@ -9446,6 +9506,21 @@
"dev": true,
"license": "MIT"
},
"node_modules/scslre": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz",
"integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.8.0",
"refa": "^0.12.0",
"regexp-ast-analysis": "^0.7.0"
},
"engines": {
"node": "^14.0.0 || >=16.0.0"
}
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",

View File

@ -26,6 +26,7 @@
"eslint-plugin-no-unsanitized": "^4.1.5",
"eslint-plugin-perfectionist": "^5.9.0",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-regexp": "^3.1.0",
"eslint-plugin-unicorn": "^64.0.0",
"globals": "^17.6.0",
"gulp": "^5.0.1",

View File

@ -564,7 +564,7 @@ function validateFontName(fontFamily, mustWarn = false) {
} else {
// See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident.
for (const ident of fontFamily.split(/[ \t]+/)) {
if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) {
if (/^(?:\d|-[\d-])/.test(ident) || !/^[\w\\-]+$/.test(ident)) {
if (mustWarn) {
warn(`FontFamily contains invalid <custom-ident>: ${fontFamily}.`);
}

View File

@ -1392,7 +1392,7 @@ class PDFDocument {
}
let fontFamily = descriptor.get("FontFamily");
// For example, "Wingdings 3" is not a valid font name in the css specs.
fontFamily = fontFamily.replaceAll(/[ ]+(\d)/g, "$1");
fontFamily = fontFamily.replaceAll(/ +(\d)/g, "$1");
const fontWeight = descriptor.get("FontWeight");
// Angle is expressed in degrees counterclockwise in PDF

View File

@ -4203,9 +4203,7 @@ class PartialEvaluator {
isSerifFont(baseFontName) {
// Simulating descriptor flags attribute
const fontNameWoStyle = baseFontName.split("-", 1)[0];
return (
fontNameWoStyle in getSerifFonts() || /serif/gi.test(fontNameWoStyle)
);
return fontNameWoStyle in getSerifFonts() || /serif/i.test(fontNameWoStyle);
}
getBaseFontMetrics(name) {
@ -4436,6 +4434,13 @@ class PartialEvaluator {
// FontDescriptor is only required for Type3 fonts when the document
// is a tagged pdf.
descriptor = Dict.empty;
} else if (composite) {
// Some PDFs omit the FontDescriptor on the descendant CIDFont when
// referencing one of the standard Acrobat CJK fonts via a predefined
// CMap (e.g. /Encoding /90ms-RKSJ-H with /BaseFont /HeiseiMin-W3).
// Fall through so the CMap is loaded by the composite-font path
// below; otherwise multi-byte codes would be decoded byte-by-byte.
descriptor = Dict.empty;
} else {
// Before PDF 1.5 if the font was one of the base 14 fonts, having a
// FontDescriptor was not required.

View File

@ -37,13 +37,10 @@ function getFloat214(view, offset) {
function getSubroutineBias(subrs) {
const numSubrs = subrs.length;
let bias = 32768;
if (numSubrs < 1240) {
bias = 107;
} else if (numSubrs < 33900) {
bias = 1131;
if (numSubrs >= 33900) {
return 32768;
}
return bias;
return numSubrs < 1240 ? 107 : 1131;
}
function parseCmap(data, start, end) {

View File

@ -21,6 +21,10 @@ const NORMAL = {
style: "normal",
weight: "normal",
};
const MEDIUM = {
style: "normal",
weight: "500",
};
const BOLD = {
style: "normal",
weight: "bold",
@ -364,6 +368,194 @@ const substitutionMap = new Map([
alias: "\xCB\xCE\xCC\xE5",
},
],
// Standard Acrobat CJK fonts. These BaseFont names appear in PDFs that
// don't embed a CJK font and rely on the reader having Acrobat's bundled
// CJK fonts installed.
// Adobe-Japan1 - Mincho (serif).
[
"HeiseiMin-W3",
{
local: [
"Hiragino Mincho ProN",
"Hiragino Mincho Pro",
"Yu Mincho",
"YuMincho",
"Source Han Serif JP",
"Noto Serif JP",
"Noto Serif CJK JP",
"IPAexMincho",
"IPAMincho",
"Takao Mincho",
"MS Mincho",
"MS PMincho",
],
style: NORMAL,
ultimate: "serif",
},
],
// Adobe-Japan1 - Gothic (sans-serif).
[
"HeiseiKakuGo-W5",
{
local: [
"Hiragino Kaku Gothic ProN",
"Hiragino Kaku Gothic Pro",
"Hiragino Sans",
"Yu Gothic",
"YuGothic",
"Source Han Sans JP",
"Noto Sans JP",
"Noto Sans CJK JP",
"IPAexGothic",
"IPAGothic",
"Takao Gothic",
"Meiryo",
"MS Gothic",
"MS PGothic",
],
style: MEDIUM,
ultimate: "sans-serif",
},
],
// Common Adobe-Japan1 variants and Kozuka names.
["HeiseiMin-W3-Acro", { alias: "HeiseiMin-W3" }],
["HeiseiKakuGo-W5-Acro", { alias: "HeiseiKakuGo-W5" }],
["KozMinPro-Regular", { alias: "HeiseiMin-W3" }],
["KozMinProVI-Regular", { alias: "HeiseiMin-W3" }],
["KozMinPr6N-Regular", { alias: "HeiseiMin-W3" }],
["KozGoPro-Regular", { alias: "HeiseiKakuGo-W5" }],
["KozGoProVI-Regular", { alias: "HeiseiKakuGo-W5" }],
["KozGoPr6N-Regular", { alias: "HeiseiKakuGo-W5" }],
// Adobe-GB1 - Song (Simplified Chinese serif).
[
"STSong-Light",
{
local: [
"STSong",
"Songti SC",
"Source Han Serif SC",
"Source Han Serif CN",
"Noto Serif SC",
"Noto Serif CJK SC",
"AR PL UMing CN",
"SimSun",
"NSimSun",
],
style: NORMAL,
ultimate: "serif",
},
],
// Adobe-GB1 - Hei (Simplified Chinese sans-serif).
[
"STHeiti-Regular",
{
local: [
"STHeiti",
"Heiti SC",
"PingFang SC",
"Source Han Sans SC",
"Source Han Sans CN",
"Noto Sans SC",
"Noto Sans CJK SC",
"Microsoft YaHei",
"SimHei",
"WenQuanYi Zen Hei",
],
style: NORMAL,
ultimate: "sans-serif",
},
],
["STSongStd-Light", { alias: "STSong-Light" }],
["AdobeSongStd-Light", { alias: "STSong-Light" }],
["AdobeHeitiStd-Regular", { alias: "STHeiti-Regular" }],
// KaiTi (regular script) and FangSong (imitation Song) are different
// typographic styles; route to the existing GB2312-keyed entries above.
["AdobeKaitiStd-Regular", { alias: "\xBF\xAC\xCC\xE5" }],
["AdobeFangsongStd-Regular", { alias: "\xB7\xC2\xCB\xCE" }],
// Adobe-CNS1 - Sung (Traditional Chinese serif).
[
"MSung-Light",
{
local: [
"Songti TC",
"LiSong Pro",
"Source Han Serif TC",
"Source Han Serif TW",
"Noto Serif TC",
"Noto Serif CJK TC",
"AR PL UMing TW",
"PMingLiU",
"MingLiU",
"MingLiU_HKSCS",
],
style: NORMAL,
ultimate: "serif",
},
],
// Adobe-CNS1 - Hei (Traditional Chinese sans-serif).
[
"MHei-Medium",
{
local: [
"Heiti TC",
"STHeiti",
"Source Han Sans TC",
"Source Han Sans TW",
"Noto Sans TC",
"Noto Sans CJK TC",
"PingFang TC",
"Microsoft JhengHei",
],
style: MEDIUM,
ultimate: "sans-serif",
},
],
["MSungStd-Light", { alias: "MSung-Light" }],
["AdobeMingStd-Light", { alias: "MSung-Light" }],
// Adobe-Korea1 - Myeongjo (Korean serif).
[
"HYSMyeongJo-Medium",
{
local: [
"AppleMyungjo",
"Source Han Serif KR",
"Noto Serif KR",
"Noto Serif CJK KR",
"Nanum Myeongjo",
"Batang",
],
style: MEDIUM,
ultimate: "serif",
},
],
// Adobe-Korea1 - Gothic (Korean sans-serif).
[
"HYGoThic-Medium",
{
local: [
"Apple SD Gothic Neo",
"AppleGothic",
"Source Han Sans KR",
"Noto Sans KR",
"Noto Sans CJK KR",
"Nanum Gothic",
"Malgun Gothic",
"Dotum",
"Gulim",
],
style: MEDIUM,
ultimate: "sans-serif",
},
],
["HYSMyeongJoStd-Medium", { alias: "HYSMyeongJo-Medium" }],
["AdobeMyungjoStd-Medium", { alias: "HYSMyeongJo-Medium" }],
// Bold variants reuse the same fallback list with a bold style override
// so the @font-face declaration requests a bold local() match.
["HYGoThic-Bold", { alias: "HYGoThic-Medium", style: BOLD }],
["AdobeGothicStd-Bold", { alias: "HYGoThic-Medium", style: BOLD }],
]);
const fontAliases = new Map([["Arial-Black", "ArialBlack"]]);
@ -579,8 +771,8 @@ function getFontSubstitution(
return null;
}
// Maybe we'll be lucky and the OS will have the font.
const bold = /bold/gi.test(baseFontName);
const italic = /oblique|italic/gi.test(baseFontName);
const bold = /bold/i.test(baseFontName);
const italic = /oblique|italic/i.test(baseFontName);
const style =
(bold && italic && BOLDITALIC) ||
(bold && BOLD) ||

View File

@ -1235,16 +1235,16 @@ class Font {
}
}
this.bold = /bold/gi.test(fontName);
this.italic = /oblique|italic/gi.test(fontName);
this.bold = /bold/i.test(fontName);
this.italic = /oblique|italic/i.test(fontName);
// Use 'name' instead of 'fontName' here because the original
// name ArialBlack for example will be replaced by Helvetica.
this.black = /Black/g.test(name);
this.black = /Black/.test(name);
// Use 'name' instead of 'fontName' here because the original
// name ArialNarrow for example will be replaced by Helvetica.
const isNarrow = /Narrow/g.test(name);
const isNarrow = /Narrow/.test(name);
// if at least one width is present, remeasure all chars when exists
this.remeasure =

View File

@ -132,7 +132,7 @@ class Lexer {
this.pos = 0;
this.len = data.length;
// Sticky regexes: set lastIndex before exec() to match at an exact offset.
this._numberPattern = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/y;
this._numberPattern = /[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/iy;
this._identifierPattern = /[a-z]+/y;
}

View File

@ -16,7 +16,11 @@
import { stringToBytes, Util, warn } from "../shared/util.js";
function isAscii(str) {
return typeof str === "string" && (!str || /^[\x00-\x7F]*$/.test(str));
return (
typeof str === "string" &&
// eslint-disable-next-line no-control-regex
(!str || /^[\x00-\x7F]*$/.test(str))
);
}
// If the string is null or undefined then it is returned as is.
@ -91,6 +95,7 @@ function stringToPDFString(str, keepEscapeSequence = false) {
if (keepEscapeSequence || !decoded.includes("\x1b")) {
return decoded;
}
// eslint-disable-next-line no-control-regex
return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
} catch (ex) {
warn(`stringToPDFString: "${ex}".`);

View File

@ -242,7 +242,7 @@ function getUnicodeRangeFor(value, lastPosition = -1) {
return -1;
}
const SpecialCharRegExp = new RegExp("^(\\s)|(\\p{Mn})|(\\p{Cf})$", "u");
const SpecialCharRegExp = /^(\s)|(\p{Mn})|(\p{Cf})$/u;
const CategoryCache = new Map();
function getCharUnicodeCategory(char) {

View File

@ -1003,7 +1003,7 @@ class Rename extends ContentObject {
// is no colon.
if (
this[$content].toLowerCase().startsWith("xml") ||
new RegExp("[\\p{L}_][\\p{L}\\d._\\p{M}-]*", "u").test(this[$content])
/[\p{L}_][\p{L}\d._\p{M}-]*/u.test(this[$content])
) {
warn("XFA - Rename: invalid XFA name");
}

View File

@ -90,7 +90,7 @@ class FontFinder {
return font;
}
const pattern = /,|-|_| |bolditalic|bold|italic|regular|it/gi;
const pattern = /[,\-_ ]|bolditalic|bold|italic|regular|it/gi;
let name = fontName.replaceAll(pattern, "");
font = this.fonts.get(name);
if (font) {

View File

@ -116,11 +116,11 @@ const TOKEN = {
upto: 54,
};
const hexPattern = /^[uU]([0-9a-fA-F]{4,8})/;
const numberPattern = /^\d*(?:\.\d*)?(?:[Ee][+-]?\d+)?/;
const dotNumberPattern = /^\d*(?:[Ee][+-]?\d+)?/;
const hexPattern = /^u([0-9a-f]{4,8})/i;
const numberPattern = /^\d*(?:\.\d*)?(?:E[+-]?\d+)?/i;
const dotNumberPattern = /^\d*(?:E[+-]?\d+)?/i;
const eolPattern = /[\r\n]+/;
const identifierPattern = new RegExp("^[\\p{L}_$!][\\p{L}\\p{N}_$]*", "u");
const identifierPattern = /^[\p{L}_$!][\p{L}\p{N}_$]*/u;
class Token {
constructor(id, value = null) {

View File

@ -134,8 +134,7 @@ function mapStyle(styleStr, node, richText) {
? `${style[key]} ${newValue}`
: newValue;
} else {
style[key.replaceAll(/-([a-zA-Z])/g, (_, x) => x.toUpperCase())] =
newValue;
style[key.replaceAll(/-([a-z])/gi, (_, x) => x.toUpperCase())] = newValue;
}
}

View File

@ -1793,16 +1793,14 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
case "deleteWordBackward": {
const match = value
.substring(0, selectionStart)
.match(/\w*[^\w]*$/);
.match(/\w*\W*$/);
if (match) {
selStart -= match[0].length;
}
break;
}
case "deleteWordForward": {
const match = value
.substring(selectionStart)
.match(/^[^\w]*\w*/);
const match = value.substring(selectionStart).match(/^\W*\w*/);
if (match) {
selEnd += match[0].length;
}

View File

@ -81,6 +81,7 @@ function getFilenameFromContentDispositionHeader(contentDisposition) {
}
function textdecode(encoding, value) {
if (encoding) {
// eslint-disable-next-line no-control-regex
if (!/^[\x00-\xFF]+$/.test(value)) {
return value;
}
@ -184,6 +185,7 @@ function getFilenameFromContentDispositionHeader(contentDisposition) {
// Firefox also decodes words even where RFC 2047 section 5 states:
// "An 'encoded-word' MUST NOT appear within a 'quoted-string'."
// eslint-disable-next-line no-control-regex
if (!value.startsWith("=?") || /[\x00-\x19\x80-\xff]/.test(value)) {
return value;
}
@ -195,12 +197,12 @@ function getFilenameFromContentDispositionHeader(contentDisposition) {
// encoded-text = any printable ASCII character other than ? or space.
// ... but Firefox permits ? and space.
return value.replaceAll(
/=\?([\w-]*)\?([QqBb])\?((?:[^?]|\?(?!=))*)\?=/g,
/=\?([\w-]*)\?([QB])\?((?:[^?]|\?(?!=))*)\?=/gi,
function (matches, charset, encoding, text) {
if (encoding === "q" || encoding === "Q") {
// RFC 2047 section 4.2.
text = text.replaceAll("_", " ");
text = text.replaceAll(/=([0-9a-fA-F]{2})/g, function (match, hex) {
text = text.replaceAll(/=([0-9a-f]{2})/gi, function (match, hex) {
return String.fromCharCode(parseInt(hex, 16));
});
return textdecode(charset, text);

View File

@ -296,7 +296,7 @@ class PDFDateString {
"(\\d{2})?" + // Hour (optional)
"(\\d{2})?" + // Minute (optional)
"(\\d{2})?" + // Second (optional)
"([Z|+|-])?" + // Universal time relation (optional)
"([Z|+\\-])?" + // Universal time relation (optional)
"(\\d{2})?" + // Offset hour (optional)
"'?" + // Splitting apostrophe (optional)
"(\\d{2})?" + // Offset minute (optional)
@ -756,7 +756,7 @@ function renderRichText({ html, dir, className }, container) {
if (typeof html === "string") {
const p = document.createElement("p");
p.dir = dir || "auto";
const lines = html.split(/(?:\r\n?|\n)/);
const lines = html.split(/\r\n?|\n/);
for (let i = 0, ii = lines.length; i < ii; ++i) {
const line = lines[i];
p.append(document.createTextNode(line));

View File

@ -132,6 +132,9 @@ class DOMFilterFactory extends BaseFilterFactory {
if (!this.#_defs) {
const div = this.#document.createElement("div");
const { style } = div;
// The pdf colors are mostly in light mode (white background with a black
// foreground), so the filters are created in light mode.
style.colorScheme = "only light";
style.visibility = "hidden";
style.contain = "strict";
style.width = style.height = 0;

View File

@ -140,7 +140,7 @@ class PDFNetworkStream extends BasePDFStream {
const chunk = getArrayBuffer(xhr.response);
if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
const rangeHeader = xhr.getResponseHeader("Content-Range");
if (/bytes (\d+)-(\d+)\/(\d+)/.test(rangeHeader)) {
if (/bytes \d+-\d+\/\d+/.test(rangeHeader)) {
pendingRequest.onDone(chunk);
} else {
warn(`Missing or invalid "Content-Range" header.`);

View File

@ -473,7 +473,8 @@ class TextLayer {
// OffscreenCanvas.
const canvas = document.createElement("canvas");
canvas.style.cssText =
"position:absolute;top:0;left:0;width:0;height:0;display:none";
"position:absolute;top:0;left:0;width:0;height:0;display:none;" +
"letter-spacing:normal;word-spacing:normal";
canvas.lang = lang;
document.body.append(canvas);
ctx = canvas.getContext("2d", {

View File

@ -26,8 +26,9 @@ class AForm {
// The e-mail address regex below originates from:
// https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
// eslint-disable-next-line regexp/use-ignore-case
this._emailRegex = new RegExp(
"^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+" +
"^[\\w.!#$%&'*+/=?^`{|}~-]+" +
"@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?" +
"(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
);
@ -184,12 +185,12 @@ class AForm {
// comma sep
pattern = event.willCommit
? /^[+-]?(\d+(,\d*)?|,\d+)$/
: /^[+-]?\d*,?\d*$/;
: /^[+-]?\d*(?:,\d*)?$/;
} else {
// dot sep
pattern = event.willCommit
? /^[+-]?(\d+(\.\d*)?|\.\d+)$/
: /^[+-]?\d*\.?\d*$/;
: /^[+-]?\d*(?:\.\d*)?$/;
}
if (!pattern.test(value)) {
@ -571,7 +572,7 @@ class AForm {
event.rc = true;
}
const re = /([-()]|\s)+/g;
const re = /[-()\s]+/g;
value = value.replaceAll(re, "");
for (const format of formats) {
this.#AFSpecial_KeystrokeEx_helper(

View File

@ -62,6 +62,7 @@ class Util extends PDFObject {
throw new TypeError("First argument of printf must be a string");
}
// eslint-disable-next-line regexp/no-misleading-capturing-group
const pattern = /%(,[0-4])?([+ 0#]+)?(\d+)?(\.\d+)?(.)/g;
const PLUS = 1;
const SPACE = 2;

View File

@ -1039,7 +1039,7 @@ function normalizeUnicode(str) {
// required.
// It appears that most the chars here contain some ligatures.
NormalizeRegex =
/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
/([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
NormalizationMap = new Map([["ſt", "ſt"]]);
}
return str.replaceAll(NormalizeRegex, (_, p1, p2) =>

View File

@ -21,7 +21,7 @@ function rewriteWebArchiveUrl(url) {
// 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?:\/\/.+)/g;
/(^https?:\/\/web\.archive\.org\/web\/)(\d+)(\/https?:\/\/.+)/;
const urlParts = webArchiveRegex.exec(url);
if (urlParts) {
return `${urlParts[1]}${urlParts[2]}if_${urlParts[3]}`;

View File

@ -36,7 +36,7 @@ describe("font_fpgm", function () {
verifyTtxOutput(output);
expect(
/(ENDF\[ \]|SVTCA\[0\])\s*\/\*.*\*\/\s*<\/assembly>\s*<\/fpgm>/.test(
/(?:ENDF\[ \]|SVTCA\[0\])\s*\/\*.*\*\/\s*<\/assembly>\s*<\/fpgm>/.test(
output
)
).toEqual(true);

View File

@ -109,7 +109,7 @@ describe("font_glyf", function () {
);
expect(notdef).not.toBeNull();
expect(notdef[1] || "").not.toMatch(
/<component\b[^>]*glyphName="\.notdef"/
/<component\b[^>]+glyphName="\.notdef"/
);
});
});
@ -132,7 +132,7 @@ describe("font_glyf", function () {
const output = await ttx(font.data);
verifyTtxOutput(output);
expect(
/<OS_2>\s*(<!--[\s\S]*?-->\s*)?<version value="3"\/>/.test(output)
/<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);

View File

@ -49,6 +49,61 @@ import { startBrowser } from "../test.mjs";
*/
describe("Text layer", () => {
describe("Text layout", () => {
let pages;
beforeEach(async () => {
pages = await loadAndWait(
"tracemonkey.pdf",
".textLayer .endOfContent",
100,
{
postPageSetup: async page => {
await page.evaluate(() => {
const style = document.createElement("style");
style.textContent = `
body,
#mainContainer {
letter-spacing: 5px;
word-spacing: 5px;
}
`;
document.documentElement.append(style);
});
},
}
);
});
afterEach(async () => {
await closePages(pages);
});
it("must ignore inherited text spacing styles", async () => {
await Promise.all(
pages.map(async ([_, page]) => {
const spacing = await page.evaluate(() => {
const textLayer = document.querySelector(".textLayer");
const span = textLayer.querySelector("span");
const textLayerStyle = getComputedStyle(textLayer);
const spanStyle = getComputedStyle(span);
return {
textLayerLetterSpacing: textLayerStyle.letterSpacing,
textLayerWordSpacing: textLayerStyle.wordSpacing,
spanLetterSpacing: spanStyle.letterSpacing,
spanWordSpacing: spanStyle.wordSpacing,
};
});
expect(spacing.textLayerLetterSpacing).toEqual("normal");
expect(spacing.spanLetterSpacing).toEqual("normal");
expect(["0px", "normal"]).toContain(spacing.textLayerWordSpacing);
expect(["0px", "normal"]).toContain(spacing.spanWordSpacing);
})
);
});
});
describe("Text selection", () => {
// page.mouse.move(x, y, { steps: ... }) doesn't work in Firefox, because
// puppeteer will send fractional intermediate positions and Firefox doesn't
@ -530,7 +585,7 @@ describe("Text layer", () => {
// Selection starts mid-word in Heading 1, so assert the stable
// trailing content rather than exact full-line boundaries.
.toHaveRoughlySelected(
/ing 1\s+This paragraph 1\.\s+Heading 2\s+This paragraph 2/s
/ing 1\s+This paragraph 1\.\s+Heading 2\s+This paragraph 2/
);
})
);

View File

@ -924,3 +924,4 @@
!Embedded_font.pdf
!issue18548_reduced.pdf
!issue_cff_unsigned_bbox.pdf
!90ms_rksj_h_sample.pdf

View 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

View File

@ -205,7 +205,7 @@ window.onload = function () {
}
line = match[1];
match = line.match(
/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL)(\(EXPECTED RANDOM\)|) \| ([^|]+) \|(.*)/
/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL)(\(EXPECTED RANDOM\))? \| ([^|]+) \|(.*)/
);
if (match) {
const state = match[1];
@ -225,7 +225,7 @@ window.onload = function () {
continue;
}
match = line.match(
/^ {2}IMAGE[^:]*\((\d+\.?\d*)x(\d+\.?\d*)x(\d+\.?\d*)\): (.*)$/
/^ {2}IMAGE[^:]*\((\d+(?:\.\d*)?)x(\d+(?:\.\d*)?)x(\d+(?:\.\d*)?)\): (.*)$/
);
if (match) {
const item = gTestItems.at(-1);

View File

@ -78,7 +78,7 @@ function parseOptions() {
// Expand `-X=value` short-option forms into `["-X", "value"]` since
// parseArgs only strips the `=` separator for long options (--foo=bar).
const args = process.argv.slice(2).flatMap(arg => {
const m = arg.match(/^(-[a-zA-Z])=(.*)/s);
const m = arg.match(/^(-[a-z])=(.*)/is);
return m ? [m[1], m[2]] : [arg];
});
const { values } = parseArgs({
@ -423,15 +423,38 @@ async function startRefTest(masterMode, showRefImages) {
checkRefsTmp();
}
function handleSessionTimeout(session) {
if (session.closed) {
async function handleSessionTimeout(session) {
if (session.closed || session.recovering) {
return;
}
const inflightIds = Object.keys(session.tasks);
const suffix = inflightIds.length > 0 ? ` (${inflightIds.join(", ")})` : "";
console.log(
`${TEST_UNEXPECTED_FAIL} | test failed ${session.name} has not responded in ${browserTimeout}s`
`${TEST_UNEXPECTED_FAIL} | test failed ${session.name} has not responded in ${browserTimeout}s${suffix}`
);
session.numErrors += session.remaining;
session.remaining = 0;
session.taskResults = {};
session.tasks = {};
monitorBrowserTimeout(session, null);
if (session.page) {
session.recovering = true;
try {
await session.page.reload({
timeout: browserTimeout * 1000,
waitUntil: "domcontentloaded",
});
session.recovering = false;
monitorBrowserTimeout(session, handleSessionTimeout);
return;
} catch (err) {
console.log(
`Failed to reload ${session.name} after timeout: ${err.message}`
);
session.recovering = false;
}
}
closeSession(session.name);
}
@ -768,9 +791,15 @@ async function handleWsBinaryResult(data) {
const { browser, id, round, page, failure, lastPageNum, numberOfTasks } =
meta;
const session = getSession(browser);
if (!session || session.closed) {
return;
}
monitorBrowserTimeout(session, handleSessionTimeout);
const taskResults = session.taskResults[id];
if (!taskResults) {
return;
}
if (!taskResults[round]) {
taskResults[round] = [];
}

View File

@ -4009,6 +4009,25 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
await loadingTask.destroy();
});
it("gets text content from a Type0 composite font with no FontDescriptor, using a predefined CMap", async function () {
const loadingTask = getDocument(
buildGetDocumentParams("90ms_rksj_h_sample.pdf", {
cMapUrl: CMAP_URL,
useWorkerFetch: false,
})
);
const pdfDoc = await loadingTask.promise;
const pdfPage = await pdfDoc.getPage(1);
const { items } = await pdfPage.getTextContent({
disableNormalization: true,
});
const text = mergeText(items);
expect(text).toEqual("Hello ASCII\n日本語テスト");
await loadingTask.destroy();
});
it("gets text content with a rised text", async function () {
const loadingTask = getDocument(buildGetDocumentParams("issue16221.pdf"));
const pdfDoc = await loadingTask.promise;

View File

@ -91,7 +91,7 @@ describe("custom ownerDocument", function () {
const checkFont = font => /g_d\d+_f1/.test(font.family);
const checkFontFaceRule = rule =>
/^@font-face {font-family:"g_d\d+_f1";src:/.test(rule);
/^@font-face \{font-family:"g_d\d+_f1";src:/.test(rule);
beforeEach(() => {
globalThis.FontFace = function MockFontFace(name) {

View File

@ -546,4 +546,96 @@ describe("getFontSubstitution", function () {
/^"ArialBlack",g_d(\d+)_sf(\d+),sans-serif$/
);
});
it("should substitute HeiseiMin-W3", () => {
const fontName = "HeiseiMin-W3";
const fontSubstitution = getFontSubstitution(
new Map(),
idFactory,
localFontPath,
fontName,
undefined,
"CIDFontType2"
);
expect(fontSubstitution).toEqual(
jasmine.objectContaining({
guessFallback: false,
baseFontName: "HeiseiMin-W3",
src:
"local(Hiragino Mincho ProN),local(Hiragino Mincho Pro)," +
"local(Yu Mincho),local(YuMincho),local(Source Han Serif JP)," +
"local(Noto Serif JP),local(Noto Serif CJK JP)," +
"local(IPAexMincho),local(IPAMincho),local(Takao Mincho)," +
"local(MS Mincho),local(MS PMincho)",
style: {
style: "normal",
weight: "normal",
},
})
);
expect(fontSubstitution.css).toMatch(
/^"HeiseiMin W3",g_d(\d+)_sf(\d+),serif$/
);
});
it("should substitute a Kozuka Mincho alias", () => {
const fontName = "KozMinPr6N-Regular";
const fontSubstitution = getFontSubstitution(
new Map(),
idFactory,
localFontPath,
fontName,
undefined,
"CIDFontType0"
);
expect(fontSubstitution).toEqual(
jasmine.objectContaining({
guessFallback: false,
baseFontName: "KozMinPr6N-Regular",
src:
"local(Hiragino Mincho ProN),local(Hiragino Mincho Pro)," +
"local(Yu Mincho),local(YuMincho),local(Source Han Serif JP)," +
"local(Noto Serif JP),local(Noto Serif CJK JP)," +
"local(IPAexMincho),local(IPAMincho),local(Takao Mincho)," +
"local(MS Mincho),local(MS PMincho)",
style: {
style: "normal",
weight: "normal",
},
})
);
expect(fontSubstitution.css).toMatch(
/^"KozMinPr6N",g_d(\d+)_sf(\d+),serif$/
);
});
it("should substitute HYGoThic-Medium", () => {
const fontName = "HYGoThic-Medium";
const fontSubstitution = getFontSubstitution(
new Map(),
idFactory,
localFontPath,
fontName,
undefined,
"CIDFontType2"
);
expect(fontSubstitution).toEqual(
jasmine.objectContaining({
guessFallback: false,
baseFontName: "HYGoThic-Medium",
src:
"local(Apple SD Gothic Neo),local(AppleGothic)," +
"local(Source Han Sans KR),local(Noto Sans KR)," +
"local(Noto Sans CJK KR),local(Nanum Gothic)," +
"local(Malgun Gothic),local(Dotum),local(Gulim)",
style: {
style: "normal",
weight: "500",
},
})
);
expect(fontSubstitution.css).toMatch(
/^"HYGoThic",g_d(\d+)_sf(\d+),sans-serif$/
);
});
});

View File

@ -1115,7 +1115,7 @@ const PDFViewerApplication = {
// - The title may contain incorrectly encoded characters, which thus
// looks broken, hence we ignore the Metadata entry when it contains
// characters from the Specials Unicode block (fixes bug 1605526).
if (title !== "Untitled" && !/[\uFFF0-\uFFFF]/g.test(title)) {
if (title !== "Untitled" && !/[\uFFF0-\uFFFF]/.test(title)) {
return title;
}
}

View File

@ -19,7 +19,7 @@ if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
const isAndroid = /Android/.test(userAgent);
const isIOS =
/\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) ||
/\b(?:iPad|iPhone|iPod)(?=;)/.test(userAgent) ||
(platform === "MacIntel" && maxTouchPoints > 1);
// Limit canvas size to 5 mega-pixels on mobile.

View File

@ -138,7 +138,8 @@ class Autolinker {
static findLinks(text) {
// Regex can be tested and verified at https://regex101.com/r/rXoLiT/2.
this.#regex ??=
/\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[[\p{P}--\-]<>]]+(?:\.[\S--[[\p{P}--\-]<>]]+)+)/gmv;
// eslint-disable-next-line regexp/no-super-linear-backtracking
/\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[[\p{P}--\-]<>]]+(?:\.[\S--[[\p{P}--\-]<>]]+)+)/gv;
const [normalizedText, diffs] = normalize(text, { ignoreDashEOL: true });
const matches = normalizedText.matchAll(this.#regex);

View File

@ -36,8 +36,8 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) {
// Run this code outside DOMContentLoaded to make sure that the URL
// is rewritten as soon as possible.
const queryString = document.location.search.slice(1);
const m = /(^|&)file=([^&]*)/.exec(queryString);
let defaultUrl = m ? decodeURIComponent(m[2]) : "";
const m = /(?:^|&)file=([^&]*)/.exec(queryString);
let defaultUrl = m ? decodeURIComponent(m[1]) : "";
if (!defaultUrl && queryString.startsWith("DNR:")) {
// Redirected via DNR, see registerPdfRedirectRule in pdfHandler.js.
defaultUrl = queryString.slice(4);

View File

@ -115,10 +115,7 @@ class FontView {
return;
}
const ext = MIMETYPE_TO_EXTENSION.get(font.mimetype) ?? "font";
const name = (font.name || font.loadedName).replaceAll(
/[^a-z0-9_-]/gi,
"_"
);
const name = (font.name || font.loadedName).replaceAll(/[^\w-]/g, "_");
const blob = new Blob([font.data], { type: font.mimetype });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");

View File

@ -75,8 +75,8 @@ let DIACRITICS_EXCEPTION_STR; // Lazily initialized, see below.
const DIACRITICS_REG_EXP = /\p{M}+/gu;
const SPECIAL_CHARS_REG_EXP = /([+^$|])|(\p{P}+)|(\s+)|(\p{M})|(\p{L})/gu;
const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u;
const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u;
const NOT_DIACRITIC_FROM_END_REG_EXP = /(\P{M})\p{M}*$/u;
const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*(\P{M})/u;
// The range [AC00-D7AF] corresponds to the Hangul syllables.
// The few other chars are some CJK Compatibility Ideographs.
@ -149,7 +149,7 @@ function normalize(text, options = {}) {
];
normalizationRegex = new RegExp(
regexps.map(r => `(${r})`).join("|"),
"gum"
"gmu"
);
if (hasSyllables) {

View File

@ -22,6 +22,8 @@
overflow: clip;
opacity: 1;
line-height: 1;
letter-spacing: normal;
word-spacing: normal;
text-size-adjust: none;
forced-color-adjust: none;
transform-origin: 0 0;

View File

@ -175,6 +175,7 @@ function parseQueryString(query) {
return params;
}
// eslint-disable-next-line no-control-regex
const InvisibleCharsRegExp = /[\x00-\x1F]/g;
/**
@ -266,14 +267,11 @@ function approximateFraction(x) {
b = q;
}
}
let result;
// Select closest of the neighbours to x.
if (x_ - a / b < c / d - x_) {
result = x_ === x ? [a, b] : [b, a];
} else {
result = x_ === x ? [c, d] : [d, c];
return x_ === x ? [a, b] : [b, a];
}
return result;
return x_ === x ? [c, d] : [d, c];
}
/**