Reputation: 4228
I need to use some google fonts on an intranet application. The clients may or may not have internet connection. Reading the license terms, it appears that its legally allowed.
Upvotes: 295
Views: 214586
Reputation: 17316
More and more google font "classics" like "Open Sans" or "Inter" are nowadays available as static (one file for each weight and style) or variable fonts (all weights or other stylistic variations in one file).
The aforementioned tools like google-webfont-helper or fontsource are great for static font kit creation but often lack features for downloading variable fonts (or to choose between both types).
I came up with a client side approach to create google font kits by fetching the google API generated CSS via JavaScript:
/**
* create fontkit zip
*/
async function getFontKit(gfontUrl) {
let dir = "../fonts/";
let fontFaceData = await getFontFaceArr(gfontUrl, dir);
let fontFamily = fontFaceData[0].fontFamily;
let files = fontFaceData.map((item) => {
return {
orig: item.file,
local: item.fileLocal
};
});
let css = fontFaceData
.map((item) => {
return item.cssLocal;
})
.join("\n")
.replaceAll("{", "{\n")
.replaceAll("}", "}\n")
.replaceAll(";", ";\n");
// create zip
let objectUrl = await zipFontKit(files, fontFamily, css, dir);
return {
objectUrl,
css
};
}
/**
* filter CSS rules
* by subsets
* get file urls
*/
async function getFontFaceArr(href, dir = "") {
if (!href) return false;
// accept either href or CSS text
let isCSS = href.includes("@font-face");
let css = isCSS ? href : await (await fetch(href)).text();
// custom text substitution doesn't have subset comments
let textParam = !isCSS ?
Object.fromEntries(new URLSearchParams(href).entries()).text :
"custom";
//create temporary style element
let styleTmp = new CSSStyleSheet();
styleTmp.replaceSync(css);
let cssRules = [...styleTmp.cssRules].filter((item) => {
return item.type === 5;
});
// get language or text subset rules
let subsetRules = textParam ?
cssRules.map((rule) => {
return `subset_${textParam} */${rule.cssText}`;
}) :
css
.split("/*")
.filter(Boolean)
.map((item) => {
return item.trim();
});
// collect data in object
let newCSS = [];
for (let i = 0; i < subsetRules.length; i++) {
let subsetName = subsetRules[i].split("*/")[0].trim();
let style = cssRules[i].style;
let fontFamily = style.getPropertyValue("font-family").replaceAll('"', "");
let fontWeight = style.getPropertyValue("font-weight");
let fontStyle = style.getPropertyValue("font-style");
let fontStretch = style.getPropertyValue("font-stretch");
let unicodeRange = style.getPropertyValue("unicode-range");
let src = style
.getPropertyValue("src")
.match(/[^]*?url\((.*?)\)/)[1]
.replaceAll('"', "");
let ext = "woff2";
// local font name
let fontname =
fontFamily.replaceAll(" ", "") +
"_" + [
subsetName,
fontWeight,
fontStyle != "normal" ? fontStyle : "",
fontStretch != "normal" && fontStretch !== "100%" ? fontStretch : ""
]
.filter(Boolean)
.map((val) => {
return val.trim().replaceAll("%", "").replaceAll(" ", "-");
})
.join("_") +
"." +
ext;
// replace remote with local paths
let srcLocal = dir + fontname;
let css = cssRules[i];
let cssLocal =
`/** ${subsetName} **/\n` + css.cssText.replaceAll(src, srcLocal) + `\n`;
newCSS.push({
file: src,
fileLocal: fontname,
fontFamily: fontFamily,
css: css.cssText,
cssLocal: cssLocal,
unicodeRange: unicodeRange,
subset: subsetName
});
}
return newCSS;
}
/**
* generate zip
*/
async function zipFontKit(fontFiles, fontFamily, newCSS = "", dir = "fonts/") {
let fontFamilyFile = fontFamily.replaceAll(" ", "-");
let zip = new JSZip();
let cssDir = "";
let fileDir = "";
if (dir.includes("..")) {
fileDir = dir.replaceAll("../", "");
cssDir = "css/";
}
// add sample css
if (newCSS) {
zip.file(`${cssDir}${fontFamilyFile}.css`, newCSS);
}
// add fonts
for (let i = 0; i < fontFiles.length; i++) {
let src = fontFiles[i].orig;
let filename = fontFiles[i].local;
let font = await (await fetch(src)).arrayBuffer();
//console.log(dir);
zip.file(fileDir + filename, font, {
type: "uint8array"
});
}
let blob = await zip.generateAsync({
type: "blob"
});
return URL.createObjectURL(blob);
}
* {
box-sizing: border-box;
}
body {
font-family: 'Fira Sans', 'Segoe UI', sans-serif;
}
input {
display: block;
width: 100%;
}
textarea {
width: 100%;
min-height: 15em;
}
.btn-default {
display: inline-block;
text-decoration: none;
border: 1px solid #000;
color: #fff;
background: #000;
padding: 0.3em;
line-height: 1em;
border-radius: 0.3em;
text-align: center;
}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.js" defer></script>
<h3>API Url</h3>
<p><input id="inputUrl" type="text" list="gfontUrls" value="https://fonts.googleapis.com/css2?family=Open+Sans:[email protected]&display=swap ">
<datalist id="gfontUrls">
<option value="@font-face {font-family: "Open Sans";font-style: normal;font-weight: 400;font-stretch: 100%;src: url(https://fonts.gstatic.com/s/opensans/v40/memSYaGs126MiZpBA-UvWbX2vVnXBbObj2OVZyOOSr4dVJWUgsjZ0B4gaVI.woff2);unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;} ">Open Sans fontface</option>
<option value="https://fonts.googleapis.com/css2?family=Open+Sans">Open Sans 400</option>
<option value="https://fonts.googleapis.com/css2?family=Open+Sans:[email protected]&display=swap">Open Sans</option>
<option value="https://fonts.googleapis.com/css2?family=Roboto+Serif:ital,wght@0,100..900;1,100..900&display=swap">Roboto</option>
</datalist>
</p>
<h3>New CSS</h3>
<textarea name="" id="cssout"></textarea>
<p><a id="btnDownload" class="btn-default" href="" download="fontkit.zip">Download Fontkit</a></p>
<script>
window.addEventListener('DOMContentLoaded', (e) => {
inputUrl.addEventListener('input', async(e) => {
let gfontUrl = inputUrl.value;
//hide btn
btnDownload.style.display = "none";
let {
objectUrl,
css
} = await getFontKit(gfontUrl)
// output
cssout.value = css;
btnDownload.href = objectUrl;
btnDownload.style.display = "block";
})
inputUrl.dispatchEvent(new Event('input'));
})
</script>
Downloads don't work in snippets but you can open the download link in a new tab or test this example on codepen
@font-face
rules (separated by comments like /* latin */
font-weight
or font-style
are extracted by creating a temporary stylesheet object let styleTmp = new CSSStyleSheet();
styleTmp.replaceSync(css);
This way you can choose between a static or variable font kit.
You can't retrieve other formats like truetype
or woff
with this approach (unless you spoof your agent id) but this should be negligible as all modern browsers support woff2
I extended this concept by adding search and filter functionalities in this helper tool
google font finder.
Disclaimer I'm the author of this helper. Feel free to fork this repository on github.
Upvotes: 0
Reputation: 66748
Please keep in mind that my answer has aged a lot.
There are other more technically sophisticated answers below, e.g.:
so don't let the fact that this is the currently accepted answer give you the impression that this is still the best one.
You can also now also download google's entire font set via on github at their google/font repository. They also provide a ~1GB zip snapshot of their fonts.
You first download your font selection as a zipped package, providing you with a bunch of true type fonts. Copy them somewhere public, somewhere you can link to from your css.
On the google webfont download page, you'll find a include link like so:
http://fonts.googleapis.com/css?family=Cantarell:400,700,400italic,700italic|Candal
It links to a CSS defining the fonts via a bunch of @font-face
defintions.
Open it in a browser to copy and paste them into your own CSS and modify the urls to include the right font file and format types.
So this:
@font-face {
font-family: 'Cantarell';
font-style: normal;
font-weight: 700;
src: local('Cantarell Bold'), local('Cantarell-Bold'), url(http://themes.googleusercontent.com/static/fonts/cantarell/v3/Yir4ZDsCn4g1kWopdg-ehHhCUOGz7vYGh680lGh-uXM.woff) format('woff');
}
becomes this:
/* Your local CSS File */
@font-face {
font-family: 'Cantarell';
font-style: normal;
font-weight: 700;
src: local('Cantarell Bold'), local('Cantarell-Bold'), url(../font/Cantarell-Bold.ttf) format('truetype');
}
As you can see, a downside of hosting the fonts on your own system this way is, that you restrict yourself to the true type format, whilst the google webfont service determines by the accessing device which formats will be transmitted.
Furthermore, I had to add a .htaccess
file to my the directory holding the fonts containing mime types to avoid errors from popping up in Chrome Dev Tools.
For this solution, only true type is needed, but defining more does not hurt when you want to include different fonts as well, like font-awesome
.
#.htaccess
AddType application/vnd.ms-fontobject .eot
AddType font/ttf .ttf
AddType font/otf .otf
AddType application/x-font-woff .woff
Upvotes: 233
Reputation: 2269
Great solution is google-webfonts-helper .
It allows you to select more than one font variant, which saves a lot of time.
Upvotes: 167
Reputation: 638
You may follow the script which is developed using PHP.
Where you can download any google fonts by using the script.
It will download the fonts and create a CSS file and archive to zip.
You can download the source code from the GitHub https://github.com/souravmsh/google-fonts-downloader
$obj = new GoogleFontsDownloader;
if(isset($_GET['url']) && !empty($_GET['url']))
{
$obj->generate($_GET['url']);
}
if(isset($_GET['download']) && !empty($_GET['download']) && $_GET['download']=='true')
{
$obj->download();
}
/**
* GoogleFontsDownloader
* Easy way to download any google fonts.
* @author Shohrab Hossain
* @version 1.0.0
*/
class GoogleFontsDownloader
{
private $url = '';
private $dir = 'dist/';
private $fontsDir = 'fonts/';
private $cssDir = 'css/';
private $fileName = 'fonts.css';
private $content = '';
private $errors = '';
private $success = '';
public $is_downloadable = false;
public function __construct()
{
ini_set('allow_url_fopen', 'on');
ini_set('allow_url_include', 'on');
}
public function generate($url = null)
{
if (filter_var($url, FILTER_VALIDATE_URL) === FALSE)
{
$this->errors .= "<li><strong>Invalid url!</strong> $url</li>";
}
else
{
$this->url = $url;
// delete previous files
$this->_destroy();
// write font.css
$this->_css();
// write fonts
$this->_fonts();
// archive files
$this->_archive();
}
// show all messages
$this->_message();
}
public function download()
{
// Download the created zip file
$zipFileName = trim($this->dir, '/').'.zip';
if (file_exists($zipFileName))
{
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename = $zipFileName");
header("Pragma: no-cache");
header("Expires: 0");
readfile("$zipFileName");
// delete file
unlink($zipFileName);
array_map('unlink', glob("$this->dir/*.*"));
rmdir($this->dir);
}
}
private function _archive()
{
if (is_dir($this->dir))
{
$zipFileName = trim($this->dir, '/').'.zip';
$zip = new \ZipArchive();
if ($zip->open($zipFileName, ZipArchive::CREATE) === TRUE)
{
$zip->addGlob($this->dir. "*.*");
$zip->addGlob($this->dir. "*/*.*");
if ($zip->status == ZIPARCHIVE::ER_OK)
{
$this->success .= '<li>Zip create successful!</li>';
$this->is_downloadable = true;
}
else
{
$this->errors .= '<li>Failed to create to zip</li>';
}
}
else
{
$this->errors .= '<li>ZipArchive not found!</li>';
}
$zip->close();
}
else
{
$this->errors .= "<li><strong>File</strong> not exists!</li>";
}
}
private function _css()
{
$filePath = $this->dir.$this->cssDir.$this->fileName;
$content = $this->_request($this->url);
if (!empty($content))
{
if (file_put_contents($filePath, $content))
{
$this->success .= "<li>$this->fileName generated successful!</li>";
$this->content = $content;
}
else
{
$this->errors .= '<li>Permission errro in $this->fileName! Unable to write $filePath.</li>';
}
}
else
{
$this->errors .= '<li>Unable to create fonts.css file!</li>';
}
}
private function _fonts()
{
if (!empty($this->content))
{
preg_match_all('#\bhttps?://[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/))#', $this->content, $match);
$gFontPaths = $match[0];
if (!empty($gFontPaths) && is_array($gFontPaths) && sizeof($gFontPaths)>0)
{
$count = 0;
foreach ($gFontPaths as $url)
{
$name = basename($url);
$filePath = $this->dir.$this->fontsDir.$name;
$this->content = str_replace($url, '../'.$this->fontsDir.$name, $this->content);
$fontContent = $this->_request($url);
if (!empty($fontContent))
{
file_put_contents($filePath, $fontContent);
$count++;
$this->success .= "<li>The font $name downloaded!</li>";
}
else
{
$this->errors .= "<li>Unable to download the font $name!</li>";
}
}
file_put_contents($this->dir.$this->cssDir.$this->fileName, $this->content);
$this->success .= "<li>Total $count font(s) downloaded!</li>";
}
}
}
private function _request($url)
{
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_SSL_VERIFYPEER => FALSE,
CURLOPT_HEADER => FALSE,
CURLOPT_FOLLOWLOCATION => TRUE,
CURLOPT_URL => $url,
CURLOPT_REFERER => $url,
CURLOPT_RETURNTRANSFER => TRUE,
));
$result = curl_exec($ch);
curl_close($ch);
if (!empty($result))
{
return $result;
}
return false;
}
private function _destroy()
{
$cssPath = $this->dir.$this->cssDir.$this->fileName;
if (file_exists($cssPath) && is_file($cssPath))
{
unlink($cssPath);
}
else
{
mkdir($this->dir.$this->cssDir, 0777, true);
}
$fontsPath = $this->dir.$this->fontsDir;
if (!is_dir($fontsPath))
{
mkdir($fontsPath, 0777, true);
}
else
{
array_map(function($font) use($fontsPath) {
if (file_exists($fontsPath.$font) && is_file($fontsPath.$font))
{
unlink($fontsPath.$font);
}
}, glob($fontsPath.'*.*'));
}
}
private function _message()
{
if (strlen($this->errors)>0)
{
echo "<div class='alert alert-danger'><ul>$this->errors</ul></div>";
}
if (strlen($this->success)>0)
{
echo "<div class='alert alert-success'><ul>$this->success</ul></div>";
}
}
}
Upvotes: 1
Reputation: 3627
Edit: As luckyrumo pointed out, typefaces is depricated in favour of: https://github.com/fontsource/fontsource
If you're using Webpack, you might be interested in this project: https://github.com/KyleAMathews/typefaces
E.g. say you want to use Roboto font:
npm install typeface-roboto --save
Then just import it in your app's entrypoint (main js file):
import 'typeface-roboto'
Upvotes: 2
Reputation: 2548
google-webfonts-helper
Red Rose
:Copy CSS
tab
Modern Browser
if you wish to support only modern browsers (woff2, woff)Best Support
if you wish to support all browsers (I chose this - woff2, woff,ttf,svg,eot)../fonts/
path, you can edit it to represent your actual path (for me it was ../assets/fonts/
)red-rose-v1-latin-ext_latin
; unzip it and place all fonts directly into your assets/fonts
directory (based on what you gave earlier)/* red-rose-regular - latin-ext_latin */
@font-face {
font-family: 'Red Rose';
font-style: normal;
font-weight: 400;
src: url('../assets/fonts/red-rose-v1-latin-ext_latin-regular.eot'); /* IE9 Compat Modes */
src: local('Red Rose Regular'), local('RedRose-Regular'),
url('../assets/fonts/red-rose-v1-latin-ext_latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('../assets/fonts/red-rose-v1-latin-ext_latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('../assets/fonts/red-rose-v1-latin-ext_latin-regular.woff') format('woff'), /* Modern Browsers */
url('../assets/fonts/red-rose-v1-latin-ext_latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('../assets/fonts/red-rose-v1-latin-ext_latin-regular.svg#RedRose') format('svg'); /* Legacy iOS */
}
/* Red Rose will now be available for use in your stylesheet, provide this font */
:root {
font-family: 'Red Rose', cursive, sans-serif;
}
Upvotes: 3
Reputation: 1135
As you want to host all fonts (or some of them) at your own server, you a download fonts from this repo and use it the way you want: https://github.com/praisedpk/Local-Google-Fonts
If you just want to do this to fix the leverage browser caching issue that comes with Google Fonts, you can use alternative fonts CDN, and include fonts as:
<link href="https://pagecdn.io/lib/easyfonts/fonts.css" rel="stylesheet" />
Or a specific font, as:
<link href="https://pagecdn.io/lib/easyfonts/lato.css" rel="stylesheet" />
Upvotes: 2
Reputation: 4284
You can download source fonts from https://github.com/google/fonts
After that use font-ranger
tool to split your large Unicode font into multiple subsets (e.g. latin, cyrillic). You should do the following with the tool:
Font-Ranger: https://www.npmjs.com/package/font-ranger
P.S. You can also automate this using Node.js API
Upvotes: 0
Reputation: 29
You can actually download all font format variants directly from Google and include them in your css to serve from your server. That way you don't have to concern about Google tracking your site's users. However, the downside maybe slowing down your own serving speed. Fonts are quite demanding on resources. I have not done any tests in this issue yet, and wonder if anyone has similar thoughts.
Upvotes: 2
Reputation: 330
It is legally allowed as long as you stick to the terms of the font's license - usually the OFL.
You'll need a set of web font formats, and the Font Squirrel Webfont Generator can produce these.
But the OFL required the fonts be renamed if they are modified, and using the generator means modifying them.
Upvotes: 6
Reputation: 4716
I used grunt-local-googlefont in a grunt task.
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
"local-googlefont" : {
"opensans" : {
"options" : {
"family" : "Open Sans",
"sizes" : [
300,
400,
600
],
"userAgents" : [
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)", //download eot
"Mozilla/5.0 (Linux; U; Android 4.1.2; nl-nl; GT-I9300 Build/JZO54K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", //download ttf
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1944.0 Safari/537.36" //download woff and woff2
],
"cssDestination" : "build/fonts/css",
"fontDestination" : "build/fonts",
"styleSheetExtension" : "css",
"fontDestinationCssPrefix" : "fonts"
}
}
}
});
grunt.loadNpmTasks('grunt-local-googlefont');
};
Then, to retrieve them:
grunt local-googlefont:opensans
Note, I'm using a fork from the original, which works better when retrieving fonts with whitespaces in their names.
Upvotes: 1
Reputation: 2998
I wrote a bash script that fetches the CSS file on Google's servers with different user agents, downloads the different font formats to a local directory and writes a CSS file including them. Note that the script needs Bash version 4.x.
See https://neverpanic.de/blog/2014/03/19/downloading-google-web-fonts-for-local-hosting/ for the script (I'm not reproducing it here so I only have to update it in one place when I need to).
Edit: Moved to https://github.com/neverpanic/google-font-download
Upvotes: 65
Reputation: 585
I have a script written in PHP similar to that of @neverpanic that automatically downloads both the CSS and fonts (both hinted and unhinted) from Google. It then serves the correct CSS and fonts from your own server based on the User Agent. It keeps its own cache, so fonts and CSS of a User Agent will only be downloaded once.
It's in a premature stage, but it can be found here: DaAwesomeP / php-offline-fonts
Upvotes: 3
Reputation: 1019
The contents of the CSS file (from the include URL) depends on what browser I view it from. For example, when browsing to http://fonts.googleapis.com/css?family=Open+Sans using Chrome, the file only contained WOFF links. Using Internet Explorer (below), it included both EOT and WOFF. I pasted all the links into my browser to download them.
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: url(http://themes.googleusercontent.com/static/fonts/opensans/v6/cJZKeOuBrn4kERxqtaUH3fY6323mHUZFJMgTvxaG2iE.eot);
src: local('Open Sans'), local('OpenSans'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/cJZKeOuBrn4kERxqtaUH3fY6323mHUZFJMgTvxaG2iE.eot) format('embedded-opentype'), url(http://themes.googleusercontent.com/static/fonts/opensans/v6/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff) format('woff');
}
When you host your own web fonts, you need to correctly link to each font type, deal with legacy browser bugs, etc. When you use Google Web Fonts (hosted by Google), Google automatically links to the correct font types for that browser.
Upvotes: 14