Browse Source

Intermediate changes
commit_hash:f4cb1bdccfb534d71b7f461fc8f8e5656c47bfa5

robot-piglet 1 month ago
parent
commit
6b7c255668

+ 12 - 1
contrib/python/fonttools/.dist-info/METADATA

@@ -1,6 +1,6 @@
 Metadata-Version: 2.2
 Metadata-Version: 2.2
 Name: fonttools
 Name: fonttools
-Version: 4.55.4
+Version: 4.55.6
 Summary: Tools to manipulate font files
 Summary: Tools to manipulate font files
 Home-page: http://github.com/fonttools/fonttools
 Home-page: http://github.com/fonttools/fonttools
 Author: Just van Rossum
 Author: Just van Rossum
@@ -389,6 +389,17 @@ Have fun!
 Changelog
 Changelog
 ~~~~~~~~~
 ~~~~~~~~~
 
 
+4.55.6 (released 2025-01-24)
+----------------------------
+
+- [glyf] Fixed regression introduced in 4.55.5 when computing bounds of nested composite glyphs  with transformed components (#3752).
+
+4.55.5 (released 2025-01-23)
+----------------------------
+
+- [glyf] Fixed recalcBounds of transformed components with unrounded coordinates (#3750).
+- [feaLib] Allow duplicate script/language statements (#3749).
+
 4.55.4 (released 2025-01-21)
 4.55.4 (released 2025-01-21)
 ----------------------------
 ----------------------------
 
 

+ 1 - 1
contrib/python/fonttools/fontTools/__init__.py

@@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 
 
-version = __version__ = "4.55.4"
+version = __version__ = "4.55.6"
 
 
 __all__ = ["version", "log", "configLogger"]
 __all__ = ["version", "log", "configLogger"]

+ 7 - 1
contrib/python/fonttools/fontTools/feaLib/builder.py

@@ -1106,7 +1106,13 @@ class Builder(object):
         if (language == "dflt" or include_default) and lookups:
         if (language == "dflt" or include_default) and lookups:
             self.features_[key] = lookups[:]
             self.features_[key] = lookups[:]
         else:
         else:
-            self.features_[key] = []
+            # if we aren't including default we need to manually remove the
+            # default lookups, which were added to all declared langsystems
+            # as they were encountered (we don't remove all lookups because
+            # we want to allow duplicate script/lang statements;
+            # see https://github.com/fonttools/fonttools/issues/3748
+            cur_lookups = self.features_.get(key, [])
+            self.features_[key] = [x for x in cur_lookups if x not in lookups]
         self.language_systems = frozenset([(self.script_, language)])
         self.language_systems = frozenset([(self.script_, language)])
 
 
         if required:
         if required:

+ 30 - 6
contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py

@@ -1187,7 +1187,7 @@ class Glyph(object):
         ):
         ):
             return
             return
         try:
         try:
-            coords, endPts, flags = self.getCoordinates(glyfTable)
+            coords, endPts, flags = self.getCoordinates(glyfTable, round=otRound)
             self.xMin, self.yMin, self.xMax, self.yMax = coords.calcIntBounds()
             self.xMin, self.yMin, self.xMax, self.yMax = coords.calcIntBounds()
         except NotImplementedError:
         except NotImplementedError:
             pass
             pass
@@ -1206,9 +1206,7 @@ class Glyph(object):
         Return True if bounds were calculated, False otherwise.
         Return True if bounds were calculated, False otherwise.
         """
         """
         for compo in self.components:
         for compo in self.components:
-            if hasattr(compo, "firstPt") or hasattr(compo, "transform"):
-                return False
-            if not float(compo.x).is_integer() or not float(compo.y).is_integer():
+            if not compo._hasOnlyIntegerTranslate():
                 return False
                 return False
 
 
         # All components are untransformed and have an integer x/y translate
         # All components are untransformed and have an integer x/y translate
@@ -1241,7 +1239,7 @@ class Glyph(object):
         else:
         else:
             return self.numberOfContours == -1
             return self.numberOfContours == -1
 
 
-    def getCoordinates(self, glyfTable):
+    def getCoordinates(self, glyfTable, *, round=noRound):
         """Return the coordinates, end points and flags
         """Return the coordinates, end points and flags
 
 
         This method returns three values: A :py:class:`GlyphCoordinates` object,
         This method returns three values: A :py:class:`GlyphCoordinates` object,
@@ -1267,13 +1265,27 @@ class Glyph(object):
             for compo in self.components:
             for compo in self.components:
                 g = glyfTable[compo.glyphName]
                 g = glyfTable[compo.glyphName]
                 try:
                 try:
-                    coordinates, endPts, flags = g.getCoordinates(glyfTable)
+                    coordinates, endPts, flags = g.getCoordinates(
+                        glyfTable, round=round
+                    )
                 except RecursionError:
                 except RecursionError:
                     raise ttLib.TTLibError(
                     raise ttLib.TTLibError(
                         "glyph '%s' contains a recursive component reference"
                         "glyph '%s' contains a recursive component reference"
                         % compo.glyphName
                         % compo.glyphName
                     )
                     )
                 coordinates = GlyphCoordinates(coordinates)
                 coordinates = GlyphCoordinates(coordinates)
+                # if asked to round e.g. while computing bboxes, it's important we
+                # do it immediately before a component transform is applied to a
+                # simple glyph's coordinates in case these might still contain floats;
+                # however, if the referenced component glyph is another composite, we
+                # must not round here but only at the end, after all the nested
+                # transforms have been applied, or else rounding errors will compound.
+                if (
+                    round is not noRound
+                    and g.numberOfContours > 0
+                    and not compo._hasOnlyIntegerTranslate()
+                ):
+                    coordinates.toInt(round=round)
                 if hasattr(compo, "firstPt"):
                 if hasattr(compo, "firstPt"):
                     # component uses two reference points: we apply the transform _before_
                     # component uses two reference points: we apply the transform _before_
                     # computing the offset between the points
                     # computing the offset between the points
@@ -1930,6 +1942,18 @@ class GlyphComponent(object):
         result = self.__eq__(other)
         result = self.__eq__(other)
         return result if result is NotImplemented else not result
         return result if result is NotImplemented else not result
 
 
+    def _hasOnlyIntegerTranslate(self):
+        """Return True if it's a 'simple' component.
+
+        That is, it has no anchor points and no transform other than integer translate.
+        """
+        return (
+            not hasattr(self, "firstPt")
+            and not hasattr(self, "transform")
+            and float(self.x).is_integer()
+            and float(self.y).is_integer()
+        )
+
 
 
 class GlyphCoordinates(object):
 class GlyphCoordinates(object):
     """A list of glyph coordinates.
     """A list of glyph coordinates.

+ 1 - 1
contrib/python/fonttools/ya.make

@@ -2,7 +2,7 @@
 
 
 PY3_LIBRARY()
 PY3_LIBRARY()
 
 
-VERSION(4.55.4)
+VERSION(4.55.6)
 
 
 LICENSE(MIT)
 LICENSE(MIT)
 
 

+ 1 - 1
contrib/python/google-auth/py3/.dist-info/METADATA

@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Metadata-Version: 2.1
 Name: google-auth
 Name: google-auth
-Version: 2.37.0
+Version: 2.38.0
 Summary: Google Authentication Library
 Summary: Google Authentication Library
 Home-page: https://github.com/googleapis/google-auth-library-python
 Home-page: https://github.com/googleapis/google-auth-library-python
 Author: Google Cloud Platform
 Author: Google Cloud Platform

+ 22 - 0
contrib/python/google-auth/py3/google/auth/_default.py

@@ -85,6 +85,17 @@ def load_credentials_from_file(
     user credentials, external account credentials, or impersonated service
     user credentials, external account credentials, or impersonated service
     account credentials.
     account credentials.
 
 
+    .. warning::
+        Important: If you accept a credential configuration (credential JSON/File/Stream)
+        from an external source for authentication to Google Cloud Platform, you must
+        validate it before providing it to any Google API or client library. Providing an
+        unvalidated credential configuration to Google APIs or libraries can compromise
+        the security of your systems and data. For more information, refer to
+        `Validate credential configurations from external sources`_.
+
+        .. _Validate credential configurations from external sources:
+            https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
+
     Args:
     Args:
         filename (str): The full path to the credentials file.
         filename (str): The full path to the credentials file.
         scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
         scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
@@ -137,6 +148,17 @@ def load_credentials_from_dict(
     user credentials, external account credentials, or impersonated service
     user credentials, external account credentials, or impersonated service
     account credentials.
     account credentials.
 
 
+    .. warning::
+        Important: If you accept a credential configuration (credential JSON/File/Stream)
+        from an external source for authentication to Google Cloud Platform, you must
+        validate it before providing it to any Google API or client library. Providing an
+        unvalidated credential configuration to Google APIs or libraries can compromise
+        the security of your systems and data. For more information, refer to
+        `Validate credential configurations from external sources`_.
+
+    .. _Validate credential configurations from external sources:
+        https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
+
     Args:
     Args:
         info (Dict[str, Any]): A dict object containing the credentials
         info (Dict[str, Any]): A dict object containing the credentials
         scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
         scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If

+ 10 - 2
contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py

@@ -201,7 +201,7 @@ def get(
     url = _helpers.update_query(base_url, query_params)
     url = _helpers.update_query(base_url, query_params)
 
 
     backoff = ExponentialBackoff(total_attempts=retry_count)
     backoff = ExponentialBackoff(total_attempts=retry_count)
-
+    failure_reason = None
     for attempt in backoff:
     for attempt in backoff:
         try:
         try:
             response = request(url=url, method="GET", headers=headers_to_use)
             response = request(url=url, method="GET", headers=headers_to_use)
@@ -213,6 +213,11 @@ def get(
                     retry_count,
                     retry_count,
                     response.status,
                     response.status,
                 )
                 )
+                failure_reason = (
+                    response.data.decode("utf-8")
+                    if hasattr(response.data, "decode")
+                    else response.data
+                )
                 continue
                 continue
             else:
             else:
                 break
                 break
@@ -225,10 +230,13 @@ def get(
                 retry_count,
                 retry_count,
                 e,
                 e,
             )
             )
+            failure_reason = e
     else:
     else:
         raise exceptions.TransportError(
         raise exceptions.TransportError(
             "Failed to retrieve {} from the Google Compute Engine "
             "Failed to retrieve {} from the Google Compute Engine "
-            "metadata service. Compute Engine Metadata server unavailable".format(url)
+            "metadata service. Compute Engine Metadata server unavailable due to {}".format(
+                url, failure_reason
+            )
         )
         )
 
 
     content = _helpers.from_bytes(response.data)
     content = _helpers.from_bytes(response.data)

+ 5 - 0
contrib/python/google-auth/py3/google/auth/iam.py

@@ -48,6 +48,11 @@ _IAM_SIGN_ENDPOINT = (
     + "/serviceAccounts/{}:signBlob"
     + "/serviceAccounts/{}:signBlob"
 )
 )
 
 
+_IAM_SIGNJWT_ENDPOINT = (
+    "https://iamcredentials.googleapis.com/v1/projects/-"
+    + "/serviceAccounts/{}:signJwt"
+)
+
 _IAM_IDTOKEN_ENDPOINT = (
 _IAM_IDTOKEN_ENDPOINT = (
     "https://iamcredentials.googleapis.com/v1/"
     "https://iamcredentials.googleapis.com/v1/"
     + "projects/-/serviceAccounts/{}:generateIdToken"
     + "projects/-/serviceAccounts/{}:generateIdToken"

+ 100 - 1
contrib/python/google-auth/py3/google/auth/impersonated_credentials.py

@@ -38,12 +38,15 @@ from google.auth import exceptions
 from google.auth import iam
 from google.auth import iam
 from google.auth import jwt
 from google.auth import jwt
 from google.auth import metrics
 from google.auth import metrics
+from google.oauth2 import _client
 
 
 
 
 _REFRESH_ERROR = "Unable to acquire impersonated credentials"
 _REFRESH_ERROR = "Unable to acquire impersonated credentials"
 
 
 _DEFAULT_TOKEN_LIFETIME_SECS = 3600  # 1 hour in seconds
 _DEFAULT_TOKEN_LIFETIME_SECS = 3600  # 1 hour in seconds
 
 
+_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
+
 
 
 def _make_iam_token_request(
 def _make_iam_token_request(
     request,
     request,
@@ -177,6 +180,7 @@ class Credentials(
         target_principal,
         target_principal,
         target_scopes,
         target_scopes,
         delegates=None,
         delegates=None,
+        subject=None,
         lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
         lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
         quota_project_id=None,
         quota_project_id=None,
         iam_endpoint_override=None,
         iam_endpoint_override=None,
@@ -204,9 +208,12 @@ class Credentials(
             quota_project_id (Optional[str]): The project ID used for quota and billing.
             quota_project_id (Optional[str]): The project ID used for quota and billing.
                 This project may be different from the project used to
                 This project may be different from the project used to
                 create the credentials.
                 create the credentials.
-            iam_endpoint_override (Optiona[str]): The full IAM endpoint override
+            iam_endpoint_override (Optional[str]): The full IAM endpoint override
                 with the target_principal embedded. This is useful when supporting
                 with the target_principal embedded. This is useful when supporting
                 impersonation with regional endpoints.
                 impersonation with regional endpoints.
+            subject (Optional[str]): sub field of a JWT. This field should only be set
+                if you wish to impersonate as a user. This feature is useful when
+                using domain wide delegation.
         """
         """
 
 
         super(Credentials, self).__init__()
         super(Credentials, self).__init__()
@@ -231,6 +238,7 @@ class Credentials(
         self._target_principal = target_principal
         self._target_principal = target_principal
         self._target_scopes = target_scopes
         self._target_scopes = target_scopes
         self._delegates = delegates
         self._delegates = delegates
+        self._subject = subject
         self._lifetime = lifetime or _DEFAULT_TOKEN_LIFETIME_SECS
         self._lifetime = lifetime or _DEFAULT_TOKEN_LIFETIME_SECS
         self.token = None
         self.token = None
         self.expiry = _helpers.utcnow()
         self.expiry = _helpers.utcnow()
@@ -275,6 +283,39 @@ class Credentials(
         # Apply the source credentials authentication info.
         # Apply the source credentials authentication info.
         self._source_credentials.apply(headers)
         self._source_credentials.apply(headers)
 
 
+        #  If a subject is specified a domain-wide delegation auth-flow is initiated
+        #  to impersonate as the provided subject (user).
+        if self._subject:
+            if self.universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN:
+                raise exceptions.GoogleAuthError(
+                    "Domain-wide delegation is not supported in universes other "
+                    + "than googleapis.com"
+                )
+
+            now = _helpers.utcnow()
+            payload = {
+                "iss": self._target_principal,
+                "scope": _helpers.scopes_to_string(self._target_scopes or ()),
+                "sub": self._subject,
+                "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
+                "iat": _helpers.datetime_to_secs(now),
+                "exp": _helpers.datetime_to_secs(now) + _DEFAULT_TOKEN_LIFETIME_SECS,
+            }
+
+            assertion = _sign_jwt_request(
+                request=request,
+                principal=self._target_principal,
+                headers=headers,
+                payload=payload,
+                delegates=self._delegates,
+            )
+
+            self.token, self.expiry, _ = _client.jwt_grant(
+                request, _GOOGLE_OAUTH2_TOKEN_ENDPOINT, assertion
+            )
+
+            return
+
         self.token, self.expiry = _make_iam_token_request(
         self.token, self.expiry = _make_iam_token_request(
             request=request,
             request=request,
             principal=self._target_principal,
             principal=self._target_principal,
@@ -478,3 +519,61 @@ class IDTokenCredentials(credentials.CredentialsWithQuotaProject):
         self.expiry = datetime.utcfromtimestamp(
         self.expiry = datetime.utcfromtimestamp(
             jwt.decode(id_token, verify=False)["exp"]
             jwt.decode(id_token, verify=False)["exp"]
         )
         )
+
+
+def _sign_jwt_request(request, principal, headers, payload, delegates=[]):
+    """Makes a request to the Google Cloud IAM service to sign a JWT using a
+    service account's system-managed private key.
+    Args:
+        request (Request): The Request object to use.
+        principal (str): The principal to request an access token for.
+        headers (Mapping[str, str]): Map of headers to transmit.
+        payload (Mapping[str, str]): The JWT payload to sign. Must be a
+            serialized JSON object that contains a JWT Claims Set.
+        delegates (Sequence[str]): The chained list of delegates required
+            to grant the final access_token.  If set, the sequence of
+            identities must have "Service Account Token Creator" capability
+            granted to the prceeding identity.  For example, if set to
+            [serviceAccountB, serviceAccountC], the source_credential
+            must have the Token Creator role on serviceAccountB.
+            serviceAccountB must have the Token Creator on
+            serviceAccountC.
+            Finally, C must have Token Creator on target_principal.
+            If left unset, source_credential must have that role on
+            target_principal.
+
+    Raises:
+        google.auth.exceptions.TransportError: Raised if there is an underlying
+            HTTP connection error
+        google.auth.exceptions.RefreshError: Raised if the impersonated
+            credentials are not available.  Common reasons are
+            `iamcredentials.googleapis.com` is not enabled or the
+            `Service Account Token Creator` is not assigned
+    """
+    iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal)
+
+    body = {"delegates": delegates, "payload": json.dumps(payload)}
+    body = json.dumps(body).encode("utf-8")
+
+    response = request(url=iam_endpoint, method="POST", headers=headers, body=body)
+
+    # support both string and bytes type response.data
+    response_body = (
+        response.data.decode("utf-8")
+        if hasattr(response.data, "decode")
+        else response.data
+    )
+
+    if response.status != http_client.OK:
+        raise exceptions.RefreshError(_REFRESH_ERROR, response_body)
+
+    try:
+        jwt_response = json.loads(response_body)
+        signed_jwt = jwt_response["signedJwt"]
+        return signed_jwt
+
+    except (KeyError, ValueError) as caught_exc:
+        new_exc = exceptions.RefreshError(
+            "{}: No signed JWT in response.".format(_REFRESH_ERROR), response_body
+        )
+        raise new_exc from caught_exc

Some files were not shown because too many files changed in this diff