├── assets
├── scripts
│ ├── tosvg.pe
│ └── tottf.pe
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
├── css
│ └── styles.css
└── js
│ └── uikit-icons.min.js
├── screenshot@2x.png
├── docker-compose.yml
├── config.docker.yml
├── composer.json
├── config.default.yml
├── config.default-osx.yml
├── README.md
├── .gitignore
├── index.php
├── src
├── Converters
│ ├── ConverterInterface.php
│ ├── WoffConverter.php
│ ├── Woff2Converter.php
│ ├── TrueTypeConverter.php
│ ├── EmbedOpenTypeConverter.php
│ └── ScalableVectorGraphicsConverter.php
├── Form
│ └── FontType.php
├── Subsetters
│ └── PythonFontSubset.php
├── Util
│ └── StringHandler.php
├── WebFont.php
└── App.php
└── views
├── base.html.twig
└── forms.html.twig
/assets/scripts/tosvg.pe:
--------------------------------------------------------------------------------
1 | #!/usr/bin/fontforge
2 |
3 | Open($1)
4 | Generate($1:r + ".svg")
5 |
--------------------------------------------------------------------------------
/assets/scripts/tottf.pe:
--------------------------------------------------------------------------------
1 | #!/usr/bin/fontforge
2 |
3 | Open($1)
4 | Generate($1:r + ".ttf")
5 |
--------------------------------------------------------------------------------
/screenshot@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ambroisemaupate/webfont-generator/HEAD/screenshot@2x.png
--------------------------------------------------------------------------------
/assets/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ambroisemaupate/webfont-generator/HEAD/assets/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/assets/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ambroisemaupate/webfont-generator/HEAD/assets/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/assets/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ambroisemaupate/webfont-generator/HEAD/assets/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/assets/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ambroisemaupate/webfont-generator/HEAD/assets/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/assets/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ambroisemaupate/webfont-generator/HEAD/assets/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | app:
4 | environment:
5 | DEBUG: 1
6 | image: ambroisemaupate/webfontgenerator
7 | ports:
8 | - "8080:80"
9 | volumes:
10 | - ./:/var/www/html:cached
11 |
--------------------------------------------------------------------------------
/config.docker.yml:
--------------------------------------------------------------------------------
1 | converters:
2 | ttf:
3 | path: /usr/bin/fontforge
4 | class: WebfontGenerator\Converters\TrueTypeConverter
5 | svg:
6 | path: /usr/bin/fontforge
7 | class: WebfontGenerator\Converters\ScalableVectorGraphicsConverter
8 | woff:
9 | path: /usr/bin/sfnt2woff
10 | class: WebfontGenerator\Converters\WoffConverter
11 | woff2:
12 | path: /usr/bin/woff2_compress
13 | class: WebfontGenerator\Converters\Woff2Converter
14 | eot:
15 | path: /bin/ttf2eot
16 | class: WebfontGenerator\Converters\EmbedOpenTypeConverter
17 |
18 | pyftsubset: /usr/local/bin/pyftsubset
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "php": "^7.1",
4 | "twig/twig": "^2.5",
5 | "symfony/yaml": "^4.1",
6 | "symfony/http-foundation": "^4.1",
7 | "symfony/finder": "^4.1",
8 | "symfony/filesystem": "^4.1",
9 | "symfony/form": "^4.1",
10 | "symfony/validator": "^4.1",
11 | "symfony/twig-bridge": "^4.1",
12 | "symfony/translation": "^4.1",
13 | "symfony/config": "^4.1",
14 | "ext-zip": "*"
15 | },
16 | "autoload": {
17 | "psr-4": {"WebfontGenerator\\": "src/"}
18 | },
19 | "require-dev": {
20 | "squizlabs/php_codesniffer": "^3.3"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/config.default.yml:
--------------------------------------------------------------------------------
1 | ---
2 | converters:
3 | ttf:
4 | # http://fontforge.github.io/
5 | path: /usr/bin/fontforge
6 | class: WebfontGenerator\Converters\TrueTypeConverter
7 | svg:
8 | path: /usr/bin/fontforge
9 | class: WebfontGenerator\Converters\ScalableVectorGraphicsConverter
10 | woff:
11 | # http://people.mozilla.com/~jkew/woff/
12 | path: /usr/bin/sfnt2woff
13 | class: WebfontGenerator\Converters\WoffConverter
14 | woff2:
15 | # https://github.com/google/woff2
16 | path: /usr/bin/woff2_compress
17 | class: WebfontGenerator\Converters\Woff2Converter
18 | eot:
19 | # http://code.google.com/p/ttf2eot/
20 | path: /usr/bin/ttf2eot
21 | class: WebfontGenerator\Converters\EmbedOpenTypeConverter
22 |
23 | pyftsubset: /usr/local/bin/pyftsubset
--------------------------------------------------------------------------------
/config.default-osx.yml:
--------------------------------------------------------------------------------
1 | ---
2 | converters:
3 | # Needed to convert to TTF and SVG
4 | # http://fontforge.github.io/
5 | ttf:
6 | path: /Applications/FontForge.app/Contents/Resources/opt/local/bin/fontforge
7 | class: WebfontGenerator\Converters\TrueTypeConverter
8 | svg:
9 | path: /Applications/FontForge.app/Contents/Resources/opt/local/bin/fontforge
10 | class: WebfontGenerator\Converters\ScalableVectorGraphicsConverter
11 | woff:
12 | # http://people.mozilla.com/~jkew/woff/
13 | path: /usr/bin/sfnt2woff
14 | class: WebfontGenerator\Converters\WoffConverter
15 | woff2:
16 | # https://github.com/google/woff2
17 | path: /usr/bin/woff2_compress
18 | class: WebfontGenerator\Converters\Woff2Converter
19 | eot:
20 | # http://code.google.com/p/ttf2eot/
21 | path: /usr/bin/ttf2eot
22 | class: WebfontGenerator\Converters\EmbedOpenTypeConverter
23 |
24 | pyftsubset: /usr/local/bin/pyftsubset
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # webfont-generator
2 | **Subset and convert one or many OTF/TTF files in EOT, SVG, WOFF and WOFF2.**
3 |
4 | Built thanks to:
5 |
6 | * [fontforge](http://fontforge.github.io/), for converting to ttf and svg. You only need to setup command line scripting tool.
7 | * [ttf2eot](https://github.com/wget/ttf2eot)
8 | * [sfnt2woff](https://github.com/kseo/sfnt2woff)
9 | * [woff2_compress](https://github.com/google/woff2)
10 | * [fonttools (pyftsubset)](https://github.com/fonttools/fonttools#other-tools)
11 |
12 | 
13 |
14 | ## Usage with Docker
15 |
16 | ```bash
17 | docker run -ti --name "webfontgen" -p 8080:80 ambroisemaupate/webfontgenerator
18 | ```
19 |
20 | Then open your browser on `http://localhost:8080`, upload your OTF/TTF font file and… enjoy!
21 |
22 | ## Development
23 |
24 | Clone this repository, then:
25 |
26 | ```bash
27 | cp config.docker.yml config.yml
28 | composer install
29 | docker-compose up
30 | ```
31 |
32 | Then open your browser on `http://localhost:8080`
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io
2 |
3 | ### Composer ###
4 | composer.phar
5 | vendor/
6 | old
7 |
8 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
9 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
10 | composer.lock
11 |
12 |
13 | ### OSX ###
14 | .DS_Store
15 | .AppleDouble
16 | .LSOverride
17 |
18 | # Icon must end with two \r
19 | Icon
20 |
21 |
22 | # Thumbnails
23 | ._*
24 |
25 | # Files that might appear on external disk
26 | .Spotlight-V100
27 | .Trashes
28 |
29 | # Directories potentially created on remote AFP share
30 | .AppleDB
31 | .AppleDesktop
32 | Network Trash Folder
33 | Temporary Items
34 | .apdisk
35 |
36 | # Managed by Composer
37 | /app/bootstrap.php.cache
38 | /var/bootstrap.php.cache
39 | /bin/*
40 | !bin/console
41 | !bin/symfony_requirements
42 | /vendor/
43 |
44 | # Assets and user uploads
45 | /web/bundles/
46 | /web/uploads/
47 |
48 | # PHPUnit
49 | /app/phpunit.xml
50 | /phpunit.xml
51 |
52 | # Build data
53 | /build/
54 | /dist/
55 | /cache/
56 |
57 | # Composer PHAR
58 | /composer.phar
59 |
60 | ### Font converter
61 | config.yml
62 |
63 | .nfs.*
64 |
--------------------------------------------------------------------------------
/assets/css/styles.css:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * FontConverter GUI
4 | * Ambroise Maupate
5 | */
6 |
7 | body {
8 | background-color: #EFEFEF;
9 | font-family: -apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
10 | min-height: 100vh;
11 | box-sizing: border-box;
12 | padding: 50px 0;
13 | }
14 | .uk-container-center {
15 | max-width: 750px;
16 | box-sizing: border-box;
17 | margin: 0px auto;
18 | /* text-align: center; */
19 | border: 1px solid #CCCCCC;
20 | background-color: white;
21 | overflow: hidden;
22 | }
23 | .disclaimer {
24 | max-width: 500px;
25 | box-sizing: border-box;
26 | margin: 10px auto;
27 | font-size: 10px;
28 | color: #CCCCCC;
29 | }
30 | .disclaimer a {
31 | color: #CCCCCC;
32 | }
33 | .inner-form {
34 | padding: 20px 20px 0px;
35 | }
36 | .main-title {
37 | max-width: 750px;
38 | margin: 0px auto;
39 | color: #999999;
40 | font-family: "Patua One",-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
41 | font-size: 50px;
42 | letter-spacing: 0.01em;
43 | text-align: center;
44 | line-height: 1em;
45 | padding: 20px 0px;
46 | }
47 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | handle($request)->send();
35 |
--------------------------------------------------------------------------------
/src/Converters/ConverterInterface.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Webfont generator
7 |
8 |
9 |
10 |
11 |
12 |
13 | Webfont generator
14 |
15 | {% form_theme form 'forms.html.twig' %}
16 | {{ form_start(form, {
17 | attr: {
18 | class: "uk-form uk-form-stacked",
19 | id: "ttf-form"
20 | }
21 | }) }}
22 |
36 | {{ form_end(form) }}
37 |
38 | Source available at github.com/ambroisemaupate/webfont-generator
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/Form/FontType.php:
--------------------------------------------------------------------------------
1 | add('files', FileType::class, [
29 | 'multiple' => true,
30 | 'constraints' => [
31 | new NotBlank(),
32 | new All([
33 | 'constraints' => [
34 | new File([
35 | 'maxSize' => '2M',
36 | 'mimeTypes' => [
37 | 'application/x-font-ttf',
38 | 'application/vnd.ms-opentype'
39 | ]
40 | ])
41 | ]
42 | ])
43 | ]
44 | ])
45 | ->add('subset_latin', CheckboxType::class, [
46 | 'label' => 'Subset fonts to Latin range',
47 | 'help' => 'Only export glyphs within selected ranges.',
48 | 'required' => false,
49 | 'attr' => ['class' => 'uk-checkbox']
50 | ])
51 | ->add('subset_ranges', ChoiceType::class, [
52 | 'label' => 'Subset ranges',
53 | 'help' => 'Choose your Unicode ranges (http://jrgraphix.net/research/unicode.php).',
54 | 'choices' => PythonFontSubset::$ranges,
55 | 'multiple' => true,
56 | 'expanded' => true,
57 | ])
58 | ;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/views/forms.html.twig:
--------------------------------------------------------------------------------
1 | {% extends 'form_div_layout.html.twig' %}
2 |
3 | {%- block form_errors -%}
4 | {%- if errors|length > 0 -%}
5 |
6 | {%- for error in errors -%}
7 |
{{ error.message }}
8 | {%- endfor -%}
9 |
10 | {%- endif -%}
11 | {%- endblock form_errors -%}
12 |
13 | {%- block checkbox_row -%}
14 | {%- set widget_attr = {} -%}
15 | {%- if help is not empty -%}
16 | {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%}
17 | {%- endif -%}
18 |
19 | {{- form_errors(form) -}}
20 | {{- form_widget(form, widget_attr) }} {{ label -}}
21 | {{- form_help(form) -}}
22 |
23 | {%- endblock checkbox_row -%}
24 |
25 | {% block form_help -%}
26 | {%- if help is not empty -%}
27 |
28 | {%- if translation_domain is same as(false) -%}
29 | {{- help -}}
30 | {%- else -%}
31 | {{- help|trans({}, translation_domain) -}}
32 | {%- endif -%}
33 |
34 | {%- endif -%}
35 | {%- endblock form_help %}
36 |
37 | {%- block choice_row -%}
38 | {%- set widget_attr = {} -%}
39 | {%- if help is not empty -%}
40 | {%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%}
41 | {%- endif -%}
42 |
43 | {{- form_label(form) -}}
44 | {{- form_errors(form) -}}
45 | {{- form_help(form) -}}
46 | {{- form_widget(form, widget_attr) -}}
47 |
48 | {%- endblock choice_row -%}
49 |
50 | {%- block choice_widget_expanded -%}
51 |
67 | {%- endblock choice_widget_expanded -%}
--------------------------------------------------------------------------------
/src/Converters/WoffConverter.php:
--------------------------------------------------------------------------------
1 | woffCompress = $binPath;
43 | }
44 |
45 | public function convert(File $input)
46 | {
47 | if (!file_exists($this->woffCompress)) {
48 | throw new \RuntimeException('sfnt2woff could not be found.');
49 | }
50 | $output = [];
51 | $outFile = $this->getWOFFPath($input);
52 |
53 | exec(
54 | $this->woffCompress.' "'.$input->getRealPath().'"',
55 | $output,
56 | $return
57 | );
58 |
59 | if (0 !== $return) {
60 | throw new \RuntimeException('sfnt2woff could not convert '.$input->getBasename().' to Woff format.');
61 | } else {
62 | return new File($outFile);
63 | }
64 | }
65 |
66 | public function getWOFFPath(File $input)
67 | {
68 | $basename = StringHandler::slugify($input->getBasename('.'.$input->getExtension()));
69 |
70 | return $input->getPath().DIRECTORY_SEPARATOR.$basename.'.woff';
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Converters/Woff2Converter.php:
--------------------------------------------------------------------------------
1 | woffCompress = $binPath;
43 | }
44 |
45 | public function convert(File $input)
46 | {
47 | if (!file_exists($this->woffCompress)) {
48 | throw new \RuntimeException('woff2_compress could not be found.');
49 | }
50 | $output = [];
51 | $outFile = $this->getWOFFPath($input);
52 | exec(
53 | $this->woffCompress.' "'.$input->getRealPath().'"',
54 | $output,
55 | $return
56 | );
57 |
58 | if (0 !== $return) {
59 | throw new \RuntimeException('woff2_compress could not convert '.$input->getBasename().' to Woff2 format.');
60 | } else {
61 | return new File($outFile);
62 | }
63 | }
64 |
65 | public function getWOFFPath(File $input)
66 | {
67 | $basename = StringHandler::slugify($input->getBasename('.'.$input->getExtension()));
68 |
69 | return $input->getPath().DIRECTORY_SEPARATOR.$basename.'.woff2';
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/Converters/TrueTypeConverter.php:
--------------------------------------------------------------------------------
1 | fontforge = $binPath;
43 | }
44 |
45 | public function convert(File $input)
46 | {
47 | if (!file_exists($this->fontforge)) {
48 | throw new \RuntimeException('Fontforge could not be found.');
49 | }
50 |
51 | $output = [];
52 | $outFile = $this->getTTFPath($input);
53 | exec(
54 | $this->fontforge.' -script '.ROOT.'/assets/scripts/tottf.pe "'.$input->getRealPath().'"',
55 | $output,
56 | $return
57 | );
58 |
59 | if (0 !== $return) {
60 | throw new \RuntimeException('Fontforge could not convert '.$input->getBasename().' to TrueType format.');
61 | } else {
62 | return new File($outFile);
63 | }
64 | }
65 |
66 | public function getTTFPath(File $input)
67 | {
68 | $basename = StringHandler::slugify($input->getBasename('.'.$input->getExtension()));
69 |
70 | return $input->getPath().DIRECTORY_SEPARATOR.$basename.'.ttf';
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Converters/EmbedOpenTypeConverter.php:
--------------------------------------------------------------------------------
1 | ttf2eot = $binPath;
43 | }
44 |
45 | public function convert(File $input)
46 | {
47 | if (!file_exists($this->ttf2eot)) {
48 | throw new \RuntimeException('ttf2eot could not be found.');
49 | }
50 | $eotPath = $this->getEOTPath($input);
51 | $output = [];
52 | exec(
53 | $this->ttf2eot.' "'.$input->getRealPath(). '" > ' . $eotPath.'',
54 | $output,
55 | $return
56 | );
57 |
58 | $outputHuman = implode(' ', $output);
59 |
60 | if (0 !== $return) {
61 | throw new \RuntimeException('ttf2eot could not convert '.$input->getBasename().' to EOT format.' . $outputHuman);
62 | } else {
63 | return new File($eotPath);
64 | }
65 | }
66 |
67 | public function getEOTPath(File $input)
68 | {
69 | $basename = StringHandler::slugify($input->getBasename('.'.$input->getExtension()));
70 |
71 | return $input->getPath().DIRECTORY_SEPARATOR.$basename.'.eot';
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Converters/ScalableVectorGraphicsConverter.php:
--------------------------------------------------------------------------------
1 | fontforge = $binPath;
43 | }
44 |
45 | public function convert(File $input)
46 | {
47 | if (!file_exists($this->fontforge)) {
48 | throw new \RuntimeException('Fontforge could not be found.');
49 | }
50 |
51 | $output = [];
52 | $outFile = $this->getSVGPath($input);
53 | exec(
54 | $this->fontforge.' -script '.ROOT.'/assets/scripts/tosvg.pe "'.$input->getRealPath().'"',
55 | $output,
56 | $return
57 | );
58 |
59 | if (0 !== $return) {
60 | throw new \RuntimeException('Fontforge could not convert '.$input->getBasename().' to SVG format.');
61 | } else {
62 | return new File($outFile);
63 | }
64 | }
65 |
66 | public function getSVGPath(File $input)
67 | {
68 | $basename = StringHandler::slugify($input->getBasename('.'.$input->getExtension()));
69 |
70 | return $input->getPath().DIRECTORY_SEPARATOR.$basename.'.svg';
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Subsetters/PythonFontSubset.php:
--------------------------------------------------------------------------------
1 | 'U+0000-007F',
18 | 'latin1_supplement' => 'U+00A0-00FF',
19 | 'latin_extended_a' => 'U+0100-017F',
20 | 'latin_extended_b' => 'U+0180-024F',
21 | 'ipa_extensions' => 'U+0250-02AF',
22 | 'spacing_modifier_letters' => 'U+02B0-02FF',
23 | 'combining_diacritical_marks' => 'U+0300-036F',
24 | 'greek_coptic' => 'U+0370-03FF',
25 | 'cyrillic' => 'U+0400-04FF',
26 | 'cyrillic_supplementary' => 'U+0500-052F',
27 | 'armenian' => 'U+0530-058F',
28 | 'hebrew' => 'U+0590-05FF',
29 | 'arabic' => 'U+0600-06FF',
30 | 'syriac' => 'U+0700-074F',
31 | 'thaana' => 'U+0780-07BF',
32 | 'devanagari' => 'U+0900-097F',
33 | 'bengali' => 'U+0980-09FF',
34 | 'gurmukhi' => 'U+0A00-0A7F',
35 | 'gujarati' => 'U+0A80-0AFF',
36 | 'oriya' => 'U+0B00-0B7F',
37 | 'tamil' => 'U+0B80-0BFF',
38 | 'telugu' => 'U+0C00-0C7F',
39 | 'kannada' => 'U+0C80-0CFF',
40 | 'malayalam' => 'U+0D00-0D7F',
41 | 'sinhala' => 'U+0D80-0DFF',
42 | 'thai' => 'U+0E00-0E7F',
43 | 'lao' => 'U+0E80-0EFF',
44 | 'tibetan' => 'U+0F00-0FFF',
45 | // …
46 | 'latin_extended_additional' => 'U+1E00-1EFF',
47 | 'greek_extended' => 'U+1F00-1FFF',
48 | 'general_punctuation' => 'U+2000-206F',
49 | 'superscripts_subscripts' => 'U+2070-209F',
50 | 'currency_symbols' => 'U+20A0-20CF',
51 | //…
52 | 'number_forms' => 'U+2150-218F',
53 | 'arrows' => 'U+2190-21FF',
54 | 'mathematical_operators' => 'U+2200-22FF',
55 | ];
56 |
57 | public function __construct($binPath)
58 | {
59 | $this->binPath = $binPath;
60 | }
61 |
62 | /**
63 | * @return array
64 | */
65 | public static function getBaseSet(): array
66 | {
67 | return [
68 | static::getUnicodes('basic_latin'),
69 | static::getUnicodes('latin1_supplement'),
70 | static::getUnicodes('latin_extended_a'),
71 | static::getUnicodes('spacing_modifier_letters'),
72 | static::getUnicodes('combining_diacritical_marks'),
73 | static::getUnicodes('general_punctuation'),
74 | static::getUnicodes('currency_symbols'),
75 | ];
76 | }
77 |
78 | /**
79 | * @param string $set
80 | *
81 | * @return mixed
82 | */
83 | public static function getUnicodes(string $set): string
84 | {
85 | if (!array_key_exists($set, static::$ranges)) {
86 | throw new \InvalidArgumentException('Unicode range does not exist.');
87 | }
88 | return static::$ranges[$set];
89 | }
90 |
91 | /**
92 | * @param File $input
93 | * @param array $unicodes
94 | *
95 | * @return File
96 | */
97 | public function subset(File $input, array $unicodes = [])
98 | {
99 | if (!file_exists($this->binPath)) {
100 | throw new \RuntimeException('pyftsubset binary could not be found at path '.$this->binPath);
101 | }
102 | $outFile = $this->getSubsetPath($input);
103 |
104 | if (count($unicodes) === 0) {
105 | $unicodes = $this->getBaseSet();
106 | }
107 |
108 | $cmd = $this->binPath.' "'.$input->getRealPath().'" --unicodes="'. implode(',', $unicodes).'" --output-file="'.$outFile.'"';
109 |
110 | exec(
111 | $cmd,
112 | $output,
113 | $return
114 | );
115 |
116 | if (0 !== $return) {
117 | throw new \RuntimeException('pyftsubset could not subset '.$input->getBasename().' font file.');
118 | } else {
119 | return new File($outFile);
120 | }
121 | }
122 |
123 | /**
124 | * @param File $input
125 | *
126 | * @return string
127 | */
128 | public function getSubsetPath(File $input)
129 | {
130 | $basename = StringHandler::slugify($input->getBasename('.'.$input->getExtension()));
131 |
132 | return $input->getPath().DIRECTORY_SEPARATOR.$basename.'-subset.'.$input->getExtension();
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Util/StringHandler.php:
--------------------------------------------------------------------------------
1 | id = uniqid();
74 | $this->originalFiles = [];
75 | $this->fs = $fs;
76 | $this->buildDir = ROOT . "/build/".$this->id;
77 | $this->distDir = ROOT . "/dist";
78 | $this->outputFiles = [];
79 | $this->converters = $converters;
80 | $this->fontSubset = $fontSubset;
81 | $this->unicodeRanges = $unicodeRanges;
82 |
83 | if (!$this->fs->exists($this->buildDir)) {
84 | $this->fs->mkdir($this->buildDir);
85 | }
86 | if (!$this->fs->exists($this->distDir)) {
87 | $this->fs->mkdir($this->distDir);
88 | }
89 | }
90 |
91 | /**
92 | * @param UploadedFile $tmpFile
93 | */
94 | public function addFontFile(UploadedFile $tmpFile)
95 | {
96 | $original = pathinfo($tmpFile->getClientOriginalName());
97 | $basename = StringHandler::slugify(basename($tmpFile->getClientOriginalName(), $original['extension']));
98 | $this->originalFiles[] = $tmpFile->move($this->buildDir, $basename . '.' . $original['extension']);
99 | }
100 |
101 | /**
102 | * @return null|File
103 | */
104 | public function getFirstOriginalFile()
105 | {
106 | return count($this->originalFiles) > 0 ? $this->originalFiles[0] : null;
107 | }
108 |
109 | /**
110 | * @return array
111 | */
112 | public function getOriginalFiles()
113 | {
114 | return $this->originalFiles;
115 | }
116 |
117 | /**
118 | * @param File $file
119 | * @return WebFont
120 | */
121 | protected function addOutputFile(File $file)
122 | {
123 | $this->outputFiles[] = $file;
124 | return $this;
125 | }
126 |
127 | /**
128 | * @return File
129 | * @throws \Exception
130 | */
131 | public function getZipFile()
132 | {
133 | $zipPath = $this->getZipPath();
134 | $zip = new \ZipArchive();
135 | $zip->open($zipPath, \ZipArchive::CREATE);
136 |
137 | foreach ($this->getOriginalFiles() as $originalFile) {
138 | $zip->addFile($originalFile->getRealpath(), 'original-'.$originalFile->getBasename());
139 | }
140 |
141 | foreach ($this->outputFiles as $file) {
142 | $zip->addFile($file->getRealpath(), $file->getBasename());
143 | }
144 |
145 | if (!$zip->close()) {
146 | throw new \Exception("Impossible to create ZIP archive", 1);
147 | }
148 |
149 | $this->zipFile = new File($zipPath);
150 | $this->fs->remove($this->buildDir);
151 | return $this->zipFile;
152 | }
153 |
154 | /**
155 | * @return string
156 | */
157 | public function getZipPath()
158 | {
159 | if (null === $this->getFirstOriginalFile()) {
160 | throw new \RuntimeException('No font file have been added yet.');
161 | }
162 | return $this->distDir .
163 | DIRECTORY_SEPARATOR .
164 | strtolower(str_replace(".", "-", $this->getFirstOriginalFile()->getBasename())) .
165 | "-" .
166 | $this->id .
167 | ".zip";
168 | }
169 |
170 | /**
171 | *
172 | */
173 | public function convert()
174 | {
175 | foreach ($this->getOriginalFiles() as $originalFile) {
176 | foreach ($this->converters as $converter) {
177 | $this->addOutputFile($converter->convert($originalFile));
178 | }
179 | }
180 | }
181 |
182 | /**
183 | *
184 | */
185 | public function subsetAndConvert()
186 | {
187 | if ($this->fontSubset === null) {
188 | throw new \RuntimeException('Cannot subset with null Font Subsetter.');
189 | }
190 |
191 | foreach ($this->getOriginalFiles() as $originalFile) {
192 | $subsetFont = $this->fontSubset->subset($originalFile, $this->unicodeRanges);
193 | foreach ($this->converters as $converter) {
194 | $this->addOutputFile($converter->convert($subsetFont));
195 | }
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/App.php:
--------------------------------------------------------------------------------
1 | addLoader('xlf', new XliffFileLoader());
76 | $this->getTwig()->addExtension(new TranslationExtension($translator));
77 | $validator = Validation::createValidatorBuilder()
78 | ->setTranslationDomain(null)
79 | ->setTranslator($translator)
80 | ->getValidator();
81 | // there are built-in translations for the core error messages
82 | $translator->addResource(
83 | 'xlf',
84 | $vendorFormDirectory.'/Resources/translations/validators.en.xlf',
85 | 'en',
86 | 'validators'
87 | );
88 | $translator->addResource(
89 | 'xlf',
90 | $vendorValidatorDirectory.'/Resources/translations/validators.en.xlf',
91 | 'en',
92 | 'validators'
93 | );
94 |
95 | return Forms::createFormFactoryBuilder()
96 | ->addExtension(new HttpFoundationExtension())
97 | ->addExtension(new ValidatorExtension($validator))
98 | ->getFormFactory();
99 | }
100 |
101 | /**
102 | * @param Request $request
103 | *
104 | * @return Response
105 | * @throws \Exception
106 | */
107 | public function handle(Request $request)
108 | {
109 | if (!empty($this->getConfig()['converters'])) {
110 | $formFactory = $this->createFormFactory();
111 | $form = $formFactory->createNamed('fonts', FontType::class, [
112 | 'subset_latin' => true,
113 | 'subset_ranges' => PythonFontSubset::getBaseSet()
114 | ]);
115 | $form->handleRequest($request);
116 |
117 | if ($form->isSubmitted() && $form->isValid()) {
118 | try {
119 | $fs = new Filesystem();
120 | $font = new WebFont(
121 | $fs,
122 | $this->getFontConverters(),
123 | $this->getFontSubsetter(),
124 | $form->get('subset_ranges')->getData()
125 | );
126 | /** @var UploadedFile $file */
127 | foreach ($form->get('files')->getData() as $file) {
128 | $font->addFontFile($file);
129 | }
130 | if ($form->get('subset_latin')->getData() === true) {
131 | $font->subsetAndConvert();
132 | } else {
133 | $font->convert();
134 | }
135 |
136 | $zipFile = $font->getZipFile();
137 | $response = new BinaryFileResponse($zipFile);
138 | $response->setContentDisposition(
139 | ResponseHeaderBag::DISPOSITION_ATTACHMENT,
140 | $zipFile->getBasename()
141 | );
142 | $response->prepare($request);
143 | return $response;
144 | } catch (\RuntimeException $exception) {
145 | $form->addError(new FormError($exception->getMessage()));
146 | }
147 | }
148 | $this->assignation['form'] = $form->createView();
149 |
150 | $response = new Response($this->getTwig()->render('base.html.twig', $this->assignation));
151 | $response->setCharset('UTF-8');
152 | $response->prepare($request);
153 |
154 | return $response;
155 | } else {
156 | throw new \Exception("You must define converters path in your “config.yml” file.", 1);
157 | }
158 | }
159 |
160 | /**
161 | * @return null|Environment
162 | */
163 | public function getTwig()
164 | {
165 | if (null === $this->twig) {
166 | $defaultFormTheme = 'form_div_layout.html.twig';
167 | $appVariableReflection = new \ReflectionClass('\Symfony\Bridge\Twig\AppVariable');
168 | $vendorTwigBridgeDirectory = dirname($appVariableReflection->getFileName());
169 |
170 | $this->twig = new Environment(
171 | new FilesystemLoader([
172 | ROOT. '/views',
173 | $vendorTwigBridgeDirectory.'/Resources/views/Form',
174 | ]),
175 | [
176 | 'debug' => isset($_ENV['DEBUG']) && $_ENV['DEBUG'] == true ? true : false,
177 | 'cache' => ROOT . "/cache",
178 | ]
179 | );
180 |
181 | $formEngine = new TwigRendererEngine([$defaultFormTheme], $this->twig);
182 | $this->twig->addRuntimeLoader(new FactoryRuntimeLoader([
183 | FormRenderer::class => function () use ($formEngine) {
184 | return new FormRenderer($formEngine);
185 | },
186 | ]));
187 | $this->twig->addExtension(new FormExtension());
188 | }
189 |
190 | return $this->twig;
191 | }
192 |
193 | /**
194 | * @return mixed|null
195 | */
196 | public function getConfig()
197 | {
198 | if (null === $this->config &&
199 | file_exists(ROOT . "/" . $this->configPath)) {
200 | $yaml = new Parser();
201 | $this->config = $yaml->parse(file_get_contents(ROOT . "/" . $this->configPath));
202 | }
203 |
204 | return $this->config;
205 | }
206 |
207 | /**
208 | * @param $class
209 | * @param $path
210 | *
211 | * @return ConverterInterface
212 | */
213 | protected function getFontConverter($class, $path)
214 | {
215 | $converterClass = $class;
216 | $c = new $converterClass($path);
217 | if ($c instanceof ConverterInterface) {
218 | return $c;
219 | } else {
220 | throw new \RuntimeException($class . "must implement ConverterInterface.");
221 | }
222 | }
223 |
224 | /**
225 | * @return ConverterInterface[]
226 | */
227 | protected function getFontConverters()
228 | {
229 | $converters = [];
230 | foreach ($this->config['converters'] as $converter) {
231 | if (!empty($converter['path']) && !empty($converter['class'])) {
232 | $converters[] = $this->getFontConverter($converter['class'], $converter['path']);
233 | }
234 | }
235 |
236 | return $converters;
237 | }
238 |
239 | /**
240 | * @return null|PythonFontSubset
241 | */
242 | private function getFontSubsetter()
243 | {
244 | if (!empty($this->config['pyftsubset'])) {
245 | return new PythonFontSubset($this->config['pyftsubset']);
246 | }
247 | return null;
248 | }
249 | }
250 |
--------------------------------------------------------------------------------
/assets/js/uikit-icons.min.js:
--------------------------------------------------------------------------------
1 | /*! UIkit 3.0.0-rc.9 | http://www.getuikit.com | (c) 2014 - 2017 YOOtheme | MIT License */
2 |
3 | !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define("uikiticons",i):t.UIkitIcons=i()}(this,function(){"use strict";function i(t){i.installed||t.icon.add({"500px":' ',album:' ',"arrow-down":' ',"arrow-left":' ',"arrow-right":' ',"arrow-up":' ',ban:' ',behance:' ',bell:' ',bold:' ',bolt:' ',bookmark:' ',calendar:' ',camera:' ',cart:' ',check:' ',"chevron-down":' ',"chevron-left":' ',"chevron-right":' ',"chevron-up":' ',clock:' ',close:' ',"cloud-download":' ',"cloud-upload":' ',code:' ',cog:' ',comment:' ',commenting:' ',comments:' ',copy:' ',"credit-card":' ',database:' ',desktop:' ',download:' ',dribbble:' ',expand:' ',facebook:' ',"file-edit":' ',file:' ',flickr:' ',folder:' ',forward:' ',foursquare:' ',future:' ',"git-branch":' ',"git-fork":' ',"github-alt":' ',github:' ',gitter:' ',"google-plus":' ',google:' ',grid:' ',happy:' ',hashtag:' ',heart:' ',history:' ',home:' ',image:' ',info:' ',instagram:' ',italic:' ',joomla:' ',laptop:' ',lifesaver:' ',link:' ',linkedin:' ',list:' ',location:' ',lock:' ',mail:' ',menu:' ',"minus-circle":' ',minus:' ',"more-vertical":' ',more:' ',move:' ',nut:' ',pagekit:' ',"paint-bucket":' ',pencil:' ',"phone-landscape":' ',phone:' ',pinterest:' ',"play-circle":' ',play:' ',"plus-circle":' ',plus:' ',pull:' ',push:' ',question:' ',"quote-right":' ',receiver:' ',refresh:' ',reply:' ',rss:' ',search:' ',server:' ',settings:' ',shrink:' ',"sign-in":' ',"sign-out":' ',social:' ',soundcloud:' ',star:' ',strikethrough:' ',table:' ',"tablet-landscape":' ',tablet:' ',tag:' ',thumbnails:' ',trash:' ',"triangle-down":' ',"triangle-left":' ',"triangle-right":' ',"triangle-up":' ',tripadvisor:' ',tumblr:' ',tv:' ',twitter:' ',uikit:' ',unlock:' ',upload:' ',user:' ',users:' ',"video-camera":' ',vimeo:' ',warning:' ',whatsapp:' ',wordpress:' ',world:' ',xing:' ',yelp:' ',youtube:' '})}return"undefined"!=typeof window&&window.UIkit&&window.UIkit.use(i),i});
--------------------------------------------------------------------------------