diff --git a/common/lib/xmodule/xmodule/templates/problem/empty.yaml b/common/lib/xmodule/xmodule/templates/problem/empty.yaml
index 39c9e7671c..97a2aef423 100644
--- a/common/lib/xmodule/xmodule/templates/problem/empty.yaml
+++ b/common/lib/xmodule/xmodule/templates/problem/empty.yaml
@@ -2,11 +2,8 @@
metadata:
display_name: Blank Common Problem
rerandomize: never
- showanswer: always
+ showanswer: finished
markdown: ""
- weight: ""
- empty: True
- attempts: ""
data: |
diff --git a/common/lib/xmodule/xmodule/templates/problem/emptyadvanced.yaml b/common/lib/xmodule/xmodule/templates/problem/emptyadvanced.yaml
index bba7b3a8ac..3d696ec2fd 100644
--- a/common/lib/xmodule/xmodule/templates/problem/emptyadvanced.yaml
+++ b/common/lib/xmodule/xmodule/templates/problem/emptyadvanced.yaml
@@ -2,10 +2,7 @@
metadata:
display_name: Blank Advanced Problem
rerandomize: never
- showanswer: always
- weight: ""
- attempts: ""
- empty: True
+ showanswer: finished
data: |
diff --git a/common/lib/xmodule/xmodule/templates/problem/forumularesponse.yaml b/common/lib/xmodule/xmodule/templates/problem/forumularesponse.yaml
index b4c53a107b..0401a01c31 100644
--- a/common/lib/xmodule/xmodule/templates/problem/forumularesponse.yaml
+++ b/common/lib/xmodule/xmodule/templates/problem/forumularesponse.yaml
@@ -2,9 +2,7 @@
metadata:
display_name: Math Expression Input
rerandomize: never
- showanswer: always
- weight: ""
- attempts: ""
+ showanswer: finished
data: |
diff --git a/common/lib/xmodule/xmodule/templates/problem/imageresponse.yaml b/common/lib/xmodule/xmodule/templates/problem/imageresponse.yaml
index 3ef619d54b..ab1f22e3b2 100644
--- a/common/lib/xmodule/xmodule/templates/problem/imageresponse.yaml
+++ b/common/lib/xmodule/xmodule/templates/problem/imageresponse.yaml
@@ -2,9 +2,7 @@
metadata:
display_name: Image Mapped Input
rerandomize: never
- showanswer: always
- weight: ""
- attempts: ""
+ showanswer: finished
data: |
diff --git a/common/lib/xmodule/xmodule/templates/problem/multiplechoice.yaml b/common/lib/xmodule/xmodule/templates/problem/multiplechoice.yaml
index 3a35a35199..10d51de280 100644
--- a/common/lib/xmodule/xmodule/templates/problem/multiplechoice.yaml
+++ b/common/lib/xmodule/xmodule/templates/problem/multiplechoice.yaml
@@ -2,9 +2,7 @@
metadata:
display_name: Multiple Choice
rerandomize: never
- showanswer: always
- weight: ""
- attempts: ""
+ showanswer: finished
markdown:
"A multiple choice problem presents radio buttons for student input. Students can only select a single
option presented. Multiple Choice questions have been the subject of many areas of research due to the early
diff --git a/common/lib/xmodule/xmodule/templates/problem/numericalresponse.yaml b/common/lib/xmodule/xmodule/templates/problem/numericalresponse.yaml
index 1dc46f5f51..548fd94fab 100644
--- a/common/lib/xmodule/xmodule/templates/problem/numericalresponse.yaml
+++ b/common/lib/xmodule/xmodule/templates/problem/numericalresponse.yaml
@@ -2,9 +2,7 @@
metadata:
display_name: Numerical Input
rerandomize: never
- showanswer: always
- weight: ""
- attempts: ""
+ showanswer: finished
markdown:
"A numerical input problem accepts a line of text input from the
student, and evaluates the input for correctness based on its
diff --git a/common/lib/xmodule/xmodule/templates/problem/optionresponse.yaml b/common/lib/xmodule/xmodule/templates/problem/optionresponse.yaml
index f523c7fdc5..c2edfb1cbc 100644
--- a/common/lib/xmodule/xmodule/templates/problem/optionresponse.yaml
+++ b/common/lib/xmodule/xmodule/templates/problem/optionresponse.yaml
@@ -2,9 +2,7 @@
metadata:
display_name: Dropdown
rerandomize: never
- showanswer: always
- weight: ""
- attempts: ""
+ showanswer: finished
markdown:
"Dropdown problems give a limited set of options for students to respond with, and present those options
in a format that encourages them to search for a specific answer rather than being immediately presented
diff --git a/common/lib/xmodule/xmodule/templates/problem/string_response.yaml b/common/lib/xmodule/xmodule/templates/problem/string_response.yaml
index c018d3f6cf..64e3dc062f 100644
--- a/common/lib/xmodule/xmodule/templates/problem/string_response.yaml
+++ b/common/lib/xmodule/xmodule/templates/problem/string_response.yaml
@@ -2,9 +2,7 @@
metadata:
display_name: Text Input
rerandomize: never
- showanswer: always
- weight: ""
- attempts: ""
+ showanswer: finished
# Note, the extra newlines are needed to make the yaml parser add blank lines instead of folding
markdown:
"A text input problem accepts a line of text from the
diff --git a/common/lib/xmodule/xmodule/templates/word_cloud/default.yaml b/common/lib/xmodule/xmodule/templates/word_cloud/default.yaml
index 7f1c838ca9..53e9eeaae4 100644
--- a/common/lib/xmodule/xmodule/templates/word_cloud/default.yaml
+++ b/common/lib/xmodule/xmodule/templates/word_cloud/default.yaml
@@ -1,9 +1,5 @@
---
metadata:
display_name: Word cloud
- version: 1
- num_inputs: 5
- num_top_words: 250
- display_student_percents: True
data: {}
children: []
diff --git a/common/lib/xmodule/xmodule/tests/test_error_module.py b/common/lib/xmodule/xmodule/tests/test_error_module.py
new file mode 100644
index 0000000000..d6b6f77ae6
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/test_error_module.py
@@ -0,0 +1,51 @@
+"""
+Tests for ErrorModule and NonStaffErrorModule
+"""
+import unittest
+from xmodule.tests import test_system
+import xmodule.error_module as error_module
+
+
+class TestErrorModule(unittest.TestCase):
+ """
+ Tests for ErrorModule and ErrorDescriptor
+ """
+ def setUp(self):
+ self.system = test_system()
+ self.org = "org"
+ self.course = "course"
+ self.fake_xml = ""
+ self.broken_xml = ""
+ self.error_msg = "Error"
+
+ def test_error_module_create(self):
+ descriptor = error_module.ErrorDescriptor.from_xml(
+ self.fake_xml, self.system, self.org, self.course)
+ self.assertTrue(isinstance(descriptor, error_module.ErrorDescriptor))
+
+ def test_error_module_rendering(self):
+ descriptor = error_module.ErrorDescriptor.from_xml(
+ self.fake_xml, self.system, self.org, self.course, self.error_msg)
+ module = descriptor.xmodule(self.system)
+ rendered_html = module.get_html()
+ self.assertIn(self.error_msg, rendered_html)
+ self.assertIn(self.fake_xml, rendered_html)
+
+
+class TestNonStaffErrorModule(TestErrorModule):
+ """
+ Tests for NonStaffErrorModule and NonStaffErrorDescriptor
+ """
+
+ def test_non_staff_error_module_create(self):
+ descriptor = error_module.NonStaffErrorDescriptor.from_xml(
+ self.fake_xml, self.system, self.org, self.course)
+ self.assertTrue(isinstance(descriptor, error_module.NonStaffErrorDescriptor))
+
+ def test_non_staff_error_module_rendering(self):
+ descriptor = error_module.NonStaffErrorDescriptor.from_xml(
+ self.fake_xml, self.system, self.org, self.course)
+ module = descriptor.xmodule(self.system)
+ rendered_html = module.get_html()
+ self.assertNotIn(self.error_msg, rendered_html)
+ self.assertNotIn(self.fake_xml, rendered_html)
diff --git a/common/lib/xmodule/xmodule/tests/test_import.py b/common/lib/xmodule/xmodule/tests/test_import.py
index 5fe57892be..a75dfc8d20 100644
--- a/common/lib/xmodule/xmodule/tests/test_import.py
+++ b/common/lib/xmodule/xmodule/tests/test_import.py
@@ -460,8 +460,8 @@ class ImportTestCase(BaseCourseTestCase):
)
module = modulestore.get_instance(course.id, location)
self.assertEqual(len(module.get_children()), 0)
- self.assertEqual(module.num_inputs, '5')
- self.assertEqual(module.num_top_words, '250')
+ self.assertEqual(module.num_inputs, 5)
+ self.assertEqual(module.num_top_words, 250)
def test_cohort_config(self):
"""
diff --git a/common/lib/xmodule/xmodule/tests/test_xml_module.py b/common/lib/xmodule/xmodule/tests/test_xml_module.py
index e41bcdd73a..dd59ca2b48 100644
--- a/common/lib/xmodule/xmodule/tests/test_xml_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_xml_module.py
@@ -1,69 +1,141 @@
+# disable missing docstring
+#pylint: disable=C0111
+
from xmodule.x_module import XModuleFields
-from xblock.core import Scope, String, Object
-from xmodule.fields import Date, StringyInteger
+from xblock.core import Scope, String, Object, Boolean
+from xmodule.fields import Date, StringyInteger, StringyFloat
from xmodule.xml_module import XmlDescriptor
import unittest
-from . import test_system
+from .import test_system
from mock import Mock
+class CrazyJsonString(String):
+ def to_json(self, value):
+ return value + " JSON"
+
+
class TestFields(object):
# Will be returned by editable_metadata_fields.
- max_attempts = StringyInteger(scope=Scope.settings, default=1000)
+ max_attempts = StringyInteger(scope=Scope.settings, default=1000, values={'min': 1, 'max': 10})
# Will not be returned by editable_metadata_fields because filtered out by non_editable_metadata_fields.
due = Date(scope=Scope.settings)
# Will not be returned by editable_metadata_fields because is not Scope.settings.
student_answers = Object(scope=Scope.user_state)
# Will be returned, and can override the inherited value from XModule.
- display_name = String(scope=Scope.settings, default='local default')
+ display_name = String(scope=Scope.settings, default='local default', display_name='Local Display Name',
+ help='local help')
+ # Used for testing select type, effect of to_json method
+ string_select = CrazyJsonString(
+ scope=Scope.settings,
+ default='default value',
+ values=[{'display_name': 'first', 'value': 'value a'},
+ {'display_name': 'second', 'value': 'value b'}]
+ )
+ # Used for testing select type
+ float_select = StringyFloat(scope=Scope.settings, default=.999, values=[1.23, 0.98])
+ # Used for testing float type
+ float_non_select = StringyFloat(scope=Scope.settings, default=.999, values={'min': 0, 'step': .3})
+ # Used for testing that Booleans get mapped to select type
+ boolean_select = Boolean(scope=Scope.settings)
class EditableMetadataFieldsTest(unittest.TestCase):
-
def test_display_name_field(self):
editable_fields = self.get_xml_editable_fields({})
# Tests that the xblock fields (currently tags and name) get filtered out.
# Also tests that xml_attributes is filtered out of XmlDescriptor.
self.assertEqual(1, len(editable_fields), "Expected only 1 editable field for xml descriptor.")
- self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
- explicitly_set=False, inheritable=False, value=None, default_value=None)
+ self.assert_field_values(
+ editable_fields, 'display_name', XModuleFields.display_name,
+ explicitly_set=False, inheritable=False, value=None, default_value=None
+ )
def test_override_default(self):
# Tests that explicitly_set is correct when a value overrides the default (not inheritable).
editable_fields = self.get_xml_editable_fields({'display_name': 'foo'})
- self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
- explicitly_set=True, inheritable=False, value='foo', default_value=None)
+ self.assert_field_values(
+ editable_fields, 'display_name', XModuleFields.display_name,
+ explicitly_set=True, inheritable=False, value='foo', default_value=None
+ )
- def test_additional_field(self):
- descriptor = self.get_descriptor({'max_attempts' : '7'})
+ def test_integer_field(self):
+ descriptor = self.get_descriptor({'max_attempts': '7'})
editable_fields = descriptor.editable_metadata_fields
- self.assertEqual(2, len(editable_fields))
- self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts,
- explicitly_set=True, inheritable=False, value=7, default_value=1000)
- self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
- explicitly_set=False, inheritable=False, value='local default', default_value='local default')
+ self.assertEqual(6, len(editable_fields))
+ self.assert_field_values(
+ editable_fields, 'max_attempts', TestFields.max_attempts,
+ explicitly_set=True, inheritable=False, value=7, default_value=1000, type='Integer',
+ options=TestFields.max_attempts.values
+ )
+ self.assert_field_values(
+ editable_fields, 'display_name', TestFields.display_name,
+ explicitly_set=False, inheritable=False, value='local default', default_value='local default'
+ )
editable_fields = self.get_descriptor({}).editable_metadata_fields
- self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts,
- explicitly_set=False, inheritable=False, value=1000, default_value=1000)
+ self.assert_field_values(
+ editable_fields, 'max_attempts', TestFields.max_attempts,
+ explicitly_set=False, inheritable=False, value=1000, default_value=1000, type='Integer',
+ options=TestFields.max_attempts.values
+ )
def test_inherited_field(self):
- model_val = {'display_name' : 'inherited'}
+ model_val = {'display_name': 'inherited'}
descriptor = self.get_descriptor(model_val)
# Mimic an inherited value for display_name (inherited and inheritable are the same in this case).
descriptor._inherited_metadata = model_val
descriptor._inheritable_metadata = model_val
editable_fields = descriptor.editable_metadata_fields
- self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
- explicitly_set=False, inheritable=True, value='inherited', default_value='inherited')
+ self.assert_field_values(
+ editable_fields, 'display_name', TestFields.display_name,
+ explicitly_set=False, inheritable=True, value='inherited', default_value='inherited'
+ )
- descriptor = self.get_descriptor({'display_name' : 'explicit'})
+ descriptor = self.get_descriptor({'display_name': 'explicit'})
# Mimic the case where display_name WOULD have been inherited, except we explicitly set it.
- descriptor._inheritable_metadata = {'display_name' : 'inheritable value'}
+ descriptor._inheritable_metadata = {'display_name': 'inheritable value'}
descriptor._inherited_metadata = {}
editable_fields = descriptor.editable_metadata_fields
- self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
- explicitly_set=True, inheritable=True, value='explicit', default_value='inheritable value')
+ self.assert_field_values(
+ editable_fields, 'display_name', TestFields.display_name,
+ explicitly_set=True, inheritable=True, value='explicit', default_value='inheritable value'
+ )
+
+ def test_type_and_options(self):
+ # test_display_name_field verifies that a String field is of type "Generic".
+ # test_integer_field verifies that a StringyInteger field is of type "Integer".
+
+ descriptor = self.get_descriptor({})
+ editable_fields = descriptor.editable_metadata_fields
+
+ # Tests for select
+ self.assert_field_values(
+ editable_fields, 'string_select', TestFields.string_select,
+ explicitly_set=False, inheritable=False, value='default value', default_value='default value',
+ type='Select', options=[{'display_name': 'first', 'value': 'value a JSON'},
+ {'display_name': 'second', 'value': 'value b JSON'}]
+ )
+
+ self.assert_field_values(
+ editable_fields, 'float_select', TestFields.float_select,
+ explicitly_set=False, inheritable=False, value=.999, default_value=.999,
+ type='Select', options=[1.23, 0.98]
+ )
+
+ self.assert_field_values(
+ editable_fields, 'boolean_select', TestFields.boolean_select,
+ explicitly_set=False, inheritable=False, value=None, default_value=None,
+ type='Select', options=[{'display_name': "True", "value": True}, {'display_name': "False", "value": False}]
+ )
+
+ # Test for float
+ self.assert_field_values(
+ editable_fields, 'float_non_select', TestFields.float_non_select,
+ explicitly_set=False, inheritable=False, value=.999, default_value=.999,
+ type='Float', options={'min': 0, 'step': .3}
+ )
+
# Start of helper methods
def get_xml_editable_fields(self, model_data):
@@ -73,7 +145,6 @@ class EditableMetadataFieldsTest(unittest.TestCase):
def get_descriptor(self, model_data):
class TestModuleDescriptor(TestFields, XmlDescriptor):
-
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(TestModuleDescriptor, self).non_editable_metadata_fields
@@ -84,10 +155,19 @@ class EditableMetadataFieldsTest(unittest.TestCase):
system.render_template = Mock(return_value="Test Template HTML
")
return TestModuleDescriptor(system=system, location=None, model_data=model_data)
- def assert_field_values(self, editable_fields, name, field, explicitly_set, inheritable, value, default_value):
+ def assert_field_values(self, editable_fields, name, field, explicitly_set, inheritable, value, default_value,
+ type='Generic', options=[]):
test_field = editable_fields[name]
- self.assertEqual(field, test_field['field'])
+
+ self.assertEqual(field.name, test_field['field_name'])
+ self.assertEqual(field.display_name, test_field['display_name'])
+ self.assertEqual(field.help, test_field['help'])
+
+ self.assertEqual(field.to_json(value), test_field['value'])
+ self.assertEqual(field.to_json(default_value), test_field['default_value'])
+
+ self.assertEqual(options, test_field['options'])
+ self.assertEqual(type, test_field['type'])
+
self.assertEqual(explicitly_set, test_field['explicitly_set'])
self.assertEqual(inheritable, test_field['inheritable'])
- self.assertEqual(value, test_field['value'])
- self.assertEqual(default_value, test_field['default_value'])
diff --git a/common/lib/xmodule/xmodule/word_cloud_module.py b/common/lib/xmodule/xmodule/word_cloud_module.py
index 440da8b887..e38b8cf195 100644
--- a/common/lib/xmodule/xmodule/word_cloud_module.py
+++ b/common/lib/xmodule/xmodule/word_cloud_module.py
@@ -2,7 +2,7 @@
generate and view word cloud.
On the client side we show:
-If student does not yet anwered - `num_inputs` numbers of text inputs.
+If student does not yet answered - `num_inputs` numbers of text inputs.
If student have answered - words he entered and cloud.
"""
@@ -14,7 +14,8 @@ from xmodule.raw_module import RawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xmodule.x_module import XModule
-from xblock.core import Scope, String, Object, Boolean, List, Integer
+from xblock.core import Scope, Object, Boolean, List
+from fields import StringyBoolean, StringyInteger
log = logging.getLogger(__name__)
@@ -31,22 +32,23 @@ def pretty_bool(value):
class WordCloudFields(object):
"""XFields for word cloud."""
- display_name = String(
- help="Display name for this module",
- scope=Scope.settings
- )
- num_inputs = Integer(
- help="Number of inputs.",
+ num_inputs = StringyInteger(
+ display_name="Inputs",
+ help="Number of text boxes available for students to input words/sentences.",
scope=Scope.settings,
- default=5
+ default=5,
+ values={"min": 1}
)
- num_top_words = Integer(
- help="Number of max words, which will be displayed.",
+ num_top_words = StringyInteger(
+ display_name="Maximum Words",
+ help="Maximum number of words to be displayed in generated word cloud.",
scope=Scope.settings,
- default=250
+ default=250,
+ values={"min": 1}
)
- display_student_percents = Boolean(
- help="Display usage percents for each word?",
+ display_student_percents = StringyBoolean(
+ display_name="Show Percents",
+ help="Statistics are shown for entered words near that word.",
scope=Scope.settings,
default=True
)
@@ -205,7 +207,7 @@ class WordCloudModule(WordCloudFields, XModule):
# Update top_words.
self.top_words = self.top_dict(
temp_all_words,
- int(self.num_top_words)
+ self.num_top_words
)
# Save all_words in database.
@@ -226,7 +228,7 @@ class WordCloudModule(WordCloudFields, XModule):
'element_id': self.location.html_id(),
'element_class': self.location.category,
'ajax_url': self.system.ajax_url,
- 'num_inputs': int(self.num_inputs),
+ 'num_inputs': self.num_inputs,
'submitted': self.submitted
}
self.content = self.system.render_template('word_cloud.html', context)
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index 76ac6a1ff6..3ae70543cb 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -1,4 +1,5 @@
import logging
+import copy
import yaml
import os
@@ -9,7 +10,7 @@ from pkg_resources import resource_listdir, resource_string, resource_isdir
from xmodule.modulestore import Location
from xmodule.modulestore.exceptions import ItemNotFoundError
-from xblock.core import XBlock, Scope, String
+from xblock.core import XBlock, Scope, String, Integer, Float
log = logging.getLogger(__name__)
@@ -75,12 +76,13 @@ class HTMLSnippet(object):
"""
raise NotImplementedError(
"get_html() must be provided by specific modules - not present in {0}"
- .format(self.__class__))
+ .format(self.__class__))
class XModuleFields(object):
display_name = String(
- help="Display name for this module",
+ display_name="Display Name",
+ help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings,
default=None
)
@@ -356,7 +358,7 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
metadata_translations = {
'slug': 'url_name',
'name': 'display_name',
- }
+ }
# ============================= STRUCTURAL MANIPULATION ===================
def __init__(self,
@@ -458,7 +460,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
"""
return False
-
# ================================= JSON PARSING ===========================
@staticmethod
def load_from_json(json_data, system, default_class=None):
@@ -523,10 +524,10 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
# ================================= XML PARSING ============================
@staticmethod
def load_from_xml(xml_data,
- system,
- org=None,
- course=None,
- default_class=None):
+ system,
+ org=None,
+ course=None,
+ default_class=None):
"""
This method instantiates the correct subclass of XModuleDescriptor based
on the contents of xml_data.
@@ -541,7 +542,7 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
class_ = XModuleDescriptor.load_class(
etree.fromstring(xml_data).tag,
default_class
- )
+ )
# leave next line, commented out - useful for low-level debugging
# log.debug('[XModuleDescriptor.load_from_xml] tag=%s, class_=%s' % (
# etree.fromstring(xml_data).tag,class_))
@@ -625,7 +626,7 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
"""
inherited_metadata = getattr(self, '_inherited_metadata', {})
inheritable_metadata = getattr(self, '_inheritable_metadata', {})
- metadata = {}
+ metadata_fields = {}
for field in self.fields:
if field.scope != Scope.settings or field in self.non_editable_metadata_fields:
@@ -641,13 +642,39 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
if field.name in inherited_metadata:
explicitly_set = False
- metadata[field.name] = {'field': field,
- 'value': value,
- 'default_value': default_value,
- 'inheritable': inheritable,
- 'explicitly_set': explicitly_set }
+ # We support the following editors:
+ # 1. A select editor for fields with a list of possible values (includes Booleans).
+ # 2. Number editors for integers and floats.
+ # 3. A generic string editor for anything else (editing JSON representation of the value).
+ type = "Generic"
+ values = [] if field.values is None else copy.deepcopy(field.values)
+ if isinstance(values, tuple):
+ values = list(values)
+ if isinstance(values, list):
+ if len(values) > 0:
+ type = "Select"
+ for index, choice in enumerate(values):
+ json_choice = copy.deepcopy(choice)
+ if isinstance(json_choice, dict) and 'value' in json_choice:
+ json_choice['value'] = field.to_json(json_choice['value'])
+ else:
+ json_choice = field.to_json(json_choice)
+ values[index] = json_choice
+ elif isinstance(field, Integer):
+ type = "Integer"
+ elif isinstance(field, Float):
+ type = "Float"
+ metadata_fields[field.name] = {'field_name': field.name,
+ 'type': type,
+ 'display_name': field.display_name,
+ 'value': field.to_json(value),
+ 'options': values,
+ 'default_value': field.to_json(default_value),
+ 'inheritable': inheritable,
+ 'explicitly_set': explicitly_set,
+ 'help': field.help}
- return metadata
+ return metadata_fields
class DescriptorSystem(object):
@@ -740,7 +767,7 @@ class ModuleSystem(object):
s3_interface=None,
cache=None,
can_execute_unsafe_code=None,
- ):
+ ):
'''
Create a closure around the system environment.
diff --git a/common/static/css/vendor/html5-input-polyfills/number-polyfill.css b/common/static/css/vendor/html5-input-polyfills/number-polyfill.css
new file mode 100644
index 0000000000..f3d8805739
--- /dev/null
+++ b/common/static/css/vendor/html5-input-polyfills/number-polyfill.css
@@ -0,0 +1,87 @@
+/* HTML5 Number polyfill | Jonathan Stipe | https://github.com/jonstipe/number-polyfill*/
+div.number-spin-btn-container {
+ display: inline-block;
+ position: absolute;
+ vertical-align: middle;
+ margin: 0 0 0 3px;
+ padding: 0;
+ left: 74%;
+ top: 6px;
+}
+
+div.number-spin-btn {
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+ border-width: 2px;
+ border-color: #ededed #777777 #777777 #ededed;
+ border-style: solid;
+ background-color: #eeeeee;
+ width: 1em;
+ font-size: 14px; }
+ div.number-spin-btn:hover {
+ /* added blue hover color */
+ background-color: rgb(85, 151, 221);
+ cursor: pointer; }
+ div.number-spin-btn:active {
+ border-width: 2px;
+ border-color: #5e5e5e #d8d8d8 #d8d8d8 #5e5e5e;
+ border-style: solid;
+ background-color: #999999; }
+
+div.number-spin-btn-up {
+ border-bottom-width: 1px;
+ -moz-border-radius: 0px;
+ -webkit-border-radius: 0px;
+ border-radius: 0px;
+ font-size: 14px; }
+ div.number-spin-btn-up:before {
+ border-width: 0 0.3em 0.3em 0.3em;
+ border-color: transparent transparent black transparent;
+ top: 25%; }
+ div.number-spin-btn-up:active {
+ border-bottom-width: 1px; }
+ div.number-spin-btn-up:active:before {
+ border-bottom-color: white;
+ top: 26%;
+ left: 51%; }
+
+div.number-spin-btn-down {
+ border-top-width: 1px;
+ -moz-border-radius: 0px 0px 3px 3px;
+ -webkit-border-radius: 0px 0px 3px 3px;
+ border-radius: 0px 0px 3px 3px; }
+ div.number-spin-btn-down:before {
+ border-width: 0.3em 0.3em 0 0.3em;
+ border-color: black transparent transparent transparent;
+ top: 75%; }
+ div.number-spin-btn-down:active {
+ border-top-width: 1px; }
+ div.number-spin-btn-down:active:before {
+ border-top-color: white;
+ top: 76%;
+ left: 51%; }
+
+div.number-spin-btn-up:before,
+div.number-spin-btn-down:before {
+ content: "";
+ width: 0;
+ height: 0;
+ border-style: solid;
+ position: absolute;
+ left: 50%;
+ margin: -0.15em 0 0 -0.3em;
+ padding: 0; }
+
+input:disabled + div.number-spin-btn-container > div.number-spin-btn-up:active, input:disabled + div.number-spin-btn-container > div.number-spin-btn-down:active {
+ border-color: #ededed #777777 #777777 #ededed;
+ border-style: solid;
+ background-color: #cccccc; }
+input:disabled + div.number-spin-btn-container > div.number-spin-btn-up:before, input:disabled + div.number-spin-btn-container > div.number-spin-btn-up:active:before {
+ border-bottom-color: #999999;
+ top: 25%;
+ left: 50%; }
+input:disabled + div.number-spin-btn-container > div.number-spin-btn-down:before, input:disabled + div.number-spin-btn-container > div.number-spin-btn-down:active:before {
+ border-top-color: #999999;
+ top: 75%;
+ left: 50%; }
diff --git a/common/static/js/vendor/html5-input-polyfills/number-polyfill.js b/common/static/js/vendor/html5-input-polyfills/number-polyfill.js
new file mode 100644
index 0000000000..9e95972c00
--- /dev/null
+++ b/common/static/js/vendor/html5-input-polyfills/number-polyfill.js
@@ -0,0 +1,298 @@
+// Generated by CoffeeScript 1.4.0
+
+/*
+HTML5 Number polyfill | Jonathan Stipe | https://github.com/jonstipe/number-polyfill
+*/
+
+(function() {
+
+ (function($) {
+ var i;
+ i = document.createElement("input");
+ i.setAttribute("type", "number");
+ if (i.type === "text") {
+ $.fn.inputNumber = function() {
+ var clipValues, decrement, domMouseScrollHandler, extractNumDecimalDigits, getParams, increment, matchStep, mouseWheelHandler;
+ getParams = function(elem) {
+ var $elem, max, min, step, val;
+ $elem = $(elem);
+ step = $elem.attr('step');
+ min = $elem.attr('min');
+ max = $elem.attr('max');
+ val = parseFloat($elem.val());
+ step = /^-?\d+(?:\.\d+)?$/.test(step) ? parseFloat(step) : null;
+ min = /^-?\d+(?:\.\d+)?$/.test(min) ? parseFloat(min) : null;
+ max = /^-?\d+(?:\.\d+)?$/.test(max) ? parseFloat(max) : null;
+ if (isNaN(val)) {
+ val = min || 0;
+ }
+ return {
+ min: min,
+ max: max,
+ step: step,
+ val: val
+ };
+ };
+ clipValues = function(value, min, max) {
+ if ((max != null) && value > max) {
+ return max;
+ } else if ((min != null) && value < min) {
+ return min;
+ } else {
+ return value;
+ }
+ };
+ extractNumDecimalDigits = function(input) {
+ var num, raisedNum;
+ if (input != null) {
+ num = 0;
+ raisedNum = input;
+ while (raisedNum !== Math.round(raisedNum)) {
+ num += 1;
+ raisedNum = input * Math.pow(10, num);
+ }
+ return num;
+ } else {
+ return 0;
+ }
+ };
+ matchStep = function(value, min, max, step) {
+ var mod, raiseTo, raisedMod, raisedStep, raisedStepDown, raisedStepUp, raisedValue, stepDecimalDigits, stepDown, stepUp;
+ stepDecimalDigits = extractNumDecimalDigits(step);
+ if (step == null) {
+ return value;
+ } else if (stepDecimalDigits === 0) {
+ mod = (value - (min || 0)) % step;
+ if (mod === 0) {
+ return value;
+ } else {
+ stepDown = value - mod;
+ stepUp = stepDown + step;
+ if ((stepUp > max) || ((value - stepDown) < (stepUp - value))) {
+ return stepDown;
+ } else {
+ return stepUp;
+ }
+ }
+ } else {
+ raiseTo = Math.pow(10, stepDecimalDigits);
+ raisedStep = step * raiseTo;
+ raisedMod = (value - (min || 0)) * raiseTo % raisedStep;
+ if (raisedMod === 0) {
+ return value;
+ } else {
+ raisedValue = value * raiseTo;
+ raisedStepDown = raisedValue - raisedMod;
+ raisedStepUp = raisedStepDown + raisedStep;
+ if (((raisedStepUp / raiseTo) > max) || ((raisedValue - raisedStepDown) < (raisedStepUp - raisedValue))) {
+ return raisedStepDown / raiseTo;
+ } else {
+ return raisedStepUp / raiseTo;
+ }
+ }
+ }
+ };
+ increment = function(elem) {
+ var newVal, params, raiseTo;
+ if (!$(elem).is(":disabled")) {
+ params = getParams(elem);
+ raiseTo = Math.pow(10, Math.max(extractNumDecimalDigits(params['val']), extractNumDecimalDigits(params['step'])));
+ newVal = (Math.round(params['val'] * raiseTo) + Math.round((params['step'] || 1) * raiseTo)) / raiseTo;
+ if ((params['max'] != null) && newVal > params['max']) {
+ newVal = params['max'];
+ }
+ newVal = matchStep(newVal, params['min'], params['max'], params['step']);
+ $(elem).val(newVal).change();
+ }
+ return null;
+ };
+ decrement = function(elem) {
+ var newVal, params, raiseTo;
+ if (!$(elem).is(":disabled")) {
+ params = getParams(elem);
+ raiseTo = Math.pow(10, Math.max(extractNumDecimalDigits(params['val']), extractNumDecimalDigits(params['step'])));
+ newVal = (Math.round(params['val'] * raiseTo) - Math.round((params['step'] || 1) * raiseTo)) / raiseTo;
+ if ((params['min'] != null) && newVal < params['min']) {
+ newVal = params['min'];
+ }
+ newVal = matchStep(newVal, params['min'], params['max'], params['step']);
+ $(elem).val(newVal).change();
+ }
+ return null;
+ };
+ domMouseScrollHandler = function(e) {
+ e.preventDefault();
+ if (e.originalEvent.detail < 0) {
+ increment(this);
+ } else {
+ decrement(this);
+ }
+ return null;
+ };
+ mouseWheelHandler = function(e) {
+ e.preventDefault();
+ if (e.originalEvent.wheelDelta > 0) {
+ increment(this);
+ } else {
+ decrement(this);
+ }
+ return null;
+ };
+ $(this).filter('input[type="number"]').each(function() {
+ var $downBtn, $elem, $upBtn, attrMutationCallback, attrObserver, btnContainer, downBtn, elem, halfHeight, upBtn;
+ elem = this;
+ $elem = $(elem);
+ halfHeight = ($elem.outerHeight() / 6) + 'px';
+ upBtn = document.createElement('div');
+ downBtn = document.createElement('div');
+ $upBtn = $(upBtn);
+ $downBtn = $(downBtn);
+ btnContainer = document.createElement('div');
+ $upBtn.addClass('number-spin-btn number-spin-btn-up').css('height', halfHeight);
+ $downBtn.addClass('number-spin-btn number-spin-btn-down').css('height', halfHeight);
+ btnContainer.appendChild(upBtn);
+ btnContainer.appendChild(downBtn);
+ $(btnContainer).addClass('number-spin-btn-container').insertAfter(elem);
+ $elem.on({
+ focus: function(e) {
+ $elem.on({
+ DOMMouseScroll: domMouseScrollHandler,
+ mousewheel: mouseWheelHandler
+ });
+ return null;
+ },
+ blur: function(e) {
+ $elem.off({
+ DOMMouseScroll: domMouseScrollHandler,
+ mousewheel: mouseWheelHandler
+ });
+ return null;
+ },
+ keypress: function(e) {
+ var _ref, _ref1;
+ if (e.keyCode === 38) {
+ increment(this);
+ } else if (e.keyCode === 40) {
+ decrement(this);
+ } else if (((_ref = e.keyCode) !== 8 && _ref !== 9 && _ref !== 35 && _ref !== 36 && _ref !== 37 && _ref !== 39) && ((_ref1 = e.which) !== 45 && _ref1 !== 46 && _ref1 !== 48 && _ref1 !== 49 && _ref1 !== 50 && _ref1 !== 51 && _ref1 !== 52 && _ref1 !== 53 && _ref1 !== 54 && _ref1 !== 55 && _ref1 !== 56 && _ref1 !== 57)) {
+ e.preventDefault();
+ }
+ return null;
+ },
+ change: function(e) {
+ var newVal, params;
+ if (e.originalEvent != null) {
+ params = getParams(this);
+ newVal = clipValues(params['val'], params['min'], params['max']);
+ newVal = matchStep(newVal, params['min'], params['max'], params['step'], params['stepDecimal']);
+ $(this).val(newVal);
+ }
+ return null;
+ }
+ });
+ $upBtn.on("mousedown", function(e) {
+ var releaseFunc, timeoutFunc;
+ increment(elem);
+ timeoutFunc = function(elem, incFunc) {
+ incFunc(elem);
+ $elem.data("timeoutID", window.setTimeout(timeoutFunc, 10, elem, incFunc));
+ return null;
+ };
+ releaseFunc = function(e) {
+ window.clearTimeout($elem.data("timeoutID"));
+ $(document).off('mouseup', releaseFunc);
+ $upBtn.off('mouseleave', releaseFunc);
+ return null;
+ };
+ $(document).on('mouseup', releaseFunc);
+ $upBtn.on('mouseleave', releaseFunc);
+ $elem.data("timeoutID", window.setTimeout(timeoutFunc, 700, elem, increment));
+ return null;
+ });
+ $downBtn.on("mousedown", function(e) {
+ var releaseFunc, timeoutFunc;
+ decrement(elem);
+ timeoutFunc = function(elem, decFunc) {
+ decFunc(elem);
+ $elem.data("timeoutID", window.setTimeout(timeoutFunc, 10, elem, decFunc));
+ return null;
+ };
+ releaseFunc = function(e) {
+ window.clearTimeout($elem.data("timeoutID"));
+ $(document).off('mouseup', releaseFunc);
+ $downBtn.off('mouseleave', releaseFunc);
+ return null;
+ };
+ $(document).on('mouseup', releaseFunc);
+ $downBtn.on('mouseleave', releaseFunc);
+ $elem.data("timeoutID", window.setTimeout(timeoutFunc, 700, elem, decrement));
+ return null;
+ });
+ $elem.css("textAlign", 'left');
+ if ($elem.css("opacity") !== "1") {
+ $(btnContainer).css("opacity", $elem.css("opacity"));
+ }
+ if ($elem.css("visibility") !== "visible") {
+ $(btnContainer).css("visibility", $elem.css("visibility"));
+ }
+ if (elem.style.display !== "") {
+ $(btnContainer).css("display", $elem.css("display"));
+ }
+ if ((typeof WebKitMutationObserver !== "undefined" && WebKitMutationObserver !== null) || (typeof MutationObserver !== "undefined" && MutationObserver !== null)) {
+ attrMutationCallback = function(mutations, observer) {
+ var mutation, _i, _len;
+ for (_i = 0, _len = mutations.length; _i < _len; _i++) {
+ mutation = mutations[_i];
+ if (mutation.type === "attributes") {
+ if (mutation.attributeName === "class") {
+ $(btnContainer).removeClass(mutation.oldValue).addClass(elem.className);
+ } else if (mutation.attributeName === "style") {
+ $(btnContainer).css({
+ "opacity": elem.style.opacity,
+ "visibility": elem.style.visibility,
+ "display": elem.style.display
+ });
+ }
+ }
+ }
+ return null;
+ };
+ attrObserver = (typeof WebKitMutationObserver !== "undefined" && WebKitMutationObserver !== null) ? new WebKitMutationObserver(attrMutationCallback) : ((typeof MutationObserver !== "undefined" && MutationObserver !== null) ? new MutationObserver(attrMutationCallback) : null);
+ attrObserver.observe(elem, {
+ attributes: true,
+ attributeOldValue: true,
+ attributeFilter: ["class", "style"]
+ });
+ } else if (typeof MutationEvent !== "undefined" && MutationEvent !== null) {
+ $elem.on("DOMAttrModified", function(evt) {
+ if (evt.originalEvent.attrName === "class") {
+ $(btnContainer).removeClass(evt.originalEvent.prevValue).addClass(evt.originalEvent.newValue);
+ } else if (evt.originalEvent.attrName === "style") {
+ $(btnContainer).css({
+ "display": elem.style.display,
+ "visibility": elem.style.visibility,
+ "opacity": elem.style.opacity
+ });
+ }
+ return null;
+ });
+ }
+ return null;
+ });
+ return $(this);
+ };
+ $(function() {
+ $('input[type="number"]').inputNumber();
+ return null;
+ });
+ null;
+ } else {
+ $.fn.inputNumber = function() {
+ return $(this);
+ };
+ null;
+ }
+ return null;
+ })(jQuery);
+
+}).call(this);
diff --git a/common/static/js/vendor/pdfjs/pdf.js b/common/static/js/vendor/pdfjs/pdf.js
new file mode 100644
index 0000000000..34d1996e44
--- /dev/null
+++ b/common/static/js/vendor/pdfjs/pdf.js
@@ -0,0 +1,38067 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* Copyright 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var PDFJS = {};
+PDFJS.version = '0.7.234';
+PDFJS.build = '4247339';
+
+(function pdfjsWrapper() {
+ // Use strict in our context only - users might not want it
+ 'use strict';
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* Copyright 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* globals assertWellFormed, calculateMD5, Catalog, error, info, isArray,
+ isArrayBuffer, isDict, isName, isStream, isString, Lexer,
+ Linearization, NullStream, PartialEvaluator, shadow, Stream,
+ StreamsSequenceStream, stringToPDFString, TODO, Util, warn, XRef */
+
+'use strict';
+
+var globalScope = (typeof window === 'undefined') ? this : window;
+
+var isWorker = (typeof window == 'undefined');
+
+var ERRORS = 0, WARNINGS = 1, INFOS = 5;
+var verbosity = WARNINGS;
+
+// The global PDFJS object exposes the API
+// In production, it will be declared outside a global wrapper
+// In development, it will be declared here
+if (!globalScope.PDFJS) {
+ globalScope.PDFJS = {};
+}
+
+// getPdf()
+// Convenience function to perform binary Ajax GET
+// Usage: getPdf('http://...', callback)
+// getPdf({
+// url:String ,
+// [,progress:Function, error:Function]
+// },
+// callback)
+function getPdf(arg, callback) {
+ var params = arg;
+ if (typeof arg === 'string')
+ params = { url: arg };
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', params.url);
+
+ var headers = params.headers;
+ if (headers) {
+ for (var property in headers) {
+ if (typeof headers[property] === 'undefined')
+ continue;
+
+ xhr.setRequestHeader(property, params.headers[property]);
+ }
+ }
+
+ xhr.mozResponseType = xhr.responseType = 'arraybuffer';
+
+ var protocol = params.url.substring(0, params.url.indexOf(':') + 1);
+ xhr.expected = (protocol === 'http:' || protocol === 'https:') ? 200 : 0;
+
+ if ('progress' in params)
+ xhr.onprogress = params.progress || undefined;
+
+ var calledErrorBack = false;
+
+ if ('error' in params) {
+ xhr.onerror = function errorBack() {
+ if (!calledErrorBack) {
+ calledErrorBack = true;
+ params.error();
+ }
+ };
+ }
+
+ xhr.onreadystatechange = function getPdfOnreadystatechange(e) {
+ if (xhr.readyState === 4) {
+ if (xhr.status === xhr.expected) {
+ var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
+ xhr.responseArrayBuffer || xhr.response);
+ callback(data);
+ } else if (params.error && !calledErrorBack) {
+ calledErrorBack = true;
+ params.error(e);
+ }
+ }
+ };
+ xhr.send(null);
+}
+globalScope.PDFJS.getPdf = getPdf;
+globalScope.PDFJS.pdfBug = false;
+
+var Page = (function PageClosure() {
+ function Page(xref, pageIndex, pageDict, ref) {
+ this.pageIndex = pageIndex;
+ this.pageDict = pageDict;
+ this.xref = xref;
+ this.ref = ref;
+
+ this.displayReadyPromise = null;
+ }
+
+ Page.prototype = {
+ getPageProp: function Page_getPageProp(key) {
+ return this.pageDict.get(key);
+ },
+ inheritPageProp: function Page_inheritPageProp(key) {
+ var dict = this.pageDict;
+ var obj = dict.get(key);
+ while (obj === undefined) {
+ dict = dict.get('Parent');
+ if (!dict)
+ break;
+ obj = dict.get(key);
+ }
+ return obj;
+ },
+ get content() {
+ return shadow(this, 'content', this.getPageProp('Contents'));
+ },
+ get resources() {
+ return shadow(this, 'resources', this.inheritPageProp('Resources'));
+ },
+ get mediaBox() {
+ var obj = this.inheritPageProp('MediaBox');
+ // Reset invalid media box to letter size.
+ if (!isArray(obj) || obj.length !== 4)
+ obj = [0, 0, 612, 792];
+ return shadow(this, 'mediaBox', obj);
+ },
+ get view() {
+ var mediaBox = this.mediaBox;
+ var cropBox = this.inheritPageProp('CropBox');
+ if (!isArray(cropBox) || cropBox.length !== 4)
+ return shadow(this, 'view', mediaBox);
+
+ // From the spec, 6th ed., p.963:
+ // "The crop, bleed, trim, and art boxes should not ordinarily
+ // extend beyond the boundaries of the media box. If they do, they are
+ // effectively reduced to their intersection with the media box."
+ cropBox = Util.intersect(cropBox, mediaBox);
+ if (!cropBox)
+ return shadow(this, 'view', mediaBox);
+
+ return shadow(this, 'view', cropBox);
+ },
+ get annotations() {
+ return shadow(this, 'annotations', this.inheritPageProp('Annots'));
+ },
+ get rotate() {
+ var rotate = this.inheritPageProp('Rotate') || 0;
+ // Normalize rotation so it's a multiple of 90 and between 0 and 270
+ if (rotate % 90 !== 0) {
+ rotate = 0;
+ } else if (rotate >= 360) {
+ rotate = rotate % 360;
+ } else if (rotate < 0) {
+ // The spec doesn't cover negatives, assume its counterclockwise
+ // rotation. The following is the other implementation of modulo.
+ rotate = ((rotate % 360) + 360) % 360;
+ }
+ return shadow(this, 'rotate', rotate);
+ },
+ getContentStream: function Page_getContentStream() {
+ var content = this.content;
+ if (isArray(content)) {
+ // fetching items
+ var xref = this.xref;
+ var i, n = content.length;
+ var streams = [];
+ for (i = 0; i < n; ++i)
+ streams.push(xref.fetchIfRef(content[i]));
+ content = new StreamsSequenceStream(streams);
+ } else if (isStream(content)) {
+ content.reset();
+ } else if (!content) {
+ // replacing non-existent page content with empty one
+ content = new NullStream();
+ }
+ return content;
+ },
+ getOperatorList: function Page_getOperatorList(handler, dependency) {
+ var xref = this.xref;
+ var contentStream = this.getContentStream();
+ var resources = this.resources;
+ var pe = this.pe = new PartialEvaluator(
+ xref, handler, this.pageIndex,
+ 'p' + this.pageIndex + '_');
+
+ var list = pe.getOperatorList(contentStream, resources, dependency);
+ pe.optimizeQueue(list);
+ return list;
+ },
+ extractTextContent: function Page_extractTextContent() {
+ var handler = {
+ on: function nullHandlerOn() {},
+ send: function nullHandlerSend() {}
+ };
+
+ var xref = this.xref;
+ var contentStream = this.getContentStream();
+ var resources = xref.fetchIfRef(this.resources);
+
+ var pe = new PartialEvaluator(
+ xref, handler, this.pageIndex,
+ 'p' + this.pageIndex + '_');
+ return pe.getTextContent(contentStream, resources);
+ },
+ getLinks: function Page_getLinks() {
+ var links = [];
+ var annotations = this.getAnnotations();
+ var i, n = annotations.length;
+ for (i = 0; i < n; ++i) {
+ if (annotations[i].type != 'Link')
+ continue;
+ links.push(annotations[i]);
+ }
+ return links;
+ },
+ getAnnotations: function Page_getAnnotations() {
+ var xref = this.xref;
+ function getInheritableProperty(annotation, name) {
+ var item = annotation;
+ while (item && !item.has(name)) {
+ item = item.get('Parent');
+ }
+ if (!item)
+ return null;
+ return item.get(name);
+ }
+ function isValidUrl(url) {
+ if (!url)
+ return false;
+ var colon = url.indexOf(':');
+ if (colon < 0)
+ return false;
+ var protocol = url.substr(0, colon);
+ switch (protocol) {
+ case 'http':
+ case 'https':
+ case 'ftp':
+ case 'mailto':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ var annotations = this.annotations || [];
+ var i, n = annotations.length;
+ var items = [];
+ for (i = 0; i < n; ++i) {
+ var annotationRef = annotations[i];
+ var annotation = xref.fetch(annotationRef);
+ if (!isDict(annotation))
+ continue;
+ var subtype = annotation.get('Subtype');
+ if (!isName(subtype))
+ continue;
+ var rect = annotation.get('Rect');
+
+ var item = {};
+ item.type = subtype.name;
+ item.rect = rect;
+ switch (subtype.name) {
+ case 'Link':
+ var a = annotation.get('A');
+ if (a) {
+ switch (a.get('S').name) {
+ case 'URI':
+ var url = a.get('URI');
+ // TODO: pdf spec mentions urls can be relative to a Base
+ // entry in the dictionary.
+ if (!isValidUrl(url))
+ url = '';
+ item.url = url;
+ break;
+ case 'GoTo':
+ item.dest = a.get('D');
+ break;
+ case 'GoToR':
+ var url = a.get('F');
+ if (isDict(url)) {
+ // We assume that the 'url' is a Filspec dictionary
+ // and fetch the url without checking any further
+ url = url.get('F') || '';
+ }
+
+ // TODO: pdf reference says that GoToR
+ // can also have 'NewWindow' attribute
+ if (!isValidUrl(url))
+ url = '';
+ item.url = url;
+ item.dest = a.get('D');
+ break;
+ default:
+ TODO('unrecognized link type: ' + a.get('S').name);
+ }
+ } else if (annotation.has('Dest')) {
+ // simple destination link
+ var dest = annotation.get('Dest');
+ item.dest = isName(dest) ? dest.name : dest;
+ }
+ break;
+ case 'Widget':
+ var fieldType = getInheritableProperty(annotation, 'FT');
+ if (!isName(fieldType))
+ break;
+ item.fieldType = fieldType.name;
+ // Building the full field name by collecting the field and
+ // its ancestors 'T' properties and joining them using '.'.
+ var fieldName = [];
+ var namedItem = annotation, ref = annotationRef;
+ while (namedItem) {
+ var parent = namedItem.get('Parent');
+ var parentRef = namedItem.getRaw('Parent');
+ var name = namedItem.get('T');
+ if (name) {
+ fieldName.unshift(stringToPDFString(name));
+ } else {
+ // The field name is absent, that means more than one field
+ // with the same name may exist. Replacing the empty name
+ // with the '`' plus index in the parent's 'Kids' array.
+ // This is not in the PDF spec but necessary to id the
+ // the input controls.
+ var kids = parent.get('Kids');
+ var j, jj;
+ for (j = 0, jj = kids.length; j < jj; j++) {
+ var kidRef = kids[j];
+ if (kidRef.num == ref.num && kidRef.gen == ref.gen)
+ break;
+ }
+ fieldName.unshift('`' + j);
+ }
+ namedItem = parent;
+ ref = parentRef;
+ }
+ item.fullName = fieldName.join('.');
+ var alternativeText = stringToPDFString(annotation.get('TU') || '');
+ item.alternativeText = alternativeText;
+ var da = getInheritableProperty(annotation, 'DA') || '';
+ var m = /([\d\.]+)\sTf/.exec(da);
+ if (m)
+ item.fontSize = parseFloat(m[1]);
+ item.textAlignment = getInheritableProperty(annotation, 'Q');
+ item.flags = getInheritableProperty(annotation, 'Ff') || 0;
+ break;
+ case 'Text':
+ var content = annotation.get('Contents');
+ var title = annotation.get('T');
+ item.content = stringToPDFString(content || '');
+ item.title = stringToPDFString(title || '');
+ item.name = !annotation.has('Name') ? 'Note' :
+ annotation.get('Name').name;
+ break;
+ default:
+ TODO('unimplemented annotation type: ' + subtype.name);
+ break;
+ }
+ items.push(item);
+ }
+ return items;
+ }
+ };
+
+ return Page;
+})();
+
+/**
+ * The `PDFDocument` holds all the data of the PDF file. Compared to the
+ * `PDFDoc`, this one doesn't have any job management code.
+ * Right now there exists one PDFDocument on the main thread + one object
+ * for each worker. If there is no worker support enabled, there are two
+ * `PDFDocument` objects on the main thread created.
+ */
+var PDFDocument = (function PDFDocumentClosure() {
+ function PDFDocument(arg, password) {
+ if (isStream(arg))
+ init.call(this, arg, password);
+ else if (isArrayBuffer(arg))
+ init.call(this, new Stream(arg), password);
+ else
+ error('PDFDocument: Unknown argument type');
+ }
+
+ function init(stream, password) {
+ assertWellFormed(stream.length > 0, 'stream must have data');
+ this.stream = stream;
+ this.setup(password);
+ this.acroForm = this.catalog.catDict.get('AcroForm');
+ }
+
+ function find(stream, needle, limit, backwards) {
+ var pos = stream.pos;
+ var end = stream.end;
+ var str = '';
+ if (pos + limit > end)
+ limit = end - pos;
+ for (var n = 0; n < limit; ++n)
+ str += stream.getChar();
+ stream.pos = pos;
+ var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
+ if (index == -1)
+ return false; /* not found */
+ stream.pos += index;
+ return true; /* found */
+ }
+
+ var DocumentInfoValidators = {
+ get entries() {
+ // Lazily build this since all the validation functions below are not
+ // defined until after this file loads.
+ return shadow(this, 'entries', {
+ Title: isString,
+ Author: isString,
+ Subject: isString,
+ Keywords: isString,
+ Creator: isString,
+ Producer: isString,
+ CreationDate: isString,
+ ModDate: isString,
+ Trapped: isName
+ });
+ }
+ };
+
+ PDFDocument.prototype = {
+ get linearization() {
+ var length = this.stream.length;
+ var linearization = false;
+ if (length) {
+ try {
+ linearization = new Linearization(this.stream);
+ if (linearization.length != length)
+ linearization = false;
+ } catch (err) {
+ warn('The linearization data is not available ' +
+ 'or unreadable pdf data is found');
+ linearization = false;
+ }
+ }
+ // shadow the prototype getter with a data property
+ return shadow(this, 'linearization', linearization);
+ },
+ get startXRef() {
+ var stream = this.stream;
+ var startXRef = 0;
+ var linearization = this.linearization;
+ if (linearization) {
+ // Find end of first obj.
+ stream.reset();
+ if (find(stream, 'endobj', 1024))
+ startXRef = stream.pos + 6;
+ } else {
+ // Find startxref by jumping backward from the end of the file.
+ var step = 1024;
+ var found = false, pos = stream.end;
+ while (!found && pos > 0) {
+ pos -= step - 'startxref'.length;
+ if (pos < 0)
+ pos = 0;
+ stream.pos = pos;
+ found = find(stream, 'startxref', step, true);
+ }
+ if (found) {
+ stream.skip(9);
+ var ch;
+ do {
+ ch = stream.getChar();
+ } while (Lexer.isSpace(ch));
+ var str = '';
+ while ((ch - '0') <= 9) {
+ str += ch;
+ ch = stream.getChar();
+ }
+ startXRef = parseInt(str, 10);
+ if (isNaN(startXRef))
+ startXRef = 0;
+ }
+ }
+ // shadow the prototype getter with a data property
+ return shadow(this, 'startXRef', startXRef);
+ },
+ get mainXRefEntriesOffset() {
+ var mainXRefEntriesOffset = 0;
+ var linearization = this.linearization;
+ if (linearization)
+ mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
+ // shadow the prototype getter with a data property
+ return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset);
+ },
+ // Find the header, remove leading garbage and setup the stream
+ // starting from the header.
+ checkHeader: function PDFDocument_checkHeader() {
+ var stream = this.stream;
+ stream.reset();
+ if (find(stream, '%PDF-', 1024)) {
+ // Found the header, trim off any garbage before it.
+ stream.moveStart();
+ // Reading file format version
+ var MAX_VERSION_LENGTH = 12;
+ var version = '', ch;
+ while ((ch = stream.getChar()) > ' ') {
+ if (version.length >= MAX_VERSION_LENGTH) {
+ break;
+ }
+ version += ch;
+ }
+ // removing "%PDF-"-prefix
+ this.pdfFormatVersion = version.substring(5);
+ return;
+ }
+ // May not be a PDF file, continue anyway.
+ },
+ setup: function PDFDocument_setup(password) {
+ this.checkHeader();
+ var xref = new XRef(this.stream,
+ this.startXRef,
+ this.mainXRefEntriesOffset,
+ password);
+ this.xref = xref;
+ this.catalog = new Catalog(xref);
+ },
+ get numPages() {
+ var linearization = this.linearization;
+ var num = linearization ? linearization.numPages : this.catalog.numPages;
+ // shadow the prototype getter
+ return shadow(this, 'numPages', num);
+ },
+ getDocumentInfo: function PDFDocument_getDocumentInfo() {
+ var docInfo = {
+ PDFFormatVersion: this.pdfFormatVersion,
+ IsAcroFormPresent: !!this.acroForm
+ };
+ if (this.xref.trailer.has('Info')) {
+ var infoDict = this.xref.trailer.get('Info');
+
+ var validEntries = DocumentInfoValidators.entries;
+ // Only fill the document info with valid entries from the spec.
+ for (var key in validEntries) {
+ if (infoDict.has(key)) {
+ var value = infoDict.get(key);
+ // Make sure the value conforms to the spec.
+ if (validEntries[key](value)) {
+ docInfo[key] = typeof value !== 'string' ? value :
+ stringToPDFString(value);
+ } else {
+ info('Bad value in document info for "' + key + '"');
+ }
+ }
+ }
+ }
+ return shadow(this, 'getDocumentInfo', docInfo);
+ },
+ getFingerprint: function PDFDocument_getFingerprint() {
+ var xref = this.xref, fileID;
+ if (xref.trailer.has('ID')) {
+ fileID = '';
+ var id = xref.trailer.get('ID')[0];
+ id.split('').forEach(function(el) {
+ fileID += Number(el.charCodeAt(0)).toString(16);
+ });
+ } else {
+ // If we got no fileID, then we generate one,
+ // from the first 100 bytes of PDF
+ var data = this.stream.bytes.subarray(0, 100);
+ var hash = calculateMD5(data, 0, data.length);
+ fileID = '';
+ for (var i = 0, length = hash.length; i < length; i++) {
+ fileID += Number(hash[i]).toString(16);
+ }
+ }
+
+ return shadow(this, 'getFingerprint', fileID);
+ },
+ getPage: function PDFDocument_getPage(n) {
+ return this.catalog.getPage(n);
+ }
+ };
+
+ return PDFDocument;
+})();
+
+
+
+// Use only for debugging purposes. This should not be used in any code that is
+// in mozilla master.
+var log = (function() {
+ if ('console' in globalScope && 'log' in globalScope['console']) {
+ return globalScope['console']['log'].bind(globalScope['console']);
+ } else {
+ return function nop() {
+ };
+ }
+})();
+
+// 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) {
+ 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) {
+ // If multiple arguments were passed, pass them all to the log function.
+ if (arguments.length > 1) {
+ var logArguments = ['Error:'];
+ logArguments.push.apply(logArguments, arguments);
+ log.apply(null, logArguments);
+ // Join the arguments into a single string for the lines below.
+ msg = [].join.call(arguments, ' ');
+ } else {
+ 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() {
+ try {
+ throw new Error();
+ } catch (e) {
+ return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
+ }
+}
+
+function assert(cond, msg) {
+ if (!cond)
+ error(msg);
+}
+
+// Combines two URLs. The baseUrl shall be absolute URL. If the url is an
+// absolute URL, it will be returned as is.
+function combineUrl(baseUrl, url) {
+ if (!url)
+ return baseUrl;
+ if (url.indexOf(':') >= 0)
+ return url;
+ if (url.charAt(0) == '/') {
+ // absolute path
+ var i = baseUrl.indexOf('://');
+ i = baseUrl.indexOf('/', i + 3);
+ return baseUrl.substring(0, i) + url;
+ } else {
+ // relative path
+ var pathLength = baseUrl.length, i;
+ i = baseUrl.lastIndexOf('#');
+ pathLength = i >= 0 ? i : pathLength;
+ i = baseUrl.lastIndexOf('?', pathLength);
+ pathLength = i >= 0 ? i : pathLength;
+ var prefixLength = baseUrl.lastIndexOf('/', pathLength);
+ return baseUrl.substring(0, prefixLength + 1) + url;
+ }
+}
+
+// In a well-formed PDF, |cond| holds. If it doesn't, subsequent
+// behavior is undefined.
+function assertWellFormed(cond, msg) {
+ if (!cond)
+ 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,
+ configurable: true,
+ writable: false });
+ 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;
+})();
+
+var UnknownErrorException = (function UnknownErrorExceptionClosure() {
+ function UnknownErrorException(msg, details) {
+ this.name = 'UnknownErrorException';
+ this.message = msg;
+ this.details = details;
+ }
+
+ UnknownErrorException.prototype = new Error();
+ UnknownErrorException.constructor = UnknownErrorException;
+
+ return UnknownErrorException;
+})();
+
+var InvalidPDFException = (function InvalidPDFExceptionClosure() {
+ function InvalidPDFException(msg) {
+ this.name = 'InvalidPDFException';
+ this.message = msg;
+ }
+
+ InvalidPDFException.prototype = new Error();
+ InvalidPDFException.constructor = InvalidPDFException;
+
+ return InvalidPDFException;
+})();
+
+var MissingPDFException = (function MissingPDFExceptionClosure() {
+ function MissingPDFException(msg) {
+ this.name = 'MissingPDFException';
+ this.message = msg;
+ }
+
+ MissingPDFException.prototype = new Error();
+ MissingPDFException.constructor = MissingPDFException;
+
+ return MissingPDFException;
+})();
+
+function bytesToString(bytes) {
+ var str = '';
+ var length = bytes.length;
+ for (var n = 0; n < length; ++n)
+ str += String.fromCharCode(bytes[n]);
+ return str;
+}
+
+function stringToBytes(str) {
+ var length = str.length;
+ var bytes = new Uint8Array(length);
+ for (var n = 0; n < length; ++n)
+ bytes[n] = str.charCodeAt(n) & 0xFF;
+ return bytes;
+}
+
+var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
+
+var Util = PDFJS.Util = (function UtilClosure() {
+ function Util() {}
+
+ Util.makeCssRgb = function Util_makeCssRgb(rgb) {
+ return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')';
+ };
+
+ Util.makeCssCmyk = function Util_makeCssCmyk(cmyk) {
+ var cs = new DeviceCmykCS();
+ Util.makeCssCmyk = function makeCssCmyk(cmyk) {
+ var rgb = cs.getRgb(cmyk, 0);
+ return Util.makeCssRgb(rgb);
+ };
+ return Util.makeCssCmyk(cmyk);
+ };
+
+ // For 2d affine transforms
+ Util.applyTransform = function Util_applyTransform(p, m) {
+ var xt = p[0] * m[0] + p[1] * m[2] + m[4];
+ var yt = p[0] * m[1] + p[1] * m[3] + m[5];
+ return [xt, yt];
+ };
+
+ Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
+ var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
+ return [xt, yt];
+ };
+
+ Util.inverseTransform = function Util_inverseTransform(m) {
+ var d = m[0] * m[3] - m[1] * m[2];
+ return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d,
+ (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
+ };
+
+ // Apply a generic 3d matrix M on a 3-vector v:
+ // | a b c | | X |
+ // | d e f | x | Y |
+ // | g h i | | Z |
+ // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
+ // with v as [X,Y,Z]
+ Util.apply3dTransform = function Util_apply3dTransform(m, v) {
+ return [
+ m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
+ m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
+ m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
+ ];
+ };
+
+ // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
+ // For coordinate systems whose origin lies in the bottom-left, this
+ // means normalization to (BL,TR) ordering. For systems with origin in the
+ // top-left, this means (TL,BR) ordering.
+ Util.normalizeRect = function Util_normalizeRect(rect) {
+ var r = rect.slice(0); // clone rect
+ if (rect[0] > rect[2]) {
+ r[0] = rect[2];
+ r[2] = rect[0];
+ }
+ if (rect[1] > rect[3]) {
+ r[1] = rect[3];
+ r[3] = rect[1];
+ }
+ return r;
+ };
+
+ // Returns a rectangle [x1, y1, x2, y2] corresponding to the
+ // intersection of rect1 and rect2. If no intersection, returns 'false'
+ // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
+ Util.intersect = function Util_intersect(rect1, rect2) {
+ function compare(a, b) {
+ return a - b;
+ }
+
+ // Order points along the axes
+ var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare),
+ orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare),
+ result = [];
+
+ rect1 = Util.normalizeRect(rect1);
+ rect2 = Util.normalizeRect(rect2);
+
+ // X: first and second points belong to different rectangles?
+ if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) ||
+ (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) {
+ // Intersection must be between second and third points
+ result[0] = orderedX[1];
+ result[2] = orderedX[2];
+ } else {
+ return false;
+ }
+
+ // Y: first and second points belong to different rectangles?
+ if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) ||
+ (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) {
+ // Intersection must be between second and third points
+ result[1] = orderedY[1];
+ result[3] = orderedY[2];
+ } else {
+ return false;
+ }
+
+ return result;
+ };
+
+ Util.sign = function Util_sign(num) {
+ return num < 0 ? -1 : 1;
+ };
+
+ return Util;
+})();
+
+var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() {
+ function PageViewport(viewBox, scale, rotate, offsetX, offsetY) {
+ // creating transform to convert pdf coordinate system to the normal
+ // canvas like coordinates taking in account scale and rotation
+ var centerX = (viewBox[2] + viewBox[0]) / 2;
+ var centerY = (viewBox[3] + viewBox[1]) / 2;
+ var rotateA, rotateB, rotateC, rotateD;
+ switch (rotate % 360) {
+ case -180:
+ case 180:
+ rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
+ break;
+ case -270:
+ case 90:
+ rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
+ break;
+ case -90:
+ case 270:
+ rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
+ break;
+ //case 360:
+ //case 0:
+ default:
+ rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
+ break;
+ }
+ var offsetCanvasX, offsetCanvasY;
+ var width, height;
+ if (rotateA === 0) {
+ offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
+ width = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ height = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ } else {
+ offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
+ offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
+ width = Math.abs(viewBox[2] - viewBox[0]) * scale;
+ height = Math.abs(viewBox[3] - viewBox[1]) * scale;
+ }
+ // creating transform for the following operations:
+ // translate(-centerX, -centerY), rotate and flip vertically,
+ // scale, and translate(offsetCanvasX, offsetCanvasY)
+ this.transform = [
+ rotateA * scale,
+ rotateB * scale,
+ rotateC * scale,
+ rotateD * scale,
+ offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
+ offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
+ ];
+
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.width = width;
+ this.height = height;
+ this.fontScale = scale;
+ }
+ PageViewport.prototype = {
+ convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
+ return Util.applyTransform([x, y], this.transform);
+ },
+ convertToViewportRectangle:
+ function PageViewport_convertToViewportRectangle(rect) {
+ var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
+ var br = Util.applyTransform([rect[2], rect[3]], this.transform);
+ return [tl[0], tl[1], br[0], br[1]];
+ },
+ convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
+ return Util.applyInverseTransform([x, y], this.transform);
+ }
+ };
+ return PageViewport;
+})();
+
+var PDFStringTranslateTable = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014,
+ 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C,
+ 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160,
+ 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC
+];
+
+function stringToPDFString(str) {
+ var i, n = str.length, str2 = '';
+ if (str[0] === '\xFE' && str[1] === '\xFF') {
+ // UTF16BE BOM
+ for (i = 2; i < n; i += 2)
+ str2 += String.fromCharCode(
+ (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1));
+ } else {
+ for (i = 0; i < n; ++i) {
+ var code = PDFStringTranslateTable[str.charCodeAt(i)];
+ str2 += code ? String.fromCharCode(code) : str.charAt(i);
+ }
+ }
+ return str2;
+}
+
+function stringToUTF8String(str) {
+ return decodeURIComponent(escape(str));
+}
+
+function isBool(v) {
+ return typeof v == 'boolean';
+}
+
+function isInt(v) {
+ return typeof v == 'number' && ((v | 0) == v);
+}
+
+function isNum(v) {
+ return typeof v == 'number';
+}
+
+function isString(v) {
+ return typeof v == 'string';
+}
+
+function isNull(v) {
+ return v === null;
+}
+
+function isName(v) {
+ return v instanceof Name;
+}
+
+function isCmd(v, cmd) {
+ return v instanceof Cmd && (!cmd || v.cmd == cmd);
+}
+
+function isDict(v, type) {
+ if (!(v instanceof Dict)) {
+ return false;
+ }
+ if (!type) {
+ return true;
+ }
+ var dictType = v.get('Type');
+ return isName(dictType) && dictType.name == type;
+}
+
+function isArray(v) {
+ return v instanceof Array;
+}
+
+function isStream(v) {
+ return typeof v == 'object' && v !== null && v !== undefined &&
+ ('getChar' in v);
+}
+
+function isArrayBuffer(v) {
+ return typeof v == 'object' && v !== null && v !== undefined &&
+ ('byteLength' in v);
+}
+
+function isRef(v) {
+ return v instanceof Ref;
+}
+
+function isPDFFunction(v) {
+ var fnDict;
+ if (typeof v != 'object')
+ return false;
+ else if (isDict(v))
+ fnDict = v;
+ else if (isStream(v))
+ fnDict = v.dict;
+ else
+ return false;
+ return fnDict.has('FunctionType');
+}
+
+/**
+ * 'Promise' object.
+ * Each object that is stored in PDFObjects is based on a Promise object that
+ * contains the status of the object and the data. There migth be situations,
+ * where a function want to use the value of an object, but it isn't ready at
+ * that time. To get a notification, once the object is ready to be used, s.o.
+ * can add a callback using the `then` method on the promise that then calls
+ * the callback once the object gets resolved.
+ * A promise can get resolved only once and only once the data of the promise
+ * can be set. If any of these happens twice or the data is required before
+ * it was set, an exception is throw.
+ */
+var Promise = PDFJS.Promise = (function PromiseClosure() {
+ var EMPTY_PROMISE = {};
+
+ /**
+ * If `data` is passed in this constructor, the promise is created resolved.
+ * If there isn't data, it isn't resolved at the beginning.
+ */
+ function Promise(name, data) {
+ this.name = name;
+ this.isRejected = false;
+ this.error = null;
+ this.exception = null;
+ // If you build a promise and pass in some data it's already resolved.
+ if (data !== null && data !== undefined) {
+ this.isResolved = true;
+ this._data = data;
+ this.hasData = true;
+ } else {
+ this.isResolved = false;
+ this._data = EMPTY_PROMISE;
+ }
+ this.callbacks = [];
+ this.errbacks = [];
+ this.progressbacks = [];
+ }
+ /**
+ * Builds a promise that is resolved when all the passed in promises are
+ * resolved.
+ * @param {Promise[]} promises Array of promises to wait for.
+ * @return {Promise} New dependant promise.
+ */
+ Promise.all = function Promise_all(promises) {
+ var deferred = new Promise();
+ var unresolved = promises.length;
+ var results = [];
+ if (unresolved === 0) {
+ deferred.resolve(results);
+ return deferred;
+ }
+ for (var i = 0, ii = promises.length; i < ii; ++i) {
+ var promise = promises[i];
+ promise.then((function(i) {
+ return function(value) {
+ results[i] = value;
+ unresolved--;
+ if (unresolved === 0)
+ deferred.resolve(results);
+ };
+ })(i));
+ }
+ return deferred;
+ };
+ Promise.prototype = {
+ hasData: false,
+
+ set data(value) {
+ if (value === undefined) {
+ return;
+ }
+ if (this._data !== EMPTY_PROMISE) {
+ error('Promise ' + this.name +
+ ': Cannot set the data of a promise twice');
+ }
+ this._data = value;
+ this.hasData = true;
+
+ if (this.onDataCallback) {
+ this.onDataCallback(value);
+ }
+ },
+
+ get data() {
+ if (this._data === EMPTY_PROMISE) {
+ error('Promise ' + this.name + ': Cannot get data that isn\'t set');
+ }
+ return this._data;
+ },
+
+ onData: function Promise_onData(callback) {
+ if (this._data !== EMPTY_PROMISE) {
+ callback(this._data);
+ } else {
+ this.onDataCallback = callback;
+ }
+ },
+
+ resolve: function Promise_resolve(data) {
+ if (this.isResolved) {
+ error('A Promise can be resolved only once ' + this.name);
+ }
+ if (this.isRejected) {
+ error('The Promise was already rejected ' + this.name);
+ }
+
+ this.isResolved = true;
+ this.data = (typeof data !== 'undefined') ? data : null;
+ var callbacks = this.callbacks;
+
+ for (var i = 0, ii = callbacks.length; i < ii; i++) {
+ callbacks[i].call(null, data);
+ }
+ },
+
+ progress: function Promise_progress(data) {
+ var callbacks = this.progressbacks;
+ for (var i = 0, ii = callbacks.length; i < ii; i++) {
+ callbacks[i].call(null, data);
+ }
+ },
+
+ reject: function Promise_reject(reason, exception) {
+ if (this.isRejected) {
+ error('A Promise can be rejected only once ' + this.name);
+ }
+ if (this.isResolved) {
+ error('The Promise was already resolved ' + this.name);
+ }
+
+ this.isRejected = true;
+ this.error = reason || null;
+ this.exception = exception || null;
+ var errbacks = this.errbacks;
+
+ for (var i = 0, ii = errbacks.length; i < ii; i++) {
+ errbacks[i].call(null, reason, exception);
+ }
+ },
+
+ then: function Promise_then(callback, errback, progressback) {
+ if (!callback) {
+ error('Requiring callback' + this.name);
+ }
+
+ // If the promise is already resolved, call the callback directly.
+ if (this.isResolved) {
+ var data = this.data;
+ callback.call(null, data);
+ } else if (this.isRejected && errback) {
+ var error = this.error;
+ var exception = this.exception;
+ errback.call(null, error, exception);
+ } else {
+ this.callbacks.push(callback);
+ if (errback)
+ this.errbacks.push(errback);
+ }
+
+ if (progressback)
+ this.progressbacks.push(progressback);
+ }
+ };
+
+ return Promise;
+})();
+
+var StatTimer = (function StatTimerClosure() {
+ function rpad(str, pad, length) {
+ while (str.length < length)
+ str += pad;
+ return str;
+ }
+ function StatTimer() {
+ this.started = {};
+ this.times = [];
+ this.enabled = true;
+ }
+ StatTimer.prototype = {
+ time: function StatTimer_time(name) {
+ if (!this.enabled)
+ return;
+ if (name in this.started)
+ throw 'Timer is already running for ' + name;
+ this.started[name] = Date.now();
+ },
+ timeEnd: function StatTimer_timeEnd(name) {
+ if (!this.enabled)
+ return;
+ if (!(name in this.started))
+ throw 'Timer has not been started for ' + name;
+ this.times.push({
+ 'name': name,
+ 'start': this.started[name],
+ 'end': Date.now()
+ });
+ // Remove timer from started so it can be called again.
+ delete this.started[name];
+ },
+ toString: function StatTimer_toString() {
+ var times = this.times;
+ var out = '';
+ // Find the longest name for padding purposes.
+ var longest = 0;
+ for (var i = 0, ii = times.length; i < ii; ++i) {
+ var name = times[i]['name'];
+ if (name.length > longest)
+ longest = name.length;
+ }
+ for (var i = 0, ii = times.length; i < ii; ++i) {
+ var span = times[i];
+ var duration = span.end - span.start;
+ out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
+ }
+ return out;
+ }
+ };
+ return StatTimer;
+})();
+
+PDFJS.createBlob = function createBlob(data, contentType) {
+ if (typeof Blob === 'function')
+ return new Blob([data], { type: contentType });
+ // Blob builder is deprecated in FF14 and removed in FF18.
+ var bb = new MozBlobBuilder();
+ bb.append(data);
+ return bb.getBlob(contentType);
+};
+
+
+/**
+ * This is the main entry point for loading a PDF and interacting with it.
+ * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
+ * 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|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) {
+ var workerInitializedPromise, workerReadyPromise, transport;
+
+ if (typeof source === 'string') {
+ source = { url: source };
+ } else if (isArrayBuffer(source)) {
+ source = { data: source };
+ } else if (typeof source !== 'object') {
+ error('Invalid parameter in getDocument, need either Uint8Array, ' +
+ 'string or a parameter object');
+ }
+
+ if (!source.url && !source.data)
+ error('Invalid parameter array, need either .data or .url');
+
+ // copy/use all keys as is except 'url' -- full path is required
+ var params = {};
+ for (var key in source) {
+ if (key === 'url' && typeof window !== 'undefined') {
+ params[key] = combineUrl(window.location.href, source[key]);
+ continue;
+ }
+ params[key] = source[key];
+ }
+
+ workerInitializedPromise = new PDFJS.Promise();
+ workerReadyPromise = new PDFJS.Promise();
+ transport = new WorkerTransport(workerInitializedPromise, workerReadyPromise);
+ workerInitializedPromise.then(function transportInitialized() {
+ transport.fetchDocument(params);
+ });
+ return workerReadyPromise;
+};
+
+/**
+ * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
+ * properties that can be read synchronously.
+ */
+var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
+ function PDFDocumentProxy(pdfInfo, transport) {
+ this.pdfInfo = pdfInfo;
+ this.transport = transport;
+ }
+ PDFDocumentProxy.prototype = {
+ /**
+ * @return {number} Total number of pages the PDF contains.
+ */
+ get numPages() {
+ return this.pdfInfo.numPages;
+ },
+ /**
+ * @return {string} A unique ID to identify a PDF. Not guaranteed to be
+ * unique.
+ */
+ get fingerprint() {
+ return this.pdfInfo.fingerprint;
+ },
+ /**
+ * @return {boolean} true if embedded document fonts are in use. Will be
+ * set during rendering of the pages.
+ */
+ get embeddedFontsUsed() {
+ return this.transport.embeddedFontsUsed;
+ },
+ /**
+ * @param {number} The page number to get. The first page is 1.
+ * @return {Promise} A promise that is resolved with a {PDFPageProxy}
+ * object.
+ */
+ getPage: function PDFDocumentProxy_getPage(number) {
+ return this.transport.getPage(number);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a lookup table for
+ * mapping named destinations to reference numbers.
+ */
+ getDestinations: function PDFDocumentProxy_getDestinations() {
+ var promise = new PDFJS.Promise();
+ var destinations = this.pdfInfo.destinations;
+ promise.resolve(destinations);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {array} that is a
+ * tree outline (if it has one) of the PDF. The tree is in the format of:
+ * [
+ * {
+ * title: string,
+ * bold: boolean,
+ * italic: boolean,
+ * color: rgb array,
+ * dest: dest obj,
+ * items: array of more items like this
+ * },
+ * ...
+ * ].
+ */
+ getOutline: function PDFDocumentProxy_getOutline() {
+ var promise = new PDFJS.Promise();
+ var outline = this.pdfInfo.outline;
+ promise.resolve(outline);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {object} that has
+ * info and metadata properties. Info is an {object} filled with anything
+ * available in the information dictionary and similarly metadata is a
+ * {Metadata} object with information from the metadata section of the PDF.
+ */
+ getMetadata: function PDFDocumentProxy_getMetadata() {
+ var promise = new PDFJS.Promise();
+ var info = this.pdfInfo.info;
+ var metadata = this.pdfInfo.metadata;
+ promise.resolve({
+ info: info,
+ metadata: metadata ? new PDFJS.Metadata(metadata) : null
+ });
+ return promise;
+ },
+ isEncrypted: function PDFDocumentProxy_isEncrypted() {
+ var promise = new PDFJS.Promise();
+ promise.resolve(this.pdfInfo.encrypted);
+ return promise;
+ },
+ /**
+ * @return {Promise} A promise that is resolved with a TypedArray that has
+ * the raw data from the PDF.
+ */
+ getData: function PDFDocumentProxy_getData() {
+ var promise = new PDFJS.Promise();
+ this.transport.getData(promise);
+ return promise;
+ },
+ destroy: function PDFDocumentProxy_destroy() {
+ this.transport.destroy();
+ }
+ };
+ return PDFDocumentProxy;
+})();
+
+var PDFPageProxy = (function PDFPageProxyClosure() {
+ function PDFPageProxy(pageInfo, transport) {
+ this.pageInfo = pageInfo;
+ this.transport = transport;
+ this.stats = new StatTimer();
+ this.stats.enabled = !!globalScope.PDFJS.enableStats;
+ this.commonObjs = transport.commonObjs;
+ this.objs = new PDFObjects();
+ this.renderInProgress = false;
+ this.cleanupAfterRender = false;
+ }
+ PDFPageProxy.prototype = {
+ /**
+ * @return {number} Page number of the page. First page is 1.
+ */
+ get pageNumber() {
+ return this.pageInfo.pageIndex + 1;
+ },
+ /**
+ * @return {number} The number of degrees the page is rotated clockwise.
+ */
+ get rotate() {
+ return this.pageInfo.rotate;
+ },
+ /**
+ * @return {object} The reference that points to this page. It has 'num' and
+ * 'gen' properties.
+ */
+ get ref() {
+ return this.pageInfo.ref;
+ },
+ /**
+ * @return {array} An array of the visible portion of the PDF page in the
+ * user space units - [x1, y1, x2, y2].
+ */
+ get view() {
+ return this.pageInfo.view;
+ },
+ /**
+ * @param {number} scale The desired scale of the viewport.
+ * @param {number} rotate Degrees to rotate the viewport. If omitted this
+ * defaults to the page rotation.
+ * @return {PageViewport} Contains 'width' and 'height' properties along
+ * with transforms required for rendering.
+ */
+ getViewport: function PDFPageProxy_getViewport(scale, rotate) {
+ if (arguments.length < 2)
+ rotate = this.rotate;
+ return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
+ },
+ /**
+ * @return {Promise} A promise that is resolved with an {array} of the
+ * annotation objects.
+ */
+ getAnnotations: function PDFPageProxy_getAnnotations() {
+ if (this.annotationsPromise)
+ return this.annotationsPromise;
+
+ var promise = new PDFJS.Promise();
+ this.annotationsPromise = promise;
+ this.transport.getAnnotations(this.pageInfo.pageIndex);
+ return promise;
+ },
+ /**
+ * Begins the process of rendering a page to the desired context.
+ * @param {object} params A parameter object that supports:
+ * {
+ * canvasContext(required): A 2D context of a DOM Canvas object.,
+ * textLayer(optional): An object that has beginLayout, endLayout, and
+ * appendText functions.,
+ * imageLayer(optional): An object that has beginLayout, endLayout and
+ * appendImage functions.,
+ * continueCallback(optional): A function that will be called each time
+ * the rendering is paused. To continue
+ * rendering call the function that is the
+ * first argument to the callback.
+ * }.
+ * @return {Promise} A promise that is resolved when the page finishes
+ * rendering.
+ */
+ render: function PDFPageProxy_render(params) {
+ this.renderInProgress = true;
+
+ var promise = new Promise();
+ var stats = this.stats;
+ stats.time('Overall');
+ // If there is no displayReadyPromise yet, then the operatorList was never
+ // requested before. Make the request and create the promise.
+ if (!this.displayReadyPromise) {
+ this.displayReadyPromise = new Promise();
+ this.destroyed = false;
+
+ this.stats.time('Page Request');
+ this.transport.messageHandler.send('RenderPageRequest', {
+ pageIndex: this.pageNumber - 1
+ });
+ }
+
+ var self = this;
+ function complete(error) {
+ self.renderInProgress = false;
+ if (self.destroyed || self.cleanupAfterRender) {
+ delete self.displayReadyPromise;
+ delete self.operatorList;
+ self.objs.clear();
+ }
+
+ if (error)
+ promise.reject(error);
+ else
+ promise.resolve();
+ }
+ var continueCallback = params.continueCallback;
+
+ // Once the operatorList and fonts are loaded, do the actual rendering.
+ this.displayReadyPromise.then(
+ function pageDisplayReadyPromise() {
+ if (self.destroyed) {
+ complete();
+ return;
+ }
+
+ var gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
+ this.objs, params.textLayer, params.imageLayer);
+ try {
+ this.display(gfx, params.viewport, complete, continueCallback);
+ } catch (e) {
+ complete(e);
+ }
+ }.bind(this),
+ function pageDisplayReadPromiseError(reason) {
+ complete(reason);
+ }
+ );
+
+ return promise;
+ },
+ /**
+ * For internal use only.
+ */
+ startRenderingFromOperatorList:
+ function PDFPageProxy_startRenderingFromOperatorList(operatorList,
+ fonts) {
+ var self = this;
+ this.operatorList = operatorList;
+
+ var displayContinuation = function pageDisplayContinuation() {
+ // Always defer call to display() to work around bug in
+ // Firefox error reporting from XHR callbacks.
+ setTimeout(function pageSetTimeout() {
+ self.displayReadyPromise.resolve();
+ });
+ };
+
+ this.ensureFonts(fonts,
+ function pageStartRenderingFromOperatorListEnsureFonts() {
+ displayContinuation();
+ }
+ );
+ },
+ /**
+ * For internal use only.
+ */
+ ensureFonts: function PDFPageProxy_ensureFonts(fonts, callback) {
+ this.stats.time('Font Loading');
+ // Convert the font names to the corresponding font obj.
+ var fontObjs = [];
+ for (var i = 0, ii = fonts.length; i < ii; i++) {
+ var obj = this.commonObjs.getData(fonts[i]);
+ if (obj.error) {
+ warn('Error during font loading: ' + obj.error);
+ continue;
+ }
+ if (!obj.coded) {
+ this.transport.embeddedFontsUsed = true;
+ }
+ fontObjs.push(obj);
+ }
+
+ // Load all the fonts
+ FontLoader.bind(
+ fontObjs,
+ function pageEnsureFontsFontObjs(fontObjs) {
+ this.stats.timeEnd('Font Loading');
+
+ callback.call(this);
+ }.bind(this)
+ );
+ },
+ /**
+ * For internal use only.
+ */
+ display: function PDFPageProxy_display(gfx, viewport, callback,
+ continueCallback) {
+ var stats = this.stats;
+ stats.time('Rendering');
+
+ gfx.beginDrawing(viewport);
+
+ var startIdx = 0;
+ var length = this.operatorList.fnArray.length;
+ var operatorList = this.operatorList;
+ var stepper = null;
+ if (PDFJS.pdfBug && 'StepperManager' in globalScope &&
+ globalScope['StepperManager'].enabled) {
+ stepper = globalScope['StepperManager'].create(this.pageNumber - 1);
+ stepper.init(operatorList);
+ stepper.nextBreakPoint = stepper.getNextBreakPoint();
+ }
+
+ var continueWrapper;
+ if (continueCallback)
+ continueWrapper = function() { continueCallback(next); };
+ else
+ continueWrapper = next;
+
+ var self = this;
+ function next() {
+ startIdx = gfx.executeOperatorList(operatorList, startIdx,
+ continueWrapper, stepper);
+ if (startIdx == length) {
+ gfx.endDrawing();
+ stats.timeEnd('Rendering');
+ stats.timeEnd('Overall');
+ if (callback) callback();
+ }
+ }
+ continueWrapper();
+ },
+ /**
+ * @return {Promise} That is resolved with the a {string} that is the text
+ * content from the page.
+ */
+ getTextContent: function PDFPageProxy_getTextContent() {
+ var promise = new PDFJS.Promise();
+ this.transport.messageHandler.send('GetTextContent', {
+ pageIndex: this.pageNumber - 1
+ },
+ function textContentCallback(textContent) {
+ promise.resolve(textContent);
+ }
+ );
+ return promise;
+ },
+ /**
+ * Stub for future feature.
+ */
+ getOperationList: function PDFPageProxy_getOperationList() {
+ var promise = new PDFJS.Promise();
+ var operationList = { // not implemented
+ dependencyFontsID: null,
+ operatorList: null
+ };
+ promise.resolve(operationList);
+ return promise;
+ },
+ /**
+ * Destroys resources allocated by the page.
+ */
+ destroy: function PDFPageProxy_destroy() {
+ this.destroyed = true;
+
+ if (!this.renderInProgress) {
+ delete this.operatorList;
+ delete this.displayReadyPromise;
+ this.objs.clear();
+ }
+ }
+ };
+ return PDFPageProxy;
+})();
+/**
+ * For internal use only.
+ */
+var WorkerTransport = (function WorkerTransportClosure() {
+ function WorkerTransport(workerInitializedPromise, workerReadyPromise) {
+ this.workerReadyPromise = workerReadyPromise;
+ this.commonObjs = new PDFObjects();
+
+ this.pageCache = [];
+ this.pagePromises = [];
+ this.embeddedFontsUsed = false;
+
+ // If worker support isn't disabled explicit and the browser has worker
+ // support, create a new web worker and test if it/the browser fullfills
+ // all requirements to run parts of pdf.js in a web worker.
+ // Right now, the requirement is, that an Uint8Array is still an Uint8Array
+ // as it arrives on the worker. Chrome added this with version 15.
+ if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
+ var workerSrc = PDFJS.workerSrc;
+ if (typeof workerSrc === 'undefined') {
+ error('No PDFJS.workerSrc specified');
+ }
+
+ try {
+ // Some versions of FF can't create a worker on localhost, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
+ var worker = new Worker(workerSrc);
+ var messageHandler = new MessageHandler('main', worker);
+ this.messageHandler = messageHandler;
+
+ messageHandler.on('test', function transportTest(supportTypedArray) {
+ if (supportTypedArray) {
+ this.worker = worker;
+ this.setupMessageHandler(messageHandler);
+ } else {
+ globalScope.PDFJS.disableWorker = true;
+ this.setupFakeWorker();
+ }
+ workerInitializedPromise.resolve();
+ }.bind(this));
+
+ var testObj = new Uint8Array(1);
+ // Some versions of Opera throw a DATA_CLONE_ERR on
+ // serializing the typed array.
+ messageHandler.send('test', testObj);
+ return;
+ } catch (e) {
+ info('The worker has been disabled.');
+ }
+ }
+ // Either workers are disabled, not supported or have thrown an exception.
+ // Thus, we fallback to a faked worker.
+ globalScope.PDFJS.disableWorker = true;
+ this.setupFakeWorker();
+ workerInitializedPromise.resolve();
+ }
+ WorkerTransport.prototype = {
+ destroy: function WorkerTransport_destroy() {
+ if (this.worker)
+ this.worker.terminate();
+
+ this.pageCache = [];
+ this.pagePromises = [];
+ },
+ setupFakeWorker: function WorkerTransport_setupFakeWorker() {
+ warn('Setting up fake worker.');
+ // If we don't use a worker, just post/sendMessage to the main thread.
+ var fakeWorker = {
+ postMessage: function WorkerTransport_postMessage(obj) {
+ fakeWorker.onmessage({data: obj});
+ },
+ terminate: function WorkerTransport_terminate() {}
+ };
+
+ var messageHandler = new MessageHandler('main', fakeWorker);
+ this.setupMessageHandler(messageHandler);
+
+ // If the main thread is our worker, setup the handling for the messages
+ // the main thread sends to it self.
+ WorkerMessageHandler.setup(messageHandler);
+ },
+
+ setupMessageHandler:
+ function WorkerTransport_setupMessageHandler(messageHandler) {
+ this.messageHandler = messageHandler;
+
+ messageHandler.on('GetDoc', function transportDoc(data) {
+ var pdfInfo = data.pdfInfo;
+ var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
+ this.pdfDocument = pdfDocument;
+ 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('InvalidPDF', function transportInvalidPDF(data) {
+ this.workerReadyPromise.reject(data.exception.name, data.exception);
+ }, this);
+
+ messageHandler.on('MissingPDF', function transportMissingPDF(data) {
+ this.workerReadyPromise.reject(data.exception.message, data.exception);
+ }, this);
+
+ messageHandler.on('UnknownError', function transportUnknownError(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);
+ this.pageCache[pageInfo.pageIndex] = page;
+ var promise = this.pagePromises[pageInfo.pageIndex];
+ promise.resolve(page);
+ }, this);
+
+ messageHandler.on('GetAnnotations', function transportAnnotations(data) {
+ var annotations = data.annotations;
+ var promise = this.pageCache[data.pageIndex].annotationsPromise;
+ promise.resolve(annotations);
+ }, this);
+
+ messageHandler.on('RenderPage', function transportRender(data) {
+ var page = this.pageCache[data.pageIndex];
+ var depFonts = data.depFonts;
+
+ page.stats.timeEnd('Page Request');
+ page.startRenderingFromOperatorList(data.operatorList, depFonts);
+ }, this);
+
+ messageHandler.on('commonobj', function transportObj(data) {
+ var id = data[0];
+ var type = data[1];
+ if (this.commonObjs.hasData(id))
+ return;
+
+ switch (type) {
+ case 'Font':
+ var exportedData = data[2];
+
+ // At this point, only the font object is created but the font is
+ // not yet attached to the DOM. This is done in `FontLoader.bind`.
+ var font;
+ if ('error' in exportedData)
+ font = new ErrorFont(exportedData.error);
+ else
+ font = new Font(exportedData);
+ this.commonObjs.resolve(id, font);
+ break;
+ default:
+ error('Got unknown common object type ' + type);
+ }
+ }, this);
+
+ messageHandler.on('obj', function transportObj(data) {
+ var id = data[0];
+ var pageIndex = data[1];
+ var type = data[2];
+ var pageProxy = this.pageCache[pageIndex];
+ if (pageProxy.objs.hasData(id))
+ return;
+
+ switch (type) {
+ case 'JpegStream':
+ var imageData = data[3];
+ loadJpegStream(id, imageData, pageProxy.objs);
+ break;
+ case 'Image':
+ var imageData = data[3];
+ pageProxy.objs.resolve(id, imageData);
+
+ // heuristics that will allow not to store large data
+ var MAX_IMAGE_SIZE_TO_STORE = 8000000;
+ if ('data' in imageData &&
+ imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) {
+ pageProxy.cleanupAfterRender = true;
+ }
+ break;
+ default:
+ error('Got unknown object type ' + type);
+ }
+ }, this);
+
+ messageHandler.on('DocProgress', function transportDocProgress(data) {
+ this.workerReadyPromise.progress({
+ loaded: data.loaded,
+ total: data.total
+ });
+ }, this);
+
+ messageHandler.on('DocError', function transportDocError(data) {
+ this.workerReadyPromise.reject(data);
+ }, this);
+
+ messageHandler.on('PageError', function transportError(data) {
+ var page = this.pageCache[data.pageNum - 1];
+ if (page.displayReadyPromise)
+ page.displayReadyPromise.reject(data.error);
+ else
+ error(data.error);
+ }, this);
+
+ messageHandler.on('JpegDecode', function(data, promise) {
+ var imageData = data[0];
+ var components = data[1];
+ if (components != 3 && components != 1)
+ error('Only 3 component or 1 component can be returned');
+
+ var img = new Image();
+ img.onload = (function messageHandler_onloadClosure() {
+ var width = img.width;
+ var height = img.height;
+ var size = width * height;
+ var rgbaLength = size * 4;
+ var buf = new Uint8Array(size * components);
+ var tmpCanvas = createScratchCanvas(width, height);
+ var tmpCtx = tmpCanvas.getContext('2d');
+ tmpCtx.drawImage(img, 0, 0);
+ var data = tmpCtx.getImageData(0, 0, width, height).data;
+
+ if (components == 3) {
+ for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
+ buf[j] = data[i];
+ buf[j + 1] = data[i + 1];
+ buf[j + 2] = data[i + 2];
+ }
+ } else if (components == 1) {
+ for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
+ buf[j] = data[i];
+ }
+ }
+ promise.resolve({ data: buf, width: width, height: height});
+ }).bind(this);
+ var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
+ img.src = src;
+ });
+ },
+
+ fetchDocument: function WorkerTransport_fetchDocument(source) {
+ this.messageHandler.send('GetDocRequest', {source: source});
+ },
+
+ getData: function WorkerTransport_getData(promise) {
+ this.messageHandler.send('GetData', null, function(data) {
+ promise.resolve(data);
+ });
+ },
+
+ getPage: function WorkerTransport_getPage(pageNumber, promise) {
+ var pageIndex = pageNumber - 1;
+ if (pageIndex in this.pagePromises)
+ return this.pagePromises[pageIndex];
+ var promise = new PDFJS.Promise('Page ' + pageNumber);
+ this.pagePromises[pageIndex] = promise;
+ this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
+ return promise;
+ },
+
+ getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
+ this.messageHandler.send('GetAnnotationsRequest',
+ { pageIndex: pageIndex });
+ }
+ };
+ return WorkerTransport;
+
+})();
+
+
+//