diff --git a/projects/packages/google-fonts-provider/.gitattributes b/projects/packages/google-fonts-provider/.gitattributes
new file mode 100644
index 0000000000000..9f76af3f06e7e
--- /dev/null
+++ b/projects/packages/google-fonts-provider/.gitattributes
@@ -0,0 +1,16 @@
+# Files not needed to be distributed in the package.
+.gitattributes export-ignore
+.github/ export-ignore
+package.json export-ignore
+
+# Files to include in the mirror repo, but excluded via gitignore
+# Remember to end all directories with `/**` to properly tag every file.
+# /src/js/example.min.js production-include
+
+# Files to exclude from the mirror repo, but included in the monorepo.
+# Remember to end all directories with `/**` to properly tag every file.
+.gitignore production-exclude
+changelog/** production-exclude
+phpunit.xml.dist production-exclude
+.phpcs.dir.xml production-exclude
+tests/** production-exclude
diff --git a/projects/packages/google-fonts-provider/.gitignore b/projects/packages/google-fonts-provider/.gitignore
new file mode 100644
index 0000000000000..140fd587d2d52
--- /dev/null
+++ b/projects/packages/google-fonts-provider/.gitignore
@@ -0,0 +1,2 @@
+vendor/
+node_modules/
diff --git a/projects/packages/google-fonts-provider/.phpcs.dir.xml b/projects/packages/google-fonts-provider/.phpcs.dir.xml
new file mode 100644
index 0000000000000..c76dbaee826cc
--- /dev/null
+++ b/projects/packages/google-fonts-provider/.phpcs.dir.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/packages/google-fonts-provider/CHANGELOG.md b/projects/packages/google-fonts-provider/CHANGELOG.md
new file mode 100644
index 0000000000000..721294abd00ad
--- /dev/null
+++ b/projects/packages/google-fonts-provider/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
diff --git a/projects/packages/google-fonts-provider/README.md b/projects/packages/google-fonts-provider/README.md
new file mode 100644
index 0000000000000..3a884b36f31ec
--- /dev/null
+++ b/projects/packages/google-fonts-provider/README.md
@@ -0,0 +1,20 @@
+# google-fonts-provider
+
+WordPress Webfonts provider for Google Fonts
+
+## How to install google-fonts-provider
+
+### Installation From Git Repo
+
+## Contribute
+
+## Get Help
+
+## Security
+
+Need to report a security vulnerability? Go to [https://automattic.com/security/](https://automattic.com/security/) or directly to our security bug bounty site [https://hackerone.com/automattic](https://hackerone.com/automattic).
+
+## License
+
+google-fonts-provider is licensed under [GNU General Public License v2 (or later)](./LICENSE.txt)
+
diff --git a/projects/packages/google-fonts-provider/changelog/.gitkeep b/projects/packages/google-fonts-provider/changelog/.gitkeep
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/projects/packages/google-fonts-provider/composer.json b/projects/packages/google-fonts-provider/composer.json
new file mode 100644
index 0000000000000..2fcdc56532ebe
--- /dev/null
+++ b/projects/packages/google-fonts-provider/composer.json
@@ -0,0 +1,44 @@
+{
+ "name": "automattic/jetpack-google-fonts-provider",
+ "description": "WordPress Webfonts provider for Google Fonts",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "require": {},
+ "require-dev": {
+ "yoast/phpunit-polyfills": "1.0.3",
+ "automattic/jetpack-changelogger": "^3.0"
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "scripts": {
+ "phpunit": [
+ "./vendor/phpunit/phpunit/phpunit --colors=always"
+ ],
+ "test-coverage": [
+ "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
+ ],
+ "test-php": [
+ "@composer phpunit"
+ ]
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../../packages/*",
+ "options": {
+ "monorepo": true
+ }
+ }
+ ],
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.1.x-dev"
+ },
+ "textdomain": "jetpack-google-fonts-provider"
+ }
+}
diff --git a/projects/packages/google-fonts-provider/package.json b/projects/packages/google-fonts-provider/package.json
new file mode 100644
index 0000000000000..cdb9d61bb3c1e
--- /dev/null
+++ b/projects/packages/google-fonts-provider/package.json
@@ -0,0 +1,29 @@
+{
+ "private": true,
+ "name": "@automattic/jetpack-google-fonts-provider",
+ "version": "0.1.0-alpha",
+ "description": "WordPress Webfonts provider for Google Fonts",
+ "homepage": "https://jetpack.com",
+ "bugs": {
+ "url": "https://github.com/Automattic/jetpack/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Automattic/jetpack.git"
+ },
+ "license": "GPL-2.0-or-later",
+ "author": "Automattic",
+ "scripts": {
+ "build": "echo 'Not implemented.",
+ "build-js": "echo 'Not implemented.",
+ "build-production": "echo 'Not implemented.",
+ "build-production-js": "echo 'Not implemented.",
+ "clean": "true"
+ },
+ "devDependencies": {},
+ "engines": {
+ "node": "^14.18.3 || ^16.13.2",
+ "pnpm": "^6.23.6",
+ "yarn": "use pnpm instead - see docs/yarn-upgrade.md"
+ }
+}
diff --git a/projects/packages/google-fonts-provider/phpunit.xml.dist b/projects/packages/google-fonts-provider/phpunit.xml.dist
new file mode 100644
index 0000000000000..3223c32458db2
--- /dev/null
+++ b/projects/packages/google-fonts-provider/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+ tests/php
+
+
+
+
+
+
+ src
+
+
+
diff --git a/projects/packages/google-fonts-provider/src/class-google-fonts-provider.php b/projects/packages/google-fonts-provider/src/class-google-fonts-provider.php
new file mode 100644
index 0000000000000..04340986a9f67
--- /dev/null
+++ b/projects/packages/google-fonts-provider/src/class-google-fonts-provider.php
@@ -0,0 +1,343 @@
+get_remote_styles( $url, $args );
+
+ /*
+ * Early return if the request failed.
+ * Cache an empty string for 60 seconds to avoid bottlenecks.
+ */
+ if ( empty( $css ) ) {
+ set_site_transient( $id, '', MINUTE_IN_SECONDS );
+ return '';
+ }
+
+ // Cache the CSS for a month.
+ set_site_transient( $id, $css, MONTH_IN_SECONDS );
+ }
+
+ return $css;
+ }
+
+ /**
+ * Gets styles from the remote font service via the given URL.
+ *
+ * @param string $url The URL to fetch.
+ * @param array $args Optional. The arguments to pass to `wp_remote_get()`.
+ * Default empty array.
+ * @return string The styles on success. Empty string on failure.
+ */
+ protected function get_remote_styles( $url, array $args = array() ) {
+ // Use a modern user-agent, to get woff2 files.
+ $args['user-agent'] = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0';
+
+ // Get the remote URL contents.
+ $response = wp_safe_remote_get( $url, $args );
+
+ // Early return if the request failed.
+ if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
+ return '';
+ }
+
+ // Get the response body.
+ return wp_remote_retrieve_body( $response );
+ }
+
+ /**
+ * Gets the `@font-face` CSS styles for Google Fonts.
+ *
+ * This method does the following processing tasks:
+ * 1. Orchestrates an optimized Google Fonts API URL for each font-family.
+ * 2. Caches each URL, if not already cached.
+ * 3. Does a remote request to the Google Fonts API service to fetch the styles.
+ * 4. Generates the `@font-face` for all its webfonts.
+ *
+ * @return string The `@font-face` CSS.
+ */
+ public function get_css() {
+ $css = '';
+ $urls = $this->build_collection_api_urls();
+
+ foreach ( $urls as $url ) {
+ $css .= $this->get_cached_remote_styles( 'google_fonts_' . md5( $url ), $url );
+ }
+
+ return $css;
+ }
+
+ /**
+ * Builds the Google Fonts URL for a collection of webfonts.
+ *
+ * For example, if given the following webfonts:
+ * ```
+ * array(
+ * array(
+ * 'font-family' => 'Source Serif Pro',
+ * 'font-style' => 'normal',
+ * 'font-weight' => '200 400',
+ * ),
+ * array(
+ * 'font-family' => 'Source Serif Pro',
+ * 'font-style' => 'italic',
+ * 'font-weight' => '400 600',
+ * ),
+ * )
+ * ```
+ * then the returned collection would be:
+ * ```
+ * array(
+ * 'https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,200;0,300;0,400;1,400;1,500;1,600&display=fallback'
+ * )
+ * ```
+ *
+ * @return array Collection of font-family urls.
+ */
+ private function build_collection_api_urls() {
+ $font_families_urls = array();
+
+ /*
+ * Iterate over each font-family group to build the Google Fonts API URL
+ * for that specific family. Each is added to the collection of URLs to be
+ * returned to the `get_css()` method for making the remote request.
+ */
+ foreach ( $this->organize_webfonts() as $font_display => $font_families ) {
+ $url_parts = array();
+ foreach ( $font_families as $font_family => $webfonts ) {
+ list( $normal_weights, $italic_weights ) = $this->collect_font_weights( $webfonts );
+
+ // Build the font-style with its font-weights.
+ $url_part = rawurlencode( $font_family );
+ if ( empty( $italic_weights ) && ! empty( $normal_weights ) ) {
+ $url_part .= ':wght@' . implode( ';', $normal_weights );
+ } elseif ( ! empty( $italic_weights ) && empty( $normal_weights ) ) {
+ $url_part .= ':ital,wght@1,' . implode( ';', $normal_weights );
+ } elseif ( ! empty( $italic_weights ) && ! empty( $normal_weights ) ) {
+ $url_part .= ':ital,wght@0,' . implode( ';0,', $normal_weights ) . ';1,' . implode( ';1,', $italic_weights );
+ }
+
+ // Add it to the collection.
+ $url_parts[] = $url_part;
+ }
+
+ // Build the URL for this font-family and add it to the collection.
+ $font_families_urls[] = $this->root_url . '?family=' . implode( '&family=', $url_parts ) . '&display=' . $font_display;
+ }
+
+ return $font_families_urls;
+ }
+
+ /**
+ * Organizes the webfonts by font-display and then font-family.
+ *
+ * To optimizing building the URL for the Google Fonts API request,
+ * this method organizes the webfonts first by font-display and then
+ * by font-family.
+ *
+ * For example, if given the following webfonts:
+ * ```
+ * array(
+ * array(
+ * 'font-family' => 'Source Serif Pro',
+ * 'font-style' => 'normal',
+ * 'font-weight' => '200 400',
+ * ),
+ * array(
+ * 'font-family' => 'Source Serif Pro',
+ * 'font-style' => 'italic',
+ * 'font-weight' => '400 600',
+ * ),
+ * )
+ * ```
+ * then the returned collection would be:
+ * ```
+ * array(
+ * 'fallback' => array(
+ * 'Source Serif Pro' => array(
+ * array(
+ * 'font-family' => 'Source Serif Pro',
+ * 'font-style' => 'normal',
+ * 'font-weight' => '200 400',
+ * ),
+ * array(
+ * 'font-family' => 'Source Serif Pro',
+ * 'font-style' => 'italic',
+ * 'font-weight' => '400 600',
+ * ),
+ * ),
+ * ),
+ * )
+ *
+ * @return array[][] Webfonts organized by font-display and then font-family.
+ */
+ private function organize_webfonts() {
+ $font_display_groups = array();
+
+ /*
+ * Group by font-display.
+ * Each font-display will need to be a separate request.
+ */
+ foreach ( $this->webfonts as $webfont ) {
+ if ( ! isset( $font_display_groups[ $webfont['font-display'] ] ) ) {
+ $font_display_groups[ $webfont['font-display'] ] = array();
+ }
+ $font_display_groups[ $webfont['font-display'] ][] = $webfont;
+ }
+
+ /*
+ * Iterate over each font-display group and group by font-family.
+ * Multiple font-families can be combined in the same request,
+ * but their params need to be grouped.
+ */
+ foreach ( $font_display_groups as $font_display => $font_display_group ) {
+ $font_families = array();
+
+ foreach ( $font_display_group as $webfont ) {
+ if ( ! isset( $font_families[ $webfont['font-family'] ] ) ) {
+ $font_families[ $webfont['font-family'] ] = array();
+ }
+ $font_families[ $webfont['font-family'] ][] = $webfont;
+ }
+
+ $font_display_groups[ $font_display ] = $font_families;
+ }
+
+ return $font_display_groups;
+ }
+
+ /**
+ * Collects all font-weights grouped by 'normal' and 'italic' font-style.
+ *
+ * For example, if given the following webfonts:
+ * ```
+ * array(
+ * array(
+ * 'font-family' => 'Source Serif Pro',
+ * 'font-style' => 'normal',
+ * 'font-weight' => '200 400',
+ * ),
+ * array(
+ * 'font-family' => 'Source Serif Pro',
+ * 'font-style' => 'italic',
+ * 'font-weight' => '400 600',
+ * ),
+ * )
+ * ```
+ * Then the returned collection would be:
+ * ```
+ * array(
+ * array( 200, 300, 400 ),
+ * array( 400, 500, 600 ),
+ * )
+ * ```
+ *
+ * @param array $webfonts Webfonts to process.
+ * @return array[] {
+ * The font-weights grouped by font-style.
+ *
+ * @type array $normal_weights Individual font-weight values for 'normal' font-style.
+ * @type array $italic_weights Individual font-weight values for 'italic' font-style.
+ * }
+ */
+ private function collect_font_weights( array $webfonts ) {
+ $normal_weights = array();
+ $italic_weights = array();
+
+ foreach ( $webfonts as $webfont ) {
+ $font_weights = $this->get_font_weights( $webfont['font-weight'] );
+ // Skip this webfont if it does not have a font-weight defined.
+ if ( empty( $font_weights ) ) {
+ continue;
+ }
+
+ // Add the individual font-weights to the end of font-style array.
+ if ( 'italic' === $webfont['font-style'] ) {
+ array_push( $italic_weights, ...$font_weights );
+ } else {
+ array_push( $normal_weights, ...$font_weights );
+ }
+ }
+
+ // Remove duplicates.
+ $normal_weights = array_unique( $normal_weights );
+ $italic_weights = array_unique( $italic_weights );
+
+ return array( $normal_weights, $italic_weights );
+ }
+
+ /**
+ * Converts the given string of font-weight into an array of individual weight values.
+ *
+ * When given a single font-weight, the value is wrapped into an array.
+ *
+ * A range of font-weights is specified as '400 600' with the lightest value first,
+ * a space, and then the heaviest value last.
+ *
+ * When given a range of font-weight values, the range is converted into individual
+ * font-weight values. For example, a range of '400 600' is converted into
+ * `array( 400, 500, 600 )`.
+ *
+ * @param string $font_weights The font-weights string.
+ * @return array The font-weights array.
+ */
+ private function get_font_weights( $font_weights ) {
+ $font_weights = trim( $font_weights );
+
+ // A single font-weight.
+ if ( false === strpos( $font_weights, ' ' ) ) {
+ return array( $font_weights );
+ }
+
+ // Process a range of font-weight values that are delimited by ' '.
+ $font_weights = explode( ' ', $font_weights );
+
+ // If there are 2 values, treat them as a range.
+ if ( 2 === count( $font_weights ) ) {
+ $font_weights = range( (int) $font_weights[0], (int) $font_weights[1], 100 );
+ }
+
+ return $font_weights;
+ }
+}
diff --git a/projects/packages/google-fonts-provider/tests/php/bootstrap.php b/projects/packages/google-fonts-provider/tests/php/bootstrap.php
new file mode 100644
index 0000000000000..46763b04a2cdb
--- /dev/null
+++ b/projects/packages/google-fonts-provider/tests/php/bootstrap.php
@@ -0,0 +1,11 @@
+ $font_family,
+ 'provider' => 'google',
+ ),
+ )
+ );
+ }
+}
+add_action( 'after_setup_theme', 'jetpack_add_google_font_provider' );