feat: Added Letter Spacing option (#302)

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
pull/304/head
pigolitsyn_m 3 months ago committed by GitHub
parent 23491950b6
commit d57e8c2155
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

3
.gitignore vendored

@ -1,3 +1,4 @@
vendor/
.vscode/
.env
.env
.idea

@ -120,22 +120,23 @@ Feel free to [open a PR](https://github.com/DenverCoder1/readme-typing-svg/issue
## 🔧 Options
| Parameter | Details | Type | Example |
| :----------: | :-------------------------------------------------------------------------: | :-----: | :---------------------------------: |
| `lines` | Text to display with lines separated by `;` and `+` for spaces | string | `First+line;Second+line;Third+line` |
| `height` | Height of the output SVG in pixels (default: `50`) | integer | Any positive number |
| `width` | Width of the output SVG in pixels (default: `400`) | integer | Any positive number |
| `size` | Font size in pixels (default: `20`) | integer | Any positive number |
| `font` | Font family (default: `monospace`) | string | Any font from Google Fonts |
| `color` | Color of the text (default: `36BCF7`) | string | Hex code without # (eg. `F724A9`) |
| `background` | Background color of the text (default: `00000000`) | string | Hex code without # (eg. `FEFF4C`) |
| `center` | `true` to center text or `false` for left aligned (default: `false`) | boolean | `true` or `false` |
| `vCenter` | `true` to center vertically or `false`(default) to align above the center | boolean | `true` or `false` |
| `multiline` | `true` to wrap lines or `false` to retype on one line (default: `false`) | boolean | `true` or `false` |
| `duration` | Duration of the printing of a single line in milliseconds (default: `5000`) | integer | Any positive number |
| `pause` | Duration of the pause between lines in milliseconds (default: `0`) | integer | Any non-negative number |
| `repeat` | `true` to loop around to the first line after the last (default: `true`) | boolean | `true` or `false` |
| `separator` | Separator used between lines in the lines parameter (default: `;`) | string | `;`, `;;`, `/`, etc. |
| Parameter | Details | Type | Example |
| :-------------: | :-------------------------------------------------------------------------: | :-----: | :---------------------------------------------------------------------------------------------------------------: |
| `lines` | Text to display with lines separated by `;` and `+` for spaces | string | `First+line;Second+line;Third+line` |
| `height` | Height of the output SVG in pixels (default: `50`) | integer | Any positive number |
| `width` | Width of the output SVG in pixels (default: `400`) | integer | Any positive number |
| `size` | Font size in pixels (default: `20`) | integer | Any positive number |
| `font` | Font family (default: `monospace`) | string | Any font from Google Fonts |
| `color` | Color of the text (default: `36BCF7`) | string | Hex code without # (eg. `F724A9`) |
| `background` | Background color of the text (default: `00000000`) | string | Hex code without # (eg. `FEFF4C`) |
| `center` | `true` to center text or `false` for left aligned (default: `false`) | boolean | `true` or `false` |
| `vCenter` | `true` to center vertically or `false`(default) to align above the center | boolean | `true` or `false` |
| `multiline` | `true` to wrap lines or `false` to retype on one line (default: `false`) | boolean | `true` or `false` |
| `duration` | Duration of the printing of a single line in milliseconds (default: `5000`) | integer | Any positive number |
| `pause` | Duration of the pause between lines in milliseconds (default: `0`) | integer | Any non-negative number |
| `repeat` | `true` to loop around to the first line after the last (default: `true`) | boolean | `true` or `false` |
| `separator` | Separator used between lines in the lines parameter (default: `;`) | string | `;`, `;;`, `/`, etc. |
| `letterSpacing` | Letter spacing (default: `normal`) | string | Any css values for the [letter-spacing](https://developer.mozilla.org/en-US/docs/Web/CSS/letter-spacing) property |
## 📤 Deploying it on your own

@ -70,6 +70,17 @@
<label for="size">Font size</label>
<input class="param" type="number" id="size" name="size" alt="Font size" placeholder="20" value="20">
<div class="label-group">
<label for="letterSpacing">Letter spacing</label>
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/letter-spacing" target="_blank" class="icon tooltip" title="Enter any css value for the letter-spacing property">
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
<path d="M12 6C9.831 6 8.066 7.765 8.066 9.934h2C10.066 8.867 10.934 8 12 8s1.934.867 1.934 1.934c0 .598-.481 1.032-1.216 1.626-.255.207-.496.404-.691.599C11.029 13.156 11 14.215 11 14.333V15h2l-.001-.633c.001-.016.033-.386.441-.793.15-.15.339-.3.535-.458.779-.631 1.958-1.584 1.958-3.182C15.934 7.765 14.169 6 12 6zM11 16H13V18H11z"></path>
<path d="M12,2C6.486,2,2,6.486,2,12s4.486,10,10,10s10-4.486,10-10S17.514,2,12,2z M12,20c-4.411,0-8-3.589-8-8s3.589-8,8-8 s8,3.589,8,8S16.411,20,12,20z"></path>
</svg>
</a>
</div>
<input class="param" type="text" id="letterSpacing" name="letterSpacing" alt="Letter spacing" placeholder="normal" value="normal">
<label for="duration">Duration (ms per line)</label>
<input class="param" type="number" id="duration" name="duration" alt="Print duration (ms)" placeholder="5000" value="5000">

@ -6,6 +6,7 @@ let preview = {
color: "36BCF7",
background: "00000000",
size: "20",
letterSpacing: "normal",
center: "false",
vCenter: "false",
multiline: "false",

@ -58,6 +58,9 @@ class RendererModel
/** @var string $fontCSS CSS required for displaying the selected font */
public $fontCSS;
/** @var string $letterSpacing Letter spacing */
public $letterSpacing;
/** @var string $template Path to template file */
public $template;
@ -78,6 +81,7 @@ class RendererModel
"repeat" => "true",
"separator" => ";",
"random" => "false",
"letterSpacing" => "normal",
];
/**
@ -106,6 +110,7 @@ class RendererModel
$this->pause = $this->checkNumberNonNegative($params["pause"] ?? $this->DEFAULTS["pause"], "pause");
$this->repeat = $this->checkBoolean($params["repeat"] ?? $this->DEFAULTS["repeat"]);
$this->fontCSS = $this->fetchFontCSS($this->font, $this->weight, $params["lines"]);
$this->letterSpacing = $this->checkLetterSpacing($params["letterSpacing"] ?? $this->DEFAULTS["letterSpacing"]);
}
/**
@ -224,4 +229,43 @@ class RendererModel
// font is not found
return "";
}
/**
* Validate unit for size properties
*
* This method validates if the given unit is a valid CSS size unit.
* It supports various units such as px, em, rem, pt, pc, in, cm, mm,
* ex, ch, vh, vw, vmin, vmax, and percentages.
*
* @param string $unit Unit for validation
* @return bool True if valid, false otherwise
*/
private function isValidUnit($unit)
{
return (bool) preg_match("/^(-?\\d+(\\.\\d+)?(px|em|rem|pt|pc|in|cm|mm|ex|ch|vh|vw|vmin|vmax|%))$/", $unit);
}
/**
* Validate letter spacing
*
* This method validates the letter spacing property for fonts.
* It allows specific keywords (normal, inherit, initial, revert, revert-layer, unset)
* and valid CSS size units.
*
* @param string $letterSpacing Letter spacing for validation
* @return string Validated letter spacing
*/
private function checkLetterSpacing($letterSpacing)
{
// List of valid keywords for letter-spacing
$keywords = "normal|inherit|initial|revert|revert-layer|unset";
// Check if the input matches one of the keywords or a valid unit
if (preg_match("/^($keywords)$/", $letterSpacing) || $this->isValidUnit($letterSpacing)) {
return $letterSpacing;
}
// Return the default letter spacing value if the input is invalid
return $this->DEFAULTS["letterSpacing"];
}
}

@ -57,7 +57,8 @@
</path>
<text font-family='"<?= $font ?>", monospace' fill='<?= $color ?>' font-size='<?= $size ?>'
dominant-baseline='<?= $vCenter ? "middle" : "auto" ?>'
x='<?= $center ? "50%" : "0%" ?>' text-anchor='<?= $center ? "middle" : "start" ?>'>
x='<?= $center ? "50%" : "0%" ?>' text-anchor='<?= $center ? "middle" : "start" ?>'
letter-spacing='<?= $letterSpacing ?>'>
<textPath xlink:href='#path<?= $i ?>'>
<?= $lines[$i] . "\n" ?>
</textPath>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -41,6 +41,7 @@ class RendererView
"duration" => $this->model->duration,
"pause" => $this->model->pause,
"repeat" => $this->model->repeat,
"letterSpacing" => $this->model->letterSpacing,
]);
// render SVG with output buffering
ob_start();

