Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Cpdf often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Cpdf, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
21 | class Cpdf |
||
|
|||
22 | { |
||
23 | |||
24 | /** |
||
25 | * @var integer The current number of pdf objects in the document |
||
26 | */ |
||
27 | public $numObj = 0; |
||
28 | |||
29 | /** |
||
30 | * @var array This array contains all of the pdf objects, ready for final assembly |
||
31 | */ |
||
32 | public $objects = array(); |
||
33 | |||
34 | /** |
||
35 | * @var integer The objectId (number within the objects array) of the document catalog |
||
36 | */ |
||
37 | public $catalogId; |
||
38 | |||
39 | /** |
||
40 | * @var array Array carrying information about the fonts that the system currently knows about |
||
41 | * Used to ensure that a font is not loaded twice, among other things |
||
42 | */ |
||
43 | public $fonts = array(); |
||
44 | |||
45 | /** |
||
46 | * @var string The default font metrics file to use if no other font has been loaded. |
||
47 | * The path to the directory containing the font metrics should be included |
||
48 | */ |
||
49 | public $defaultFont = './fonts/Helvetica.afm'; |
||
50 | |||
51 | /** |
||
52 | * @string A record of the current font |
||
53 | */ |
||
54 | public $currentFont = ''; |
||
55 | |||
56 | /** |
||
57 | * @var string The current base font |
||
58 | */ |
||
59 | public $currentBaseFont = ''; |
||
60 | |||
61 | /** |
||
62 | * @var integer The number of the current font within the font array |
||
63 | */ |
||
64 | public $currentFontNum = 0; |
||
65 | |||
66 | /** |
||
67 | * @var integer |
||
68 | */ |
||
69 | public $currentNode; |
||
70 | |||
71 | /** |
||
72 | * @var integer Object number of the current page |
||
73 | */ |
||
74 | public $currentPage; |
||
75 | |||
76 | /** |
||
77 | * @var integer Object number of the currently active contents block |
||
78 | */ |
||
79 | public $currentContents; |
||
80 | |||
81 | /** |
||
82 | * @var integer Number of fonts within the system |
||
83 | */ |
||
84 | public $numFonts = 0; |
||
85 | |||
86 | /** |
||
87 | * @var integer Number of graphic state resources used |
||
88 | */ |
||
89 | private $numStates = 0; |
||
90 | |||
91 | /** |
||
92 | * @var array Number of graphic state resources used |
||
93 | */ |
||
94 | private $gstates = array(); |
||
95 | |||
96 | /** |
||
97 | * @var array Current color for fill operations, defaults to inactive value, |
||
98 | * all three components should be between 0 and 1 inclusive when active |
||
99 | */ |
||
100 | public $currentColor = null; |
||
101 | |||
102 | /** |
||
103 | * @var array Current color for stroke operations (lines etc.) |
||
104 | */ |
||
105 | public $currentStrokeColor = null; |
||
106 | |||
107 | /** |
||
108 | * @var string Fill rule (nonzero or evenodd) |
||
109 | */ |
||
110 | public $fillRule = "nonzero"; |
||
111 | |||
112 | /** |
||
113 | * @var string Current style that lines are drawn in |
||
114 | */ |
||
115 | public $currentLineStyle = ''; |
||
116 | |||
117 | /** |
||
118 | * @var array Current line transparency (partial graphics state) |
||
119 | */ |
||
120 | public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0); |
||
121 | |||
122 | /** |
||
123 | * array Current fill transparency (partial graphics state) |
||
124 | */ |
||
125 | public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0); |
||
126 | |||
127 | /** |
||
128 | * @var array An array which is used to save the state of the document, mainly the colors and styles |
||
129 | * it is used to temporarily change to another state, then change back to what it was before |
||
130 | */ |
||
131 | public $stateStack = array(); |
||
132 | |||
133 | /** |
||
134 | * @var integer Number of elements within the state stack |
||
135 | */ |
||
136 | public $nStateStack = 0; |
||
137 | |||
138 | /** |
||
139 | * @var integer Number of page objects within the document |
||
140 | */ |
||
141 | public $numPages = 0; |
||
142 | |||
143 | /** |
||
144 | * @var array Object Id storage stack |
||
145 | */ |
||
146 | public $stack = array(); |
||
147 | |||
148 | /** |
||
149 | * @var integer Number of elements within the object Id storage stack |
||
150 | */ |
||
151 | public $nStack = 0; |
||
152 | |||
153 | /** |
||
154 | * an array which contains information about the objects which are not firmly attached to pages |
||
155 | * these have been added with the addObject function |
||
156 | */ |
||
157 | public $looseObjects = array(); |
||
158 | |||
159 | /** |
||
160 | * array contains information about how the loose objects are to be added to the document |
||
161 | */ |
||
162 | public $addLooseObjects = array(); |
||
163 | |||
164 | /** |
||
165 | * @var integer The objectId of the information object for the document |
||
166 | * this contains authorship, title etc. |
||
167 | */ |
||
168 | public $infoObject = 0; |
||
169 | |||
170 | /** |
||
171 | * @var integer Number of images being tracked within the document |
||
172 | */ |
||
173 | public $numImages = 0; |
||
174 | |||
175 | /** |
||
176 | * @var array An array containing options about the document |
||
177 | * it defaults to turning on the compression of the objects |
||
178 | */ |
||
179 | public $options = array('compression' => true); |
||
180 | |||
181 | /** |
||
182 | * @var integer The objectId of the first page of the document |
||
183 | */ |
||
184 | public $firstPageId; |
||
185 | |||
186 | /** |
||
187 | * @var integer The object Id of the procset object |
||
188 | */ |
||
189 | public $procsetObjectId; |
||
190 | |||
191 | /** |
||
192 | * @var array Store the information about the relationship between font families |
||
193 | * this used so that the code knows which font is the bold version of another font, etc. |
||
194 | * the value of this array is initialised in the constructor function. |
||
195 | */ |
||
196 | public $fontFamilies = array(); |
||
197 | |||
198 | /** |
||
199 | * @var string Folder for php serialized formats of font metrics files. |
||
200 | * If empty string, use same folder as original metrics files. |
||
201 | * This can be passed in from class creator. |
||
202 | * If this folder does not exist or is not writable, Cpdf will be **much** slower. |
||
203 | * Because of potential trouble with php safe mode, folder cannot be created at runtime. |
||
204 | */ |
||
205 | public $fontcache = ''; |
||
206 | |||
207 | /** |
||
208 | * @var integer The version of the font metrics cache file. |
||
209 | * This value must be manually incremented whenever the internal font data structure is modified. |
||
210 | */ |
||
211 | public $fontcacheVersion = 6; |
||
212 | |||
213 | /** |
||
214 | * @var string Temporary folder. |
||
215 | * If empty string, will attempt system tmp folder. |
||
216 | * This can be passed in from class creator. |
||
217 | */ |
||
218 | public $tmp = ''; |
||
219 | |||
220 | /** |
||
221 | * @var string Track if the current font is bolded or italicised |
||
222 | */ |
||
223 | public $currentTextState = ''; |
||
224 | |||
225 | /** |
||
226 | * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information |
||
227 | */ |
||
228 | public $messages = ''; |
||
229 | |||
230 | /** |
||
231 | * @var string The encryption array for the document encryption is stored here |
||
232 | */ |
||
233 | public $arc4 = ''; |
||
234 | |||
235 | /** |
||
236 | * @var integer The object Id of the encryption information |
||
237 | */ |
||
238 | public $arc4_objnum = 0; |
||
239 | |||
240 | /** |
||
241 | * @var string The file identifier, used to uniquely identify a pdf document |
||
242 | */ |
||
243 | public $fileIdentifier = ''; |
||
244 | |||
245 | /** |
||
246 | * @var boolean A flag to say if a document is to be encrypted or not |
||
247 | */ |
||
248 | public $encrypted = false; |
||
249 | |||
250 | /** |
||
251 | * @var string The encryption key for the encryption of all the document content (structure is not encrypted) |
||
252 | */ |
||
253 | public $encryptionKey = ''; |
||
254 | |||
255 | /** |
||
256 | * @var array Array which forms a stack to keep track of nested callback functions |
||
257 | */ |
||
258 | public $callback = array(); |
||
259 | |||
260 | /** |
||
261 | * @var integer The number of callback functions in the callback array |
||
262 | */ |
||
263 | public $nCallback = 0; |
||
264 | |||
265 | /** |
||
266 | * @var array Store label->id pairs for named destinations, these will be used to replace internal links |
||
267 | * done this way so that destinations can be defined after the location that links to them |
||
268 | */ |
||
269 | public $destinations = array(); |
||
270 | |||
271 | /** |
||
272 | * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the |
||
273 | * publiciables within the class, so that the user can rollback at will (from each 'start' command) |
||
274 | * note that this includes the objects array, so these can be large. |
||
275 | */ |
||
276 | public $checkpoint = ''; |
||
277 | |||
278 | /** |
||
279 | * @var array Table of Image origin filenames and image labels which were already added with o_image(). |
||
280 | * Allows to merge identical images |
||
281 | */ |
||
282 | public $imagelist = array(); |
||
283 | |||
284 | /** |
||
285 | * @var boolean Whether the text passed in should be treated as Unicode or just local character set. |
||
286 | */ |
||
287 | public $isUnicode = false; |
||
288 | |||
289 | /** |
||
290 | * @var string the JavaScript code of the document |
||
291 | */ |
||
292 | public $javascript = ''; |
||
293 | |||
294 | /** |
||
295 | * @var boolean whether the compression is possible |
||
296 | */ |
||
297 | protected $compressionReady = false; |
||
298 | |||
299 | /** |
||
300 | * @var array Current page size |
||
301 | */ |
||
302 | protected $currentPageSize = array("width" => 0, "height" => 0); |
||
303 | |||
304 | /** |
||
305 | * @var array All the chars that will be required in the font subsets |
||
306 | */ |
||
307 | protected $stringSubsets = array(); |
||
308 | |||
309 | /** |
||
310 | * @var string The target internal encoding |
||
311 | */ |
||
312 | static protected $targetEncoding = 'Windows-1252'; |
||
313 | |||
314 | /** |
||
315 | * @var array The list of the core fonts |
||
316 | */ |
||
317 | static protected $coreFonts = array( |
||
318 | 'courier', |
||
319 | 'courier-bold', |
||
320 | 'courier-oblique', |
||
321 | 'courier-boldoblique', |
||
322 | 'helvetica', |
||
323 | 'helvetica-bold', |
||
324 | 'helvetica-oblique', |
||
325 | 'helvetica-boldoblique', |
||
326 | 'times-roman', |
||
327 | 'times-bold', |
||
328 | 'times-italic', |
||
329 | 'times-bolditalic', |
||
330 | 'symbol', |
||
331 | 'zapfdingbats' |
||
332 | ); |
||
333 | |||
334 | /** |
||
335 | * Class constructor |
||
336 | * This will start a new document |
||
337 | * |
||
338 | * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero. |
||
339 | * @param boolean $isUnicode Whether text will be treated as Unicode or not. |
||
340 | * @param string $fontcache The font cache folder |
||
341 | * @param string $tmp The temporary folder |
||
342 | */ |
||
343 | function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '') |
||
344 | { |
||
345 | $this->isUnicode = $isUnicode; |
||
346 | $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\"); |
||
347 | $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir()); |
||
348 | $this->newDocument($pageSize); |
||
349 | |||
350 | $this->compressionReady = function_exists('gzcompress'); |
||
351 | |||
352 | if (in_array('Windows-1252', mb_list_encodings())) { |
||
353 | self::$targetEncoding = 'Windows-1252'; |
||
354 | } |
||
355 | |||
356 | // also initialize the font families that are known about already |
||
357 | $this->setFontFamily('init'); |
||
358 | } |
||
359 | |||
360 | /** |
||
361 | * Document object methods (internal use only) |
||
362 | * |
||
363 | * There is about one object method for each type of object in the pdf document |
||
364 | * Each function has the same call list ($id,$action,$options). |
||
365 | * $id = the object ID of the object, or what it is to be if it is being created |
||
366 | * $action = a string specifying the action to be performed, though ALL must support: |
||
367 | * 'new' - create the object with the id $id |
||
368 | * 'out' - produce the output for the pdf object |
||
369 | * $options = optional, a string or array containing the various parameters for the object |
||
370 | * |
||
371 | * These, in conjunction with the output function are the ONLY way for output to be produced |
||
372 | * within the pdf 'file'. |
||
373 | */ |
||
374 | |||
375 | /** |
||
376 | * Destination object, used to specify the location for the user to jump to, presently on opening |
||
377 | * |
||
378 | * @param $id |
||
379 | * @param $action |
||
380 | * @param string $options |
||
381 | * @return string|null |
||
382 | */ |
||
383 | protected function o_destination($id, $action, $options = '') |
||
419 | |||
420 | /** |
||
421 | * set the viewer preferences |
||
422 | * |
||
423 | * @param $id |
||
424 | * @param $action |
||
425 | * @param string|array $options |
||
426 | * @return string|null |
||
427 | */ |
||
428 | protected function o_viewerPreferences($id, $action, $options = '') |
||
528 | |||
529 | /** |
||
530 | * define the document catalog, the overall controller for the document |
||
531 | * |
||
532 | * @param $id |
||
533 | * @param $action |
||
534 | * @param string|array $options |
||
535 | * @return string|null |
||
536 | */ |
||
537 | protected function o_catalog($id, $action, $options = '') |
||
602 | |||
603 | /** |
||
604 | * object which is a parent to the pages in the document |
||
605 | * |
||
606 | * @param $id |
||
607 | * @param $action |
||
608 | * @param string $options |
||
609 | * @return string|null |
||
610 | */ |
||
611 | protected function o_pages($id, $action, $options = '') |
||
750 | |||
751 | /** |
||
752 | * define the outlines in the doc, empty for now |
||
753 | * |
||
754 | * @param $id |
||
755 | * @param $action |
||
756 | * @param string $options |
||
757 | * @return string|null |
||
758 | */ |
||
759 | protected function o_outlines($id, $action, $options = '') |
||
792 | |||
793 | /** |
||
794 | * an object to hold the font description |
||
795 | * |
||
796 | * @param $id |
||
797 | * @param $action |
||
798 | * @param string|array $options |
||
799 | * @return string|null |
||
800 | */ |
||
801 | protected function o_font($id, $action, $options = '') |
||
981 | |||
982 | /** |
||
983 | * a font descriptor, needed for including additional fonts |
||
984 | * |
||
985 | * @param $id |
||
986 | * @param $action |
||
987 | * @param string $options |
||
988 | * @return null|string |
||
989 | */ |
||
990 | protected function o_fontDescriptor($id, $action, $options = '') |
||
1046 | |||
1047 | /** |
||
1048 | * the font encoding |
||
1049 | * |
||
1050 | * @param $id |
||
1051 | * @param $action |
||
1052 | * @param string $options |
||
1053 | * @return null|string |
||
1054 | */ |
||
1055 | protected function o_fontEncoding($id, $action, $options = '') |
||
1099 | |||
1100 | /** |
||
1101 | * a descendent cid font, needed for unicode fonts |
||
1102 | * |
||
1103 | * @param $id |
||
1104 | * @param $action |
||
1105 | * @param string|array $options |
||
1106 | * @return null|string |
||
1107 | */ |
||
1108 | protected function o_fontDescendentCID($id, $action, $options = '') |
||
1109 | { |
||
1110 | if ($action !== 'new') { |
||
1111 | $o = &$this->objects[$id]; |
||
1112 | } |
||
1113 | |||
1114 | switch ($action) { |
||
1115 | case 'new': |
||
1116 | $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options); |
||
1117 | |||
1118 | // we need a CID system info section |
||
1119 | $cidSystemInfoId = ++$this->numObj; |
||
1120 | $this->o_contents($cidSystemInfoId, 'new', 'raw'); |
||
1121 | $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId; |
||
1122 | $res = "<</Registry (Adobe)\n"; // A string identifying an issuer of character collections |
||
1123 | $res .= "/Ordering (UCS)\n"; // A string that uniquely names a character collection issued by a specific registry |
||
1124 | $res .= "/Supplement 0\n"; // The supplement number of the character collection. |
||
1125 | $res .= ">>"; |
||
1126 | $this->objects[$cidSystemInfoId]['c'] = $res; |
||
1127 | |||
1128 | // and a CID to GID map |
||
1129 | $cidToGidMapId = ++$this->numObj; |
||
1130 | $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options); |
||
1131 | $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId; |
||
1132 | break; |
||
1133 | |||
1134 | case 'add': |
||
1135 | foreach ($options as $k => $v) { |
||
1136 | switch ($k) { |
||
1137 | case 'BaseFont': |
||
1138 | $o['info']['name'] = $v; |
||
1139 | break; |
||
1140 | |||
1141 | case 'FirstChar': |
||
1142 | case 'LastChar': |
||
1143 | case 'MissingWidth': |
||
1144 | case 'FontDescriptor': |
||
1145 | case 'SubType': |
||
1146 | $this->addMessage("o_fontDescendentCID $k : $v"); |
||
1147 | $o['info'][$k] = $v; |
||
1148 | break; |
||
1149 | } |
||
1150 | } |
||
1151 | |||
1152 | // pass values down to cid to gid map |
||
1153 | $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options); |
||
1154 | break; |
||
1155 | |||
1156 | case 'out': |
||
1157 | $res = "\n$id 0 obj\n"; |
||
1158 | $res .= "<</Type /Font\n"; |
||
1159 | $res .= "/Subtype /CIDFontType2\n"; |
||
1160 | $res .= "/BaseFont /" . $o['info']['name'] . "\n"; |
||
1161 | $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n"; |
||
1162 | // if (isset($o['info']['FirstChar'])) { |
||
1163 | // $res.= "/FirstChar ".$o['info']['FirstChar']."\n"; |
||
1164 | // } |
||
1165 | |||
1166 | // if (isset($o['info']['LastChar'])) { |
||
1167 | // $res.= "/LastChar ".$o['info']['LastChar']."\n"; |
||
1168 | // } |
||
1169 | View Code Duplication | if (isset($o['info']['FontDescriptor'])) { |
|
1170 | $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n"; |
||
1171 | } |
||
1172 | |||
1173 | if (isset($o['info']['MissingWidth'])) { |
||
1174 | $res .= "/DW " . $o['info']['MissingWidth'] . "\n"; |
||
1175 | } |
||
1176 | |||
1177 | if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) { |
||
1178 | $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths']; |
||
1179 | $w = ''; |
||
1180 | foreach ($cid_widths as $cid => $width) { |
||
1181 | $w .= "$cid [$width] "; |
||
1182 | } |
||
1183 | $res .= "/W [$w]\n"; |
||
1184 | } |
||
1185 | |||
1186 | $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n"; |
||
1187 | $res .= ">>\n"; |
||
1188 | $res .= "endobj"; |
||
1189 | |||
1190 | return $res; |
||
1191 | } |
||
1192 | |||
1193 | return null; |
||
1194 | } |
||
1195 | |||
1196 | /** |
||
1197 | * a font glyph to character map, needed for unicode fonts |
||
1198 | * |
||
1199 | * @param $id |
||
1200 | * @param $action |
||
1201 | * @param string $options |
||
1202 | * @return null|string |
||
1203 | */ |
||
1204 | protected function o_fontGIDtoCIDMap($id, $action, $options = '') |
||
1205 | { |
||
1206 | if ($action !== 'new') { |
||
1207 | $o = &$this->objects[$id]; |
||
1208 | } |
||
1209 | |||
1210 | switch ($action) { |
||
1211 | case 'new': |
||
1212 | $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options); |
||
1213 | break; |
||
1214 | |||
1215 | case 'out': |
||
1216 | $res = "\n$id 0 obj\n"; |
||
1217 | $fontFileName = $o['info']['fontFileName']; |
||
1218 | $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']); |
||
1219 | |||
1220 | $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) && |
||
1221 | $this->fonts[$fontFileName]['CIDtoGID_Compressed']; |
||
1222 | |||
1223 | if (!$compressed && isset($o['raw'])) { |
||
1224 | $res .= $tmp; |
||
1225 | } else { |
||
1226 | $res .= "<<"; |
||
1227 | |||
1228 | View Code Duplication | if (!$compressed && $this->compressionReady && $this->options['compression']) { |
|
1229 | // then implement ZLIB based compression on this content stream |
||
1230 | $compressed = true; |
||
1231 | $tmp = gzcompress($tmp, 6); |
||
1232 | } |
||
1233 | if ($compressed) { |
||
1234 | $res .= "\n/Filter /FlateDecode"; |
||
1235 | } |
||
1236 | |||
1237 | if ($this->encrypted) { |
||
1238 | $this->encryptInit($id); |
||
1239 | $tmp = $this->ARC4($tmp); |
||
1240 | } |
||
1241 | |||
1242 | $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream"; |
||
1243 | } |
||
1244 | |||
1245 | $res .= "\nendobj"; |
||
1246 | |||
1247 | return $res; |
||
1248 | } |
||
1249 | |||
1250 | return null; |
||
1251 | } |
||
1252 | |||
1253 | /** |
||
1254 | * the document procset, solves some problems with printing to old PS printers |
||
1255 | * |
||
1256 | * @param $id |
||
1257 | * @param $action |
||
1258 | * @param string $options |
||
1259 | * @return null|string |
||
1260 | */ |
||
1261 | protected function o_procset($id, $action, $options = '') |
||
1298 | |||
1299 | /** |
||
1300 | * define the document information |
||
1301 | * |
||
1302 | * @param $id |
||
1303 | * @param $action |
||
1304 | * @param string $options |
||
1305 | * @return null|string |
||
1306 | */ |
||
1307 | protected function o_info($id, $action, $options = '') |
||
1364 | |||
1365 | /** |
||
1366 | * an action object, used to link to URLS initially |
||
1367 | * |
||
1368 | * @param $id |
||
1369 | * @param $action |
||
1370 | * @param string $options |
||
1371 | * @return null|string |
||
1372 | */ |
||
1373 | protected function o_action($id, $action, $options = '') |
||
1424 | |||
1425 | /** |
||
1426 | * an annotation object, this will add an annotation to the current page. |
||
1427 | * initially will support just link annotations |
||
1428 | * |
||
1429 | * @param $id |
||
1430 | * @param $action |
||
1431 | * @param string $options |
||
1432 | * @return null|string |
||
1433 | */ |
||
1434 | protected function o_annotation($id, $action, $options = '') |
||
1491 | |||
1492 | /** |
||
1493 | * a page object, it also creates a contents object to hold its contents |
||
1494 | * |
||
1495 | * @param $id |
||
1496 | * @param $action |
||
1497 | * @param string $options |
||
1498 | * @return null|string |
||
1499 | */ |
||
1500 | protected function o_page($id, $action, $options = '') |
||
1602 | |||
1603 | /** |
||
1604 | * the contents objects hold all of the content which appears on pages |
||
1605 | * |
||
1606 | * @param $id |
||
1607 | * @param $action |
||
1608 | * @param string|array $options |
||
1609 | * @return null|string |
||
1610 | */ |
||
1611 | protected function o_contents($id, $action, $options = '') |
||
1670 | |||
1671 | /** |
||
1672 | * @param $id |
||
1673 | * @param $action |
||
1674 | * @return string|null |
||
1675 | */ |
||
1676 | protected function o_embedjs($id, $action) |
||
1677 | { |
||
1678 | switch ($action) { |
||
1679 | case 'new': |
||
1680 | $this->objects[$id] = array( |
||
1681 | 't' => 'embedjs', |
||
1682 | 'info' => array( |
||
1683 | 'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]' |
||
1684 | ) |
||
1685 | ); |
||
1686 | break; |
||
1687 | |||
1688 | View Code Duplication | case 'out': |
|
1689 | $o = &$this->objects[$id]; |
||
1690 | $res = "\n$id 0 obj\n<< "; |
||
1691 | foreach ($o['info'] as $k => $v) { |
||
1692 | $res .= "\n/$k $v"; |
||
1693 | } |
||
1694 | $res .= "\n>>\nendobj"; |
||
1695 | |||
1696 | return $res; |
||
1697 | } |
||
1698 | |||
1699 | return null; |
||
1700 | } |
||
1701 | |||
1702 | /** |
||
1703 | * @param $id |
||
1704 | * @param $action |
||
1705 | * @param string $code |
||
1706 | * @return null|string |
||
1707 | */ |
||
1708 | protected function o_javascript($id, $action, $code = '') |
||
1709 | { |
||
1710 | switch ($action) { |
||
1711 | case 'new': |
||
1712 | $this->objects[$id] = array( |
||
1713 | 't' => 'javascript', |
||
1714 | 'info' => array( |
||
1715 | 'S' => '/JavaScript', |
||
1716 | 'JS' => '(' . $this->filterText($code, true, false) . ')', |
||
1717 | ) |
||
1718 | ); |
||
1719 | break; |
||
1720 | |||
1721 | View Code Duplication | case 'out': |
|
1722 | $o = &$this->objects[$id]; |
||
1723 | $res = "\n$id 0 obj\n<< "; |
||
1724 | |||
1725 | foreach ($o['info'] as $k => $v) { |
||
1726 | $res .= "\n/$k $v"; |
||
1727 | } |
||
1728 | $res .= "\n>>\nendobj"; |
||
1729 | |||
1730 | return $res; |
||
1731 | } |
||
1732 | |||
1733 | return null; |
||
1734 | } |
||
1735 | |||
1736 | /** |
||
1737 | * an image object, will be an XObject in the document, includes description and data |
||
1738 | * |
||
1739 | * @param $id |
||
1740 | * @param $action |
||
1741 | * @param string $options |
||
1742 | * @return null|string |
||
1743 | */ |
||
1744 | protected function o_image($id, $action, $options = '') |
||
1877 | |||
1878 | /** |
||
1879 | * graphics state object |
||
1880 | * |
||
1881 | * @param $id |
||
1882 | * @param $action |
||
1883 | * @param string $options |
||
1884 | * @return null|string |
||
1885 | */ |
||
1886 | protected function o_extGState($id, $action, $options = "") |
||
1944 | |||
1945 | /** |
||
1946 | * encryption object. |
||
1947 | * |
||
1948 | * @param $id |
||
1949 | * @param $action |
||
1950 | * @param string $options |
||
1951 | * @return string|null |
||
1952 | */ |
||
1953 | protected function o_encryption($id, $action, $options = '') |
||
2033 | |||
2034 | /** |
||
2035 | * ARC4 functions |
||
2036 | * A series of function to implement ARC4 encoding in PHP |
||
2037 | */ |
||
2038 | |||
2039 | /** |
||
2040 | * calculate the 16 byte version of the 128 bit md5 digest of the string |
||
2041 | * |
||
2042 | * @param $string |
||
2043 | * @return string |
||
2044 | */ |
||
2045 | function md5_16($string) |
||
2055 | |||
2056 | /** |
||
2057 | * initialize the encryption for processing a particular object |
||
2058 | * |
||
2059 | * @param $id |
||
2060 | */ |
||
2061 | function encryptInit($id) |
||
2077 | |||
2078 | /** |
||
2079 | * initialize the ARC4 encryption |
||
2080 | * |
||
2081 | * @param string $key |
||
2082 | */ |
||
2083 | function ARC4_init($key = '') |
||
2111 | |||
2112 | /** |
||
2113 | * ARC4 encrypt a text string |
||
2114 | * |
||
2115 | * @param $text |
||
2116 | * @return string |
||
2117 | */ |
||
2118 | function ARC4($text) |
||
2137 | |||
2138 | /** |
||
2139 | * functions which can be called to adjust or add to the document |
||
2140 | */ |
||
2141 | |||
2142 | /** |
||
2143 | * add a link in the document to an external URL |
||
2144 | * |
||
2145 | * @param $url |
||
2146 | * @param $x0 |
||
2147 | * @param $y0 |
||
2148 | * @param $x1 |
||
2149 | * @param $y1 |
||
2150 | */ |
||
2151 | function addLink($url, $x0, $y0, $x1, $y1) |
||
2157 | |||
2158 | /** |
||
2159 | * add a link in the document to an internal destination (ie. within the document) |
||
2160 | * |
||
2161 | * @param $label |
||
2162 | * @param $x0 |
||
2163 | * @param $y0 |
||
2164 | * @param $x1 |
||
2165 | * @param $y1 |
||
2166 | */ |
||
2167 | function addInternalLink($label, $x0, $y0, $x1, $y1) |
||
2173 | |||
2174 | /** |
||
2175 | * set the encryption of the document |
||
2176 | * can be used to turn it on and/or set the passwords which it will have. |
||
2177 | * also the functions that the user will have are set here, such as print, modify, add |
||
2178 | * |
||
2179 | * @param string $userPass |
||
2180 | * @param string $ownerPass |
||
2181 | * @param array $pc |
||
2182 | */ |
||
2183 | function setEncryption($userPass = '', $ownerPass = '', $pc = array()) |
||
2210 | |||
2211 | /** |
||
2212 | * should be used for internal checks, not implemented as yet |
||
2213 | */ |
||
2214 | function checkAllHere() |
||
2217 | |||
2218 | /** |
||
2219 | * return the pdf stream as a string returned from the function |
||
2220 | * |
||
2221 | * @param bool $debug |
||
2222 | * @return string |
||
2223 | */ |
||
2224 | function output($debug = false) |
||
2293 | |||
2294 | /** |
||
2295 | * initialize a new document |
||
2296 | * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum |
||
2297 | * this function is called automatically by the constructor function |
||
2298 | * |
||
2299 | * @param array $pageSize |
||
2300 | */ |
||
2301 | private function newDocument($pageSize = array(0, 0, 612, 792)) |
||
2331 | |||
2332 | /** |
||
2333 | * open the font file and return a php structure containing it. |
||
2334 | * first check if this one has been done before and saved in a form more suited to php |
||
2335 | * note that if a php serialized version does not exist it will try and make one, but will |
||
2336 | * require write access to the directory to do it... it is MUCH faster to have these serialized |
||
2337 | * files. |
||
2338 | * |
||
2339 | * @param $font |
||
2340 | */ |
||
2341 | private function openFont($font) |
||
2575 | |||
2576 | /** |
||
2577 | * if the font is not loaded then load it and make the required object |
||
2578 | * else just make it the current font |
||
2579 | * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding' |
||
2580 | * note that encoding='none' will need to be used for symbolic fonts |
||
2581 | * and 'differences' => an array of mappings between numbers 0->255 and character names. |
||
2582 | * |
||
2583 | * @param $fontName |
||
2584 | * @param string $encoding |
||
2585 | * @param bool $set |
||
2586 | * @return int |
||
2587 | */ |
||
2588 | function selectFont($fontName, $encoding = '', $set = true) |
||
2930 | |||
2931 | /** |
||
2932 | * sets up the current font, based on the font families, and the current text state |
||
2933 | * note that this system is quite flexible, a bold-italic font can be completely different to a |
||
2934 | * italic-bold font, and even bold-bold will have to be defined within the family to have meaning |
||
2935 | * This function is to be called whenever the currentTextState is changed, it will update |
||
2936 | * the currentFont setting to whatever the appropriate family one is. |
||
2937 | * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont |
||
2938 | * This function will change the currentFont to whatever it should be, but will not change the |
||
2939 | * currentBaseFont. |
||
2940 | */ |
||
2941 | private function setCurrentFont() |
||
2965 | |||
2966 | /** |
||
2967 | * function for the user to find out what the ID is of the first page that was created during |
||
2968 | * startup - useful if they wish to add something to it later. |
||
2969 | * |
||
2970 | * @return int |
||
2971 | */ |
||
2972 | function getFirstPageId() |
||
2976 | |||
2977 | /** |
||
2978 | * add content to the currently active object |
||
2979 | * |
||
2980 | * @param $content |
||
2981 | */ |
||
2982 | private function addContent($content) |
||
2986 | |||
2987 | /** |
||
2988 | * sets the color for fill operations |
||
2989 | * |
||
2990 | * @param $color |
||
2991 | * @param bool $force |
||
2992 | */ |
||
2993 | View Code Duplication | function setColor($color, $force = false) |
|
3011 | |||
3012 | /** |
||
3013 | * sets the color for fill operations |
||
3014 | * |
||
3015 | * @param $fillRule |
||
3016 | */ |
||
3017 | function setFillRule($fillRule) |
||
3025 | |||
3026 | /** |
||
3027 | * sets the color for stroke operations |
||
3028 | * |
||
3029 | * @param $color |
||
3030 | * @param bool $force |
||
3031 | */ |
||
3032 | View Code Duplication | function setStrokeColor($color, $force = false) |
|
3050 | |||
3051 | /** |
||
3052 | * Set the graphics state for compositions |
||
3053 | * |
||
3054 | * @param $parameters |
||
3055 | */ |
||
3056 | function setGraphicsState($parameters) |
||
3067 | |||
3068 | /** |
||
3069 | * Set current blend mode & opacity for lines. |
||
3070 | * |
||
3071 | * Valid blend modes are: |
||
3072 | * |
||
3073 | * Normal, Multiply, Screen, Overlay, Darken, Lighten, |
||
3074 | * ColorDogde, ColorBurn, HardLight, SoftLight, Difference, |
||
3075 | * Exclusion |
||
3076 | * |
||
3077 | * @param string $mode the blend mode to use |
||
3078 | * @param float $opacity 0.0 fully transparent, 1.0 fully opaque |
||
3079 | */ |
||
3080 | View Code Duplication | function setLineTransparency($mode, $opacity) |
|
3118 | |||
3119 | /** |
||
3120 | * Set current blend mode & opacity for filled objects. |
||
3121 | * |
||
3122 | * Valid blend modes are: |
||
3123 | * |
||
3124 | * Normal, Multiply, Screen, Overlay, Darken, Lighten, |
||
3125 | * ColorDogde, ColorBurn, HardLight, SoftLight, Difference, |
||
3126 | * Exclusion |
||
3127 | * |
||
3128 | * @param string $mode the blend mode to use |
||
3129 | * @param float $opacity 0.0 fully transparent, 1.0 fully opaque |
||
3130 | */ |
||
3131 | View Code Duplication | function setFillTransparency($mode, $opacity) |
|
3168 | |||
3169 | /** |
||
3170 | * draw a line from one set of coordinates to another |
||
3171 | * |
||
3172 | * @param $x1 |
||
3173 | * @param $y1 |
||
3174 | * @param $x2 |
||
3175 | * @param $y2 |
||
3176 | * @param bool $stroke |
||
3177 | */ |
||
3178 | function line($x1, $y1, $x2, $y2, $stroke = true) |
||
3186 | |||
3187 | /** |
||
3188 | * draw a bezier curve based on 4 control points |
||
3189 | * |
||
3190 | * @param $x0 |
||
3191 | * @param $y0 |
||
3192 | * @param $x1 |
||
3193 | * @param $y1 |
||
3194 | * @param $x2 |
||
3195 | * @param $y2 |
||
3196 | * @param $x3 |
||
3197 | * @param $y3 |
||
3198 | */ |
||
3199 | function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3) |
||
3207 | |||
3208 | /** |
||
3209 | * draw a part of an ellipse |
||
3210 | * |
||
3211 | * @param $x0 |
||
3212 | * @param $y0 |
||
3213 | * @param $astart |
||
3214 | * @param $afinish |
||
3215 | * @param $r1 |
||
3216 | * @param int $r2 |
||
3217 | * @param int $angle |
||
3218 | * @param int $nSeg |
||
3219 | */ |
||
3220 | function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8) |
||
3224 | |||
3225 | /** |
||
3226 | * draw a filled ellipse |
||
3227 | * |
||
3228 | * @param $x0 |
||
3229 | * @param $y0 |
||
3230 | * @param $r1 |
||
3231 | * @param int $r2 |
||
3232 | * @param int $angle |
||
3233 | * @param int $nSeg |
||
3234 | * @param int $astart |
||
3235 | * @param int $afinish |
||
3236 | */ |
||
3237 | function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360) |
||
3241 | |||
3242 | /** |
||
3243 | * @param $x |
||
3244 | * @param $y |
||
3245 | */ |
||
3246 | function lineTo($x, $y) |
||
3250 | |||
3251 | /** |
||
3252 | * @param $x |
||
3253 | * @param $y |
||
3254 | */ |
||
3255 | function moveTo($x, $y) |
||
3259 | |||
3260 | /** |
||
3261 | * draw a bezier curve based on 4 control points |
||
3262 | * |
||
3263 | * @param $x1 |
||
3264 | * @param $y1 |
||
3265 | * @param $x2 |
||
3266 | * @param $y2 |
||
3267 | * @param $x3 |
||
3268 | * @param $y3 |
||
3269 | */ |
||
3270 | function curveTo($x1, $y1, $x2, $y2, $x3, $y3) |
||
3274 | |||
3275 | /** |
||
3276 | * draw a bezier curve based on 4 control points |
||
3277 | */ function quadTo($cpx, $cpy, $x, $y) |
||
3281 | |||
3282 | function closePath() |
||
3286 | |||
3287 | function endPath() |
||
3291 | |||
3292 | /** |
||
3293 | * draw an ellipse |
||
3294 | * note that the part and filled ellipse are just special cases of this function |
||
3295 | * |
||
3296 | * draws an ellipse in the current line style |
||
3297 | * centered at $x0,$y0, radii $r1,$r2 |
||
3298 | * if $r2 is not set, then a circle is drawn |
||
3299 | * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse. |
||
3300 | * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a |
||
3301 | * pretty crappy shape at 2, as we are approximating with bezier curves. |
||
3302 | * |
||
3303 | * @param $x0 |
||
3304 | * @param $y0 |
||
3305 | * @param $r1 |
||
3306 | * @param int $r2 |
||
3307 | * @param int $angle |
||
3308 | * @param int $nSeg |
||
3309 | * @param int $astart |
||
3310 | * @param int $afinish |
||
3311 | * @param bool $close |
||
3312 | * @param bool $fill |
||
3313 | * @param bool $stroke |
||
3314 | * @param bool $incomplete |
||
3315 | */ |
||
3316 | function ellipse( |
||
3414 | |||
3415 | /** |
||
3416 | * this sets the line drawing style. |
||
3417 | * width, is the thickness of the line in user units |
||
3418 | * cap is the type of cap to put on the line, values can be 'butt','round','square' |
||
3419 | * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the |
||
3420 | * end of the line. |
||
3421 | * join can be 'miter', 'round', 'bevel' |
||
3422 | * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the |
||
3423 | * on and off dashes. |
||
3424 | * (2) represents 2 on, 2 off, 2 on , 2 off ... |
||
3425 | * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc |
||
3426 | * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts. |
||
3427 | * |
||
3428 | * @param int $width |
||
3429 | * @param string $cap |
||
3430 | * @param string $join |
||
3431 | * @param string $dash |
||
3432 | * @param int $phase |
||
3433 | */ |
||
3434 | function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0) |
||
3462 | |||
3463 | /** |
||
3464 | * draw a polygon, the syntax for this is similar to the GD polygon command |
||
3465 | * |
||
3466 | * @param $p |
||
3467 | * @param $np |
||
3468 | * @param bool $f |
||
3469 | */ |
||
3470 | function polygon($p, $np, $f = false) |
||
3484 | |||
3485 | /** |
||
3486 | * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not |
||
3487 | * the coordinates of the upper-right corner |
||
3488 | * |
||
3489 | * @param $x1 |
||
3490 | * @param $y1 |
||
3491 | * @param $width |
||
3492 | * @param $height |
||
3493 | */ |
||
3494 | function filledRectangle($x1, $y1, $width, $height) |
||
3498 | |||
3499 | /** |
||
3500 | * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not |
||
3501 | * the coordinates of the upper-right corner |
||
3502 | * |
||
3503 | * @param $x1 |
||
3504 | * @param $y1 |
||
3505 | * @param $width |
||
3506 | * @param $height |
||
3507 | */ |
||
3508 | function rectangle($x1, $y1, $width, $height) |
||
3512 | |||
3513 | /** |
||
3514 | * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not |
||
3515 | * the coordinates of the upper-right corner |
||
3516 | * |
||
3517 | * @param $x1 |
||
3518 | * @param $y1 |
||
3519 | * @param $width |
||
3520 | * @param $height |
||
3521 | */ |
||
3522 | function rect($x1, $y1, $width, $height) |
||
3526 | |||
3527 | function stroke() |
||
3531 | |||
3532 | function fill() |
||
3536 | |||
3537 | function fillStroke() |
||
3541 | |||
3542 | /** |
||
3543 | * save the current graphic state |
||
3544 | */ |
||
3545 | function save() |
||
3546 | { |
||
3547 | // we must reset the color cache or it will keep bad colors after clipping |
||
3548 | $this->currentColor = null; |
||
3549 | $this->currentStrokeColor = null; |
||
3550 | $this->addContent("\nq"); |
||
3551 | } |
||
3552 | |||
3553 | /** |
||
3554 | * restore the last graphic state |
||
3555 | */ |
||
3556 | function restore() |
||
3557 | { |
||
3558 | // we must reset the color cache or it will keep bad colors after clipping |
||
3559 | $this->currentColor = null; |
||
3560 | $this->currentStrokeColor = null; |
||
3561 | $this->addContent("\nQ"); |
||
3562 | } |
||
3563 | |||
3564 | /** |
||
3565 | * draw a clipping rectangle, all the elements added after this will be clipped |
||
3566 | * |
||
3567 | * @param $x1 |
||
3568 | * @param $y1 |
||
3569 | * @param $width |
||
3570 | * @param $height |
||
3571 | */ |
||
3572 | function clippingRectangle($x1, $y1, $width, $height) |
||
3573 | { |
||
3574 | $this->save(); |
||
3575 | $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height)); |
||
3576 | } |
||
3577 | |||
3578 | /** |
||
3579 | * draw a clipping rounded rectangle, all the elements added after this will be clipped |
||
3580 | * |
||
3581 | * @param $x1 |
||
3582 | * @param $y1 |
||
3583 | * @param $w |
||
3584 | * @param $h |
||
3585 | * @param $rTL |
||
3586 | * @param $rTR |
||
3587 | * @param $rBR |
||
3588 | * @param $rBL |
||
3589 | */ |
||
3590 | function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) |
||
3591 | { |
||
3592 | $this->save(); |
||
3593 | |||
3594 | // start: top edge, left end |
||
3595 | $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h)); |
||
3596 | |||
3597 | // line: bottom edge, left end |
||
3598 | $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL)); |
||
3599 | |||
3600 | // curve: bottom-left corner |
||
3601 | $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true); |
||
3602 | |||
3603 | // line: right edge, bottom end |
||
3604 | $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1)); |
||
3605 | |||
3606 | // curve: bottom-right corner |
||
3607 | $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true); |
||
3608 | |||
3609 | // line: right edge, top end |
||
3610 | $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR)); |
||
3611 | |||
3612 | // curve: bottom-right corner |
||
3613 | $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true); |
||
3614 | |||
3615 | // line: bottom edge, right end |
||
3616 | $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h)); |
||
3617 | |||
3618 | // curve: top-right corner |
||
3619 | $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true); |
||
3620 | |||
3621 | // line: top edge, left end |
||
3622 | $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1)); |
||
3623 | |||
3624 | // Close & clip |
||
3625 | $this->addContent(" W n"); |
||
3626 | } |
||
3627 | |||
3628 | /** |
||
3629 | * ends the last clipping shape |
||
3630 | */ |
||
3631 | function clippingEnd() |
||
3632 | { |
||
3633 | $this->restore(); |
||
3634 | } |
||
3635 | |||
3636 | /** |
||
3637 | * scale |
||
3638 | * |
||
3639 | * @param float $s_x scaling factor for width as percent |
||
3640 | * @param float $s_y scaling factor for height as percent |
||
3641 | * @param float $x Origin abscissa |
||
3642 | * @param float $y Origin ordinate |
||
3643 | */ |
||
3644 | function scale($s_x, $s_y, $x, $y) |
||
3645 | { |
||
3646 | $y = $this->currentPageSize["height"] - $y; |
||
3647 | |||
3648 | $tm = array( |
||
3649 | $s_x, |
||
3650 | 0, |
||
3651 | 0, |
||
3652 | $s_y, |
||
3653 | $x * (1 - $s_x), |
||
3654 | $y * (1 - $s_y) |
||
3655 | ); |
||
3656 | |||
3657 | $this->transform($tm); |
||
3658 | } |
||
3659 | |||
3660 | /** |
||
3661 | * translate |
||
3662 | * |
||
3663 | * @param float $t_x movement to the right |
||
3664 | * @param float $t_y movement to the bottom |
||
3665 | */ |
||
3666 | function translate($t_x, $t_y) |
||
3667 | { |
||
3668 | $tm = array( |
||
3669 | 1, |
||
3670 | 0, |
||
3671 | 0, |
||
3672 | 1, |
||
3673 | $t_x, |
||
3674 | -$t_y |
||
3675 | ); |
||
3676 | |||
3677 | $this->transform($tm); |
||
3678 | } |
||
3679 | |||
3680 | /** |
||
3681 | * rotate |
||
3682 | * |
||
3683 | * @param float $angle angle in degrees for counter-clockwise rotation |
||
3684 | * @param float $x Origin abscissa |
||
3685 | * @param float $y Origin ordinate |
||
3686 | */ |
||
3687 | function rotate($angle, $x, $y) |
||
3688 | { |
||
3689 | $y = $this->currentPageSize["height"] - $y; |
||
3690 | |||
3691 | $a = deg2rad($angle); |
||
3692 | $cos_a = cos($a); |
||
3693 | $sin_a = sin($a); |
||
3694 | |||
3695 | $tm = array( |
||
3696 | $cos_a, |
||
3697 | -$sin_a, |
||
3698 | $sin_a, |
||
3699 | $cos_a, |
||
3700 | $x - $sin_a * $y - $cos_a * $x, |
||
3701 | $y - $cos_a * $y + $sin_a * $x, |
||
3702 | ); |
||
3703 | |||
3704 | $this->transform($tm); |
||
3705 | } |
||
3706 | |||
3707 | /** |
||
3708 | * skew |
||
3709 | * |
||
3710 | * @param float $angle_x |
||
3711 | * @param float $angle_y |
||
3712 | * @param float $x Origin abscissa |
||
3713 | * @param float $y Origin ordinate |
||
3714 | */ |
||
3715 | function skew($angle_x, $angle_y, $x, $y) |
||
3716 | { |
||
3717 | $y = $this->currentPageSize["height"] - $y; |
||
3718 | |||
3719 | $tan_x = tan(deg2rad($angle_x)); |
||
3720 | $tan_y = tan(deg2rad($angle_y)); |
||
3721 | |||
3722 | $tm = array( |
||
3723 | 1, |
||
3724 | -$tan_y, |
||
3725 | -$tan_x, |
||
3726 | 1, |
||
3727 | $tan_x * $y, |
||
3728 | $tan_y * $x, |
||
3729 | ); |
||
3730 | |||
3731 | $this->transform($tm); |
||
3732 | } |
||
3733 | |||
3734 | /** |
||
3735 | * apply graphic transformations |
||
3736 | * |
||
3737 | * @param array $tm transformation matrix |
||
3738 | */ |
||
3739 | function transform($tm) |
||
3740 | { |
||
3741 | $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm)); |
||
3742 | } |
||
3743 | |||
3744 | /** |
||
3745 | * add a new page to the document |
||
3746 | * this also makes the new page the current active object |
||
3747 | * |
||
3748 | * @param int $insert |
||
3749 | * @param int $id |
||
3750 | * @param string $pos |
||
3751 | * @return int |
||
3752 | */ |
||
3753 | function newPage($insert = 0, $id = 0, $pos = 'after') |
||
3800 | |||
3801 | /** |
||
3802 | * Streams the PDF to the client. |
||
3803 | * |
||
3804 | * @param string $filename The filename to present to the client. |
||
3805 | * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1). |
||
3806 | */ |
||
3807 | function stream($filename = "document.pdf", $options = array()) |
||
3840 | |||
3841 | /** |
||
3842 | * return the height in units of the current font in the given size |
||
3843 | * |
||
3844 | * @param $size |
||
3845 | * @return float|int |
||
3846 | */ |
||
3847 | function getFontHeight($size) |
||
3877 | |||
3878 | /** |
||
3879 | * @param $size |
||
3880 | * @return float|int |
||
3881 | */ |
||
3882 | function getFontXHeight($size) |
||
3883 | { |
||
3884 | if (!$this->numFonts) { |
||
3885 | $this->selectFont($this->defaultFont); |
||
3886 | } |
||
3887 | |||
3888 | $font = $this->fonts[$this->currentFont]; |
||
3889 | |||
3890 | // for the current font, and the given size, what is the height of the font in user units |
||
3891 | if (isset($font['XHeight'])) { |
||
3892 | $xh = $font['Ascender'] - $font['Descender']; |
||
3893 | } else { |
||
3894 | $xh = $this->getFontHeight($size) / 2; |
||
3895 | } |
||
3896 | |||
3897 | return $size * $xh / 1000; |
||
3898 | } |
||
3899 | |||
3900 | /** |
||
3901 | * return the font descender, this will normally return a negative number |
||
3902 | * if you add this number to the baseline, you get the level of the bottom of the font |
||
3903 | * it is in the pdf user units |
||
3904 | * |
||
3905 | * @param $size |
||
3906 | * @return float|int |
||
3907 | */ |
||
3908 | function getFontDescender($size) |
||
3909 | { |
||
3910 | // note that this will most likely return a negative value |
||
3911 | if (!$this->numFonts) { |
||
3912 | $this->selectFont($this->defaultFont); |
||
3913 | } |
||
3914 | |||
3915 | //$h = $this->fonts[$this->currentFont]['FontBBox'][1]; |
||
3916 | $h = $this->fonts[$this->currentFont]['Descender']; |
||
3917 | |||
3918 | return $size * $h / 1000; |
||
3919 | } |
||
3920 | |||
3921 | /** |
||
3922 | * filter the text, this is applied to all text just before being inserted into the pdf document |
||
3923 | * it escapes the various things that need to be escaped, and so on |
||
3924 | * |
||
3925 | * @access private |
||
3926 | * |
||
3927 | * @param $text |
||
3928 | * @param bool $bom |
||
3929 | * @param bool $convert_encoding |
||
3930 | * @return string |
||
3931 | */ |
||
3932 | function filterText($text, $bom = true, $convert_encoding = true) |
||
3953 | |||
3954 | /** |
||
3955 | * return array containing codepoints (UTF-8 character values) for the |
||
3956 | * string passed in. |
||
3957 | * |
||
3958 | * based on the excellent TCPDF code by Nicola Asuni and the |
||
3959 | * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html |
||
3960 | * |
||
3961 | * @access private |
||
3962 | * @author Orion Richardson |
||
3963 | * @since January 5, 2008 |
||
3964 | * |
||
3965 | * @param string $text UTF-8 string to process |
||
3966 | * |
||
3967 | * @return array UTF-8 codepoints array for the string |
||
3968 | */ |
||
3969 | function utf8toCodePointsArray(&$text) |
||
3970 | { |
||
3971 | $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040 |
||
3972 | $unicode = array(); // array containing unicode values |
||
3973 | $bytes = array(); // array containing single character byte sequences |
||
3974 | $numbytes = 1; // number of octets needed to represent the UTF-8 character |
||
3975 | |||
3976 | for ($i = 0; $i < $length; $i++) { |
||
3977 | $c = ord($text[$i]); // get one string character at time |
||
3978 | if (count($bytes) === 0) { // get starting octect |
||
3979 | if ($c <= 0x7F) { |
||
3980 | $unicode[] = $c; // use the character "as is" because is ASCII |
||
3981 | $numbytes = 1; |
||
3982 | View Code Duplication | } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN) |
|
3983 | $bytes[] = ($c - 0xC0) << 0x06; |
||
3984 | $numbytes = 2; |
||
3985 | } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN) |
||
3986 | $bytes[] = ($c - 0xE0) << 0x0C; |
||
3987 | $numbytes = 3; |
||
3988 | View Code Duplication | } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN) |
|
3989 | $bytes[] = ($c - 0xF0) << 0x12; |
||
3990 | $numbytes = 4; |
||
3991 | } else { |
||
3992 | // use replacement character for other invalid sequences |
||
3993 | $unicode[] = 0xFFFD; |
||
3994 | $bytes = array(); |
||
3995 | $numbytes = 1; |
||
3996 | } |
||
3997 | } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN |
||
3998 | $bytes[] = $c - 0x80; |
||
3999 | if (count($bytes) === $numbytes) { |
||
4000 | // compose UTF-8 bytes to a single unicode value |
||
4001 | $c = $bytes[0]; |
||
4002 | for ($j = 1; $j < $numbytes; $j++) { |
||
4003 | $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06)); |
||
4004 | } |
||
4005 | if ((($c >= 0xD800) AND ($c <= 0xDFFF)) OR ($c >= 0x10FFFF)) { |
||
4006 | // The definition of UTF-8 prohibits encoding character numbers between |
||
4007 | // U+D800 and U+DFFF, which are reserved for use with the UTF-16 |
||
4008 | // encoding form (as surrogate pairs) and do not directly represent |
||
4009 | // characters. |
||
4010 | $unicode[] = 0xFFFD; // use replacement character |
||
4011 | } else { |
||
4012 | $unicode[] = $c; // add char to array |
||
4013 | } |
||
4014 | // reset data for next char |
||
4015 | $bytes = array(); |
||
4016 | $numbytes = 1; |
||
4017 | } |
||
4018 | } else { |
||
4019 | // use replacement character for other invalid sequences |
||
4020 | $unicode[] = 0xFFFD; |
||
4021 | $bytes = array(); |
||
4022 | $numbytes = 1; |
||
4023 | } |
||
4024 | } |
||
4025 | |||
4026 | return $unicode; |
||
4027 | } |
||
4028 | |||
4029 | /** |
||
4030 | * convert UTF-8 to UTF-16 with an additional byte order marker |
||
4031 | * at the front if required. |
||
4032 | * |
||
4033 | * based on the excellent TCPDF code by Nicola Asuni and the |
||
4034 | * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html |
||
4035 | * |
||
4036 | * @access private |
||
4037 | * @author Orion Richardson |
||
4038 | * @since January 5, 2008 |
||
4039 | * |
||
4040 | * @param string $text UTF-8 string to process |
||
4041 | * @param boolean $bom whether to add the byte order marker |
||
4042 | * |
||
4043 | * @return string UTF-16 result string |
||
4044 | */ |
||
4045 | function utf8toUtf16BE(&$text, $bom = true) |
||
4046 | { |
||
4047 | $out = $bom ? "\xFE\xFF" : ''; |
||
4048 | |||
4049 | $unicode = $this->utf8toCodePointsArray($text); |
||
4050 | foreach ($unicode as $c) { |
||
4051 | if ($c === 0xFFFD) { |
||
4052 | $out .= "\xFF\xFD"; // replacement character |
||
4053 | } elseif ($c < 0x10000) { |
||
4054 | $out .= chr($c >> 0x08) . chr($c & 0xFF); |
||
4055 | } else { |
||
4056 | $c -= 0x10000; |
||
4057 | $w1 = 0xD800 | ($c >> 0x10); |
||
4058 | $w2 = 0xDC00 | ($c & 0x3FF); |
||
4059 | $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF); |
||
4060 | } |
||
4061 | } |
||
4062 | |||
4063 | return $out; |
||
4064 | } |
||
4065 | |||
4066 | /** |
||
4067 | * given a start position and information about how text is to be laid out, calculate where |
||
4068 | * on the page the text will end |
||
4069 | * |
||
4070 | * @param $x |
||
4071 | * @param $y |
||
4072 | * @param $angle |
||
4073 | * @param $size |
||
4074 | * @param $wa |
||
4075 | * @param $text |
||
4076 | * @return array |
||
4077 | */ |
||
4078 | private function getTextPosition($x, $y, $angle, $size, $wa, $text) |
||
4079 | { |
||
4080 | // given this information return an array containing x and y for the end position as elements 0 and 1 |
||
4081 | $w = $this->getTextWidth($size, $text); |
||
4082 | |||
4083 | // need to adjust for the number of spaces in this text |
||
4084 | $words = explode(' ', $text); |
||
4085 | $nspaces = count($words) - 1; |
||
4086 | $w += $wa * $nspaces; |
||
4087 | $a = deg2rad((float)$angle); |
||
4088 | |||
4089 | return array(cos($a) * $w + $x, -sin($a) * $w + $y); |
||
4090 | } |
||
4091 | |||
4092 | /** |
||
4093 | * Callback method used by smallCaps |
||
4094 | * |
||
4095 | * @param array $matches |
||
4096 | * |
||
4097 | * @return string |
||
4098 | */ |
||
4099 | function toUpper($matches) |
||
4100 | { |
||
4101 | return mb_strtoupper($matches[0]); |
||
4102 | } |
||
4103 | |||
4104 | function concatMatches($matches) |
||
4105 | { |
||
4106 | $str = ""; |
||
4107 | foreach ($matches as $match) { |
||
4108 | $str .= $match[0]; |
||
4109 | } |
||
4110 | |||
4111 | return $str; |
||
4112 | } |
||
4113 | |||
4114 | /** |
||
4115 | * register text for font subsetting |
||
4116 | * |
||
4117 | * @param $font |
||
4118 | * @param $text |
||
4119 | */ |
||
4120 | function registerText($font, $text) |
||
4121 | { |
||
4122 | if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) { |
||
4123 | return; |
||
4124 | } |
||
4125 | |||
4126 | if (!isset($this->stringSubsets[$font])) { |
||
4127 | $this->stringSubsets[$font] = array(); |
||
4128 | } |
||
4129 | |||
4130 | $this->stringSubsets[$font] = array_unique( |
||
4131 | array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text)) |
||
4132 | ); |
||
4133 | } |
||
4134 | |||
4135 | /** |
||
4136 | * add text to the document, at a specified location, size and angle on the page |
||
4137 | * |
||
4138 | * @param $x |
||
4139 | * @param $y |
||
4140 | * @param $size |
||
4141 | * @param $text |
||
4142 | * @param int $angle |
||
4143 | * @param int $wordSpaceAdjust |
||
4144 | * @param int $charSpaceAdjust |
||
4145 | * @param bool $smallCaps |
||
4146 | */ |
||
4147 | function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false) |
||
4250 | |||
4251 | /** |
||
4252 | * calculate how wide a given text string will be on a page, at a given size. |
||
4253 | * this can be called externally, but is also used by the other class functions |
||
4254 | * |
||
4255 | * @param $size |
||
4256 | * @param $text |
||
4257 | * @param int $word_spacing |
||
4258 | * @param int $char_spacing |
||
4259 | * @return float|int |
||
4260 | */ |
||
4261 | function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0) |
||
4359 | |||
4360 | /** |
||
4361 | * this will be called at a new page to return the state to what it was on the |
||
4362 | * end of the previous page, before the stack was closed down |
||
4363 | * This is to get around not being able to have open 'q' across pages |
||
4364 | * |
||
4365 | * @param int $pageEnd |
||
4366 | */ |
||
4367 | function saveState($pageEnd = 0) |
||
4390 | |||
4391 | /** |
||
4392 | * restore a previously saved state |
||
4393 | * |
||
4394 | * @param int $pageEnd |
||
4395 | */ |
||
4396 | function restoreState($pageEnd = 0) |
||
4411 | |||
4412 | /** |
||
4413 | * make a loose object, the output will go into this object, until it is closed, then will revert to |
||
4414 | * the current one. |
||
4415 | * this object will not appear until it is included within a page. |
||
4416 | * the function will return the object number |
||
4417 | * |
||
4418 | * @return int |
||
4419 | */ |
||
4420 | function openObject() |
||
4432 | |||
4433 | /** |
||
4434 | * open an existing object for editing |
||
4435 | * |
||
4436 | * @param $id |
||
4437 | */ |
||
4438 | function reopenObject($id) |
||
4449 | |||
4450 | /** |
||
4451 | * close an object |
||
4452 | */ |
||
4453 | function closeObject() |
||
4465 | |||
4466 | /** |
||
4467 | * stop an object from appearing on pages from this point on |
||
4468 | * |
||
4469 | * @param $id |
||
4470 | */ |
||
4471 | function stopObject($id) |
||
4479 | |||
4480 | /** |
||
4481 | * after an object has been created, it wil only show if it has been added, using this function. |
||
4482 | * |
||
4483 | * @param $id |
||
4484 | * @param string $options |
||
4485 | */ |
||
4486 | function addObject($id, $options = 'add') |
||
4537 | |||
4538 | /** |
||
4539 | * return a storable representation of a specific object |
||
4540 | * |
||
4541 | * @param $id |
||
4542 | * @return string|null |
||
4543 | */ |
||
4544 | function serializeObject($id) |
||
4552 | |||
4553 | /** |
||
4554 | * restore an object from its stored representation. returns its new object id. |
||
4555 | * |
||
4556 | * @param $obj |
||
4557 | * @return int |
||
4558 | */ |
||
4559 | function restoreSerializedObject($obj) |
||
4567 | |||
4568 | /** |
||
4569 | * add content to the documents info object |
||
4570 | * |
||
4571 | * @param $label |
||
4572 | * @param int $value |
||
4573 | */ |
||
4574 | function addInfo($label, $value = 0) |
||
4588 | |||
4589 | /** |
||
4590 | * set the viewer preferences of the document, it is up to the browser to obey these. |
||
4591 | * |
||
4592 | * @param $label |
||
4593 | * @param int $value |
||
4594 | */ |
||
4595 | function setPreferences($label, $value = 0) |
||
4606 | |||
4607 | /** |
||
4608 | * extract an integer from a position in a byte stream |
||
4609 | * |
||
4610 | * @param $data |
||
4611 | * @param $pos |
||
4612 | * @param $num |
||
4613 | * @return int |
||
4614 | */ |
||
4615 | private function getBytes(&$data, $pos, $num) |
||
4616 | { |
||
4617 | // return the integer represented by $num bytes from $pos within $data |
||
4618 | $ret = 0; |
||
4619 | for ($i = 0; $i < $num; $i++) { |
||
4620 | $ret *= 256; |
||
4621 | $ret += ord($data[$pos + $i]); |
||
4622 | } |
||
4623 | |||
4624 | return $ret; |
||
4625 | } |
||
4626 | |||
4627 | /** |
||
4628 | * Check if image already added to pdf image directory. |
||
4629 | * If yes, need not to create again (pass empty data) |
||
4630 | * |
||
4631 | * @param $imgname |
||
4632 | * @return bool |
||
4633 | */ |
||
4634 | function image_iscached($imgname) |
||
4635 | { |
||
4636 | return isset($this->imagelist[$imgname]); |
||
4637 | } |
||
4638 | |||
4639 | /** |
||
4640 | * add a PNG image into the document, from a GD object |
||
4641 | * this should work with remote files |
||
4642 | * |
||
4643 | * @param string $file The PNG file |
||
4644 | * @param float $x X position |
||
4645 | * @param float $y Y position |
||
4646 | * @param float $w Width |
||
4647 | * @param float $h Height |
||
4648 | * @param resource $img A GD resource |
||
4649 | * @param bool $is_mask true if the image is a mask |
||
4650 | * @param bool $mask true if the image is masked |
||
4651 | * @throws Exception |
||
4652 | */ |
||
4653 | function addImagePng($file, $x, $y, $w = 0.0, $h = 0.0, &$img, $is_mask = false, $mask = null) |
||
4654 | { |
||
4655 | if (!function_exists("imagepng")) { |
||
4656 | throw new \Exception("The PHP GD extension is required, but is not installed."); |
||
4657 | } |
||
4658 | |||
4659 | //if already cached, need not to read again |
||
4660 | if (isset($this->imagelist[$file])) { |
||
4661 | $data = null; |
||
4662 | } else { |
||
4663 | // Example for transparency handling on new image. Retain for current image |
||
4664 | // $tIndex = imagecolortransparent($img); |
||
4665 | // if ($tIndex > 0) { |
||
4666 | // $tColor = imagecolorsforindex($img, $tIndex); |
||
4667 | // $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']); |
||
4668 | // imagefill($new_img, 0, 0, $new_tIndex); |
||
4669 | // imagecolortransparent($new_img, $new_tIndex); |
||
4670 | // } |
||
4671 | // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn |
||
4672 | //imagealphablending($img, true); |
||
4673 | |||
4674 | //default, but explicitely set to ensure pdf compatibility |
||
4675 | imagesavealpha($img, false/*!$is_mask && !$mask*/); |
||
4676 | |||
4677 | $error = 0; |
||
4678 | //DEBUG_IMG_TEMP |
||
4679 | //debugpng |
||
4680 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
4681 | print '[addImagePng ' . $file . ']'; |
||
4682 | } |
||
4683 | |||
4684 | ob_start(); |
||
4685 | @imagepng($img); |
||
4686 | $data = ob_get_clean(); |
||
4687 | |||
4688 | View Code Duplication | if ($data == '') { |
|
4689 | $error = 1; |
||
4690 | $errormsg = 'trouble writing file from GD'; |
||
4691 | //DEBUG_IMG_TEMP |
||
4692 | //debugpng |
||
4693 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
4694 | print 'trouble writing file from GD'; |
||
4695 | } |
||
4696 | } |
||
4697 | |||
4698 | if ($error) { |
||
4699 | $this->addMessage('PNG error - (' . $file . ') ' . $errormsg); |
||
4700 | |||
4701 | return; |
||
4702 | } |
||
4703 | } //End isset($this->imagelist[$file]) (png Duplicate removal) |
||
4704 | |||
4705 | $this->addPngFromBuf($file, $x, $y, $w, $h, $data, $is_mask, $mask); |
||
4706 | } |
||
4707 | |||
4708 | /** |
||
4709 | * @param $file |
||
4710 | * @param $x |
||
4711 | * @param $y |
||
4712 | * @param $w |
||
4713 | * @param $h |
||
4714 | * @param $byte |
||
4715 | */ |
||
4716 | protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte) |
||
4717 | { |
||
4718 | // generate images |
||
4719 | $img = imagecreatefrompng($file); |
||
4720 | |||
4721 | if ($img === false) { |
||
4722 | return; |
||
4723 | } |
||
4724 | |||
4725 | // FIXME The pixel transformation doesn't work well with 8bit PNGs |
||
4726 | $eight_bit = ($byte & 4) !== 4; |
||
4727 | |||
4728 | $wpx = imagesx($img); |
||
4729 | $hpx = imagesy($img); |
||
4730 | |||
4731 | imagesavealpha($img, false); |
||
4732 | |||
4733 | // create temp alpha file |
||
4734 | $tempfile_alpha = tempnam($this->tmp, "cpdf_img_"); |
||
4735 | @unlink($tempfile_alpha); |
||
4736 | $tempfile_alpha = "$tempfile_alpha.png"; |
||
4737 | |||
4738 | // create temp plain file |
||
4739 | $tempfile_plain = tempnam($this->tmp, "cpdf_img_"); |
||
4740 | @unlink($tempfile_plain); |
||
4741 | $tempfile_plain = "$tempfile_plain.png"; |
||
4742 | |||
4743 | $imgalpha = imagecreate($wpx, $hpx); |
||
4744 | imagesavealpha($imgalpha, false); |
||
4745 | |||
4746 | // generate gray scale palette (0 -> 255) |
||
4747 | for ($c = 0; $c < 256; ++$c) { |
||
4748 | imagecolorallocate($imgalpha, $c, $c, $c); |
||
4749 | } |
||
4750 | |||
4751 | // Use PECL gmagick + Graphics Magic to process transparent PNG images |
||
4752 | if (extension_loaded("gmagick")) { |
||
4753 | $gmagick = new \Gmagick($file); |
||
4754 | $gmagick->setimageformat('png'); |
||
4755 | |||
4756 | // Get opacity channel (negative of alpha channel) |
||
4757 | $alpha_channel_neg = clone $gmagick; |
||
4758 | $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY); |
||
4759 | |||
4760 | // Negate opacity channel |
||
4761 | $alpha_channel = new \Gmagick(); |
||
4762 | $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png"); |
||
4763 | $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0); |
||
4764 | $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED); |
||
4765 | $alpha_channel->writeimage($tempfile_alpha); |
||
4766 | |||
4767 | // Cast to 8bit+palette |
||
4768 | $imgalpha_ = imagecreatefrompng($tempfile_alpha); |
||
4769 | imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx); |
||
4770 | imagedestroy($imgalpha_); |
||
4771 | imagepng($imgalpha, $tempfile_alpha); |
||
4772 | |||
4773 | // Make opaque image |
||
4774 | $color_channels = new \Gmagick(); |
||
4775 | $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png"); |
||
4776 | $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0); |
||
4777 | $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0); |
||
4778 | $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0); |
||
4779 | $color_channels->writeimage($tempfile_plain); |
||
4780 | |||
4781 | $imgplain = imagecreatefrompng($tempfile_plain); |
||
4782 | } |
||
4783 | // Use PECL imagick + ImageMagic to process transparent PNG images |
||
4784 | elseif (extension_loaded("imagick")) { |
||
4785 | // Native cloning was added to pecl-imagick in svn commit 263814 |
||
4786 | // the first version containing it was 3.0.1RC1 |
||
4787 | static $imagickClonable = null; |
||
4788 | if ($imagickClonable === null) { |
||
4789 | $imagickClonable = version_compare(Imagick::IMAGICK_EXTVER, '3.0.1rc1') > 0; |
||
4790 | } |
||
4791 | |||
4792 | $imagick = new \Imagick($file); |
||
4793 | $imagick->setFormat('png'); |
||
4794 | |||
4795 | // Get opacity channel (negative of alpha channel) |
||
4796 | $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone(); |
||
4797 | $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA); |
||
4798 | $alpha_channel->negateImage(true); |
||
4799 | $alpha_channel->writeImage($tempfile_alpha); |
||
4800 | |||
4801 | // Cast to 8bit+palette |
||
4802 | $imgalpha_ = imagecreatefrompng($tempfile_alpha); |
||
4803 | imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx); |
||
4804 | imagedestroy($imgalpha_); |
||
4805 | imagepng($imgalpha, $tempfile_alpha); |
||
4806 | |||
4807 | // Make opaque image |
||
4808 | $color_channels = new \Imagick(); |
||
4809 | $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png"); |
||
4810 | $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0); |
||
4811 | $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0); |
||
4812 | $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0); |
||
4813 | $color_channels->writeImage($tempfile_plain); |
||
4814 | |||
4815 | $imgplain = imagecreatefrompng($tempfile_plain); |
||
4816 | } else { |
||
4817 | // allocated colors cache |
||
4818 | $allocated_colors = array(); |
||
4819 | |||
4820 | // extract alpha channel |
||
4821 | for ($xpx = 0; $xpx < $wpx; ++$xpx) { |
||
4822 | for ($ypx = 0; $ypx < $hpx; ++$ypx) { |
||
4823 | $color = imagecolorat($img, $xpx, $ypx); |
||
4824 | $col = imagecolorsforindex($img, $color); |
||
4825 | $alpha = $col['alpha']; |
||
4826 | |||
4827 | if ($eight_bit) { |
||
4828 | // with gamma correction |
||
4829 | $gammacorr = 2.2; |
||
4830 | $pixel = pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255; |
||
4831 | } else { |
||
4832 | // without gamma correction |
||
4833 | $pixel = (127 - $alpha) * 2; |
||
4834 | |||
4835 | $key = $col['red'] . $col['green'] . $col['blue']; |
||
4836 | |||
4837 | if (!isset($allocated_colors[$key])) { |
||
4838 | $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']); |
||
4839 | $allocated_colors[$key] = $pixel_img; |
||
4840 | } else { |
||
4841 | $pixel_img = $allocated_colors[$key]; |
||
4842 | } |
||
4843 | |||
4844 | imagesetpixel($img, $xpx, $ypx, $pixel_img); |
||
4845 | } |
||
4846 | |||
4847 | imagesetpixel($imgalpha, $xpx, $ypx, $pixel); |
||
4848 | } |
||
4849 | } |
||
4850 | |||
4851 | // extract image without alpha channel |
||
4852 | $imgplain = imagecreatetruecolor($wpx, $hpx); |
||
4853 | imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx); |
||
4854 | imagedestroy($img); |
||
4855 | |||
4856 | imagepng($imgalpha, $tempfile_alpha); |
||
4857 | imagepng($imgplain, $tempfile_plain); |
||
4858 | } |
||
4859 | |||
4860 | // embed mask image |
||
4861 | $this->addImagePng($tempfile_alpha, $x, $y, $w, $h, $imgalpha, true); |
||
4862 | imagedestroy($imgalpha); |
||
4863 | |||
4864 | // embed image, masked with previously embedded mask |
||
4865 | $this->addImagePng($tempfile_plain, $x, $y, $w, $h, $imgplain, false, true); |
||
4866 | imagedestroy($imgplain); |
||
4867 | |||
4868 | // remove temp files |
||
4869 | unlink($tempfile_alpha); |
||
4870 | unlink($tempfile_plain); |
||
4871 | } |
||
4872 | |||
4873 | /** |
||
4874 | * add a PNG image into the document, from a file |
||
4875 | * this should work with remote files |
||
4876 | * |
||
4877 | * @param $file |
||
4878 | * @param $x |
||
4879 | * @param $y |
||
4880 | * @param int $w |
||
4881 | * @param int $h |
||
4882 | * @throws Exception |
||
4883 | */ |
||
4884 | function addPngFromFile($file, $x, $y, $w = 0, $h = 0) |
||
4949 | |||
4950 | /** |
||
4951 | * add a PNG image into the document, from a file |
||
4952 | * this should work with remote files |
||
4953 | * |
||
4954 | * @param $file |
||
4955 | * @param $x |
||
4956 | * @param $y |
||
4957 | * @param int $w |
||
4958 | * @param int $h |
||
4959 | */ |
||
4960 | function addSvgFromFile($file, $x, $y, $w = 0, $h = 0) |
||
4975 | |||
4976 | /** |
||
4977 | * add a PNG image into the document, from a memory buffer of the file |
||
4978 | * |
||
4979 | * @param $file |
||
4980 | * @param $x |
||
4981 | * @param $y |
||
4982 | * @param float $w |
||
4983 | * @param float $h |
||
4984 | * @param $data |
||
4985 | * @param bool $is_mask |
||
4986 | * @param null $mask |
||
4987 | */ |
||
4988 | function addPngFromBuf($file, $x, $y, $w = 0.0, $h = 0.0, &$data, $is_mask = false, $mask = null) |
||
4989 | { |
||
4990 | if (isset($this->imagelist[$file])) { |
||
4991 | $data = null; |
||
4992 | $info['width'] = $this->imagelist[$file]['w']; |
||
4993 | $info['height'] = $this->imagelist[$file]['h']; |
||
4994 | $label = $this->imagelist[$file]['label']; |
||
4995 | } else { |
||
4996 | if ($data == null) { |
||
4997 | $this->addMessage('addPngFromBuf error - data not present!'); |
||
4998 | |||
4999 | return; |
||
5000 | } |
||
5001 | |||
5002 | $error = 0; |
||
5003 | |||
5004 | if (!$error) { |
||
5005 | $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10); |
||
5006 | |||
5007 | if (mb_substr($data, 0, 8, '8bit') != $header) { |
||
5008 | $error = 1; |
||
5009 | |||
5010 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
5011 | print '[addPngFromFile this file does not have a valid header ' . $file . ']'; |
||
5012 | } |
||
5013 | |||
5014 | $errormsg = 'this file does not have a valid header'; |
||
5015 | } |
||
5016 | } |
||
5017 | |||
5018 | if (!$error) { |
||
5019 | // set pointer |
||
5020 | $p = 8; |
||
5021 | $len = mb_strlen($data, '8bit'); |
||
5022 | |||
5023 | // cycle through the file, identifying chunks |
||
5024 | $haveHeader = 0; |
||
5025 | $info = array(); |
||
5026 | $idata = ''; |
||
5027 | $pdata = ''; |
||
5028 | |||
5029 | while ($p < $len) { |
||
5030 | $chunkLen = $this->getBytes($data, $p, 4); |
||
5031 | $chunkType = mb_substr($data, $p + 4, 4, '8bit'); |
||
5032 | |||
5033 | switch ($chunkType) { |
||
5034 | case 'IHDR': |
||
5035 | // this is where all the file information comes from |
||
5036 | $info['width'] = $this->getBytes($data, $p + 8, 4); |
||
5037 | $info['height'] = $this->getBytes($data, $p + 12, 4); |
||
5038 | $info['bitDepth'] = ord($data[$p + 16]); |
||
5039 | $info['colorType'] = ord($data[$p + 17]); |
||
5040 | $info['compressionMethod'] = ord($data[$p + 18]); |
||
5041 | $info['filterMethod'] = ord($data[$p + 19]); |
||
5042 | $info['interlaceMethod'] = ord($data[$p + 20]); |
||
5043 | |||
5044 | //print_r($info); |
||
5045 | $haveHeader = 1; |
||
5046 | View Code Duplication | if ($info['compressionMethod'] != 0) { |
|
5047 | $error = 1; |
||
5048 | |||
5049 | //debugpng |
||
5050 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
5051 | print '[addPngFromFile unsupported compression method ' . $file . ']'; |
||
5052 | } |
||
5053 | |||
5054 | $errormsg = 'unsupported compression method'; |
||
5055 | } |
||
5056 | |||
5057 | View Code Duplication | if ($info['filterMethod'] != 0) { |
|
5058 | $error = 1; |
||
5059 | |||
5060 | //debugpng |
||
5061 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
5062 | print '[addPngFromFile unsupported filter method ' . $file . ']'; |
||
5063 | } |
||
5064 | |||
5065 | $errormsg = 'unsupported filter method'; |
||
5066 | } |
||
5067 | break; |
||
5068 | |||
5069 | case 'PLTE': |
||
5070 | $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit'); |
||
5071 | break; |
||
5072 | |||
5073 | case 'IDAT': |
||
5074 | $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit'); |
||
5075 | break; |
||
5076 | |||
5077 | case 'tRNS': |
||
5078 | //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk |
||
5079 | //print "tRNS found, color type = ".$info['colorType']."\n"; |
||
5080 | $transparency = array(); |
||
5081 | |||
5082 | switch ($info['colorType']) { |
||
5083 | // indexed color, rbg |
||
5084 | case 3: |
||
5085 | /* corresponding to entries in the plte chunk |
||
5086 | Alpha for palette index 0: 1 byte |
||
5087 | Alpha for palette index 1: 1 byte |
||
5088 | ...etc... |
||
5089 | */ |
||
5090 | // there will be one entry for each palette entry. up until the last non-opaque entry. |
||
5091 | // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent) |
||
5092 | $transparency['type'] = 'indexed'; |
||
5093 | $trans = 0; |
||
5094 | |||
5095 | for ($i = $chunkLen; $i >= 0; $i--) { |
||
5096 | if (ord($data[$p + 8 + $i]) == 0) { |
||
5097 | $trans = $i; |
||
5098 | } |
||
5099 | } |
||
5100 | |||
5101 | $transparency['data'] = $trans; |
||
5102 | break; |
||
5103 | |||
5104 | // grayscale |
||
5105 | case 0: |
||
5106 | /* corresponding to entries in the plte chunk |
||
5107 | Gray: 2 bytes, range 0 .. (2^bitdepth)-1 |
||
5108 | */ |
||
5109 | // $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale |
||
5110 | $transparency['type'] = 'indexed'; |
||
5111 | $transparency['data'] = ord($data[$p + 8 + 1]); |
||
5112 | break; |
||
5113 | |||
5114 | // truecolor |
||
5115 | case 2: |
||
5116 | /* corresponding to entries in the plte chunk |
||
5117 | Red: 2 bytes, range 0 .. (2^bitdepth)-1 |
||
5118 | Green: 2 bytes, range 0 .. (2^bitdepth)-1 |
||
5119 | Blue: 2 bytes, range 0 .. (2^bitdepth)-1 |
||
5120 | */ |
||
5121 | $transparency['r'] = $this->getBytes($data, $p + 8, 2); |
||
5122 | // r from truecolor |
||
5123 | $transparency['g'] = $this->getBytes($data, $p + 10, 2); |
||
5124 | // g from truecolor |
||
5125 | $transparency['b'] = $this->getBytes($data, $p + 12, 2); |
||
5126 | // b from truecolor |
||
5127 | |||
5128 | $transparency['type'] = 'color-key'; |
||
5129 | break; |
||
5130 | |||
5131 | //unsupported transparency type |
||
5132 | default: |
||
5133 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
5134 | print '[addPngFromFile unsupported transparency type ' . $file . ']'; |
||
5135 | } |
||
5136 | break; |
||
5137 | } |
||
5138 | |||
5139 | // KS End new code |
||
5140 | break; |
||
5141 | |||
5142 | default: |
||
5143 | break; |
||
5144 | } |
||
5145 | |||
5146 | $p += $chunkLen + 12; |
||
5147 | } |
||
5148 | |||
5149 | View Code Duplication | if (!$haveHeader) { |
|
5150 | $error = 1; |
||
5151 | |||
5152 | //debugpng |
||
5153 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
5154 | print '[addPngFromFile information header is missing ' . $file . ']'; |
||
5155 | } |
||
5156 | |||
5157 | $errormsg = 'information header is missing'; |
||
5158 | } |
||
5159 | |||
5160 | View Code Duplication | if (isset($info['interlaceMethod']) && $info['interlaceMethod']) { |
|
5161 | $error = 1; |
||
5162 | |||
5163 | //debugpng |
||
5164 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
5165 | print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']'; |
||
5166 | } |
||
5167 | |||
5168 | $errormsg = 'There appears to be no support for interlaced images in pdf.'; |
||
5169 | } |
||
5170 | } |
||
5171 | |||
5172 | View Code Duplication | if (!$error && $info['bitDepth'] > 8) { |
|
5173 | $error = 1; |
||
5174 | |||
5175 | //debugpng |
||
5176 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
5177 | print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']'; |
||
5178 | } |
||
5179 | |||
5180 | $errormsg = 'only bit depth of 8 or less is supported'; |
||
5181 | } |
||
5182 | |||
5183 | if (!$error) { |
||
5184 | switch ($info['colorType']) { |
||
5185 | case 3: |
||
5186 | $color = 'DeviceRGB'; |
||
5187 | $ncolor = 1; |
||
5188 | break; |
||
5189 | |||
5190 | case 2: |
||
5191 | $color = 'DeviceRGB'; |
||
5192 | $ncolor = 3; |
||
5193 | break; |
||
5194 | |||
5195 | case 0: |
||
5196 | $color = 'DeviceGray'; |
||
5197 | $ncolor = 1; |
||
5198 | break; |
||
5199 | |||
5200 | default: |
||
5201 | $error = 1; |
||
5202 | |||
5203 | //debugpng |
||
5204 | if (defined("DEBUGPNG") && DEBUGPNG) { |
||
5205 | print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']'; |
||
5206 | } |
||
5207 | |||
5208 | $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.'; |
||
5209 | } |
||
5210 | } |
||
5211 | |||
5212 | if ($error) { |
||
5213 | $this->addMessage('PNG error - (' . $file . ') ' . $errormsg); |
||
5214 | |||
5215 | return; |
||
5216 | } |
||
5217 | |||
5218 | //print_r($info); |
||
5219 | // so this image is ok... add it in. |
||
5220 | $this->numImages++; |
||
5221 | $im = $this->numImages; |
||
5222 | $label = "I$im"; |
||
5223 | $this->numObj++; |
||
5224 | |||
5225 | // $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width'])); |
||
5226 | $options = array( |
||
5227 | 'label' => $label, |
||
5228 | 'data' => $idata, |
||
5229 | 'bitsPerComponent' => $info['bitDepth'], |
||
5230 | 'pdata' => $pdata, |
||
5231 | 'iw' => $info['width'], |
||
5232 | 'ih' => $info['height'], |
||
5233 | 'type' => 'png', |
||
5234 | 'color' => $color, |
||
5235 | 'ncolor' => $ncolor, |
||
5236 | 'masked' => $mask, |
||
5237 | 'isMask' => $is_mask |
||
5238 | ); |
||
5239 | |||
5240 | if (isset($transparency)) { |
||
5241 | $options['transparency'] = $transparency; |
||
5242 | } |
||
5243 | |||
5244 | $this->o_image($this->numObj, 'new', $options); |
||
5245 | $this->imagelist[$file] = array('label' => $label, 'w' => $info['width'], 'h' => $info['height']); |
||
5246 | } |
||
5247 | |||
5248 | if ($is_mask) { |
||
5249 | return; |
||
5250 | } |
||
5251 | |||
5252 | if ($w <= 0 && $h <= 0) { |
||
5253 | $w = $info['width']; |
||
5254 | $h = $info['height']; |
||
5255 | } |
||
5256 | |||
5257 | if ($w <= 0) { |
||
5258 | $w = $h / $info['height'] * $info['width']; |
||
5259 | } |
||
5260 | |||
5261 | if ($h <= 0) { |
||
5262 | $h = $w * $info['height'] / $info['width']; |
||
5263 | } |
||
5264 | |||
5265 | $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label)); |
||
5266 | } |
||
5267 | |||
5268 | /** |
||
5269 | * add a JPEG image into the document, from a file |
||
5270 | * |
||
5271 | * @param $img |
||
5272 | * @param $x |
||
5273 | * @param $y |
||
5274 | * @param int $w |
||
5275 | * @param int $h |
||
5276 | */ |
||
5277 | function addJpegFromFile($img, $x, $y, $w = 0, $h = 0) |
||
5319 | |||
5320 | /** |
||
5321 | * common code used by the two JPEG adding functions |
||
5322 | * @param $data |
||
5323 | * @param $x |
||
5324 | * @param $y |
||
5325 | * @param int $w |
||
5326 | * @param int $h |
||
5327 | * @param $imageWidth |
||
5328 | * @param $imageHeight |
||
5329 | * @param int $channels |
||
5330 | * @param $imgname |
||
5331 | */ |
||
5332 | private function addJpegImage_common( |
||
5384 | |||
5385 | /** |
||
5386 | * specify where the document should open when it first starts |
||
5387 | * |
||
5388 | * @param $style |
||
5389 | * @param int $a |
||
5390 | * @param int $b |
||
5391 | * @param int $c |
||
5392 | */ |
||
5393 | View Code Duplication | function openHere($style, $a = 0, $b = 0, $c = 0) |
|
5414 | |||
5415 | /** |
||
5416 | * Add JavaScript code to the PDF document |
||
5417 | * |
||
5418 | * @param string $code |
||
5419 | */ |
||
5420 | function addJavascript($code) |
||
5421 | { |
||
5422 | $this->javascript .= $code; |
||
5423 | } |
||
5424 | |||
5425 | /** |
||
5426 | * create a labelled destination within the document |
||
5427 | * |
||
5428 | * @param $label |
||
5429 | * @param $style |
||
5430 | * @param int $a |
||
5431 | * @param int $b |
||
5432 | * @param int $c |
||
5433 | */ |
||
5434 | View Code Duplication | function addDestination($label, $style, $a = 0, $b = 0, $c = 0) |
|
5450 | |||
5451 | /** |
||
5452 | * define font families, this is used to initialize the font families for the default fonts |
||
5453 | * and for the user to add new ones for their fonts. The default bahavious can be overridden should |
||
5454 | * that be desired. |
||
5455 | * |
||
5456 | * @param $family |
||
5457 | * @param string $options |
||
5458 | */ |
||
5459 | function setFontFamily($family, $options = '') |
||
5499 | |||
5500 | /** |
||
5501 | * used to add messages for use in debugging |
||
5502 | * |
||
5503 | * @param $message |
||
5504 | */ |
||
5505 | function addMessage($message) |
||
5509 | |||
5510 | /** |
||
5511 | * a few functions which should allow the document to be treated transactionally. |
||
5512 | * |
||
5513 | * @param $action |
||
5514 | */ |
||
5515 | function transaction($action) |
||
5562 | } |
||
5563 |
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.