Skip to content

Commit 46b4048

Browse files
hany-zDenverCoder1
andauthored
feat: Added card_height option (DenverCoder1#673)
Co-authored-by: Jonah Lawrence <[email protected]>
1 parent efeb0c0 commit 46b4048

File tree

7 files changed

+100
-39
lines changed

7 files changed

+100
-39
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ If the `theme` parameter is specified, any color customizations specified will b
7373
| `exclude_days` | List of days of the week to exclude from streaks | Comma-separated list of day abbreviations (Sun, Mon, Tue, Wed, Thu, Fri, Sat) e.g. `Sun,Sat` |
7474
| `disable_animations` | Disable SVG animations (Default: `false`) | `true` or `false` |
7575
| `card_width` | Width of the card in pixels (Default: `495`) | Positive integer, minimum width is 100px per column |
76+
| `card_height` | Height of the card in pixels (Default: `195`) | Positive integer, minimum height is 170px |
7677
| `hide_total_contributions` | Hide the total contributions (Default: `false`) | `true` or `false` |
7778
| `hide_current_streak` | Hide the current streak (Default: `false`) | `true` or `false` |
7879
| `hide_longest_streak` | Hide the longest streak (Default: `false`) | `true` or `false` |

src/card.php

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,19 @@ function getCardWidth(array $params, int $numColumns = 3): int
322322
return max($minimumWidth, intval($params["card_width"] ?? $defaultWidth));
323323
}
324324

