1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-19 06:38:07 +02:00
pdf.js/web/pdf_document_properties.js
Jonas Jenwald c681ff25d8 Change how we handle l10n-args for dates in the DocumentProperties dialog (bug 1922618)
Strangely enough the code works just fine as-is in the GENERIC viewer, so there must be some difference between the Firefox built-in Fluent implementation and the Fluent.js one.

It seems that we can work-around the problem by handling this l10n-arg the same way that we handle dates in the AnnotationLayer, and testing this with the Firefox Devtools it seems that it should work.
2024-10-04 19:50:05 +02:00

347 lines
11 KiB
JavaScript

/* Copyright 2012 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.
*/
/** @typedef {import("./event_utils.js").EventBus} EventBus */
/** @typedef {import("./interfaces.js").IL10n} IL10n */
/** @typedef {import("./overlay_manager.js").OverlayManager} OverlayManager */
// eslint-disable-next-line max-len
/** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */
import { getPageSizeInches, isPortraitOrientation } from "./ui_utils.js";
import { PDFDateString } from "pdfjs-lib";
// See https://en.wikibooks.org/wiki/Lentis/Conversion_to_the_Metric_Standard_in_the_United_States
const NON_METRIC_LOCALES = ["en-us", "en-lr", "my"];
// Should use the format: `width x height`, in portrait orientation. The names,
// which are l10n-ids, should be lowercase.
// See https://en.wikipedia.org/wiki/Paper_size
const US_PAGE_NAMES = {
"8.5x11": "pdfjs-document-properties-page-size-name-letter",
"8.5x14": "pdfjs-document-properties-page-size-name-legal",
};
const METRIC_PAGE_NAMES = {
"297x420": "pdfjs-document-properties-page-size-name-a-three",
"210x297": "pdfjs-document-properties-page-size-name-a-four",
};
function getPageName(size, isPortrait, pageNames) {
const width = isPortrait ? size.width : size.height;
const height = isPortrait ? size.height : size.width;
return pageNames[`${width}x${height}`];
}
/**
* @typedef {Object} PDFDocumentPropertiesOptions
* @property {HTMLDialogElement} dialog - The overlay's DOM element.
* @property {Object} fields - Names and elements of the overlay's fields.
* @property {HTMLButtonElement} closeButton - Button for closing the overlay.
*/
class PDFDocumentProperties {
#fieldData = null;
/**
* @param {PDFDocumentPropertiesOptions} options
* @param {OverlayManager} overlayManager - Manager for the viewer overlays.
* @param {EventBus} eventBus - The application event bus.
* @param {IL10n} l10n - Localization service.
* @param {function} fileNameLookup - The function that is used to lookup
* the document fileName.
*/
constructor(
{ dialog, fields, closeButton },
overlayManager,
eventBus,
l10n,
fileNameLookup
) {
this.dialog = dialog;
this.fields = fields;
this.overlayManager = overlayManager;
this.l10n = l10n;
this._fileNameLookup = fileNameLookup;
this.#reset();
// Bind the event listener for the Close button.
closeButton.addEventListener("click", this.close.bind(this));
this.overlayManager.register(this.dialog);
eventBus._on("pagechanging", evt => {
this._currentPageNumber = evt.pageNumber;
});
eventBus._on("rotationchanging", evt => {
this._pagesRotation = evt.pagesRotation;
});
}
/**
* Open the document properties overlay.
*/
async open() {
await Promise.all([
this.overlayManager.open(this.dialog),
this._dataAvailableCapability.promise,
]);
const currentPageNumber = this._currentPageNumber;
const pagesRotation = this._pagesRotation;
// If the document properties were previously fetched (for this PDF file),
// just update the dialog immediately to avoid redundant lookups.
if (
this.#fieldData &&
currentPageNumber === this.#fieldData._currentPageNumber &&
pagesRotation === this.#fieldData._pagesRotation
) {
this.#updateUI();
return;
}
// Get the document properties.
const {
info,
/* metadata, */
/* contentDispositionFilename, */
contentLength,
} = await this.pdfDocument.getMetadata();
const [
fileName,
fileSize,
creationDate,
modificationDate,
pageSize,
isLinearized,
] = await Promise.all([
this._fileNameLookup(),
this.#parseFileSize(contentLength),
this.#parseDate(info.CreationDate),
this.#parseDate(info.ModDate),
// eslint-disable-next-line arrow-body-style
this.pdfDocument.getPage(currentPageNumber).then(pdfPage => {
return this.#parsePageSize(getPageSizeInches(pdfPage), pagesRotation);
}),
this.#parseLinearization(info.IsLinearized),
]);
this.#fieldData = Object.freeze({
fileName,
fileSize,
title: info.Title,
author: info.Author,
subject: info.Subject,
keywords: info.Keywords,
creationDate,
modificationDate,
creator: info.Creator,
producer: info.Producer,
version: info.PDFFormatVersion,
pageCount: this.pdfDocument.numPages,
pageSize,
linearized: isLinearized,
_currentPageNumber: currentPageNumber,
_pagesRotation: pagesRotation,
});
this.#updateUI();
// Get the correct fileSize, since it may not have been available
// or could potentially be wrong.
const { length } = await this.pdfDocument.getDownloadInfo();
if (contentLength === length) {
return; // The fileSize has already been correctly set.
}
const data = Object.assign(Object.create(null), this.#fieldData);
data.fileSize = await this.#parseFileSize(length);
this.#fieldData = Object.freeze(data);
this.#updateUI();
}
/**
* Close the document properties overlay.
*/
async close() {
this.overlayManager.close(this.dialog);
}
/**
* Set a reference to the PDF document in order to populate the dialog fields
* with the document properties. Note that the dialog will contain no
* information if this method is not called.
*
* @param {PDFDocumentProxy} pdfDocument - A reference to the PDF document.
*/
setDocument(pdfDocument) {
if (this.pdfDocument) {
this.#reset();
this.#updateUI();
}
if (!pdfDocument) {
return;
}
this.pdfDocument = pdfDocument;
this._dataAvailableCapability.resolve();
}
#reset() {
this.pdfDocument = null;
this.#fieldData = null;
this._dataAvailableCapability = Promise.withResolvers();
this._currentPageNumber = 1;
this._pagesRotation = 0;
}
/**
* Always updates all of the dialog fields, to prevent inconsistent UI state.
* NOTE: If the contents of a particular field is neither a non-empty string,
* nor a number, it will fall back to "-".
*/
#updateUI() {
if (this.#fieldData && this.overlayManager.active !== this.dialog) {
// Don't bother updating the dialog if it's already been closed,
// unless it's being reset (i.e. `this.#fieldData === null`),
// since it will be updated the next time `this.open` is called.
return;
}
for (const id in this.fields) {
const content = this.#fieldData?.[id];
this.fields[id].textContent = content || content === 0 ? content : "-";
}
}
async #parseFileSize(b = 0) {
const kb = b / 1024,
mb = kb / 1024;
return kb
? this.l10n.get(
mb >= 1
? "pdfjs-document-properties-size-mb"
: "pdfjs-document-properties-size-kb",
{ mb, kb, b }
)
: undefined;
}
async #parsePageSize(pageSizeInches, pagesRotation) {
if (!pageSizeInches) {
return undefined;
}
// Take the viewer rotation into account as well; compare with Adobe Reader.
if (pagesRotation % 180 !== 0) {
pageSizeInches = {
width: pageSizeInches.height,
height: pageSizeInches.width,
};
}
const isPortrait = isPortraitOrientation(pageSizeInches),
nonMetric = NON_METRIC_LOCALES.includes(this.l10n.getLanguage());
let sizeInches = {
width: Math.round(pageSizeInches.width * 100) / 100,
height: Math.round(pageSizeInches.height * 100) / 100,
};
// 1in == 25.4mm; no need to round to 2 decimals for millimeters.
let sizeMillimeters = {
width: Math.round(pageSizeInches.width * 25.4 * 10) / 10,
height: Math.round(pageSizeInches.height * 25.4 * 10) / 10,
};
let nameId =
getPageName(sizeInches, isPortrait, US_PAGE_NAMES) ||
getPageName(sizeMillimeters, isPortrait, METRIC_PAGE_NAMES);
if (
!nameId &&
!(
Number.isInteger(sizeMillimeters.width) &&
Number.isInteger(sizeMillimeters.height)
)
) {
// Attempt to improve the page name detection by falling back to fuzzy
// matching of the metric dimensions, to account for e.g. rounding errors
// and/or PDF files that define the page sizes in an imprecise manner.
const exactMillimeters = {
width: pageSizeInches.width * 25.4,
height: pageSizeInches.height * 25.4,
};
const intMillimeters = {
width: Math.round(sizeMillimeters.width),
height: Math.round(sizeMillimeters.height),
};
// Try to avoid false positives, by only considering "small" differences.
if (
Math.abs(exactMillimeters.width - intMillimeters.width) < 0.1 &&
Math.abs(exactMillimeters.height - intMillimeters.height) < 0.1
) {
nameId = getPageName(intMillimeters, isPortrait, METRIC_PAGE_NAMES);
if (nameId) {
// Update *both* sizes, computed above, to ensure that the displayed
// dimensions always correspond to the detected page name.
sizeInches = {
width: Math.round((intMillimeters.width / 25.4) * 100) / 100,
height: Math.round((intMillimeters.height / 25.4) * 100) / 100,
};
sizeMillimeters = intMillimeters;
}
}
}
const [{ width, height }, unit, name, orientation] = await Promise.all([
nonMetric ? sizeInches : sizeMillimeters,
this.l10n.get(
nonMetric
? "pdfjs-document-properties-page-size-unit-inches"
: "pdfjs-document-properties-page-size-unit-millimeters"
),
nameId && this.l10n.get(nameId),
this.l10n.get(
isPortrait
? "pdfjs-document-properties-page-size-orientation-portrait"
: "pdfjs-document-properties-page-size-orientation-landscape"
),
]);
return this.l10n.get(
name
? "pdfjs-document-properties-page-size-dimension-name-string"
: "pdfjs-document-properties-page-size-dimension-string",
{ width, height, unit, name, orientation }
);
}
async #parseDate(inputDate) {
const dateObj = PDFDateString.toDateObject(inputDate);
return dateObj
? this.l10n.get("pdfjs-document-properties-date-time-string", {
dateObj: dateObj.valueOf(),
})
: undefined;
}
#parseLinearization(isLinearized) {
return this.l10n.get(
isLinearized
? "pdfjs-document-properties-linearized-yes"
: "pdfjs-document-properties-linearized-no"
);
}
}
export { PDFDocumentProperties };