/** * Embed a text in an image. * Similar to the the media firewall function. * * @param resource $im Image to watermark * @param string $text Text to display * @param int $maxsize Maximum size for the font * @param string $color Font color * @param string $font Font to be used * @param string $vpos Description of the vertical position (top, middle, bottom, accross) * @param string $hpos Description of the horizontal position (right, left, top2bottom, bottom2top) */ protected function embedText($im, $text, $maxsize, $color, $font, $vpos, $hpos) { // there are two ways to embed text with PHP // (preferred) using GD and FreeType you can embed text using any True Type font // (fall back) if that is not available, you can insert basic monospaced text $col = $this->hexrgb($color); $textcolor = imagecolorallocate($im, $col['red'], $col['green'], $col['blue']); // make adjustments to settings that imagestring and imagestringup can’t handle if (!$this->use_ttf) { // imagestringup only writes up, can’t use top2bottom if ($hpos === 'top2bottom') { $hpos = 'bottom2top'; } } $text = I18N::reverseText($text); $height = imagesy($im); $width = imagesx($im); $calc_angle = rad2deg(atan($height / $width)); $hypoth = $height / sin(deg2rad($calc_angle)); // vertical and horizontal position of the text switch ($vpos) { default: case 'top': $taille = $this->textLength($maxsize, $width, $text); $pos_y = $height * 0.15 + $taille; $pos_x = $width * 0.15; $rotation = 0; break; case 'middle': $taille = $this->textLength($maxsize, $width, $text); $pos_y = ($height + $taille) / 2; $pos_x = $width * 0.15; $rotation = 0; break; case 'bottom': $taille = $this->textLength($maxsize, $width, $text); $pos_y = $height * 0.85 - $taille; $pos_x = $width * 0.15; $rotation = 0; break; case 'across': switch ($hpos) { default: case 'left': $taille = $this->textLength($maxsize, $hypoth, $text); $pos_y = $height * 0.85 - $taille; $pos_x = $width * 0.15; $rotation = $calc_angle; break; case 'right': $taille = $this->textLength($maxsize, $hypoth, $text); $pos_y = $height * 0.15 - $taille; $pos_x = $width * 0.85; $rotation = $calc_angle + 180; break; case 'top2bottom': $taille = $this->textLength($maxsize, $height, $text); $pos_y = $height * 0.15 - $taille; $pos_x = $width * 0.9 - $taille; $rotation = -90; break; case 'bottom2top': $taille = $this->textLength($maxsize, $height, $text); $pos_y = $height * 0.85; $pos_x = $width * 0.15; $rotation = 90; break; } break; } // apply the text if ($this->use_ttf) { // if imagettftext throws errors, catch them with a custom error handler set_error_handler(array($this, 'imageTtfTextErrorHandler')); imagettftext($im, $taille, $rotation, $pos_x, $pos_y, $textcolor, $font, $text); restore_error_handler(); } // Don’t use an ‘else’ here since imagettftextErrorHandler may have changed the value of $useTTF from true to false if (!$this->use_ttf) { if ($rotation !== 90) { imagestring($im, 5, $pos_x, $pos_y, $text, $textcolor); } else { imagestringup($im, 5, $pos_x, $pos_y, $text, $textcolor); } } }
/** * Generate both the HTML and PNG components of the fan chart * * The HTML and PNG components both require the same co-ordinate calculations, * so we generate them using the same code, but we send them in separate * HTTP requests. * * @param string $what "png" or "html" * * @return string */ public function generateFanChart($what) { $treeid = $this->sosaAncestors($this->generations); $fanw = 640 * $this->fan_width / 100; $fandeg = 90 * $this->fan_style; $html = ''; $treesize = count($treeid) + 1; // generations count $gen = log($treesize) / log(2) - 1; $sosa = $treesize - 1; // fan size if ($fandeg == 0) { $fandeg = 360; } $fandeg = min($fandeg, 360); $fandeg = max($fandeg, 90); $cx = $fanw / 2 - 1; // center x $cy = $cx; // center y $rx = $fanw - 1; $rw = $fanw / ($gen + 1); $fanh = $fanw; // fan height if ($fandeg == 180) { $fanh = round($fanh * ($gen + 1) / ($gen * 2)); } if ($fandeg == 270) { $fanh = round($fanh * 0.86); } $scale = $fanw / 640; // image init $image = ImageCreate($fanw, $fanh); $white = ImageColorAllocate($image, 0xff, 0xff, 0xff); ImageFilledRectangle($image, 0, 0, $fanw, $fanh, $white); ImageColorTransparent($image, $white); $color = ImageColorAllocate($image, hexdec(substr(Theme::theme()->parameter('chart-font-color'), 0, 2)), hexdec(substr(Theme::theme()->parameter('chart-font-color'), 2, 2)), hexdec(substr(Theme::theme()->parameter('chart-font-color'), 4, 2))); $bgcolor = ImageColorAllocate($image, hexdec(substr(Theme::theme()->parameter('chart-background-u'), 0, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-u'), 2, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-u'), 4, 2))); $bgcolorM = ImageColorAllocate($image, hexdec(substr(Theme::theme()->parameter('chart-background-m'), 0, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-m'), 2, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-m'), 4, 2))); $bgcolorF = ImageColorAllocate($image, hexdec(substr(Theme::theme()->parameter('chart-background-f'), 0, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-f'), 2, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-f'), 4, 2))); // imagemap $imagemap = '<map id="fanmap" name="fanmap">'; // loop to create fan cells while ($gen >= 0) { // clean current generation area $deg2 = 360 + ($fandeg - 180) / 2; $deg1 = $deg2 - $fandeg; ImageFilledArc($image, $cx, $cy, $rx, $rx, $deg1, $deg2, $bgcolor, IMG_ARC_PIE); $rx -= 3; // calculate new angle $p2 = pow(2, $gen); $angle = $fandeg / $p2; $deg2 = 360 + ($fandeg - 180) / 2; $deg1 = $deg2 - $angle; // special case for rootid cell if ($gen == 0) { $deg1 = 90; $deg2 = 360 + $deg1; } // draw each cell while ($sosa >= $p2) { $person = $treeid[$sosa]; if ($person) { $name = $person->getFullName(); $addname = $person->getAddName(); $text = I18N::reverseText($name); if ($addname) { $text .= "\n" . I18N::reverseText($addname); } $text .= "\n" . I18N::reverseText($person->getLifeSpan()); switch ($person->getSex()) { case 'M': $bg = $bgcolorM; break; case 'F': $bg = $bgcolorF; break; default: $bg = $bgcolor; break; } ImageFilledArc($image, $cx, $cy, $rx, $rx, $deg1, $deg2, $bg, IMG_ARC_PIE); // split and center text by lines $wmax = (int) ($angle * 7 / Theme::theme()->parameter('chart-font-size') * $scale); $wmax = min($wmax, 35 * $scale); if ($gen == 0) { $wmax = min($wmax, 17 * $scale); } $text = $this->splitAlignText($text, $wmax); // text angle $tangle = 270 - ($deg1 + $angle / 2); if ($gen == 0) { $tangle = 0; } // calculate text position $deg = $deg1 + 0.44; if ($deg2 - $deg1 > 40) { $deg = $deg1 + ($deg2 - $deg1) / 11; } if ($deg2 - $deg1 > 80) { $deg = $deg1 + ($deg2 - $deg1) / 7; } if ($deg2 - $deg1 > 140) { $deg = $deg1 + ($deg2 - $deg1) / 4; } if ($gen == 0) { $deg = 180; } $rad = deg2rad($deg); $mr = ($rx - $rw / 4) / 2; if ($gen > 0 && $deg2 - $deg1 > 80) { $mr = $rx / 2; } $tx = $cx + $mr * cos($rad); $ty = $cy - $mr * -sin($rad); if ($sosa == 1) { $ty -= $mr / 2; } // print text ImageTtfText($image, Theme::theme()->parameter('chart-font-size'), $tangle, $tx, $ty, $color, Theme::theme()->parameter('chart-font-name'), $text); $imagemap .= '<area shape="poly" coords="'; // plot upper points $mr = $rx / 2; $deg = $deg1; while ($deg <= $deg2) { $rad = deg2rad($deg); $tx = round($cx + $mr * cos($rad)); $ty = round($cy - $mr * -sin($rad)); $imagemap .= "{$tx},{$ty},"; $deg += ($deg2 - $deg1) / 6; } // plot lower points $mr = ($rx - $rw) / 2; $deg = $deg2; while ($deg >= $deg1) { $rad = deg2rad($deg); $tx = round($cx + $mr * cos($rad)); $ty = round($cy - $mr * -sin($rad)); $imagemap .= "{$tx},{$ty},"; $deg -= ($deg2 - $deg1) / 6; } // join first point $mr = $rx / 2; $deg = $deg1; $rad = deg2rad($deg); $tx = round($cx + $mr * cos($rad)); $ty = round($cy - $mr * -sin($rad)); $imagemap .= "{$tx},{$ty}"; // add action url $pid = $person->getXref(); $imagemap .= '" href="#' . $pid . '"'; $tempURL = 'fanchart.php?rootid=' . $pid . '&generations=' . $this->generations . '&fan_width=' . $this->fan_width . '&fan_style=' . $this->fan_style . '&ged=' . $person->getTree()->getNameUrl(); $html .= '<div id="' . $pid . '" class="fan_chart_menu">'; $html .= '<div class="person_box"><div class="details1">'; $html .= '<a href="' . $person->getHtmlUrl() . '" class="name1">' . $name; if ($addname) { $html .= $addname; } $html .= '</a>'; $html .= '<ul class="charts">'; $html .= '<li><a href="pedigree.php?rootid=' . $pid . '&ged=' . $person->getTree()->getNameUrl() . '" >' . I18N::translate('Pedigree') . '</a></li>'; if (Module::getModuleByName('googlemap')) { $html .= '<li><a href="module.php?mod=googlemap&mod_action=pedigree_map&rootid=' . $pid . '&ged=' . $person->getTree()->getNameUrl() . '">' . I18N::translate('Pedigree map') . '</a></li>'; } $gedcomid = $person->getTree()->getUserPreference(Auth::user(), 'gedcomid'); if ($gedcomid && $gedcomid != $pid) { $html .= '<li><a href="relationship.php?pid1=' . $gedcomid . '&pid2=' . $pid . '&ged=' . $person->getTree()->getNameUrl() . '">' . I18N::translate('Relationship to me') . '</a></li>'; } $html .= '<li><a href="descendancy.php?rootid=' . $pid . '&ged=' . $person->getTree()->getNameUrl() . '" >' . I18N::translate('Descendants') . '</a></li>'; $html .= '<li><a href="ancestry.php?rootid=' . $pid . '&ged=' . $person->getTree()->getNameUrl() . '">' . I18N::translate('Ancestors') . '</a></li>'; $html .= '<li><a href="compact.php?rootid=' . $pid . '&ged=' . $person->getTree()->getNameUrl() . '">' . I18N::translate('Compact tree') . '</a></li>'; $html .= '<li><a href="' . $tempURL . '">' . I18N::translate('Fan chart') . '</a></li>'; $html .= '<li><a href="hourglass.php?rootid=' . $pid . '&ged=' . $person->getTree()->getNameUrl() . '">' . I18N::translate('Hourglass chart') . '</a></li>'; if (Module::getModuleByName('tree')) { $html .= '<li><a href="module.php?mod=tree&mod_action=treeview&ged=' . $person->getTree()->getNameUrl() . '&rootid=' . $pid . '">' . I18N::translate('Interactive tree') . '</a></li>'; } $html .= '</ul>'; // spouse(s) and children foreach ($person->getSpouseFamilies() as $family) { $spouse = $family->getSpouse($person); if ($spouse) { $html .= '<a href="' . $spouse->getHtmlUrl() . '" class="name1">' . $spouse->getFullName() . '</a>'; $kids = $family->getChildren(); if ($kids) { $html .= '<ul class="children">'; foreach ($kids as $child) { $html .= '<li><a href="' . $child->getHtmlUrl() . '" class="name1">' . $child->getFullName() . '</a></li>'; } $html .= '</ul>'; } } } // siblings foreach ($person->getChildFamilies() as $family) { $children = $family->getChildren(); if ($children) { $html .= '<div class="name1">'; // With two children in a family, you have only one sibling. $html .= count($children) > 2 ? I18N::translate('Siblings') : I18N::translate('Sibling'); $html .= '</div>'; $html .= '<ul class="siblings">'; foreach ($children as $sibling) { if ($sibling !== $person) { $html .= '<li><a href="' . $sibling->getHtmlUrl() . '" class="name1"> ' . $sibling->getFullName() . '</a></li>'; } } $html .= '</ul>'; } } $html .= '</div></div>'; $html .= '</div>'; $imagemap .= ' alt="' . strip_tags($person->getFullName()) . '" title="' . strip_tags($person->getFullName()) . '">'; } $deg1 -= $angle; $deg2 -= $angle; $sosa--; } $rx -= $rw; $gen--; } $imagemap .= '</map>'; switch ($what) { case 'html': return $html . $imagemap . '<div id="fan_chart_img"><img src="' . WT_SCRIPT_NAME . '?rootid=' . $this->root->getXref() . '&fan_style=' . $this->fan_style . '&generations=' . $this->generations . '&fan_width=' . $this->fan_width . '&img=1" width="' . $fanw . '" height="' . $fanh . '" alt="' . strip_tags($this->getPageTitle()) . '" usemap="#fanmap"></div>'; case 'png': ImageStringUp($image, 1, $fanw - 10, $fanh / 3, WT_BASE_URL, $color); ob_start(); ImagePng($image); ImageDestroy($image); return ob_get_clean(); default: throw new \InvalidArgumentException(__METHOD__ . ' ' . $what); } }
/** * Generate both the HTML and PNG components of the fan chart * * The HTML and PNG components both require the same co-ordinate calculations, * so we generate them using the same code, but we send them in separate * HTTP requests. * * @param string $what "png" or "html" * * @return string */ public function generateFanChart($what) { $treeid = $this->sosaAncestors($this->generations); $fanw = 640 * $this->fan_width / 100; $fandeg = 90 * $this->fan_style; $html = ''; $treesize = count($treeid) + 1; // generations count $gen = log($treesize) / log(2) - 1; $sosa = $treesize - 1; // fan size if ($fandeg == 0) { $fandeg = 360; } $fandeg = min($fandeg, 360); $fandeg = max($fandeg, 90); $cx = $fanw / 2 - 1; // center x $cy = $cx; // center y $rx = $fanw - 1; $rw = $fanw / ($gen + 1); $fanh = $fanw; // fan height if ($fandeg == 180) { $fanh = round($fanh * ($gen + 1) / ($gen * 2)); } if ($fandeg == 270) { $fanh = round($fanh * 0.86); } $scale = $fanw / 640; // image init $image = imagecreate($fanw, $fanh); $white = imagecolorallocate($image, 0xff, 0xff, 0xff); imagefilledrectangle($image, 0, 0, $fanw, $fanh, $white); imagecolortransparent($image, $white); $color = imagecolorallocate($image, hexdec(substr(Theme::theme()->parameter('chart-font-color'), 0, 2)), hexdec(substr(Theme::theme()->parameter('chart-font-color'), 2, 2)), hexdec(substr(Theme::theme()->parameter('chart-font-color'), 4, 2))); $bgcolor = imagecolorallocate($image, hexdec(substr(Theme::theme()->parameter('chart-background-u'), 0, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-u'), 2, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-u'), 4, 2))); $bgcolorM = imagecolorallocate($image, hexdec(substr(Theme::theme()->parameter('chart-background-m'), 0, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-m'), 2, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-m'), 4, 2))); $bgcolorF = imagecolorallocate($image, hexdec(substr(Theme::theme()->parameter('chart-background-f'), 0, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-f'), 2, 2)), hexdec(substr(Theme::theme()->parameter('chart-background-f'), 4, 2))); // imagemap $imagemap = '<map id="fanmap" name="fanmap">'; // loop to create fan cells while ($gen >= 0) { // clean current generation area $deg2 = 360 + ($fandeg - 180) / 2; $deg1 = $deg2 - $fandeg; imagefilledarc($image, $cx, $cy, $rx, $rx, $deg1, $deg2, $bgcolor, IMG_ARC_PIE); $rx -= 3; // calculate new angle $p2 = pow(2, $gen); $angle = $fandeg / $p2; $deg2 = 360 + ($fandeg - 180) / 2; $deg1 = $deg2 - $angle; // special case for rootid cell if ($gen == 0) { $deg1 = 90; $deg2 = 360 + $deg1; } // draw each cell while ($sosa >= $p2) { $person = $treeid[$sosa]; if ($person) { $name = $person->getFullName(); $addname = $person->getAddName(); $text = I18N::reverseText($name); if ($addname) { $text .= "\n" . I18N::reverseText($addname); } $text .= "\n" . I18N::reverseText($person->getLifeSpan()); switch ($person->getSex()) { case 'M': $bg = $bgcolorM; break; case 'F': $bg = $bgcolorF; break; default: $bg = $bgcolor; break; } imagefilledarc($image, $cx, $cy, $rx, $rx, $deg1, $deg2, $bg, IMG_ARC_PIE); // split and center text by lines $wmax = (int) ($angle * 7 / Theme::theme()->parameter('chart-font-size') * $scale); $wmax = min($wmax, 35 * $scale); if ($gen == 0) { $wmax = min($wmax, 17 * $scale); } $text = $this->splitAlignText($text, $wmax); // text angle $tangle = 270 - ($deg1 + $angle / 2); if ($gen == 0) { $tangle = 0; } // calculate text position $deg = $deg1 + 0.44; if ($deg2 - $deg1 > 40) { $deg = $deg1 + ($deg2 - $deg1) / 11; } if ($deg2 - $deg1 > 80) { $deg = $deg1 + ($deg2 - $deg1) / 7; } if ($deg2 - $deg1 > 140) { $deg = $deg1 + ($deg2 - $deg1) / 4; } if ($gen == 0) { $deg = 180; } $rad = deg2rad($deg); $mr = ($rx - $rw / 4) / 2; if ($gen > 0 && $deg2 - $deg1 > 80) { $mr = $rx / 2; } $tx = $cx + $mr * cos($rad); $ty = $cy - $mr * -sin($rad); if ($sosa == 1) { $ty -= $mr / 2; } // print text imagettftext($image, Theme::theme()->parameter('chart-font-size'), $tangle, $tx, $ty, $color, Theme::theme()->parameter('chart-font-name'), $text); $imagemap .= '<area shape="poly" coords="'; // plot upper points $mr = $rx / 2; $deg = $deg1; while ($deg <= $deg2) { $rad = deg2rad($deg); $tx = round($cx + $mr * cos($rad)); $ty = round($cy - $mr * -sin($rad)); $imagemap .= "{$tx},{$ty},"; $deg += ($deg2 - $deg1) / 6; } // plot lower points $mr = ($rx - $rw) / 2; $deg = $deg2; while ($deg >= $deg1) { $rad = deg2rad($deg); $tx = round($cx + $mr * cos($rad)); $ty = round($cy - $mr * -sin($rad)); $imagemap .= "{$tx},{$ty},"; $deg -= ($deg2 - $deg1) / 6; } // join first point $mr = $rx / 2; $deg = $deg1; $rad = deg2rad($deg); $tx = round($cx + $mr * cos($rad)); $ty = round($cy - $mr * -sin($rad)); $imagemap .= "{$tx},{$ty}"; // add action url $pid = $person->getXref(); $imagemap .= '" href="#' . $pid . '"'; $html .= '<div id="' . $pid . '" class="fan_chart_menu">'; $html .= '<div class="person_box"><div class="details1">'; $html .= '<a href="' . $person->getHtmlUrl() . '" class="name1">' . $name; if ($addname) { $html .= $addname; } $html .= '</a>'; $html .= '<ul class="charts">'; foreach (Theme::theme()->individualBoxMenu($person) as $menu) { $html .= $menu->getMenuAsList(); } $html .= '</ul>'; $html .= '</div></div>'; $html .= '</div>'; $imagemap .= ' alt="' . strip_tags($person->getFullName()) . '" title="' . strip_tags($person->getFullName()) . '">'; } $deg1 -= $angle; $deg2 -= $angle; $sosa--; } $rx -= $rw; $gen--; } $imagemap .= '</map>'; switch ($what) { case 'html': return $html . $imagemap . '<div id="fan_chart_img"><img src="' . WT_SCRIPT_NAME . '?rootid=' . $this->root->getXref() . '&fan_style=' . $this->fan_style . '&generations=' . $this->generations . '&fan_width=' . $this->fan_width . '&img=1" width="' . $fanw . '" height="' . $fanh . '" alt="' . strip_tags($this->getPageTitle()) . '" usemap="#fanmap"></div>'; case 'png': imagestringup($image, 1, $fanw - 10, $fanh / 3, WT_BASE_URL, $color); ob_start(); imagepng($image); imagedestroy($image); return ob_get_clean(); default: throw new \InvalidArgumentException(__METHOD__ . ' ' . $what); } }