This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 | /* globals CustomStyle, PDFJS */ |
||
17 | |||
18 | 'use strict'; |
||
19 | |||
20 | var MAX_TEXT_DIVS_TO_RENDER = 100000; |
||
21 | |||
22 | var NonWhitespaceRegexp = /\S/; |
||
23 | |||
24 | function isAllWhitespace(str) { |
||
25 | return !NonWhitespaceRegexp.test(str); |
||
26 | } |
||
27 | |||
28 | /** |
||
29 | * @typedef {Object} TextLayerBuilderOptions |
||
30 | * @property {HTMLDivElement} textLayerDiv - The text layer container. |
||
31 | * @property {number} pageIndex - The page index. |
||
32 | * @property {PageViewport} viewport - The viewport of the text layer. |
||
33 | * @property {PDFFindController} findController |
||
34 | */ |
||
35 | |||
36 | /** |
||
37 | * TextLayerBuilder provides text-selection functionality for the PDF. |
||
38 | * It does this by creating overlay divs over the PDF text. These divs |
||
39 | * contain text that matches the PDF text they are overlaying. This object |
||
40 | * also provides a way to highlight text that is being searched for. |
||
41 | * @class |
||
42 | */ |
||
43 | var TextLayerBuilder = (function TextLayerBuilderClosure() { |
||
44 | function TextLayerBuilder(options) { |
||
45 | this.textLayerDiv = options.textLayerDiv; |
||
46 | this.renderingDone = false; |
||
47 | this.divContentDone = false; |
||
48 | this.pageIdx = options.pageIndex; |
||
49 | this.pageNumber = this.pageIdx + 1; |
||
50 | this.matches = []; |
||
51 | this.viewport = options.viewport; |
||
52 | this.textDivs = []; |
||
53 | this.findController = options.findController || null; |
||
54 | } |
||
55 | |||
56 | TextLayerBuilder.prototype = { |
||
57 | _finishRendering: function TextLayerBuilder_finishRendering() { |
||
58 | this.renderingDone = true; |
||
59 | |||
60 | var event = document.createEvent('CustomEvent'); |
||
61 | event.initCustomEvent('textlayerrendered', true, true, { |
||
62 | pageNumber: this.pageNumber |
||
63 | }); |
||
64 | this.textLayerDiv.dispatchEvent(event); |
||
65 | }, |
||
66 | |||
67 | renderLayer: function TextLayerBuilder_renderLayer() { |
||
68 | var textLayerFrag = document.createDocumentFragment(); |
||
69 | var textDivs = this.textDivs; |
||
70 | var textDivsLength = textDivs.length; |
||
71 | var canvas = document.createElement('canvas'); |
||
72 | var ctx = canvas.getContext('2d'); |
||
73 | |||
74 | // No point in rendering many divs as it would make the browser |
||
75 | // unusable even after the divs are rendered. |
||
76 | if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) { |
||
77 | this._finishRendering(); |
||
78 | return; |
||
79 | } |
||
80 | |||
81 | var lastFontSize; |
||
82 | var lastFontFamily; |
||
83 | for (var i = 0; i < textDivsLength; i++) { |
||
84 | var textDiv = textDivs[i]; |
||
85 | if (textDiv.dataset.isWhitespace !== undefined) { |
||
86 | continue; |
||
87 | } |
||
88 | |||
89 | var fontSize = textDiv.style.fontSize; |
||
90 | var fontFamily = textDiv.style.fontFamily; |
||
91 | |||
92 | // Only build font string and set to context if different from last. |
||
93 | if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) { |
||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
94 | ctx.font = fontSize + ' ' + fontFamily; |
||
95 | lastFontSize = fontSize; |
||
96 | lastFontFamily = fontFamily; |
||
97 | } |
||
98 | |||
99 | var width = ctx.measureText(textDiv.textContent).width; |
||
100 | if (width > 0) { |
||
101 | textLayerFrag.appendChild(textDiv); |
||
102 | var transform; |
||
103 | if (textDiv.dataset.canvasWidth !== undefined) { |
||
104 | // Dataset values come of type string. |
||
105 | var textScale = textDiv.dataset.canvasWidth / width; |
||
106 | transform = 'scaleX(' + textScale + ')'; |
||
107 | } else { |
||
108 | transform = ''; |
||
109 | } |
||
110 | var rotation = textDiv.dataset.angle; |
||
111 | if (rotation) { |
||
112 | transform = 'rotate(' + rotation + 'deg) ' + transform; |
||
113 | } |
||
114 | if (transform) { |
||
115 | CustomStyle.setProp('transform' , textDiv, transform); |
||
116 | } |
||
117 | } |
||
118 | } |
||
119 | |||
120 | this.textLayerDiv.appendChild(textLayerFrag); |
||
121 | this._finishRendering(); |
||
122 | this.updateMatches(); |
||
123 | }, |
||
124 | |||
125 | /** |
||
126 | * Renders the text layer. |
||
127 | * @param {number} timeout (optional) if specified, the rendering waits |
||
128 | * for specified amount of ms. |
||
129 | */ |
||
130 | render: function TextLayerBuilder_render(timeout) { |
||
131 | if (!this.divContentDone || this.renderingDone) { |
||
132 | return; |
||
133 | } |
||
134 | |||
135 | if (this.renderTimer) { |
||
136 | clearTimeout(this.renderTimer); |
||
137 | this.renderTimer = null; |
||
138 | } |
||
139 | |||
140 | if (!timeout) { // Render right away |
||
141 | this.renderLayer(); |
||
142 | } else { // Schedule |
||
143 | var self = this; |
||
144 | this.renderTimer = setTimeout(function() { |
||
145 | self.renderLayer(); |
||
146 | self.renderTimer = null; |
||
147 | }, timeout); |
||
148 | } |
||
149 | }, |
||
150 | |||
151 | appendText: function TextLayerBuilder_appendText(geom, styles) { |
||
152 | var style = styles[geom.fontName]; |
||
153 | var textDiv = document.createElement('div'); |
||
154 | this.textDivs.push(textDiv); |
||
155 | if (isAllWhitespace(geom.str)) { |
||
156 | textDiv.dataset.isWhitespace = true; |
||
157 | return; |
||
158 | } |
||
159 | var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform); |
||
160 | var angle = Math.atan2(tx[1], tx[0]); |
||
161 | if (style.vertical) { |
||
162 | angle += Math.PI / 2; |
||
163 | } |
||
164 | var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3])); |
||
165 | var fontAscent = fontHeight; |
||
166 | if (style.ascent) { |
||
167 | fontAscent = style.ascent * fontAscent; |
||
168 | } else if (style.descent) { |
||
169 | fontAscent = (1 + style.descent) * fontAscent; |
||
170 | } |
||
171 | |||
172 | var left; |
||
173 | var top; |
||
174 | if (angle === 0) { |
||
175 | left = tx[4]; |
||
176 | top = tx[5] - fontAscent; |
||
177 | } else { |
||
178 | left = tx[4] + (fontAscent * Math.sin(angle)); |
||
179 | top = tx[5] - (fontAscent * Math.cos(angle)); |
||
180 | } |
||
181 | textDiv.style.left = left + 'px'; |
||
182 | textDiv.style.top = top + 'px'; |
||
183 | textDiv.style.fontSize = fontHeight + 'px'; |
||
184 | textDiv.style.fontFamily = style.fontFamily; |
||
185 | |||
186 | textDiv.textContent = geom.str; |
||
187 | // |fontName| is only used by the Font Inspector. This test will succeed |
||
188 | // when e.g. the Font Inspector is off but the Stepper is on, but it's |
||
189 | // not worth the effort to do a more accurate test. |
||
190 | if (PDFJS.pdfBug) { |
||
191 | textDiv.dataset.fontName = geom.fontName; |
||
192 | } |
||
193 | // Storing into dataset will convert number into string. |
||
194 | if (angle !== 0) { |
||
195 | textDiv.dataset.angle = angle * (180 / Math.PI); |
||
196 | } |
||
197 | // We don't bother scaling single-char text divs, because it has very |
||
198 | // little effect on text highlighting. This makes scrolling on docs with |
||
199 | // lots of such divs a lot faster. |
||
200 | if (textDiv.textContent.length > 1) { |
||
201 | if (style.vertical) { |
||
202 | textDiv.dataset.canvasWidth = geom.height * this.viewport.scale; |
||
203 | } else { |
||
204 | textDiv.dataset.canvasWidth = geom.width * this.viewport.scale; |
||
205 | } |
||
206 | } |
||
207 | }, |
||
208 | |||
209 | setTextContent: function TextLayerBuilder_setTextContent(textContent) { |
||
210 | this.textContent = textContent; |
||
211 | |||
212 | var textItems = textContent.items; |
||
213 | for (var i = 0, len = textItems.length; i < len; i++) { |
||
214 | this.appendText(textItems[i], textContent.styles); |
||
215 | } |
||
216 | this.divContentDone = true; |
||
217 | }, |
||
218 | |||
219 | convertMatches: function TextLayerBuilder_convertMatches(matches) { |
||
220 | var i = 0; |
||
221 | var iIndex = 0; |
||
222 | var bidiTexts = this.textContent.items; |
||
223 | var end = bidiTexts.length - 1; |
||
224 | var queryLen = (this.findController === null ? |
||
225 | 0 : this.findController.state.query.length); |
||
226 | var ret = []; |
||
227 | |||
228 | for (var m = 0, len = matches.length; m < len; m++) { |
||
229 | // Calculate the start position. |
||
230 | var matchIdx = matches[m]; |
||
231 | |||
232 | // Loop over the divIdxs. |
||
233 | while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) { |
||
234 | iIndex += bidiTexts[i].str.length; |
||
235 | i++; |
||
236 | } |
||
237 | |||
238 | if (i === bidiTexts.length) { |
||
239 | console.error('Could not find a matching mapping'); |
||
240 | } |
||
241 | |||
242 | var match = { |
||
243 | begin: { |
||
244 | divIdx: i, |
||
245 | offset: matchIdx - iIndex |
||
246 | } |
||
247 | }; |
||
248 | |||
249 | // Calculate the end position. |
||
250 | matchIdx += queryLen; |
||
251 | |||
252 | // Somewhat the same array as above, but use > instead of >= to get |
||
253 | // the end position right. |
||
254 | while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) { |
||
255 | iIndex += bidiTexts[i].str.length; |
||
256 | i++; |
||
257 | } |
||
258 | |||
259 | match.end = { |
||
260 | divIdx: i, |
||
261 | offset: matchIdx - iIndex |
||
262 | }; |
||
263 | ret.push(match); |
||
264 | } |
||
265 | |||
266 | return ret; |
||
267 | }, |
||
268 | |||
269 | renderMatches: function TextLayerBuilder_renderMatches(matches) { |
||
270 | // Early exit if there is nothing to render. |
||
271 | if (matches.length === 0) { |
||
272 | return; |
||
273 | } |
||
274 | |||
275 | var bidiTexts = this.textContent.items; |
||
276 | var textDivs = this.textDivs; |
||
277 | var prevEnd = null; |
||
278 | var pageIdx = this.pageIdx; |
||
279 | var isSelectedPage = (this.findController === null ? |
||
280 | false : (pageIdx === this.findController.selected.pageIdx)); |
||
281 | var selectedMatchIdx = (this.findController === null ? |
||
282 | -1 : this.findController.selected.matchIdx); |
||
283 | var highlightAll = (this.findController === null ? |
||
284 | false : this.findController.state.highlightAll); |
||
285 | var infinity = { |
||
286 | divIdx: -1, |
||
287 | offset: undefined |
||
288 | }; |
||
289 | |||
290 | function beginText(begin, className) { |
||
291 | var divIdx = begin.divIdx; |
||
292 | textDivs[divIdx].textContent = ''; |
||
293 | appendTextToDiv(divIdx, 0, begin.offset, className); |
||
294 | } |
||
295 | |||
296 | function appendTextToDiv(divIdx, fromOffset, toOffset, className) { |
||
297 | var div = textDivs[divIdx]; |
||
298 | var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset); |
||
299 | var node = document.createTextNode(content); |
||
300 | if (className) { |
||
301 | var span = document.createElement('span'); |
||
302 | span.className = className; |
||
303 | span.appendChild(node); |
||
304 | div.appendChild(span); |
||
305 | return; |
||
306 | } |
||
307 | div.appendChild(node); |
||
308 | } |
||
309 | |||
310 | var i0 = selectedMatchIdx, i1 = i0 + 1; |
||
311 | if (highlightAll) { |
||
312 | i0 = 0; |
||
313 | i1 = matches.length; |
||
314 | } else if (!isSelectedPage) { |
||
315 | // Not highlighting all and this isn't the selected page, so do nothing. |
||
316 | return; |
||
317 | } |
||
318 | |||
319 | for (var i = i0; i < i1; i++) { |
||
320 | var match = matches[i]; |
||
321 | var begin = match.begin; |
||
322 | var end = match.end; |
||
323 | var isSelected = (isSelectedPage && i === selectedMatchIdx); |
||
324 | var highlightSuffix = (isSelected ? ' selected' : ''); |
||
325 | |||
326 | if (this.findController) { |
||
327 | this.findController.updateMatchPosition(pageIdx, i, textDivs, |
||
328 | begin.divIdx, end.divIdx); |
||
329 | } |
||
330 | |||
331 | // Match inside new div. |
||
332 | if (!prevEnd || begin.divIdx !== prevEnd.divIdx) { |
||
333 | // If there was a previous div, then add the text at the end. |
||
334 | if (prevEnd !== null) { |
||
335 | appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset); |
||
336 | } |
||
337 | // Clear the divs and set the content until the starting point. |
||
338 | beginText(begin); |
||
339 | } else { |
||
340 | appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset); |
||
341 | } |
||
342 | |||
343 | if (begin.divIdx === end.divIdx) { |
||
344 | appendTextToDiv(begin.divIdx, begin.offset, end.offset, |
||
345 | 'highlight' + highlightSuffix); |
||
346 | } else { |
||
347 | appendTextToDiv(begin.divIdx, begin.offset, infinity.offset, |
||
348 | 'highlight begin' + highlightSuffix); |
||
349 | for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) { |
||
350 | textDivs[n0].className = 'highlight middle' + highlightSuffix; |
||
351 | } |
||
352 | beginText(end, 'highlight end' + highlightSuffix); |
||
353 | } |
||
354 | prevEnd = end; |
||
355 | } |
||
356 | |||
357 | if (prevEnd) { |
||
358 | appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset); |
||
359 | } |
||
360 | }, |
||
361 | |||
362 | updateMatches: function TextLayerBuilder_updateMatches() { |
||
363 | // Only show matches when all rendering is done. |
||
364 | if (!this.renderingDone) { |
||
365 | return; |
||
366 | } |
||
367 | |||
368 | // Clear all matches. |
||
369 | var matches = this.matches; |
||
370 | var textDivs = this.textDivs; |
||
371 | var bidiTexts = this.textContent.items; |
||
372 | var clearedUntilDivIdx = -1; |
||
373 | |||
374 | // Clear all current matches. |
||
375 | for (var i = 0, len = matches.length; i < len; i++) { |
||
376 | var match = matches[i]; |
||
377 | var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx); |
||
378 | for (var n = begin, end = match.end.divIdx; n <= end; n++) { |
||
379 | var div = textDivs[n]; |
||
380 | div.textContent = bidiTexts[n].str; |
||
381 | div.className = ''; |
||
382 | } |
||
383 | clearedUntilDivIdx = match.end.divIdx + 1; |
||
384 | } |
||
385 | |||
386 | if (this.findController === null || !this.findController.active) { |
||
387 | return; |
||
388 | } |
||
389 | |||
390 | // Convert the matches on the page controller into the match format |
||
391 | // used for the textLayer. |
||
392 | this.matches = this.convertMatches(this.findController === null ? |
||
393 | [] : (this.findController.pageMatches[this.pageIdx] || [])); |
||
394 | this.renderMatches(this.matches); |
||
395 | } |
||
396 | }; |
||
397 | return TextLayerBuilder; |
||
398 | })(); |
||
399 | |||
400 | /** |
||
401 | * @constructor |
||
402 | * @implements IPDFTextLayerFactory |
||
403 | */ |
||
404 | function DefaultTextLayerFactory() {} |
||
405 | DefaultTextLayerFactory.prototype = { |
||
406 | /** |
||
407 | * @param {HTMLDivElement} textLayerDiv |
||
408 | * @param {number} pageIndex |
||
409 | * @param {PageViewport} viewport |
||
410 | * @returns {TextLayerBuilder} |
||
411 | */ |
||
412 | createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) { |
||
413 | return new TextLayerBuilder({ |
||
414 | textLayerDiv: textLayerDiv, |
||
415 | pageIndex: pageIndex, |
||
416 | viewport: viewport |
||
417 | }); |
||
418 | } |
||
419 | }; |
||
420 |