Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Font Library: revised API design #57616

Closed
creativecoder opened this issue Jan 6, 2024 · 14 comments
Closed

Font Library: revised API design #57616

creativecoder opened this issue Jan 6, 2024 · 14 comments
Labels
[Feature] Font Library [Feature] Typography Font and typography-related issues and PRs Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Discussion For issues that are high-level and not yet ready to implement.

Comments

@creativecoder
Copy link
Contributor

creativecoder commented Jan 6, 2024

Companion issue to #55278

This issue outlines a proposed design for font management REST API endpoints.

Font Family

A font family with the settings used in theme.json format. A font family may have child font faces that are part of the family.

GET wp/v2/font-families

List the installed font families.

Font family theme.json properties are nested in font_family_settings to keep camel-cased properties out of the root level of the response, where snake-casing is required.

Request:

GET wp/v2/font-families

Response:

[
    {
        // ID of wp_font_family post
        "id": 1,
        "theme_json_version": 2,
        // Array of child font face ids
        "font_faces": [ 1007, 1008 ],
        // theme.json settings used under `settings.typography.fontFamilies`
        "font_family_settings": {
            "name": "Piazzolla",
            "fontFamily": "Piazzolla, serif",
            "slug": "piazzolla",
            "preview": "https://my-wp-site.com/wp-content/fonts/previews/piazzolla-400-normal.svg"
        },
        // See below for example `_links`
        "_links": { ... }
    },
    {
        "id": 2,
        "theme_json_version": 2,
        "font_faces": [ 1009, 1010 ],
        "font_family_settings": {
            "name": "Lobster",
            "fontFamily": "Lobster, sans-serif",
            "slug": "lobster",
            "preview": "https://my-wp-site.com/wp-content/fonts/previews/lobster-400-normal.svg"
        },
        "_links": { ... }
    },
    ...
]

GET wp/v2/font-families/<id>

Get a font family by id.

Request:

GET wp/v2/font-families/1

Response:

{
    "id": 1,
    "theme_json_version": 2,
    "font_faces": [ 258 ],
    "font_family_settings": {
        "name": "Piazzolla",
        "fontFamily": "Piazzolla, serif",
        "slug": "piazzolla"
    },
    "_links": {
        "self": [{
            "href": "https://my-wp-site.com/wp-json/wp/v2/font-families/1"
        }],
        "collection": [{
            "href": "https://my-wp-site.com/wp-json/wp/v2/font-families"
        }],
        "font_faces": [{
            "embeddable": true,
            "href": "https://my-wp-site.com/wp-json/wp/v2/font-families/1/font-faces"
        }]
    }
}

Using embeddings to get font face information, as well.

Request:

GET wp/v2/font-families/1?_embed=true
GET wp/v2/font-families/1?_embed=font_faces

Response:

{
    "id": 1,
    "theme_json_version": 2,
    "font_faces": [ 258 ],
    "font_family_settings": {
        "name": "Piazzolla",
        "fontFamily": "Piazzolla, serif",
        "slug": "piazzolla",
        "preview": "https://my-wp-site.com/wp-content/fonts/previews/piazzolla-400-normal.svg"
    },
    "_links": { ... },
    "_embedded": {
        "font_faces": [
            {
                "id": 258,
                "theme_json_version": 2,
                "parent": "1",
                "font_face_settings": {
                    "fontStyle": "normal",
                    "fontWeight":  "400",
                    "fontFamily": "Piazzolla",
                    "src": [
                         "https://my-wp-site.com/wp-content/fonts/piazzolla-400-normal.ttf",
                         "https://my-wp-site.com/wp-content/fonts/piazzolla-400-normal.woff2"
                    ],
                    "preview": "https://my-wp-site.com/wp-content/fonts/previews/piazzolla-400-normal.svg"
                }
            }
        ]
    }
}

POST wp/v2/font-families

Create a new font family.

Request:

POST wp/v2/font-families

{
    "theme_json_version": 2,
    "font_family_settings": {
        "name": "Piazzolla",
        "fontFamily": "Piazzolla, serif",
        "slug": "piazzolla"
    }
}