@ -367,4 +367,33 @@ final class OptionsTest extends TestCase
$model = new RendererModel("src/templates/main.php", $params);
$this->assertEquals(false, $model->random);
}
/**
* Test Letter Spacing
*/
public function testLetterSpacing(): void
{
// default
$params = [
"lines" => "text",
];
$model = new RendererModel("src/templates/main.php", $params);
$this->assertEquals("normal", $model->letterSpacing);
// invalid
$params = [
"lines" => "text",
"letterSpacing" => "invalid",
];
$model = new RendererModel("src/templates/main.php", $params);
$this->assertEquals("normal", $model->letterSpacing);
// valid
$params = [
"lines" => "text",
"letterSpacing" => "10px",
];
$model = new RendererModel("src/templates/main.php", $params);
$this->assertEquals("10px", $model->letterSpacing);
}
}

@ -326,4 +326,24 @@ final class RendererTest extends TestCase
$this->assertStringContainsString("> $line </textPath>", $actualSVG);
}
}
/**
* Test Letter Spacing
*/
public function testLetterSpacing()
{
$params = [
"lines" => implode(";", [
"Full-stack web and app developer",
"Self-taught UI/UX Designer",
"10+ years of coding experience",
"Always learning new things",
]),
"letterSpacing" => "10px",
];
$controller = new RendererController($params);
$actualSVG = preg_replace("/\s+/", " ", $controller->render());
$this->assertStringContainsString("letter-spacing='10px'", $actualSVG);
$this->assertStringNotContainsString("letter-spacing='normal'", $actualSVG);
}
}

