diff --git a/Cargo.lock b/Cargo.lock index fe23eed3e..060141395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,7 +307,6 @@ dependencies = [ name = "dc_bundle" version = "0.31.0" dependencies = [ - "euclid", "prost", "prost-build", "serde", diff --git a/crates/dc_bundle/Cargo.toml b/crates/dc_bundle/Cargo.toml index 8c3bd2e69..5f32b115d 100644 --- a/crates/dc_bundle/Cargo.toml +++ b/crates/dc_bundle/Cargo.toml @@ -13,8 +13,5 @@ thiserror.workspace = true serde.workspace = true serde_bytes.workspace = true -# Temporary dependencies during the transition to protobuf structures -euclid.workspace = true - [build-dependencies] prost-build.workspace = true diff --git a/crates/dc_bundle/build.rs b/crates/dc_bundle/build.rs index 8b30e71a3..d4543ddcd 100644 --- a/crates/dc_bundle/build.rs +++ b/crates/dc_bundle/build.rs @@ -22,6 +22,7 @@ fn main() -> Result<(), Box> { prost_config.message_attribute("DimensionProto", "#[derive(Copy)]"); prost_config.message_attribute("DimensionProto.Auto", "#[derive(Copy)]"); prost_config.message_attribute("DimensionProto.Undefined", "#[derive(Copy)]"); + prost_config.message_attribute("LayoutTransform", "#[derive(Copy)]"); prost_config.enum_attribute("Dimension", "#[derive(Copy)]"); prost_config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]"); diff --git a/crates/dc_bundle/src/definition/modifier.rs b/crates/dc_bundle/src/definition/modifier.rs index 669921a92..6d983c342 100644 --- a/crates/dc_bundle/src/definition/modifier.rs +++ b/crates/dc_bundle/src/definition/modifier.rs @@ -13,5 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - include!(concat!(env!("OUT_DIR"), "/designcompose.definition.modifier.rs")); +pub mod affine_transform; +pub mod layout_transform; diff --git a/crates/dc_bundle/src/definition/modifier/LICENSE-APACHE b/crates/dc_bundle/src/definition/modifier/LICENSE-APACHE new file mode 100644 index 000000000..d5bf2b3ff --- /dev/null +++ b/crates/dc_bundle/src/definition/modifier/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/crates/dc_bundle/src/definition/modifier/LICENSE-MIT b/crates/dc_bundle/src/definition/modifier/LICENSE-MIT new file mode 100644 index 000000000..aa5bd00e0 --- /dev/null +++ b/crates/dc_bundle/src/definition/modifier/LICENSE-MIT @@ -0,0 +1,26 @@ + +Copyright (c) 2012-2013 Mozilla Foundation + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/crates/dc_bundle/src/definition/modifier/affine_transform.rs b/crates/dc_bundle/src/definition/modifier/affine_transform.rs new file mode 100644 index 000000000..421583098 --- /dev/null +++ b/crates/dc_bundle/src/definition/modifier/affine_transform.rs @@ -0,0 +1,22 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the same directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::definition::modifier::AffineTransform; + +/// Implementations are forked from euclid Transform2D. +impl AffineTransform { + /// Create a transform specifying its matrix elements in row-major order. + /// + /// Beware: This library is written with the assumption that row vectors + /// are being used. If your matrices use column vectors (i.e. transforming a vector + /// is `T * v`), then please use `column_major` + pub const fn row_major(m11: f32, m12: f32, m21: f32, m22: f32, m31: f32, m32: f32) -> Self { + AffineTransform { m11, m12, m21, m22, m31, m32 } + } +} diff --git a/crates/dc_bundle/src/definition/modifier/layout_transform.rs b/crates/dc_bundle/src/definition/modifier/layout_transform.rs new file mode 100644 index 000000000..4a5ae7578 --- /dev/null +++ b/crates/dc_bundle/src/definition/modifier/layout_transform.rs @@ -0,0 +1,376 @@ +// Copyright 2013 The Servo Project Developers. See the COPYRIGHT +// file at the same directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::definition::modifier::AffineTransform; +use crate::definition::modifier::LayoutTransform; + +/// Implementations are forked from euclid Transform3D. +impl LayoutTransform { + /// Create a transform specifying its components in row-major order. + /// + /// For example, the translation terms m41, m42, m43 on the last row with the + /// row-major convention) are the 13rd, 14th and 15th parameters. + /// + /// Beware: This library is written with the assumption that row vectors + /// are being used. If your matrices use column vectors (i.e. transforming a vector + /// is `T * v`), then please use `column_major` + pub const fn row_major( + m11: f32, + m12: f32, + m13: f32, + m14: f32, + m21: f32, + m22: f32, + m23: f32, + m24: f32, + m31: f32, + m32: f32, + m33: f32, + m34: f32, + m41: f32, + m42: f32, + m43: f32, + m44: f32, + ) -> Self { + LayoutTransform { + m11, + m12, + m13, + m14, + m21, + m22, + m23, + m24, + m31, + m32, + m33, + m34, + m41, + m42, + m43, + m44, + } + } + + /// Create a 4 by 4 transform representing a 2d transformation, specifying its components + /// in row-major order: + /// + /// ```text + /// m11 m12 0 0 + /// m21 m22 0 0 + /// 0 0 1 0 + /// m41 m42 0 1 + /// ``` + #[inline] + pub fn row_major_2d(m11: f32, m12: f32, m21: f32, m22: f32, m41: f32, m42: f32) -> Self { + Self::row_major( + m11, m12, 0f32, 0f32, // row1 + m21, m22, 0f32, 0f32, // row2 + 0f32, 0f32, 1f32, 0f32, //row3 + m41, m42, 0f32, 1f32, //row4 + ) + } + + /// Create a 3d translation transform: + /// + /// ```text + /// 1 0 0 0 + /// 0 1 0 0 + /// 0 0 1 0 + /// x y z 1 + /// ``` + #[inline] + pub fn create_translation(x: f32, y: f32, z: f32) -> Self { + Self::row_major( + 1f32, 0f32, 0f32, 0f32, // row1 + 0f32, 1f32, 0f32, 0f32, // row2 + 0f32, 0f32, 1f32, 0f32, // row3 + x, y, z, 1f32, // row4 + ) + } + + /// Creates an identity matrix: + /// + /// ```text + /// 1 0 0 0 + /// 0 1 0 0 + /// 0 0 1 0 + /// 0 0 0 1 + /// ``` + #[inline] + pub fn identity() -> Self { + Self::create_translation(0f32, 0f32, 0f32) + } + + /// Create a 3d rotation transform from an angle / axis. + /// The supplied axis must be normalized. + pub fn create_rotation(x: f32, y: f32, z: f32, theta: f32) -> Self { + let xx = x * x; + let yy = y * y; + let zz = z * z; + + let half_theta = theta / 2f32; + let sc = half_theta.sin() * half_theta.cos(); + let sq = half_theta.sin() * half_theta.sin(); + + Self::row_major( + 1f32 - 2f32 * (yy + zz) * sq, + 2f32 * (x * y * sq - z * sc), + 2f32 * (x * z * sq + y * sc), + 0f32, + 2f32 * (x * y * sq + z * sc), + 1f32 - 2f32 * (xx + zz) * sq, + 2f32 * (y * z * sq - x * sc), + 0f32, + 2f32 * (x * z * sq - y * sc), + 2f32 * (y * z * sq + x * sc), + 1f32 - 2f32 * (xx + yy) * sq, + 0f32, + 0f32, + 0f32, + 0f32, + 1f32, + ) + } + + /// Returns a transform with a rotation applied before self's transformation. + #[must_use] + pub fn pre_rotate(&self, x: f32, y: f32, z: f32, theta: f32) -> Self { + self.pre_transform(&Self::create_rotation(x, y, z, theta)) + } + + /// Returns the multiplication of the two matrices such that mat's transformation + /// applies before self's transformation. + /// + /// Assuming row vectors, this is equivalent to mat * self + #[inline] + #[must_use] + pub fn pre_transform(&self, mat: &LayoutTransform) -> LayoutTransform { + mat.post_transform(self) + } + + /// Returns the multiplication of the two matrices such that mat's transformation + /// applies after self's transformation. + /// + /// Assuming row vectors, this is equivalent to self * mat + #[must_use] + pub fn post_transform(&self, mat: &LayoutTransform) -> Self { + Self::row_major( + self.m11 * mat.m11 + self.m12 * mat.m21 + self.m13 * mat.m31 + self.m14 * mat.m41, + self.m11 * mat.m12 + self.m12 * mat.m22 + self.m13 * mat.m32 + self.m14 * mat.m42, + self.m11 * mat.m13 + self.m12 * mat.m23 + self.m13 * mat.m33 + self.m14 * mat.m43, + self.m11 * mat.m14 + self.m12 * mat.m24 + self.m13 * mat.m34 + self.m14 * mat.m44, + self.m21 * mat.m11 + self.m22 * mat.m21 + self.m23 * mat.m31 + self.m24 * mat.m41, + self.m21 * mat.m12 + self.m22 * mat.m22 + self.m23 * mat.m32 + self.m24 * mat.m42, + self.m21 * mat.m13 + self.m22 * mat.m23 + self.m23 * mat.m33 + self.m24 * mat.m43, + self.m21 * mat.m14 + self.m22 * mat.m24 + self.m23 * mat.m34 + self.m24 * mat.m44, + self.m31 * mat.m11 + self.m32 * mat.m21 + self.m33 * mat.m31 + self.m34 * mat.m41, + self.m31 * mat.m12 + self.m32 * mat.m22 + self.m33 * mat.m32 + self.m34 * mat.m42, + self.m31 * mat.m13 + self.m32 * mat.m23 + self.m33 * mat.m33 + self.m34 * mat.m43, + self.m31 * mat.m14 + self.m32 * mat.m24 + self.m33 * mat.m34 + self.m34 * mat.m44, + self.m41 * mat.m11 + self.m42 * mat.m21 + self.m43 * mat.m31 + self.m44 * mat.m41, + self.m41 * mat.m12 + self.m42 * mat.m22 + self.m43 * mat.m32 + self.m44 * mat.m42, + self.m41 * mat.m13 + self.m42 * mat.m23 + self.m43 * mat.m33 + self.m44 * mat.m43, + self.m41 * mat.m14 + self.m42 * mat.m24 + self.m43 * mat.m34 + self.m44 * mat.m44, + ) + } + + /// Returns a transform with a translation applied after self's transformation. + #[must_use] + pub fn post_translate(&self, x: f32, y: f32, z: f32) -> Self { + self.post_transform(&Self::create_translation(x, y, z)) + } + + /// Compute the determinant of the transform. + pub fn determinant(&self) -> f32 { + self.m14 * self.m23 * self.m32 * self.m41 + - self.m13 * self.m24 * self.m32 * self.m41 + - self.m14 * self.m22 * self.m33 * self.m41 + + self.m12 * self.m24 * self.m33 * self.m41 + + self.m13 * self.m22 * self.m34 * self.m41 + - self.m12 * self.m23 * self.m34 * self.m41 + - self.m14 * self.m23 * self.m31 * self.m42 + + self.m13 * self.m24 * self.m31 * self.m42 + + self.m14 * self.m21 * self.m33 * self.m42 + - self.m11 * self.m24 * self.m33 * self.m42 + - self.m13 * self.m21 * self.m34 * self.m42 + + self.m11 * self.m23 * self.m34 * self.m42 + + self.m14 * self.m22 * self.m31 * self.m43 + - self.m12 * self.m24 * self.m31 * self.m43 + - self.m14 * self.m21 * self.m32 * self.m43 + + self.m11 * self.m24 * self.m32 * self.m43 + + self.m12 * self.m21 * self.m34 * self.m43 + - self.m11 * self.m22 * self.m34 * self.m43 + - self.m13 * self.m22 * self.m31 * self.m44 + + self.m12 * self.m23 * self.m31 * self.m44 + + self.m13 * self.m21 * self.m32 * self.m44 + - self.m11 * self.m23 * self.m32 * self.m44 + - self.m12 * self.m21 * self.m33 * self.m44 + + self.m11 * self.m22 * self.m33 * self.m44 + } + + /// Multiplies all of the transform's component by a scalar and returns the result. + #[must_use] + pub fn mul_s(&self, x: f32) -> Self { + Self::row_major( + self.m11 * x, + self.m12 * x, + self.m13 * x, + self.m14 * x, + self.m21 * x, + self.m22 * x, + self.m23 * x, + self.m24 * x, + self.m31 * x, + self.m32 * x, + self.m33 * x, + self.m34 * x, + self.m41 * x, + self.m42 * x, + self.m43 * x, + self.m44 * x, + ) + } + + /// Returns the inverse transform if possible. + pub fn inverse(&self) -> Option { + let det = self.determinant(); + + if det == 0f32 { + return None; + } + + let m = Self::row_major( + self.m23 * self.m34 * self.m42 - self.m24 * self.m33 * self.m42 + + self.m24 * self.m32 * self.m43 + - self.m22 * self.m34 * self.m43 + - self.m23 * self.m32 * self.m44 + + self.m22 * self.m33 * self.m44, + self.m14 * self.m33 * self.m42 + - self.m13 * self.m34 * self.m42 + - self.m14 * self.m32 * self.m43 + + self.m12 * self.m34 * self.m43 + + self.m13 * self.m32 * self.m44 + - self.m12 * self.m33 * self.m44, + self.m13 * self.m24 * self.m42 - self.m14 * self.m23 * self.m42 + + self.m14 * self.m22 * self.m43 + - self.m12 * self.m24 * self.m43 + - self.m13 * self.m22 * self.m44 + + self.m12 * self.m23 * self.m44, + self.m14 * self.m23 * self.m32 + - self.m13 * self.m24 * self.m32 + - self.m14 * self.m22 * self.m33 + + self.m12 * self.m24 * self.m33 + + self.m13 * self.m22 * self.m34 + - self.m12 * self.m23 * self.m34, + self.m24 * self.m33 * self.m41 + - self.m23 * self.m34 * self.m41 + - self.m24 * self.m31 * self.m43 + + self.m21 * self.m34 * self.m43 + + self.m23 * self.m31 * self.m44 + - self.m21 * self.m33 * self.m44, + self.m13 * self.m34 * self.m41 - self.m14 * self.m33 * self.m41 + + self.m14 * self.m31 * self.m43 + - self.m11 * self.m34 * self.m43 + - self.m13 * self.m31 * self.m44 + + self.m11 * self.m33 * self.m44, + self.m14 * self.m23 * self.m41 + - self.m13 * self.m24 * self.m41 + - self.m14 * self.m21 * self.m43 + + self.m11 * self.m24 * self.m43 + + self.m13 * self.m21 * self.m44 + - self.m11 * self.m23 * self.m44, + self.m13 * self.m24 * self.m31 - self.m14 * self.m23 * self.m31 + + self.m14 * self.m21 * self.m33 + - self.m11 * self.m24 * self.m33 + - self.m13 * self.m21 * self.m34 + + self.m11 * self.m23 * self.m34, + self.m22 * self.m34 * self.m41 - self.m24 * self.m32 * self.m41 + + self.m24 * self.m31 * self.m42 + - self.m21 * self.m34 * self.m42 + - self.m22 * self.m31 * self.m44 + + self.m21 * self.m32 * self.m44, + self.m14 * self.m32 * self.m41 + - self.m12 * self.m34 * self.m41 + - self.m14 * self.m31 * self.m42 + + self.m11 * self.m34 * self.m42 + + self.m12 * self.m31 * self.m44 + - self.m11 * self.m32 * self.m44, + self.m12 * self.m24 * self.m41 - self.m14 * self.m22 * self.m41 + + self.m14 * self.m21 * self.m42 + - self.m11 * self.m24 * self.m42 + - self.m12 * self.m21 * self.m44 + + self.m11 * self.m22 * self.m44, + self.m14 * self.m22 * self.m31 + - self.m12 * self.m24 * self.m31 + - self.m14 * self.m21 * self.m32 + + self.m11 * self.m24 * self.m32 + + self.m12 * self.m21 * self.m34 + - self.m11 * self.m22 * self.m34, + self.m23 * self.m32 * self.m41 + - self.m22 * self.m33 * self.m41 + - self.m23 * self.m31 * self.m42 + + self.m21 * self.m33 * self.m42 + + self.m22 * self.m31 * self.m43 + - self.m21 * self.m32 * self.m43, + self.m12 * self.m33 * self.m41 - self.m13 * self.m32 * self.m41 + + self.m13 * self.m31 * self.m42 + - self.m11 * self.m33 * self.m42 + - self.m12 * self.m31 * self.m43 + + self.m11 * self.m32 * self.m43, + self.m13 * self.m22 * self.m41 + - self.m12 * self.m23 * self.m41 + - self.m13 * self.m21 * self.m42 + + self.m11 * self.m23 * self.m42 + + self.m12 * self.m21 * self.m43 + - self.m11 * self.m22 * self.m43, + self.m12 * self.m23 * self.m31 - self.m13 * self.m22 * self.m31 + + self.m13 * self.m21 * self.m32 + - self.m11 * self.m23 * self.m32 + - self.m12 * self.m21 * self.m33 + + self.m11 * self.m22 * self.m33, + ); + + Some(m.mul_s(1f32 / det)) + } + + /// Create a 3d scale transform: + /// + /// ```text + /// x 0 0 0 + /// 0 y 0 0 + /// 0 0 z 0 + /// 0 0 0 1 + /// ``` + #[inline] + pub fn create_scale(x: f32, y: f32, z: f32) -> Self { + Self::row_major( + x, 0f32, 0f32, 0f32, // row1 + 0f32, y, 0f32, 0f32, // row2 + 0f32, 0f32, z, 0f32, //row3 + 0f32, 0f32, 0f32, 1f32, //row4 + ) + } + + /// Returns a transform with a scale applied after self's transformation. + #[must_use] + pub fn post_scale(&self, x: f32, y: f32, z: f32) -> Self { + self.post_transform(&Self::create_scale(x, y, z)) + } + + /// Create a 2D transform picking the relevant terms from this transform. + /// + /// This method assumes that self represents a 2d transformation, callers + /// should check that [`self.is_2d()`] returns `true` beforehand. + /// + /// [`self.is_2d()`]: #method.is_2d + pub fn to_2d(&self) -> AffineTransform { + AffineTransform::row_major(self.m11, self.m12, self.m21, self.m22, self.m41, self.m42) + } +} diff --git a/crates/dc_bundle/src/legacy_definition/element/background.rs b/crates/dc_bundle/src/legacy_definition/element/background.rs index 12e31a902..fb6d62678 100644 --- a/crates/dc_bundle/src/legacy_definition/element/background.rs +++ b/crates/dc_bundle/src/legacy_definition/element/background.rs @@ -15,8 +15,8 @@ */ use crate::definition::element::color_or_var::ColorOrVar; +use crate::definition::modifier::AffineTransform; use crate::legacy_definition::modifier::filter::FilterOp; -use crate::legacy_definition::modifier::transform::AffineTransform; use serde::{Deserialize, Serialize}; /// Instead of keeping decoded images in ViewStyle objects, we keep keys to the images in the diff --git a/crates/dc_bundle/src/legacy_definition/modifier.rs b/crates/dc_bundle/src/legacy_definition/modifier.rs index 2ac48383a..2bd2ce95f 100644 --- a/crates/dc_bundle/src/legacy_definition/modifier.rs +++ b/crates/dc_bundle/src/legacy_definition/modifier.rs @@ -18,4 +18,3 @@ pub mod blend; pub mod filter; pub mod shadow; pub mod text; -pub mod transform; diff --git a/crates/dc_bundle/src/legacy_definition/modifier/transform.rs b/crates/dc_bundle/src/legacy_definition/modifier/transform.rs deleted file mode 100644 index 9adbf6e0c..000000000 --- a/crates/dc_bundle/src/legacy_definition/modifier/transform.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use serde::Deserialize; - -#[derive(Clone, Debug, Deserialize)] -pub struct LayoutPixel; - -pub type LayoutTransform = euclid::Transform3D; -pub type AffineTransform = euclid::Transform2D; diff --git a/crates/dc_bundle/src/legacy_definition/view/node_style.rs b/crates/dc_bundle/src/legacy_definition/view/node_style.rs index 58e814ef0..78a186b2e 100644 --- a/crates/dc_bundle/src/legacy_definition/view/node_style.rs +++ b/crates/dc_bundle/src/legacy_definition/view/node_style.rs @@ -17,6 +17,7 @@ use crate::definition::element::num_or_var::NumOrVar; use crate::definition::element::{FontFeature, FontStyle, Hyperlink, Size, TextDecoration}; use crate::definition::layout::FlexWrap; +use crate::definition::modifier::LayoutTransform; use crate::legacy_definition::element::background::Background; use crate::legacy_definition::element::font::{FontStretch, FontWeight}; use crate::legacy_definition::element::path::{LineHeight, Stroke}; @@ -27,7 +28,6 @@ use crate::legacy_definition::modifier::blend::BlendMode; use crate::legacy_definition::modifier::filter::FilterOp; use crate::legacy_definition::modifier::shadow::{BoxShadow, TextShadow}; use crate::legacy_definition::modifier::text::{TextAlign, TextAlignVertical, TextOverflow}; -use crate::legacy_definition::modifier::transform::LayoutTransform; use crate::legacy_definition::plugin::meter_data::MeterData; use serde::{Deserialize, Serialize}; diff --git a/crates/figma_import/src/transform_flexbox.rs b/crates/figma_import/src/transform_flexbox.rs index b33449f03..6dbbf12fb 100644 --- a/crates/figma_import/src/transform_flexbox.rs +++ b/crates/figma_import/src/transform_flexbox.rs @@ -38,6 +38,7 @@ use dc_bundle::definition::element::dimension_proto::Dimension; use dc_bundle::definition::element::num_or_var::NumOrVar; use dc_bundle::definition::element::Path; use dc_bundle::definition::layout::FlexWrap; +use dc_bundle::definition::modifier::LayoutTransform; use dc_bundle::legacy_definition::element::background::Background; use dc_bundle::legacy_definition::element::path::{LineHeight, StrokeAlign, StrokeWeight}; use dc_bundle::legacy_definition::element::reactions::{FrameExtras, Reaction}; @@ -51,7 +52,6 @@ use dc_bundle::legacy_definition::modifier::blend::BlendMode; use dc_bundle::legacy_definition::modifier::filter::FilterOp; use dc_bundle::legacy_definition::modifier::shadow::{BoxShadow, ShadowBox, TextShadow}; use dc_bundle::legacy_definition::modifier::text::{TextAlign, TextAlignVertical, TextOverflow}; -use dc_bundle::legacy_definition::modifier::transform::LayoutTransform; use dc_bundle::legacy_definition::view::component::ComponentInfo; use dc_bundle::legacy_definition::view::text_style::{StyledTextRun, TextStyle}; use dc_bundle::legacy_definition::view::view::{RenderMethod, ScrollInfo, View}; @@ -591,7 +591,8 @@ fn compute_background( // Figma has already applied the rotation in "stretch" mode. if *rotation != 0.0 && *scale_mode != figma_schema::ScaleMode::Stretch { - transform = transform.pre_rotate(0.0, 0.0, 1.0, euclid::Angle::degrees(*rotation)); + transform = + transform.pre_rotate(0.0, 0.0, 1.0, euclid::Angle::degrees(*rotation).get()); } if let Some(scale_factor) = *scaling_factor { @@ -839,11 +840,11 @@ fn visit_node( // Provide an unaltered transform from the last relevant parent. style.node_style.relative_transform = Some(r.clone()); // And an additional transform with translation removed. - let r = r.post_translate(euclid::vec3( + let r = r.post_translate( -(bounds.x() - parent_bounds.x()), -(bounds.y() - parent_bounds.y()), 0.0, - )); + ); style.node_style.transform = Some(r); } } diff --git a/crates/figma_import/tests/layout-unit-tests.dcf b/crates/figma_import/tests/layout-unit-tests.dcf index 540d27477..a13b5c5a7 100644 Binary files a/crates/figma_import/tests/layout-unit-tests.dcf and b/crates/figma_import/tests/layout-unit-tests.dcf differ diff --git a/designcompose/src/main/assets/figma/DesignSwitcherDoc_Ljph4e3sC0lHcynfXpoh9f.dcf b/designcompose/src/main/assets/figma/DesignSwitcherDoc_Ljph4e3sC0lHcynfXpoh9f.dcf index 66e212791..6c7afa8ce 100644 Binary files a/designcompose/src/main/assets/figma/DesignSwitcherDoc_Ljph4e3sC0lHcynfXpoh9f.dcf and b/designcompose/src/main/assets/figma/DesignSwitcherDoc_Ljph4e3sC0lHcynfXpoh9f.dcf differ diff --git a/designcompose/src/main/java/com/android/designcompose/Layout.kt b/designcompose/src/main/java/com/android/designcompose/Layout.kt index f828e9c81..04587cef1 100644 --- a/designcompose/src/main/java/com/android/designcompose/Layout.kt +++ b/designcompose/src/main/java/com/android/designcompose/Layout.kt @@ -51,6 +51,7 @@ import com.android.designcompose.serdegen.GridSpan import com.android.designcompose.serdegen.ItemSpacing import com.android.designcompose.serdegen.JustifyContent import com.android.designcompose.serdegen.Layout +import com.android.designcompose.serdegen.LayoutTransform import com.android.designcompose.serdegen.OverflowDirection import com.android.designcompose.serdegen.PositionType import com.android.designcompose.serdegen.Size @@ -112,8 +113,8 @@ data class ExternalLayoutData( val flexBasis: Dimension, val alignSelf: AlignSelf, val positionType: PositionType, - val transform: Optional>, - val relativeTransform: Optional>, + val transform: Optional, + val relativeTransform: Optional, ) // ParentLayoutInfo holds data necessary to perform layout. When a node subscribes to layout, it diff --git a/designcompose/src/main/java/com/android/designcompose/MathUtils.kt b/designcompose/src/main/java/com/android/designcompose/MathUtils.kt index d75ef9bb5..e32ffadbb 100644 --- a/designcompose/src/main/java/com/android/designcompose/MathUtils.kt +++ b/designcompose/src/main/java/com/android/designcompose/MathUtils.kt @@ -19,6 +19,7 @@ package com.android.designcompose import android.graphics.PointF import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Matrix +import com.android.designcompose.serdegen.LayoutTransform import java.util.Optional import kotlin.math.atan2 import kotlin.math.cos @@ -66,29 +67,29 @@ internal data class DecomposedMatrix2D( } } -internal fun Matrix.toFloatList(): List { - return listOf( - values[0], - values[1], - values[2], - values[3], - values[4], - values[5], - values[6], - values[7], - values[8], - values[9], - values[10], - values[11], - values[12], - values[13], - values[14], - values[15] - ) +internal fun Matrix.toLayoutTransform(): LayoutTransform { + val builder = LayoutTransform.Builder() + builder.m11 = values[0] + builder.m12 = values[1] + builder.m13 = values[2] + builder.m14 = values[3] + builder.m21 = values[4] + builder.m22 = values[5] + builder.m23 = values[6] + builder.m24 = values[7] + builder.m31 = values[8] + builder.m32 = values[9] + builder.m33 = values[10] + builder.m34 = values[11] + builder.m41 = values[12] + builder.m42 = values[13] + builder.m43 = values[14] + builder.m44 = values[15] + return builder.build() } // Decompose a matrix in list form into its translation, angle, and scale parts -internal fun Optional>.decompose(density: Float): DecomposedMatrix2D { +internal fun Optional.decompose(density: Float): DecomposedMatrix2D { val matrix = this.asComposeTransform(density) return matrix?.decompose() ?: DecomposedMatrix2D() } diff --git a/designcompose/src/main/java/com/android/designcompose/Utils.kt b/designcompose/src/main/java/com/android/designcompose/Utils.kt index 7490861a8..1ce6f90ce 100644 --- a/designcompose/src/main/java/com/android/designcompose/Utils.kt +++ b/designcompose/src/main/java/com/android/designcompose/Utils.kt @@ -56,6 +56,7 @@ import com.android.designcompose.proto.newDimensionRectPointsZero import com.android.designcompose.proto.start import com.android.designcompose.proto.toOptDimProto import com.android.designcompose.proto.top +import com.android.designcompose.serdegen.AffineTransform import com.android.designcompose.serdegen.AlignContent import com.android.designcompose.serdegen.AlignItems import com.android.designcompose.serdegen.AlignSelf @@ -74,6 +75,7 @@ import com.android.designcompose.serdegen.JustifyContent import com.android.designcompose.serdegen.Layout import com.android.designcompose.serdegen.LayoutSizing import com.android.designcompose.serdegen.LayoutStyle +import com.android.designcompose.serdegen.LayoutTransform import com.android.designcompose.serdegen.LineHeight import com.android.designcompose.serdegen.NodeStyle import com.android.designcompose.serdegen.NumOrVar @@ -1275,41 +1277,53 @@ internal constructor( * * XXX: Doesn't consider transform origin. */ -internal fun Optional>.asComposeTransform( +internal fun Optional.asComposeTransform( density: Float ): androidx.compose.ui.graphics.Matrix? { return map { - if (it.size != 16) { + val transform = + androidx.compose.ui.graphics.Matrix( + floatArrayOf( + it.m11, + it.m12, + it.m13, + it.m14, + it.m21, + it.m22, + it.m23, + it.m24, + it.m31, + it.m32, + it.m33, + it.m34, + it.m41, + it.m42, + it.m43, + it.m44 + ) + ) + if (transform.isIdentity()) { null } else { - val transform = androidx.compose.ui.graphics.Matrix(it.toFloatArray()) - if (transform.isIdentity()) { - null - } else { - val adjust = androidx.compose.ui.graphics.Matrix() - adjust.scale(1.0f / density, 1.0f / density, 1.0f / density) - adjust.timesAssign(transform) - val unadjust = androidx.compose.ui.graphics.Matrix() - unadjust.scale(density, density, density) - adjust.timesAssign(unadjust) - adjust - } + val adjust = androidx.compose.ui.graphics.Matrix() + adjust.scale(1.0f / density, 1.0f / density, 1.0f / density) + adjust.timesAssign(transform) + val unadjust = androidx.compose.ui.graphics.Matrix() + unadjust.scale(density, density, density) + adjust.timesAssign(unadjust) + adjust } } .orElse(null) } -internal fun Optional>.asSkiaMatrix(): Matrix? { +internal fun Optional.asSkiaMatrix(): Matrix? { return map { - if (it.size != 6) { - null - } else { - val skMatrix = Matrix() - skMatrix.setValues( - floatArrayOf(it[0], it[1], it[4], it[2], it[3], it[5], 0.0f, 0.0f, 1.0f) - ) - skMatrix - } + val skMatrix = Matrix() + skMatrix.setValues( + floatArrayOf(it.m11, it.m12, it.m31, it.m21, it.m22, it.m32, 0.0f, 0.0f, 1.0f) + ) + skMatrix } .orElse(null) } diff --git a/designcompose/src/main/java/com/android/designcompose/squoosh/SquooshAnimate.kt b/designcompose/src/main/java/com/android/designcompose/squoosh/SquooshAnimate.kt index fbe98315c..5f9c6ba50 100644 --- a/designcompose/src/main/java/com/android/designcompose/squoosh/SquooshAnimate.kt +++ b/designcompose/src/main/java/com/android/designcompose/squoosh/SquooshAnimate.kt @@ -33,7 +33,7 @@ import com.android.designcompose.serdegen.View import com.android.designcompose.serdegen.ViewData import com.android.designcompose.serdegen.ViewShape import com.android.designcompose.serdegen.ViewStyle -import com.android.designcompose.toFloatList +import com.android.designcompose.toLayoutTransform import java.util.Optional import kotlin.jvm.optionals.getOrElse @@ -150,7 +150,7 @@ internal class SquooshAnimatedScale( transform.scaleY = scaleY target.style = target.style.withNodeStyle { s -> - s.transform = Optional.of(transform.toMatrix().toFloatList()) + s.transform = Optional.of(transform.toMatrix().toLayoutTransform()) } updateLayout( @@ -211,7 +211,7 @@ internal class SquooshAnimatedLayout( val targetDecomposed = fromDecomposed.interpolateTo(toDecomposed, value) target.style = target.style.withNodeStyle { s -> - s.transform = Optional.of(targetDecomposed.toMatrix().toFloatList()) + s.transform = Optional.of(targetDecomposed.toMatrix().toLayoutTransform()) } val fromLayout = from.computedLayout!! diff --git a/plugins/gradle-plugin-internal/src/main/java/com/android/designcompose/gradle/internal/InternalGradlePlugin.kt b/plugins/gradle-plugin-internal/src/main/java/com/android/designcompose/gradle/internal/InternalGradlePlugin.kt index 054dc157e..6f7a1ef02 100644 --- a/plugins/gradle-plugin-internal/src/main/java/com/android/designcompose/gradle/internal/InternalGradlePlugin.kt +++ b/plugins/gradle-plugin-internal/src/main/java/com/android/designcompose/gradle/internal/InternalGradlePlugin.kt @@ -89,7 +89,6 @@ class InternalGradlePlugin : Plugin { test.inputs.properties(mapOf("isFetch" to isFetch)) test.outputs.doNotCacheIf("Always fetch DCF files") { isFetch.get() } - test.doFirst { if (isFetch.get()) { test.useJUnit { diff --git a/reference-apps/tutorial/app/src/main/assets/figma/TutorialDoc_3z4xExq0INrL9vxPhj9tl7.dcf b/reference-apps/tutorial/app/src/main/assets/figma/TutorialDoc_3z4xExq0INrL9vxPhj9tl7.dcf index 7d5947281..51f94dece 100644 Binary files a/reference-apps/tutorial/app/src/main/assets/figma/TutorialDoc_3z4xExq0INrL9vxPhj9tl7.dcf and b/reference-apps/tutorial/app/src/main/assets/figma/TutorialDoc_3z4xExq0INrL9vxPhj9tl7.dcf differ