abstract_renderer.cls.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. <?php
  2. /**
  3. * @package dompdf
  4. * @link http://dompdf.github.com/
  5. * @author Benj Carson <benjcarson@digitaljunkies.ca>
  6. * @author Helmut Tischer <htischer@weihenstephan.org>
  7. * @author Fabien Ménager <fabien.menager@gmail.com>
  8. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  9. */
  10. /**
  11. * Base renderer class
  12. *
  13. * @access private
  14. * @package dompdf
  15. */
  16. abstract class Abstract_Renderer {
  17. /**
  18. * Rendering backend
  19. *
  20. * @var Canvas
  21. */
  22. protected $_canvas;
  23. /**
  24. * Current dompdf instance
  25. *
  26. * @var DOMPDF
  27. */
  28. protected $_dompdf;
  29. /**
  30. * Class constructor
  31. *
  32. * @param DOMPDF $dompdf The current dompdf instance
  33. */
  34. function __construct(DOMPDF $dompdf) {
  35. $this->_dompdf = $dompdf;
  36. $this->_canvas = $dompdf->get_canvas();
  37. }
  38. /**
  39. * Render a frame.
  40. *
  41. * Specialized in child classes
  42. *
  43. * @param Frame $frame The frame to render
  44. */
  45. abstract function render(Frame $frame);
  46. //........................................................................
  47. /**
  48. * Render a background image over a rectangular area
  49. *
  50. * @param string $url The background image to load
  51. * @param float $x The left edge of the rectangular area
  52. * @param float $y The top edge of the rectangular area
  53. * @param float $width The width of the rectangular area
  54. * @param float $height The height of the rectangular area
  55. * @param Style $style The associated Style object
  56. */
  57. protected function _background_image($url, $x, $y, $width, $height, $style) {
  58. $sheet = $style->get_stylesheet();
  59. // Skip degenerate cases
  60. if ( $width == 0 || $height == 0 ) {
  61. return;
  62. }
  63. $box_width = $width;
  64. $box_height = $height;
  65. //debugpng
  66. if (DEBUGPNG) print '[_background_image '.$url.']';
  67. list($img, $type, /*$msg*/) = Image_Cache::resolve_url(
  68. $url,
  69. $sheet->get_protocol(),
  70. $sheet->get_host(),
  71. $sheet->get_base_path(),
  72. $this->_dompdf
  73. );
  74. // Bail if the image is no good
  75. if ( Image_Cache::is_broken($img) ) {
  76. return;
  77. }
  78. //Try to optimize away reading and composing of same background multiple times
  79. //Postponing read with imagecreatefrom ...()
  80. //final composition parameters and name not known yet
  81. //Therefore read dimension directly from file, instead of creating gd object first.
  82. //$img_w = imagesx($src); $img_h = imagesy($src);
  83. list($img_w, $img_h) = dompdf_getimagesize($img);
  84. if (!isset($img_w) || $img_w == 0 || !isset($img_h) || $img_h == 0) {
  85. return;
  86. }
  87. $repeat = $style->background_repeat;
  88. $dpi = $this->_dompdf->get_option("dpi");
  89. //Increase background resolution and dependent box size according to image resolution to be placed in
  90. //Then image can be copied in without resize
  91. $bg_width = round((float)($width * $dpi) / 72);
  92. $bg_height = round((float)($height * $dpi) / 72);
  93. //Need %bg_x, $bg_y as background pos, where img starts, converted to pixel
  94. list($bg_x, $bg_y) = $style->background_position;
  95. if ( is_percent($bg_x) ) {
  96. // The point $bg_x % from the left edge of the image is placed
  97. // $bg_x % from the left edge of the background rectangle
  98. $p = ((float)$bg_x)/100.0;
  99. $x1 = $p * $img_w;
  100. $x2 = $p * $bg_width;
  101. $bg_x = $x2 - $x1;
  102. }
  103. else {
  104. $bg_x = (float)($style->length_in_pt($bg_x)*$dpi) / 72;
  105. }
  106. $bg_x = round($bg_x + $style->length_in_pt($style->border_left_width)*$dpi / 72);
  107. if ( is_percent($bg_y) ) {
  108. // The point $bg_y % from the left edge of the image is placed
  109. // $bg_y % from the left edge of the background rectangle
  110. $p = ((float)$bg_y)/100.0;
  111. $y1 = $p * $img_h;
  112. $y2 = $p * $bg_height;
  113. $bg_y = $y2 - $y1;
  114. }
  115. else {
  116. $bg_y = (float)($style->length_in_pt($bg_y)*$dpi) / 72;
  117. }
  118. $bg_y = round($bg_y + $style->length_in_pt($style->border_top_width)*$dpi / 72);
  119. //clip background to the image area on partial repeat. Nothing to do if img off area
  120. //On repeat, normalize start position to the tile at immediate left/top or 0/0 of area
  121. //On no repeat with positive offset: move size/start to have offset==0
  122. //Handle x/y Dimensions separately
  123. if ( $repeat !== "repeat" && $repeat !== "repeat-x" ) {
  124. //No repeat x
  125. if ($bg_x < 0) {
  126. $bg_width = $img_w + $bg_x;
  127. }
  128. else {
  129. $x += ($bg_x * 72)/$dpi;
  130. $bg_width = $bg_width - $bg_x;
  131. if ($bg_width > $img_w) {
  132. $bg_width = $img_w;
  133. }
  134. $bg_x = 0;
  135. }
  136. if ($bg_width <= 0) {
  137. return;
  138. }
  139. $width = (float)($bg_width * 72)/$dpi;
  140. }
  141. else {
  142. //repeat x
  143. if ($bg_x < 0) {
  144. $bg_x = - ((-$bg_x) % $img_w);
  145. }
  146. else {
  147. $bg_x = $bg_x % $img_w;
  148. if ($bg_x > 0) {
  149. $bg_x -= $img_w;
  150. }
  151. }
  152. }
  153. if ( $repeat !== "repeat" && $repeat !== "repeat-y" ) {
  154. //no repeat y
  155. if ($bg_y < 0) {
  156. $bg_height = $img_h + $bg_y;
  157. }
  158. else {
  159. $y += ($bg_y * 72)/$dpi;
  160. $bg_height = $bg_height - $bg_y;
  161. if ($bg_height > $img_h) {
  162. $bg_height = $img_h;
  163. }
  164. $bg_y = 0;
  165. }
  166. if ($bg_height <= 0) {
  167. return;
  168. }
  169. $height = (float)($bg_height * 72)/$dpi;
  170. }
  171. else {
  172. //repeat y
  173. if ($bg_y < 0) {
  174. $bg_y = - ((-$bg_y) % $img_h);
  175. }
  176. else {
  177. $bg_y = $bg_y % $img_h;
  178. if ($bg_y > 0) {
  179. $bg_y -= $img_h;
  180. }
  181. }
  182. }
  183. //Optimization, if repeat has no effect
  184. if ( $repeat === "repeat" && $bg_y <= 0 && $img_h+$bg_y >= $bg_height ) {
  185. $repeat = "repeat-x";
  186. }
  187. if ( $repeat === "repeat" && $bg_x <= 0 && $img_w+$bg_x >= $bg_width ) {
  188. $repeat = "repeat-y";
  189. }
  190. if ( ($repeat === "repeat-x" && $bg_x <= 0 && $img_w+$bg_x >= $bg_width) ||
  191. ($repeat === "repeat-y" && $bg_y <= 0 && $img_h+$bg_y >= $bg_height) ) {
  192. $repeat = "no-repeat";
  193. }
  194. //Use filename as indicator only
  195. //different names for different variants to have different copies in the pdf
  196. //This is not dependent of background color of box! .'_'.(is_array($bg_color) ? $bg_color["hex"] : $bg_color)
  197. //Note: Here, bg_* are the start values, not end values after going through the tile loops!
  198. $filedummy = $img;
  199. $is_png = false;
  200. $filedummy .= '_'.$bg_width.'_'.$bg_height.'_'.$bg_x.'_'.$bg_y.'_'.$repeat;
  201. //Optimization to avoid multiple times rendering the same image.
  202. //If check functions are existing and identical image already cached,
  203. //then skip creation of duplicate, because it is not needed by addImagePng
  204. if ( $this->_canvas instanceof CPDF_Adapter &&
  205. $this->_canvas->get_cpdf()->image_iscached($filedummy) ) {
  206. $bg = null;
  207. }
  208. else {
  209. // Create a new image to fit over the background rectangle
  210. $bg = imagecreatetruecolor($bg_width, $bg_height);
  211. switch (strtolower($type)) {
  212. case IMAGETYPE_PNG:
  213. $is_png = true;
  214. imagesavealpha($bg, true);
  215. imagealphablending($bg, false);
  216. $src = imagecreatefrompng($img);
  217. break;
  218. case IMAGETYPE_JPEG:
  219. $src = imagecreatefromjpeg($img);
  220. break;
  221. case IMAGETYPE_GIF:
  222. $src = imagecreatefromgif($img);
  223. break;
  224. case IMAGETYPE_BMP:
  225. $src = imagecreatefrombmp($img);
  226. break;
  227. default:
  228. return; // Unsupported image type
  229. }
  230. if ( $src == null ) {
  231. return;
  232. }
  233. //Background color if box is not relevant here
  234. //Non transparent image: box clipped to real size. Background non relevant.
  235. //Transparent image: The image controls the transparency and lets shine through whatever background.
  236. //However on transparent image preset the composed image with the transparency color,
  237. //to keep the transparency when copying over the non transparent parts of the tiles.
  238. $ti = imagecolortransparent($src);
  239. if ( $ti >= 0 ) {
  240. $tc = imagecolorsforindex($src, $ti);
  241. $ti = imagecolorallocate($bg, $tc['red'], $tc['green'], $tc['blue']);
  242. imagefill($bg, 0, 0, $ti);
  243. imagecolortransparent($bg, $ti);
  244. }
  245. //This has only an effect for the non repeatable dimension.
  246. //compute start of src and dest coordinates of the single copy
  247. if ( $bg_x < 0 ) {
  248. $dst_x = 0;
  249. $src_x = -$bg_x;
  250. }
  251. else {
  252. $src_x = 0;
  253. $dst_x = $bg_x;
  254. }
  255. if ( $bg_y < 0 ) {
  256. $dst_y = 0;
  257. $src_y = -$bg_y;
  258. }
  259. else {
  260. $src_y = 0;
  261. $dst_y = $bg_y;
  262. }
  263. //For historical reasons exchange meanings of variables:
  264. //start_* will be the start values, while bg_* will be the temporary start values in the loops
  265. $start_x = $bg_x;
  266. $start_y = $bg_y;
  267. // Copy regions from the source image to the background
  268. if ( $repeat === "no-repeat" ) {
  269. // Simply place the image on the background
  270. imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $img_w, $img_h);
  271. }
  272. else if ( $repeat === "repeat-x" ) {
  273. for ( $bg_x = $start_x; $bg_x < $bg_width; $bg_x += $img_w ) {
  274. if ( $bg_x < 0 ) {
  275. $dst_x = 0;
  276. $src_x = -$bg_x;
  277. $w = $img_w + $bg_x;
  278. }
  279. else {
  280. $dst_x = $bg_x;
  281. $src_x = 0;
  282. $w = $img_w;
  283. }
  284. imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $w, $img_h);
  285. }
  286. }
  287. else if ( $repeat === "repeat-y" ) {
  288. for ( $bg_y = $start_y; $bg_y < $bg_height; $bg_y += $img_h ) {
  289. if ( $bg_y < 0 ) {
  290. $dst_y = 0;
  291. $src_y = -$bg_y;
  292. $h = $img_h + $bg_y;
  293. }
  294. else {
  295. $dst_y = $bg_y;
  296. $src_y = 0;
  297. $h = $img_h;
  298. }
  299. imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $img_w, $h);
  300. }
  301. }
  302. else if ( $repeat === "repeat" ) {
  303. for ( $bg_y = $start_y; $bg_y < $bg_height; $bg_y += $img_h ) {
  304. for ( $bg_x = $start_x; $bg_x < $bg_width; $bg_x += $img_w ) {
  305. if ( $bg_x < 0 ) {
  306. $dst_x = 0;
  307. $src_x = -$bg_x;
  308. $w = $img_w + $bg_x;
  309. }
  310. else {
  311. $dst_x = $bg_x;
  312. $src_x = 0;
  313. $w = $img_w;
  314. }
  315. if ( $bg_y < 0 ) {
  316. $dst_y = 0;
  317. $src_y = -$bg_y;
  318. $h = $img_h + $bg_y;
  319. }
  320. else {
  321. $dst_y = $bg_y;
  322. $src_y = 0;
  323. $h = $img_h;
  324. }
  325. imagecopy($bg, $src, $dst_x, $dst_y, $src_x, $src_y, $w, $h);
  326. }
  327. }
  328. }
  329. else {
  330. print 'Unknown repeat!';
  331. }
  332. imagedestroy($src);
  333. } /* End optimize away creation of duplicates */
  334. $this->_canvas->clipping_rectangle($x, $y, $box_width, $box_height);
  335. //img: image url string
  336. //img_w, img_h: original image size in px
  337. //width, height: box size in pt
  338. //bg_width, bg_height: box size in px
  339. //x, y: left/top edge of box on page in pt
  340. //start_x, start_y: placement of image relative to pattern
  341. //$repeat: repeat mode
  342. //$bg: GD object of result image
  343. //$src: GD object of original image
  344. //When using cpdf and optimization to direct png creation from gd object is available,
  345. //don't create temp file, but place gd object directly into the pdf
  346. if ( !$is_png && $this->_canvas instanceof CPDF_Adapter ) {
  347. // Note: CPDF_Adapter image converts y position
  348. $this->_canvas->get_cpdf()->addImagePng($filedummy, $x, $this->_canvas->get_height() - $y - $height, $width, $height, $bg);
  349. }
  350. else {
  351. $tmp_dir = $this->_dompdf->get_option("temp_dir");
  352. $tmp_name = tempnam($tmp_dir, "bg_dompdf_img_");
  353. @unlink($tmp_name);
  354. $tmp_file = "$tmp_name.png";
  355. //debugpng
  356. if (DEBUGPNG) print '[_background_image '.$tmp_file.']';
  357. imagepng($bg, $tmp_file);
  358. $this->_canvas->image($tmp_file, $x, $y, $width, $height);
  359. imagedestroy($bg);
  360. //debugpng
  361. if (DEBUGPNG) print '[_background_image unlink '.$tmp_file.']';
  362. if (!DEBUGKEEPTEMP) {
  363. unlink($tmp_file);
  364. }
  365. }
  366. $this->_canvas->clipping_end();
  367. }
  368. protected function _get_dash_pattern($style, $width) {
  369. $pattern = array();
  370. switch ($style) {
  371. default:
  372. /*case "solid":
  373. case "double":
  374. case "groove":
  375. case "inset":
  376. case "outset":
  377. case "ridge":*/
  378. case "none": break;
  379. case "dotted":
  380. if ( $width <= 1 )
  381. $pattern = array($width, $width*2);
  382. else
  383. $pattern = array($width);
  384. break;
  385. case "dashed":
  386. $pattern = array(3 * $width);
  387. break;
  388. }
  389. return $pattern;
  390. }
  391. protected function _border_none($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  392. return;
  393. }
  394. protected function _border_hidden($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  395. return;
  396. }
  397. // Border rendering functions
  398. protected function _border_dotted($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  399. $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "dotted", $r1, $r2);
  400. }
  401. protected function _border_dashed($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  402. $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "dashed", $r1, $r2);
  403. }
  404. protected function _border_solid($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  405. // TODO: Solve rendering where one corner is beveled (radius == 0), one corner isn't.
  406. if ( $corner_style !== "bevel" || $r1 > 0 || $r2 > 0 ) {
  407. // do it the simple way
  408. $this->_border_line($x, $y, $length, $color, $widths, $side, $corner_style, "solid", $r1, $r2);
  409. return;
  410. }
  411. list($top, $right, $bottom, $left) = $widths;
  412. // All this polygon business is for beveled corners...
  413. switch ($side) {
  414. case "top":
  415. $points = array($x, $y,
  416. $x + $length, $y,
  417. $x + $length - $right, $y + $top,
  418. $x + $left, $y + $top);
  419. $this->_canvas->polygon($points, $color, null, null, true);
  420. break;
  421. case "bottom":
  422. $points = array($x, $y,
  423. $x + $length, $y,
  424. $x + $length - $right, $y - $bottom,
  425. $x + $left, $y - $bottom);
  426. $this->_canvas->polygon($points, $color, null, null, true);
  427. break;
  428. case "left":
  429. $points = array($x, $y,
  430. $x, $y + $length,
  431. $x + $left, $y + $length - $bottom,
  432. $x + $left, $y + $top);
  433. $this->_canvas->polygon($points, $color, null, null, true);
  434. break;
  435. case "right":
  436. $points = array($x, $y,
  437. $x, $y + $length,
  438. $x - $right, $y + $length - $bottom,
  439. $x - $right, $y + $top);
  440. $this->_canvas->polygon($points, $color, null, null, true);
  441. break;
  442. default:
  443. return;
  444. }
  445. }
  446. protected function _apply_ratio($side, $ratio, $top, $right, $bottom, $left, &$x, &$y, &$length, &$r1, &$r2) {
  447. switch ($side) {
  448. case "top":
  449. $r1 -= $left * $ratio;
  450. $r2 -= $right * $ratio;
  451. $x += $left * $ratio;
  452. $y += $top * $ratio;
  453. $length -= $left * $ratio + $right * $ratio;
  454. break;
  455. case "bottom":
  456. $r1 -= $right * $ratio;
  457. $r2 -= $left * $ratio;
  458. $x += $left * $ratio;
  459. $y -= $bottom * $ratio;
  460. $length -= $left * $ratio + $right * $ratio;
  461. break;
  462. case "left":
  463. $r1 -= $top * $ratio;
  464. $r2 -= $bottom * $ratio;
  465. $x += $left * $ratio;
  466. $y += $top * $ratio;
  467. $length -= $top * $ratio + $bottom * $ratio;
  468. break;
  469. case "right":
  470. $r1 -= $bottom * $ratio;
  471. $r2 -= $top * $ratio;
  472. $x -= $right * $ratio;
  473. $y += $top * $ratio;
  474. $length -= $top * $ratio + $bottom * $ratio;
  475. break;
  476. default:
  477. return;
  478. }
  479. }
  480. protected function _border_double($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  481. list($top, $right, $bottom, $left) = $widths;
  482. $third_widths = array($top / 3, $right / 3, $bottom / 3, $left / 3);
  483. // draw the outer border
  484. $this->_border_solid($x, $y, $length, $color, $third_widths, $side, $corner_style, $r1, $r2);
  485. $this->_apply_ratio($side, 2/3, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2);
  486. $this->_border_solid($x, $y, $length, $color, $third_widths, $side, $corner_style, $r1, $r2);
  487. }
  488. protected function _border_groove($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  489. list($top, $right, $bottom, $left) = $widths;
  490. $half_widths = array($top / 2, $right / 2, $bottom / 2, $left / 2);
  491. $this->_border_inset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
  492. $this->_apply_ratio($side, 0.5, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2);
  493. $this->_border_outset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
  494. }
  495. protected function _border_ridge($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  496. list($top, $right, $bottom, $left) = $widths;
  497. $half_widths = array($top / 2, $right / 2, $bottom / 2, $left / 2);
  498. $this->_border_outset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
  499. $this->_apply_ratio($side, 0.5, $top, $right, $bottom, $left, $x, $y, $length, $r1, $r2);
  500. $this->_border_inset($x, $y, $length, $color, $half_widths, $side, $corner_style, $r1, $r2);
  501. }
  502. protected function _tint($c) {
  503. if ( !is_numeric($c) )
  504. return $c;
  505. return min(1, $c + 0.16);
  506. }
  507. protected function _shade($c) {
  508. if ( !is_numeric($c) )
  509. return $c;
  510. return max(0, $c - 0.33);
  511. }
  512. protected function _border_inset($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  513. switch ($side) {
  514. case "top":
  515. case "left":
  516. $shade = array_map(array($this, "_shade"), $color);
  517. $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2);
  518. break;
  519. case "bottom":
  520. case "right":
  521. $tint = array_map(array($this, "_tint"), $color);
  522. $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2);
  523. break;
  524. default:
  525. return;
  526. }
  527. }
  528. protected function _border_outset($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $r1 = 0, $r2 = 0) {
  529. switch ($side) {
  530. case "top":
  531. case "left":
  532. $tint = array_map(array($this, "_tint"), $color);
  533. $this->_border_solid($x, $y, $length, $tint, $widths, $side, $corner_style, $r1, $r2);
  534. break;
  535. case "bottom":
  536. case "right":
  537. $shade = array_map(array($this, "_shade"), $color);
  538. $this->_border_solid($x, $y, $length, $shade, $widths, $side, $corner_style, $r1, $r2);
  539. break;
  540. default:
  541. return;
  542. }
  543. }
  544. // Draws a solid, dotted, or dashed line, observing the border radius
  545. protected function _border_line($x, $y, $length, $color, $widths, $side, $corner_style = "bevel", $pattern_name, $r1 = 0, $r2 = 0) {
  546. list($top, $right, $bottom, $left) = $widths;
  547. $width = $$side;
  548. $pattern = $this->_get_dash_pattern($pattern_name, $width);
  549. $half_width = $width/2;
  550. $r1 -= $half_width;
  551. $r2 -= $half_width;
  552. $adjust = $r1/80;
  553. $length -= $width;
  554. switch ($side) {
  555. case "top":
  556. $x += $half_width;
  557. $y += $half_width;
  558. if ( $r1 > 0 ) {
  559. $this->_canvas->arc($x + $r1, $y + $r1, $r1, $r1, 90-$adjust, 135+$adjust, $color, $width, $pattern);
  560. }
  561. $this->_canvas->line($x + $r1, $y, $x + $length - $r2, $y, $color, $width, $pattern);
  562. if ( $r2 > 0 ) {
  563. $this->_canvas->arc($x + $length - $r2, $y + $r2, $r2, $r2, 45-$adjust, 90+$adjust, $color, $width, $pattern);
  564. }
  565. break;
  566. case "bottom":
  567. $x += $half_width;
  568. $y -= $half_width;
  569. if ( $r1 > 0 ) {
  570. $this->_canvas->arc($x + $r1, $y - $r1, $r1, $r1, 225-$adjust, 270+$adjust, $color, $width, $pattern);
  571. }
  572. $this->_canvas->line($x + $r1, $y, $x + $length - $r2, $y, $color, $width, $pattern);
  573. if ( $r2 > 0 ) {
  574. $this->_canvas->arc($x + $length - $r2, $y - $r2, $r2, $r2, 270-$adjust, 315+$adjust, $color, $width, $pattern);
  575. }
  576. break;
  577. case "left":
  578. $y += $half_width;
  579. $x += $half_width;
  580. if ( $r1 > 0 ) {
  581. $this->_canvas->arc($x + $r1, $y + $r1, $r1, $r1, 135-$adjust, 180+$adjust, $color, $width, $pattern);
  582. }
  583. $this->_canvas->line($x, $y + $r1, $x, $y + $length - $r2, $color, $width, $pattern);
  584. if ( $r2 > 0 ) {
  585. $this->_canvas->arc($x + $r2, $y + $length - $r2, $r2, $r2, 180-$adjust, 225+$adjust, $color, $width, $pattern);
  586. }
  587. break;
  588. case "right":
  589. $y += $half_width;
  590. $x -= $half_width;
  591. if ( $r1 > 0 ) {
  592. $this->_canvas->arc($x - $r1, $y + $r1, $r1, $r1, 0-$adjust, 45+$adjust, $color, $width, $pattern);
  593. }
  594. $this->_canvas->line($x, $y + $r1, $x, $y + $length - $r2, $color, $width, $pattern);
  595. if ( $r2 > 0 ) {
  596. $this->_canvas->arc($x - $r2, $y + $length - $r2, $r2, $r2, 315-$adjust, 360+$adjust, $color, $width, $pattern);
  597. }
  598. break;
  599. }
  600. }
  601. protected function _set_opacity($opacity) {
  602. if ( is_numeric($opacity) && $opacity <= 1.0 && $opacity >= 0.0 ) {
  603. $this->_canvas->set_opacity( $opacity );
  604. }
  605. }
  606. protected function _debug_layout($box, $color = "red", $style = array()) {
  607. $this->_canvas->rectangle($box[0], $box[1], $box[2], $box[3], CSS_Color::parse($color), 0.1, $style);
  608. }
  609. }