@ -13,7 +13,8 @@
</path>
<text font-family='"monospace", monospace' fill='#36BCF7' font-size='20'
dominant-baseline='middle'
x='50%' text-anchor='middle'>
x='50%' text-anchor='middle'
letter-spacing='normal'>
<textPath xlink:href='#path0'>
Full-stack web and app developer
</textPath>
@ -27,7 +28,8 @@
</path>
<text font-family='"monospace", monospace' fill='#36BCF7' font-size='20'
dominant-baseline='middle'
x='50%' text-anchor='middle'>
x='50%' text-anchor='middle'
letter-spacing='normal'>
<textPath xlink:href='#path1'>
Self-taught UI/UX Designer
</textPath>
@ -41,7 +43,8 @@
</path>
<text font-family='"monospace", monospace' fill='#36BCF7' font-size='20'
dominant-baseline='middle'
x='50%' text-anchor='middle'>
x='50%' text-anchor='middle'
letter-spacing='normal'>
<textPath xlink:href='#path2'>
10+ years of coding experience
</textPath>
@ -55,7 +58,8 @@
</path>
<text font-family='"monospace", monospace' fill='#36BCF7' font-size='20'
dominant-baseline='middle'
x='50%' text-anchor='middle'>
x='50%' text-anchor='middle'
letter-spacing='normal'>
<textPath xlink:href='#path3'>
Always learning new things
</textPath>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Loading…
Cancel
Save