Merge pull request #18 from DenverCoder1/fetch-google-fonts
@ -1,2 +1,3 @@
|
||||
vendor/
|
||||
.vscode/
|
||||
.env
|
@ -0,0 +1,107 @@
|
||||
<?php declare (strict_types = 1);
|
||||
|
||||
/**
|
||||
* Controller for choosing model and rendering SVG outputs
|
||||
*/
|
||||
class RendererController
|
||||
{
|
||||
|
||||
/**
|
||||
* @var RendererModel $model
|
||||
*/
|
||||
private $model;
|
||||
|
||||
/**
|
||||
* @var RendererView $view
|
||||
*/
|
||||
private $view;
|
||||
|
||||
/**
|
||||
* @var array<string, string> $params
|
||||
*/
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* Construct RendererController
|
||||
*
|
||||
* @param array<string, string> $params request parameters
|
||||
*/
|
||||
public function __construct($params, $database = null)
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
// create new database connection if none was passed
|
||||
$database = $database ?? new DatabaseConnection();
|
||||
|
||||
// set up model and view
|
||||
try {
|
||||
// create renderer model
|
||||
$this->model = new RendererModel(__DIR__ . "/../templates/main.php", $params, $database);
|
||||
// create renderer view
|
||||
$this->view = new RendererView($this->model);
|
||||
} catch (Exception $error) {
|
||||
// create error rendering model
|
||||
$this->model = new ErrorModel(__DIR__ . "/../templates/error.php", $error->getMessage());
|
||||
// create error rendering view
|
||||
$this->view = new ErrorView($this->model);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the demo site
|
||||
*/
|
||||
private function redirectToDemo(): void
|
||||
{
|
||||
header('Location: demo/');
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content type for page output
|
||||
*/
|
||||
private function setContentType($type): void
|
||||
{
|
||||
header("Content-type: {$type}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache to refresh periodically
|
||||
* This ensures any updates will roll out to all profiles
|
||||
*/
|
||||
private function setCacheRefreshDaily(): void
|
||||
{
|
||||
// set cache to refresh once per day
|
||||
$timestamp = gmdate("D, d M Y 23:59:00") . " GMT";
|
||||
header("Expires: $timestamp");
|
||||
header("Last-Modified: $timestamp");
|
||||
header("Pragma: no-cache");
|
||||
header("Cache-Control: no-cache, must-revalidate");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set output headers
|
||||
*/
|
||||
public function setHeaders(): void
|
||||
{
|
||||
// redirect to demo site if no text is given
|
||||
if (!isset($this->params["lines"])) {
|
||||
$this->redirectToDemo();
|
||||
}
|
||||
|
||||
// set the content type header
|
||||
$this->setContentType("image/svg+xml");
|
||||
|
||||
// set cache headers
|
||||
$this->setCacheRefreshDaily();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rendered SVG
|
||||
*
|
||||
* @return string The SVG to output
|
||||
*/
|
||||
public function render(): string
|
||||
{
|
||||
return $this->view->render();
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
.loader,
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
display: none;
|
||||
border-radius: 50%;
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
-webkit-animation: load7 1.8s infinite ease-in-out;
|
||||
animation: load7 1.8s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.loader {
|
||||
color: var(--blue-light);
|
||||
font-size: 10px;
|
||||
margin: 0 0 38px 15px;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
-webkit-transform: translateY(-4px) translateZ(0) scale(0.5);
|
||||
-ms-transform: translateY(-4px) translateZ(0) scale(0.5);
|
||||
transform: translateY(-4px) translateZ(0) scale(0.5);
|
||||
-webkit-animation-delay: -0.16s;
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.loader:before {
|
||||
left: -3.5em;
|
||||
-webkit-animation-delay: -0.32s;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.loader:after {
|
||||
left: 3.5em;
|
||||
}
|
||||
|
||||
@-webkit-keyframes load7 {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
box-shadow: 0 2.5em 0 -1.3em;
|
||||
}
|
||||
|
||||
40% {
|
||||
box-shadow: 0 2.5em 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load7 {
|
||||
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
box-shadow: 0 2.5em 0 -1.3em;
|
||||
}
|
||||
|
||||
40% {
|
||||
box-shadow: 0 2.5em 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.loading + .loader,
|
||||
.loading + .loader:before,
|
||||
.loading + .loader:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: none;
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class for accessing the font database
|
||||
*/
|
||||
class DatabaseConnection
|
||||
{
|
||||
/**
|
||||
* PostgreSQL Database Connection
|
||||
* @var resource|false
|
||||
*/
|
||||
private $conn = false;
|
||||
|
||||
/**
|
||||
* Constructor for DatabaseConnection class
|
||||
* Creates a database connection
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// database is missing from env
|
||||
if (!isset($_ENV["DATABASE_URL"])) {
|
||||
return;
|
||||
}
|
||||
// connect to database
|
||||
$conn_string = preg_replace(
|
||||
"/^postgres:\/\/(.+?):(.+?)@(.+?):(\d+?)\/(.+)$/",
|
||||
"host=$3 port=$4 dbname=$5 user=$1 password=$2",
|
||||
$_ENV["DATABASE_URL"]
|
||||
);
|
||||
$this->conn = pg_connect($conn_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch CSS from PostgreSQL Database
|
||||
*
|
||||
* @param string $font Google Font to fetch
|
||||
* @return array<string> array containing the date and the CSS for displaying the font
|
||||
*/
|
||||
public function fetchFontCSS($font)
|
||||
{
|
||||
// check connection
|
||||
if ($this->conn) {
|
||||
// fetch font from database
|
||||
$result = pg_query_params($this->conn, 'SELECT fetch_date, css FROM fonts WHERE family = $1', array($font));
|
||||
if (!$result) {
|
||||
return false;
|
||||
}
|
||||
$row = pg_fetch_row($result);
|
||||
if ($row) {
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert font CSS into database
|
||||
*
|
||||
* @param string $font Font Family
|
||||
* @param string $css CSS with Base64 encoding
|
||||
* @return bool True if successful, false if connection failed
|
||||
*/
|
||||
public function insertFontCSS($font, $css)
|
||||
{
|
||||
if ($this->conn) {
|
||||
$entry = array(
|
||||
"family" => $font,
|
||||
"css" => $css,
|
||||
"fetch_date" => date('Y-m-d'),
|
||||
);
|
||||
$result = pg_insert($this->conn, 'fonts', $entry);
|
||||
if (!$result) {
|
||||
throw new InvalidArgumentException("Insertion of Google Font to database failed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class for converting Google Fonts to base 64 for displaying through SVG image
|
||||
*/
|
||||
class GoogleFontConverter
|
||||
{
|
||||
/**
|
||||
* Fetch CSS from Google Fonts
|
||||
*
|
||||
* @param string $font Google Font to fetch
|
||||
* @return string|false The CSS for displaying the font
|
||||
*/
|
||||
public static function fetchFontCSS($font)
|
||||
{
|
||||
$url = "https://fonts.googleapis.com/css2?family=" . str_replace(" ", "+", $font);
|
||||
try {
|
||||
// get the CSS for the font
|
||||
$response = self::curl_get_contents($url);
|
||||
// find all font files and convert them to base64 Data URIs
|
||||
return self::encodeFonts($response);
|
||||
} catch (InvalidArgumentException $error) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode font urls in string as base 64
|
||||
*
|
||||
* @param string $css The CSS from Google Fonts
|
||||
* @return string CSS with urls replaced with base 64 Data URIs
|
||||
*/
|
||||
private static function encodeFonts($css)
|
||||
{
|
||||
$urlRegex = '/\((https\:\/\/fonts\.gstatic\.com.+?)\) format\(\'(.*?)\'\)/';
|
||||
preg_match_all($urlRegex, $css, $matches);
|
||||
$urls = array_combine($matches[1], $matches[2]);
|
||||
// go over all links and replace with data URI
|
||||
foreach ($urls as $url => $fontType) {
|
||||
$response = self::curl_get_contents($url);
|
||||
$dataURI = "data:font/{$fontType};base64," . base64_encode($response);
|
||||
$css = str_replace($url, $dataURI, $css);
|
||||
}
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contents of a URL
|
||||
*
|
||||
* @param string $url The URL to fetch
|
||||
* @return string Response from URL
|
||||
*/
|
||||
private static function curl_get_contents($url): string
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_VERBOSE, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($httpCode != 200) {
|
||||
throw new InvalidArgumentException("Failed to fetch Google Font from API.");
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 388 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 170 KiB |
Before Width: | Height: | Size: 499 B After Width: | Height: | Size: 387 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.9 KiB |