Response:

{
    "id": 1,
    "theme_json_version": 2,
    "font_faces": [],
    "font_family_settings": {
        "name": "Piazzolla",
        "fontFamily": "Piazzolla, serif",
        "slug": "piazzolla",
        "preview"
    },
    "_links": { ... }
}

POST wp/v2/font-families/<id>

Update font family by id. It merges the received data with the existing data in the font family post (the same way the wp/v2/posts endpoint works).

Request:

POST wp/v2/font-families/1

{
    "font_family_settings": {
        "fontFamily": "Piazzolla, times, serif",
    }
}

Response:

{
    "id": 1,
    "theme_json_version": 2,
    "font_faces": [ 258 ],
    "font_family_settings": {
        "name": "Piazzolla",
        "fontFamily": "Piazzolla, times, serif",
        "slug": "piazzolla"
    },
    "_links": { ... }
}

DELETE wp/v2/font-families/<id>

Delete font family by id. All child font faces and assets are deleted.

Request:

DELETE wp/v2/font-families/1

Response:

{
    "code": "rest_trash_not_supported",
    "message": "The post does not support trashing. Set 'force=true' to delete.",
    "data": { "status": 501 }
}

Request:

DELETE /font-families/1?force=true

Response:

{
    "deleted": true,
    "previous": {
        "id": 1,
        "theme_json_version": 2,
        "font_faces": [ 258 ],
        "font_family_settings": {
            "name": "Piazzolla",
            "fontFamily": "Piazzolla, times, serif",
            "slug": "piazzolla",
        }
    }
}

Font Face

Font faces that are used within a font family.

Font face theme.json properties are nested in font_face_settings to keep camel-cased properties out of the root level of the response, where snake-casing is required.

The API design uses the wp/v2/posts/<id>/revisions endpoints as a model, since that is also a nested route with a parent/child relationship.

GET wp/v2/font-families/<id>/font-faces

Get all of the font faces for a font family.

Request:

GET wp/v2/font-families/1/font-faces

Response:

[
    {
        // ID of the wp_font_face custom post type
        "id": 1835,
        "theme_json_version": 2,
        // ID of parent wp_font_family post
        "parent": "1",
        // theme.json settings used under `settings.typography.fontFamilies.fontFace`
        "font_face_settings": {
            "fontStyle": "normal",
            "fontWeight":  "400",
            "fontFamily": "Piazzolla",
            // src can be a string for a single font asset, or an array for multiple assets
            "src": [
                 "https://my-wp-site.com/wp-content/fonts/piazzolla-400-normal.ttf",
                 "https://my-wp-site.com/wp-content/fonts/piazzolla-400-normal.woff2"
            ],
            "preview": "https://my-wp-site.com/wp-content/fonts/previews/piazzolla-400-normal.svg"
        },
        "_links": { ... }
    },
    ...
]

GET wp/v2/font-families/<id>/font-faces/<id>

Get a single font face for a font family.

Request:

GET wp/v2/font-families/1/font-faces/1835

Response:

{
    "id": 1835,
    "theme_json_version": 2,
    "parent": "1",
    "font_face_settings": {
        "fontStyle": "normal",
        "fontWeight":  "400",
        "fontFamily": "Piazzolla",
        "src": [
             "https://my-wp-site.com/wp-content/fonts/piazzolla-400-normal.ttf",
             "https://my-wp-site.com/wp-content/fonts/piazzolla-400-normal.woff2"
        ],
        "preview": "https://my-wp-site.com/wp-content/fonts/previews/piazzolla-400-normal.svg"
    },
    "_links": {
        "self": [{
            "href": "https://my-wp-site.com/wp-json/wp/v2/font-families/1/font-faces/1835"
        }],
        "collection": [{
            "href": "https://my-wp-site.com/wp-json/wp/v2/font-families/1/font-faces"
        }],
        "parent": [{
            "href": "https://my-wp-site.com/wp-json/wp/v2/font-families/1"
        }]
    }
}

