diff --git a/l10n/en-US/viewer.properties b/l10n/en-US/viewer.properties index 2c893a4a3..97e2a9401 100644 --- a/l10n/en-US/viewer.properties +++ b/l10n/en-US/viewer.properties @@ -62,6 +62,26 @@ hand_tool_enable_label=Enable hand tool hand_tool_disable.title=Disable hand tool hand_tool_disable_label=Disable hand tool +# Document properties dialog box +document_properties.title=Document Properties +document_properties_label=Document Properties +document_properties_file_name=File name: +document_properties_file_size=File size: +document_properties_kb={{size_kb}} KB +document_properties_mb={{size_mb}} MB +document_properties_title=Title: +document_properties_author=Author: +document_properties_subject=Subject: +document_properties_keywords=Keywords: +document_properties_creation_date=Creation Date: +document_properties_modification_date=Modification Date: +document_properties_date_string={{date}}, {{time}} +document_properties_creator=Creator: +document_properties_producer=PDF Producer: +document_properties_version=PDF Version: +document_properties_page_count=Page Count: +document_properties_close=Close + # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are # tooltips) diff --git a/l10n/nl/viewer.properties b/l10n/nl/viewer.properties index 38f266d5f..44f6b4420 100644 --- a/l10n/nl/viewer.properties +++ b/l10n/nl/viewer.properties @@ -62,6 +62,26 @@ hand_tool_enable_label=Handcursor inschakelen hand_tool_disable.title=Handcursor uitschakelen hand_tool_disable_label=Handcursor uitschakelen +# Document properties dialog box +document_properties.title=Documenteigenschappen +document_properties_label=Documenteigenschappen +document_properties_file_name=Bestandsnaam: +document_properties_file_size=Bestandsgrootte: +document_properties_kb={{size_kb}} KB +document_properties_mb={{size_mb}} MB +document_properties_title=Titel: +document_properties_author=Auteur: +document_properties_subject=Onderwerp: +document_properties_keywords=Trefwoorden: +document_properties_creation_date=Aanmaakdatum: +document_properties_modification_date=Wijzigingsdatum: +document_properties_date_string={{date}}, {{time}} +document_properties_creator=Toepassing: +document_properties_producer=PDF-producent: +document_properties_version=PDF-versie: +document_properties_page_count=Aantal pagina's: +document_properties_close=Sluiten + # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are # tooltips) diff --git a/src/display/api.js b/src/display/api.js index 0b948c1e6..390d2d44b 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -925,10 +925,14 @@ var WorkerTransport = (function WorkerTransportClosure() { }, dataLoaded: function WorkerTransport_dataLoaded() { + if (this.dataLoadedPromise) { + return this.dataLoadedPromise; + } var promise = new PDFJS.LegacyPromise(); this.messageHandler.send('DataLoaded', null, function(args) { promise.resolve(args); }); + this.dataLoadedPromise = promise; return promise; }, diff --git a/web/document_properties.js b/web/document_properties.js new file mode 100644 index 000000000..1fea86759 --- /dev/null +++ b/web/document_properties.js @@ -0,0 +1,179 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* 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. + */ +/* globals PDFView, mozL10n */ + +'use strict'; + +var DocumentProperties = { + overlayContainer: null, + fileSize: '', + visible: false, + + // Document property fields (in the viewer). + fileNameField: null, + fileSizeField: null, + titleField: null, + authorField: null, + subjectField: null, + keywordsField: null, + creationDateField: null, + modificationDateField: null, + creatorField: null, + producerField: null, + versionField: null, + pageCountField: null, + + initialize: function documentPropertiesInitialize(options) { + this.overlayContainer = options.overlayContainer; + + // Set the document property fields. + this.fileNameField = options.fileNameField; + this.fileSizeField = options.fileSizeField; + this.titleField = options.titleField; + this.authorField = options.authorField; + this.subjectField = options.subjectField; + this.keywordsField = options.keywordsField; + this.creationDateField = options.creationDateField; + this.modificationDateField = options.modificationDateField; + this.creatorField = options.creatorField; + this.producerField = options.producerField; + this.versionField = options.versionField; + this.pageCountField = options.pageCountField; + + // Bind the event listener for the Close button. + if (options.closeButton) { + options.closeButton.addEventListener('click', this.hide.bind(this)); + } + }, + + getProperties: function documentPropertiesGetProperties() { + var self = this; + + // Get the file size. + PDFView.pdfDocument.dataLoaded().then(function(data) { + self.setFileSize(data.length); + }); + + // Get the other document properties. + PDFView.pdfDocument.getMetadata().then(function(data) { + var fields = [ + { field: self.fileNameField, content: PDFView.url }, + { field: self.fileSizeField, content: self.fileSize }, + { field: self.titleField, content: data.info.Title }, + { field: self.authorField, content: data.info.Author }, + { field: self.subjectField, content: data.info.Subject }, + { field: self.keywordsField, content: data.info.Keywords }, + { field: self.creationDateField, + content: self.parseDate(data.info.CreationDate) }, + { field: self.modificationDateField, + content: self.parseDate(data.info.ModDate) }, + { field: self.creatorField, content: data.info.Creator }, + { field: self.producerField, content: data.info.Producer }, + { field: self.versionField, content: data.info.PDFFormatVersion }, + { field: self.pageCountField, content: PDFView.pdfDocument.numPages } + ]; + + // Show the properties in the dialog. + for (var item in fields) { + var element = fields[item]; + if (element.field && element.content !== undefined && + element.content !== '') { + element.field.textContent = element.content; + } + } + }); + }, + + setFileSize: function documentPropertiesSetFileSize(fileSize) { + var kb = Math.round(fileSize / 1024); + if (kb < 1024) { + this.fileSize = mozL10n.get('document_properties_kb', + {size_kb: kb}, '{{size_kb}} KB'); + } else { + var mb = Math.round((kb / 1024) * 100) / 100; + this.fileSize = mozL10n.get('document_properties_mb', + {size_mb: mb}, '{{size_mb}} MB'); + } + }, + + show: function documentPropertiesShow() { + if (this.visible) { + return; + } + this.visible = true; + this.overlayContainer.classList.remove('hidden'); + this.overlayContainer.lastElementChild.classList.remove('hidden'); + this.getProperties(); + }, + + hide: function documentPropertiesClose() { + if (!this.visible) { + return; + } + this.visible = false; + this.overlayContainer.classList.add('hidden'); + this.overlayContainer.lastElementChild.classList.add('hidden'); + }, + + parseDate: function documentPropertiesParseDate(inputDate) { + // This is implemented according to the PDF specification (see + // http://www.gnupdf.org/Date for an overview), but note that + // Adobe Reader doesn't handle changing the date to universal time + // and doesn't use the user's time zone (they're effectively ignoring + // the HH' and mm' parts of the date string). + var dateToParse = inputDate; + if (dateToParse === undefined) { + return ''; + } + + // Remove the D: prefix if it is available. + if (dateToParse.substring(0,2) === 'D:') { + dateToParse = dateToParse.substring(2); + } + + // Get all elements from the PDF date string. + // JavaScript's Date object expects the month to be between + // 0 and 11 instead of 1 and 12, so we're correcting for this. + var year = parseInt(dateToParse.substring(0,4), 10); + var month = parseInt(dateToParse.substring(4,6), 10) - 1; + var day = parseInt(dateToParse.substring(6,8), 10); + var hours = parseInt(dateToParse.substring(8,10), 10); + var minutes = parseInt(dateToParse.substring(10,12), 10); + var seconds = parseInt(dateToParse.substring(12,14), 10); + var utRel = dateToParse.substring(14,15); + var offsetHours = parseInt(dateToParse.substring(15,17), 10); + var offsetMinutes = parseInt(dateToParse.substring(18,20), 10); + + // As per spec, utRel = 'Z' means equal to universal time. + // The other cases ('-' and '+') have to be handled here. + if (utRel == '-') { + hours += offsetHours; + minutes += offsetMinutes; + } else if (utRel == '+') { + hours -= offsetHours; + minutes += offsetMinutes; + } + + // Return the new date format from the user's locale. + var date = new Date(Date.UTC(year, month, day, hours, minutes, seconds)); + var dateString = date.toLocaleDateString(); + var timeString = date.toLocaleTimeString(); + return mozL10n.get('document_properties_date_string', + {date: dateString, time: timeString}, + '{{date}}, {{time}}'); + } +}; diff --git a/web/images/secondaryToolbarButton-documentProperties.png b/web/images/secondaryToolbarButton-documentProperties.png new file mode 100644 index 000000000..a9e82db33 Binary files /dev/null and b/web/images/secondaryToolbarButton-documentProperties.png differ diff --git a/web/password_prompt.js b/web/password_prompt.js index 38053fb60..3a1078019 100644 --- a/web/password_prompt.js +++ b/web/password_prompt.js @@ -62,6 +62,7 @@ var PasswordPrompt = { } this.visible = true; this.overlayContainer.classList.remove('hidden'); + this.overlayContainer.firstElementChild.classList.remove('hidden'); this.passwordField.focus(); var promptString = mozL10n.get('password_label', null, @@ -82,6 +83,7 @@ var PasswordPrompt = { this.visible = false; this.passwordField.value = ''; this.overlayContainer.classList.add('hidden'); + this.overlayContainer.firstElementChild.classList.add('hidden'); }, verifyPassword: function passwordPromptVerifyPassword() { diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 0d0389bd5..a3d7563ac 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -26,6 +26,7 @@ var SecondaryToolbar = { initialize: function secondaryToolbarInitialize(options) { this.toolbar = options.toolbar; this.presentationMode = options.presentationMode; + this.documentProperties = options.documentProperties; this.buttonContainer = this.toolbar.firstElementChild; // Define the toolbar buttons. @@ -39,6 +40,7 @@ var SecondaryToolbar = { this.lastPage = options.lastPage; this.pageRotateCw = options.pageRotateCw; this.pageRotateCcw = options.pageRotateCcw; + this.documentPropertiesButton = options.documentPropertiesButton; // Attach the event listeners. var elements = [ @@ -55,7 +57,9 @@ var SecondaryToolbar = { { element: this.firstPage, handler: this.firstPageClick }, { element: this.lastPage, handler: this.lastPageClick }, { element: this.pageRotateCw, handler: this.pageRotateCwClick }, - { element: this.pageRotateCcw, handler: this.pageRotateCcwClick } + { element: this.pageRotateCcw, handler: this.pageRotateCcwClick }, + { element: this.documentPropertiesButton, + handler: this.documentPropertiesClick } ]; for (var item in elements) { @@ -74,17 +78,17 @@ var SecondaryToolbar = { openFileClick: function secondaryToolbarOpenFileClick(evt) { document.getElementById('fileInput').click(); - this.close(evt.target); + this.close(); }, printClick: function secondaryToolbarPrintClick(evt) { window.print(); - this.close(evt.target); + this.close(); }, downloadClick: function secondaryToolbarDownloadClick(evt) { PDFView.download(); - this.close(evt.target); + this.close(); }, viewBookmarkClick: function secondaryToolbarViewBookmarkClick(evt) { @@ -109,6 +113,11 @@ var SecondaryToolbar = { PDFView.rotatePages(-90); }, + documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) { + this.documentProperties.show(); + this.close(); + }, + // Misc. functions for interacting with the toolbar. setMaxHeight: function secondaryToolbarSetMaxHeight(container) { if (!container || !this.buttonContainer) { diff --git a/web/viewer.css b/web/viewer.css index fa04f09ed..a524c424a 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -585,7 +585,7 @@ html[dir='rtl'] .splitToolbarButton > .toolbarButton { .splitToolbarButton > .toolbarButton:hover, .splitToolbarButton > .toolbarButton:focus, .dropdownToolbarButton:hover, -.promptButton:hover, +.overlayButton:hover, .toolbarButton.textButton:hover, .toolbarButton.textButton:focus { background-color: hsla(0,0%,0%,.2); @@ -642,7 +642,7 @@ html[dir='rtl'] .splitToolbarButtonSeparator { .toolbarButton, .dropdownToolbarButton, -.promptButton, +.overlayButton, .secondaryToolbarButton { min-width: 16px; padding: 2px 6px 0; @@ -665,12 +665,12 @@ html[dir='rtl'] .splitToolbarButtonSeparator { } html[dir='ltr'] .toolbarButton, -html[dir='ltr'] .promptButton, +html[dir='ltr'] .overlayButton, html[dir='ltr'] .dropdownToolbarButton { margin: 3px 2px 4px 0; } html[dir='rtl'] .toolbarButton, -html[dir='rtl'] .promptButton, +html[dir='rtl'] .overlayButton, html[dir='rtl'] .dropdownToolbarButton { margin: 3px 0 4px 2px; } @@ -678,7 +678,7 @@ html[dir='rtl'] .dropdownToolbarButton { .toolbarButton:hover, .toolbarButton:focus, .dropdownToolbarButton, -.promptButton, +.overlayButton, .secondaryToolbarButton:hover, .secondaryToolbarButton:focus { background-color: hsla(0,0%,0%,.12); @@ -692,7 +692,7 @@ html[dir='rtl'] .dropdownToolbarButton { } .toolbarButton:hover:active, -.promptButton:hover:active, +.overlayButton:hover:active, .dropdownToolbarButton:hover:active, .secondaryToolbarButton:hover:active { background-color: hsla(0,0%,0%,.2); @@ -766,7 +766,7 @@ html[dir='rtl'] .dropdownToolbarButton { background: hsl(0,0%,24%); } -.promptButton { +.overlayButton { margin: 3px 2px 4px 5px !important; line-height: 16px; padding: 2px 6px 3px 6px; @@ -1004,6 +1004,10 @@ html[dir="rtl"] .secondaryToolbarButton > span { content: url(images/secondaryToolbarButton-handTool.png); } +.secondaryToolbarButton.documentProperties::before { + content: url(images/secondaryToolbarButton-documentProperties.png); +} + .verticalToolbarSeparator { display: block; padding: 8px 0; @@ -1431,6 +1435,63 @@ canvas { border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42); } +#documentPropertiesContainer { + display: table-cell; + vertical-align: middle; + text-align: center; +} + +#documentPropertiesContainer > * { + display: inline-block; + padding: 15px; + border-spacing: 4px; + max-width: 350px; + max-height: 350px; + color: hsl(0,0%,85%); + font-size: 12px; + line-height: 14px; + text-align: left; + cursor: default; + background-color: #474747; /* fallback */ + background-image: url(images/texture.png), + linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95)); + box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08), + inset 0 1px 1px hsla(0,0%,0%,.15), + inset 0 -1px 0 hsla(0,0%,100%,.05), + 0 1px 0 hsla(0,0%,0%,.15), + 0 1px 1px hsla(0,0%,0%,.1); +} + +#documentPropertiesContainer .separator { + display: block; + margin: 4px 0 4px 0; + height: 1px; + width: 100%; + background-color: hsla(0,0%,0%,.5); + box-shadow: 0 0 0 1px hsla(0,0%,100%,.08); +} + +#documentPropertiesContainer .row { + display: table-row; +} + +html[dir='ltr'] #documentPropertiesContainer .row > * { + display: table-cell; + min-width: 100px; +} + +html[dir='rtl'] #documentPropertiesContainer .row > * { + display: table-cell; + min-width: 100px; + text-align: right; +} + +#documentPropertiesContainer .buttonRow { + margin-top: 10px; + text-align: center; + vertical-align: middle; +} + .clearBoth { clear: both; } diff --git a/web/viewer.html b/web/viewer.html index 5c9e8a81b..1bff71f1a 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -77,6 +77,7 @@ limitations under the License. + @@ -171,6 +172,12 @@ limitations under the License. + +
+ + @@ -299,7 +306,7 @@ limitations under the License.
diff --git a/web/viewer.js b/web/viewer.js index b3726f60d..373a78ebc 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -19,7 +19,7 @@ getFileName, scrollIntoView, getPDFFileNameFromURL, PDFHistory, Preferences, ViewHistory, PageView, ThumbnailView, noContextMenuHandler, SecondaryToolbar, PasswordPrompt, - PresentationMode, HandTool, Promise */ + PresentationMode, HandTool, Promise, DocumentProperties */ 'use strict'; @@ -90,6 +90,7 @@ var currentPageNumber = 1; //#include password_prompt.js //#include presentation_mode.js //#include hand_tool.js +//#include document_properties.js var PDFView = { pages: [], @@ -162,7 +163,9 @@ var PDFView = { firstPage: document.getElementById('firstPage'), lastPage: document.getElementById('lastPage'), pageRotateCw: document.getElementById('pageRotateCw'), - pageRotateCcw: document.getElementById('pageRotateCcw') + pageRotateCcw: document.getElementById('pageRotateCcw'), + documentProperties: DocumentProperties, + documentPropertiesButton: document.getElementById('documentProperties') }); PasswordPrompt.initialize({ @@ -182,6 +185,23 @@ var PDFView = { pageRotateCcw: document.getElementById('contextPageRotateCcw') }); + DocumentProperties.initialize({ + overlayContainer: document.getElementById('overlayContainer'), + closeButton: document.getElementById('documentPropertiesClose'), + fileNameField: document.getElementById('fileNameField'), + fileSizeField: document.getElementById('fileSizeField'), + titleField: document.getElementById('titleField'), + authorField: document.getElementById('authorField'), + subjectField: document.getElementById('subjectField'), + keywordsField: document.getElementById('keywordsField'), + creationDateField: document.getElementById('creationDateField'), + modificationDateField: document.getElementById('modificationDateField'), + creatorField: document.getElementById('creatorField'), + producerField: document.getElementById('producerField'), + versionField: document.getElementById('versionField'), + pageCountField: document.getElementById('pageCountField') + }); + this.initialized = true; container.addEventListener('scroll', function() { self.lastScroll = Date.now();