Skip to content

Commit d40fb6c

Browse files
authored
Merge pull request #140 from Rocksheep/main
2 parents 2871375 + 1db68e3 commit d40fb6c

File tree

8 files changed

+140
-96
lines changed

8 files changed

+140
-96
lines changed

CONTRIBUTING.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Make sure your request is meaningful and you have tested the app locally before
1111

1212
* [PHP 7.4+](https://www.apachefriends.org/index.html)
1313
* [Composer](https://getcomposer.org)
14+
* [Imagick](https://www.php.net/imagick)
1415

1516
#### Linux
1617

@@ -51,6 +52,13 @@ putenv("TOKEN=ghp_example123");
5152
putenv("USERNAME=DenverCoder1");
5253
```
5354

55+
### Install dependencies
56+
Run the following command to install all the required dependencies to work on this project.
57+
58+
```bash
59+
composer install
60+
```
61+
5462
### Running the app locally
5563

5664
```bash
@@ -63,12 +71,6 @@ Open http://localhost:8000/demo/ to run the demo site
6371

6472
### Running the tests
6573

66-
Before you can run tests, PHPUnit must be installed. You can install it using Composer by running the following command.
67-
68-
```bash
69-
composer install
70-
```
71-
7274
Run the following command to run the PHPUnit test script which will verify that the tested functionality is still working.
7375

7476
```bash

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ If the `theme` parameter is specified, any color customizations specified will b
7474
| `sideLabels` | Total and longest streak labels | **hex code** without `#` or **css color** |
7575
| `dates` | Date range text color | **hex code** without `#` or **css color** |
7676
| `date_format` | Date format (Default: `M j[, Y]`) | See note below on [Date Formats](#date-formats) |
77-
| `type` | Output format (Default: `svg`) | Current options: `svg` or `json` |
77+
| `type` | Output format (Default: `svg`) | Current options: `svg`, `png` or `json` |
7878

7979
### Date Formats
8080

@@ -188,6 +188,7 @@ Make sure your request is meaningful and you have tested the app locally before
188188

189189
- [PHP 7.4+](https://www.apachefriends.org/index.html)
190190
- [Composer](https://getcomposer.org)
191+
- [Imagick](https://www.php.net/imagick)
191192

192193
#### Linux
193194

@@ -212,6 +213,13 @@ git clone https://github.com/DenverCoder1/github-readme-streak-stats.git
212213
cd github-readme-streak-stats
213214
```
214215

216+
### Install dependencies
217+
Run the following command to install all the required dependencies to work on this project.
218+
219+
```bash
220+
composer install
221+
```
222+
215223
### Authorization
216224

217225
To get the GitHub API to run locally you will need to provide a token.
@@ -238,12 +246,6 @@ Open <http://localhost:8000/demo/> to run the demo site.
238246

239247
### Running the tests
240248

241-
Before you can run tests, PHPUnit must be installed. You can install it using Composer by running the following command.
242-
243-
```bash
244-
composer install
245-
```
246-
247249
Run the following command to run the PHPUnit test script which will verify that the tested functionality is still working.
248250

249251
```bash

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
]
1919
},
2020
"require": {
21-
"php": "^7.4|^8.0"
21+
"php": "^7.4|^8.0",
22+
"ext-imagick": "*"
2223
},
2324
"require-dev": {
2425
"phpunit/phpunit": "^9"
@@ -27,4 +28,4 @@
2728
"start": "php -S localhost:8000 -t src",
2829
"test": "./vendor/bin/phpunit --testdox tests"
2930
}
30-
}
31+
}

src/card.php

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ function generateCard(array $stats, array $params = null): string
9090
$theme = getRequestedTheme($params ?? $_REQUEST);
9191

9292
// get date format
93-
$dateFormat = isset(($params ?? $_REQUEST)["date_format"])
94-
? ($params ?? $_REQUEST)["date_format"]
93+
$dateFormat = isset(($params ?? $_REQUEST)["date_format"])
94+
? ($params ?? $_REQUEST)["date_format"]
9595
: "M j[, Y]";
9696

9797
// total contributions
@@ -117,8 +117,7 @@ function generateCard(array $stats, array $params = null): string
117117
$longestStreakRange .= " - " . $longestStreakEnd;
118118
}
119119

120-
return "
121-
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' style='isolation:isolate' viewBox='0 0 495 195' width='495px' height='195px'>
120+
return "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' style='isolation:isolate' viewBox='0 0 495 195' width='495px' height='195px'>
122121
<style>
123122
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700);
124123
@keyframes currstreak {
@@ -139,7 +138,7 @@ function generateCard(array $stats, array $params = null): string
139138
<g clip-path='url(#_clipPath_OZGVUqgkTHHpPTYeqOmK3uLgktRVSwWw)'>
140139
<g style='isolation:isolate'>
141140
<path d='M 4.5 0 L 490.5 0 C 492.984 0 495 2.016 495 4.5 L 495 190.5 C 495 192.984 492.984 195 490.5 195 L 4.5 195 C 2.016 195 0 192.984 0 190.5 L 0 4.5 C 0 2.016 2.016 0 4.5 0 Z'
142-
style='stroke: {$theme["border"]}; fill: {$theme["background"]};stroke-miterlimit:10;rx: 4.5;'/>
141+
style='stroke: {$theme["border"]}; fill: {$theme["background"]};stroke-miterlimit:10;rx: 4.5;'/>
143142
</g>
144143
<g style='isolation:isolate'>
145144
<line x1='330' y1='28' x2='330' y2='170' vector-effect='non-scaling-stroke' stroke-width='1' stroke='{$theme["stroke"]}' stroke-linejoin='miter' stroke-linecap='square' stroke-miterlimit='3'/>
@@ -149,23 +148,23 @@ function generateCard(array $stats, array $params = null): string
149148
<!-- Total Contributions Big Number -->
150149
<g transform='translate(1,48)'>
151150
<rect width='163' height='50' stroke='none' fill='none'></rect>
152-
<text x='81.5' y='25' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:700;font-size:28px;font-style:normal;fill:{$theme["sideNums"]};stroke:none; opacity: 0; animation: fadein 0.5s linear forwards 0.6s;'>
151+
<text x='81.5' y='32' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:700;font-size:28px;font-style:normal;fill:{$theme["sideNums"]};stroke:none; opacity: 0; animation: fadein 0.5s linear forwards 0.6s;'>
153152
{$totalContributions}
154153
</text>
155154
</g>
156155
157156
<!-- Total Contributions Label -->
158157
<g transform='translate(1,84)'>
159158
<rect width='163' height='50' stroke='none' fill='none'></rect>
160-
<text x='81.5' y='25' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:400;font-size:14px;font-style:normal;fill:{$theme["sideLabels"]};stroke:none; opacity: 0; animation: fadein 0.5s linear forwards 0.7s;'>
159+
<text x='81.5' y='32' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:400;font-size:14px;font-style:normal;fill:{$theme["sideLabels"]};stroke:none; opacity: 0; animation: fadein 0.5s linear forwards 0.7s;'>
161160
Total Contributions
162161
</text>
163162
</g>
164163
165164
<!-- total contributions range -->
166165
<g transform='translate(1,114)'>
167166
<rect width='163' height='50' stroke='none' fill='none'></rect>
168-
<text x='81.5' y='25' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:400;font-size:12px;font-style:normal;fill:{$theme["dates"]};stroke:none; opacity: 0; animation: fadein 0.5s linear forwards 0.8s;'>
167+
<text x='81.5' y='32' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:400;font-size:12px;font-style:normal;fill:{$theme["dates"]};stroke:none; opacity: 0; animation: fadein 0.5s linear forwards 0.8s;'>
169168
{$totalContributionsRange}
170169
</text>
171170
</g>
@@ -174,70 +173,65 @@ function generateCard(array $stats, array $params = null): string
174173
<!-- Current Streak Big Number -->
175174
<g transform='translate(166,48)'>
176175
<rect width='163' height='50' stroke='none' fill='none'></rect>
177-
<text x='81.5' y='25' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:700;font-size:28px;font-style:normal;fill:{$theme["currStreakNum"]};stroke:none;animation: currstreak 0.6s linear forwards;'>
176+
<text x='81.5' y='32' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:700;font-size:28px;font-style:normal;fill:{$theme["currStreakNum"]};stroke:none;animation: currstreak 0.6s linear forwards;'>
178177
{$currentStreak}
179178
</text>
180179
</g>
181180
182181
<!-- Current Streak Label -->
183182
<g transform='translate(166,108)'>
184183
<rect width='163' height='50' stroke='none' fill='none'></rect>
185-
<text x='81.5' y='25' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:700;font-size:14px;font-style:normal;fill:{$theme["currStreakLabel"]};stroke:none;opacity: 0; animation: fadein 0.5s linear forwards 0.9s;'>
184+
<text x='81.5' y='32' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:700;font-size:14px;font-style:normal;fill:{$theme["currStreakLabel"]};stroke:none;opacity: 0; animation: fadein 0.5s linear forwards 0.9s;'>
186185
Current Streak
187186
</text>
188187
</g>
189188
190189
<!-- Current Streak Range -->
191190
<g transform='translate(166,145)'>
192191
<rect width='163' height='26' stroke='none' fill='none'></rect>
193-
<text x='81.5' y='13' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:400;font-size:12px;font-style:normal;fill:{$theme["dates"]};stroke:none;opacity: 0; animation: fadein 0.5s linear forwards 0.9s;'>
192+
<text x='81.5' y='21' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:400;font-size:12px;font-style:normal;fill:{$theme["dates"]};stroke:none;opacity: 0; animation: fadein 0.5s linear forwards 0.9s;'>
194193
{$currentStreakRange}
195194
</text>
196195
</g>
197196
198-
<!-- mask for background behind fire -->
199-
<defs>
200-
<mask id='cut-off-area'>
201-
<rect x='0' y='0' width='500' height='500' fill='white' />
202-
<ellipse cx='247.5' cy='31' rx='13' ry='18'/>
203-
</mask>
204-
</defs>
205197
<!-- ring around number -->
206-
<circle cx='247.5' cy='71' r='40' mask='url(#cut-off-area)' style='fill:none;stroke:{$theme["ring"]};stroke-width:5;opacity: 0; animation: fadein 0.5s linear forwards 0.4s;'></circle>
198+
<circle cx='247.5' cy='71' r='40' style='fill:none;stroke:{$theme["ring"]};stroke-width:5;opacity: 0; animation: fadein 0.5s linear forwards 0.4s;'></circle>
199+
<ellipse cx='247.5' cy='32' rx='13' ry='18' fill='{$theme["background"]}' />
207200
<!-- fire icon -->
208201
<g style='opacity: 0; animation: fadein 0.5s linear forwards 0.6s;'>
209202
<path d=' M 235.5 19.5 L 259.5 19.5 L 259.5 43.5 L 235.5 43.5 L 235.5 19.5 Z ' fill='none'/>
210203
<path d=' M 249 20.17 C 249 20.17 249.74 22.82 249.74 24.97 C 249.74 27.03 248.39 28.7 246.33 28.7 C 244.26 28.7 242.7 27.03 242.7 24.97 L 242.73 24.61 C 240.71 27.01 239.5 30.12 239.5 33.5 C 239.5 37.92 243.08 41.5 247.5 41.5 C 251.92 41.5 255.5 37.92 255.5 33.5 C 255.5 28.11 252.91 23.3 249 20.17 Z M 247.21 38.5 C 245.43 38.5 243.99 37.1 243.99 35.36 C 243.99 33.74 245.04 32.6 246.8 32.24 C 248.57 31.88 250.4 31.03 251.42 29.66 C 251.81 30.95 252.01 32.31 252.01 33.7 C 252.01 36.35 249.86 38.5 247.21 38.5 Z ' fill='{$theme["fire"]}'/>
211204
</g>
205+
212206
</g>
213207
<g style='isolation:isolate'>
214208
<!-- Longest Streak Big Number -->
215209
<g transform='translate(331,48)'>
216210
<rect width='163' height='50' stroke='none' fill='none'></rect>
217-
<text x='81.5' y='25' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:700;font-size:28px;font-style:normal;fill:{$theme["sideNums"]};stroke:none; opacity: 0; animation: fadein 0.5s linear forwards 1.2s;'>
211+
<text x='81.5' y='32' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:700;font-size:28px;font-style:normal;fill:{$theme["sideNums"]};stroke:none; opacity: 0; animation: fadein 0.5s linear forwards 1.2s;'>
218212
{$longestStreak}
219213
</text>
220214
</g>
221215
222216
<!-- Longest Streak Label -->
223217
<g transform='translate(331,84)'>
224218
<rect width='163' height='50' stroke='none' fill='none'></rect>
225-
<text x='81.5' y='25' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:400;font-size:14px;font-style:normal;fill:{$theme["sideLabels"]};stroke:none;opacity: 0; animation: fadein 0.5s linear forwards 1.3s;'>
219+
<text x='81.5' y='32' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:400;font-size:14px;font-style:normal;fill:{$theme["sideLabels"]};stroke:none;opacity: 0; animation: fadein 0.5s linear forwards 1.3s;'>
226220
Longest Streak
227221
</text>
228222
</g>
229223
230224
<!-- Longest Streak Range -->
231225
<g transform='translate(331,114)'>
232226
<rect width='163' height='50' stroke='none' fill='none'></rect>
233-
<text x='81.5' y='25' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:400;font-size:12px;font-style:normal;fill:{$theme["dates"]};stroke:none;opacity: 0; animation: fadein 0.5s linear forwards 1.4s;'>
227+
<text x='81.5' y='32' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:400;font-size:12px;font-style:normal;fill:{$theme["dates"]};stroke:none;opacity: 0; animation: fadein 0.5s linear forwards 1.4s;'>
234228
{$longestStreakRange}
235229
</text>
236230
</g>
237231
</g>
238232
</g>
239233
</svg>
240-
";
234+
";
241235
}
242236

243237
/**
@@ -253,8 +247,7 @@ function generateErrorCard(string $message, array $params = null): string
253247
// get requested theme, use $_REQUEST if no params array specified
254248
$theme = getRequestedTheme($params ?? $_REQUEST);
255249

256-
return "
257-
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' style='isolation:isolate' viewBox='0 0 495 195' width='495px' height='195px'>
250+
return "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' style='isolation:isolate' viewBox='0 0 495 195' width='495px' height='195px'>
258251
<style>
259252
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,700);
260253
</style>
@@ -272,7 +265,7 @@ function generateErrorCard(string $message, array $params = null): string
272265
<!-- Error Label -->
273266
<g transform='translate(166,108)'>
274267
<rect width='163' height='50' stroke='none' fill='none'></rect>
275-
<text x='81.5' y='50' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:&quot;Open Sans&quot;, Roboto, system-ui, sans-serif;font-weight:400;font-size:14px;font-style:normal;fill:{$theme["sideLabels"]};stroke:none;'>
268+
<text x='81.5' y='50' dy='0.25em' stroke-width='0' text-anchor='middle' style='font-family:Open Sans, Roboto, system-ui, sans-serif;font-weight:400;font-size:14px;font-style:normal;fill:{$theme["sideLabels"]};stroke:none;'>
276269
{$message}
277270
</text>
278271
</g>
@@ -294,5 +287,49 @@ function generateErrorCard(string $message, array $params = null): string
294287
</g>
295288
</g>
296289
</svg>
297-
";
290+
";
291+
}
292+
293+
/**
294+
* Displays a card as an SVG image
295+
*
296+
* @param string $svg The SVG for the card to display
297+
*/
298+
function echoAsSvg(string $svg): void {
299+
// set content type to SVG image
300+
header("Content-Type: image/svg+xml");
301+
302+
// echo SVG data for streak stats
303+
echo $svg;
304+
}
305+
306+
/**
307+
* Displays a card as a PNG image
308+
*
309+
* @param string $svg The SVG for the card to display
310+
*
311+
* @throws ImagickException
312+
*/
313+
function echoAsPng(string $svg): void {
314+
// remove style and animations
315+
$svg = preg_replace('/(<style>\X*<\/style>)/m', '', $svg);
316+
$svg = preg_replace('/(opacity: 0;)/m', 'opacity: 1;', $svg);
317+
$svg = preg_replace('/(animation: fadein.*?;)/m', 'opacity: 1;', $svg);
318+
$svg = preg_replace('/(animation: currentstreak.*?;)/m', 'font-size: 28px;', $svg);
319+
320+
// create canvas
321+
$imagick = new Imagick();
322+
$imagick->setBackgroundColor(new ImagickPixel('transparent'));
323+
324+
// add svg image
325+
$imagick->readImageBlob($svg);
326+
$imagick->setImageFormat('png');
327+
328+
// echo PNG data
329+
header('Content-Type: image/png');
330+
echo $imagick->getImageBlob();
331+
332+
// clean up memory
333+
$imagick->clear();
334+
$imagick->destroy();
298335
}

src/index.php

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,26 @@
88
if (file_exists("config.php")) {
99
require_once "config.php";
1010
}
11+
12+
$requestedType = $_REQUEST['type'] ?? 'svg';
13+
1114
// if environment variables are not loaded, display error
1215
if (!getenv("TOKEN") || !getenv("USERNAME")) {
1316
$message = file_exists("config.php")
1417
? "Missing token or username in config. Check Contributing.md for details."
1518
: "src/config.php was not found. Check Contributing.md for details.";
16-
die(generateErrorCard($message));
19+
20+
21+
$card = generateErrorCard($message);
22+
if ($requestedType === "png") {
23+
echoAsPng($card);
24+
}
25+
echoAsSvg($card);
26+
27+
exit;
1728
}
1829

30+
1931
// set cache to refresh once per day
2032
$timestamp = gmdate("D, d M Y 23:59:00") . " GMT";
2133
header("Expires: $timestamp");
@@ -35,10 +47,16 @@
3547
$contributions = getContributionDates($contributionGraphs);
3648
$stats = getContributionStats($contributions);
3749
} catch (InvalidArgumentException $error) {
38-
die(generateErrorCard($error->getMessage()));
50+
$card = generateErrorCard($error->getMessage());
51+
if ($requestedType === "png") {
52+
echoAsPng($card);
53+
}
54+
echoAsSvg($card);
55+
56+
exit;
3957
}
4058

41-
if (isset($_REQUEST["type"]) && $_REQUEST["type"] === "json") {
59+
if ($requestedType === "json") {
4260
// set content type to JSON
4361
header('Content-Type: application/json');
4462
// echo JSON data for streak stats
@@ -47,8 +65,8 @@
4765
exit;
4866
}
4967

50-
// set content type to SVG image
51-
header("Content-Type: image/svg+xml");
52-
53-
// echo SVG data for streak stats
54-
echo generateCard($stats);
68+
$card = generateCard($stats);
69+
if ($requestedType === "png") {
70+
echoAsPng($card);
71+
}
72+
echoAsSvg($card);

0 commit comments

Comments
 (0)