1168ff6f3Sric2016<?php 2168ff6f3Sric2016/** 3168ff6f3Sric2016 * webtrees: online genealogy 48fcd0d32SGreg Roach * Copyright (C) 2019 webtrees development team 5168ff6f3Sric2016 * This program is free software: you can redistribute it and/or modify 6168ff6f3Sric2016 * it under the terms of the GNU General Public License as published by 7168ff6f3Sric2016 * the Free Software Foundation, either version 3 of the License, or 8168ff6f3Sric2016 * (at your option) any later version. 9168ff6f3Sric2016 * This program is distributed in the hope that it will be useful, 10168ff6f3Sric2016 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11168ff6f3Sric2016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12168ff6f3Sric2016 * GNU General Public License for more details. 13168ff6f3Sric2016 * You should have received a copy of the GNU General Public License 14168ff6f3Sric2016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15168ff6f3Sric2016 */ 16e7f56f2aSGreg Roachdeclare(strict_types=1); 17e7f56f2aSGreg Roach 18168ff6f3Sric2016namespace Fisharebest\Webtrees\Module; 19168ff6f3Sric2016 20389266c0SGreg Roachuse Fisharebest\Webtrees\Auth; 21e5a6b4d4SGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface; 22168ff6f3Sric2016use Fisharebest\Webtrees\I18N; 23168ff6f3Sric2016use Fisharebest\Webtrees\Individual; 246664b4a3SGreg Roachuse Fisharebest\Webtrees\Menu; 25389266c0SGreg Roachuse Fisharebest\Webtrees\Services\ChartService; 26389266c0SGreg Roachuse Fisharebest\Webtrees\Tree; 27f397d0fdSGreg Roachuse Fisharebest\Webtrees\Webtrees; 286ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface; 296ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 30168ff6f3Sric2016 31168ff6f3Sric2016/** 32168ff6f3Sric2016 * Class FanChartModule 33168ff6f3Sric2016 */ 3437eb8894SGreg Roachclass FanChartModule extends AbstractModule implements ModuleChartInterface 35c1010edaSGreg Roach{ 3649a243cbSGreg Roach use ModuleChartTrait; 3749a243cbSGreg Roach 38389266c0SGreg Roach // Chart styles 39389266c0SGreg Roach private const STYLE_HALF_CIRCLE = 2; 40389266c0SGreg Roach private const STYLE_THREE_QUARTER_CIRCLE = 3; 41389266c0SGreg Roach private const STYLE_FULL_CIRCLE = 4; 42389266c0SGreg Roach 43389266c0SGreg Roach // Limits 44389266c0SGreg Roach private const MINIMUM_GENERATIONS = 2; 45389266c0SGreg Roach private const MAXIMUM_GENERATIONS = 9; 46389266c0SGreg Roach private const MINIMUM_WIDTH = 50; 47389266c0SGreg Roach private const MAXIMUM_WIDTH = 500; 48389266c0SGreg Roach 49389266c0SGreg Roach // Defaults 50389266c0SGreg Roach private const DEFAULT_STYLE = self::STYLE_THREE_QUARTER_CIRCLE; 51389266c0SGreg Roach private const DEFAULT_GENERATIONS = 4; 52389266c0SGreg Roach private const DEFAULT_WIDTH = 100; 53389266c0SGreg Roach 54168ff6f3Sric2016 /** 550cfd6963SGreg Roach * How should this module be identified in the control panel, etc.? 56168ff6f3Sric2016 * 57168ff6f3Sric2016 * @return string 58168ff6f3Sric2016 */ 5949a243cbSGreg Roach public function title(): string 60c1010edaSGreg Roach { 61bbb76c12SGreg Roach /* I18N: Name of a module/chart */ 62bbb76c12SGreg Roach return I18N::translate('Fan chart'); 63168ff6f3Sric2016 } 64168ff6f3Sric2016 65168ff6f3Sric2016 /** 66168ff6f3Sric2016 * A sentence describing what this module does. 67168ff6f3Sric2016 * 68168ff6f3Sric2016 * @return string 69168ff6f3Sric2016 */ 7049a243cbSGreg Roach public function description(): string 71c1010edaSGreg Roach { 72bbb76c12SGreg Roach /* I18N: Description of the “Fan Chart” module */ 73bbb76c12SGreg Roach return I18N::translate('A fan chart of an individual’s ancestors.'); 74168ff6f3Sric2016 } 75168ff6f3Sric2016 76168ff6f3Sric2016 /** 77377a2979SGreg Roach * CSS class for the URL. 78377a2979SGreg Roach * 79377a2979SGreg Roach * @return string 80377a2979SGreg Roach */ 81377a2979SGreg Roach public function chartMenuClass(): string 82377a2979SGreg Roach { 83377a2979SGreg Roach return 'menu-chart-fanchart'; 84377a2979SGreg Roach } 85377a2979SGreg Roach 86377a2979SGreg Roach /** 874eb71cfaSGreg Roach * Return a menu item for this chart - for use in individual boxes. 884eb71cfaSGreg Roach * 8960bc3e3fSGreg Roach * @param Individual $individual 9060bc3e3fSGreg Roach * 914eb71cfaSGreg Roach * @return Menu|null 924eb71cfaSGreg Roach */ 93377a2979SGreg Roach public function chartBoxMenu(Individual $individual): ?Menu 94c1010edaSGreg Roach { 95e6562982SGreg Roach return $this->chartMenu($individual); 96e6562982SGreg Roach } 97e6562982SGreg Roach 98e6562982SGreg Roach /** 99e6562982SGreg Roach * The title for a specific instance of this chart. 100e6562982SGreg Roach * 101e6562982SGreg Roach * @param Individual $individual 102e6562982SGreg Roach * 103e6562982SGreg Roach * @return string 104e6562982SGreg Roach */ 105e6562982SGreg Roach public function chartTitle(Individual $individual): string 106e6562982SGreg Roach { 107389266c0SGreg Roach /* I18N: http://en.wikipedia.org/wiki/Family_tree#Fan_chart - %s is an individual’s name */ 10839ca88baSGreg Roach return I18N::translate('Fan chart of %s', $individual->fullName()); 109e6562982SGreg Roach } 110e6562982SGreg Roach 111e6562982SGreg Roach /** 112389266c0SGreg Roach * A form to request the chart parameters. 113389266c0SGreg Roach * 1146ccdf4f0SGreg Roach * @param ServerRequestInterface $request 115389266c0SGreg Roach * @param Tree $tree 116e5a6b4d4SGreg Roach * @param UserInterface $user 117389266c0SGreg Roach * @param ChartService $chart_service 118389266c0SGreg Roach * 1196ccdf4f0SGreg Roach * @return ResponseInterface 120389266c0SGreg Roach */ 1216ccdf4f0SGreg Roach public function getChartAction(ServerRequestInterface $request, Tree $tree, UserInterface $user, ChartService $chart_service): ResponseInterface 122389266c0SGreg Roach { 123*0b93976aSGreg Roach $ajax = $request->getQueryParams()['ajax'] ?? ''; 124*0b93976aSGreg Roach $xref = $request->getQueryParams()['xref'] ?? ''; 125389266c0SGreg Roach $individual = Individual::getInstance($xref, $tree); 126389266c0SGreg Roach 127389266c0SGreg Roach Auth::checkIndividualAccess($individual); 1289867b2f0SGreg Roach Auth::checkComponentAccess($this, 'chart', $tree, $user); 129389266c0SGreg Roach 130*0b93976aSGreg Roach $chart_style = (int) ($request->getQueryParams()['chart_style'] ?? self::DEFAULT_STYLE); 131*0b93976aSGreg Roach $fan_width = (int) ($request->getQueryParams()['fan_width'] ?? self::DEFAULT_WIDTH); 132*0b93976aSGreg Roach $generations = (int) ($request->getQueryParams()['generations'] ?? self::DEFAULT_GENERATIONS); 133389266c0SGreg Roach 134389266c0SGreg Roach $fan_width = min($fan_width, self::MAXIMUM_WIDTH); 135389266c0SGreg Roach $fan_width = max($fan_width, self::MINIMUM_WIDTH); 136389266c0SGreg Roach 137389266c0SGreg Roach $generations = min($generations, self::MAXIMUM_GENERATIONS); 138389266c0SGreg Roach $generations = max($generations, self::MINIMUM_GENERATIONS); 139389266c0SGreg Roach 140*0b93976aSGreg Roach if ($ajax === '1') { 141389266c0SGreg Roach return $this->chart($individual, $chart_style, $fan_width, $generations, $chart_service); 142389266c0SGreg Roach } 143389266c0SGreg Roach 144389266c0SGreg Roach $ajax_url = $this->chartUrl($individual, [ 1459b5537c3SGreg Roach 'ajax' => true, 146389266c0SGreg Roach 'chart_style' => $chart_style, 147389266c0SGreg Roach 'fan_width' => $fan_width, 148389266c0SGreg Roach 'generations' => $generations, 149389266c0SGreg Roach ]); 150389266c0SGreg Roach 1519b5537c3SGreg Roach return $this->viewResponse('modules/fanchart/page', [ 152389266c0SGreg Roach 'ajax_url' => $ajax_url, 153389266c0SGreg Roach 'chart_style' => $chart_style, 154389266c0SGreg Roach 'chart_styles' => $this->chartStyles(), 155389266c0SGreg Roach 'fan_width' => $fan_width, 156389266c0SGreg Roach 'generations' => $generations, 157389266c0SGreg Roach 'individual' => $individual, 158389266c0SGreg Roach 'maximum_generations' => self::MAXIMUM_GENERATIONS, 159389266c0SGreg Roach 'minimum_generations' => self::MINIMUM_GENERATIONS, 160389266c0SGreg Roach 'maximum_width' => self::MAXIMUM_WIDTH, 161389266c0SGreg Roach 'minimum_width' => self::MINIMUM_WIDTH, 16226684e68SGreg Roach 'module_name' => $this->name(), 163389266c0SGreg Roach 'title' => $this->chartTitle($individual), 164389266c0SGreg Roach ]); 165389266c0SGreg Roach } 166389266c0SGreg Roach 167389266c0SGreg Roach /** 168389266c0SGreg Roach * Generate both the HTML and PNG components of the fan chart 169e6562982SGreg Roach * 170e6562982SGreg Roach * @param Individual $individual 171389266c0SGreg Roach * @param int $chart_style 172389266c0SGreg Roach * @param int $fan_width 173389266c0SGreg Roach * @param int $generations 174389266c0SGreg Roach * @param ChartService $chart_service 175e6562982SGreg Roach * 1766ccdf4f0SGreg Roach * @return ResponseInterface 177e6562982SGreg Roach */ 1786ccdf4f0SGreg Roach protected function chart(Individual $individual, int $chart_style, int $fan_width, int $generations, ChartService $chart_service): ResponseInterface 179e6562982SGreg Roach { 180389266c0SGreg Roach $ancestors = $chart_service->sosaStradonitzAncestors($individual, $generations); 181389266c0SGreg Roach 182389266c0SGreg Roach $gen = $generations - 1; 183e364afe4SGreg Roach $sosa = 2 ** $generations - 1; 184389266c0SGreg Roach 185389266c0SGreg Roach // fan size 186389266c0SGreg Roach $fanw = 640 * $fan_width / 100; 187389266c0SGreg Roach $cx = $fanw / 2 - 1; // center x 188389266c0SGreg Roach $cy = $cx; // center y 189389266c0SGreg Roach $rx = $fanw - 1; 190389266c0SGreg Roach $rw = $fanw / ($gen + 1); 191389266c0SGreg Roach $fanh = $fanw; // fan height 192389266c0SGreg Roach if ($chart_style === self::STYLE_HALF_CIRCLE) { 193389266c0SGreg Roach $fanh = $fanh * ($gen + 1) / ($gen * 2); 194389266c0SGreg Roach } 195389266c0SGreg Roach if ($chart_style === self::STYLE_THREE_QUARTER_CIRCLE) { 196e364afe4SGreg Roach $fanh *= 0.86; 197389266c0SGreg Roach } 198389266c0SGreg Roach $scale = $fanw / 640; 199389266c0SGreg Roach 200389266c0SGreg Roach // Create the image 201389266c0SGreg Roach $image = imagecreate((int) $fanw, (int) $fanh); 202389266c0SGreg Roach 203389266c0SGreg Roach // Create colors 204389266c0SGreg Roach $transparent = imagecolorallocate($image, 0, 0, 0); 205389266c0SGreg Roach imagecolortransparent($image, $transparent); 206389266c0SGreg Roach 207cab242e7SGreg Roach $theme = app(ModuleThemeInterface::class); 208389266c0SGreg Roach 209389266c0SGreg Roach $foreground = $this->imageColor($image, $theme->parameter('chart-font-color')); 210389266c0SGreg Roach 211389266c0SGreg Roach $backgrounds = [ 212389266c0SGreg Roach 'M' => $this->imageColor($image, $theme->parameter('chart-background-m')), 213389266c0SGreg Roach 'F' => $this->imageColor($image, $theme->parameter('chart-background-f')), 214389266c0SGreg Roach 'U' => $this->imageColor($image, $theme->parameter('chart-background-u')), 215389266c0SGreg Roach ]; 216389266c0SGreg Roach 217389266c0SGreg Roach imagefilledrectangle($image, 0, 0, (int) $fanw, (int) $fanh, $transparent); 218389266c0SGreg Roach 219389266c0SGreg Roach $fandeg = 90 * $chart_style; 220389266c0SGreg Roach 221389266c0SGreg Roach // Popup menus for each ancestor 222389266c0SGreg Roach $html = ''; 223389266c0SGreg Roach 224389266c0SGreg Roach // Areas for the imagemap 225389266c0SGreg Roach $areas = ''; 226389266c0SGreg Roach 227389266c0SGreg Roach // loop to create fan cells 228389266c0SGreg Roach while ($gen >= 0) { 229389266c0SGreg Roach // clean current generation area 230389266c0SGreg Roach $deg2 = 360 + ($fandeg - 180) / 2; 231389266c0SGreg Roach $deg1 = $deg2 - $fandeg; 232389266c0SGreg Roach imagefilledarc($image, (int) $cx, (int) $cy, (int) $rx, (int) $rx, (int) $deg1, (int) $deg2, $backgrounds['U'], IMG_ARC_PIE); 233389266c0SGreg Roach $rx -= 3; 234389266c0SGreg Roach 235389266c0SGreg Roach // calculate new angle 236389266c0SGreg Roach $p2 = 2 ** $gen; 237389266c0SGreg Roach $angle = $fandeg / $p2; 238389266c0SGreg Roach $deg2 = 360 + ($fandeg - 180) / 2; 239389266c0SGreg Roach $deg1 = $deg2 - $angle; 240389266c0SGreg Roach // special case for rootid cell 241389266c0SGreg Roach if ($gen == 0) { 242389266c0SGreg Roach $deg1 = 90; 243389266c0SGreg Roach $deg2 = 360 + $deg1; 244389266c0SGreg Roach } 245389266c0SGreg Roach 246389266c0SGreg Roach // draw each cell 247389266c0SGreg Roach while ($sosa >= $p2) { 248389266c0SGreg Roach if ($ancestors->has($sosa)) { 249389266c0SGreg Roach $person = $ancestors->get($sosa); 25039ca88baSGreg Roach $name = $person->fullName(); 25139ca88baSGreg Roach $addname = $person->alternateName(); 252389266c0SGreg Roach 253389266c0SGreg Roach $text = I18N::reverseText($name); 254389266c0SGreg Roach if ($addname) { 255389266c0SGreg Roach $text .= "\n" . I18N::reverseText($addname); 256389266c0SGreg Roach } 257389266c0SGreg Roach 258389266c0SGreg Roach $text .= "\n" . I18N::reverseText($person->getLifeSpan()); 259389266c0SGreg Roach 26039ca88baSGreg Roach $background = $backgrounds[$person->sex()]; 261389266c0SGreg Roach 262389266c0SGreg Roach imagefilledarc($image, (int) $cx, (int) $cy, (int) $rx, (int) $rx, (int) $deg1, (int) $deg2, $background, IMG_ARC_PIE); 263389266c0SGreg Roach 264389266c0SGreg Roach // split and center text by lines 265389266c0SGreg Roach $wmax = (int) ($angle * 7 / 7 * $scale); 266389266c0SGreg Roach $wmax = min($wmax, 35 * $scale); 267389266c0SGreg Roach if ($gen == 0) { 268389266c0SGreg Roach $wmax = min($wmax, 17 * $scale); 269389266c0SGreg Roach } 270389266c0SGreg Roach $text = $this->splitAlignText($text, (int) $wmax); 271389266c0SGreg Roach 272389266c0SGreg Roach // text angle 273389266c0SGreg Roach $tangle = 270 - ($deg1 + $angle / 2); 274389266c0SGreg Roach if ($gen == 0) { 275389266c0SGreg Roach $tangle = 0; 276389266c0SGreg Roach } 277389266c0SGreg Roach 278389266c0SGreg Roach // calculate text position 279389266c0SGreg Roach $deg = $deg1 + 0.44; 280389266c0SGreg Roach if ($deg2 - $deg1 > 40) { 281389266c0SGreg Roach $deg = $deg1 + ($deg2 - $deg1) / 11; 282389266c0SGreg Roach } 283389266c0SGreg Roach if ($deg2 - $deg1 > 80) { 284389266c0SGreg Roach $deg = $deg1 + ($deg2 - $deg1) / 7; 285389266c0SGreg Roach } 286389266c0SGreg Roach if ($deg2 - $deg1 > 140) { 287389266c0SGreg Roach $deg = $deg1 + ($deg2 - $deg1) / 4; 288389266c0SGreg Roach } 289389266c0SGreg Roach if ($gen == 0) { 290389266c0SGreg Roach $deg = 180; 291389266c0SGreg Roach } 292389266c0SGreg Roach $rad = deg2rad($deg); 293389266c0SGreg Roach $mr = ($rx - $rw / 4) / 2; 294389266c0SGreg Roach if ($gen > 0 && $deg2 - $deg1 > 80) { 295389266c0SGreg Roach $mr = $rx / 2; 296389266c0SGreg Roach } 297389266c0SGreg Roach $tx = $cx + $mr * cos($rad); 298389266c0SGreg Roach $ty = $cy + $mr * sin($rad); 299389266c0SGreg Roach if ($sosa == 1) { 300389266c0SGreg Roach $ty -= $mr / 2; 301389266c0SGreg Roach } 302389266c0SGreg Roach 303389266c0SGreg Roach // print text 304389266c0SGreg Roach imagettftext( 305389266c0SGreg Roach $image, 306389266c0SGreg Roach 7, 307389266c0SGreg Roach $tangle, 308389266c0SGreg Roach (int) $tx, 309389266c0SGreg Roach (int) $ty, 310389266c0SGreg Roach $foreground, 311f397d0fdSGreg Roach Webtrees::ROOT_DIR . 'resources/fonts/DejaVuSans.ttf', 312389266c0SGreg Roach $text 313389266c0SGreg Roach ); 314389266c0SGreg Roach 315389266c0SGreg Roach $areas .= '<area shape="poly" coords="'; 316389266c0SGreg Roach // plot upper points 317389266c0SGreg Roach $mr = $rx / 2; 318389266c0SGreg Roach $deg = $deg1; 319389266c0SGreg Roach while ($deg <= $deg2) { 320389266c0SGreg Roach $rad = deg2rad($deg); 321389266c0SGreg Roach $tx = round($cx + $mr * cos($rad)); 322389266c0SGreg Roach $ty = round($cy + $mr * sin($rad)); 323389266c0SGreg Roach $areas .= "$tx,$ty,"; 324389266c0SGreg Roach $deg += ($deg2 - $deg1) / 6; 325389266c0SGreg Roach } 326389266c0SGreg Roach // plot lower points 327389266c0SGreg Roach $mr = ($rx - $rw) / 2; 328389266c0SGreg Roach $deg = $deg2; 329389266c0SGreg Roach while ($deg >= $deg1) { 330389266c0SGreg Roach $rad = deg2rad($deg); 331389266c0SGreg Roach $tx = round($cx + $mr * cos($rad)); 332389266c0SGreg Roach $ty = round($cy + $mr * sin($rad)); 333389266c0SGreg Roach $areas .= "$tx,$ty,"; 334389266c0SGreg Roach $deg -= ($deg2 - $deg1) / 6; 335389266c0SGreg Roach } 336389266c0SGreg Roach // join first point 337389266c0SGreg Roach $mr = $rx / 2; 338389266c0SGreg Roach $deg = $deg1; 339389266c0SGreg Roach $rad = deg2rad($deg); 340389266c0SGreg Roach $tx = round($cx + $mr * cos($rad)); 341389266c0SGreg Roach $ty = round($cy + $mr * sin($rad)); 342389266c0SGreg Roach $areas .= "$tx,$ty"; 343389266c0SGreg Roach // add action url 344389266c0SGreg Roach $areas .= '" href="#' . $person->xref() . '"'; 345389266c0SGreg Roach $html .= '<div id="' . $person->xref() . '" class="fan_chart_menu">'; 346389266c0SGreg Roach $html .= '<div class="person_box"><div class="details1">'; 347b6c326d8SGreg Roach $html .= '<div class="charts">'; 348b6c326d8SGreg Roach $html .= '<a href="' . e($person->url()) . '" class="dropdown-item">' . $name . '</a>'; 349389266c0SGreg Roach foreach ($theme->individualBoxMenu($person) as $menu) { 350b6c326d8SGreg Roach $html .= '<a href="' . e($menu->getLink()) . '" class="dropdown-item p-1 ' . e($menu->getClass()) . '">' . $menu->getLabel() . '</a>'; 351389266c0SGreg Roach } 352b6c326d8SGreg Roach $html .= '</div>'; 353389266c0SGreg Roach $html .= '</div></div>'; 354389266c0SGreg Roach $html .= '</div>'; 35539ca88baSGreg Roach $areas .= ' alt="' . strip_tags($person->fullName()) . '" title="' . strip_tags($person->fullName()) . '">'; 356389266c0SGreg Roach } 357389266c0SGreg Roach $deg1 -= $angle; 358389266c0SGreg Roach $deg2 -= $angle; 359389266c0SGreg Roach $sosa--; 360389266c0SGreg Roach } 361389266c0SGreg Roach $rx -= $rw; 362389266c0SGreg Roach $gen--; 363389266c0SGreg Roach } 364389266c0SGreg Roach 365389266c0SGreg Roach ob_start(); 366389266c0SGreg Roach imagepng($image); 367389266c0SGreg Roach imagedestroy($image); 368389266c0SGreg Roach $png = ob_get_clean(); 369389266c0SGreg Roach 3706ccdf4f0SGreg Roach return response(view('modules/fanchart/chart', [ 371389266c0SGreg Roach 'fanh' => $fanh, 372389266c0SGreg Roach 'fanw' => $fanw, 373389266c0SGreg Roach 'html' => $html, 374389266c0SGreg Roach 'areas' => $areas, 375389266c0SGreg Roach 'png' => $png, 376389266c0SGreg Roach 'title' => $this->chartTitle($individual), 377389266c0SGreg Roach ])); 378389266c0SGreg Roach } 379389266c0SGreg Roach 380389266c0SGreg Roach /** 381389266c0SGreg Roach * split and center text by lines 382389266c0SGreg Roach * 383389266c0SGreg Roach * @param string $data input string 384389266c0SGreg Roach * @param int $maxlen max length of each line 385389266c0SGreg Roach * 386389266c0SGreg Roach * @return string $text output string 387389266c0SGreg Roach */ 388389266c0SGreg Roach protected function splitAlignText(string $data, int $maxlen): string 389389266c0SGreg Roach { 390389266c0SGreg Roach $RTLOrd = [ 391389266c0SGreg Roach 215, 392389266c0SGreg Roach 216, 393389266c0SGreg Roach 217, 394389266c0SGreg Roach 218, 395389266c0SGreg Roach 219, 396389266c0SGreg Roach ]; 397389266c0SGreg Roach 398389266c0SGreg Roach $lines = explode("\n", $data); 399389266c0SGreg Roach // more than 1 line : recursive calls 400389266c0SGreg Roach if (count($lines) > 1) { 401389266c0SGreg Roach $text = ''; 402389266c0SGreg Roach foreach ($lines as $line) { 403389266c0SGreg Roach $text .= $this->splitAlignText($line, $maxlen) . "\n"; 404389266c0SGreg Roach } 405389266c0SGreg Roach 406389266c0SGreg Roach return $text; 407389266c0SGreg Roach } 408389266c0SGreg Roach // process current line word by word 409389266c0SGreg Roach $split = explode(' ', $data); 410389266c0SGreg Roach $text = ''; 411389266c0SGreg Roach $line = ''; 412389266c0SGreg Roach 413389266c0SGreg Roach // do not split hebrew line 414389266c0SGreg Roach $found = false; 415389266c0SGreg Roach foreach ($RTLOrd as $ord) { 416389266c0SGreg Roach if (strpos($data, chr($ord)) !== false) { 417389266c0SGreg Roach $found = true; 418389266c0SGreg Roach } 419389266c0SGreg Roach } 420389266c0SGreg Roach if ($found) { 421389266c0SGreg Roach $line = $data; 422389266c0SGreg Roach } else { 423389266c0SGreg Roach foreach ($split as $word) { 424389266c0SGreg Roach $len = strlen($line); 425389266c0SGreg Roach $wlen = strlen($word); 426389266c0SGreg Roach if (($len + $wlen) < $maxlen) { 427389266c0SGreg Roach if (!empty($line)) { 428389266c0SGreg Roach $line .= ' '; 429389266c0SGreg Roach } 430e364afe4SGreg Roach $line .= $word; 431389266c0SGreg Roach } else { 432389266c0SGreg Roach $p = max(0, (int) (($maxlen - $len) / 2)); 433389266c0SGreg Roach if (!empty($line)) { 434389266c0SGreg Roach $line = str_repeat(' ', $p) . $line; // center alignment using spaces 435389266c0SGreg Roach $text .= $line . "\n"; 436389266c0SGreg Roach } 437389266c0SGreg Roach $line = $word; 438389266c0SGreg Roach } 439389266c0SGreg Roach } 440389266c0SGreg Roach } 441389266c0SGreg Roach // last line 442389266c0SGreg Roach if (!empty($line)) { 443389266c0SGreg Roach $len = strlen($line); 44422d65e5aSGreg Roach if (in_array(ord($line{0}), $RTLOrd, true)) { 445389266c0SGreg Roach $len /= 2; 446389266c0SGreg Roach } 447389266c0SGreg Roach $p = max(0, (int) (($maxlen - $len) / 2)); 448389266c0SGreg Roach $line = str_repeat(' ', $p) . $line; // center alignment using spaces 449389266c0SGreg Roach $text .= $line; 450389266c0SGreg Roach } 451389266c0SGreg Roach 452389266c0SGreg Roach return $text; 453389266c0SGreg Roach } 454389266c0SGreg Roach 455389266c0SGreg Roach /** 456389266c0SGreg Roach * Convert a CSS color into a GD color. 457389266c0SGreg Roach * 458389266c0SGreg Roach * @param resource $image 459389266c0SGreg Roach * @param string $css_color 460389266c0SGreg Roach * 461389266c0SGreg Roach * @return int 462389266c0SGreg Roach */ 463389266c0SGreg Roach protected function imageColor($image, string $css_color): int 464389266c0SGreg Roach { 465389266c0SGreg Roach return imagecolorallocate( 466389266c0SGreg Roach $image, 467389266c0SGreg Roach (int) hexdec(substr($css_color, 0, 2)), 468389266c0SGreg Roach (int) hexdec(substr($css_color, 2, 2)), 469389266c0SGreg Roach (int) hexdec(substr($css_color, 4, 2)) 470389266c0SGreg Roach ); 471389266c0SGreg Roach } 472389266c0SGreg Roach 473389266c0SGreg Roach /** 474389266c0SGreg Roach * This chart can display its output in a number of styles 475389266c0SGreg Roach * 476389266c0SGreg Roach * @return array 477389266c0SGreg Roach */ 478389266c0SGreg Roach protected function chartStyles(): array 479389266c0SGreg Roach { 480389266c0SGreg Roach return [ 481389266c0SGreg Roach /* I18N: layout option for the fan chart */ 482389266c0SGreg Roach self::STYLE_HALF_CIRCLE => I18N::translate('half circle'), 483389266c0SGreg Roach /* I18N: layout option for the fan chart */ 484389266c0SGreg Roach self::STYLE_THREE_QUARTER_CIRCLE => I18N::translate('three-quarter circle'), 485389266c0SGreg Roach /* I18N: layout option for the fan chart */ 486389266c0SGreg Roach self::STYLE_FULL_CIRCLE => I18N::translate('full circle'), 487389266c0SGreg Roach ]; 488e6562982SGreg Roach } 489168ff6f3Sric2016} 490