viewerjs-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +65 -0
  6. data/Rakefile +2 -0
  7. data/app/assets/AGPL-3.0.txt +661 -0
  8. data/app/assets/images/kogmbh.png +0 -0
  9. data/app/assets/images/nlnet.png +0 -0
  10. data/app/assets/images/texture.png +0 -0
  11. data/app/assets/images/toolbarButton-download.png +0 -0
  12. data/app/assets/images/toolbarButton-fullscreen.png +0 -0
  13. data/app/assets/images/toolbarButton-menuArrows.png +0 -0
  14. data/app/assets/images/toolbarButton-pageDown.png +0 -0
  15. data/app/assets/images/toolbarButton-pageUp.png +0 -0
  16. data/app/assets/images/toolbarButton-presentation.png +0 -0
  17. data/app/assets/images/toolbarButton-zoomIn.png +0 -0
  18. data/app/assets/images/toolbarButton-zoomOut.png +0 -0
  19. data/app/assets/javascripts/pdf.worker.js +39900 -0
  20. data/app/assets/javascripts/viewerjs/ODFViewerPlugin.js +219 -0
  21. data/app/assets/javascripts/viewerjs/PDFViewerPlugin.js.erb +348 -0
  22. data/app/assets/javascripts/viewerjs/PluginLoader.js +47 -0
  23. data/app/assets/javascripts/viewerjs/compatibility.js +491 -0
  24. data/app/assets/javascripts/viewerjs/pdf.js +7651 -0
  25. data/app/assets/javascripts/viewerjs/pdf_find_bar.js +175 -0
  26. data/app/assets/javascripts/viewerjs/pdf_find_controller.js +355 -0
  27. data/app/assets/javascripts/viewerjs/pdfjsversion.js +1 -0
  28. data/app/assets/javascripts/viewerjs/text_layer_builder.js +385 -0
  29. data/app/assets/javascripts/viewerjs/ui_utils.js +270 -0
  30. data/app/assets/javascripts/viewerjs/viewer.js +230 -0
  31. data/app/assets/javascripts/viewerjs/webodf.js +631 -0
  32. data/app/assets/javascripts/viewerjs_rails.js +22 -0
  33. data/app/assets/stylesheets/ODFViewerPlugin.css.scss +29 -0
  34. data/app/assets/stylesheets/PDFViewerPlugin.css.scss +36 -0
  35. data/app/assets/stylesheets/example.local.css.scss +27 -0
  36. data/app/assets/stylesheets/viewer.css.scss +816 -0
  37. data/app/assets/stylesheets/viewerjs_rails.css +15 -0
  38. data/lib/viewerjs/rails.rb +19 -0
  39. data/lib/viewerjs/rails/version.rb +5 -0
  40. data/lib/viewerjs/view_helpers.rb +75 -0
  41. data/viewerjs-rails.gemspec +23 -0
  42. metadata +113 -0
