@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add very basic support for generating PDF documents

Summary: Ref T13358. This is very minimal, but technically works. The eventual goal is to generate PDF invoices to make my life easier when I have to interact with Enterprise Vendor Procurement.

Test Plan: {F6672439}

Maniphest Tasks: T13358

Differential Revision: https://secure.phabricator.com/D20692

+625
+31
src/__phutil_library_map__.php
··· 3878 3878 'PhabricatorOwnersPathsSearchEngineAttachment' => 'applications/owners/engineextension/PhabricatorOwnersPathsSearchEngineAttachment.php', 3879 3879 'PhabricatorOwnersSchemaSpec' => 'applications/owners/storage/PhabricatorOwnersSchemaSpec.php', 3880 3880 'PhabricatorOwnersSearchField' => 'applications/owners/searchfield/PhabricatorOwnersSearchField.php', 3881 + 'PhabricatorPDFCatalogObject' => 'applications/phortune/pdf/PhabricatorPDFCatalogObject.php', 3882 + 'PhabricatorPDFContentsObject' => 'applications/phortune/pdf/PhabricatorPDFContentsObject.php', 3881 3883 'PhabricatorPDFDocumentEngine' => 'applications/files/document/PhabricatorPDFDocumentEngine.php', 3884 + 'PhabricatorPDFFontObject' => 'applications/phortune/pdf/PhabricatorPDFFontObject.php', 3885 + 'PhabricatorPDFFragment' => 'applications/phortune/pdf/PhabricatorPDFFragment.php', 3886 + 'PhabricatorPDFFragmentOffset' => 'applications/phortune/pdf/PhabricatorPDFFragmentOffset.php', 3887 + 'PhabricatorPDFGenerator' => 'applications/phortune/pdf/PhabricatorPDFGenerator.php', 3888 + 'PhabricatorPDFHeadFragment' => 'applications/phortune/pdf/PhabricatorPDFHeadFragment.php', 3889 + 'PhabricatorPDFInfoObject' => 'applications/phortune/pdf/PhabricatorPDFInfoObject.php', 3890 + 'PhabricatorPDFIterator' => 'applications/phortune/pdf/PhabricatorPDFIterator.php', 3891 + 'PhabricatorPDFObject' => 'applications/phortune/pdf/PhabricatorPDFObject.php', 3892 + 'PhabricatorPDFPageObject' => 'applications/phortune/pdf/PhabricatorPDFPageObject.php', 3893 + 'PhabricatorPDFPagesObject' => 'applications/phortune/pdf/PhabricatorPDFPagesObject.php', 3894 + 'PhabricatorPDFResourcesObject' => 'applications/phortune/pdf/PhabricatorPDFResourcesObject.php', 3895 + 'PhabricatorPDFTailFragment' => 'applications/phortune/pdf/PhabricatorPDFTailFragment.php', 3882 3896 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', 3883 3897 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 3884 3898 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', ··· 10101 10115 'PhabricatorOwnersPathsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 10102 10116 'PhabricatorOwnersSchemaSpec' => 'PhabricatorConfigSchemaSpec', 10103 10117 'PhabricatorOwnersSearchField' => 'PhabricatorSearchTokenizerField', 10118 + 'PhabricatorPDFCatalogObject' => 'PhabricatorPDFObject', 10119 + 'PhabricatorPDFContentsObject' => 'PhabricatorPDFObject', 10104 10120 'PhabricatorPDFDocumentEngine' => 'PhabricatorDocumentEngine', 10121 + 'PhabricatorPDFFontObject' => 'PhabricatorPDFObject', 10122 + 'PhabricatorPDFFragment' => 'Phobject', 10123 + 'PhabricatorPDFFragmentOffset' => 'Phobject', 10124 + 'PhabricatorPDFGenerator' => 'Phobject', 10125 + 'PhabricatorPDFHeadFragment' => 'PhabricatorPDFFragment', 10126 + 'PhabricatorPDFInfoObject' => 'PhabricatorPDFObject', 10127 + 'PhabricatorPDFIterator' => array( 10128 + 'Phobject', 10129 + 'Iterator', 10130 + ), 10131 + 'PhabricatorPDFObject' => 'PhabricatorPDFFragment', 10132 + 'PhabricatorPDFPageObject' => 'PhabricatorPDFObject', 10133 + 'PhabricatorPDFPagesObject' => 'PhabricatorPDFObject', 10134 + 'PhabricatorPDFResourcesObject' => 'PhabricatorPDFObject', 10135 + 'PhabricatorPDFTailFragment' => 'PhabricatorPDFFragment', 10105 10136 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', 10106 10137 'PhabricatorPHID' => 'Phobject', 10107 10138 'PhabricatorPHIDConstants' => 'Phobject',
+26
src/applications/phortune/pdf/PhabricatorPDFCatalogObject.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFCatalogObject 4 + extends PhabricatorPDFObject { 5 + 6 + private $pagesObject; 7 + 8 + public function setPagesObject(PhabricatorPDFPagesObject $pages_object) { 9 + $this->pagesObject = $this->newChildObject($pages_object); 10 + return $this; 11 + } 12 + 13 + public function getPagesObject() { 14 + return $this->pagesObject; 15 + } 16 + 17 + protected function writeObject() { 18 + $this->writeLine('/Type /Catalog'); 19 + 20 + $pages_object = $this->getPagesObject(); 21 + if ($pages_object) { 22 + $this->writeLine('/Pages %d 0 R', $pages_object->getObjectIndex()); 23 + } 24 + } 25 + 26 + }
+25
src/applications/phortune/pdf/PhabricatorPDFContentsObject.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFContentsObject 4 + extends PhabricatorPDFObject { 5 + 6 + private $rawContent; 7 + 8 + public function setRawContent($raw_content) { 9 + $this->rawContent = $raw_content; 10 + return $this; 11 + } 12 + 13 + public function getRawContent() { 14 + return $this->rawContent; 15 + } 16 + 17 + protected function writeObject() { 18 + $data = $this->getRawContent(); 19 + 20 + $stream_length = $this->newStream($data); 21 + 22 + $this->writeLine('/Filter /FlateDecode /Length %d', $stream_length); 23 + } 24 + 25 + }
+14
src/applications/phortune/pdf/PhabricatorPDFFontObject.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFFontObject 4 + extends PhabricatorPDFObject { 5 + 6 + protected function writeObject() { 7 + $this->writeLine('/Type /Font'); 8 + 9 + $this->writeLine('/BaseFont /Helvetica-Bold'); 10 + $this->writeLine('/Subtype /Type1'); 11 + $this->writeLine('/Encoding /WinAnsiEncoding'); 12 + } 13 + 14 + }
+38
src/applications/phortune/pdf/PhabricatorPDFFragment.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorPDFFragment 4 + extends Phobject { 5 + 6 + private $rope; 7 + 8 + public function getAsBytes() { 9 + $this->rope = new PhutilRope(); 10 + 11 + $this->writeFragment(); 12 + 13 + $rope = $this->rope; 14 + $this->rope = null; 15 + 16 + return $rope->getAsString(); 17 + } 18 + 19 + public function hasRefTableEntry() { 20 + return false; 21 + } 22 + 23 + abstract protected function writeFragment(); 24 + 25 + final protected function writeLine($pattern) { 26 + $pattern = $pattern."\n"; 27 + 28 + $argv = func_get_args(); 29 + $argv[0] = $pattern; 30 + 31 + $line = call_user_func_array('sprintf', $argv); 32 + 33 + $this->rope->append($line); 34 + 35 + return $this; 36 + } 37 + 38 + }
+27
src/applications/phortune/pdf/PhabricatorPDFFragmentOffset.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFFragmentOffset 4 + extends Phobject { 5 + 6 + private $fragment; 7 + private $offset; 8 + 9 + public function setFragment(PhabricatorPDFFragment $fragment) { 10 + $this->fragment = $fragment; 11 + return $this; 12 + } 13 + 14 + public function getFragment() { 15 + return $this->fragment; 16 + } 17 + 18 + public function setOffset($offset) { 19 + $this->offset = $offset; 20 + return $this; 21 + } 22 + 23 + public function getOffset() { 24 + return $this->offset; 25 + } 26 + 27 + }
+59
src/applications/phortune/pdf/PhabricatorPDFGenerator.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFGenerator 4 + extends Phobject { 5 + 6 + private $objects = array(); 7 + private $hasIterator = false; 8 + 9 + private $infoObject; 10 + private $catalogObject; 11 + 12 + public function addObject(PhabricatorPDFObject $object) { 13 + if ($this->hasIterator) { 14 + throw new Exception( 15 + pht( 16 + 'This generator has already emitted an iterator. You can not '. 17 + 'modify the PDF document after you begin writing it.')); 18 + } 19 + 20 + $this->objects[] = $object; 21 + $index = count($this->objects); 22 + 23 + $object->setGenerator($this, $index); 24 + 25 + return $this; 26 + } 27 + 28 + public function getObjects() { 29 + return $this->objects; 30 + } 31 + 32 + public function newIterator() { 33 + $this->hasIterator = true; 34 + return id(new PhabricatorPDFIterator()) 35 + ->setGenerator($this); 36 + } 37 + 38 + public function setInfoObject(PhabricatorPDFInfoObject $info_object) { 39 + $this->addObject($info_object); 40 + $this->infoObject = $info_object; 41 + return $this; 42 + } 43 + 44 + public function getInfoObject() { 45 + return $this->infoObject; 46 + } 47 + 48 + public function setCatalogObject( 49 + PhabricatorPDFCatalogObject $catalog_object) { 50 + $this->addObject($catalog_object); 51 + $this->catalogObject = $catalog_object; 52 + return $this; 53 + } 54 + 55 + public function getCatalogObject() { 56 + return $this->catalogObject; 57 + } 58 + 59 + }
+10
src/applications/phortune/pdf/PhabricatorPDFHeadFragment.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFHeadFragment 4 + extends PhabricatorPDFFragment { 5 + 6 + protected function writeFragment() { 7 + $this->writeLine('%s', '%PDF-1.3'); 8 + } 9 + 10 + }
+11
src/applications/phortune/pdf/PhabricatorPDFInfoObject.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFInfoObject 4 + extends PhabricatorPDFObject { 5 + 6 + final protected function writeObject() { 7 + $this->writeLine('/Producer (Phabricator 20190801)'); 8 + $this->writeLine('/CreationDate (D:%s)', date('YmdHis')); 9 + } 10 + 11 + }
+103
src/applications/phortune/pdf/PhabricatorPDFIterator.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFIterator 4 + extends Phobject 5 + implements Iterator { 6 + 7 + private $generator; 8 + private $hasRewound; 9 + 10 + private $fragments; 11 + private $fragmentKey; 12 + private $fragmentBytes; 13 + private $fragmentOffsets = array(); 14 + private $byteLength; 15 + 16 + public function setGenerator(PhabricatorPDFGenerator $generator) { 17 + if ($this->generator) { 18 + throw new Exception( 19 + pht( 20 + 'This iterator already has a generator. You can not modify the '. 21 + 'generator for a given iterator.')); 22 + } 23 + 24 + $this->generator = $generator; 25 + 26 + return $this; 27 + } 28 + 29 + public function getGenerator() { 30 + if (!$this->generator) { 31 + throw new Exception( 32 + pht( 33 + 'This PDF iterator has no associated PDF generator.')); 34 + } 35 + 36 + return $this->generator; 37 + } 38 + 39 + public function getFragmentOffsets() { 40 + return $this->fragmentOffsets; 41 + } 42 + 43 + public function current() { 44 + return $this->fragmentBytes; 45 + } 46 + 47 + public function key() { 48 + return $this->framgentKey; 49 + } 50 + 51 + public function next() { 52 + $this->fragmentKey++; 53 + 54 + if (!$this->valid()) { 55 + return; 56 + } 57 + 58 + $fragment = $this->fragments[$this->fragmentKey]; 59 + 60 + $this->fragmentOffsets[] = id(new PhabricatorPDFFragmentOffset()) 61 + ->setFragment($fragment) 62 + ->setOffset($this->byteLength); 63 + 64 + $bytes = $fragment->getAsBytes(); 65 + 66 + $this->fragmentBytes = $bytes; 67 + $this->byteLength += strlen($bytes); 68 + } 69 + 70 + public function rewind() { 71 + if ($this->hasRewound) { 72 + throw new Exception( 73 + pht( 74 + 'PDF iterators may not be rewound. Create a new iterator to emit '. 75 + 'another PDF.')); 76 + } 77 + 78 + $generator = $this->getGenerator(); 79 + $objects = $generator->getObjects(); 80 + 81 + $this->fragments = array(); 82 + $this->fragments[] = new PhabricatorPDFHeadFragment(); 83 + 84 + foreach ($objects as $object) { 85 + $this->fragments[] = $object; 86 + } 87 + 88 + $this->fragments[] = id(new PhabricatorPDFTailFragment()) 89 + ->setIterator($this); 90 + 91 + $this->hasRewound = true; 92 + 93 + $this->fragmentKey = -1; 94 + $this->byteLength = 0; 95 + 96 + $this->next(); 97 + } 98 + 99 + public function valid() { 100 + return isset($this->fragments[$this->fragmentKey]); 101 + } 102 + 103 + }
+95
src/applications/phortune/pdf/PhabricatorPDFObject.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorPDFObject 4 + extends PhabricatorPDFFragment { 5 + 6 + private $generator; 7 + private $objectIndex; 8 + private $children = array(); 9 + private $streams = array(); 10 + 11 + final public function hasRefTableEntry() { 12 + return true; 13 + } 14 + 15 + final protected function writeFragment() { 16 + $this->writeLine('%d 0 obj', $this->getObjectIndex()); 17 + $this->writeLine('<<'); 18 + $this->writeObject(); 19 + $this->writeLine('>>'); 20 + 21 + $streams = $this->streams; 22 + $this->streams = array(); 23 + foreach ($streams as $stream) { 24 + $this->writeLine('stream'); 25 + $this->writeLine('%s', $stream); 26 + $this->writeLine('endstream'); 27 + } 28 + 29 + $this->writeLine('endobj'); 30 + } 31 + 32 + final public function setGenerator( 33 + PhabricatorPDFGenerator $generator, 34 + $index) { 35 + 36 + if ($this->getGenerator()) { 37 + throw new Exception( 38 + pht( 39 + 'This PDF object is already registered with a PDF generator. You '. 40 + 'can not register an object with more than one generator.')); 41 + } 42 + 43 + $this->generator = $generator; 44 + $this->objectIndex = $index; 45 + 46 + foreach ($this->getChildren() as $child) { 47 + $generator->addObject($child); 48 + } 49 + 50 + return $this; 51 + } 52 + 53 + final public function getGenerator() { 54 + return $this->generator; 55 + } 56 + 57 + final public function getObjectIndex() { 58 + if (!$this->objectIndex) { 59 + throw new Exception( 60 + pht( 61 + 'Trying to get index for object ("%s") which has not been '. 62 + 'registered with a generator.', 63 + get_class($this))); 64 + } 65 + 66 + return $this->objectIndex; 67 + } 68 + 69 + final protected function newChildObject(PhabricatorPDFObject $object) { 70 + if ($this->generator) { 71 + throw new Exception( 72 + pht( 73 + 'Trying to add a new PDF Object child after already registering '. 74 + 'the object with a generator.')); 75 + } 76 + 77 + $this->children[] = $object; 78 + return $object; 79 + } 80 + 81 + private function getChildren() { 82 + return $this->children; 83 + } 84 + 85 + abstract protected function writeObject(); 86 + 87 + final protected function newStream($raw_data) { 88 + $stream_data = gzcompress($raw_data); 89 + 90 + $this->streams[] = $stream_data; 91 + 92 + return strlen($stream_data); 93 + } 94 + 95 + }
+48
src/applications/phortune/pdf/PhabricatorPDFPageObject.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFPageObject 4 + extends PhabricatorPDFObject { 5 + 6 + private $pagesObject; 7 + private $contentsObject; 8 + private $resourcesObject; 9 + 10 + public function setPagesObject(PhabricatorPDFPagesObject $pages) { 11 + $this->pagesObject = $pages; 12 + return $this; 13 + } 14 + 15 + public function setContentsObject(PhabricatorPDFContentsObject $contents) { 16 + $this->contentsObject = $this->newChildObject($contents); 17 + return $this; 18 + } 19 + 20 + public function setResourcesObject(PhabricatorPDFResourcesObject $resources) { 21 + $this->resourcesObject = $this->newChildObject($resources); 22 + return $this; 23 + } 24 + 25 + protected function writeObject() { 26 + $this->writeLine('/Type /Page'); 27 + 28 + $pages_object = $this->pagesObject; 29 + $contents_object = $this->contentsObject; 30 + $resources_object = $this->resourcesObject; 31 + 32 + if ($pages_object) { 33 + $pages_index = $pages_object->getObjectIndex(); 34 + $this->writeLine('/Parent %d 0 R', $pages_index); 35 + } 36 + 37 + if ($contents_object) { 38 + $contents_index = $contents_object->getObjectIndex(); 39 + $this->writeLine('/Contents %d 0 R', $contents_index); 40 + } 41 + 42 + if ($resources_object) { 43 + $resources_index = $resources_object->getObjectIndex(); 44 + $this->writeLine('/Resources %d 0 R', $resources_index); 45 + } 46 + } 47 + 48 + }
+38
src/applications/phortune/pdf/PhabricatorPDFPagesObject.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFPagesObject 4 + extends PhabricatorPDFObject { 5 + 6 + private $pageObjects = array(); 7 + 8 + public function addPageObject(PhabricatorPDFPageObject $page) { 9 + $page->setPagesObject($this); 10 + $this->pageObjects[] = $this->newChildObject($page); 11 + return $this; 12 + } 13 + 14 + public function getPageObjects() { 15 + return $this->pageObjects; 16 + } 17 + 18 + protected function writeObject() { 19 + $this->writeLine('/Type /Pages'); 20 + 21 + $page_objects = $this->getPageObjects(); 22 + 23 + $this->writeLine('/Count %d', count($page_objects)); 24 + $this->writeLine('/MediaBox [%d %d %0.2f %0.2f]', 0, 0, 595.28, 841.89); 25 + 26 + if ($page_objects) { 27 + $kids = array(); 28 + foreach ($page_objects as $page_object) { 29 + $kids[] = sprintf( 30 + '%d 0 R', 31 + $page_object->getObjectIndex()); 32 + } 33 + 34 + $this->writeLine('/Kids [%s]', implode(' ', $kids)); 35 + } 36 + } 37 + 38 + }
+28
src/applications/phortune/pdf/PhabricatorPDFResourcesObject.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFResourcesObject 4 + extends PhabricatorPDFObject { 5 + 6 + private $fontObjects = array(); 7 + 8 + public function addFontObject(PhabricatorPDFFontObject $font) { 9 + $this->fontObjects[] = $this->newChildObject($font); 10 + return $this; 11 + } 12 + 13 + public function getFontObjects() { 14 + return $this->fontObjects; 15 + } 16 + 17 + protected function writeObject() { 18 + $this->writeLine('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); 19 + 20 + $fonts = $this->getFontObjects(); 21 + foreach ($fonts as $font) { 22 + $this->writeLine('/Font <<'); 23 + $this->writeLine('/F%d %d 0 R', 1, $font->getObjectIndex()); 24 + $this->writeLine('>>'); 25 + } 26 + } 27 + 28 + }
+72
src/applications/phortune/pdf/PhabricatorPDFTailFragment.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFTailFragment 4 + extends PhabricatorPDFFragment { 5 + 6 + private $iterator; 7 + 8 + public function setIterator(PhabricatorPDFIterator $iterator) { 9 + $this->iterator = $iterator; 10 + return $this; 11 + } 12 + 13 + public function getIterator() { 14 + return $this->iterator; 15 + } 16 + 17 + protected function writeFragment() { 18 + $iterator = $this->getIterator(); 19 + $generator = $iterator->getGenerator(); 20 + $objects = $generator->getObjects(); 21 + 22 + $xref_offset = null; 23 + 24 + $this->writeLine('xref'); 25 + $this->writeLine('0 %d', count($objects) + 1); 26 + $this->writeLine('%010d %05d f ', 0, 0xFFFF); 27 + 28 + $offset_map = array(); 29 + 30 + $fragment_offsets = $iterator->getFragmentOffsets(); 31 + foreach ($fragment_offsets as $fragment_offset) { 32 + $fragment = $fragment_offset->getFragment(); 33 + $offset = $fragment_offset->getOffset(); 34 + 35 + if ($fragment === $this) { 36 + $xref_offset = $offset; 37 + } 38 + 39 + if (!$fragment->hasRefTableEntry()) { 40 + continue; 41 + } 42 + 43 + $offset_map[$fragment->getObjectIndex()] = $offset; 44 + } 45 + 46 + ksort($offset_map); 47 + 48 + foreach ($offset_map as $offset) { 49 + $this->writeLine('%010d %05d n ', $offset, 0); 50 + } 51 + 52 + $this->writeLine('trailer'); 53 + $this->writeLine('<<'); 54 + $this->writeLine('/Size %d', count($objects) + 1); 55 + 56 + $info_object = $generator->getInfoObject(); 57 + if ($info_object) { 58 + $this->writeLine('/Info %d 0 R', $info_object->getObjectIndex()); 59 + } 60 + 61 + $catalog_object = $generator->getCatalogObject(); 62 + if ($catalog_object) { 63 + $this->writeLine('/Root %d 0 R', $catalog_object->getObjectIndex()); 64 + } 65 + 66 + $this->writeLine('>>'); 67 + $this->writeLine('startxref'); 68 + $this->writeLine('%d', $xref_offset); 69 + $this->writeLine('%s', '%%EOF'); 70 + } 71 + 72 + }