325+
/**
326+
* Get the card height from params taking into account minimum and default values
327+
*
328+
* @param array<string,string> $params Request parameters
329+
* @return int Card width
330+
*/
331+
function getCardHeight(array $params): int
332+
{
333+
$defaultHeight = 195;
334+
$minimumHeight = 170;
335+
return max($minimumHeight, intval($params["card_height"] ?? $defaultHeight));
336+
}
337+
325338
/**
326339
* Generate SVG output for a stats array
327340
*
@@ -364,7 +377,11 @@ function generateCard(array $stats, array $params = null): string
364377
$rectWidth = $cardWidth - 1;
365378
$columnWidth = $numColumns > 0 ? $cardWidth / $numColumns : 0;
366379

367-
// offsets for the bars between columns
380+
$cardHeight = getCardHeight($params);
381+
$rectHeight = $cardHeight - 1;
382+
$heightOffset = ($cardHeight - 195) / 2;
383+
384+
// X offsets for the bars between columns
368385
$barOffsets = [-999, -999];
369386
for ($i = 0; $i < $numColumns - 1; $i++) {
370387
$barOffsets[$i] = $columnWidth * ($i + 1);
@@ -384,6 +401,22 @@ function generateCard(array $stats, array $params = null): string
384401
$currentStreakOffset = $showCurrentStreak ? $columnOffsets[$nextColumnIndex++] : -999;
385402
$longestStreakOffset = $showLongestStreak ? $columnOffsets[$nextColumnIndex++] : -999;
386403

404+
// Y offsets for the bars
405+
$barHeightOffsets = [28 + $heightOffset / 2, 170 + $heightOffset];
406+
// Y offsets for the numbers and dates
407+
$longestStreakHeightOffset = $totalContributionsHeightOffset = [
408+
48 + $heightOffset,
409+
84 + $heightOffset,
410+
114 + $heightOffset,
411+
];
412+
$currentStreakHeightOffset = [
413+
48 + $heightOffset,
414+
108 + $heightOffset,
415+
145 + $heightOffset,
416+
71 + $heightOffset,
417+
19.5 + $heightOffset,
418+
];
419+
387420
// total contributions
388421
$totalContributions = $numFormatter->format($stats["totalContributions"]);
389422
$firstContribution = formatDate($stats["firstContribution"], $dateFormat, $localeCode);
@@ -440,7 +473,7 @@ function generateCard(array $stats, array $params = null): string
440473
}
441474

442475
return "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'
443-
style='isolation: isolate' viewBox='0 0 {$cardWidth} 195' width='{$cardWidth}px' height='195px' direction='{$direction}'>
476+
style='isolation: isolate' viewBox='0 0 {$cardWidth} {$cardHeight}' width='{$cardWidth}px' height='{$cardHeight}px' direction='{$direction}'>
444477
<style>
445478
@keyframes currstreak {
446479
0% { font-size: 3px; opacity: 0.2; }
@@ -454,94 +487,94 @@ function generateCard(array $stats, array $params = null): string
454487
</style>
455488
<defs>
456489
<clipPath id='outer_rectangle'>
457-
<rect width='{$cardWidth}' height='195' rx='{$borderRadius}'/>
490+
<rect width='{$cardWidth}' height='{$cardHeight}' rx='{$borderRadius}'/>
458491
</clipPath>
459492
<mask id='mask_out_ring_behind_fire'>
460-
<rect width='{$cardWidth}' height='195' fill='white'/>
493+
<rect width='{$cardWidth}' height='{$cardHeight}' fill='white'/>
461494
<ellipse id='mask-ellipse' cx='{$currentStreakOffset}' cy='32' rx='13' ry='18' fill='black'/>
462495
</mask>
463496
{$theme["backgroundGradient"]}
464497
</defs>
465498
<g clip-path='url(#outer_rectangle)'>
466499
<g style='isolation: isolate'>
467-
<rect stroke='{$theme["border"]}' fill='{$theme["background"]}' rx='{$borderRadius}' x='0.5' y='0.5' width='{$rectWidth}' height='194'/>
500+
<rect stroke='{$theme["border"]}' fill='{$theme["background"]}' rx='{$borderRadius}' x='0.5' y='0.5' width='{$rectWidth}' height='{$rectHeight}'/>
468501
</g>
469502
<g style='isolation: isolate'>
470-
<line x1='{$barOffsets[0]}' y1='28' x2='{$barOffsets[0]}' y2='170' vector-effect='non-scaling-stroke' stroke-width='1' stroke='{$theme["stroke"]}' stroke-linejoin='miter' stroke-linecap='square' stroke-miterlimit='3'/>
471-
<line x1='{$barOffsets[1]}' y1='28' x2='{$barOffsets[1]}' y2='170' vector-effect='non-scaling-stroke' stroke-width='1' stroke='{$theme["stroke"]}' stroke-linejoin='miter' stroke-linecap='square' stroke-miterlimit='3'/>
503+
<line x1='{$barOffsets[0]}' y1='{$barHeightOffsets[0]}' x2='{$barOffsets[0]}' y2='{$barHeightOffsets[1]}' vector-effect='non-scaling-stroke' stroke-width='1' stroke='{$theme["stroke"]}' stroke-linejoin='miter' stroke-linecap='square' stroke-miterlimit='3'/>
504+
<line x1='{$barOffsets[1]}' y1='$barHeightOffsets[0]' x2='{$barOffsets[1]}' y2='$barHeightOffsets[1]' vector-effect='non-scaling-stroke' stroke-width='1' stroke='{$theme["stroke"]}' stroke-linejoin='miter' stroke-linecap='square' stroke-miterlimit='3'/>
472505
</g>
473506
<g style='isolation: isolate'>
474507
<!-- Total Contributions big number -->
475-
<g transform='translate({$totalContributionsOffset},48)'>
508+
<g transform='translate({$totalContributionsOffset}, {$totalContributionsHeightOffset[0]})'>
476509
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='{$theme["sideNums"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.6s'>
477510
{$totalContributions}
478511
</text>
479512
</g>
480513
481514
<!-- Total Contributions label -->
482-
<g transform='translate({$totalContributionsOffset},84)'>
515+
<g transform='translate({$totalContributionsOffset}, {$totalContributionsHeightOffset[1]})'>
483516
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='{$theme["sideLabels"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.7s'>
484517
{$totalContributionsText}
485518
</text>
486519
</g>
487520
488521
<!-- Total Contributions range -->
489-
<g transform='translate({$totalContributionsOffset},114)'>
522+
<g transform='translate({$totalContributionsOffset}, {$totalContributionsHeightOffset[2]})'>
490523
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='{$theme["dates"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.8s'>
491524
{$totalContributionsRange}
492525
</text>
493526
</g>
494527
</g>
495528
<g style='isolation: isolate'>
496529
<!-- Current Streak big number -->
497-
<g transform='translate({$currentStreakOffset},48)'>
530+
<g transform='translate({$currentStreakOffset}, {$currentStreakHeightOffset[0]})'>
498531
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='{$theme["currStreakNum"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='animation: currstreak 0.6s linear forwards'>
499532
{$currentStreak}
500533
</text>
501534
</g>
502535
503536
<!-- Current Streak label -->
504-
<g transform='translate({$currentStreakOffset},108)'>
537+
<g transform='translate({$currentStreakOffset}, {$currentStreakHeightOffset[1]})'>
505538
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='{$theme["currStreakLabel"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='700' font-size='14px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.9s'>
506539
{$currentStreakText}
507540
</text>
508541
</g>
509542
510543
<!-- Current Streak range -->
511-
<g transform='translate({$currentStreakOffset},145)'>
544+
<g transform='translate({$currentStreakOffset}, {$currentStreakHeightOffset[2]})'>
512545
<text x='0' y='21' stroke-width='0' text-anchor='middle' fill='{$theme["dates"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.9s'>
513546
{$currentStreakRange}
514547
</text>
515548
</g>
516549
517550
<!-- Ring around number -->
518551
<g mask='url(#mask_out_ring_behind_fire)'>
519-
<circle cx='{$currentStreakOffset}' cy='71' r='40' fill='none' stroke='{$theme["ring"]}' stroke-width='5' style='opacity: 0; animation: fadein 0.5s linear forwards 0.4s'></circle>
552+
<circle cx='{$currentStreakOffset}' cy='{$currentStreakHeightOffset[3]}' r='40' fill='none' stroke='{$theme["ring"]}' stroke-width='5' style='opacity: 0; animation: fadein 0.5s linear forwards 0.4s'></circle>
520553
</g>
521554
<!-- Fire icon -->
522-
<g transform='translate({$currentStreakOffset}, 19.5)' stroke-opacity='0' style='opacity: 0; animation: fadein 0.5s linear forwards 0.6s'>
555+
<g transform='translate({$currentStreakOffset}, {$currentStreakHeightOffset[4]})' stroke-opacity='0' style='opacity: 0; animation: fadein 0.5s linear forwards 0.6s'>
523556
<path d='M -12 -0.5 L 15 -0.5 L 15 23.5 L -12 23.5 L -12 -0.5 Z' fill='none'/>
524557
<path d='M 1.5 0.67 C 1.5 0.67 2.24 3.32 2.24 5.47 C 2.24 7.53 0.89 9.2 -1.17 9.2 C -3.23 9.2 -4.79 7.53 -4.79 5.47 L -4.76 5.11 C -6.78 7.51 -8 10.62 -8 13.99 C -8 18.41 -4.42 22 0 22 C 4.42 22 8 18.41 8 13.99 C 8 8.6 5.41 3.79 1.5 0.67 Z M -0.29 19 C -2.07 19 -3.51 17.6 -3.51 15.86 C -3.51 14.24 -2.46 13.1 -0.7 12.74 C 1.07 12.38 2.9 11.53 3.92 10.16 C 4.31 11.45 4.51 12.81 4.51 14.2 C 4.51 16.85 2.36 19 -0.29 19 Z' fill='{$theme["fire"]}' stroke-opacity='0'/>
525558
</g>
526559
527560
</g>
528561
<g style='isolation: isolate'>
529562
<!-- Longest Streak big number -->
530-
<g transform='translate({$longestStreakOffset},48)'>
563+
<g transform='translate({$longestStreakOffset}, {$longestStreakHeightOffset[0]})'>
531564
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='{$theme["sideNums"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='700' font-size='28px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 1.2s'>
532565
{$longestStreak}
533566
</text>
534567
</g>
535568
536569
<!-- Longest Streak label -->
537-
<g transform='translate({$longestStreakOffset},84)'>
570+
<g transform='translate({$longestStreakOffset}, {$longestStreakHeightOffset[1]})'>
538571
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='{$theme["sideLabels"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 1.3s'>
539572
{$longestStreakText}
540573
</text>
541574
</g>
542575
543576
<!-- Longest Streak range -->
544-
<g transform='translate({$longestStreakOffset},114)'>
577+
<g transform='translate({$longestStreakOffset}, {$longestStreakHeightOffset[2]})'>
545578
<text x='0' y='32' stroke-width='0' text-anchor='middle' fill='{$theme["dates"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='400' font-size='12px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 1.4s'>
546579
{$longestStreakRange}
547580
</text>
@@ -575,25 +608,31 @@ function generateErrorCard(string $message, array $params = null): string
575608
$rectWidth = $cardWidth - 1;
576609
$centerOffset = $cardWidth / 2;
577610

578-
return "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' style='isolation: isolate' viewBox='0 0 {$cardWidth} 195' width='{$cardWidth}px' height='195px'>
611+
// read card_height parameter
612+
$cardHeight = getCardHeight($params);
613+
$rectHeight = $cardHeight - 1;
614+
$heightOffset = ($cardHeight - 195) / 2;
615+
$errorLabelOffset = $cardHeight / 2 + 10.5;
616+
617+
return "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' style='isolation: isolate' viewBox='0 0 {$cardWidth} {$cardHeight}' width='{$cardWidth}px' height='{$cardHeight}px'>
579618
<style>
580619
a {
581620
fill: {$theme["dates"]};
582621
}
583622
</style>
584623
<defs>
585624
<clipPath id='outer_rectangle'>
586-
<rect width='{$cardWidth}' height='195' rx='{$borderRadius}'/>
625+
<rect width='{$cardWidth}' height='{$cardHeight}' rx='{$borderRadius}'/>
587626
</clipPath>
588627
{$theme["backgroundGradient"]}
589628
</defs>
590629
<g clip-path='url(#outer_rectangle)'>
591630
<g style='isolation: isolate'>
592-
<rect stroke='{$theme["border"]}' fill='{$theme["background"]}' rx='{$borderRadius}' x='0.5' y='0.5' width='{$rectWidth}' height='194'/>
631+
<rect stroke='{$theme["border"]}' fill='{$theme["background"]}' rx='{$borderRadius}' x='0.5' y='0.5' width='{$rectWidth}' height='{$rectHeight}'/>
593632
</g>
594633
<g style='isolation: isolate'>
595634
<!-- Error lable -->
596-
<g transform='translate({$centerOffset},108)'>
635+
<g transform='translate({$centerOffset}, {$errorLabelOffset})'>
597636
<text x='0' y='50' dy='0.25em' stroke-width='0' text-anchor='middle' fill='{$theme["sideLabels"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='400' font-size='14px' font-style='normal'>
598637
{$message}
599638
</text>
@@ -607,7 +646,7 @@ function generateErrorCard(string $message, array $params = null): string
607646
</mask>
608647
</defs>
609648
<!-- Sad face -->
610-
<g transform='translate({$centerOffset}, 0)'>
649+
<g transform='translate({$centerOffset}, {$heightOffset})'>
611650
<path fill='{$theme["fire"]}' d='M0,35.8c-25.2,0-45.7,20.5-45.7,45.7s20.5,45.8,45.7,45.8s45.7-20.5,45.7-45.7S25.2,35.8,0,35.8z M0,122.3c-11.2,0-21.4-4.5-28.8-11.9c-2.9-2.9-5.4-6.3-7.4-10c-3-5.7-4.6-12.1-4.6-18.9c0-22.5,18.3-40.8,40.8-40.8 c10.7,0,20.4,4.1,27.7,10.9c3.8,3.5,6.9,7.7,9.1,12.4c2.6,5.3,4,11.3,4,17.6C40.8,104.1,22.5,122.3,0,122.3z'/>
612651
<path fill='{$theme["fire"]}' d='M4.8,93.8c5.4,1.1,10.3,4.2,13.7,8.6l3.9-3c-4.1-5.3-10-9-16.6-10.4c-10.6-2.2-21.7,1.9-28.3,10.4l3.9,3 C-13.1,95.3-3.9,91.9,4.8,93.8z'/>
613652
<circle fill='{$theme["fire"]}' cx='-15' cy='71' r='4.9'/>
@@ -704,7 +743,7 @@ function ($matches) {
704743
* @param int $cardWidth The width of the card
705744
* @return string The generated PNG data
706745
*/
707-
function convertSvgToPng(string $svg, int $cardWidth): string
746+
function convertSvgToPng(string $svg, int $cardWidth, int $cardHeight): string
708747
{
709748
// trim off all whitespaces to make it a valid SVG string
710749
$svg = trim($svg);
@@ -722,7 +761,7 @@ function convertSvgToPng(string $svg, int $cardWidth): string
722761
// `--export-filename -`: write output to stdout
723762
// `-w 495 -h 195`: set width and height of the output image
724763
// `--export-type png`: set the output format to PNG
725-
$cmd = "echo {$svg} | inkscape --pipe --export-filename - -w {$cardWidth} -h 195 --export-type png";
764+
$cmd = "echo {$svg} | inkscape --pipe --export-filename - -w {$cardWidth} -h {$cardHeight} --export-type png";
726765

727766
// convert svg to png
728767
$png = shell_exec($cmd); // skipcq: PHP-A1009
@@ -772,7 +811,8 @@ function generateOutput(string|array $output, array $params = null): array
772811
try {
773812
// extract width from SVG
774813
$cardWidth = (int) preg_replace("/.*width=[\"'](\d+)px[\"'].*/", "$1", $svg);
775-
$png = convertSvgToPng($svg, $cardWidth);
814+
$cardHeight = (int) preg_replace("/.*height=[\"'](\d+)px[\"'].*/", "$1", $svg);
815+
$png = convertSvgToPng($svg, $cardWidth, $cardHeight);
776816
return [
777817
"contentType" => "image/png",
778818
"body" => $png,

src/demo/index.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ function gtag() {
170170
<label for="card-width">Card Width</label>
171171
<input class="param" type="number" id="card-width" name="card_width" placeholder="495" value="495" step="1" min="300" />
172172

173+
<label for="card-width">Card Height</label>
174+
<input class="param" type="number" id="card-width" name="card_height" placeholder="195" value="195" step="1" min="170" />
175+
173176
<label for="type">Output Type</label>
174177
<select class="param" id="type" name="type">
175178
<option value="svg">SVG</option>
@@ -276,4 +279,4 @@ function gtag() {
276279
</a>
277280
</body>
278281

279-
</html>
282+
</html>

src/demo/js/script.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const preview = {
1515
type: "svg",
1616
exclude_days: "",
1717
card_width: "495",
18+
card_height: "195",
1819
hide_total_contributions: "false",
1920
hide_current_streak: "false",
2021
hide_longest_streak: "false",

0 commit comments

Comments
 (0)