POST wp/v2/font-families/<id>/font-faces

Add a font face to an existing font family.

Creates one font face for a font family, stores its asset(s), and creates its preview(s). Currently, the font face assets can be added in 2 ways:

  • Provide a src that points to a binary file included in the request (multipart/form-data)
  • Provide a src property for the font face that points to a (local or remote) URL

The Content-Type is always multipart/form-data with a JSON string for the data, so that file uploads are supported when used.

Upload font face assets attached in the http request:

Request:

POST wp/v2/font-families/1/font-faces
Content-Type: multipart/form-data;

file-0-0 (binary)
file-0-1 (binary)
{
    "theme_json_version": 2,
    "font_face_settings": {
        "fontStyle": "normal",
        "fontWeight": "400",
        "fontFamily": "Piazzolla",
        // ... can include any font face properties in theme.json
        "src": [
            "file-0-0",
            "file-0-1"
        ]
    }
}

Response:

{
    "id": 1835,
    "theme_json_version": 2,
    "parent": "1",
    "font_face_settings": {
        "fontStyle": "normal",
        "fontWeight":  "400",
        "fontFamily": "Piazzolla",
        "src": [
                "https://my-wp-site.com/wp-content/fonts/piazzolla-400-normal.ttf",
                "https://my-wp-site.com/wp-content/fonts/piazzolla-400-normal.woff2"
         ],
         "preview": ""
    }
}

Install font definition without processing src:

Request:

POST wp/v2/font-families/1/font-faces
Content-Type: multipart/form-data;

{
    "theme_json_version": 2,
    "font_face_settings": {
        "fontStyle": "normal",
        "fontWeight":  "400",
        "fontFamily": "Piazzolla",
        "src": "https://example.com/fonts/piazzolla-400-normal.ttf"
    }
}

Response:

{
    "id": 1835,
    "parent": "1",
    "theme_json_version": 2,
    "font_face_settings": {
        "fontStyle": "normal",
        "fontWeight":  "400",
        "fontFamily": "Piazzolla",
        "src": "https://example.com/fonts/piazzolla-400-normal.ttf",
        "preview": "https://example.com/fonts/piazzolla-400-normal.ttf"
    }
}

DELETE wp/v2/font-families/<id>/font-faces/<id>

Delete one font face from a font family.

Request:

DELETE wp/v2/font-families/1/font-faces/462?force=true

Response:

{
    "deleted": true,
    "previous" : {
        "id": 462,
        "theme_json_version": 2,
        "parent": 1,
        "font_face_settings": {
            "fontStyle": "normal",
            "fontWeight": "400",
            "fontFamily": "Piazzolla",
            "src": "https://my-wp-site.com/wp-content/fonts/piazzolla-400-normal.ttf",
            "preview": "https://my-wp-site.com/wp-content/fonts/previews/piazzolla-400-normal.ttf"
        }
    }
}

Font Collections

A font collection is a list of font family definitions that can be installed.

GET wp/v2/font-collections

List font collections

Request:

GET wp/v2/font-collections

Response:

[
    {
        "slug": "collection-example",
        "name": "Collection Example",
        "description": "This a Font Collection Example",
        "font_families": [
	    {
                "categories": [ "sans-serif" ],
                    "font_family_settings": {
                    "name": "Roboto",
                    "fontFamily": "Roboto, sans-serif",
                    "slug": "roboto",
                    "category": "sans-serif",
                    "fontFace": [
                        {
                            "src": "https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf",
                            "fontWeight": "400",
                            "fontStyle": "normal",
                            "fontFamily": "Roboto",
                            "preview": "https://s.w.org/images/fonts/16.7/previews/roboto/roboto-400-normal.svg"
                        },
                        {
                            "src": "https://fonts.gstatic.com/s/roboto/v30/KFOiCnqEu92Fr1Mu51QrIzcXLsnzjYk.ttf",
                            "fontWeight": "400",
                            "fontStyle": "italic",
                            "fontFamily": "Roboto",
                            "preview": "https://s.w.org/images/fonts/16.7/previews/roboto/roboto-400-italic.svg"
                        }
                    ]
                }
            }
        ],
        "categories": [
            {
                "slug": "sans-serif",
                "name": "Sans Serif"
            }
        ]
    },
    {
        "slug": "default-font-collection",
        "name": "Google Fonts",
        "description": "Google Fonts collection.",
        "font_families": [
            // font familiy objects
        ]
        "categories": [
            // category objects
        ]
    }
]

