[
  {
    "path": ".coveralls.yml",
    "content": "coverage_clover: clover.xml\njson_path: coveralls-upload.json\nsrc_dir: src\n"
  },
  {
    "path": ".gitignore",
    "content": "/nbproject\n/vendor\n/composer.lock\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\n\nlanguage: php\n\ncache:\n  directories:\n    - $HOME/.composer/cache\n\nmatrix:\n  fast_finish: true\n  include:\n    - php: 5.5\n      env:\n        - EXECUTE_CS_CHECK=true\n    - php: 5.6\n      env:\n        - EXECUTE_TEST_COVERALLS=true\n    - php: 7\n    - php: hhvm\n\nbefore_install:\n  - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi\n  - composer self-update\n  - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then composer require --dev --no-update satooshi/php-coveralls dev-master ; fi\n\ninstall:\n  - travis_retry composer install --no-interaction\n\nscript:\n  - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/phpunit --coverage-clover clover.xml ; fi\n  - if [[ $EXECUTE_TEST_COVERALLS != 'true' ]]; then ./vendor/bin/phpunit ; fi\n  - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/phpcs ; fi\n\nafter_script:\n  - if [[ $EXECUTE_TEST_COVERALLS == 'true' ]]; then ./vendor/bin/coveralls ; fi\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2015, Ben Scholzen (DASPRiD)\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# Bacon PDF\n\n[![Build Status](https://api.travis-ci.org/Bacon/BaconPdf.png?branch=master)](http://travis-ci.org/Bacon/BaconPdf)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Bacon/BaconPdf/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Bacon/BaconPdf/?branch=master)\n[![Coverage Status](https://coveralls.io/repos/Bacon/BaconPdf/badge.svg?branch=master&service=github)](https://coveralls.io/github/Bacon/BaconPdf?branch=master)\n[![Documentation Status](https://readthedocs.org/projects/baconpdf/badge/?version=latest)](http://baconpdf.readthedocs.org/en/latest/?badge=latest)\n\n## Introduction\nBaconPdf is a new PDF library for PHP with a clean interface. It comes with both writing and reading capabilities for\nPDFs up to version 1.7.\n\n## Documentation\nYou can find the latest documentation over at Read the Docs:\nhttps://baconpdf.readthedocs.org/en/latest/\n\n## Running benchmarks\nWhen doing performance sensitive changes to core classes, make sure to run the benchmarks before and after making your\nchanges to ensure that they don't cause a huge impact:\n\n```bash\nphp vendor/bin/athletic -p benchmark -b vendor/autoload.php\n```"
  },
  {
    "path": "benchmark/ObjectWriterEvent.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfBenchmark;\n\nuse Athletic\\AthleticEvent;\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\nuse SplFileObject;\n\nclass ObjectWriterEvent extends AthleticEvent\n{\n    /**\n     * @var ObjectWriter\n     */\n    private $objectWriter;\n\n    /**\n     * {@inheritdoc}\n     */\n    public function setUp()\n    {\n        $this->objectWriter = new ObjectWriter(new SplFileObject('php://memory', 'w+'));\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function writeRawLine()\n    {\n        $this->objectWriter->writeRawLine('foo');\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function startDictionary()\n    {\n        $this->objectWriter->startDictionary();\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function endDictionary()\n    {\n        $this->objectWriter->endDictionary();\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function startArray()\n    {\n        $this->objectWriter->startArray();\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function endArray()\n    {\n        $this->objectWriter->endArray();\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function writeNull()\n    {\n        $this->objectWriter->writeNull();\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function writeBoolean()\n    {\n        $this->objectWriter->writeBoolean(true);\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function writeIntegerNumber()\n    {\n        $this->objectWriter->writeNumber(1);\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function writeFloatNumber()\n    {\n        $this->objectWriter->writeNumber(1.1);\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function writeName()\n    {\n        $this->objectWriter->writeName('foo');\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function writeLiteralString()\n    {\n        $this->objectWriter->writeLiteralString('foo');\n    }\n\n    /**\n     * @iterations 10000\n     */\n    public function writeHexadecimalString()\n    {\n        $this->objectWriter->writeHexadecimalString('foo');\n    }\n}\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"bacon/bacon-pdf\",\n    \"description\": \"BaconPdf is a powerful PDF library.\",\n    \"license\" : \"BSD-2-Clause\",\n    \"homepage\": \"https://github.com/Bacon/BaconPdf\",\n    \"require\": {\n        \"php\": \"^5.5.11|^7.0\"\n    },\n    \"authors\": [\n        {\n            \"name\": \"Ben Scholzen 'DASPRiD'\",\n            \"email\": \"mail@dasprids.de\",\n            \"homepage\": \"http://www.dasprids.de\",\n            \"role\": \"Developer\"\n        }\n    ],\n    \"autoload\": {\n        \"psr-4\": {\n            \"Bacon\\\\Pdf\\\\\": \"src/\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Bacon\\\\PdfBenchmark\\\\\": \"benchmark/\",\n            \"Bacon\\\\PdfTest\\\\\": \"test/\"\n        }\n    },\n    \"require-dev\": {\n        \"ext-openssl\": \"*\",\n        \"squizlabs/php_codesniffer\": \"^2.4\",\n        \"phpunit/phpunit\": \"^4.8|^5.0\",\n        \"athletic/athletic\": \"^0.1.8\"\n    },\n    \"suggest\": {\n        \"ext-openssl\": \"Allows using the encryption features\"\n    }\n}\n"
  },
  {
    "path": "doc/.gitignore",
    "content": "/_build"
  },
  {
    "path": "doc/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  livehtml   to make live HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  applehelp  to make an Apple Help Book\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  coverage   to run coverage check of the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\nlivehtml:\n\tsphinx-autobuild -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/BaconPdf.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/BaconPdf.qhc\"\n\napplehelp:\n\t$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp\n\t@echo\n\t@echo \"Build finished. The help book is in $(BUILDDIR)/applehelp.\"\n\t@echo \"N.B. You won't be able to view it unless you put it in\" \\\n\t      \"~/Library/Documentation/Help or install it in your application\" \\\n\t      \"bundle.\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/BaconPdf\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BaconPdf\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\ncoverage:\n\t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage\n\t@echo \"Testing of coverage in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/coverage/python.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "doc/_static/theme_overrides.css",
    "content": "/* override table width restrictions */\n@media screen and (min-width: 767px) {\n    .wy-table-responsive table td {\n        white-space: normal !important;\n    }\n\n    .wy-table-responsive {\n        overflow: visible !important;\n    }\n}\n"
  },
  {
    "path": "doc/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# BaconPdf documentation build configuration file, created by\n# sphinx-quickstart on Sat Nov 28 15:36:16 2015.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys\nimport os\nimport shlex\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = []\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n# source_suffix = ['.rst', '.md']\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'BaconPdf'\ncopyright = u'2015, Ben Scholzen (DASPRiD)'\nauthor = u'Ben Scholzen (DASPRiD)'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = '1.0'\n# The full version, including alpha/beta/rc tags.\nrelease = '1.0.0dev'\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'alabaster'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#html_extra_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Language to be used for generating the HTML full-text search index.\n# Sphinx supports the following languages:\n#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'\n#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'\n#html_search_language = 'en'\n\n# A dictionary with options for the search language support, empty by default.\n# Now only 'ja' uses this config value\n#html_search_options = {'type': 'default'}\n\n# The name of a javascript file (relative to the configuration directory) that\n# implements a search results scorer. If empty, the default will be used.\n#html_search_scorer = 'scorer.js'\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'BaconPdfdoc'\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n\n# Latex figure (float) alignment\n#'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n  (master_doc, 'BaconPdf.tex', u'BaconPdf Documentation',\n   u'Ben Scholzen (DASPRiD)', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (master_doc, 'baconpdf', u'BaconPdf Documentation',\n     [author], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  (master_doc, 'BaconPdf', u'BaconPdf Documentation',\n   author, 'BaconPdf', 'One line description of project.',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n\n# Set up ReadTheDocs theme\nimport sphinx_rtd_theme\nhtml_theme = 'sphinx_rtd_theme'\nhtml_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n\ndef setup(app):\n    # overrides for wide tables in RTD theme\n    app.add_stylesheet('theme_overrides.css')\n\n# Set up PHP syntax highlights\nfrom sphinx.highlighting import lexers\nfrom pygments.lexers.web import PhpLexer\nlexers[\"php\"] = PhpLexer(startinline=True, linenos=1)\nlexers[\"php-annotations\"] = PhpLexer(startinline=True, linenos=1)\nprimary_domain = \"php\"\nhighlight_language = \"php\"\n\nrst_prolog = \"\"\"\n.. role:: hidden\n   :class: hidden\n\"\"\""
  },
  {
    "path": "doc/index.rst",
    "content": "Welcome to BaconPdf's documentation!\n====================================\n\n`BaconPdf`_ is a powerful yet simple PDF library written in PHP. It aims to support\nthe full PDF 1.7 standard. This documentation aims to guide you through the usage of the library. If you encounter\nissues in the documentation or think that some topic needs more explanations, feel free to `open an issue`_ on GitHub.\n\nIn cases where this documentation refers to chapters of the PDF specification, those are always for version `1.7`_.\n\n.. _BaconPdf: https://github.com/Bacon/BaconPdf\n.. _open an issue: https://github.com/Bacon/BaconPdf/issues\n.. _1.7: http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf\n\n.. toctree::\n   :caption: Table of Contents\n   :maxdepth: 2\n\n   metadata"
  },
  {
    "path": "doc/metadata.rst",
    "content": "Handling document metadata\n==========================\n\nPDF documents can hold general information, like the document's title, author and such, also known as metadata. To set\nor retrieve them, you have to obtain the ``DocumentInformation`` object from the PDF writer::\n\n    $information = $pdfWriter->getDocumentInformation();\n\nYou can then set or remove metadata with their respective methods::\n\n    $information->set('Title', 'My awesome PDF document');\n    $information->remove('Author');\n\nWhen retrieving metadata, it can happen that these do not exist. Since BaconPdf follows a strict API, it will throw an\nexception when the requested entry does not exist. You are advised to always check for the existence of the entry you\nwant to retrieve before actually retrieving it::\n\n    if ($information->has('Title') {\n        $title = $information->get('Title');\n    } else {\n        $title = '';\n    }\n\nSince the ``get()`` method will always return a string, there are two special cases in the metadata which must be\nretrieved via special methods, namely ``CreationDate`` and ``ModDate``. Even though those entries have their own methods\nfor retrieval, checking their existence is still done via the ``has()`` method::\n\n    if ($information->has('CreationDate')) {\n        $creationDate = $information->getCreationDate();\n    }\n\n    if ($information->has('ModDate')) {\n        $modificationDate = $information->getModificationDate();\n    }\n\nWhile the PDF specification names a list of standard entries in the metadata, it also allows arbitrary entries, thus the\n``Info`` object does not distinguish between them, except for a few exceptions. Those exceptions are ``CreationDate`` and\n``ModDate``, which cannot be set manually, but will always be manages by BaconPdf itself. Another exception is the\n``Trapped`` entry, which is limited to three possible values (``True``, ``False`` and ``Unkown``). Every other entry can\nbe any kind of text string. Keep in mind that the key names are case-sensitive, so setting the standard-entries with all\nlower-cased keys will not work. The standard entries are the following:\n\nStandard PDF Metadata\n---------------------\n\n.. list-table::\n   :widths: 1 9\n   :header-rows: 1\n\n   * - Key\n     - Description\n   * - ``Title``\n     - The document's title.\n   * - ``Author``\n     - The name of the person who created the document.\n   * - ``Subject``\n     - The subject of the document.\n   * - ``Keywords``\n     - Keywords associated with the document.\n   * - ``Creator``\n     - If the document was converted to PDF from another format, the name of the application that created the original\n       document from which it was converted. This would usually be the name of your application.\n   * - ``Producer``\n     - If the document was converted to PDF from another format, the name of the application that converted it to PDF.\n       This entry defaults to \"BaconPdf\".\n   * - ``Trapped``\n     - Whether the document contains trapping information.\n\nFor more information on those entries, see chapter 10.2.1 of the PDF specification.\n"
  },
  {
    "path": "example/.gitignore",
    "content": "/*.pdf\n"
  },
  {
    "path": "example/empty-page.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nuse Bacon\\Pdf\\PdfWriter;\n\nrequire_once __DIR__ . '/../vendor/autoload.php';\n\n$writer = PdfWriter::toFile(__DIR__ . '/empty-page.pdf');\n$writer->getDocumentInformation()->set('Title', 'Empty Page Example');\n$writer->addPage(595, 842);\n$writer->endDocument();\n"
  },
  {
    "path": "phpcs.xml",
    "content": "<?xml version=\"1.0\"?>\n<ruleset name=\"BaconPdf\">\n    <file>./src</file>\n    <file>./test</file>\n    \n    <rule ref=\"PSR2\"/>\n    <rule ref=\"Generic.Arrays.DisallowLongArraySyntax\"/>\n    \n    <rule ref=\"Generic.Files.LineLength\">\n        <exclude-pattern>test/*</exclude-pattern>\n    </rule>\n</ruleset>\n\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"./vendor/phpunit/phpunit/phpunit.xsd\"\n         bootstrap=\"vendor/autoload.php\"\n         backupGlobals=\"false\"\n         backupStaticAttributes=\"false\"\n         beStrictAboutChangesToGlobalState=\"true\"\n         beStrictAboutOutputDuringTests=\"true\"\n         beStrictAboutTestsThatDoNotTestAnything=\"true\"\n         beStrictAboutTodoAnnotatedTests=\"true\"\n         forceCoversAnnotation=\"true\"\n         colors=\"true\">\n    <testsuite name=\"BaconPdf Test Suite\">\n        <directory>./test</directory>\n    </testsuite>\n\n    <filter>\n        <whitelist addUncoveredFilesFromWhitelist=\"true\">\n            <directory suffix=\".php\">./src</directory>\n        </whitelist>\n    </filter>\n</phpunit>\n"
  },
  {
    "path": "src/DocumentInformation.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf;\n\nuse Bacon\\Pdf\\Exception\\DomainException;\nuse Bacon\\Pdf\\Utils\\StringUtils;\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\nuse DateTimeImmutable;\nuse OutOfBoundsException;\n\nfinal class DocumentInformation\n{\n    /**\n     * @var array\n     */\n    private $data = ['Producer' => 'BaconPdf'];\n\n    /**\n     * Sets an entry in the information dictionary.\n     *\n     * The CreationData and ModDate values are restricted for internal use, so trying to set them will trigger an\n     * exception. Setting the \"Trapped\" value is allowed, but it must be one of the values \"True\", \"False\" or \"Unknown\".\n     * You can set any key in here, but the following are the standard keys recognized by the PDF standard. Keep in mind\n     * that the keys are case-sensitive:\n     *\n     * Title, Author, Subject, Keywords, Creator, Producer and Trapped.\n     *\n     * @param  string $key\n     * @param  string $value\n     * @throws DomainException\n     */\n    public function set($key, $value)\n    {\n        if ('CreationDate' === $key || 'ModDate' === $key) {\n            throw new DomainException('CreationDate and ModDate must not be set manually');\n        }\n\n        if ('Trapped' === $key) {\n            if (!in_array($value, ['True', 'False', 'Unknown'])) {\n                throw new DomainException('Value for \"Trapped\" must be either \"True\", \"False\" or \"Unknown\"');\n            }\n\n            $this->data['Trapped'] = $value;\n            return;\n        }\n\n        $this->data[$key] = $value;\n    }\n\n    /**\n     * Removes an entry from the information dictionary.\n     *\n     * @param string $key\n     */\n    public function remove($key)\n    {\n        unset($this->data[$key]);\n    }\n\n    /**\n     * Checks whether an entry exists in the information dictionary.\n     *\n     * @param  string $key\n     * @return bool\n     */\n    public function has($key)\n    {\n        return array_key_exists($key, $this->data);\n    }\n\n    /**\n     * Retrieves the value for a specific entry in the information dictionary.\n     *\n     * You may retrieve any entry from the information dictionary through this method, except for \"CreationData\" and\n     * \"ModDate\". Those two entries have their own respective methods to be retrieved.\n     *\n     * @param  string $key\n     * @return string\n     * @throws DomainException\n     * @throws OutOfBoundsException\n     */\n    public function get($key)\n    {\n        if ('CreationDate' === $key || 'ModDate' === $key) {\n            throw new DomainException('CreationDate and ModDate must be retrieved through their respective methods');\n        }\n\n        if (!array_key_exists($key, $this->data)) {\n            throw new OutOfBoundsException(sprintf('Entry for key \"%s\" not found', $key));\n        }\n\n        return $this->data[$key];\n    }\n\n    /**\n     * @return DateTimeImmutable\n     */\n    public function getCreationDate()\n    {\n        return $this->retrieveDate('CreationDate');\n    }\n\n    /**\n     * @return DateTimeImmutable\n     */\n    public function getModificationDate()\n    {\n        return $this->retrieveDate('ModDate');\n    }\n\n    /**\n     * Writes the info dictionary.\n     *\n     * @param ObjectWriter $objectWriter\n     * @internal\n     */\n    public function writeInfoDictionary(ObjectWriter $objectWriter)\n    {\n        $objectWriter->startDictionary();\n\n        foreach ($this->data as $key => $value) {\n            $objectWriter->writeName($key);\n\n            switch ($key) {\n                case 'CreationDate':\n                case 'ModDate':\n                    $objectWriter->writeLiteralString(StringUtils::formatDateTime($value));\n                    break;\n\n                case 'Trapped':\n                    $objectWriter->writeName($value);\n                    break;\n\n                default:\n                    $objectWriter->writeLiteralString(StringUtils::encodeString($value));\n            }\n        }\n\n        $objectWriter->endDictionary();\n    }\n\n    /**\n     * @param  string $key\n     * @return DateTimeImmutable\n     * @throws OutOfBoundsException\n     */\n    private function retrieveDate($key)\n    {\n        if (!array_key_exists($key, $this->data)) {\n            throw new OutOfBoundsException(sprintf('Entry for key \"%s\" not found', $key));\n        }\n\n        return $this->data[$key];\n    }\n}\n"
  },
  {
    "path": "src/Encryption/AbstractEncryption.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Encryption;\n\nuse Bacon\\Pdf\\Exception\\RuntimeException;\nuse Bacon\\Pdf\\Exception\\UnexpectedValueException;\nuse Bacon\\Pdf\\Exception\\UnsupportedPasswordException;\nuse Bacon\\Pdf\\Options\\EncryptionOptions;\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\n\nabstract class AbstractEncryption implements EncryptionInterface\n{\n    // @codingStandardsIgnoreStart\n    const ENCRYPTION_PADDING = \"\\x28\\xbf\\x4e\\x5e\\x4e\\x75\\x8a\\x41\\x64\\x00\\x4e\\x56\\xff\\xfa\\x01\\x08\\x2e\\x2e\\x00\\xb6\\xd0\\x68\\x3e\\x80\\x2f\\x0c\\xa9\\xfe\\x64\\x53\\x69\\x7a\";\n    // @codingStandardsIgnoreEnd\n\n    /**\n     * @var string\n     */\n    private $encryptionKey;\n\n    /**\n     * @var string\n     */\n    private $userEntry;\n\n    /**\n     * @var string\n     */\n    private $ownerEntry;\n\n    /**\n     * @var Permissions\n     */\n    private $userPermissions;\n\n    /**\n     * @param  string      $permanentFileIdentifier\n     * @param  string      $userPassword\n     * @param  string      $ownerPassword\n     * @param  Permissions $userPermissions\n     * @throws UnexpectedValueException\n     */\n    public function __construct(\n        $permanentFileIdentifier,\n        $userPassword,\n        $ownerPassword,\n        Permissions $userPermissions\n    ) {\n        // @codeCoverageIgnoreStart\n        if (!extension_loaded('openssl')) {\n            throw new RuntimeException('The OpenSSL extension is required for encryption');\n        }\n        // @codeCoverageIgnoreEnd\n\n        $encodedUserPassword  = $this->encodePassword($userPassword);\n        $encodedOwnerPassword = $this->encodePassword($ownerPassword);\n\n        $revision  = $this->getRevision();\n        $keyLength = $this->getKeyLength() / 8;\n\n        if (!in_array($keyLength, [40 / 8, 128 / 8])) {\n            throw new UnexpectedValueException('Key length must be either 40 or 128');\n        }\n\n        $this->ownerEntry = $this->computeOwnerEntry(\n            $encodedOwnerPassword,\n            $encodedUserPassword,\n            $revision,\n            $keyLength\n        );\n\n        if (2 === $revision) {\n            list($this->userEntry, $this->encryptionKey) = $this->computeUserEntryRev2(\n                $encodedUserPassword,\n                $this->ownerEntry,\n                $revision,\n                $permanentFileIdentifier\n            );\n        } else {\n            list($this->userEntry, $this->encryptionKey) = $this->computeUserEntryRev3OrGreater(\n                $encodedUserPassword,\n                $revision,\n                $keyLength,\n                $this->ownerEntry,\n                $userPermissions->toInt($revision),\n                $permanentFileIdentifier\n            );\n        }\n\n        $this->userPermissions = $userPermissions;\n    }\n\n    /**\n     * Returns an encryption fitting for a specific PDF version.\n     *\n     * @param  string            $pdfVersion\n     * @param  string            $permanentFileIdentifier\n     * @param  EncryptionOptions $options\n     * @return EncryptionInterface\n     */\n    public static function forPdfVersion($pdfVersion, $permanentFileIdentifier, EncryptionOptions $options)\n    {\n        if (version_compare($pdfVersion, '1.6', '>=')) {\n            $encryptionClassName = Pdf16Encryption::class;\n        } elseif (version_compare($pdfVersion, '1.4', '>=')) {\n            $encryptionClassName = Pdf14Encryption::class;\n        } else {\n            $encryptionClassName = Pdf11Encryption::class;\n        }\n\n        return new $encryptionClassName(\n            $permanentFileIdentifier,\n            $options->getUserPassword(),\n            $options->getOwnerPassword(),\n            $options->getUserPermissions()\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeEncryptEntry(ObjectWriter $objectWriter)\n    {\n        $objectWriter->writeName('Encrypt');\n        $objectWriter->startDictionary();\n\n        $objectWriter->writeName('Filter');\n        $objectWriter->writeName('Standard');\n\n        $objectWriter->writeName('V');\n        $objectWriter->writeNumber($this->getAlgorithm());\n\n        $objectWriter->writeName('R');\n        $objectWriter->writeNumber($this->getRevision());\n\n        $objectWriter->writeName('O');\n        $objectWriter->writeHexadecimalString($this->ownerEntry);\n\n        $objectWriter->writeName('U');\n        $objectWriter->writeHexadecimalString($this->userEntry);\n\n        $objectWriter->writeName('P');\n        $objectWriter->writeNumber($this->userPermissions->toInt($this->getRevision()));\n\n        $this->writeAdditionalEncryptDictionaryEntries($objectWriter);\n\n        $objectWriter->endDictionary();\n    }\n\n    /**\n     * Adds additional entries to the encrypt dictionary if required.\n     *\n     * @param ObjectWriter $objectWriter\n     */\n    protected function writeAdditionalEncryptDictionaryEntries(ObjectWriter $objectWriter)\n    {\n    }\n\n    /**\n     * Returns the revision number of the encryption.\n     *\n     * @return int\n     */\n    abstract protected function getRevision();\n\n    /**\n     * Returns the algorithm number of the encryption.\n     *\n     * @return int\n     */\n    abstract protected function getAlgorithm();\n\n    /**\n     * Returns the key length to be used.\n     *\n     * The returned value must be either 40 or 128.\n     *\n     * @return int\n     */\n    abstract protected function getKeyLength();\n\n    /**\n     * Computes an individual ecryption key for an object.\n     *\n     * @param  string $objectNumber\n     * @param  string $generationNumber\n     * @return string\n     */\n    protected function computeIndividualEncryptionKey($objectNumber, $generationNumber)\n    {\n        return substr(md5(\n            $this->encryptionKey\n            . substr(pack('V', $objectNumber), 0, 3)\n            . substr(pack('V', $generationNumber), 0, 2),\n            true\n        ), 0, min(16, strlen($this->encryptionKey) + 5));\n    }\n\n    /**\n     * Encodes a given password into latin-1 and performs length check.\n     *\n     * @param  string $password\n     * @return string\n     * @throws UnsupportedPasswordException\n     */\n    private function encodePassword($password)\n    {\n        set_error_handler(function () {\n        }, E_NOTICE);\n        $encodedPassword = iconv('UTF-8', 'ISO-8859-1', $password);\n        restore_error_handler();\n\n        if (false === $encodedPassword) {\n            throw new UnsupportedPasswordException('Password contains non-latin-1 characters');\n        }\n\n        if (strlen($encodedPassword) > 32) {\n            throw new UnsupportedPasswordException('Password is longer than 32 characters');\n        }\n\n        return $encodedPassword;\n    }\n\n    /**\n     * Computes the encryption key as defined by algorithm 3.2 in 3.5.2.\n     *\n     * @param  string $password\n     * @param  int    $revision\n     * @param  int    $keyLength\n     * @param  string $ownerEntry\n     * @param  int    $permissions\n     * @param  string $permanentFileIdentifier\n     * @param  bool   $encryptMetadata\n     * @return string\n     */\n    private function computeEncryptionKey(\n        $password,\n        $revision,\n        $keyLength,\n        $ownerEntry,\n        $permissions,\n        $permanentFileIdentifier,\n        $encryptMetadata = true\n    ) {\n        $string = substr($password . self::ENCRYPTION_PADDING, 0, 32)\n                . $ownerEntry\n                . pack('V', $permissions)\n                . $permanentFileIdentifier;\n\n        if ($revision >= 4 && $encryptMetadata) {\n            $string .= \"\\0xff\\0xff\\0xff\\0xff\";\n        }\n\n        $hash = md5($string, true);\n\n        if ($revision >= 3) {\n            for ($i = 0; $i < 50; ++$i) {\n                $hash = md5(substr($hash, 0, $keyLength), true);\n            }\n\n            return substr($hash, 0, $keyLength);\n        }\n\n        return substr($hash, 0, 5);\n    }\n\n    /**\n     * Computes the owner entry as defined by algorithm 3.3 in 3.5.2.\n     *\n     * @param  string $ownerPassword\n     * @param  string $userPassword\n     * @param  int    $revision\n     * @param  int    $keyLength\n     * @return string\n     */\n    private function computeOwnerEntry($ownerPassword, $userPassword, $revision, $keyLength)\n    {\n        $hash = md5(substr($ownerPassword . self::ENCRYPTION_PADDING, 0, 32), true);\n\n        if ($revision >= 3) {\n            for ($i = 0; $i < 50; ++$i) {\n                $hash = md5($hash, true);\n            }\n\n            $key = substr($hash, 0, $keyLength);\n        } else {\n            $key = substr($hash, 0, 5);\n        }\n\n        $value = openssl_encrypt(substr($userPassword . self::ENCRYPTION_PADDING, 0, 32), 'rc4', $key, true);\n\n        if ($revision >= 3) {\n            $value = self::applyRc4Loop($value, $key, $keyLength);\n        }\n\n        return $value;\n    }\n\n    /**\n     * Computes the user entry (rev 2) as defined by algorithm 3.4 in 3.5.2.\n     *\n     * @param  string $userPassword\n     * @param  string $ownerEntry\n     * @param  int    $userPermissionFlags\n     * @param  string $permanentFileIdentifier\n     * @return string[]\n     */\n    private function computeUserEntryRev2($userPassword, $ownerEntry, $userPermissionFlags, $permanentFileIdentifier)\n    {\n        $key = self::computeEncryptionKey(\n            $userPassword,\n            2,\n            5,\n            $ownerEntry,\n            $userPermissionFlags,\n            $permanentFileIdentifier\n        );\n\n        return [\n            openssl_encrypt(self::ENCRYPTION_PADDING, 'rc4', $key, true),\n            $key\n        ];\n    }\n\n    /**\n     * Computes the user entry (rev 3 or greater) as defined by algorithm 3.5 in 3.5.2.\n     *\n     * @param  string $userPassword\n     * @param  int    $revision\n     * @param  int    $keyLength\n     * @param  string $ownerEntry\n     * @param  int    $permissions\n     * @param  string $permanentFileIdentifier\n     * @return string[]\n     */\n    private function computeUserEntryRev3OrGreater(\n        $userPassword,\n        $revision,\n        $keyLength,\n        $ownerEntry,\n        $permissions,\n        $permanentFileIdentifier\n    ) {\n        $key = self::computeEncryptionKey(\n            $userPassword,\n            $revision,\n            $keyLength,\n            $ownerEntry,\n            $permissions,\n            $permanentFileIdentifier\n        );\n\n        $hash  = md5(self::ENCRYPTION_PADDING . $permanentFileIdentifier, true);\n        $value = self::applyRc4Loop(openssl_encrypt($hash, 'rc4', $key, true), $key, $keyLength);\n        $value .= openssl_random_pseudo_bytes(16);\n\n        return [\n            $value,\n            $key\n        ];\n    }\n\n    /**\n     * Applies loop RC4 encryption.\n     *\n     * @param  string $value\n     * @param  string $key\n     * @param  int    $keyLength\n     * @return string\n     */\n    private function applyRc4Loop($value, $key, $keyLength)\n    {\n        for ($i = 1; $i <= 19; ++$i) {\n            $newKey = '';\n\n            for ($j = 0; $j < $keyLength; ++$j) {\n                $newKey .= chr(ord($key[$j]) ^ $i);\n            }\n\n            $value = openssl_encrypt($value, 'rc4', $newKey, true);\n        }\n\n        return $value;\n    }\n}\n"
  },
  {
    "path": "src/Encryption/BitMask.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Encryption;\n\n/**\n * Bit mask for representing permissions.\n */\nfinal class BitMask\n{\n    /**\n     * @var int\n     */\n    private $value = 0;\n\n    /**\n     * Sets\n     *\n     * @param int  $bit\n     * @param bool $value\n     */\n    public function set($bit, $value)\n    {\n        if ($value) {\n            $this->value |= (1 << $bit);\n            return;\n        }\n\n        $this->value &= ~(1 << $bit);\n    }\n\n    /**\n     * @return int\n     */\n    public function toInt()\n    {\n        return $this->value;\n    }\n}\n"
  },
  {
    "path": "src/Encryption/EncryptionInterface.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Encryption;\n\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\n\ninterface EncryptionInterface\n{\n    /**\n     * Encrypts a string.\n     *\n     * @param  string $plaintext\n     * @param  int    $objectNumber\n     * @param  int    $generationNumber\n     * @return string\n     */\n    public function encrypt($plaintext, $objectNumber, $generationNumber);\n\n    /**\n     * Writes the encrypt dictionary prefixed by the \"/Encrypt\" name.\n     *\n     * @param ObjectWriter $objectWriter\n     */\n    public function writeEncryptEntry(ObjectWriter $objectWriter);\n}\n"
  },
  {
    "path": "src/Encryption/NullEncryption.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Encryption;\n\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\n\n/**\n * Null encryption which will just return plaintext.\n */\nfinal class NullEncryption implements EncryptionInterface\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function encrypt($plaintext, $objectNumber, $generationNumber)\n    {\n        return $plaintext;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeEncryptEntry(ObjectWriter $objectWriter)\n    {\n    }\n}\n"
  },
  {
    "path": "src/Encryption/Pdf11Encryption.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Encryption;\n\n/**\n * Encryption for handling PDF version 1.1 and above.\n */\nclass Pdf11Encryption extends AbstractEncryption\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function encrypt($plaintext, $objectNumber, $generationNumber)\n    {\n        return openssl_encrypt(\n            $plaintext,\n            'rc4',\n            $this->computeIndividualEncryptionKey($objectNumber, $generationNumber),\n            true\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getRevision()\n    {\n        return 2;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getAlgorithm()\n    {\n        return 1;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getKeyLength()\n    {\n        return 40;\n    }\n}\n"
  },
  {
    "path": "src/Encryption/Pdf14Encryption.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Encryption;\n\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\n\n/**\n * Encryption for handling PDF version 1.4 and above.\n */\nclass Pdf14Encryption extends Pdf11Encryption\n{\n    /**\n     * {@inheritdoc}\n     */\n    protected function writeAdditionalEncryptDictionaryEntries(ObjectWriter $objectWriter)\n    {\n        parent::writeAdditionalEncryptDictionaryEntries($objectWriter);\n\n        $objectWriter->writeName('Length');\n        $objectWriter->writeNumber(128);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getRevision()\n    {\n        return 3;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getAlgorithm()\n    {\n        return 2;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getKeyLength()\n    {\n        return 128;\n    }\n}\n"
  },
  {
    "path": "src/Encryption/Pdf16Encryption.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Encryption;\n\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\n\n/**\n * Encryption for handling PDF version 1.6 and above.\n */\nclass Pdf16Encryption extends Pdf14Encryption\n{\n    /**\n     * {@inheritdoc}\n     */\n    protected function writeAdditionalEncryptDictionaryEntries(ObjectWriter $objectWriter)\n    {\n        parent::writeAdditionalEncryptDictionaryEntries($objectWriter);\n\n        $objectWriter->writeName('CF');\n        $objectWriter->startDictionary();\n\n        $objectWriter->writeName('StdCF');\n        $objectWriter->startDictionary();\n\n        $objectWriter->writeName('Type');\n        $objectWriter->writeName('CryptFilter');\n\n        $objectWriter->writeName('CFM');\n        $objectWriter->writeName('AESV2');\n\n        $objectWriter->writeName('Length');\n        $objectWriter->writeNumber(128);\n\n        $objectWriter->endDictionary();\n        $objectWriter->endDictionary();\n\n        $objectWriter->writeName('StrF');\n        $objectWriter->writeName('StdCF');\n\n        $objectWriter->writeName('StmF');\n        $objectWriter->writeName('StdCF');\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function encrypt($plaintext, $objectNumber, $generationNumber)\n    {\n        $initializationVector = openssl_random_pseudo_bytes(16);\n\n        return $initializationVector . openssl_encrypt(\n            $plaintext,\n            'aes-128-cbc',\n            $this->computeIndividualEncryptionKey($objectNumber, $generationNumber) . \"\\x73\\x41\\x6c\\x54\",\n            true,\n            $initializationVector\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getRevision()\n    {\n        return 4;\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getAlgorithm()\n    {\n        return 4;\n    }\n}\n"
  },
  {
    "path": "src/Encryption/Permissions.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Encryption;\n\n/**\n * Permissions as defined in table 3.20 in section 3.5.\n */\nfinal class Permissions\n{\n    /**\n     * @var bool\n     */\n    private $mayPrint;\n\n    /**\n     * @var bool\n     */\n    private $mayPrintHighResolution;\n\n    /**\n     * @var bool\n     */\n    private $mayModify;\n\n    /**\n     * @var bool\n     */\n    private $mayCopy;\n\n    /**\n     * @var bool\n     */\n    private $mayAnnotate;\n\n    /**\n     * @var bool\n     */\n    private $mayFillInForms;\n\n    /**\n     * @var bool\n     */\n    private $mayExtractForAccessibility;\n\n    /**\n     * @var bool\n     */\n    private $mayAssemble;\n\n    /**\n     * @param bool $mayPrint\n     * @param bool $mayPrintHighResolution\n     * @param bool $mayModify\n     * @param bool $mayCopy\n     * @param bool $mayAnnotate\n     * @param bool $mayFillInForms\n     * @param bool $mayExtractForAccessibility\n     * @param bool $mayAssemble\n     */\n    public function __construct(\n        $mayPrint,\n        $mayPrintHighResolution,\n        $mayModify,\n        $mayCopy,\n        $mayAnnotate,\n        $mayFillInForms,\n        $mayExtractForAccessibility,\n        $mayAssemble\n    ) {\n        $this->mayPrint                   = $mayPrint;\n        $this->mayPrintHighResolution     = $mayPrintHighResolution;\n        $this->mayModify                  = $mayModify;\n        $this->mayCopy                    = $mayCopy;\n        $this->mayAnnotate                = $mayAnnotate;\n        $this->mayFillInForms             = $mayFillInForms;\n        $this->mayExtractForAccessibility = $mayExtractForAccessibility;\n        $this->mayAssemble                = $mayAssemble;\n    }\n\n    /**\n     * Creates permissions which allow nothing.\n     *\n     * @return self\n     */\n    public static function allowNothing()\n    {\n        return new self(false, false, false, false, false, false, false, false);\n    }\n\n    /**\n     * Creates permissions which allow everything.\n     *\n     * @return self\n     */\n    public static function allowEverything()\n    {\n        return new self(true, true, true, true, true, true, true, true);\n    }\n\n    /**\n     * Convert the permissions to am integer bit mask.\n     *\n     * {@internal Keep in mind that the bit positions named in the PDF reference are counted from 1, while in here they\n     * are counted from 0.}}\n     *\n     * @param  int $revision\n     * @return int\n     */\n    public function toInt($revision)\n    {\n        $bitMask = new BitMask();\n        $bitMask->set(2, $this->mayPrint);\n        $bitMask->set(3, $this->mayModify);\n        $bitMask->set(4, $this->mayCopy);\n        $bitMask->set(5, $this->mayAnnotate);\n\n        if ($revision >= 3) {\n            $bitMask->set(8, $this->mayFillInForms);\n            $bitMask->set(9, $this->mayExtractForAccessibility);\n            $bitMask->set(10, $this->mayAssemble);\n            $bitMask->set(11, $this->mayPrintHighResolution);\n        }\n\n        return $bitMask->toInt();\n    }\n}\n"
  },
  {
    "path": "src/Exception/DomainException.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Exception;\n\nclass DomainException extends \\DomainException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/Exception/ExceptionInterface.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Exception;\n\ninterface ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/Exception/InvalidArgumentException.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Exception;\n\nclass InvalidArgumentException extends \\InvalidArgumentException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/Exception/OutOfBoundsException.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Exception;\n\nclass OutOfBoundsException extends \\OutOfBoundsException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/Exception/OutOfRangeException.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Exception;\n\nclass OutOfRangeException extends \\OutOfRangeException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/Exception/RuntimeException.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Exception;\n\nclass RuntimeException extends \\RuntimeException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/Exception/UnexpectedValueException.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Exception;\n\nclass UnexpectedValueException extends \\UnexpectedValueException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/Exception/UnsupportedPasswordException.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Exception;\n\nclass UnsupportedPasswordException extends \\DomainException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/Exception/WriterClosedException.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Exception;\n\nclass WriterClosedException extends \\RuntimeException implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "src/Options/EncryptionOptions.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Options;\n\nuse Bacon\\Pdf\\Encryption\\Permissions;\n\nfinal class EncryptionOptions\n{\n    /**\n     * @var string\n     */\n    private $userPassword;\n\n    /**\n     * @var string\n     */\n    private $ownerPassword;\n\n    /**\n     * @var Permissions\n     */\n    private $userPermissions;\n\n    /**\n     * @param string           $userPassword\n     * @param string|null      $ownerPassword\n     * @param Permissions|null $userPermissions\n     */\n    public function __construct($userPassword, $ownerPassword = null, Permissions $userPermissions = null)\n    {\n        $this->userPassword = $userPassword;\n        $this->ownerPassword = (null !== $ownerPassword ? $ownerPassword : $userPassword);\n        $this->userPermissions = (null !== $userPermissions ? $userPermissions : Permissions::allowEverything());\n    }\n\n    /**\n     * @return string\n     */\n    public function getUserPassword()\n    {\n        return $this->userPassword;\n    }\n\n    /**\n     * @return string\n     */\n    public function getOwnerPassword()\n    {\n        return $this->ownerPassword;\n    }\n\n    /**\n     * @return Permissions\n     */\n    public function getUserPermissions()\n    {\n        return $this->userPermissions;\n    }\n}\n"
  },
  {
    "path": "src/Options/PdfWriterOptions.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Options;\n\nuse Bacon\\Pdf\\Encryption\\AbstractEncryption;\nuse Bacon\\Pdf\\Encryption\\EncryptionInterface;\nuse Bacon\\Pdf\\Encryption\\NullEncryption;\nuse Bacon\\Pdf\\Exception\\DomainException;\n\nfinal class PdfWriterOptions\n{\n    /**\n     * @var string\n     */\n    private $pdfVersion;\n\n    /**\n     * @var EncryptionOptions|null\n     */\n    private $encryptionOptions;\n\n    /**\n     * @param  string $pdfVersion\n     * @throws DomainException\n     */\n    public function __construct($pdfVersion = '1.7')\n    {\n        if (!in_array($pdfVersion, ['1.3', '1.4', '1.5', '1.6', '1.7'])) {\n            throw new DomainException('PDF version is not in the supported range (1.3 - 1.7)');\n        }\n\n        $this->pdfVersion = $pdfVersion;\n    }\n\n    /**\n     * Returns the PDF version to use for the document.\n     *\n     * @return string\n     */\n    public function getPdfVersion()\n    {\n        return $this->pdfVersion;\n    }\n\n    /**\n     * Sets encryption options.\n     *\n     * @param EncryptionOptions $encryptionOptions\n     */\n    public function setEncryptionOptions(EncryptionOptions $encryptionOptions)\n    {\n        $this->encryptionOptions = $encryptionOptions;\n    }\n\n    /**\n     * @param  string $permanentFileIdentifier\n     * @return EncryptionInterface\n     */\n    public function getEncryption($permanentFileIdentifier)\n    {\n        if (null === $this->encryptionOptions) {\n            return new NullEncryption();\n        }\n\n        return AbstractEncryption::forPdfVersion($this->pdfVersion, $permanentFileIdentifier, $this->encryptionOptions);\n    }\n}\n"
  },
  {
    "path": "src/Options/RasterImageOptions.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Options;\n\nfinal class RasterImageOptions\n{\n    /**\n     * @var bool\n     */\n    private $useLossyCompression;\n\n    /**\n     * @var int\n     */\n    private $lossyCompressionQuality;\n\n    private function __construct($useLossyCompression = false, $lossyCompressionQuality = 100)\n    {\n        $this->useLossyCompression = $useLossyCompression;\n        $this->lossyCompressionQuality = $lossyCompressionQuality;\n    }\n\n    public function useLossyCompression()\n    {\n        return $this->useLossyCompression;\n    }\n\n    public function getLossyCompressionQuality()\n    {\n        return $this->lossyCompressionQuality;\n    }\n}\n"
  },
  {
    "path": "src/Page.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf;\n\nuse Bacon\\Pdf\\Writer\\PageWriter;\n\nfinal class Page\n{\n    /**\n     * @var PageWriter\n     */\n    private $pageWriter;\n\n    /**\n     * @param PageWriter $pageWriter\n     * @param float      $width\n     * @param float      $height\n     */\n    public function __construct(PageWriter $pageWriter, $width, $height)\n    {\n        $this->pageWriter = $pageWriter;\n        $this->pageWriter->setBox('MediaBox', new Rectangle(0, 0, $width, $height));\n    }\n\n    /**\n     * Sets the crop box to which the page should be cropped to for displaying or printing.\n     *\n     * @param Rectangle $cropBox\n     */\n    public function setCropBox(Rectangle $cropBox)\n    {\n        $this->pageWriter->setBox('CropBox', $cropBox);\n    }\n\n    /**\n     * Sets the bleed box to which the page should be clipped in a production environment.\n     *\n     * @param Rectangle $bleedBox\n     */\n    public function setBleedBox(Rectangle $bleedBox)\n    {\n        $this->pageWriter->setBox('BleedBox', $bleedBox);\n    }\n\n    /**\n     * Sets the trim box to which the finished page should be trimmed.\n     *\n     * @param Rectangle $trimBox\n     */\n    public function setTrimBox(Rectangle $trimBox)\n    {\n        $this->pageWriter->setBox('TrimBox', $trimBox);\n    }\n\n    /**\n     * Sets the art box which contains the meaningful content of the page.\n     *\n     * @param Rectangle $artBox\n     */\n    public function setArtBox(Rectangle $artBox)\n    {\n        $this->pageWriter->setBox('ArtBox', $artBox);\n    }\n\n    /**\n     * Rotates the page for output.\n     *\n     * The supplied value must be a multiple of 90, so either 0, 90, 180 oder 270.\n     *\n     * @param int $degrees\n     */\n    public function rotate($degrees)\n    {\n        $this->pageWriter->setRotation($degrees);\n    }\n}\n"
  },
  {
    "path": "src/PdfWriter.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf;\n\nuse Bacon\\Pdf\\Encryption\\EncryptionInterface;\nuse Bacon\\Pdf\\Options\\PdfWriterOptions;\nuse Bacon\\Pdf\\Writer\\DocumentWriter;\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\nuse Bacon\\Pdf\\Writer\\PageWriter;\nuse SplFileObject;\n\nclass PdfWriter\n{\n    /**\n     * @var PdfWriterOptions\n     */\n    private $options;\n\n    /**\n     * @var ObjectWriter\n     */\n    private $objectWriter;\n\n    /**\n     * @var DocumentWriter\n     */\n    private $documentWriter;\n\n    /**\n     * @var EncryptionInterface\n     */\n    private $encryption;\n\n    /**\n     * @param SplFileObject    $fileObject\n     * @param PdfWriterOptions $options\n     */\n    public function __construct(SplFileObject $fileObject, PdfWriterOptions $options = null)\n    {\n        if (null === $options) {\n            $options = new PdfWriterOptions();\n        }\n\n        $this->options = $options;\n\n        $fileIdentifier = md5(microtime(), true);\n        $this->objectWriter = new ObjectWriter($fileObject);\n        $this->documentWriter = new DocumentWriter($this->objectWriter, $options, $fileIdentifier);\n        $this->encryption = $options->getEncryption($fileIdentifier);\n    }\n\n    /**\n     * Returns the document information object.\n     *\n     * @return DocumentInformation\n     */\n    public function getDocumentInformation()\n    {\n        return $this->documentWriter->getDocumentInformation();\n    }\n\n    /**\n     * Adds a page to the document.\n     *\n     * @param  float $width\n     * @param  float $height\n     * @return Page\n     */\n    public function addPage($width, $height)\n    {\n        $pageWriter = new PageWriter($this->objectWriter);\n        $this->documentWriter->addPageWriter($pageWriter);\n\n        return new Page($pageWriter, $width, $height);\n    }\n\n    /**\n     * Imports a raster image into the document.\n     *\n     * @param  strings $filename\n     * @param  bool    $useLossyCompression\n     * @param  int     $compressionQuality\n     * @return Image\n     */\n    public function importRasterImage($filename, $useLossyCompression = false, $compressionQuality = 80)\n    {\n        return new RasterImage(\n            $this->objectWriter,\n            $filename,\n            $this->options->getPdfVersion(),\n            $useLossyCompression,\n            $compressionQuality\n        );\n    }\n\n    /**\n     * Ends the document by writing all pending data.\n     *\n     * While the PDF writer will remove all references to the passed in file object in itself to avoid further writing\n     * and to allow the file pointer to be closed, the callee may still have a reference to it. If that is the case,\n     * make sure to unset it if you don't need it.\n     *\n     * Any further attempts to append data to the PDF writer will result in an exception.\n     */\n    public function endDocument()\n    {\n        $this->documentWriter->endDocument($this->encryption);\n    }\n\n    /**\n     * Creates a PDF writer which writes everything to a file.\n     *\n     * @param  string                $filename\n     * @param  PdfWriterOptions|null $options\n     * @return static\n     */\n    public static function toFile($filename, PdfWriterOptions $options = null)\n    {\n        return new static(new SplFileObject($filename, 'wb'), $options);\n    }\n\n    /**\n     * Creates a PDF writer which outputs everything to the STDOUT.\n     *\n     * Make sure to send the appropriate headers beforehand if you are in a web environment.\n     *\n     * @param  PdfWriterOptions|null $options\n     * @return static\n     */\n    public static function output(PdfWriterOptions $options = null)\n    {\n        return new static(new SplFileObject('php://stdout', 'wb'), $options);\n    }\n}\n"
  },
  {
    "path": "src/RasterImage.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf;\n\nuse Bacon\\Pdf\\Exception\\DomainException;\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\nuse Imagick;\nuse Symfony\\Component\\Yaml\\Exception\\RuntimeException;\n\nfinal class RasterImage\n{\n    /**\n     * @var int\n     */\n    private $id;\n\n    /**\n     * @var int\n     */\n    private $width;\n\n    /**\n     * @var int\n     */\n    private $height;\n\n    /**\n     * @param  ObjectWriter $objectWriter\n     * @param  string       $filename\n     * @param  string       $pdfVersion\n     * @param  bool         $useLossyCompression\n     * @param  int          $compressionQuality\n     * @throws DomainException\n     * @throws RuntimeException\n     */\n    public function __construct(\n        ObjectWriter $objectWriter,\n        $filename,\n        $pdfVersion,\n        $useLossyCompression,\n        $compressionQuality\n    ) {\n        if ($compressionQuality < 0 || $compressionQuality > 100) {\n            throw new DomainException('Compression quality must be a value between 0 and 100');\n        }\n\n        $image = new Imagick($filename);\n        $image->stripImage();\n\n        $this->width = $image->getImageWidth();\n        $this->height = $image->getImageHeight();\n\n        $filter     = $this->determineFilter($useLossyCompression, $pdfVersion);\n        $colorSpace = $this->determineColorSpace($image);\n        $this->setFitlerParameters($image, $filter, $colorSpace, $compressionQuality);\n\n        $shadowMaskInData = null;\n        $shadowMaskId = null;\n\n        if (Imagick::ALPHACHANNEL_UNDEFINED !== $image->getImageAlphaChannel()) {\n            if (version_compare($pdfVersion, '1.4', '>=')) {\n                throw new RuntimeException('Transparent images require PDF version 1.4 or higher');\n            }\n\n            if ($filter === 'JPXDecode') {\n                $shadowMaskInData = 1;\n            } else {\n                $shadowMaskId = $this->createShadowMask($objectWriter, $image, $filter);\n            }\n        }\n\n        $streamData = $image->getImageBlob();\n        $image->clear();\n\n        $this->id = $objectWriter->startObject();\n        $objectWriter->startDictionary();\n        $this->writeCommonDictionaryEntries($objectWriter, $colorSpace, strlen($streamData), $filter);\n\n        if (null !== $shadowMaskInData) {\n            $objectWriter->writeName('SMaskInData');\n            $objectWriter->writeNumber($shadowMaskInData);\n        } elseif (null !== $shadowMaskId) {\n            $objectWriter->writeName('SMask');\n            $objectWriter->writeIndirectReference($shadowMaskId);\n        }\n\n        $objectWriter->startStream();\n        $objectWriter->writeRaw($streamData);\n        $objectWriter->endStream();\n        $objectWriter->endObject();\n    }\n\n    /**\n     * Returns the object number of the imported image.\n     *\n     * @return int\n     */\n    public function getId()\n    {\n        return $this->id;\n    }\n\n    /**\n     * Returns the width of the image in pixels.\n     *\n     * @return int\n     */\n    public function getWidth()\n    {\n        return $this->width;\n    }\n\n    /**\n     * Returns the height of the image in pixels.\n     *\n     * @return int\n     */\n    public function getHeight()\n    {\n        return $this->height;\n    }\n\n    /**\n     * @param  bool   $useLossyCompression\n     * @param  string $pdfVersion\n     * @return string\n     */\n    private function determineFilter($useLossyCompression, $pdfVersion)\n    {\n        if (!$useLossyCompression) {\n            return 'FlateDecode';\n        }\n\n        if (version_compare($pdfVersion, '1.5', '>=')) {\n            return 'JPXDecode';\n        }\n\n        return 'DCTDecode';\n    }\n\n    /**\n     * Determines the color space of an image.\n     *\n     * @param  Imagick $image\n     * @return string\n     * @throws DomainException\n     */\n    private function determineColorSpace(Imagick $image)\n    {\n        switch ($image->getColorSpace()) {\n            case Imagick::COLORSPACE_GRAY:\n                return 'DeviceGray';\n\n            case Imagick::COLORSPACE_RGB:\n                return 'DeviceRGB';\n\n            case Imagick::COLORSPACE_CMYK:\n                return 'DeviceCMYK';\n        }\n\n        throw new DomainException('Image has an unsupported colorspace, must be gray, RGB or CMYK');\n    }\n\n    /**\n     * Creates a shadow mask from an image's alpha channel.\n     *\n     * @param  ObjectWriter $objectWriter\n     * @param  Imagick      $image\n     * @param  string       $filter\n     * @return int\n     */\n    private function createShadowMask(ObjectWriter $objectWriter, Imagick $image, $filter)\n    {\n        $shadowMask = clone $image;\n        $shadowMask->separateImageChannel(Imagick::CHANNEL_ALPHA);\n\n        if ('FlateDecode' === $filter) {\n            $image->setImageFormat('GRAY');\n        }\n        \n        $streamData = $shadowMask->getImageBlob();\n        $shadowMask->clear();\n\n        $id = $objectWriter->startObject();\n\n        $objectWriter->startDictionary();\n        $this->writeCommonDictionaryEntries($objectWriter, 'DeviceGray', strlen($streamData), $filter);\n        $objectWriter->endDictionary();\n\n        $objectWriter->startStream();\n        $objectWriter->writeRaw($streamData);\n        $objectWriter->endStream();\n\n        $objectWriter->endObject();\n        return $id;\n    }\n\n    /**\n     * Writes common dictionary entries shared between actual images and their soft masks.\n     *\n     * @param ObjectWriter $objectWriter\n     * @param string       $colorSpace\n     * @param int          $length\n     * @param string       $filter\n     * @param int|null     $shadowMaskId\n     */\n    private function writeCommonDictionaryEntries(ObjectWriter $objectWriter, $colorSpace, $length, $filter)\n    {\n        $objectWriter->writeName('Type');\n        $objectWriter->writeName('XObject');\n\n        $objectWriter->writeName('Subtype');\n        $objectWriter->writeName('Image');\n\n        $objectWriter->writeName('Width');\n        $objectWriter->writeNumber($this->width);\n\n        $objectWriter->writeName('Height');\n        $objectWriter->writeNumber($this->height);\n\n        $objectWriter->writeName('ColorSpace');\n        $objectWriter->writeName($colorSpace);\n\n        $objectWriter->writeName('BitsPerComponent');\n        $objectWriter->writeNumber(8);\n\n        $objectWriter->writeName('Length');\n        $objectWriter->writeNumber($length);\n\n        $objectWriter->writeName('Filter');\n        $objectWriter->writeName($filter);\n    }\n\n    /**\n     * Sets the filter parameters for the image.\n     *\n     * @param Imagick $image\n     * @param string  $filter\n     * @param string  $colorSpace\n     * @param int     $compressionQuality\n     */\n    private function setFitlerParameters(Imagick $image, $filter, $colorSpace, $compressionQuality)\n    {\n        switch ($filter) {\n            case 'JPXDecode':\n                $image->setImageFormat('J2K');\n                $image->setImageCompression(Imagick::COMPRESSION_JPEG2000);\n                break;\n\n            case 'DCTDecode':\n                $image->setImageFormat('JPEG');\n                $image->setImageCompression(Imagick::COMPRESSION_JPEG);\n                break;\n\n            case 'FlateDecode':\n                $image->setImageFormat([\n                    'DeviceGray' => 'GRAY',\n                    'DeviceRGB' => 'RGB',\n                    'DeviceCMYK' => 'CMYK',\n                ][$colorSpace]);\n                $image->setImageCompression(Imagick::COMPRESSION_ZIP);\n                break;\n        }\n\n        $image->setImageCompressionQuality($compressionQuality);\n    }\n}\n"
  },
  {
    "path": "src/Rectangle.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf;\n\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\n\n/**\n * Rectangle data structure as defiend in section 3.8.4.\n */\nfinal class Rectangle\n{\n    /**\n     * @var float\n     */\n    private $x1;\n\n    /**\n     * @var float\n     */\n    private $y1;\n\n    /**\n     * @var float\n     */\n    private $x2;\n\n    /**\n     * @var float\n     */\n    private $y2;\n\n    /**\n     * Creates a new rectangle.\n     *\n     * It doesn't matter in which way you specify the corners, as they will internally be normalized.\n     *\n     * @param float $x1\n     * @param float $y1\n     * @param float $x2\n     * @param float $y2\n     */\n    public function __construct($x1, $y1, $x2, $y2)\n    {\n        $this->x1 = min($x1, $x2);\n        $this->y1 = min($y1, $y2);\n        $this->x2 = max($x1, $x2);\n        $this->y2 = max($y1, $y2);\n    }\n\n    /**\n     * Writes the rectangle object to a writer.\n     *\n     * @param ObjectWriter $objectWriter\n     * @internal\n     */\n    public function writeRectangleArray(ObjectWriter $objectWriter)\n    {\n        $objectWriter->startArray();\n        $objectWriter->writeNumber($this->x1);\n        $objectWriter->writeNumber($this->y1);\n        $objectWriter->writeNumber($this->x2);\n        $objectWriter->writeNumber($this->y2);\n        $objectWriter->endArray();\n    }\n}\n"
  },
  {
    "path": "src/Utils/StringUtils.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Utils;\n\nuse DateTimeInterface;\n\nfinal class StringUtils\n{\n    public function __construct()\n    {\n    }\n\n    /**\n     * Encodes a string according to section 3.8.1.\n     *\n     * @param  string $string\n     * @return string\n     */\n    public static function encodeString($string)\n    {\n        return \"\\xfe\\xff\" . iconv('UTF-8', 'UTF-16BE', $string);\n    }\n\n    /**\n     * Formats a date according to section 3.8.3.\n     *\n     * @param  DateTimeInterface $dateTime\n     * @return string\n     */\n    public static function formatDateTime(DateTimeInterface $dateTime)\n    {\n        $timeString = $dateTime->format('\\D\\:YmdHis');\n\n        if (0 === $dateTime->getTimezone()->getOffset()) {\n            return $timeString . 'Z';\n        }\n\n        return $timeString . strtr(':', \"'\", $dateTime->format('P')) . \"'\";\n    }\n}\n"
  },
  {
    "path": "src/Writer/DocumentWriter.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Writer;\n\nuse Bacon\\Pdf\\DocumentInformation;\nuse Bacon\\Pdf\\Encryption\\EncryptionInterface;\nuse Bacon\\Pdf\\Options\\PdfWriterOptions;\n\nclass DocumentWriter\n{\n    /**\n     * @var ObjectWriter\n     */\n    private $objectWriter;\n\n    /**\n     * @var PdfWriterOptions\n     */\n    private $options;\n\n    /**\n     * @var string\n     */\n    private $permanentFileIdentifier;\n\n    /**\n     * @var string\n     */\n    private $changingFileIdentifier;\n\n    /**\n     * @var int\n     */\n    private $pageTreeId;\n\n    /**\n     * @var PageWriter[]\n     */\n    private $pageWriters = [];\n\n    /**\n     * @var int[]\n     */\n    private $pageIds = [];\n\n    /**\n     * @var DocumentInformation\n     */\n    private $documentInformation;\n\n    /**\n     * @param ObjectWriter $objectWriter\n     * @param string       $fileIdentifier\n     */\n    public function __construct(ObjectWriter $objectWriter, PdfWriterOptions $options, $fileIdentifier)\n    {\n        $this->objectWriter = $objectWriter;\n        $this->options = $options;\n\n        $this->objectWriter->writeRawLine(sprintf(\"%%PDF-%s\", $this->options->getPdfVersion()));\n        $this->objectWriter->writeRawLine(\"%\\xff\\xff\\xff\\xff\");\n\n        $this->permanentFileIdentifier = $this->changingFileIdentifier = $fileIdentifier;\n        $this->pageTreeId = $this->objectWriter->allocateObjectId();\n        $this->documentInformation = new DocumentInformation();\n    }\n\n    /**\n     * Returns the document information object.\n     *\n     * @return DocumentInformation\n     */\n    public function getDocumentInformation()\n    {\n        return $this->documentInformation;\n    }\n\n    /**\n     * Adds a page writer for the page tree.\n     *\n     * @param PageWriter $pageWriter\n     */\n    public function addPageWriter(PageWriter $pageWriter)\n    {\n        $this->pageWriters[] = $pageWriter;\n    }\n\n    /**\n     * Ends the document.\n     *\n     * @param EncryptionInterface $encryption\n     */\n    public function endDocument(EncryptionInterface $encryption)\n    {\n        $this->closeRemainingPages();\n        $this->writePageTree();\n        $documentInformationId = $this->writeDocumentInformation();\n        $documentCatalogId = $this->writeDocumentCatalog();\n\n        $xrefOffset = $this->writeCrossReferenceTable();\n        $this->writeTrailer($documentInformationId, $documentCatalogId, $encryption);\n        $this->writeFooter($xrefOffset);\n    }\n\n    /**\n     * Closes pages which haven't been explicitly closed yet.\n     */\n    private function closeRemainingPages()\n    {\n        foreach ($this->pageWriters as $key => $pageWriter) {\n            $this->pageIds[] = $pageWriter->writePage($this->objectWriter, $this->pageTreeId);\n            unset($this->pageWriters[$key]);\n        }\n    }\n\n    /**\n     * Writes the page tree.\n     */\n    private function writePageTree()\n    {\n        $this->objectWriter->startObject($this->pageTreeId);\n        $this->objectWriter->startDictionary();\n\n        $this->objectWriter->writeName('Type');\n        $this->objectWriter->writeName('Pages');\n\n        $this->objectWriter->writeName('Kids');\n        $this->objectWriter->startArray();\n\n        sort($this->pageIds, SORT_NUMERIC);\n        foreach ($this->pageIds as $pageId) {\n            $this->objectWriter->writeIndirectReference($pageId);\n        }\n\n        $this->objectWriter->endArray();\n\n        $this->objectWriter->writeName('Count');\n        $this->objectWriter->writeNumber(count($this->pageIds));\n\n        $this->objectWriter->endDictionary();\n        $this->objectWriter->endObject();\n    }\n\n    /**\n     * Writes the document information.\n     *\n     * @return int\n     */\n    private function writeDocumentInformation()\n    {\n        $id = $this->objectWriter->startObject();\n        $this->documentInformation->writeInfoDictionary($this->objectWriter);\n        $this->objectWriter->endObject();\n        return $id;\n    }\n\n    /**\n     * Writes the document catalog.\n     *\n     * @return int\n     */\n    private function writeDocumentCatalog()\n    {\n        $id = $this->objectWriter->startObject();\n        $this->objectWriter->startDictionary();\n\n        $this->objectWriter->writeName('Type');\n        $this->objectWriter->writeName('Catalog');\n\n        $this->objectWriter->writeName('Pages');\n        $this->objectWriter->writeIndirectReference($this->pageTreeId);\n\n        $this->objectWriter->endDictionary();\n        $this->objectWriter->endObject();\n        return $id;\n    }\n\n    /**\n     * Writes the cross-reference table.\n     *\n     * @return int\n     */\n    private function writeCrossReferenceTable()\n    {\n        $xrefOffset = $this->objectWriter->getCurrentOffset();\n        $objectOffsets = $this->objectWriter->getObjectOffsets();\n        ksort($objectOffsets, SORT_NUMERIC);\n\n        $this->objectWriter->writeRawLine('xref');\n        $this->objectWriter->writeRawLine(sprintf('0 %d', count($objectOffsets) + 1));\n        $this->objectWriter->writeRawLine(sprintf('%010d %05d f ', 0, 65535));\n\n        foreach ($objectOffsets as $offset) {\n            $this->objectWriter->writeRawLine(sprintf('%010d %05d n ', $offset, 0));\n        }\n\n        return $xrefOffset;\n    }\n\n    /**\n     * Writes the trailer.\n     *\n     * @param int                 $documentInformationId\n     * @param int                 $documentCatalogId\n     * @param EncryptionInterface $encryption\n     */\n    private function writeTrailer($documentInformationId, $documentCatalogId, EncryptionInterface $encryption)\n    {\n        $this->objectWriter->writeRawLine('trailer');\n        $this->objectWriter->startDictionary();\n\n        $this->objectWriter->writeName('Id');\n        $this->objectWriter->startArray();\n        $this->objectWriter->writeHexadecimalString($this->permanentFileIdentifier);\n        $this->objectWriter->writeHexadecimalString($this->changingFileIdentifier);\n        $this->objectWriter->endArray();\n\n        $this->objectWriter->writeName('Info');\n        $this->objectWriter->writeIndirectReference($documentInformationId);\n\n        $this->objectWriter->writeName('Root');\n        $this->objectWriter->writeIndirectReference($documentCatalogId);\n\n        $encryption->writeEncryptEntry($this->objectWriter);\n\n        $this->objectWriter->endDictionary();\n    }\n\n    /**\n     * Writes the footer.\n     *\n     * @param int $xrefOffset\n     */\n    private function writeFooter($xrefOffset)\n    {\n        $this->objectWriter->writeRawLine('');\n        $this->objectWriter->writeRawLine('startxref');\n        $this->objectWriter->writeRawLine((string) $xrefOffset);\n        $this->objectWriter->writeRawLine(\"%%%EOF\");\n    }\n}\n"
  },
  {
    "path": "src/Writer/ObjectWriter.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Writer;\n\nuse Bacon\\Pdf\\Exception\\InvalidArgumentException;\nuse SplFileObject;\n\n/**\n * Writer responsible for writing objects to a stream.\n *\n * While the PDF specification tells that there is a line limit of 255 characters, not even Adobe's own PDF library\n * respects this limit. We ignore it as well, as it imposes a huge impact on the performance of the writer.\n *\n * {@internal This is a very performance sensitive class, which is why some code may look duplicated. Before thinking\n * about refactoring these parts, take a good look and the supplied benchmarks and verify that your changes\n * do not affect the performance in a bad way. Keep in mind that the methods in this writer are called quite\n * often.}}\n */\nclass ObjectWriter\n{\n    /**\n     * @var SplFileObject\n     */\n    private $fileObject;\n\n    /**\n     * @var bool\n     */\n    private $requiresWhitespace = false;\n\n    /**\n     * @var int\n     */\n    private $lastAllocatedObjectId = 0;\n\n    /**\n     * @var int[]\n     */\n    private $objectOffsets = [];\n\n    /**\n     * @param SplFileObject $fileObject\n     */\n    public function __construct(SplFileObject $fileObject)\n    {\n        $this->fileObject = $fileObject;\n    }\n\n    /**\n     * Returns the current position in the file.\n     *\n     * @return int\n     */\n    public function getCurrentOffset()\n    {\n        return $this->fileObject->ftell();\n    }\n\n    /**\n     * Writes a raw data line to the stream.\n     *\n     * A newline character is appended after the data. Keep in mind that you may still be after a token which requires\n     * a following whitespace, depending on the context you are in.\n     *\n     * @param string $data\n     */\n    public function writeRawLine($data)\n    {\n        $this->fileObject->fwrite($data . \"\\n\");\n    }\n\n    /**\n     * Writes raw data to the stream.\n     *\n     * @param string $data\n     */\n    public function writeRaw($data)\n    {\n        $this->fileObject->fwrite($data);\n    }\n\n    /**\n     * Returns all object offsets.\n     *\n     * @return int\n     */\n    public function getObjectOffsets()\n    {\n        return $this->objectOffsets;\n    }\n\n    /**\n     * Allocates a new ID for an object.\n     *\n     * @return int\n     */\n    public function allocateObjectId()\n    {\n        return ++$this->lastAllocatedObjectId;\n    }\n\n    /**\n     * Starts an object.\n     *\n     * If the object ID is omitted, a new one is allocated.\n     *\n     * @param  int|null $objectId\n     * @return int\n     */\n    public function startObject($objectId = null)\n    {\n        if (null === $objectId) {\n            $objectId = ++$this->lastAllocatedObjectId;\n        }\n\n        $this->objectOffsets[$objectId] = $this->fileObject->ftell();\n        $this->fileObject->fwrite(sprintf(\"%d 0 obj\\n\", $objectId));\n\n        return $objectId;\n    }\n\n    /**\n     * Ends an object.\n     */\n    public function endObject()\n    {\n        $this->fileObject->fwrite(\"\\nendobj\\n\");\n    }\n\n    /**\n     * Starts a stream.\n     */\n    public function startStream()\n    {\n        $this->fileObject->fwrite(\"stream\\n\");\n    }\n\n    public function endStream()\n    {\n        $this->fileObject->fwrite(\"\\nendstream\\n\");\n    }\n\n    /**\n     * Writes an indirect reference\n     *\n     * @param int $objectId\n     */\n    public function writeIndirectReference($objectId)\n    {\n        if ($this->requiresWhitespace) {\n            $this->fileObject->fwrite(sprintf(' %d 0 R', $objectId));\n        } else {\n            $this->fileObject->fwrite(sprintf('%d 0 R', $objectId));\n        }\n\n        $this->requiresWhitespace = true;\n    }\n\n    /**\n     * Starts a dictionary.\n     */\n    public function startDictionary()\n    {\n        $this->fileObject->fwrite('<<');\n        $this->requiresWhitespace = false;\n    }\n\n    /**\n     * Ends a dictionary.\n     */\n    public function endDictionary()\n    {\n        $this->fileObject->fwrite('>>');\n        $this->requiresWhitespace = false;\n    }\n\n    /**\n     * Starts an array.\n     */\n    public function startArray()\n    {\n        $this->fileObject->fwrite('[');\n        $this->requiresWhitespace = false;\n    }\n\n    /**\n     * Ends an array.\n     */\n    public function endArray()\n    {\n        $this->fileObject->fwrite(']');\n        $this->requiresWhitespace = false;\n    }\n\n    /**\n     * Writes a null value.\n     */\n    public function writeNull()\n    {\n        if ($this->requiresWhitespace) {\n            $this->fileObject->fwrite(' null');\n        } else {\n            $this->fileObject->fwrite('null');\n        }\n\n        $this->requiresWhitespace = true;\n    }\n\n    /**\n     * Writes a boolean.\n     *\n     * @param bool $boolean\n     */\n    public function writeBoolean($boolean)\n    {\n        if ($this->requiresWhitespace) {\n            $this->fileObject->fwrite($boolean ? ' true' : ' false');\n        } else {\n            $this->fileObject->fwrite($boolean ? 'true' : 'false');\n        }\n\n        $this->requiresWhitespace = true;\n    }\n\n    /**\n     * Writes a number.\n     *\n     * @param  int|float $number\n     * @throws InvalidArgumentException\n     */\n    public function writeNumber($number)\n    {\n        if ($this->requiresWhitespace) {\n            $this->fileObject->fwrite(' ' . (rtrim(sprintf('%.6F', $number), '0.') ?: '0'));\n        } else {\n            $this->fileObject->fwrite(rtrim(sprintf('%.6F', $number), '0.') ?: '0');\n        }\n\n        $this->requiresWhitespace = true;\n    }\n\n    /**\n     * Writes a name.\n     *\n     * @param string $name\n     */\n    public function writeName($name)\n    {\n        $this->fileObject->fwrite('/' . $name);\n        $this->requiresWhitespace = true;\n    }\n\n    /**\n     * Writes a literal string.\n     *\n     * The string itself is splitted into multiple lines after 248 characters. We chose that specific limit to avoid\n     * splitting mutli-byte characters in half.\n     *\n     * @param string $string\n     */\n    public function writeLiteralString($string)\n    {\n        $this->fileObject->fwrite('(' . strtr($string, ['(' => '\\\\(', ')' => '\\\\)', '\\\\' => '\\\\\\\\']) . ')');\n        $this->requiresWhitespace = false;\n    }\n\n    /**\n     * Writes a hexadecimal string.\n     *\n     * @param string $string\n     */\n    public function writeHexadecimalString($string)\n    {\n        $this->fileObject->fwrite('<' . bin2hex($string) . '>');\n        $this->requiresWhitespace = false;\n    }\n}\n"
  },
  {
    "path": "src/Writer/PageWriter.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\Pdf\\Writer;\n\nuse Bacon\\Pdf\\Rectangle;\nuse DomainException;\n\nclass PageWriter\n{\n    /**\n     * @var ObjectWriter\n     */\n    private $objectWriter;\n\n    /**\n     * @var int\n     */\n    private $pageId;\n\n    /**\n     * @var Rectangle[]\n     */\n    private $boxes = [];\n\n    /**\n     * @var int|null\n     */\n    private $rotation;\n\n    /**\n     * @var string\n     */\n    private $contentStream = '';\n\n    /**\n     * @param ObjectWriter $objectWriter\n     */\n    public function __construct(ObjectWriter $objectWriter)\n    {\n        $this->objectWriter = $objectWriter;\n        $this->pageId = $this->objectWriter->allocateObjectId();\n    }\n\n    /**\n     * Sets a box for the page.\n     *\n     * @param string    $name\n     * @param Rectangle $box\n     */\n    public function setBox($name, Rectangle $box)\n    {\n        $this->boxes[$name] = $box;\n    }\n\n    /**\n     * Sets the rotation of the page.\n     *\n     * @param  int $degrees\n     * @throws DomainException\n     */\n    public function setRotation($degrees)\n    {\n        if (!in_array($degrees, [0, 90, 180, 270])) {\n            throw new DomainException('Degrees value must be a multiple of 90');\n        }\n\n        $this->rotation = $degrees;\n    }\n\n    /**\n     * Appends data to the content stream.\n     *\n     * @param string $data\n     */\n    public function appendContentStream($data)\n    {\n        $this->contentStream .= $data;\n    }\n\n    /**\n     * Writes the page contents and definition to the writer.\n     *\n     * @param  ObjectWriter $objectWriter\n     * @param  int          $pageTreeId\n     * @return int\n     */\n    public function writePage(ObjectWriter $objectWriter, $pageTreeId)\n    {\n        $objectWriter->startObject($this->pageId);\n        $objectWriter->startDictionary();\n        $objectWriter->writeName('Type');\n        $objectWriter->writeName('Page');\n\n        $objectWriter->writeName('Parent');\n        $objectWriter->writeIndirectReference($pageTreeId);\n\n        $objectWriter->writeName('Resources');\n        $objectWriter->startDictionary();\n        $objectWriter->endDictionary();\n\n        $objectWriter->writeName('Contents');\n        $objectWriter->startArray();\n        $objectWriter->endArray();\n\n        foreach ($this->boxes as $name => $box) {\n            $objectWriter->writeName($name);\n            $box->writeRectangleArray($objectWriter);\n        }\n\n        if (null !== $this->rotation) {\n            $objectWriter->writeName('Rotate');\n            $objectWriter->writeNumber($this->rotation);\n        }\n\n        $objectWriter->endDictionary();\n        $objectWriter->endObject();\n        return $this->pageId;\n    }\n}\n"
  },
  {
    "path": "test/Encryption/AbstractEncryptionTest.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\Encryption;\n\nuse Bacon\\Pdf\\Encryption\\AbstractEncryption;\nuse Bacon\\Pdf\\Encryption\\Pdf11Encryption;\nuse Bacon\\Pdf\\Encryption\\Pdf14Encryption;\nuse Bacon\\Pdf\\Encryption\\Pdf16Encryption;\nuse Bacon\\Pdf\\Encryption\\Permissions;\nuse Bacon\\Pdf\\Exception\\UnexpectedValueException;\nuse Bacon\\Pdf\\Exception\\UnsupportedPasswordException;\nuse Bacon\\Pdf\\Options\\EncryptionOptions;\nuse PHPUnit_Framework_TestCase as TestCase;\n\n/**\n * @covers \\Bacon\\Pdf\\Encryption\\AbstractEncryption\n */\nclass AbstractEncryptionTest extends TestCase\n{\n    public function testForPdfVersion()\n    {\n        $this->assertInstanceOf(\n            Pdf11Encryption::class,\n            AbstractEncryption::forPdfVersion('1.3', '', new EncryptionOptions(''))\n        );\n\n        $this->assertInstanceOf(\n            Pdf14Encryption::class,\n            AbstractEncryption::forPdfVersion('1.4', '', new EncryptionOptions(''))\n        );\n\n        $this->assertInstanceOf(\n            Pdf14Encryption::class,\n            AbstractEncryption::forPdfVersion('1.5', '', new EncryptionOptions(''))\n        );\n\n        $this->assertInstanceOf(\n            Pdf16Encryption::class,\n            AbstractEncryption::forPdfVersion('1.6', '', new EncryptionOptions(''))\n        );\n\n        $this->assertInstanceOf(\n            Pdf16Encryption::class,\n            AbstractEncryption::forPdfVersion('1.7', '', new EncryptionOptions(''))\n        );\n    }\n\n    public function testTooLongishUserPassword()\n    {\n        $this->setExpectedException(UnsupportedPasswordException::class, 'Password is longer than 32 characters');\n        $this->getAbstractEncryption()->__construct('', str_repeat('a', 33), '', Permissions::allowNothing());\n    }\n\n    public function testTooLongishOwnerPassword()\n    {\n        $this->setExpectedException(UnsupportedPasswordException::class, 'Password is longer than 32 characters');\n        $this->getAbstractEncryption()->__construct('', '', str_repeat('a', 33), Permissions::allowNothing());\n    }\n\n    public function testUserPasswordWithInvalidCharacters()\n    {\n        $this->setExpectedException(UnsupportedPasswordException::class, 'Password contains non-latin-1 characters');\n        $this->getAbstractEncryption()->__construct('', 'Ŧ', '', Permissions::allowNothing());\n    }\n\n    public function testOwnerPasswordWithInvalidCharacters()\n    {\n        $this->setExpectedException(UnsupportedPasswordException::class, 'Password contains non-latin-1 characters');\n        $this->getAbstractEncryption()->__construct('', '', 'Ŧ', Permissions::allowNothing());\n    }\n\n    public function testAbstractReturnsInvalidKeyLength()\n    {\n        $this->setExpectedException(UnexpectedValueException::class, 'Key length must be either 40 or 128');\n        $this->getAbstractEncryption(100)->__construct('', '', '', Permissions::allowNothing());\n    }\n\n    /**\n     * @return AbstractEncryption\n     */\n    private function getAbstractEncryption($keyLength = 128)\n    {\n        $encryption = $this->getMockForAbstractClass(AbstractEncryption::class, [], '', false);\n        $encryption->expects($this->any())->method('getKeyLength')->willReturn($keyLength);\n        $encryption->expects($this->any())->method('getRevision')->willReturn(2);\n        $encryption->expects($this->any())->method('getAlgorithm')->willReturn(1);\n        return $encryption;\n    }\n}\n"
  },
  {
    "path": "test/Encryption/AbstractEncryptionTestCase.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\Encryption;\n\nuse Bacon\\Pdf\\Encryption\\AbstractEncryption;\nuse Bacon\\Pdf\\Encryption\\Permissions;\nuse Bacon\\PdfTest\\TestHelper\\MemoryObjectWriter;\nuse PHPUnit_Framework_TestCase as TestCase;\nuse ReflectionClass;\n\n/**\n * @covers \\Bacon\\Pdf\\Encryption\\AbstractEncryption\n */\nabstract class AbstractEncryptionTestCase extends TestCase\n{\n    /**\n     * @dataProvider encryptionTestData\n     * @param string      $plaintext\n     * @param string      $userPassword\n     * @param string|null $ownerPassword\n     * @param int         $objectNumber\n     * @param int         $generationNumber\n     */\n    public function testEncrypt(\n        $plaintext,\n        $userPassword,\n        $ownerPassword,\n        $objectNumber,\n        $generationNumber\n    ) {\n        $encryption = $this->createEncryption($userPassword, $ownerPassword);\n\n        $reflectionClass = new ReflectionClass($encryption);\n        $reflectionMethod = $reflectionClass->getMethod('computeIndividualEncryptionKey');\n        $reflectionMethod->setAccessible(true);\n        $key = $reflectionMethod->invoke($encryption, $objectNumber, $generationNumber);\n\n        $encryptedText = $encryption->encrypt($plaintext, $objectNumber, $generationNumber);\n        $decryptedText = $this->decrypt($encryptedText, $key);\n\n        $this->assertSame($plaintext, $decryptedText);\n    }\n\n    public function testWriteEncryptEntry()\n    {\n        $encryption = $this->createEncryption('foo', 'bar');\n        $memoryObjectWriter = new MemoryObjectWriter();\n        $encryption->writeEncryptEntry($memoryObjectWriter);\n\n        $this->assertStringMatchesFormat($this->getExpectedEntry(), $memoryObjectWriter->getData());\n    }\n\n    /**\n     * @return array\n     */\n    abstract public function encryptionTestData();\n\n    /**\n     * @param  string           $userPassword\n     * @param  string|null      $ownerPassword\n     * @param  Permissions|null $userPermissions\n     * @return AbstractEncryption\n     */\n    abstract protected function createEncryption(\n        $userPassword,\n        $ownerPassword = null,\n        Permissions $userPermissions = null\n    );\n\n    /**\n     * @param  string $encryptedText\n     * @param  string $key\n     * @return string\n     */\n    abstract protected function decrypt($encryptedText, $key);\n\n    /**\n     * @return string\n     */\n    abstract protected function getExpectedEntry();\n}\n"
  },
  {
    "path": "test/Encryption/BitMaskTest.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\Encryption;\n\nuse Bacon\\Pdf\\Encryption\\BitMask;\nuse PHPUnit_Framework_TestCase as TestCase;\n\n/**\n * @covers \\Bacon\\Pdf\\Encryption\\BitMask\n */\nclass BitMaskTest extends TestCase\n{\n    public function testDefault()\n    {\n        $bitMask = new BitMask();\n        $this->assertSame(0, $bitMask->toInt());\n    }\n\n    public function testSetBit()\n    {\n        $bitMask = new BitMask();\n        $bitMask->set(0, true);\n        $bitMask->set(1, true);\n        $this->assertSame(3, $bitMask->toInt());\n        $bitMask->set(0, false);\n        $this->assertSame(2, $bitMask->toInt());\n    }\n}\n"
  },
  {
    "path": "test/Encryption/NullEncryptionTest.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\Encryption;\n\nuse Bacon\\Pdf\\Encryption\\NullEncryption;\nuse Bacon\\PdfTest\\TestHelper\\MemoryObjectWriter;\nuse PHPUnit_Framework_TestCase as TestCase;\n\n/**\n * @covers \\Bacon\\Pdf\\Encryption\\NullEncryption\n */\nclass NullEncryptionTest extends TestCase\n{\n    public function testEncryptReturnsPlaintext()\n    {\n        $encryption = new NullEncryption();\n        $this->assertSame('foo', $encryption->encrypt('foo', 1, 1));\n    }\n\n    public function testWriteEncryptEntryWritesNothing()\n    {\n        $encryption = new NullEncryption();\n        $objectWriter = new MemoryObjectWriter();\n        $encryption->writeEncryptEntry($objectWriter);\n        $this->assertSame('', $objectWriter->getData());\n    }\n}\n"
  },
  {
    "path": "test/Encryption/Pdf11EncryptionTest.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\Encryption;\n\nuse Bacon\\Pdf\\Encryption\\Pdf11Encryption;\nuse Bacon\\Pdf\\Encryption\\Permissions;\n\n/**\n * @covers \\Bacon\\Pdf\\Encryption\\AbstractEncryption\n * @covers \\Bacon\\Pdf\\Encryption\\Pdf11Encryption\n */\nclass Pdf11EncryptionTest extends AbstractEncryptionTestCase\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function encryptionTestData()\n    {\n        return [\n            'same-numbers' => ['test', 'foo', null, 1, 1],\n            'changed-generation-number' => ['test', 'foo', null, 1, 2],\n            'changed-object-number' => ['test', 'foo', null, 2, 1],\n            'both-numbers-changed' => ['test', 'foo', null, 2, 2],\n            'changed-user-password' => ['test', 'bar', null, 1, 1],\n            'added-owner-password' => ['test', 'bar', 'baz', 1, 1],\n        ];\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function createEncryption(\n        $userPassword,\n        $ownerPassword = null,\n        Permissions $userPermissions = null\n    ) {\n        return new Pdf11Encryption(\n            md5('test', true),\n            $userPassword,\n            $ownerPassword ?: $userPassword,\n            $userPermissions ?: Permissions::allowNothing()\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function decrypt($encryptedText, $key)\n    {\n        return openssl_decrypt($encryptedText, 'rc4', $key, OPENSSL_RAW_DATA);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getExpectedEntry()\n    {\n        return file_get_contents(__DIR__ . '/_files/pdf11-encrypt-entry.txt');\n    }\n}\n"
  },
  {
    "path": "test/Encryption/Pdf14EncryptionTest.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\Encryption;\n\nuse Bacon\\Pdf\\Encryption\\Pdf14Encryption;\nuse Bacon\\Pdf\\Encryption\\Permissions;\n\n/**\n * @covers \\Bacon\\Pdf\\Encryption\\AbstractEncryption\n * @covers \\Bacon\\Pdf\\Encryption\\Pdf14Encryption\n */\nclass Pdf14EncryptionTest extends AbstractEncryptionTestCase\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function encryptionTestData()\n    {\n        return [\n            'same-numbers' => ['test', 'foo', null, 1, 1],\n            'changed-generation-number' => ['test', 'foo', null, 1, 2],\n            'changed-object-number' => ['test', 'foo', null, 2, 1],\n            'both-numbers-changed' => ['test', 'foo', null, 2, 2],\n            'changed-user-password' => ['test', 'bar', null, 1, 1],\n            'added-owner-password' => ['test', 'bar', 'baz', 1, 1],\n        ];\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function createEncryption(\n        $userPassword,\n        $ownerPassword = null,\n        Permissions $userPermissions = null\n    ) {\n        return new Pdf14Encryption(\n            md5('test', true),\n            $userPassword,\n            $ownerPassword ?: $userPassword,\n            $userPermissions ?: Permissions::allowNothing()\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function decrypt($encryptedText, $key)\n    {\n        return openssl_decrypt($encryptedText, 'rc4', $key, OPENSSL_RAW_DATA);\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getExpectedEntry()\n    {\n        return file_get_contents(__DIR__ . '/_files/pdf14-encrypt-entry.txt');\n    }\n}\n"
  },
  {
    "path": "test/Encryption/Pdf16EncryptionTest.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\Encryption;\n\nuse Bacon\\Pdf\\Encryption\\Pdf16Encryption;\nuse Bacon\\Pdf\\Encryption\\Permissions;\n\n/**\n * @covers \\Bacon\\Pdf\\Encryption\\AbstractEncryption\n * @covers \\Bacon\\Pdf\\Encryption\\Pdf16Encryption\n */\nclass Pdf16EncryptionTest extends AbstractEncryptionTestCase\n{\n    /**\n     * {@inheritdoc}\n     */\n    public function encryptionTestData()\n    {\n        return [\n            'same-numbers' => ['test', 'foo', null, 1, 1],\n            'changed-generation-number' => ['test', 'foo', null, 1, 2],\n            'changed-object-number' => ['test', 'foo', null, 2, 1],\n            'both-numbers-changed' => ['test', 'foo', null, 2, 2],\n            'changed-user-password' => ['test', 'bar', null, 1, 1],\n            'added-owner-password' => ['test', 'bar', 'baz', 1, 1],\n        ];\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function createEncryption(\n        $userPassword,\n        $ownerPassword = null,\n        Permissions $userPermissions = null\n    ) {\n        return new Pdf16Encryption(\n            md5('test', true),\n            $userPassword,\n            $ownerPassword ?: $userPassword,\n            $userPermissions ?: Permissions::allowNothing()\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function decrypt($encryptedText, $key)\n    {\n        return openssl_decrypt(\n            substr($encryptedText, 16),\n            'aes-128-cbc',\n            $key,\n            OPENSSL_RAW_DATA,\n            substr($encryptedText, 0, 16)\n        );\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    protected function getExpectedEntry()\n    {\n        return file_get_contents(__DIR__ . '/_files/pdf16-encrypt-entry.txt');\n    }\n}\n"
  },
  {
    "path": "test/Encryption/PermissionsTest.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\Encryption;\n\nuse Bacon\\Pdf\\Encryption\\Permissions;\nuse PHPUnit_Framework_TestCase as TestCase;\nuse ReflectionClass;\n\n/**\n * @covers \\Bacon\\Pdf\\Encryption\\Permissions\n */\nclass PermissionsTest extends TestCase\n{\n    public function testZeroPermissions()\n    {\n        $permissions = Permissions::allowNothing();\n        $this->assertSame(0, $permissions->toInt(2));\n        $this->assertSame(0, $permissions->toInt(3));\n    }\n\n    public function testFullPermissions()\n    {\n        $permissions = Permissions::allowEverything();\n        $this->assertSame(60, $permissions->toInt(2));\n        $this->assertSame(3900, $permissions->toInt(3));\n    }\n\n    /**\n     * @dataProvider individualPermissions\n     */\n    public function testIndividualPermissions($flagPosition, $rev2Value, $rev3Value)\n    {\n        $args = array_fill(0, 8, false);\n        $args[$flagPosition] = true;\n\n        $reflectionClass = new ReflectionClass(Permissions::class);\n        $permissions = $reflectionClass->newInstanceArgs($args);\n        $this->assertSame($rev2Value, $permissions->toInt(2));\n        $this->assertSame($rev3Value, $permissions->toInt(3));\n    }\n\n    /**\n     * @return array\n     */\n    public function individualPermissions()\n    {\n        return [\n            'may-print' => [0, 4, 4],\n            'may-print-high-resolution' => [1, 0, 2048],\n            'may-modify' => [2, 8, 8],\n            'may-copy' => [3, 16, 16],\n            'may-annotate' => [4, 32, 32],\n            'may-fill-in-forms' => [5, 0, 256],\n            'may-extract-for-accessibility' => [6, 0, 512],\n            'may-assemble' => [7, 0, 1024],\n        ];\n    }\n}\n"
  },
  {
    "path": "test/Encryption/_files/pdf11-encrypt-entry.txt",
    "content": "/Encrypt\n<<\n/Filter\n/Standard\n/V\n1\n/R\n2\n/O\n<947319c0b0ba83c01223fc7a3c39ef0a88ac4cc19e6b9e86f889d81f56ba57c4>\n/U\n<9dce9fbdfab50815486c48b3d9d8bb48a3d6f86e3a3768a3e8c8fb2b074b0658>\n/P\n0\n>>\n"
  },
  {
    "path": "test/Encryption/_files/pdf14-encrypt-entry.txt",
    "content": "/Encrypt\n<<\n/Filter\n/Standard\n/V\n2\n/R\n3\n/O\n<85dafdd50f5179a0aacf58d9c59c34ab55274f38c85a0b2d9ae68606ecd290be>\n/U\n<f4590529d9ae74ae6f1f9be11963d7bb%x>\n/P\n0\n/Length\n128\n>>\n"
  },
  {
    "path": "test/Encryption/_files/pdf16-encrypt-entry.txt",
    "content": "/Encrypt\n<<\n/Filter\n/Standard\n/V\n4\n/R\n4\n/O\n<85dafdd50f5179a0aacf58d9c59c34ab55274f38c85a0b2d9ae68606ecd290be>\n/U\n<71f71069a89277d41eafa571fdeccaa0%x>\n/P\n0\n/Length\n128\n/CF\n<<\n/StdCF\n<<\n/Type\n/CryptFilter\n/CFM\n/AESV2\n/Length\n128\n>>\n>>\n/StrF\n/StdCF\n/StmF\n/StdCF\n>>\n"
  },
  {
    "path": "test/TestHelper/MemoryObjectWriter.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\TestHelper;\n\nuse Bacon\\Pdf\\Exception\\InvalidArgumentException;\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\nuse SplFileObject;\n\n/**\n * This is a memory object writer which will ignore formatting for predictable test data.\n */\nclass MemoryObjectWriter extends ObjectWriter\n{\n    /**\n     * @var SplFileObject\n     */\n    private $fileObject;\n\n    /**\n     * {@inheritdoc}\n     */\n    public function __construct()\n    {\n        $this->fileObject = new SplFileObject('php://memory', 'w+b');\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeRawLine($data)\n    {\n        $this->fileObject->fwrite($data. \"\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function currentOffset()\n    {\n        return $this->fileObject->ftell();\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function startDictionary()\n    {\n        $this->fileObject->fwrite(\"<<\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function endDictionary()\n    {\n        $this->fileObject->fwrite(\">>\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function startArray()\n    {\n        $this->fileObject->fwrite(\"]\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function endArray()\n    {\n        $this->fileObject->fwrite(\"[\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeNull()\n    {\n        $this->fileObject->fwrite(\"null\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeBoolean($boolean)\n    {\n        $this->fileObject->fwrite(($boolean ? 'true' : 'false') . \"\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeNumber($number)\n    {\n        if (is_int($number)) {\n            $value = (string) $number;\n        } elseif (is_float($number)) {\n            $value = sprintf('%F', $number);\n        } else {\n            throw new InvalidArgumentException(sprintf(\n                'Expected int or float, got %s',\n                gettype($number)\n            ));\n        }\n\n        $this->fileObject->fwrite($value . \"\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeName($name)\n    {\n        $this->fileObject->fwrite('/' . $name . \"\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeLiteralString($string)\n    {\n        $this->fileObject->fwrite('(' . strtr($string, ['(' => '\\\\(', ')' => '\\\\)', '\\\\' => '\\\\\\\\']) . \")\\n\");\n    }\n\n    /**\n     * {@inheritdoc}\n     */\n    public function writeHexadecimalString($string)\n    {\n        $this->fileObject->fwrite('<' . bin2hex($string) . \">\\n\");\n    }\n\n    /**\n     * @return string\n     */\n    public function getData()\n    {\n        $currentPos = $this->fileObject->ftell();\n\n        if ($currentPos === 0) {\n            return '';\n        }\n\n        $this->fileObject->fseek(0);\n        $data = $this->fileObject->fread($currentPos);\n        $this->fileObject->fseek($currentPos);\n\n        return $data;\n    }\n}\n"
  },
  {
    "path": "test/Writer/ObjectWriterTest.php",
    "content": "<?php\n/**\n * BaconPdf\n *\n * @link      http://github.com/Bacon/BaconPdf For the canonical source repository\n * @copyright 2015 Ben Scholzen (DASPRiD)\n * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License\n */\n\nnamespace Bacon\\PdfTest\\Writer;\n\nuse Bacon\\Pdf\\Writer\\ObjectWriter;\nuse PHPUnit_Framework_TestCase as TestCase;\nuse SplFileObject;\n\n/**\n * @covers \\Bacon\\Pdf\\Writer\\ObjectWriter\n */\nclass ObjectWriterTest extends TestCase\n{\n    /**\n     * @var SplFileObject\n     */\n    private $fileObject;\n\n    /**\n     * @var ObjectWriter\n     */\n    private $objectWriter;\n\n    /**\n     * {@inheritdoc}\n     */\n    public function setUp()\n    {\n        $this->fileObject = new SplFileObject('php://memory', 'w+b');\n        $this->objectWriter = new ObjectWriter($this->fileObject);\n    }\n\n    public function testGetCurrentOffset()\n    {\n        $this->assertSame(0, $this->objectWriter->getCurrentOffset());\n        $this->fileObject->fwrite('foo');\n        $this->assertSame(3, $this->objectWriter->getCurrentOffset());\n    }\n\n    public function testObjectNumberAllocation()\n    {\n        $this->assertSame(1, $this->objectWriter->allocateObjectId());\n        $this->assertSame(2, $this->objectWriter->allocateObjectId());\n        $this->assertSame(3, $this->objectWriter->allocateObjectId());\n    }\n\n    public function testGetObjectOffsets()\n    {\n        $this->objectWriter->startObject();\n        $this->objectWriter->startObject();\n        $this->objectWriter->startObject();\n\n        $this->assertSame([1 => 0, 2 => 8, 3 => 16], $this->objectWriter->getObjectOffsets());\n    }\n\n    public function testStartObjectWithoutObjectId()\n    {\n        $this->objectWriter->startObject();\n        $this->objectWriter->startObject();\n        $this->assertSame(\"1 0 obj\\n2 0 obj\\n\", $this->getFileObjectData());\n    }\n\n    public function testStartObjectWithObjectId()\n    {\n        $this->objectWriter->startObject(10);\n        $this->assertSame(\"10 0 obj\\n\", $this->getFileObjectData());\n    }\n\n    public function testWriteIndirectReference()\n    {\n        $this->objectWriter->writeIndirectReference(1);\n        $this->assertSame('1 0 R', $this->getFileObjectData());\n    }\n\n    public function testEndObject()\n    {\n        $this->objectWriter->endObject();\n        $this->assertSame(\"\\nendobj\\n\", $this->getFileObjectData());\n    }\n\n    public function testWriteRawLine()\n    {\n        $this->objectWriter->writeRawLine('foo');\n        $this->assertSame(\"foo\\n\", $this->getFileObjectData());\n    }\n\n    public function testStartDictionary()\n    {\n        $this->objectWriter->startDictionary();\n        $this->assertSame('<<', $this->getFileObjectData());\n    }\n\n    public function testEndDictionary()\n    {\n        $this->objectWriter->endDictionary();\n        $this->assertSame('>>', $this->getFileObjectData());\n    }\n\n    public function testStartArray()\n    {\n        $this->objectWriter->startArray();\n        $this->assertSame('[', $this->getFileObjectData());\n    }\n\n    public function testEndArray()\n    {\n        $this->objectWriter->endArray();\n        $this->assertSame(']', $this->getFileObjectData());\n    }\n\n    public function testWriteNull()\n    {\n        $this->objectWriter->writeNull();\n        $this->assertSame('null', $this->getFileObjectData());\n    }\n\n    public function testWriteBooleanTrue()\n    {\n        $this->objectWriter->writeBoolean(true);\n        $this->assertSame('true', $this->getFileObjectData());\n    }\n\n    public function testWriteBooleanFalse()\n    {\n        $this->objectWriter->writeBoolean(false);\n        $this->assertSame('false', $this->getFileObjectData());\n    }\n\n    public function testWriteIntegerNumber()\n    {\n        $this->objectWriter->writeNumber(0);\n        $this->assertSame('0', $this->getFileObjectData());\n        $this->objectWriter->writeNumber(12);\n        $this->assertSame('0 12', $this->getFileObjectData());\n        $this->objectWriter->writeNumber(0);\n        $this->assertSame('0 12 0', $this->getFileObjectData());\n    }\n\n    public function testWriteFloatNumber()\n    {\n        $this->objectWriter->writeNumber(12.3456789123);\n        $this->assertSame('12.345679', $this->getFileObjectData());\n        $this->objectWriter->writeNumber(12.);\n        $this->assertSame('12.345679 12', $this->getFileObjectData());\n    }\n\n    public function testWriteName()\n    {\n        $this->objectWriter->writeName('foo');\n        $this->assertSame('/foo', $this->getFileObjectData());\n    }\n\n    public function testWriteLiteralString()\n    {\n        $this->objectWriter->writeLiteralString('foo(bar\\\\baz)bat');\n        $this->assertSame('(foo\\\\(bar\\\\\\\\baz\\\\)bat)', $this->getFileObjectData());\n    }\n\n    public function testWriteHexadecimalString()\n    {\n        $this->objectWriter->writeHexadecimalString('foo');\n        $this->assertSame('<666f6f>', $this->getFileObjectData());\n    }\n\n    /**\n     * @dataProvider whitespaceTestData\n     */\n    public function testWhitespaceHandling(array $methodCalls, $expectedData)\n    {\n        foreach ($methodCalls as $methodCall) {\n            if (!array_key_exists(1, $methodCall)) {\n                $methodCall[1] = [];\n            }\n\n            call_user_func_array([$this->objectWriter, $methodCall[0]], $methodCall[1]);\n        }\n\n        $this->assertSame($expectedData, $this->getFileObjectData());\n    }\n\n    /**\n     * @return array\n     */\n    public function whitespaceTestData()\n    {\n        return [\n            [[\n                ['writeIndirectReference', [1]],\n                ['writeIndirectReference', [2]],\n            ], '1 0 R 2 0 R'],\n            [[\n                ['startDictionary'],\n                ['endDictionary'],\n            ], '<<>>'],\n            [[\n                ['startArray'],\n                ['endArray'],\n            ], '[]'],\n            [[\n                ['startDictionary'],\n                ['writeNull'],\n                ['endDictionary'],\n            ], '<<null>>'],\n            [[\n                ['startArray'],\n                ['writeNull'],\n                ['endArray'],\n            ], '[null]'],\n            [[\n                ['writeNull'],\n                ['writeNull'],\n            ], 'null null'],\n            [[\n                ['writeBoolean', [true]],\n                ['writeBoolean', [false]],\n            ], 'true false'],\n            [[\n                ['writeNumber', [1]],\n                ['writeNumber', [1.1]],\n            ], '1 1.1'],\n            [[\n                ['writeNumber', [0]],\n                ['writeNumber', [0.0]],\n            ], '0 0'],\n            [[\n                ['writeLiteralString', ['foo']],\n                ['writeLiteralString', ['bar']],\n            ], '(foo)(bar)'],\n            [[\n                ['writeHexadecimalString', ['foo']],\n                ['writeHexadecimalString', ['bar']],\n            ], '<666f6f><626172>'],\n            [[\n                ['writeNull'],\n                ['writeRawLine', ['foo']],\n            ], \"nullfoo\\n\"],\n        ];\n    }\n\n    /**\n     * @return string\n     */\n    private function getFileObjectData()\n    {\n        $offset = $this->fileObject->ftell();\n        $this->fileObject->fseek(0);\n        $data = $this->fileObject->fread($offset);\n        $this->fileObject->fseek($offset);\n        return $data;\n    }\n}\n"
  }
]