diff --git a/extensions/firefox/chrome-mozcentral.manifest b/extensions/firefox/chrome-mozcentral.manifest new file mode 100644 index 000000000..a2a6757c3 --- /dev/null +++ b/extensions/firefox/chrome-mozcentral.manifest @@ -0,0 +1,3 @@ +resource pdf.js content/ +component {d0c5195d-e798-49d4-b1d3-9324328b2291} components/PdfStreamConverter.js +contract @mozilla.org/streamconv;1?from=application/pdf&to=*/* {d0c5195d-e798-49d4-b1d3-9324328b2291} diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js index f866a6b2f..532075bed 100644 --- a/extensions/firefox/components/PdfStreamConverter.js +++ b/extensions/firefox/components/PdfStreamConverter.js @@ -11,7 +11,7 @@ const Cr = Components.results; const Cu = Components.utils; const PDFJS_EVENT_ID = 'pdf.js.message'; const PDF_CONTENT_TYPE = 'application/pdf'; -const EXT_PREFIX = 'extensions.uriloader@pdf.js'; +const PREF_PREFIX = 'PDFJSSCRIPT_PREF_PREFIX'; const MAX_DATABASE_LENGTH = 4096; const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}'; const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}'; @@ -19,6 +19,7 @@ const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}'; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/NetUtil.jsm'); +Cu.import('resource://gre/modules/AddonManager.jsm'); let appInfo = Cc['@mozilla.org/xre/app-info;1'] .getService(Ci.nsIXULAppInfo); @@ -57,7 +58,7 @@ function getStringPref(pref, def) { } function log(aMsg) { - if (!getBoolPref(EXT_PREFIX + '.pdfBugEnabled', false)) + if (!getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false)) return; let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg); Services.console.logStringMessage(msg); @@ -91,9 +92,15 @@ function getLocalizedStrings(path) { } return map; } +function getLocalizedString(strings, id) { + if (id in strings) + return strings[id]['textContent']; + return id; +} // All the priviledged actions. -function ChromeActions() { +function ChromeActions(domWindow) { + this.domWindow = domWindow; } ChromeActions.prototype = { @@ -136,12 +143,12 @@ ChromeActions.prototype = { // Protect against something sending tons of data to setDatabase. if (data.length > MAX_DATABASE_LENGTH) return; - setStringPref(EXT_PREFIX + '.database', data); + setStringPref(PREF_PREFIX + '.database', data); }, getDatabase: function() { if (inPrivateBrowsing) return '{}'; - return getStringPref(EXT_PREFIX + '.database', '{}'); + return getStringPref(PREF_PREFIX + '.database', '{}'); }, getLocale: function() { return getStringPref('general.useragent.locale', 'en-US'); @@ -164,10 +171,30 @@ ChromeActions.prototype = { }, searchEnabled: function() { return getBoolPref(EXT_PREFIX + '.searchEnabled', false); + }, + fallback: function(url) { + var self = this; + var domWindow = this.domWindow; + var strings = getLocalizedStrings('chrome.properties'); + var message = getLocalizedString(strings, 'unsupported_feature'); + + var win = Services.wm.getMostRecentWindow('navigator:browser'); + var browser = win.gBrowser.getBrowserForDocument(domWindow.top.document); + var notificationBox = win.gBrowser.getNotificationBox(browser); + + var buttons = [{ + label: getLocalizedString(strings, 'open_with_different_viewer'), + accessKey: null, + callback: function() { + self.download(url); + } + }]; + notificationBox.appendNotification(message, 'pdfjs-fallback', null, + notificationBox.PRIORITY_WARNING_LOW, + buttons); } }; - // Event listener to trigger chrome privedged code. function RequestListener(actions) { this.actions = actions; @@ -193,7 +220,7 @@ function PdfStreamConverter() { PdfStreamConverter.prototype = { // properties required for XPCOM registration: - classID: Components.ID('{6457a96b-2d68-439a-bcfa-44465fbcdbb1}'), + classID: Components.ID('{PDFJSSCRIPT_STREAM_CONVERTER_ID}'), classDescription: 'pdf.js Component', contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*', @@ -270,7 +297,8 @@ PdfStreamConverter.prototype = { var domWindow = getDOMWindow(channel); // Double check the url is still the correct one. if (domWindow.document.documentURIObject.equals(aRequest.URI)) { - let requestListener = new RequestListener(new ChromeActions); + let requestListener = new RequestListener( + new ChromeActions(domWindow)); domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { requestListener.receive(event); }, false, true); diff --git a/extensions/firefox/install.rdf.in b/extensions/firefox/install.rdf.in deleted file mode 100644 index 084d6dc2d..000000000 --- a/extensions/firefox/install.rdf.in +++ /dev/null @@ -1,27 +0,0 @@ - - -#filter substitution - - - - - uriloader@pdf.js - - PDF Viewer - PDFJSSCRIPT_VERSION - - - {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - @FIREFOX_VERSION@ - @FIREFOX_VERSION@ - - - true - true - Mozilla - Uses HTML5 to display PDF files directly in Firefox. - https://support.mozilla.org/kb/Opening%20PDF%20files%20within%20Firefox - 2 - - diff --git a/l10n/cs/metadata.inc b/l10n/cs/metadata.inc new file mode 100644 index 000000000..ed5c2a1c7 --- /dev/null +++ b/l10n/cs/metadata.inc @@ -0,0 +1,8 @@ + + + cs + PDF Viewer + Používá HTML5 pro zobrazení PDF souborů přímo ve Firefoxu. + + + diff --git a/l10n/cs/viewer.properties b/l10n/cs/viewer.properties new file mode 100644 index 000000000..96b596b16 --- /dev/null +++ b/l10n/cs/viewer.properties @@ -0,0 +1,45 @@ +bookmark.title=Aktuální zobrazení(zkopírovat nebo otevřít v novém okně) +previous.title=Předchozí stránka +next.title=Další stránka +print.title=Tisk +download.title=Stáhnout +zoom_out.title=Zmenšit +zoom_in.title=Zvětšit +error_more_info=Více informací +error_less_info=Méně informací +error_close=Zavřít +error_build=PDF.JS Build: {{build}} +error_message=Zpráva:{{message}} +error_stack=Stack:{{stack}} +error_file=Soubor:{{file}} +error_line=Řádek:{{line}} +page_scale_width=Šířka stránky +page_scale_fit=Stránka +page_scale_auto=Automatické přibližení +page_scale_actual=Skutečná velikost +toggle_slider.title=Přepnout posuvník +thumbs.title=Zobrazit náhledy +outline.title=Zobrazit osnovu dokumentu +loading=Načítám... {{percent}}% +loading_error_indicator=Chyba +loading_error=Došlo k chybě při načítání PDF. +rendering_error=Došlo k chybě při vykreslování stránky. +page_label=Stránka: +page_of=z{{pageCount}} +no_outline=Žádné osnovy k dispozici +open_file.title=Otevřít soubor +text_annotation_type=[{{type}}Anotace] +toggle_slider_label=Přepnout posuvník +thumbs_label=Náhledy +outline_label=Přehled dokumentu +bookmark_label=Aktuální zobrazení +previous_label=Předchozí +next_label=Další +print_label=Tisk +download_label=Stáhnout +zoom_out_label=Zmenšit +zoom_in_label=Přiblížit +zoom.title=Zvětšit +thumb_page_title=Stránka{{page}} +thumb_page_canvas=Náhled stránky {{page}} +request_password=PDF je chráněn heslem: diff --git a/l10n/da/viewer.properties b/l10n/da/viewer.properties index 6d208db70..f9d77da1b 100644 --- a/l10n/da/viewer.properties +++ b/l10n/da/viewer.properties @@ -17,7 +17,7 @@ page_scale_width=Sidebredde page_scale_fit=Helside page_scale_auto=Automatisk zoom page_scale_actual=Faktisk størrelse -toggle_slider.title=Skift Slider +toggle_slider.title=Skift slider thumbs.title=Vis thumbnails outline.title=Vis dokumentoversigt loading=Indlæser... {{percent}}% @@ -29,3 +29,17 @@ page_of=af {{pageCount}} no_outline=Ingen dokumentoversigt tilgængelig open_file.title=Åbn fil text_annotation_type=[{{type}} Kommentar] +toggle_slider_label=Skift slider +thumbs_label=Thumbnails +outline_label=Dokumentoversigt +bookmark_label=Aktuel visning +previous_label=Forrige +next_label=Næste +print_label=Udskriv +download_label=Hent +zoom_out_label=Zoom ud +zoom_in_label=Zoom ind +zoom.title=Zoom +thumb_page_title=Side {{page}} +thumb_page_canvas=Thumbnail af side {{page}} +request_password=PDF filen er beskyttet med et kodeord: diff --git a/l10n/en-US/chrome.properties b/l10n/en-US/chrome.properties new file mode 100644 index 000000000..467d5920b --- /dev/null +++ b/l10n/en-US/chrome.properties @@ -0,0 +1,2 @@ +unsupported_feature=This PDF document might not be displayed correctly. +open_with_different_viewer=Open With Different Viewer diff --git a/l10n/en-US/viewer.properties b/l10n/en-US/viewer.properties index c8dbe4aba..0922062a6 100644 --- a/l10n/en-US/viewer.properties +++ b/l10n/en-US/viewer.properties @@ -42,3 +42,5 @@ zoom_in_label=Zoom In zoom.title=Zoom thumb_page_title=Page {{page}} thumb_page_canvas=Thumbnail of Page {{page}} +request_password=PDF is protected by a password: +open_file_label=Open diff --git a/l10n/fr/metadata.inc b/l10n/fr/metadata.inc new file mode 100644 index 000000000..874e93a18 --- /dev/null +++ b/l10n/fr/metadata.inc @@ -0,0 +1,7 @@ + + + fr + PDF Viewer + Utilise HTML5 pour afficher les documents PDF directement dans Firefox. + + diff --git a/l10n/fr/viewer.properties b/l10n/fr/viewer.properties new file mode 100644 index 000000000..c93df3681 --- /dev/null +++ b/l10n/fr/viewer.properties @@ -0,0 +1,45 @@ +bookmark.title=Vue courante (copier ou ouvrir dans une nouvelle fenêtre) +previous.title=Précédente +next.title=Suivante +print.title=Imprimer +download.title=Télécharger +zoom_out.title=Zoom arrière +zoom_in.title=Zoom avant +error_more_info=Plus d'informations +error_less_info=Moins d'informations +error_close=Fermer +error_build=Version de PDF.JS : {{build}} +error_message=Message : {{message}} +error_stack=Pile : {{stack}} +error_file=Fichier : {{file}} +error_line=Ligne : {{line}} +page_scale_width=Largeur de la page +page_scale_fit=Ajuster à la page +page_scale_auto=Zoom automatique +page_scale_actual=Taille réelle +toggle_slider.title=Afficher/Masquer la barre latérale +thumbs.title=Afficher/masquer les vignettes +outline.title=Afficher/masquer la structure +loading=Chargement… {{percent}}% +loading_error_indicator=Erreur +loading_error=Une erreur est survenue lors du chargement du PDF. +rendering_error=Une erreur est survenue lors de l'affichage de la page. +page_label=Page : +page_of=sur {{pageCount}} +no_outline=Pas de structure disponible +open_file.title=Ouvrir un fichier +text_annotation_type=[Annotation {{type}}] +toggle_slider_label=Afficher/Masquer la barre latérale +thumbs_label=Vignettes +outline_label=Structure du document +bookmark_label=Vue courante +previous_label=Précédente +next_label=Suivante +print_label=Imprimer +download_label=Télécharger +zoom_out_label=Zoom arrière +zoom_in_label=Zoom avant +zoom.title=Zoom +thumb_page_title=Page {{page}} +thumb_page_canvas=Aperçu de la page {{page}} +request_password=Ce PDF est protégé par un mot de passe : diff --git a/l10n/he/metadata.inc b/l10n/he/metadata.inc new file mode 100644 index 000000000..3493c0b7c --- /dev/null +++ b/l10n/he/metadata.inc @@ -0,0 +1,8 @@ + + + he + קורא PDF + הצגת קבצי PDF ישירות ב־Firefox באמצעות HTML5. + + + diff --git a/l10n/he/viewer.properties b/l10n/he/viewer.properties new file mode 100644 index 000000000..974343e04 --- /dev/null +++ b/l10n/he/viewer.properties @@ -0,0 +1,46 @@ +bookmark.title=דף נוכחי (העתקה או פתיחה בחלון חדש) +previous.title=דף קודם +next.title=דף הבא +print.title=הדפסה +download.title=הורדה +zoom_out.title=התרחקות +zoom_in.title=התקרבות +error_more_info=יותר מידע +error_less_info=פחות מידע +error_close=סגירה +error_build=בניית PDF.JS‏: {{build}} +error_message=הודעה: {{message}} +error_stack=מחסנית: {{stack}} +error_file=קובץ: {{file}} +error_line=שורה: {{line}} +page_scale_width=רוחב דף +page_scale_fit=גודל דף +page_scale_auto=התקרבות אוטומטית +page_scale_actual=גודל אמיתי +toggle_slider.title=מתג החלקה +thumbs.title=הצגת תמונות ממוזערות +outline.title=הצגת מתאר מסמך +loading=בטעינה... {{percent}}% +loading_error_indicator=שגיאה +loading_error=אירעה שגיאה בעת טעינת קובץ PDF. +rendering_error=אירעה שגיאה בעת עיבוד הדף. +page_label=דף: +page_of=מתוך {{pageCount}} +no_outline=אין מתאר זמין +open_file.title=פתיחת קובץ +text_annotation_type=[{{type}} Annotation] +toggle_slider_label=מתג החלקה +thumbs_label=תמונות ממוזערות +outline_label=מתאר מסמך +bookmark_label=תצוגה נוכחית +previous_label=קודם +next_label=הבא +print_label=הדפסה +download_label=הורדה +zoom_out_label=התרחקות +zoom_in_label=התקרבות +zoom.title=מרחק מתצוגה +thumb_page_title=דף {{page}} +thumb_page_canvas=תמונה ממוזערת של דף {{page}} +request_password=קובץ PDF מוגן בססמה: +open_file_label=פתיחה diff --git a/l10n/ja/metadata.inc b/l10n/ja/metadata.inc new file mode 100644 index 000000000..7ca474685 --- /dev/null +++ b/l10n/ja/metadata.inc @@ -0,0 +1,8 @@ + + + ja + PDF Viewer + HTML5を使用して、Firefoxで直接PDFファイルを表示します。 + + + diff --git a/l10n/ja/viewer.properties b/l10n/ja/viewer.properties new file mode 100644 index 000000000..4bbe8c48b --- /dev/null +++ b/l10n/ja/viewer.properties @@ -0,0 +1,45 @@ +bookmark.title=現在のビュー (コピーまたは新しいウインドウで開く) +previous.title=前のページ +next.title=次のページ +print.title=印刷 +download.title=ダウンロード +zoom_out.title=縮小 +zoom_in.title=拡大 +error_more_info=詳細情報 +error_less_info=詳細情報の非表示 +error_close=閉じる +error_build=PDF.JS Build: {{build}} +error_message=メッセージ: {{message}} +error_stack=スタック: {{stack}} +error_file=ファイル: {{file}} +error_line=ライン: {{line}} +page_scale_width=幅に合わせる +page_scale_fit=ページのサイズに合わせる +page_scale_auto=自動ズーム +page_scale_actual=実際のサイズ +toggle_slider.title=サイドバーの切り替え +thumbs.title=縮小版を表示 +outline.title=文書の目次を表示 +loading=読み込み中... {{percent}}% +loading_error_indicator=エラー +loading_error=PDFの読み込み中にエラーが発生しました +rendering_error=ページのレンダリング中にエラーが発生しました +page_label=ページ: +page_of=of {{pageCount}} +no_outline=利用可能な目次はありません +open_file.title=ファイルを開く +text_annotation_type=[{{type}} 注釈] +toggle_slider_label=サイドバーの切り替え +thumbs_label=縮小版 +outline_label=文書の目次 +bookmark_label=現在のビュー +previous_label=前へ +next_label=次へ +print_label=印刷 +download_label=ダウンロード +zoom_out_label=縮小 +zoom_in_label=拡大 +zoom.title=ズーム +thumb_page_title=ページ {{page}} +thumb_page_canvas=ページの縮小版 {{page}} +request_password=PDFはパスワードによって保護されています diff --git a/l10n/ru/viewer.properties b/l10n/ru/viewer.properties index a946cde98..796add2af 100644 --- a/l10n/ru/viewer.properties +++ b/l10n/ru/viewer.properties @@ -40,3 +40,6 @@ download_label=Загрузить zoom_out_label=Уменьшить zoom_in_label=Увеличить zoom.title=Масштаб +thumb_page_title=Страница {{page}} +thumb_page_canvas=Уменьшенное изображение страницы {{page}} +request_password=PDF защищён паролем: diff --git a/l10n/sv/metadata.inc b/l10n/sv/metadata.inc new file mode 100644 index 000000000..6753d83e9 --- /dev/null +++ b/l10n/sv/metadata.inc @@ -0,0 +1,8 @@ + + + sv + PDF Läsare + Använder HTML5 för att visa PDF filer direkt i Firefox. + + + diff --git a/l10n/sv/viewer.properties b/l10n/sv/viewer.properties new file mode 100644 index 000000000..3aadfd44f --- /dev/null +++ b/l10n/sv/viewer.properties @@ -0,0 +1,45 @@ +bookmark.title=Aktuell vy (visa eller öppna i nytt fönster) +previous.title=Föregående sida +next.title=Nästa sida +print.title=Skriv ut +download.title=Ladda ner +zoom_out.title=Zooma ut +zoom_in.title=Zooma in +error_more_info=Mer information +error_less_info=Mindre information +error_close=Stäng +error_build=PDF.JS bygge: {{build}} +error_message=Meddelande: {{message}} +error_stack=Stack: {{stack}} +error_file=Fil: {{file}} +error_line=Rad: {{line}} +page_scale_width=Sidbredd +page_scale_fit=Passa sida +page_scale_auto=Automatisk zoom +page_scale_actual=Faktisk storlek +toggle_slider.title=Visa/Dölj panel +thumbs.title=Visa miniatyrer +outline.title=Visa dokumentdisposition +loading=Laddar... {{percent}}% +loading_error_indicator=Fel +loading_error=Ett fel inträffade när PDF dokumentet laddades. +rendering_error=Ett fel inträffade när PDF dokumentet renderades. +page_label=Sida: +page_of=av {{pageCount}} +no_outline=Ingen dokumentdisposition tillgänglig +open_file.title=Öppna fil +text_annotation_type=[{{type}} Annotering] +toggle_slider_label=Visa/Dölj panel +thumbs_label=Miniatyrer +outline_label=Disposition +bookmark_label=Aktuell vy +previous_label=Föregående +next_label=Nästa +print_label=Skriv ut +download_label=Ladda ner +zoom_out_label=Zooma ut +zoom_in_label=Zooma in +zoom.title=Zooma +thumb_page_title=Sida {{page}} +thumb_page_canvas=Miniatyr av sida {{page}} +request_password=PDF dokumentet är skyddat av ett lösenord: \ No newline at end of file diff --git a/l10n/zh-CN/metadata.inc b/l10n/zh-CN/metadata.inc new file mode 100644 index 000000000..04be27c78 --- /dev/null +++ b/l10n/zh-CN/metadata.inc @@ -0,0 +1,8 @@ + + + zh-CN + PDF 查看器 + 使用 HTML5 来支持在 Firefox 中直接显示 PDF 文件。 + + + diff --git a/l10n/zh-CN/viewer.properties b/l10n/zh-CN/viewer.properties new file mode 100644 index 000000000..8b670fe12 --- /dev/null +++ b/l10n/zh-CN/viewer.properties @@ -0,0 +1,45 @@ +bookmark.title=当前视图(复制或在新窗口中打开) +previous.title=上一页 +next.title=下一页 +print.title=打印 +download.title=下载 +zoom_out.title=缩小 +zoom_in.title=放大 +error_more_info=更多信息 +error_less_info=更少信息 +error_close=关闭 +error_build=PDF.JS Build: {{build}} +error_message=Message: {{message}} +error_stack=Stack: {{stack}} +error_file=File: {{file}} +error_line=Line: {{line}} +page_scale_width=适合页宽 +page_scale_fit=适合页面 +page_scale_auto=自动缩放 +page_scale_actual=实际大小 +toggle_slider.title=切换侧栏 +thumbs.title=显示缩略图 +outline.title=显示文档大纲 +loading=正在载入... {{percent}}% +loading_error_indicator=错误 +loading_error=载入PDF时发生错误。 +rendering_error=呈现页面时发生错误。 +page_label=页: +page_of=/ {{pageCount}} +no_outline=无可用大纲 +open_file.title=打开文件 +text_annotation_type=[{{type}} 注解] +toggle_slider_label=切换侧栏 +thumbs_label=缩略图 +outline_label=文档大纲 +bookmark_label=当前视图 +previous_label=向上 +next_label=向下 +print_label=打印 +download_label=下载 +zoom_out_label=缩小 +zoom_in_label=放大 +zoom.title=缩放 +thumb_page_title=页码 {{page}} +thumb_page_canvas=页面 {{page}} 的缩略图 +request_password=PDF 被密码保护: diff --git a/l10n/zh-TW/metadata.inc b/l10n/zh-TW/metadata.inc new file mode 100644 index 000000000..6ef722be8 --- /dev/null +++ b/l10n/zh-TW/metadata.inc @@ -0,0 +1,8 @@ + + + zh-TW + PDF 瀏覽器 + 利用 HTML5 技術在 Firefox 中直接顯示 PDF 格式檔案。 + + + diff --git a/l10n/zh-TW/viewer.properties b/l10n/zh-TW/viewer.properties new file mode 100644 index 000000000..0f2182594 --- /dev/null +++ b/l10n/zh-TW/viewer.properties @@ -0,0 +1,45 @@ +bookmark.title=目前檢視(複製或在新視窗中開啟) +previous.title=上一頁 +next.title=下一頁 +print.title=列印 +download.title=下載 +zoom_out.title=縮小 +zoom_in.title=放大 +error_more_info=更多資訊 +error_less_info=更少資訊 +error_close=關閉 +error_build=PDF.JS 版本號: {{build}} +error_message=錯誤信息: {{message}} +error_stack=堆疊: {{stack}} +error_file=檔案: {{file}} +error_line=行數: {{line}} +page_scale_width=符合頁寬 +page_scale_fit=符合頁面 +page_scale_auto=自動縮放 +page_scale_actual=實際大小 +toggle_slider.title=切換側邊欄 +thumbs.title=顯示縮圖 +outline.title=顯示文件綱要 +loading=正在載入... {{percent}}% +loading_error_indicator=錯誤 +loading_error=載入PDF檔案時發生錯誤。 +rendering_error=渲染頁面時發生錯誤。 +page_label=頁次: +page_of=, 共 {{pageCount}} 頁 +no_outline=無可用的綱要 +open_file.title=開啟檔案 +text_annotation_type=[{{type}} 註解] +toggle_slider_label=切換側邊欄 +thumbs_label=縮圖 +outline_label=文件綱要 +bookmark_label=目前檢視 +previous_label=上一頁 +next_label=下一頁 +print_label=列印 +download_label=下載 +zoom_out_label=縮小 +zoom_in_label=放大 +zoom.title=縮放 +thumb_page_title=第 {{page}} 頁 +thumb_page_canvas=第 {{page}} 頁的縮圖 +request_password=PDF 檔案受密碼保護: diff --git a/make.js b/make.js index 88f7c69a0..227abed4b 100755 --- a/make.js +++ b/make.js @@ -9,7 +9,11 @@ var ROOT_DIR = __dirname + '/', // absolute path to project's root LOCALE_SRC_DIR = 'l10n/', GH_PAGES_DIR = BUILD_DIR + 'gh-pages/', REPO = 'git@github.com:mozilla/pdf.js.git', - PYTHON_BIN = 'python2.7'; + PYTHON_BIN = 'python2.7', + MOZCENTRAL_PREF_PREFIX = 'pdfjs', + FIREFOX_PREF_PREFIX = 'extensions.uriloader@pdf.js', + MOZCENTRAL_STREAM_CONVERTER_ID = 'd0c5195d-e798-49d4-b1d3-9324328b2291', + FIREFOX_STREAM_CONVERTER_ID = '6457a96b-2d68-439a-bcfa-44465fbcdbb1'; // // make all @@ -106,6 +110,10 @@ target.locale = function() { cp(path + '/viewer.properties', EXTENSION_LOCALE_OUTPUT + '/' + locale); } + if (test('-f', path + '/chrome.properties')) { + cp(path + '/chrome.properties', EXTENSION_LOCALE_OUTPUT + '/' + locale); + } + if (test('-f', path + '/metadata.inc')) { var metadata = cat(path + '/metadata.inc'); metadataContent += metadata; @@ -348,6 +356,9 @@ target.firefox = function() { sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, FIREFOX_BUILD_DIR + '/install.rdf'); sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, FIREFOX_BUILD_DIR + '/update.rdf'); + sed('-i', /PDFJSSCRIPT_STREAM_CONVERTER_ID/, FIREFOX_STREAM_CONVERTER_ID, FIREFOX_BUILD_DIR + 'components/PdfStreamConverter.js'); + sed('-i', /PDFJSSCRIPT_PREF_PREFIX/, FIREFOX_PREF_PREFIX, FIREFOX_BUILD_DIR + 'components/PdfStreamConverter.js'); + // Update localized metadata var localizedMetadata = cat(EXTENSION_SRC_DIR + '/firefox/metadata.inc'); sed('-i', /.*PDFJS_LOCALIZED_METADATA.*\n/, localizedMetadata, FIREFOX_BUILD_DIR + '/install.rdf'); @@ -377,26 +388,24 @@ target.mozcentral = function() { echo('### Building mozilla-central extension'); var MOZCENTRAL_DIR = BUILD_DIR + 'mozcentral/', - MOZCENTRAL_EXTENSION_DIR = MOZCENTRAL_DIR + 'browser/app/profile/extensions/uriloader@pdf.js/', + MOZCENTRAL_EXTENSION_DIR = MOZCENTRAL_DIR + 'browser/extensions/pdfjs/', MOZCENTRAL_CONTENT_DIR = MOZCENTRAL_EXTENSION_DIR + 'content/', MOZCENTRAL_L10N_DIR = MOZCENTRAL_DIR + 'browser/locales/en-US/pdfviewer/', + MOZCENTRAL_TEST_DIR = MOZCENTRAL_EXTENSION_DIR + 'test/', FIREFOX_CONTENT_DIR = EXTENSION_SRC_DIR + '/firefox/content/', FIREFOX_EXTENSION_FILES_TO_COPY = - ['*.js', + ['components/*.js', '*.svg', '*.png', '*.manifest', - 'install.rdf.in', 'README.mozilla', 'components', '../../LICENSE'], DEFAULT_LOCALE_FILES = - [LOCALE_SRC_DIR + 'en-US/viewer.properties'], + [LOCALE_SRC_DIR + 'en-US/viewer.properties', + LOCALE_SRC_DIR + 'en-US/chrome.properties'], FIREFOX_MC_EXTENSION_FILES = - ['bootstrap.js', - 'icon.png', - 'icon64.png', - 'chrome.manifest', + ['chrome.manifest', 'components', 'content', 'LICENSE']; @@ -415,6 +424,8 @@ target.mozcentral = function() { // Copy extension files cd('extensions/firefox'); cp('-R', FIREFOX_EXTENSION_FILES_TO_COPY, ROOT_DIR + MOZCENTRAL_EXTENSION_DIR); + mv('-f', ROOT_DIR + MOZCENTRAL_EXTENSION_DIR + '/chrome-mozcentral.manifest', + ROOT_DIR + MOZCENTRAL_EXTENSION_DIR + '/chrome.manifest') cd(ROOT_DIR); // Copy a standalone version of pdf.js inside the content directory @@ -446,9 +457,11 @@ target.mozcentral = function() { cp(DEFAULT_LOCALE_FILES, MOZCENTRAL_L10N_DIR); // Update the build version number - sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, MOZCENTRAL_EXTENSION_DIR + 'install.rdf.in'); sed('-i', /PDFJSSCRIPT_VERSION/, EXTENSION_VERSION, MOZCENTRAL_EXTENSION_DIR + 'README.mozilla'); + sed('-i', /PDFJSSCRIPT_STREAM_CONVERTER_ID/, MOZCENTRAL_STREAM_CONVERTER_ID, MOZCENTRAL_EXTENSION_DIR + 'components/PdfStreamConverter.js'); + sed('-i', /PDFJSSCRIPT_PREF_PREFIX/, MOZCENTRAL_PREF_PREFIX, MOZCENTRAL_EXTENSION_DIR + 'components/PdfStreamConverter.js'); + // List all files for mozilla-central cd(MOZCENTRAL_EXTENSION_DIR); var extensionFiles = ''; @@ -457,6 +470,11 @@ target.mozcentral = function() { extensionFiles += file+'\n'; }); extensionFiles.to('extension-files'); + cd(ROOT_DIR); + + // Copy test files + mkdir('-p', MOZCENTRAL_TEST_DIR); + cp('-Rf', 'test/mozcentral/*', MOZCENTRAL_TEST_DIR); }; // @@ -490,6 +508,9 @@ target.chrome = function() { // Copy a standalone version of pdf.js inside the content directory cp(BUILD_TARGET, CHROME_BUILD_CONTENT_DIR + BUILD_DIR); cp('-R', EXTENSION_WEB_FILES, CHROME_BUILD_CONTENT_DIR + '/web'); + // Replacing the l10n.js file with regular gh-pages one + rm(CHROME_BUILD_CONTENT_DIR + '/web/l10n.js'); + cp('external/webL10n/l10n.js', CHROME_BUILD_CONTENT_DIR + '/web'); cp('web/locale.properties', CHROME_BUILD_CONTENT_DIR + '/web'); mv('-f', CHROME_BUILD_CONTENT_DIR + '/web/viewer-production.html', CHROME_BUILD_CONTENT_DIR + '/web/viewer.html'); diff --git a/src/api.js b/src/api.js index 976faf898..59f9661bf 100644 --- a/src/api.js +++ b/src/api.js @@ -7,20 +7,46 @@ * is used, which means it must follow the same origin rules that any XHR does * e.g. No cross domain requests without CORS. * - * @param {string|TypedAray} source Either a url to a PDF is located or a - * typed array (Uint8Array) already populated with data. - * @param {Object} headers An object containing the http headers like this: - * { Authorization: "BASIC XXX" }. + * @param {string|TypedAray|object} source Can be an url to where a PDF is + * located, a typed array (Uint8Array) already populated with data or + * and parameter object with the following possible fields: + * - url - The URL of the PDF. + * - data - A typed array with PDF data. + * - httpHeaders - Basic authentication headers. + * - password - For decrypting password-protected PDFs. + * * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object. */ -PDFJS.getDocument = function getDocument(source, headers) { +PDFJS.getDocument = function getDocument(source) { + var url, data, headers, password, parameters = {}; + if (typeof source === 'string') { + url = source; + } else if (isArrayBuffer(source)) { + data = source; + } else if (typeof source === 'object') { + url = source.url; + data = source.data; + headers = source.httpHeaders; + password = source.password; + parameters.password = password || null; + + if (!url && !data) + error('Invalid parameter array, need either .data or .url'); + } else { + error('Invalid parameter in getDocument, need either Uint8Array, ' + + 'string or a parameter object'); + } + var promise = new PDFJS.Promise(); var transport = new WorkerTransport(promise); - if (typeof source === 'string') { + if (data) { + // assuming the data is array, instantiating directly from it + transport.sendData(data, parameters); + } else if (url) { // fetch url PDFJS.getPdf( { - url: source, + url: url, progress: function getPDFProgress(evt) { if (evt.lengthComputable) promise.progress({ @@ -35,12 +61,10 @@ PDFJS.getDocument = function getDocument(source, headers) { headers: headers }, function getPDFLoad(data) { - transport.sendData(data); + transport.sendData(data, parameters); }); - } else { - // assuming the source is array, instantiating directly from it - transport.sendData(source); } + return promise; }; @@ -122,6 +146,11 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { }); return promise; }, + isEncrypted: function PDFDocumentProxy_isEncrypted() { + var promise = new PDFJS.Promise(); + promise.resolve(this.pdfInfo.encrypted); + return promise; + }, destroy: function PDFDocumentProxy_destroy() { this.transport.destroy(); } @@ -429,7 +458,7 @@ var WorkerTransport = (function WorkerTransportClosure() { messageHandler.send('test', testObj); return; } catch (e) { - warn('The worker has been disabled.'); + info('The worker has been disabled.'); } } // Either workers are disabled, not supported or have thrown an exception. @@ -473,6 +502,14 @@ var WorkerTransport = (function WorkerTransportClosure() { this.workerReadyPromise.resolve(pdfDocument); }, this); + messageHandler.on('NeedPassword', function transportPassword(data) { + this.workerReadyPromise.reject(data.exception.message, data.exception); + }, this); + + messageHandler.on('IncorrectPassword', function transportBadPass(data) { + this.workerReadyPromise.reject(data.exception.message, data.exception); + }, this); + messageHandler.on('GetPage', function transportPage(data) { var pageInfo = data.pageInfo; var page = new PDFPageProxy(pageInfo, this); @@ -575,8 +612,8 @@ var WorkerTransport = (function WorkerTransportClosure() { }); }, - sendData: function WorkerTransport_sendData(data) { - this.messageHandler.send('GetDocRequest', data); + sendData: function WorkerTransport_sendData(data, params) { + this.messageHandler.send('GetDocRequest', {data: data, params: params}); }, getPage: function WorkerTransport_getPage(pageNumber, promise) { diff --git a/src/canvas.js b/src/canvas.js index 9d470fbec..85e82aaa5 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -343,10 +343,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { this.ctx.webkitLineDashOffset = dashPhase; }, setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) { - TODO('set rendering intent: ' + intent); + // Maybe if we one day fully support color spaces this will be important + // for now we can ignore. + // TODO set rendering intent? }, setFlatness: function CanvasGraphics_setFlatness(flatness) { - TODO('set flatness: ' + flatness); + // There's no way to control this with canvas, but we can safely ignore. + // TODO set flatness? }, setGState: function CanvasGraphics_setGState(states) { for (var i = 0, ii = states.length; i < ii; i++) { @@ -841,7 +844,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { text.length += shownText.length; } } else { - malformed('TJ array element ' + e + ' is not string or num'); + error('TJ array element ' + e + ' is not string or num'); } } diff --git a/src/colorspace.js b/src/colorspace.js index 8d8290109..26e7c1f0f 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -451,12 +451,12 @@ var LabCS = (function LabCSClosure() { error('Invalid WhitePoint components, no fallback available'); if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { - warn('Invalid BlackPoint, falling back to default'); + info('Invalid BlackPoint, falling back to default'); this.XB = this.YB = this.ZB = 0; } if (this.amin > this.amax || this.bmin > this.bmax) { - warn('Invalid Range, falling back to defaults'); + info('Invalid Range, falling back to defaults'); this.amin = -100; this.amax = 100; this.bmin = -100; diff --git a/src/core.js b/src/core.js index e1a9476a3..8e07078ef 100644 --- a/src/core.js +++ b/src/core.js @@ -7,7 +7,7 @@ var globalScope = (typeof window === 'undefined') ? this : window; var isWorker = (typeof window == 'undefined'); -var ERRORS = 0, WARNINGS = 1, TODOS = 5; +var ERRORS = 0, WARNINGS = 1, INFOS = 5; var verbosity = WARNINGS; // The global PDFJS object exposes the API @@ -362,19 +362,19 @@ var Page = (function PageClosure() { * `PDFDocument` objects on the main thread created. */ var PDFDocument = (function PDFDocumentClosure() { - function PDFDocument(arg, callback) { + function PDFDocument(arg, password) { if (isStream(arg)) - init.call(this, arg); + init.call(this, arg, password); else if (isArrayBuffer(arg)) - init.call(this, new Stream(arg)); + init.call(this, new Stream(arg), password); else error('PDFDocument: Unknown argument type'); } - function init(stream) { + function init(stream, password) { assertWellFormed(stream.length > 0, 'stream must have data'); this.stream = stream; - this.setup(); + this.setup(password); this.acroForm = this.catalog.catDict.get('AcroForm'); } @@ -465,11 +465,12 @@ var PDFDocument = (function PDFDocumentClosure() { } // May not be a PDF file, continue anyway. }, - setup: function PDFDocument_setup(ownerPassword, userPassword) { + setup: function PDFDocument_setup(password) { this.checkHeader(); var xref = new XRef(this.stream, this.startXRef, - this.mainXRefEntriesOffset); + this.mainXRefEntriesOffset, + password); this.xref = xref; this.catalog = new Catalog(xref); }, diff --git a/src/crypto.js b/src/crypto.js index dcd820554..c86551f36 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -556,7 +556,9 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata); - if (!encryptionKey && password) { + if (!encryptionKey && !password) { + throw new PasswordException('No password given', 'needpassword'); + } else if (!encryptionKey && password) { // Attempting use the password as an owner password var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword, revision, keyLength); @@ -566,7 +568,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { } if (!encryptionKey) - error('incorrect password or encryption data'); + throw new PasswordException('Incorrect Password', 'incorrectpassword'); this.encryptionKey = encryptionKey; diff --git a/src/evaluator.js b/src/evaluator.js index f99e79a37..8849a994a 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -108,39 +108,21 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // Compatibility BX: 'beginCompat', - EX: 'endCompat' + EX: 'endCompat', + + // (reserved partial commands for the lexer) + BM: null, + BD: null, + 'true': null, + fa: null, + fal: null, + fals: null, + 'false': null, + nu: null, + nul: null, + 'null': null }; - function splitCombinedOperations(operations) { - // Two or more operations can be combined together, trying to find which - // operations were concatenated. - var result = []; - var opIndex = 0; - - if (!operations) { - return null; - } - - while (opIndex < operations.length) { - var currentOp = ''; - for (var op in OP_MAP) { - if (op == operations.substr(opIndex, op.length) && - op.length > currentOp.length) { - currentOp = op; - } - } - - if (currentOp.length > 0) { - result.push(operations.substr(opIndex, currentOp.length)); - opIndex += currentOp.length; - } else { - return null; - } - } - - return result; - } - PartialEvaluator.prototype = { getOperatorList: function PartialEvaluator_getOperatorList(stream, resources, @@ -285,39 +267,19 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { resources = resources || new Dict(); var xobjs = resources.get('XObject') || new Dict(); var patterns = resources.get('Pattern') || new Dict(); - var parser = new Parser(new Lexer(stream), false, xref); + var parser = new Parser(new Lexer(stream, OP_MAP), false, xref); var res = resources; - var hasNextObj = false, nextObjs; var args = [], obj; var TILING_PATTERN = 1, SHADING_PATTERN = 2; while (true) { - if (hasNextObj) { - obj = nextObjs.pop(); - hasNextObj = (nextObjs.length > 0); - } else { - obj = parser.getObj(); - if (isEOF(obj)) - break; - } + obj = parser.getObj(); + if (isEOF(obj)) + break; if (isCmd(obj)) { var cmd = obj.cmd; var fn = OP_MAP[cmd]; - if (!fn) { - // invalid content command, trying to recover - var cmds = splitCombinedOperations(cmd); - if (cmds) { - cmd = cmds[0]; - fn = OP_MAP[cmd]; - // feeding other command on the next iteration - hasNextObj = true; - nextObjs = []; - for (var idx = 1; idx < cmds.length; idx++) { - nextObjs.push(Cmd.get(cmds[idx])); - } - } - } assertWellFormed(fn, 'Unknown command "' + cmd + '"'); // TODO figure out how to type-check vararg functions @@ -457,6 +419,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { value[1] ]); break; + case 'BM': + // We support the default so don't trigger the TODO. + if (!isName(value) || value.name != 'Normal') + TODO('graphic state operator ' + key); + break; + case 'SMask': + // We support the default so don't trigger the TODO. + if (!isName(value) || value.name != 'None') + TODO('graphic state operator ' + key); + break; + // Only generate info log messages for the following since + // they are unlikey to have a big impact on the rendering. case 'OP': case 'op': case 'OPM': @@ -469,14 +443,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { case 'HT': case 'SM': case 'SA': - case 'BM': - case 'SMask': case 'AIS': case 'TK': - TODO('graphic state operator ' + key); + // TODO implement these operators. + info('graphic state operator ' + key); break; default: - warn('Unknown graphic state operator ' + key); + info('Unknown graphic state operator ' + key); break; } } @@ -725,8 +698,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } } else if (octet == 0x3E) { if (token.length) { - // XXX guessing chars size by checking number size in the CMap - if (token.length <= 2 && properties.composite) + // Heuristic: guessing chars size by checking numbers sizes + // in the CMap entries. + if (token.length == 2 && properties.composite) properties.wideChars = false; if (token.length <= 4) { diff --git a/src/fonts.js b/src/fonts.js index f7c6e6f4e..72bcecbce 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -2706,9 +2706,9 @@ var Font = (function FontClosure() { this.isSymbolicFont = false; } - // heuristics: if removed more than 2 glyphs encoding WinAnsiEncoding - // does not set properly - if (glyphsRemoved > 2) { + // heuristics: if removed more than 10 glyphs encoding WinAnsiEncoding + // does not set properly (broken PDFs have about 100 removed glyphs) + if (glyphsRemoved > 10) { warn('Switching TrueType encoding to MacRomanEncoding for ' + this.name + ' font'); encoding = Encodings.MacRomanEncoding; @@ -4208,7 +4208,7 @@ var CFFFont = (function CFFFontClosure() { this.properties = properties; var parser = new CFFParser(file, properties); - var cff = parser.parse(); + var cff = parser.parse(true); var compiler = new CFFCompiler(cff); this.readExtra(cff); try { @@ -4299,7 +4299,7 @@ var CFFParser = (function CFFParserClosure() { this.properties = properties; } CFFParser.prototype = { - parse: function CFFParser_parse() { + parse: function CFFParser_parse(normalizeCIDData) { var properties = this.properties; var cff = new CFF(); this.cff = cff; @@ -4354,6 +4354,21 @@ var CFFParser = (function CFFParserClosure() { cff.charset = charset; cff.encoding = encoding; + if (!cff.isCIDFont || !normalizeCIDData) + return cff; + + // DirectWrite does not like CID fonts data. Trying to convert/flatten + // the font data and remove CID properties. + if (cff.fdArray.length !== 1) + error('Unable to normalize CID font in CFF data'); + + var fontDict = cff.fdArray[0]; + fontDict.setByKey(17, topDict.getByName('CharStrings')); + cff.topDict = fontDict; + cff.isCIDFont = false; + delete cff.fdArray; + delete cff.fdSelect; + return cff; }, parseHeader: function CFFParser_parseHeader() { @@ -4364,7 +4379,7 @@ var CFFParser = (function CFFParserClosure() { ++offset; if (offset != 0) { - warn('cff data is shifted'); + info('cff data is shifted'); bytes = bytes.subarray(offset); this.bytes = bytes; } @@ -4952,9 +4967,9 @@ var CFFPrivateDict = (function CFFPrivateDictClosure() { [[12, 17], 'LanguageGroup', 'num', 0], [[12, 18], 'ExpansionFactor', 'num', 0.06], [[12, 19], 'initialRandomSeed', 'num', 0], - [19, 'Subrs', 'offset', null], [20, 'defaultWidthX', 'num', 0], - [21, 'nominalWidthX', 'num', 0] + [21, 'nominalWidthX', 'num', 0], + [19, 'Subrs', 'offset', null] ]; var tables = null; function CFFPrivateDict(strings) { diff --git a/src/obj.js b/src/obj.js index 9b99eb8f7..3432ac68d 100644 --- a/src/obj.js +++ b/src/obj.js @@ -298,7 +298,7 @@ var Catalog = (function CatalogClosure() { })(); var XRef = (function XRefClosure() { - function XRef(stream, startXRef, mainXRefEntriesOffset) { + function XRef(stream, startXRef, mainXRefEntriesOffset, password) { this.stream = stream; this.entries = []; this.xrefstms = {}; @@ -311,8 +311,7 @@ var XRef = (function XRefClosure() { var encrypt = trailerDict.get('Encrypt'); if (encrypt) { var fileId = trailerDict.get('ID'); - this.encrypt = new CipherTransformFactory(encrypt, - fileId[0] /*, password */); + this.encrypt = new CipherTransformFactory(encrypt, fileId[0], password); } // get the root dictionary (catalog) object diff --git a/src/parser.js b/src/parser.js index 2855018a6..6c9b4e67f 100644 --- a/src/parser.js +++ b/src/parser.js @@ -264,8 +264,16 @@ var Parser = (function ParserClosure() { })(); var Lexer = (function LexerClosure() { - function Lexer(stream) { + function Lexer(stream, knownCommands) { this.stream = stream; + // The PDFs might have "glued" commands with other commands, operands or + // literals, e.g. "q1". The knownCommands is a dictionary of the valid + // commands and their prefixes. The prefixes are built the following way: + // if there a command that is a prefix of the other valid command or + // literal (e.g. 'f' and 'false') the following prefixes must be included, + // 'fa', 'fal', 'fals'. The prefixes are not needed, if the command has no + // other commands or literals as a prefix. The knowCommands is optional. + this.knownCommands = knownCommands; } Lexer.isSpace = function Lexer_isSpace(ch) { @@ -529,12 +537,18 @@ var Lexer = (function LexerClosure() { // command var str = ch; + var knownCommands = this.knownCommands; + var knownCommandFound = knownCommands && (str in knownCommands); while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) { + // stop if known command is found and next character does not make + // the str a command + if (knownCommandFound && !((str + ch) in knownCommands)) + break; stream.skip(); if (str.length == 128) error('Command token too long: ' + str.length); - str += ch; + knownCommandFound = knownCommands && (str in knownCommands); } if (str == 'true') return true; diff --git a/src/stream.js b/src/stream.js index 48c462fb2..b0e1beb34 100644 --- a/src/stream.js +++ b/src/stream.js @@ -1687,7 +1687,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (a1 > codingLine[codingPos]) { if (a1 > this.columns) { - warn('row is wrong length'); + info('row is wrong length'); this.err = true; a1 = this.columns; } @@ -1707,7 +1707,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (a1 > codingLine[codingPos]) { if (a1 > this.columns) { - warn('row is wrong length'); + info('row is wrong length'); this.err = true; a1 = this.columns; } @@ -1717,7 +1717,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { codingLine[codingPos] = a1; } else if (a1 < codingLine[codingPos]) { if (a1 < 0) { - warn('invalid code'); + info('invalid code'); this.err = true; a1 = 0; } @@ -1879,7 +1879,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.eof = true; break; default: - warn('bad 2d code'); + info('bad 2d code'); this.addPixels(columns, 0); this.err = true; } @@ -1942,7 +1942,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { for (var i = 0; i < 4; ++i) { code1 = this.lookBits(12); if (code1 != 1) - warn('bad rtc code: ' + code1); + info('bad rtc code: ' + code1); this.eatBits(12); if (this.encoding > 0) { this.lookBits(1); @@ -2065,7 +2065,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (result[0] && result[2]) return result[1]; } - warn('Bad two dim code'); + info('Bad two dim code'); return EOF; }; @@ -2098,7 +2098,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (result[0]) return result[1]; } - warn('bad white code'); + info('bad white code'); this.eatBits(1); return 1; }; @@ -2135,7 +2135,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (result[0]) return result[1]; } - warn('bad black code'); + info('bad black code'); this.eatBits(1); return 1; }; diff --git a/src/util.js b/src/util.js index 140b18cf1..b8359b5d1 100644 --- a/src/util.js +++ b/src/util.js @@ -3,6 +3,8 @@ 'use strict'; +// Use only for debugging purposes. This should not be used in any code that is +// in mozilla master. function log(msg) { if (console && console.log) console.log(msg); @@ -10,9 +12,36 @@ function log(msg) { print(msg); } +// A notice for devs that will not trigger the fallback UI. These are good +// for things that are helpful to devs, such as warning that Workers were +// disabled, which is important to devs but not end users. +function info(msg) { + if (verbosity >= INFOS) { + log('Info: ' + msg); + PDFJS.LogManager.notify('info', msg); + } +} + +// Non-fatal warnings that should trigger the fallback UI. function warn(msg) { - if (verbosity >= WARNINGS) + if (verbosity >= WARNINGS) { log('Warning: ' + msg); + PDFJS.LogManager.notify('warn', msg); + } +} + +// Fatal errors that should trigger the fallback UI and halt execution by +// throwing an exception. +function error(msg) { + log('Error: ' + msg); + log(backtrace()); + PDFJS.LogManager.notify('error', msg); + throw new Error(msg); +} + +// Missing features that should trigger the fallback UI. +function TODO(what) { + warn('TODO: ' + what); } function backtrace() { @@ -23,21 +52,6 @@ function backtrace() { } } -function error(msg) { - log('Error: ' + msg); - log(backtrace()); - throw new Error(msg); -} - -function TODO(what) { - if (verbosity >= TODOS) - log('TODO: ' + what); -} - -function malformed(msg) { - error('Malformed PDF: ' + msg); -} - function assert(cond, msg) { if (!cond) error(msg); @@ -47,9 +61,25 @@ function assert(cond, msg) { // behavior is undefined. function assertWellFormed(cond, msg) { if (!cond) - malformed(msg); + error(msg); } +var LogManager = PDFJS.LogManager = (function LogManagerClosure() { + var loggers = []; + return { + addLogger: function logManager_addLogger(logger) { + loggers.push(logger); + }, + notify: function(type, message) { + for (var i = 0, ii = loggers.length; i < ii; i++) { + var logger = loggers[i]; + if (logger[type]) + logger[type](message); + } + } + }; +})(); + function shadow(obj, prop, value) { Object.defineProperty(obj, prop, { value: value, enumerable: true, @@ -58,6 +88,19 @@ function shadow(obj, prop, value) { return value; } +var PasswordException = (function PasswordExceptionClosure() { + function PasswordException(msg, code) { + this.name = 'PasswordException'; + this.message = msg; + this.code = code; + } + + PasswordException.prototype = new Error(); + PasswordException.constructor = PasswordException; + + return PasswordException; +})(); + function bytesToString(bytes) { var str = ''; var length = bytes.length; @@ -456,7 +499,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { } this.isResolved = true; - this.data = data || null; + this.data = (typeof data !== 'undefined') ? data : null; var callbacks = this.callbacks; for (var i = 0, ii = callbacks.length; i < ii; i++) { @@ -471,7 +514,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { } }, - reject: function Promise_reject(reason) { + reject: function Promise_reject(reason, exception) { if (this.isRejected) { error('A Promise can be rejected only once ' + this.name); } @@ -484,7 +527,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { var errbacks = this.errbacks; for (var i = 0, ii = errbacks.length; i < ii; i++) { - errbacks[i].call(null, reason); + errbacks[i].call(null, reason, exception); } }, diff --git a/src/worker.js b/src/worker.js index b3ba7671e..a93c36e95 100644 --- a/src/worker.js +++ b/src/worker.js @@ -11,10 +11,13 @@ function MessageHandler(name, comObj) { var ah = this.actionHandler = {}; ah['console_log'] = [function ahConsoleLog(data) { - console.log.apply(console, data); + console.log.apply(console, data); }]; ah['console_error'] = [function ahConsoleError(data) { - console.error.apply(console, data); + console.error.apply(console, data); + }]; + ah['_warn'] = [function ah_Warn(data) { + warn(data); }]; comObj.onmessage = function messageHandlerComObjOnMessage(event) { @@ -88,14 +91,35 @@ var WorkerMessageHandler = { handler.on('GetDocRequest', function wphSetupDoc(data) { // Create only the model of the PDFDoc, which is enough for // processing the content of the pdf. - pdfModel = new PDFDocument(new Stream(data)); + var pdfData = data.data; + var pdfPassword = data.params.password; + try { + pdfModel = new PDFDocument(new Stream(pdfData), pdfPassword); + } catch (e) { + if (e instanceof PasswordException) { + if (e.code === 'needpassword') { + handler.send('NeedPassword', { + exception: e + }); + } else if (e.code === 'incorrectpassword') { + handler.send('IncorrectPassword', { + exception: e + }); + } + + return; + } else { + throw e; + } + } var doc = { numPages: pdfModel.numPages, fingerprint: pdfModel.getFingerprint(), destinations: pdfModel.catalog.destinations, outline: pdfModel.catalog.documentOutline, info: pdfModel.getDocumentInfo(), - metadata: pdfModel.catalog.metadata + metadata: pdfModel.catalog.metadata, + encrypted: !!pdfModel.xref.encrypt }; handler.send('GetDoc', {pdfInfo: doc}); }); @@ -240,6 +264,17 @@ var workerConsole = { if (typeof window === 'undefined') { globalScope.console = workerConsole; + // Add a logger so we can pass warnings on to the main thread, errors will + // throw an exception which will be forwarded on automatically. + PDFJS.LogManager.addLogger({ + warn: function(msg) { + postMessage({ + action: '_warn', + data: msg + }); + } + }); + var handler = new MessageHandler('worker_processor', this); WorkerMessageHandler.setup(handler); } diff --git a/test/mozcentral/Makefile.in b/test/mozcentral/Makefile.in new file mode 100644 index 000000000..8c9face17 --- /dev/null +++ b/test/mozcentral/Makefile.in @@ -0,0 +1,21 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = ../../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ +relativesrcdir = browser/extensions/pdfjs/test + +include $(DEPTH)/config/autoconf.mk +include $(topsrcdir)/config/rules.mk + +_BROWSER_TEST_FILES = \ + head.js \ + browser_pdfjs_main.js \ + file_pdfjs_test.pdf \ + $(NULL) + +libs:: $(_BROWSER_TEST_FILES) + $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) diff --git a/test/mozcentral/browser_pdfjs_main.js b/test/mozcentral/browser_pdfjs_main.js new file mode 100644 index 000000000..d3f5dd646 --- /dev/null +++ b/test/mozcentral/browser_pdfjs_main.js @@ -0,0 +1,102 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const RELATIVE_DIR = "browser/extensions/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +function test() { + waitForExplicitFinish(); + + var tab = gBrowser.addTab(TESTROOT + "file_pdfjs_test.pdf"); + var newTabBrowser = gBrowser.getBrowserForTab(tab); + newTabBrowser.addEventListener("pagechange", function onPageChange() { + newTabBrowser.removeEventListener("pagechange", onPageChange, true); + + var document = newTabBrowser.contentDocument, + window = newTabBrowser.contentWindow; + + // + // Overall sanity tests + // + ok(document.querySelector('div#viewer'), "document content has viewer UI"); + ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object"); + + // + // Sidebar: open + // + var sidebar = document.querySelector('button#sidebarToggle'), + outerContainer = document.querySelector('div#outerContainer'); + + sidebar.click(); + ok(outerContainer.classList.contains('sidebarOpen'), 'sidebar opens on click'); + + // Thumbnails are created asynchronously - wait for them + waitForElement(document, 'canvas#thumbnail2', function(error) { + if (error) + finish(); + + // + // Page change from thumbnail click + // + var pageNumber = document.querySelector('input#pageNumber'); + is(parseInt(pageNumber.value), 1, 'initial page is 1'); + + var thumbnail = document.querySelector('canvas#thumbnail2'); + ok(thumbnail, 'thumbnail2 is available'); + if (thumbnail) { + thumbnail.click(); + is(parseInt(pageNumber.value), 2, 'clicking on thumbnail changes page'); + } + + // + // Sidebar: close + // + sidebar.click(); + ok(!outerContainer.classList.contains('sidebarOpen'), 'sidebar closes on click'); + + // + // Page change from prev/next buttons + // + var prevPage = document.querySelector('button#previous'), + nextPage = document.querySelector('button#next'); + + nextPage.click(); + is(parseInt(pageNumber.value), 2, 'page increases after clicking on next'); + + prevPage.click(); + is(parseInt(pageNumber.value), 1, 'page decreases after clicking on previous'); + + // + // Bookmark button + // + var viewBookmark = document.querySelector('a#viewBookmark'); + viewBookmark.click(); + ok(viewBookmark.href.length > 0, 'viewBookmark button has href'); + + // + // Zoom in/out + // + var zoomOut = document.querySelector('button.zoomOut'), + zoomIn = document.querySelector('button.zoomIn'); + + // Zoom in + var oldWidth = document.querySelector('canvas#page1').width; + zoomIn.click(); + var newWidth = document.querySelector('canvas#page1').width; + ok(oldWidth < newWidth, 'zooming in increases page width (old: '+oldWidth+', new: '+newWidth+')'); + + // Zoom out + var oldWidth = document.querySelector('canvas#page1').width; + zoomOut.click(); + var newWidth = document.querySelector('canvas#page1').width; + ok(oldWidth > newWidth, 'zooming out decreases page width (old: '+oldWidth+', new: '+newWidth+')'); + + finish(); + }); + }, true, true); + + registerCleanupFunction(function() { + gBrowser.removeTab(tab); + }); +} diff --git a/test/mozcentral/file_pdfjs_test.pdf b/test/mozcentral/file_pdfjs_test.pdf new file mode 100644 index 000000000..ea5ebe395 Binary files /dev/null and b/test/mozcentral/file_pdfjs_test.pdf differ diff --git a/test/mozcentral/head.js b/test/mozcentral/head.js new file mode 100644 index 000000000..6ee421b2f --- /dev/null +++ b/test/mozcentral/head.js @@ -0,0 +1,21 @@ +// Waits for element 'el' to exist in the DOM of 'doc' before executing 'callback' +// Useful when elements are created asynchronously, e.g. after a Web Worker task +function waitForElement(doc, el, callback) { + var time = 0, + interval = 10, + timeout = 5000; + + var checkEl = setInterval(function() { + if (doc.querySelector(el)) { + clearInterval(checkEl); + if (callback) callback(); + } + + time += interval; + if (time > timeout) { + ok(false, 'waitForElement timed out on element: '+el); + clearInterval(checkEl); + if (callback) callback(true); + } + }, interval); +} diff --git a/test/pdfs/issue1629.pdf.link b/test/pdfs/issue1629.pdf.link new file mode 100644 index 000000000..1d18104b5 --- /dev/null +++ b/test/pdfs/issue1629.pdf.link @@ -0,0 +1 @@ +http://is.muni.cz/www/20544/noty/sbm/Taize/Adoramus_te_Christe.pdf diff --git a/test/pdfs/issue1709.pdf.link b/test/pdfs/issue1709.pdf.link new file mode 100644 index 000000000..ca5121b2d --- /dev/null +++ b/test/pdfs/issue1709.pdf.link @@ -0,0 +1 @@ +http://www.mft-online.de/files/medizinerreport_2012.pdf diff --git a/test/pdfs/issue1721.pdf.link b/test/pdfs/issue1721.pdf.link new file mode 100644 index 000000000..5a36166d8 --- /dev/null +++ b/test/pdfs/issue1721.pdf.link @@ -0,0 +1 @@ +http://www.lezarts.org/07oforhom/Faitsdivers/Hadopi/Le%20Rapport%20Hadopi,%20Intox.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index 5ec9e850f..25d2a2080 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -402,6 +402,14 @@ "link": true, "type": "eq" }, + { "id": "issue1709", + "file": "pdfs/issue1709.pdf", + "md5": "84497bd23b7c82d03d2681a1cb1d9ed0", + "rounds": 1, + "pageLimit": 10, + "link": true, + "type": "eq" + }, { "id": "issue1015", "file": "pdfs/issue1015.pdf", "md5": "b61503d1b445742b665212866afb60e2", @@ -451,6 +459,13 @@ "link": true, "type": "eq" }, + { "id": "issue1629", + "file": "pdfs/issue1629.pdf", + "md5": "0f2cbbf268383a377e95e6bbe36c6a9a", + "rounds": 1, + "link": true, + "type": "eq" + }, { "id": "issue1169", "file": "pdfs/issue1169.pdf", "md5": "3df3ed21fd43ac7fdb21e2015c8a7809", @@ -543,6 +558,14 @@ "link": false, "type": "eq" }, + { "id": "issue1721", + "file": "pdfs/issue1721.pdf", + "md5": "b47177f9e5197a76ec498733ecab60e6", + "rounds": 1, + "pageLimit": 2, + "link": true, + "type": "eq" + }, { "id": "issue1243", "file": "pdfs/issue1243.pdf", "md5": "130c849b83513d5ac5e03c6421fc7489", diff --git a/test/unit/evaluator_spec.js b/test/unit/evaluator_spec.js index 4ee0768a7..e31a525ac 100644 --- a/test/unit/evaluator_spec.js +++ b/test/unit/evaluator_spec.js @@ -78,6 +78,54 @@ describe('evaluator', function() { expect(result.fnArray[1]).toEqual('save'); expect(result.fnArray[2]).toEqual('save'); }); + + it('should handle three glued operations #2', function() { + var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(), + 'prefix'); + var resources = new ResourcesMock(); + resources.Res1 = {}; + var stream = new StringStream('B*BBMC'); + var result = evaluator.getOperatorList(stream, resources, []); + + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(3); + expect(result.fnArray[0]).toEqual('eoFillStroke'); + expect(result.fnArray[1]).toEqual('fillStroke'); + expect(result.fnArray[2]).toEqual('beginMarkedContent'); + }); + + it('should handle glued operations and operands', function() { + var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(), + 'prefix'); + var stream = new StringStream('q5 Ts'); + var result = evaluator.getOperatorList(stream, new ResourcesMock(), []); + + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(2); + expect(result.fnArray[0]).toEqual('save'); + expect(result.fnArray[1]).toEqual('setTextRise'); + expect(result.argsArray.length).toEqual(2); + expect(result.argsArray[1].length).toEqual(1); + expect(result.argsArray[1][0]).toEqual(5); + }); + + it('should handle glued operations and literals', function() { + var evaluator = new PartialEvaluator(new XrefMock(), new HandlerMock(), + 'prefix'); + var stream = new StringStream('trueifalserinulli'); + var result = evaluator.getOperatorList(stream, new ResourcesMock(), []); + + expect(!!result.fnArray && !!result.argsArray).toEqual(true); + expect(result.fnArray.length).toEqual(3); + expect(result.fnArray[0]).toEqual('setFlatness'); + expect(result.fnArray[1]).toEqual('setRenderingIntent'); + expect(result.fnArray[2]).toEqual('setFlatness'); + expect(result.argsArray.length).toEqual(3); + expect(result.argsArray[0].length).toEqual(1); + expect(result.argsArray[0][0]).toEqual(true); + expect(result.argsArray[1].length).toEqual(1); + expect(result.argsArray[1][0]).toEqual(false); + }); }); }); diff --git a/web/compatibility.js b/web/compatibility.js index 5c192c9a9..528841bb6 100644 --- a/web/compatibility.js +++ b/web/compatibility.js @@ -120,6 +120,9 @@ return new Uint8Array(new VBArray(this.responseBody).toArray()); } }); + Object.defineProperty(xhrPrototype, 'overrideMimeType', { + value: function xmlHttpRequestOverrideMimeType(mimeType) {} + }); return; } @@ -217,15 +220,84 @@ var div = document.createElement('div'); if ('dataset' in div) return; // dataset property exists - var oldCreateElement = document.createElement; - document.createElement = function newCreateElement() { - var result = oldCreateElement.apply(document, arguments); - if (arguments[0] === 'div') { - // creating dataset property for the div elements - result.dataset = {}; + + Object.defineProperty(HTMLElement.prototype, 'dataset', { + get: function() { + if (this._dataset) + return this._dataset; + + var dataset = {}; + for (var j = 0, jj = this.attributes.length; j < jj; j++) { + var attribute = this.attributes[j]; + if (attribute.name.substring(0, 5) != 'data-') + continue; + var key = attribute.name.substring(5).replace(/\-([a-z])/g, + function(all, ch) { return ch.toUpperCase(); }); + dataset[key] = attribute.value; + } + + Object.defineProperty(this, '_dataset', { + value: dataset, + writable: false, + enumerable: false + }); + return dataset; + }, + enumerable: true + }); +})(); + +// HTMLElement classList property +(function checkClassListProperty() { + var div = document.createElement('div'); + if ('classList' in div) + return; // classList property exists + + function changeList(element, itemName, add, remove) { + var s = element.className || ''; + var list = s.split(/\s+/g); + if (list[0] == '') list.shift(); + var index = list.indexOf(itemName); + if (index < 0 && add) + list.push(itemName); + if (index >= 0 && remove) + list.splice(index, 1); + element.className = list.join(' '); + } + + var classListPrototype = { + add: function(name) { + changeList(this.element, name, true, false); + }, + remove: function(name) { + changeList(this.element, name, false, true); + }, + toggle: function(name) { + changeList(this.element, name, true, true); } - return result; }; + + Object.defineProperty(HTMLElement.prototype, 'classList', { + get: function() { + if (this._classList) + return this._classList; + + var classList = Object.create(classListPrototype, { + element: { + value: this, + writable: false, + enumerable: true + } + }); + Object.defineProperty(this, '_classList', { + value: classList, + writable: false, + enumerable: false + }); + return classList; + }, + enumerable: true + }); })(); // Check console compatability @@ -252,3 +324,17 @@ document.addEventListener('click', ignoreIfTargetDisabled, true); } })(); + +// Checks if navigator.language is supported +(function checkNavigatorLanguage() { + if ('language' in navigator) + return; + Object.defineProperty(navigator, 'language', { + get: function navigatorLanguage() { + var language = navigator.userLanguage || 'en-US'; + return language.substring(0, 2).toLowerCase() + + language.substring(2).toUpperCase(); + }, + enumerable: true + }); +})(); diff --git a/web/images/toolbarButton-openFile.png b/web/images/toolbarButton-openFile.png new file mode 100644 index 000000000..12ce45f87 Binary files /dev/null and b/web/images/toolbarButton-openFile.png differ diff --git a/web/viewer.css b/web/viewer.css index 4fc426c64..0853986c4 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -428,6 +428,10 @@ html[dir='rtl'] .dropdownToolbarButton { background: transparent; } +.dropdownToolbarButton > select > option { + background: hsl(0,0%,24%); +} + #customScaleOption { display: none; } @@ -501,6 +505,11 @@ html[dir='rtl'] .toolbarButton.pageDown::before { content: url(images/toolbarButton-print.png); } +.toolbarButton.openFile::before { + display: inline-block; + content: url(images/toolbarButton-openFile.png); +} + .toolbarButton.download::before { display: inline-block; content: url(images/toolbarButton-download.png); @@ -985,3 +994,76 @@ canvas { #PDFBug table { font-size: 10px; } + +@media print { + #sidebarContainer, .toolbar, #loadingBox, #errorWrapper, .textLayer { + display: none; + } + + #mainContainer, #viewerContainer, .page, .page canvas { + position: static; + padding: 0; + margin: 0; + } + + .page { + float: left; + display: none; + box-shadow: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + } + + .page[data-loaded] { + display: block; + } +} + +@media all and (max-width: 950px) { + html[dir='ltr'] #outerContainer.sidebarMoving .outerCenter, + html[dir='ltr'] #outerContainer.sidebarOpen .outerCenter { + float: left; + left: 180px; + } + html[dir='rtl'] #outerContainer.sidebarMoving .outerCenter, + html[dir='rtl'] #outerContainer.sidebarOpen .outerCenter { + float: right; + right: 180px; + } +} + +@media all and (max-width: 770px) { + #sidebarContainer { + top: 33px; + z-index: 100; + } + #sidebarContent { + top: 32px; + background-color: hsla(0,0%,0%,.7); + } + #thumbnailView, #outlineView { + top: 66px; + } + + html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer { + left: 0px; + } + html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer { + right: 0px; + } + + html[dir='ltr'] .outerCenter { + float: left; + left: 180px; + } + html[dir='rtl'] .outerCenter { + float: right; + right: 180px; + } +} + +@media all and (max-width: 600px) { + #toolbarViewerRight { + display: none; + } +} diff --git a/web/viewer.html b/web/viewer.html index 11c6ad0b7..237c94d10 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -89,20 +89,16 @@ Next - +
- - -