xref: /webtrees/app/Module/FanChartModule.php (revision d3e2fc58c3275e261d2b60e26a55aaa7cf64e610)
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    {
1230b93976aSGreg Roach        $ajax       = $request->getQueryParams()['ajax'] ?? '';
1240b93976aSGreg 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
1300b93976aSGreg Roach        $chart_style = (int) ($request->getQueryParams()['chart_style'] ?? self::DEFAULT_STYLE);
1310b93976aSGreg Roach        $fan_width   = (int) ($request->getQueryParams()['fan_width'] ?? self::DEFAULT_WIDTH);
1320b93976aSGreg 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
1400b93976aSGreg 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);
444*d3e2fc58SGreg 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