diff --git a/l10n/en-US/viewer.properties b/l10n/en-US/viewer.properties index 4533de643..3f4723eb2 100644 --- a/l10n/en-US/viewer.properties +++ b/l10n/en-US/viewer.properties @@ -90,12 +90,22 @@ document_properties_producer=PDF Producer: document_properties_version=PDF Version: document_properties_page_count=Page Count: document_properties_page_size=Page Size: -# LOCALIZATION NOTE (document_properties_page_size_in_2): "{{width}}" and "{{height}}" -# will be replaced by the size of the (current) page, in inches. -document_properties_page_size_in_2={{width}} × {{height}} in -# LOCALIZATION NOTE (document_properties_page_size_mm_2): "{{width}}" and "{{height}}" -# will be replaced by the size of the (current) page, in millimeters. -document_properties_page_size_mm_2={{width}} × {{height}} mm +document_properties_page_size_unit_inches=in +document_properties_page_size_unit_millimeters=mm +document_properties_page_size_orientation_portrait=portrait +document_properties_page_size_orientation_landscape=landscape +document_properties_page_size_name_a3=A3 +document_properties_page_size_name_a4=A4 +document_properties_page_size_name_letter=Letter +document_properties_page_size_name_legal=Legal +# LOCALIZATION NOTE (document_properties_page_size_dimension_string): +# "{{width}}", "{{height}}", {{unit}}, and {{orientation}} will be replaced by +# the size, respectively their unit of measurement and orientation, of the (current) page. +document_properties_page_size_dimension_string={{width}} × {{height}} {{unit}} ({{orientation}}) +# LOCALIZATION NOTE (document_properties_page_size_dimension_name_string): +# "{{width}}", "{{height}}", {{unit}}, {{name}}, and {{orientation}} will be replaced by +# the size, respectively their unit of measurement, name, and orientation, of the (current) page. +document_properties_page_size_dimension_name_string={{width}} × {{height}} {{unit}} ({{name}}, {{orientation}}) document_properties_close=Close print_progress_message=Preparing document for printing… diff --git a/l10n/sv-SE/viewer.properties b/l10n/sv-SE/viewer.properties index 8b015fa50..29ac19836 100644 --- a/l10n/sv-SE/viewer.properties +++ b/l10n/sv-SE/viewer.properties @@ -90,12 +90,22 @@ document_properties_producer=PDF-producent: document_properties_version=PDF-version: document_properties_page_count=Sidantal: document_properties_page_size=Sidstorlek: -# LOCALIZATION NOTE (document_properties_page_size_in_2): "{{width}}" and "{{height}}" -# will be replaced by the size of the (current) page, in inches. -document_properties_page_size_in_2={{width}} × {{height}} tum -# LOCALIZATION NOTE (document_properties_page_size_mm_2): "{{width}}" and "{{height}}" -# will be replaced by the size of the (current) page, in millimeters. -document_properties_page_size_mm_2={{width}} × {{height}} mm +document_properties_page_size_unit_inches=tum +document_properties_page_size_unit_millimeters=mm +document_properties_page_size_orientation_portrait=stående +document_properties_page_size_orientation_landscape=liggande +document_properties_page_size_name_a3=A3 +document_properties_page_size_name_a4=A4 +document_properties_page_size_name_letter=Letter +document_properties_page_size_name_legal=Legal +# LOCALIZATION NOTE (document_properties_page_size_dimension_string): +# "{{width}}", "{{height}}", {{unit}}, and {{orientation}} will be replaced by +# the size, respectively their unit of measurement and orientation, of the (current) page. +document_properties_page_size_dimension_string={{width}} × {{height}} {{unit}} ({{orientation}}) +# LOCALIZATION NOTE (document_properties_page_size_dimension_name_string): +# "{{width}}", "{{height}}", {{unit}}, {{name}}, and {{orientation}} will be replaced by +# the size, respectively their unit of measurement, name, and orientation, of the (current) page. +document_properties_page_size_dimension_name_string={{width}} × {{height}} {{unit}} ({{name}}, {{orientation}}) document_properties_close=Stäng print_progress_message=Förbereder sidor för utskrift… diff --git a/test/unit/ui_utils_spec.js b/test/unit/ui_utils_spec.js index 180b7a036..64426c966 100644 --- a/test/unit/ui_utils_spec.js +++ b/test/unit/ui_utils_spec.js @@ -15,7 +15,7 @@ import { binarySearchFirstItem, EventBus, getPageSizeInches, getPDFFileNameFromURL, - isValidRotation, waitOnEventOrTimeout, WaitOnType + isPortraitOrientation, isValidRotation, waitOnEventOrTimeout, WaitOnType } from '../../web/ui_utils'; import { createObjectURL } from '../../src/shared/util'; import isNodeJS from '../../src/shared/is_node'; @@ -285,6 +285,27 @@ describe('ui_utils', function() { }); }); + describe('isPortraitOrientation', function() { + it('should be portrait orientation', function() { + expect(isPortraitOrientation({ + width: 200, + height: 400, + })).toEqual(true); + + expect(isPortraitOrientation({ + width: 500, + height: 500, + })).toEqual(true); + }); + + it('should be landscape orientation', function() { + expect(isPortraitOrientation({ + width: 600, + height: 300, + })).toEqual(false); + }); + }); + describe('waitOnEventOrTimeout', function() { let eventBus; diff --git a/web/base_viewer.js b/web/base_viewer.js index bc7955750..daefce3cc 100644 --- a/web/base_viewer.js +++ b/web/base_viewer.js @@ -14,9 +14,10 @@ */ import { - CSS_UNITS, DEFAULT_SCALE, DEFAULT_SCALE_VALUE, isValidRotation, - MAX_AUTO_SCALE, NullL10n, PresentationModeState, RendererType, - SCROLLBAR_PADDING, TextLayerMode, UNKNOWN_SCALE, VERTICAL_PADDING, watchScroll + CSS_UNITS, DEFAULT_SCALE, DEFAULT_SCALE_VALUE, isPortraitOrientation, + isValidRotation, MAX_AUTO_SCALE, NullL10n, PresentationModeState, + RendererType, SCROLLBAR_PADDING, TextLayerMode, UNKNOWN_SCALE, + VERTICAL_PADDING, watchScroll } from './ui_utils'; import { PDFRenderingQueue, RenderingStates } from './pdf_rendering_queue'; import { AnnotationLayerBuilder } from './annotation_layer_builder'; @@ -94,10 +95,6 @@ function isSameScale(oldScale, newScale) { return false; } -function isPortraitOrientation(size) { - return size.width <= size.height; -} - /** * Simple viewer control to display PDF content/pages. * @implements {IRenderableView} @@ -578,11 +575,10 @@ class BaseViewer { scale = Math.min(pageWidthScale, pageHeightScale); break; case 'auto': - let isLandscape = (currentPage.width > currentPage.height); // For pages in landscape mode, fit the page height to the viewer // *unless* the page would thus become too wide to fit horizontally. - let horizontalScale = isLandscape ? - Math.min(pageHeightScale, pageWidthScale) : pageWidthScale; + let horizontalScale = isPortraitOrientation(currentPage) ? + pageWidthScale : Math.min(pageHeightScale, pageWidthScale); scale = Math.min(MAX_AUTO_SCALE, horizontalScale); break; default: diff --git a/web/firefoxcom.js b/web/firefoxcom.js index 5d7bc5997..9435bc562 100644 --- a/web/firefoxcom.js +++ b/web/firefoxcom.js @@ -145,6 +145,10 @@ class MozL10n { this.mozL10n = mozL10n; } + getLanguage() { + return Promise.resolve(this.mozL10n.getLanguage()); + } + getDirection() { return Promise.resolve(this.mozL10n.getDirection()); } diff --git a/web/genericl10n.js b/web/genericl10n.js index 6c0dcfb67..e2da69a60 100644 --- a/web/genericl10n.js +++ b/web/genericl10n.js @@ -27,6 +27,12 @@ class GenericL10n { }); } + getLanguage() { + return this._ready.then((l10n) => { + return l10n.getLanguage(); + }); + } + getDirection() { return this._ready.then((l10n) => { return l10n.getDirection(); diff --git a/web/interfaces.js b/web/interfaces.js index 455185b9b..2e8b52d1d 100644 --- a/web/interfaces.js +++ b/web/interfaces.js @@ -160,6 +160,11 @@ class IPDFAnnotationLayerFactory { * @interface */ class IL10n { + /** + * @returns {Promise} - Resolves to the current locale. + */ + getLanguage() {} + /** * @returns {Promise} - Resolves to 'rtl' or 'ltr'. */ diff --git a/web/pdf_document_properties.js b/web/pdf_document_properties.js index cb5d81e8f..ce57e9474 100644 --- a/web/pdf_document_properties.js +++ b/web/pdf_document_properties.js @@ -14,12 +14,34 @@ */ import { - cloneObj, getPageSizeInches, getPDFFileNameFromURL, NullL10n + cloneObj, getPageSizeInches, getPDFFileNameFromURL, isPortraitOrientation, + NullL10n } from './ui_utils'; import { createPromiseCapability } from 'pdfjs-lib'; const DEFAULT_FIELD_CONTENT = '-'; +// 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. +// See https://en.wikipedia.org/wiki/Paper_size +const US_PAGE_NAMES = { + '8.5x11': 'Letter', + '8.5x14': 'Legal', +}; +const METRIC_PAGE_NAMES = { + '297x420': 'A3', + '210x297': 'A4', +}; + +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 {string} overlayName - Name/identifier for the overlay. @@ -55,7 +77,15 @@ class PDFDocumentProperties { eventBus.on('pagechanging', (evt) => { this._currentPageNumber = evt.pageNumber; }); + eventBus.on('rotationchanging', (evt) => { + this._pagesRotation = evt.pagesRotation; + }); } + + this._isNonMetricLocale = true; // The default viewer locale is 'en-us'. + l10n.getLanguage().then((locale) => { + this._isNonMetricLocale = NON_METRIC_LOCALES.includes(locale); + }); } /** @@ -74,11 +104,13 @@ class PDFDocumentProperties { Promise.all([this.overlayManager.open(this.overlayName), this._dataAvailableCapability.promise]).then(() => { 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']) { + currentPageNumber === this.fieldData['_currentPageNumber'] && + pagesRotation === this.fieldData['_pagesRotation']) { this._updateUI(); return; } @@ -94,11 +126,12 @@ class PDFDocumentProperties { this._parseDate(info.CreationDate), this._parseDate(info.ModDate), this.pdfDocument.getPage(currentPageNumber).then((pdfPage) => { - return this._parsePageSize(getPageSizeInches(pdfPage)); + return this._parsePageSize(getPageSizeInches(pdfPage), + pagesRotation); }), ]); }).then(([info, metadata, fileName, fileSize, creationDate, modDate, - pageSizes]) => { + pageSize]) => { freezeFieldData({ 'fileName': fileName, 'fileSize': fileSize, @@ -112,9 +145,9 @@ class PDFDocumentProperties { 'producer': info.Producer, 'version': info.PDFFormatVersion, 'pageCount': this.pdfDocument.numPages, - 'pageSizeInch': pageSizes.inch, - 'pageSizeMM': pageSizes.mm, + 'pageSize': pageSize, '_currentPageNumber': currentPageNumber, + '_pagesRotation': pagesRotation, }); this._updateUI(); @@ -191,6 +224,7 @@ class PDFDocumentProperties { delete this.fieldData; this._dataAvailableCapability = createPromiseCapability(); this._currentPageNumber = 1; + this._pagesRotation = 0; } /** @@ -240,27 +274,87 @@ class PDFDocumentProperties { /** * @private */ - _parsePageSize(pageSizeInches) { + _parsePageSize(pageSizeInches, pagesRotation) { if (!pageSizeInches) { - return Promise.resolve({ inch: undefined, mm: undefined, }); + return Promise.resolve(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); + + 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 pageName = null; + let name = getPageName(sizeInches, isPortrait, US_PAGE_NAMES) || + getPageName(sizeMillimeters, isPortrait, METRIC_PAGE_NAMES); + + if (!name && !(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) { + + name = getPageName(intMillimeters, isPortrait, METRIC_PAGE_NAMES); + if (name) { + // 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; + } + } + } + if (name) { + pageName = this.l10n.get('document_properties_page_size_name_' + + name.toLowerCase(), null, name); } - const { width, height, } = pageSizeInches; return Promise.all([ - this.l10n.get('document_properties_page_size_in_2', { - width: (Math.round(width * 100) / 100).toLocaleString(), - height: (Math.round(height * 100) / 100).toLocaleString(), - }, '{{width}} × {{height}} in'), - // 1in = 25.4mm; no need to round to 2 decimals for millimeters. - this.l10n.get('document_properties_page_size_mm_2', { - width: (Math.round(width * 25.4 * 10) / 10).toLocaleString(), - height: (Math.round(height * 25.4 * 10) / 10).toLocaleString(), - }, '{{width}} × {{height}} mm'), - ]).then((sizes) => { - return { - inch: sizes[0], - mm: sizes[1], - }; + (this._isNonMetricLocale ? sizeInches : sizeMillimeters), + this.l10n.get('document_properties_page_size_unit_' + + (this._isNonMetricLocale ? 'inches' : 'millimeters'), null, + this._isNonMetricLocale ? 'in' : 'mm'), + pageName, + this.l10n.get('document_properties_page_size_orientation_' + + (isPortrait ? 'portrait' : 'landscape'), null, + isPortrait ? 'portrait' : 'landscape'), + ]).then(([{ width, height, }, unit, name, orientation]) => { + return this.l10n.get('document_properties_page_size_dimension_' + + (name ? 'name_' : '') + 'string', { + width: width.toLocaleString(), + height: height.toLocaleString(), + unit, + name, + orientation, + }, '{{width}} × {{height}} {{unit}} (' + + (name ? '{{name}}, ' : '') + '{{orientation}})'); }); } diff --git a/web/ui_utils.js b/web/ui_utils.js index 67fd93d4a..3b3c1febe 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -58,6 +58,10 @@ function formatL10nValue(text, args) { * @implements {IL10n} */ let NullL10n = { + getLanguage() { + return Promise.resolve('en-us'); + }, + getDirection() { return Promise.resolve('ltr'); }, @@ -439,6 +443,10 @@ function isValidRotation(angle) { return Number.isInteger(angle) && angle % 90 === 0; } +function isPortraitOrientation(size) { + return size.width <= size.height; +} + function cloneObj(obj) { let result = Object.create(null); for (let i in obj) { @@ -642,6 +650,7 @@ export { SCROLLBAR_PADDING, VERTICAL_PADDING, isValidRotation, + isPortraitOrientation, isFileSchema, cloneObj, PresentationModeState, diff --git a/web/viewer.html b/web/viewer.html index 7baec33e2..2d460456c 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -352,11 +352,7 @@ See https://github.com/adobe-type-tools/cmap-resources Page Count:

-

- Page Size: -

- -
- - -

+ Page Size:

-

diff --git a/web/viewer.js b/web/viewer.js index 242bf1df6..632a217f7 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -159,8 +159,7 @@ function getViewerConfiguration() { 'producer': document.getElementById('producerField'), 'version': document.getElementById('versionField'), 'pageCount': document.getElementById('pageCountField'), - 'pageSizeInch': document.getElementById('pageSizeFieldInch'), - 'pageSizeMM': document.getElementById('pageSizeFieldMM'), + 'pageSize': document.getElementById('pageSizeField'), }, }, errorWrapper: {