@@ -0,0 +1,175 @@
1
+ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
+ /* Copyright 2012 Mozilla Foundation
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ /* globals PDFFindController, FindStates, mozL10n */
20
+
21
+ /**
22
+ * Creates a "search bar" given set of DOM elements
23
+ * that act as controls for searching, or for setting
24
+ * search preferences in the UI. This object also sets
25
+ * up the appropriate events for the controls. Actual
26
+ * searching is done by PDFFindController
27
+ */
28
+ var PDFFindBar = {
29
+
30
+ opened: false,
31
+ bar: null,
32
+ toggleButton: null,
33
+ findField: null,
34
+ highlightAll: null,
35
+ caseSensitive: null,
36
+ findMsg: null,
37
+ findStatusIcon: null,
38
+ findPreviousButton: null,
39
+ findNextButton: null,
40
+
41
+ initialize: function(options) {
42
+ if(typeof PDFFindController === 'undefined' || PDFFindController === null) {
43
+ throw 'PDFFindBar cannot be initialized ' +
44
+ 'without a PDFFindController instance.';
45
+ }
46
+
47
+ this.bar = options.bar;
48
+ this.toggleButton = options.toggleButton;
49
+ this.findField = options.findField;
50
+ this.highlightAll = options.highlightAllCheckbox;
51
+ this.caseSensitive = options.caseSensitiveCheckbox;
52
+ this.findMsg = options.findMsg;
53
+ this.findStatusIcon = options.findStatusIcon;
54
+ this.findPreviousButton = options.findPreviousButton;
55
+ this.findNextButton = options.findNextButton;
56
+
57
+ var self = this;
58
+ this.toggleButton.addEventListener('click', function() {
59
+ self.toggle();
60
+ });
61
+
62
+ this.findField.addEventListener('input', function() {
63
+ self.dispatchEvent('');
64
+ });
65
+
66
+ this.bar.addEventListener('keydown', function(evt) {
67
+ switch (evt.keyCode) {
68
+ case 13: // Enter
69
+ if (evt.target === self.findField) {
70
+ self.dispatchEvent('again', evt.shiftKey);
71
+ }
72
+ break;
73
+ case 27: // Escape
74
+ self.close();
75
+ break;
76
+ }
77
+ });
78
+
79
+ this.findPreviousButton.addEventListener('click',
80
+ function() { self.dispatchEvent('again', true); }
81
+ );
82
+
83
+ this.findNextButton.addEventListener('click', function() {
84
+ self.dispatchEvent('again', false);
85
+ });
86
+
87
+ this.highlightAll.addEventListener('click', function() {
88
+ self.dispatchEvent('highlightallchange');
89
+ });
90
+
91
+ this.caseSensitive.addEventListener('click', function() {
92
+ self.dispatchEvent('casesensitivitychange');
93
+ });
94
+ },
95
+
96
+ dispatchEvent: function(aType, aFindPrevious) {
97
+ var event = document.createEvent('CustomEvent');
98
+ event.initCustomEvent('find' + aType, true, true, {
99
+ query: this.findField.value,
100
+ caseSensitive: this.caseSensitive.checked,
101
+ highlightAll: this.highlightAll.checked,
102
+ findPrevious: aFindPrevious
103
+ });
104
+ return window.dispatchEvent(event);
105
+ },
106
+
107
+ updateUIState: function(state, previous) {
108
+ var notFound = false;
109
+ var findMsg = '';
110
+ var status = '';
111
+
112
+ switch (state) {
113
+ case FindStates.FIND_FOUND:
114
+ break;
115
+
116
+ case FindStates.FIND_PENDING:
117
+ status = 'pending';
118
+ break;
119
+
120
+ case FindStates.FIND_NOTFOUND:
121
+ findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
122
+ notFound = true;
123
+ break;
124
+
125
+ case FindStates.FIND_WRAPPED:
126
+ if (previous) {
127
+ findMsg = mozL10n.get('find_reached_top', null,
128
+ 'Reached top of document, continued from bottom');
129
+ } else {
130
+ findMsg = mozL10n.get('find_reached_bottom', null,
131
+ 'Reached end of document, continued from top');
132
+ }
133
+ break;
134
+ }
135
+
136
+ if (notFound) {
137
+ this.findField.classList.add('notFound');
138
+ } else {
139
+ this.findField.classList.remove('notFound');
140
+ }
141
+
142
+ this.findField.setAttribute('data-status', status);
143
+ this.findMsg.textContent = findMsg;
144
+ },
145
+
146
+ open: function() {
147
+ if (!this.opened) {
148
+ this.opened = true;
149
+ this.toggleButton.classList.add('toggled');
150
+ this.bar.classList.remove('hidden');
151
+ }
152
+
153
+ this.findField.select();
154
+ this.findField.focus();
155
+ },
156
+
157
+ close: function() {
158
+ if (!this.opened) return;
159
+
160
+ this.opened = false;
161
+ this.toggleButton.classList.remove('toggled');
162
+ this.bar.classList.add('hidden');
163
+
164
+ PDFFindController.active = false;
165
+ },
166
+
167
+ toggle: function() {
168
+ if (this.opened) {
169
+ this.close();
170
+ } else {
171
+ this.open();
172
+ }
173
+ }
174
+ };
175
+
@@ -0,0 +1,355 @@
1
+ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
+ /* Copyright 2012 Mozilla Foundation
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ /* globals PDFFindBar, PDFJS, FindStates, FirefoxCom, Promise */
20
+
21
+ /**
22
+ * Provides a "search" or "find" functionality for the PDF.
23
+ * This object actually performs the search for a given string.
24
+ */
25
+
26
+ var PDFFindController = {
27
+ startedTextExtraction: false,
28
+
29
+ extractTextPromises: [],
30
+
31
+ pendingFindMatches: {},
32
+
33
+ // If active, find results will be highlighted.
34
+ active: false,
35
+
36
+ // Stores the text for each page.
37
+ pageContents: [],
38
+
39
+ pageMatches: [],
40
+
41
+ // Currently selected match.
42
+ selected: {
43
+ pageIdx: -1,
44
+ matchIdx: -1
45
+ },
46
+
47
+ // Where find algorithm currently is in the document.
48
+ offset: {
49
+ pageIdx: null,
50
+ matchIdx: null
51
+ },
52
+
53
+ resumePageIdx: null,
54
+
55
+ state: null,
56
+
57
+ dirtyMatch: false,
58
+
59
+ findTimeout: null,
60
+
61
+ pdfPageSource: null,
62
+
63
+ integratedFind: false,
64
+
65
+ initialize: function(options) {
66
+ if(typeof PDFFindBar === 'undefined' || PDFFindBar === null) {
67
+ throw 'PDFFindController cannot be initialized ' +
68
+ 'without a PDFFindController instance';
69
+ }
70
+
71
+ this.pdfPageSource = options.pdfPageSource;
72
+ this.integratedFind = options.integratedFind;
73
+
74
+ var events = [
75
+ 'find',
76
+ 'findagain',
77
+ 'findhighlightallchange',
78
+ 'findcasesensitivitychange'
79
+ ];
80
+
81
+ this.firstPagePromise = new Promise(function (resolve) {
82
+ this.resolveFirstPage = resolve;
83
+ }.bind(this));
84
+ this.handleEvent = this.handleEvent.bind(this);
85
+
86
+ for (var i = 0; i < events.length; i++) {
87
+ window.addEventListener(events[i], this.handleEvent);
88
+ }
89
+ },
90
+
91
+ reset: function pdfFindControllerReset() {
92
+ this.startedTextExtraction = false;
93
+ this.extractTextPromises = [];
94
+ this.active = false;
95
+ },
96
+
97
+ calcFindMatch: function(pageIndex) {
98
+ var pageContent = this.pageContents[pageIndex];
99
+ var query = this.state.query;
100
+ var caseSensitive = this.state.caseSensitive;
101
+ var queryLen = query.length;
102
+
103
+ if (queryLen === 0) {
104
+ // Do nothing the matches should be wiped out already.
105
+ return;
106
+ }
107
+
108
+ if (!caseSensitive) {
109
+ pageContent = pageContent.toLowerCase();
110
+ query = query.toLowerCase();
111
+ }
112
+
113
+ var matches = [];
114
+
115
+ var matchIdx = -queryLen;
116
+ while (true) {
117
+ matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
118
+ if (matchIdx === -1) {
119
+ break;
120
+ }
121
+
122
+ matches.push(matchIdx);
123
+ }
124
+ this.pageMatches[pageIndex] = matches;
125
+ this.updatePage(pageIndex);
126
+ if (this.resumePageIdx === pageIndex) {
127
+ this.resumePageIdx = null;
128
+ this.nextPageMatch();
129
+ }
130
+ },
131
+
132
+ extractText: function() {
133
+ if (this.startedTextExtraction) {
134
+ return;
135
+ }
136
+ this.startedTextExtraction = true;
137
+
138
+ this.pageContents = [];
139
+ var extractTextPromisesResolves = [];
140
+ for (var i = 0, ii = this.pdfPageSource.pdfDocument.numPages; i < ii; i++) {
141
+ this.extractTextPromises.push(new Promise(function (resolve) {
142
+ extractTextPromisesResolves.push(resolve);
143
+ }));
144
+ }
145
+
146
+ var self = this;
147
+ function extractPageText(pageIndex) {
148
+ self.pdfPageSource.pages[pageIndex].getTextContent().then(
149
+ function textContentResolved(bidiTexts) {
150
+ var str = '';
151
+
152
+ for (var i = 0; i < bidiTexts.length; i++) {
153
+ str += bidiTexts[i].str;
154
+ }
155
+
156
+ // Store the pageContent as a string.
157
+ self.pageContents.push(str);
158
+
159
+ extractTextPromisesResolves[pageIndex](pageIndex);
160
+ if ((pageIndex + 1) < self.pdfPageSource.pages.length)
161
+ extractPageText(pageIndex + 1);
162
+ }
163
+ );
164
+ }
165
+ extractPageText(0);
166
+ },
167
+
168
+ handleEvent: function(e) {
169
+ if (this.state === null || e.type !== 'findagain') {
170
+ this.dirtyMatch = true;
171
+ }
172
+ this.state = e.detail;
173
+ this.updateUIState(FindStates.FIND_PENDING);
174
+
175
+ this.firstPagePromise.then(function() {
176
+ this.extractText();
177
+
178
+ clearTimeout(this.findTimeout);
179
+ if (e.type === 'find') {
180
+ // Only trigger the find action after 250ms of silence.
181
+ this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
182
+ } else {
183
+ this.nextMatch();
184
+ }
185
+ }.bind(this));
186
+ },
187
+
188
+ updatePage: function(idx) {
189
+ var page = this.pdfPageSource.pages[idx];
190
+
191
+ if (this.selected.pageIdx === idx) {
192
+ // If the page is selected, scroll the page into view, which triggers
193
+ // rendering the page, which adds the textLayer. Once the textLayer is
194
+ // build, it will scroll onto the selected match.
195
+ page.scrollIntoView();
196
+ }
197
+
198
+ if (page.textLayer) {
199
+ page.textLayer.updateMatches();
200
+ }
201
+ },
202
+
203
+ nextMatch: function() {
204
+ var previous = this.state.findPrevious;
205
+ var currentPageIndex = this.pdfPageSource.page - 1;
206
+ var numPages = this.pdfPageSource.pages.length;
207
+
208
+ this.active = true;
209
+
210
+ if (this.dirtyMatch) {
211
+ // Need to recalculate the matches, reset everything.
212
+ this.dirtyMatch = false;
213
+ this.selected.pageIdx = this.selected.matchIdx = -1;
214
+ this.offset.pageIdx = currentPageIndex;
215
+ this.offset.matchIdx = null;
216
+ this.hadMatch = false;
217
+ this.resumePageIdx = null;
218
+ this.pageMatches = [];
219
+ var self = this;
220
+
221
+ for (var i = 0; i < numPages; i++) {
222
+ // Wipe out any previous highlighted matches.
223
+ this.updatePage(i);
224
+
225
+ // As soon as the text is extracted start finding the matches.
226
+ if (!(i in this.pendingFindMatches)) {
227
+ this.pendingFindMatches[i] = true;
228
+ this.extractTextPromises[i].then(function(pageIdx) {
229
+ delete self.pendingFindMatches[pageIdx];
230
+ self.calcFindMatch(pageIdx);
231
+ });
232
+ }
233
+ }
234
+ }
235
+
236
+ // If there's no query there's no point in searching.
237
+ if (this.state.query === '') {
238
+ this.updateUIState(FindStates.FIND_FOUND);
239
+ return;
240
+ }
241
+
242
+ // If we're waiting on a page, we return since we can't do anything else.
243
+ if (this.resumePageIdx) {
244
+ return;
245
+ }
246
+
247
+ var offset = this.offset;
248
+ // If there's already a matchIdx that means we are iterating through a
249
+ // page's matches.
250
+ if (offset.matchIdx !== null) {
251
+ var numPageMatches = this.pageMatches[offset.pageIdx].length;
252
+ if ((!previous && offset.matchIdx + 1 < numPageMatches) ||
253
+ (previous && offset.matchIdx > 0)) {
254
+ // The simple case, we just have advance the matchIdx to select the next
255
+ // match on the page.
256
+ this.hadMatch = true;
257
+ offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
258
+ this.updateMatch(true);
259
+ return;
260
+ }
261
+ // We went beyond the current page's matches, so we advance to the next
262
+ // page.
263
+ this.advanceOffsetPage(previous);
264
+ }
265
+ // Start searching through the page.
266
+ this.nextPageMatch();
267
+ },
268
+
269
+ matchesReady: function(matches) {
270
+ var offset = this.offset;
271
+ var numMatches = matches.length;
272
+ var previous = this.state.findPrevious;
273
+ if (numMatches) {
274
+ // There were matches for the page, so initialize the matchIdx.
275
+ this.hadMatch = true;
276
+ offset.matchIdx = previous ? numMatches - 1 : 0;
277
+ this.updateMatch(true);
278
+ // matches were found
279
+ return true;
280
+ } else {
281
+ // No matches attempt to search the next page.
282
+ this.advanceOffsetPage(previous);
283
+ if (offset.wrapped) {
284
+ offset.matchIdx = null;
285
+ if (!this.hadMatch) {
286
+ // No point in wrapping there were no matches.
287
+ this.updateMatch(false);
288
+ // while matches were not found, searching for a page
289
+ // with matches should nevertheless halt.
290
+ return true;
291
+ }
292
+ }
293
+ // matches were not found (and searching is not done)
294
+ return false;
295
+ }
296
+ },
297
+
298
+ nextPageMatch: function() {
299
+ if (this.resumePageIdx !== null) {
300
+ console.error('There can only be one pending page.');
301
+ }
302
+ do {
303
+ var pageIdx = this.offset.pageIdx;
304
+ var matches = this.pageMatches[pageIdx];
305
+ if (!matches) {
306
+ // The matches don't exist yet for processing by "matchesReady",
307
+ // so set a resume point for when they do exist.
308
+ this.resumePageIdx = pageIdx;
309
+ break;
310
+ }
311
+ } while (!this.matchesReady(matches));
312
+ },
313
+
314
+ advanceOffsetPage: function(previous) {
315
+ var offset = this.offset;
316
+ var numPages = this.extractTextPromises.length;
317
+ offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
318
+ offset.matchIdx = null;
319
+ if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
320
+ offset.pageIdx = previous ? numPages - 1 : 0;
321
+ offset.wrapped = true;
322
+ return;
323
+ }
324
+ },
325
+
326
+ updateMatch: function(found) {
327
+ var state = FindStates.FIND_NOTFOUND;
328
+ var wrapped = this.offset.wrapped;
329
+ this.offset.wrapped = false;
330
+ if (found) {
331
+ var previousPage = this.selected.pageIdx;
332
+ this.selected.pageIdx = this.offset.pageIdx;
333
+ this.selected.matchIdx = this.offset.matchIdx;
334
+ state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND;
335
+ // Update the currently selected page to wipe out any selected matches.
336
+ if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
337
+ this.updatePage(previousPage);
338
+ }
339
+ }
340
+ this.updateUIState(state, this.state.findPrevious);
341
+ if (this.selected.pageIdx !== -1) {
342
+ this.updatePage(this.selected.pageIdx, true);
343
+ }
344
+ },
345
+
346
+ updateUIState: function(state, previous) {
347
+ if (this.integratedFind) {
348
+ FirefoxCom.request('updateFindControlState',
349
+ {result: state, findPrevious: previous});
350
+ return;
351
+ }
352
+ PDFFindBar.updateUIState(state, previous);
353
+ }
354
+ };
355
+