├── 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 | ![Webfont generator screenshot](/screenshot@2x.png) 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 |
23 |
24 | {{ form_errors(form) }} 25 | {{ form_errors(form.files) }} 26 |

Upload one or many ttf/otf font files.

27 |
28 | {{ form_widget(form.files) }} 29 | 30 |
31 | 32 |
33 | {{ form_row(form.subset_latin) }} 34 | {{ form_rest(form) }} 35 |
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 | 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 |
52 |
53 | {%- for child in form %} 54 | {%- set widget_attr = {} -%} 55 | {%- if help is not empty -%} 56 | {%- set widget_attr = child.vars|merge({attr: {'class': "uk-checkbox"}}) -%} 57 | {%- endif -%} 58 |
59 | 60 | 61 | {{- child.vars.value -}} 62 | 63 |
64 | {% endfor -%} 65 |
66 |
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}); --------------------------------------------------------------------------------