GET wp/v2/font-collections/

Get font collection by slug.

Request:

GET wp/v2/font-collections/collection-example

Response:

{
    "slug": "collection-example",
    "name": "Collection Example",
    "description": "Example font collection.",
    "theme_json_version": 2,
    "font_families": [
        {
            "categories": [ "sans-serif" ],
            "font_family_settings": {
                "name": "Roboto",
                "fontFamily": "Roboto, sans-serif",
                "slug": "roboto",
                "category": "sans-serif",
                "fontFace": [
                    {
                        "src": "https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1MmgWxPKTM1K9nz.ttf",
                        "fontWeight": "400",
                        "fontStyle": "normal",
                        "fontFamily": "Roboto",
                        "preview": "https://s.w.org/images/fonts/16.7/previews/roboto/roboto-400-normal.svg"
                    },
                    {
                        "src": "https://fonts.gstatic.com/s/roboto/v30/KFOiCnqEu92Fr1Mu51QrIzcXLsnzjYk.ttf",
                        "fontWeight": "400",
                        "fontStyle": "italic",
                        "fontFamily": "Roboto",
                        "preview": "https://s.w.org/images/fonts/16.7/previews/roboto/roboto-400-italic.svg"
                    },
                ]
            }
        }
    ],
    "categories": [
        {
            "slug": "sans-serif",
            "name": "Sans Serif"
        }
    ]
}

Activating Font Families and Font Faces

Activating a font family and/or font faces is done by directly updating Global Styles using the existing endpoint (wp/v2/global-styles), since adding the fonts to the Global Styles settings is what adds them as typography options for styles across the site.

@creativecoder creativecoder added Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Discussion For issues that are high-level and not yet ready to implement. [Feature] Typography Font and typography-related issues and PRs labels Jan 6, 2024
@matiasbenedetto
Copy link
Contributor

matiasbenedetto commented Jan 10, 2024

This is the new shape for the font families endpoints proposed.

@TimothyBJacobs @spacedmonkey @swissspidy @costdev @hellofromtonya @kadamwhite It would be important if you could take a look here and share your thoughts about this refactoring proposal.

@matiasbenedetto
Copy link
Contributor

I added the Font Collections API specs in the issue description.

@creativecoder
Copy link
Contributor Author

I removed the POST wp/v2/font-families/<id>/font-faces/<id> endpoint from the design. At least for now, it's clearer to delete an exiting font face and re-create a new one with modified settings, and avoid the complexity of detecting whether to replace src font files, or not, depending on the new settings.

@creativecoder
Copy link
Contributor Author

Noting here that we have the following endpoints implemented in a feature branch that can be reviewed directly:

GET wp/v2/font-families
GET wp/v2/font-families/<id>
POST wp/v2/font-families
POST wp/v2/font-families/<id>
DELETE wp/v2/font-families/<id>
GET wp/v2/font-families/<id>/font-faces
GET wp/v2/font-families/<id>/font-faces/<id>
POST wp/v2/font-families/<id>/font-faces
DELETE wp/v2/font-families/<id>/font-faces/<id>

We're still tweaking small bits as we update the client to work with the new API structure, but these should be in near final form.

Here's the feature branch PR: #57688

Feedback now is welcome, as this will be very close to what we plan to propose merging for WP 6.5 (after merging to Gutenberg trunk for 17.6).

@creativecoder
Copy link
Contributor Author

Noting that I've updated the font collection endpoints in the description to reflect that we're returning the full data for each collection in GET wp/v2/font-collections requests. The response can be customized using the _fields param.

@costdev
Copy link
Contributor

costdev commented Jan 26, 2024

@creativecoder Thanks for the ping! Just a few questions for clarity come to mind at the moment.

theme_json_version

Is there a benefit in having theme_json in this name, as opposed to calling it version? Is it for clarity, or is it intentional to prevent a foreseen clash in future, or something else?

font_family_settings
font_face_settings

I don't think that I saw any examples where both font_family_settings and font_face_settings would be on the same level in the same response. Could we just use settings for these since the context is clear from the endpoint and/or the font_families: { settings: {} } path, or is there a reason to be specific with these names?

GET wp/v2/font-collections/collection-example

In the example, the path is font_families[ 0 ] => { fontFace: [] } - Since it's an array of font faces, should this instead be fontFaces?

@creativecoder
Copy link
Contributor Author

Thanks @costdev for taking a look!

theme_json_version

Is there a benefit in having theme_json in this name, as opposed to calling it version? Is it for clarity, or is it intentional to prevent a foreseen clash in future, or something else?

It's for clarity. Font family and face definitions come directly from the theme.json schema, so this is meant to clarify it's the theme.json version, not the API namespace version, css spec level, or anything else.

font_family_settings
font_face_settings

I don't think that I saw any examples where both font_family_settings and font_face_settings would be on the same level in the same response. Could we just use settings for these since the context is clear from the endpoint and/or the font_families: { settings: {} } path, or is there a reason to be specific with these names?

That's correct, these 2 properties are not used in the same level of any responses. We could use settings in both cases and it should be clear from the placement within the response. This would also be closer to the theme.json schema and Global Styles request/response shape, which starts with settings:
settings: { typography: { fontFamilies: [ { fontFace: [ {} ] } ] } }

GET wp/v2/font-collections/collection-example

In the example, the path is font_families[ 0 ] => { fontFace: [] } - Since it's an array of font faces, should this instead be fontFaces?

That would make sense, though the theme.json schema is already using fontFace, so it's that way here too, for consistency.

@peterwilsoncc
Copy link
Contributor

My understanding is that this was discussed and architectured on internal A8C systems for Team Pixel. I think this is a good demonstration of why even non-Automatticians felt the editor chats could be canceled.

Further discussions like this are examples of issues that ought to be discussed publicly, either on the make/core P2 or - at the least - in GitHub discussions on this repo with a call to action on make/core.

@priethor
Copy link
Contributor

Hi Peter, thanks for the feedback 🙏 . A make/core post for increased awareness could have been more helpful. On the other hand, how would a GitHub discussion make it more public?

@peterwilsoncc
Copy link
Contributor

I'm talking exclusively of the architecture discussions that took place on the Team Pixel P2 and in the team's docs in the period prior to this issue being opened. A significant number of contributors do not have access to these discussions so discussing them is this repo or on make/core will open the discussions up to all contributors, not just those sponsored by Automattic.

@annezazu
Copy link
Contributor

annezazu commented Feb 5, 2024

Since this has been merged #57688 are we able to move this out or through the 6.5 board? It's unclear to me if any discussion will continue here or what the role of this issue is at this point. Happy to keep it open! Just want clarity and for the board to be as up to date as possible.

@creativecoder
Copy link
Contributor Author

Thanks for asking @annezazu ! Personally, I'd like to keep this issue open, at least until after the Core PR for the revised Font Library REST API implementation is opened (should be very soon) and has received feedback.

If there are any revisions needed in the API design based on that feedback, I think it would be good to track that here for continuity.

@getdave
Copy link
Contributor

getdave commented Feb 12, 2024

hi @creativecoder Are you happy you've received feedback for the REST API yet? If not please let me know and I will reach out for input. Thank you.

@creativecoder
Copy link
Contributor Author

Now that the API endpoints have been merged to Core, I'll close this issue. We can open separate issues if there are specific things that need to be addressed. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Font Library [Feature] Typography Font and typography-related issues and PRs Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Type] Discussion For issues that are high-level and not yet ready to implement.
Projects
Status: Done
Development

No branches or pull requests

8 participants