From 396c618277d36b5157db8991e3da3540fd5c32d8 Mon Sep 17 00:00:00 2001 From: Max Mitchell Date: Fri, 20 Feb 2026 08:42:16 -0800 Subject: [PATCH] Populate min_version BrushFamily proto field. Introduces code to calculate the minimum required version of Ink for a BrushFamily (and each part of a BrushFamily). The code is used during serialization to compute the value to store in the `min_version` field. During deserialization, the `min_version` field is used to determine if a BrushFamily is compatible with the current version of Ink; if it isn't, decoding it is aborted. To ensure the code in the web of `CalculateMinimumRequiredVersion` functions lines up with expectations, protobuf options are used to build consistency check tests. `CalculateMinimumRequiredVersion` serves as the enforcement of the expectations set in the protobuf itself. The tests are: * A fuzz test to generate valid brush families and serialize them. Then, it checks the value in `min_version` against all the options set for the fields, messages, and enum values, to ensure it is equal to the highest value set for the relevant options. This ensures `CalculateMinimumRequiredVersion` is consistent with the protobuf options. * A test to iterate over all the messages, fields, and enum values in `brush_family.proto`, starting at the top-level `BrushFamily` message, and verify that they all have a relevant `field_min_version`, `message_min_version`, or `enum_value_min_version` set, and that this value set is less than or equal to the maximum compatible version (`version::kMax`). * A test to verify that decoding a `proto::Version` out of the acceptable range (higher than `version::kMax`, lower than `version::kMin`, any version with `release < 1`, any stable version with `release != 1`) will fail. * A test to verify that decoding a `proto::Version` with an unrecognized `proto::Version::Cycle` enum value will fail. * A test to verify that decoding a `proto::BrushFamily` with no value set for its `min_version` field will ***not*** fail, to ensure older brushes are still able to be loaded. A more direct implementation, using reflection to examine the descriptors of the protobuf directly during serialization, was considered, but ultimately rejected. It would have required both static and runtime reflection, and increased `libink.so` size by ~2%. PiperOrigin-RevId: 872916698 --- ink/brush/BUILD.bazel | 17 +- ink/brush/brush_behavior.cc | 217 +++ ink/brush/brush_behavior.h | 5 + ink/brush/brush_coat.cc | 11 +- ink/brush/brush_coat.h | 5 + ink/brush/brush_family.cc | 11 + ink/brush/brush_family.h | 5 + ink/brush/brush_paint.cc | 92 ++ ink/brush/brush_paint.h | 5 + ink/brush/brush_tip.cc | 11 + ink/brush/brush_tip.h | 5 + ink/brush/color_function.cc | 27 + ink/brush/color_function.h | 5 + ink/brush/easing_function.cc | 53 + ink/brush/easing_function.h | 5 + ink/brush/version.h | 87 ++ ink/storage/BUILD.bazel | 8 +- ink/storage/brush.cc | 74 + ink/storage/brush.h | 4 + ink/storage/brush_test.cc | 317 +++- ink/storage/proto/BUILD.bazel | 20 + ink/storage/proto/brush_family.proto | 2082 +++++++++++++++++++++++--- ink/storage/proto/options.proto | 92 ++ 23 files changed, 2934 insertions(+), 224 deletions(-) create mode 100644 ink/brush/version.h create mode 100644 ink/storage/proto/options.proto diff --git a/ink/brush/BUILD.bazel b/ink/brush/BUILD.bazel index fdf7c0f2..8fa98280 100644 --- a/ink/brush/BUILD.bazel +++ b/ink/brush/BUILD.bazel @@ -82,9 +82,9 @@ cc_library( srcs = ["brush_coat.cc"], hdrs = ["brush_coat.h"], deps = [ - ":brush_behavior", ":brush_paint", ":brush_tip", + ":version", "//ink/geometry:mesh_format", "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/container:inlined_vector", @@ -120,6 +120,7 @@ cc_library( ":brush_coat", ":brush_paint", ":brush_tip", + ":version", "//ink/types:duration", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", @@ -160,6 +161,7 @@ cc_library( hdrs = ["brush_tip.h"], deps = [ ":brush_behavior", + ":version", "//ink/geometry:angle", "//ink/geometry:mesh_format", "//ink/geometry:vec", @@ -190,6 +192,7 @@ cc_library( srcs = ["color_function.cc"], hdrs = ["color_function.h"], deps = [ + ":version", "//ink/color", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", @@ -219,6 +222,7 @@ cc_library( srcs = ["easing_function.cc"], hdrs = ["easing_function.h"], deps = [ + ":version", "//ink/geometry:point", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", @@ -245,6 +249,7 @@ cc_library( hdrs = ["brush_behavior.h"], deps = [ ":easing_function", + ":version", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", @@ -273,6 +278,7 @@ cc_library( hdrs = ["brush_paint.h"], deps = [ ":color_function", + ":version", "//ink/geometry:angle", "//ink/geometry:mesh_format", "//ink/geometry:vec", @@ -365,6 +371,15 @@ cc_library( ], ) +cc_library( + name = "version", + hdrs = ["version.h"], + deps = [ + "@com_google_absl//absl/status", + "@com_google_absl//absl/strings", + ], +) + cc_test( name = "stock_brushes_test", srcs = ["stock_brushes_test.cc"], diff --git a/ink/brush/brush_behavior.cc b/ink/brush/brush_behavior.cc index b143119b..1304345e 100644 --- a/ink/brush/brush_behavior.cc +++ b/ink/brush/brush_behavior.cc @@ -14,6 +14,7 @@ #include "ink/brush/brush_behavior.h" +#include #include #include #include @@ -25,6 +26,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" #include "ink/brush/easing_function.h" +#include "ink/brush/version.h" namespace ink { @@ -476,6 +478,221 @@ absl::Status ValidateBrushBehavior(const BrushBehavior& behavior) { return absl::OkStatus(); } +namespace { + +Version CalculateMinimumRequiredVersion(BrushBehavior::Source source) { + switch (source) { + case BrushBehavior::Source::kNormalizedPressure: + case BrushBehavior::Source::kTiltInRadians: + case BrushBehavior::Source::kTiltXInRadians: + case BrushBehavior::Source::kTiltYInRadians: + case BrushBehavior::Source::kOrientationInRadians: + case BrushBehavior::Source::kOrientationAboutZeroInRadians: + case BrushBehavior::Source::kSpeedInMultiplesOfBrushSizePerSecond: + case BrushBehavior::Source::kVelocityXInMultiplesOfBrushSizePerSecond: + case BrushBehavior::Source::kVelocityYInMultiplesOfBrushSizePerSecond: + case BrushBehavior::Source::kDirectionInRadians: + case BrushBehavior::Source::kDirectionAboutZeroInRadians: + case BrushBehavior::Source::kNormalizedDirectionX: + case BrushBehavior::Source::kNormalizedDirectionY: + case BrushBehavior::Source::kDistanceTraveledInMultiplesOfBrushSize: + case BrushBehavior::Source::kTimeOfInputInSeconds: + case BrushBehavior::Source:: + kPredictedDistanceTraveledInMultiplesOfBrushSize: + case BrushBehavior::Source::kPredictedTimeElapsedInSeconds: + case BrushBehavior::Source::kDistanceRemainingInMultiplesOfBrushSize: + case BrushBehavior::Source::kTimeSinceInputInSeconds: + case BrushBehavior::Source:: + kAccelerationInMultiplesOfBrushSizePerSecondSquared: + case BrushBehavior::Source:: + kAccelerationXInMultiplesOfBrushSizePerSecondSquared: + case BrushBehavior::Source:: + kAccelerationYInMultiplesOfBrushSizePerSecondSquared: + case BrushBehavior::Source:: + kAccelerationForwardInMultiplesOfBrushSizePerSecondSquared: + case BrushBehavior::Source:: + kAccelerationLateralInMultiplesOfBrushSizePerSecondSquared: + case BrushBehavior::Source::kInputSpeedInCentimetersPerSecond: + case BrushBehavior::Source::kInputVelocityXInCentimetersPerSecond: + case BrushBehavior::Source::kInputVelocityYInCentimetersPerSecond: + case BrushBehavior::Source::kInputDistanceTraveledInCentimeters: + case BrushBehavior::Source::kPredictedInputDistanceTraveledInCentimeters: + case BrushBehavior::Source::kInputAccelerationInCentimetersPerSecondSquared: + case BrushBehavior::Source:: + kInputAccelerationXInCentimetersPerSecondSquared: + case BrushBehavior::Source:: + kInputAccelerationYInCentimetersPerSecondSquared: + case BrushBehavior::Source:: + kInputAccelerationForwardInCentimetersPerSecondSquared: + case BrushBehavior::Source:: + kInputAccelerationLateralInCentimetersPerSecondSquared: + case BrushBehavior::Source::kDistanceRemainingAsFractionOfStrokeLength: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::Target target) { + switch (target) { + case BrushBehavior::Target::kWidthMultiplier: + case BrushBehavior::Target::kHeightMultiplier: + case BrushBehavior::Target::kSizeMultiplier: + case BrushBehavior::Target::kSlantOffsetInRadians: + case BrushBehavior::Target::kPinchOffset: + case BrushBehavior::Target::kRotationOffsetInRadians: + case BrushBehavior::Target::kCornerRoundingOffset: + case BrushBehavior::Target::kPositionOffsetXInMultiplesOfBrushSize: + case BrushBehavior::Target::kPositionOffsetYInMultiplesOfBrushSize: + case BrushBehavior::Target::kPositionOffsetForwardInMultiplesOfBrushSize: + case BrushBehavior::Target::kPositionOffsetLateralInMultiplesOfBrushSize: + case BrushBehavior::Target::kTextureAnimationProgressOffset: + case BrushBehavior::Target::kHueOffsetInRadians: + case BrushBehavior::Target::kSaturationMultiplier: + case BrushBehavior::Target::kLuminosity: + case BrushBehavior::Target::kOpacityMultiplier: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::PolarTarget target) { + switch (target) { + case BrushBehavior::PolarTarget:: + kPositionOffsetAbsoluteInRadiansAndMultiplesOfBrushSize: + case BrushBehavior::PolarTarget:: + kPositionOffsetRelativeInRadiansAndMultiplesOfBrushSize: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion( + BrushBehavior::OutOfRange out_of_range) { + switch (out_of_range) { + case BrushBehavior::OutOfRange::kClamp: + case BrushBehavior::OutOfRange::kMirror: + case BrushBehavior::OutOfRange::kRepeat: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion( + BrushBehavior::EnabledToolTypes enabled) { + return version::k1_0_0; +} + +Version CalculateMinimumRequiredVersion( + BrushBehavior::OptionalInputProperty input) { + switch (input) { + case BrushBehavior::OptionalInputProperty::kPressure: + case BrushBehavior::OptionalInputProperty::kTilt: + case BrushBehavior::OptionalInputProperty::kOrientation: + case BrushBehavior::OptionalInputProperty::kTiltXAndY: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::BinaryOp operation) { + switch (operation) { + case BrushBehavior::BinaryOp::kProduct: + case BrushBehavior::BinaryOp::kSum: + return version::k1_0_0; + case BrushBehavior::BinaryOp::kMin: + case BrushBehavior::BinaryOp::kMax: + case BrushBehavior::BinaryOp::kAndThen: + case BrushBehavior::BinaryOp::kOrElse: + case BrushBehavior::BinaryOp::kXorElse: + return version::k1_1_0_alpha_01; + } +} + +Version CalculateMinimumRequiredVersion( + BrushBehavior::ProgressDomain progress_domain) { + switch (progress_domain) { + case BrushBehavior::ProgressDomain::kDistanceInCentimeters: + case BrushBehavior::ProgressDomain::kDistanceInMultiplesOfBrushSize: + case BrushBehavior::ProgressDomain::kTimeInSeconds: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion( + BrushBehavior::Interpolation interpolation) { + switch (interpolation) { + case BrushBehavior::Interpolation::kLerp: + case BrushBehavior::Interpolation::kInverseLerp: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::SourceNode node) { + return std::max( + CalculateMinimumRequiredVersion(node.source_out_of_range_behavior), + CalculateMinimumRequiredVersion(node.source)); +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::ConstantNode node) { + return version::k1_0_0; +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::NoiseNode node) { + return CalculateMinimumRequiredVersion(node.vary_over); +} + +Version CalculateMinimumRequiredVersion( + BrushBehavior::FallbackFilterNode node) { + return CalculateMinimumRequiredVersion(node.is_fallback_for); +} + +Version CalculateMinimumRequiredVersion( + BrushBehavior::ToolTypeFilterNode node) { + return CalculateMinimumRequiredVersion(node.enabled_tool_types); +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::DampingNode node) { + return CalculateMinimumRequiredVersion(node.damping_source); +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::ResponseNode node) { + return brush_internal::CalculateMinimumRequiredVersion(node.response_curve); +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::IntegralNode node) { + return std::max( + {version::k1_1_0_alpha_01, + CalculateMinimumRequiredVersion(node.integrate_over), + CalculateMinimumRequiredVersion(node.integral_out_of_range_behavior)}); +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::BinaryOpNode node) { + return CalculateMinimumRequiredVersion(node.operation); +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::InterpolationNode node) { + return CalculateMinimumRequiredVersion(node.interpolation); +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::TargetNode node) { + return CalculateMinimumRequiredVersion(node.target); +} + +Version CalculateMinimumRequiredVersion(BrushBehavior::PolarTargetNode node) { + return CalculateMinimumRequiredVersion(node.target); +} + +Version CalculateMinimumRequiredVersion(const BrushBehavior::Node& node) { + return std::visit( + [](const auto& node) { return CalculateMinimumRequiredVersion(node); }, + node); +} + +} // namespace + +Version CalculateMinimumRequiredVersion(const BrushBehavior& behavior) { + Version max_version = version::k1_0_0; + for (const BrushBehavior::Node& node : behavior.nodes) { + max_version = std::max(max_version, CalculateMinimumRequiredVersion(node)); + } + return max_version; +} + std::string ToFormattedString(BrushBehavior::Source source) { switch (source) { case BrushBehavior::Source::kNormalizedPressure: diff --git a/ink/brush/brush_behavior.h b/ink/brush/brush_behavior.h index d6677f5b..39fa9ac1 100644 --- a/ink/brush/brush_behavior.h +++ b/ink/brush/brush_behavior.h @@ -23,6 +23,7 @@ #include "absl/status/status.h" #include "ink/brush/easing_function.h" +#include "ink/brush/version.h" namespace ink { @@ -693,6 +694,10 @@ absl::Status ValidateBrushBehaviorTopLevel(const BrushBehavior& behavior); absl::Status ValidateBrushBehaviorNode(const BrushBehavior::Node& node); +// Calculates the minimum version of the Ink library that is required to use +// this brush behavior. +Version CalculateMinimumRequiredVersion(const BrushBehavior& behavior); + std::string ToFormattedString(BrushBehavior::Source source); std::string ToFormattedString(BrushBehavior::Target target); std::string ToFormattedString(BrushBehavior::PolarTarget target); diff --git a/ink/brush/brush_coat.cc b/ink/brush/brush_coat.cc index a1342976..1781bbbc 100644 --- a/ink/brush/brush_coat.cc +++ b/ink/brush/brush_coat.cc @@ -14,6 +14,7 @@ #include "ink/brush/brush_coat.h" +#include #include #include "absl/container/flat_hash_set.h" @@ -21,9 +22,9 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" -#include "ink/brush/brush_behavior.h" #include "ink/brush/brush_paint.h" #include "ink/brush/brush_tip.h" +#include "ink/brush/version.h" #include "ink/geometry/mesh_format.h" namespace ink { @@ -45,6 +46,14 @@ absl::Status ValidateBrushCoat(const BrushCoat& coat) { return absl::OkStatus(); } +Version CalculateMinimumRequiredVersion(const BrushCoat& coat) { + Version max_version = CalculateMinimumRequiredVersion(coat.tip); + for (const BrushPaint& paint : coat.paint_preferences) { + max_version = std::max(max_version, CalculateMinimumRequiredVersion(paint)); + } + return max_version; +} + void AddAttributeIdsRequiredByCoat( const BrushCoat& coat, absl::flat_hash_set& attribute_ids) { diff --git a/ink/brush/brush_coat.h b/ink/brush/brush_coat.h index d908b0db..c882a562 100644 --- a/ink/brush/brush_coat.h +++ b/ink/brush/brush_coat.h @@ -22,6 +22,7 @@ #include "absl/status/status.h" #include "ink/brush/brush_paint.h" #include "ink/brush/brush_tip.h" +#include "ink/brush/version.h" #include "ink/geometry/mesh_format.h" namespace ink { @@ -46,6 +47,10 @@ namespace brush_internal { // BrushFamily, and returns an error if not. absl::Status ValidateBrushCoat(const BrushCoat& coat); +// Calculates the minimum version of the Ink library that is required to use +// this brush coat. +Version CalculateMinimumRequiredVersion(const BrushCoat& coat); + // Adds the mesh attribute IDs that are required to properly render a mesh // made with this brush coat to the given `attribute_ids` set. This will always // include `kPosition` and certain other attribute IDs (`kSideDerivative`, diff --git a/ink/brush/brush_family.cc b/ink/brush/brush_family.cc index d9a7d3d1..793ffcf0 100644 --- a/ink/brush/brush_family.cc +++ b/ink/brush/brush_family.cc @@ -14,6 +14,7 @@ #include "ink/brush/brush_family.h" +#include #include #include #include @@ -28,6 +29,7 @@ #include "ink/brush/brush_coat.h" #include "ink/brush/brush_paint.h" #include "ink/brush/brush_tip.h" +#include "ink/brush/version.h" #include "ink/types/duration.h" namespace ink { @@ -94,6 +96,15 @@ std::string BrushFamily::ToFormattedString() const { return formatted; } +Version BrushFamily::CalculateMinimumRequiredVersion() const { + Version max_version = version::k1_0_0; + for (const BrushCoat& coat : coats_) { + max_version = std::max( + max_version, brush_internal::CalculateMinimumRequiredVersion(coat)); + } + return max_version; +} + namespace brush_internal { namespace { diff --git a/ink/brush/brush_family.h b/ink/brush/brush_family.h index 9cc93de4..ae67bfb1 100644 --- a/ink/brush/brush_family.h +++ b/ink/brush/brush_family.h @@ -27,6 +27,7 @@ #include "ink/brush/brush_coat.h" #include "ink/brush/brush_paint.h" #include "ink/brush/brush_tip.h" +#include "ink/brush/version.h" #include "ink/types/duration.h" namespace ink { @@ -152,6 +153,10 @@ class BrushFamily { // otherwise used internally by Ink. const Metadata& GetMetadata() const; + // Calculates the minimum version of the Ink library that is required to use + // this brush family. + Version CalculateMinimumRequiredVersion() const; + template friend void AbslStringify(Sink& sink, const BrushFamily& family) { sink.Append(family.ToFormattedString()); diff --git a/ink/brush/brush_paint.cc b/ink/brush/brush_paint.cc index 4b234e58..25307d93 100644 --- a/ink/brush/brush_paint.cc +++ b/ink/brush/brush_paint.cc @@ -14,6 +14,7 @@ #include "ink/brush/brush_paint.h" +#include #include #include @@ -24,6 +25,7 @@ #include "absl/strings/str_join.h" #include "absl/time/time.h" #include "ink/brush/color_function.h" +#include "ink/brush/version.h" #include "ink/geometry/mesh_format.h" namespace ink::brush_internal { @@ -261,6 +263,96 @@ absl::Status ValidateBrushPaint(const BrushPaint& paint) { return absl::OkStatus(); } +namespace { + +Version CalculateMinimumRequiredVersion( + BrushPaint::TextureMapping texture_mapping) { + switch (texture_mapping) { + case BrushPaint::TextureMapping::kTiling: + case BrushPaint::TextureMapping::kStamping: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion( + BrushPaint::TextureOrigin texture_origin) { + switch (texture_origin) { + case BrushPaint::TextureOrigin::kStrokeSpaceOrigin: + case BrushPaint::TextureOrigin::kFirstStrokeInput: + case BrushPaint::TextureOrigin::kLastStrokeInput: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion( + BrushPaint::TextureSizeUnit texture_size_unit) { + switch (texture_size_unit) { + case BrushPaint::TextureSizeUnit::kBrushSize: + case BrushPaint::TextureSizeUnit::kStrokeCoordinates: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion(BrushPaint::TextureWrap texture_wrap) { + switch (texture_wrap) { + case BrushPaint::TextureWrap::kRepeat: + case BrushPaint::TextureWrap::kMirror: + case BrushPaint::TextureWrap::kClamp: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion(BrushPaint::BlendMode blend_mode) { + switch (blend_mode) { + case BrushPaint::BlendMode::kModulate: + case BrushPaint::BlendMode::kDstIn: + case BrushPaint::BlendMode::kDstOut: + case BrushPaint::BlendMode::kSrcAtop: + case BrushPaint::BlendMode::kSrcIn: + case BrushPaint::BlendMode::kSrcOver: + case BrushPaint::BlendMode::kDstOver: + case BrushPaint::BlendMode::kSrc: + case BrushPaint::BlendMode::kDst: + case BrushPaint::BlendMode::kSrcOut: + case BrushPaint::BlendMode::kDstAtop: + case BrushPaint::BlendMode::kXor: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion(BrushPaint::SelfOverlap self_overlap) { + switch (self_overlap) { + case BrushPaint::SelfOverlap::kAny: + case BrushPaint::SelfOverlap::kDiscard: + case BrushPaint::SelfOverlap::kAccumulate: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion( + const BrushPaint::TextureLayer& texture_layer) { + return std::max({CalculateMinimumRequiredVersion(texture_layer.mapping), + CalculateMinimumRequiredVersion(texture_layer.origin), + CalculateMinimumRequiredVersion(texture_layer.size_unit), + CalculateMinimumRequiredVersion(texture_layer.wrap_x), + CalculateMinimumRequiredVersion(texture_layer.wrap_y), + CalculateMinimumRequiredVersion(texture_layer.blend_mode)}); +} + +} // namespace + +Version CalculateMinimumRequiredVersion(const BrushPaint& paint) { + Version max_version = CalculateMinimumRequiredVersion(paint.self_overlap); + for (const BrushPaint::TextureLayer& layer : paint.texture_layers) { + max_version = std::max(max_version, CalculateMinimumRequiredVersion(layer)); + } + for (const ColorFunction& color_function : paint.color_functions) { + max_version = + std::max(max_version, CalculateMinimumRequiredVersion(color_function)); + } + return max_version; +} + void AddAttributeIdsRequiredByPaint( const BrushPaint& paint, absl::flat_hash_set& attribute_ids) { diff --git a/ink/brush/brush_paint.h b/ink/brush/brush_paint.h index 7ef4b083..539de104 100644 --- a/ink/brush/brush_paint.h +++ b/ink/brush/brush_paint.h @@ -23,6 +23,7 @@ #include "absl/status/status.h" #include "absl/time/time.h" #include "ink/brush/color_function.h" +#include "ink/brush/version.h" #include "ink/geometry/angle.h" #include "ink/geometry/mesh_format.h" #include "ink/geometry/vec.h" @@ -313,6 +314,10 @@ absl::Status ValidateBrushPaintTopLevel(const BrushPaint& paint); absl::Status ValidateBrushPaintTextureLayer( const BrushPaint::TextureLayer& layer); +// Calculates the minimum version of the Ink library that is required to use +// this brush paint. +Version CalculateMinimumRequiredVersion(const BrushPaint& paint); + // Adds the mesh attribute IDs that are required to properly render a mesh // with this brush paint to the given `attribute_ids` set. Note that other // attributes may also be required - either for core functionality (see diff --git a/ink/brush/brush_tip.cc b/ink/brush/brush_tip.cc index 128f52d7..4593a3c4 100644 --- a/ink/brush/brush_tip.cc +++ b/ink/brush/brush_tip.cc @@ -14,6 +14,7 @@ #include "ink/brush/brush_tip.h" +#include #include #include #include @@ -25,6 +26,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" #include "ink/brush/brush_behavior.h" +#include "ink/brush/version.h" #include "ink/geometry/angle.h" #include "ink/geometry/mesh_format.h" #include "ink/geometry/vec.h" @@ -115,6 +117,15 @@ absl::Status ValidateBrushTip(const BrushTip& tip) { return absl::OkStatus(); } +Version CalculateMinimumRequiredVersion(const BrushTip& tip) { + Version max_version = version::k1_0_0; + for (const BrushBehavior& behavior : tip.behaviors) { + max_version = + std::max(max_version, CalculateMinimumRequiredVersion(behavior)); + } + return max_version; +} + void AddAttributeIdsRequiredByTip( const BrushTip& tip, absl::flat_hash_set& attribute_ids) { diff --git a/ink/brush/brush_tip.h b/ink/brush/brush_tip.h index 79e99fcf..4ad78d83 100644 --- a/ink/brush/brush_tip.h +++ b/ink/brush/brush_tip.h @@ -22,6 +22,7 @@ #include "absl/container/flat_hash_set.h" #include "absl/status/status.h" #include "ink/brush/brush_behavior.h" +#include "ink/brush/version.h" #include "ink/geometry/angle.h" #include "ink/geometry/mesh_format.h" #include "ink/geometry/vec.h" @@ -115,6 +116,10 @@ namespace brush_internal { // BrushFamily, and returns an error if not. absl::Status ValidateBrushTip(const BrushTip& tip); +// Calculates the minimum version of the Ink library that is required to use +// this brush tip. +Version CalculateMinimumRequiredVersion(const BrushTip& tip); + // Adds the mesh attribute IDs that are required to properly render a mesh // with this brush tip to the given `attribute_ids` set. Note that other // attributes may also be required - either for core functionality (see diff --git a/ink/brush/color_function.cc b/ink/brush/color_function.cc index 61a57cca..b80cbc29 100644 --- a/ink/brush/color_function.cc +++ b/ink/brush/color_function.cc @@ -21,6 +21,7 @@ #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/types/span.h" +#include "ink/brush/version.h" #include "ink/color/color.h" namespace ink { @@ -77,6 +78,32 @@ absl::Status ValidateColorFunction(const ColorFunction& color_function) { color_function.parameters); } +namespace { + +Version CalculateMinimumRequiredVersion( + const ColorFunction::OpacityMultiplier& opacity) { + return version::k1_0_0; +} + +Version CalculateMinimumRequiredVersion( + const ColorFunction::ReplaceColor& replace) { + return version::k1_0_0; +} + +Version CalculateMinimumRequiredVersion( + const ColorFunction::Parameters& parameters) { + return std::visit( + [](const auto& params) { + return CalculateMinimumRequiredVersion(params); + }, + parameters); +} +} // namespace + +Version CalculateMinimumRequiredVersion(const ColorFunction& color_function) { + return CalculateMinimumRequiredVersion(color_function.parameters); +} + std::string ToFormattedString(const ColorFunction& color_function) { return ToFormattedString(color_function.parameters); } diff --git a/ink/brush/color_function.h b/ink/brush/color_function.h index 278fc8bd..08936fc1 100644 --- a/ink/brush/color_function.h +++ b/ink/brush/color_function.h @@ -22,6 +22,7 @@ #include "absl/status/status.h" #include "absl/types/span.h" +#include "ink/brush/version.h" #include "ink/color/color.h" namespace ink { @@ -66,6 +67,10 @@ namespace brush_internal { // BrushFamily, and returns an error if not. absl::Status ValidateColorFunction(const ColorFunction& color_function); +// Calculates the minimum version of the Ink library that is required to use +// this color function. +Version CalculateMinimumRequiredVersion(const ColorFunction& color_function); + std::string ToFormattedString(const ColorFunction& color_function); std::string ToFormattedString(const ColorFunction::Parameters& parameters); std::string ToFormattedString(const ColorFunction::OpacityMultiplier& opacity); diff --git a/ink/brush/easing_function.cc b/ink/brush/easing_function.cc index ca83375d..754be239 100644 --- a/ink/brush/easing_function.cc +++ b/ink/brush/easing_function.cc @@ -23,6 +23,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" +#include "ink/brush/version.h" #include "ink/geometry/point.h" namespace ink::brush_internal { @@ -143,6 +144,58 @@ absl::Status ValidateEasingFunction(const EasingFunction& easing_function) { easing_function.parameters); } +namespace { + +Version CalculateMinimumRequiredVersion( + const EasingFunction::Predefined predefined) { + switch (predefined) { + case EasingFunction::Predefined::kLinear: + case EasingFunction::Predefined::kEase: + case EasingFunction::Predefined::kEaseIn: + case EasingFunction::Predefined::kEaseOut: + case EasingFunction::Predefined::kEaseInOut: + case EasingFunction::Predefined::kStepStart: + case EasingFunction::Predefined::kStepEnd: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion( + const EasingFunction::CubicBezier& cubic_bezier) { + return version::k1_0_0; +} + +Version CalculateMinimumRequiredVersion(const EasingFunction::Linear& linear) { + return version::k1_0_0; +} + +Version CalculateMinimumRequiredVersion( + EasingFunction::StepPosition step_position) { + switch (step_position) { + case EasingFunction::StepPosition::kJumpEnd: + case EasingFunction::StepPosition::kJumpStart: + case EasingFunction::StepPosition::kJumpNone: + case EasingFunction::StepPosition::kJumpBoth: + return version::k1_0_0; + } +} + +Version CalculateMinimumRequiredVersion(const EasingFunction::Steps& steps) { + return CalculateMinimumRequiredVersion(steps.step_position); +} + +Version CalculateMinimumRequiredVersion( + const EasingFunction::Parameters& parameters) { + return std::visit( + [](auto&& params) { return CalculateMinimumRequiredVersion(params); }, + parameters); +} +} // namespace + +Version CalculateMinimumRequiredVersion(const EasingFunction& easing_function) { + return CalculateMinimumRequiredVersion(easing_function.parameters); +} + std::string ToFormattedString(const EasingFunction& easing_function) { return ToFormattedString(easing_function.parameters); } diff --git a/ink/brush/easing_function.h b/ink/brush/easing_function.h index a100ccb4..7074f1bb 100644 --- a/ink/brush/easing_function.h +++ b/ink/brush/easing_function.h @@ -21,6 +21,7 @@ #include #include "absl/status/status.h" +#include "ink/brush/version.h" #include "ink/geometry/point.h" namespace ink { @@ -168,6 +169,10 @@ namespace brush_internal { // BrushFamily, and returns an error if not. absl::Status ValidateEasingFunction(const EasingFunction& easing_function); +// Calculates the minimum version of the Ink library that is required to use +// this easing function. +Version CalculateMinimumRequiredVersion(const EasingFunction& easing_function); + std::string ToFormattedString(const EasingFunction& easing_function); std::string ToFormattedString(EasingFunction::Predefined predefined); std::string ToFormattedString(const EasingFunction::CubicBezier& cubic_bezier); diff --git a/ink/brush/version.h b/ink/brush/version.h new file mode 100644 index 00000000..3caf61d3 --- /dev/null +++ b/ink/brush/version.h @@ -0,0 +1,87 @@ +#ifndef INK_BRUSH_VERSION_H_ +#define INK_BRUSH_VERSION_H_ + +#include +#include + +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" + +namespace ink { + +struct Version { + int major = 1; + int minor = 0; + int bug = 0; + + enum class Cycle { + kAlpha = 0, + kBeta = 1, + kReleaseCandidate = 2, + kStable = 3 + }; + Cycle cycle = Cycle::kStable; + int release = 1; + + std::strong_ordering operator<=>(const Version& other) const = default; +}; + +inline std::string ToFormattedString(Version::Cycle cycle) { + switch (cycle) { + case Version::Cycle::kAlpha: + return "-alpha"; + case Version::Cycle::kBeta: + return "-beta"; + case Version::Cycle::kReleaseCandidate: + return "-rc"; + case Version::Cycle::kStable: + return ""; + } +} + +inline std::string ToFormattedString(Version version) { + return absl::StrCat( + version.major, ".", version.minor, ".", version.bug, + ToFormattedString(version.cycle), + (version.cycle == Version::Cycle::kStable + ? "" + : absl::StrCat(version.release < 10 ? "0" : "", version.release))); +} + +namespace version { + +constexpr Version k1_0_0 = {1, 0, 0, Version::Cycle::kStable, 1}; +constexpr Version kMin = k1_0_0; +constexpr Version k1_1_0_alpha_01 = {1, 1, 0, Version::Cycle::kAlpha, 1}; +constexpr Version kMax = k1_1_0_alpha_01; + +} // namespace version + +inline absl::Status ValidateVersion(Version version) { + if (version < version::kMin) { + return absl::InvalidArgumentError( + absl::StrCat("Version must be greater than or equal to ", + ToFormattedString(version::kMin), ", but was ", + ToFormattedString(version))); + } + if (version > version::kMax) { + return absl::InvalidArgumentError( + absl::StrCat("Version must be less than or equal to ", + ToFormattedString(version::kMax), ", but was ", + ToFormattedString(version))); + } + if (version.release < 1) { + return absl::InvalidArgumentError(absl::StrCat( + "Version::release must be greater than 0, but was ", version.release)); + } + if (version.cycle == Version::Cycle::kStable && version.release != 1) { + return absl::InvalidArgumentError( + absl::StrCat("Version::release must be 1 for stable cycle, but was ", + version.release)); + } + return absl::OkStatus(); +} + +} // namespace ink + +#endif // INK_BRUSH_VERSION_H_ diff --git a/ink/storage/BUILD.bazel b/ink/storage/BUILD.bazel index fae16512..bce4ceec 100644 --- a/ink/storage/BUILD.bazel +++ b/ink/storage/BUILD.bazel @@ -309,11 +309,13 @@ cc_library( "//ink/brush:brush_tip", "//ink/brush:color_function", "//ink/brush:easing_function", + "//ink/brush:version", "//ink/geometry:angle", "//ink/geometry:point", "//ink/geometry:vec", "//ink/storage/proto:brush_cc_proto", "//ink/storage/proto:brush_family_cc_proto", + "//ink/storage/proto:options_cc_proto", "//ink/storage/proto:stroke_input_batch_cc_proto", "//ink/types:duration", "@com_google_absl//absl/container:flat_hash_set", @@ -344,20 +346,24 @@ cc_test( "//ink/brush:easing_function", "//ink/brush:fuzz_domains", "//ink/brush:type_matchers", + "//ink/brush:version", "//ink/color", - "//ink/geometry:vec", "//ink/storage/proto:brush_cc_proto", "//ink/storage/proto:brush_family_cc_proto", "//ink/storage/proto:color_cc_proto", + "//ink/storage/proto:options_cc_proto", "//ink/storage/proto:stroke_input_batch_cc_proto", "//ink/types:duration", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/status", "@com_google_absl//absl/status:status_matchers", "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:string_view", "@com_google_absl//absl/time", "@com_google_fuzztest//fuzztest", "@com_google_fuzztest//fuzztest:fuzztest_gtest_main", + "@protobuf", ], ) diff --git a/ink/storage/brush.cc b/ink/storage/brush.cc index f63f4856..101d5258 100644 --- a/ink/storage/brush.cc +++ b/ink/storage/brush.cc @@ -38,12 +38,14 @@ #include "ink/brush/brush_tip.h" #include "ink/brush/color_function.h" #include "ink/brush/easing_function.h" +#include "ink/brush/version.h" #include "ink/geometry/angle.h" #include "ink/geometry/point.h" #include "ink/geometry/vec.h" #include "ink/storage/color.h" #include "ink/storage/proto/brush.pb.h" #include "ink/storage/proto/brush_family.pb.h" +#include "ink/storage/proto/options.pb.h" #include "ink/storage/proto/stroke_input_batch.pb.h" #include "ink/types/duration.h" @@ -1692,6 +1694,56 @@ void EncodeBrushFamilyTextureMap( } } +void EncodeBrushFamilyVersion(Version version, + proto::Version& version_proto_out) { + version_proto_out.set_major(version.major); + version_proto_out.set_minor(version.minor); + version_proto_out.set_bug(version.bug); + switch (version.cycle) { + case Version::Cycle::kAlpha: + version_proto_out.set_cycle(proto::Version::CYCLE_ALPHA); + break; + case Version::Cycle::kBeta: + version_proto_out.set_cycle(proto::Version::CYCLE_BETA); + break; + case Version::Cycle::kReleaseCandidate: + version_proto_out.set_cycle(proto::Version::CYCLE_RELEASE_CANDIDATE); + break; + case Version::Cycle::kStable: + version_proto_out.set_cycle(proto::Version::CYCLE_STABLE); + break; + } + version_proto_out.set_release(version.release); +} + +absl::StatusOr DecodeBrushFamilyVersion( + const proto::Version& version_proto) { + Version version; + version.major = version_proto.major(); + version.minor = version_proto.minor(); + version.bug = version_proto.bug(); + switch (version_proto.cycle()) { + case proto::Version::CYCLE_ALPHA: + version.cycle = Version::Cycle::kAlpha; + break; + case proto::Version::CYCLE_BETA: + version.cycle = Version::Cycle::kBeta; + break; + case proto::Version::CYCLE_RELEASE_CANDIDATE: + version.cycle = Version::Cycle::kReleaseCandidate; + break; + case proto::Version::CYCLE_STABLE: + version.cycle = Version::Cycle::kStable; + break; + default: + return absl::InvalidArgumentError(absl::StrCat( + "Failed to decode BrushFamily version: unrecognized cycle ", + version_proto.cycle())); + } + version.release = version_proto.release(); + return version; +} + void EncodeBrushFamily(const BrushFamily& family, proto::BrushFamily& family_proto_out, TextureBitmapProvider get_bitmap) { @@ -1719,6 +1771,9 @@ void EncodeBrushFamily(const BrushFamily& family, } else { family_proto_out.set_developer_comment(metadata.developer_comment); } + + EncodeBrushFamilyVersion(family.CalculateMinimumRequiredVersion(), + *family_proto_out.mutable_min_version()); } absl::StatusOr> DecodeBrushFamilyCoats( @@ -1741,6 +1796,25 @@ absl::StatusOr> DecodeBrushFamilyCoats( absl::StatusOr DecodeBrushFamily( const proto::BrushFamily& family_proto, ClientTextureIdProviderAndBitmapReceiver get_client_texture_id) { + // Check that the BrushFamily proto is compatible with the current library + // version. + if (family_proto.has_min_version()) { + absl::StatusOr required_version = + DecodeBrushFamilyVersion(family_proto.min_version()); + // The decoded version is not saved anywhere; we only care whether it's + // compatible with the current library version or not when deserializing. + if (!required_version.ok()) { + return required_version.status(); + } + // DecodeBrushFamilyVersion() only ensures that the proto::Version is + // capable of being decoded into a ink::Version. We also need to check that + // it's valid and compatible with the current library version. + if (absl::Status status = ValidateVersion(*required_version); + !status.ok()) { + return status; + } + } + // ID map that also serves as a record of the IDs for which we've already // called `get_client_texture_id`. std::map old_to_new_id = {}; diff --git a/ink/storage/brush.h b/ink/storage/brush.h index e11de4e6..a497a09c 100644 --- a/ink/storage/brush.h +++ b/ink/storage/brush.h @@ -26,8 +26,10 @@ #include "ink/brush/brush_family.h" #include "ink/brush/brush_paint.h" #include "ink/brush/brush_tip.h" +#include "ink/brush/version.h" #include "ink/storage/proto/brush.pb.h" #include "ink/storage/proto/brush_family.pb.h" +#include "ink/storage/proto/options.pb.h" namespace ink { @@ -90,6 +92,8 @@ absl::StatusOr DecodeBrushFamily( [](const std::string& encoded_id, const std::string& bitmap) { return encoded_id; }); +absl::StatusOr DecodeBrushFamilyVersion( + const proto::Version& version_proto); absl::StatusOr DecodeBrushCoat( const proto::BrushCoat& coat_proto, ClientTextureIdProvider get_client_texture_id = diff --git a/ink/storage/brush_test.cc b/ink/storage/brush_test.cc index feb80419..323260d2 100644 --- a/ink/storage/brush_test.cc +++ b/ink/storage/brush_test.cc @@ -14,6 +14,7 @@ #include "ink/storage/brush.h" +#include #include #include #include @@ -22,9 +23,11 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "fuzztest/fuzztest.h" +#include "absl/container/flat_hash_set.h" #include "absl/status/status.h" #include "absl/status/status_matchers.h" #include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "ink/brush/brush.h" @@ -37,15 +40,18 @@ #include "ink/brush/easing_function.h" #include "ink/brush/fuzz_domains.h" #include "ink/brush/type_matchers.h" +#include "ink/brush/version.h" #include "ink/color/color.h" -#include "ink/geometry/vec.h" #include "ink/storage/color.h" #include "ink/storage/proto/brush.pb.h" #include "ink/storage/proto/brush_family.pb.h" #include "ink/storage/proto/color.pb.h" +#include "ink/storage/proto/options.pb.h" #include "ink/storage/proto/stroke_input_batch.pb.h" #include "ink/storage/proto_matchers.h" #include "ink/types/duration.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/message.h" namespace ink { namespace { @@ -55,7 +61,9 @@ using ::absl_testing::IsOkAndHolds; using ::absl_testing::StatusIs; using ::testing::AllOf; using ::testing::ElementsAre; +using ::testing::Eq; using ::testing::Field; +using ::testing::Ge; using ::testing::HasSubstr; using ::testing::IsNull; using ::testing::SizeIs; @@ -399,6 +407,13 @@ TEST(BrushTest, EncodeBrushWithoutTextureMap) { brush_proto.mutable_brush_family() ->mutable_input_model() ->mutable_spring_model(); + proto::Version* min_version_proto = + brush_proto.mutable_brush_family()->mutable_min_version(); + min_version_proto->set_major(1); + min_version_proto->set_minor(0); + min_version_proto->set_bug(0); + min_version_proto->set_cycle(proto::Version::CYCLE_STABLE); + min_version_proto->set_release(1); proto::BrushCoat* coat_proto = brush_proto.mutable_brush_family()->add_coats(); coat_proto->mutable_tip()->set_scale_x(1.f); @@ -482,6 +497,13 @@ TEST(BrushTest, EncodeBrushWithTextureMap) { brush_proto.mutable_brush_family() ->mutable_input_model() ->mutable_spring_model(); + proto::Version* min_version_proto = + brush_proto.mutable_brush_family()->mutable_min_version(); + min_version_proto->set_major(1); + min_version_proto->set_minor(0); + min_version_proto->set_bug(0); + min_version_proto->set_cycle(proto::Version::CYCLE_STABLE); + min_version_proto->set_release(1); brush_proto.mutable_brush_family()->mutable_texture_id_to_bitmap()->insert( {std::string(kTestTextureId1), TestPngBytes1x1()}); proto::BrushCoat* coat_proto = @@ -904,5 +926,298 @@ void EncodeDecodeValidBrushBehaviorNodeRoundTrip( FUZZ_TEST(BrushTest, EncodeDecodeValidBrushBehaviorNodeRoundTrip) .WithDomains(SerializableBrushBehaviorNode()); +void GetMaxProtoVersion(const ::google::protobuf::Message& message, + Version& max_version_out) { + const ::google::protobuf::Descriptor* descriptor = message.GetDescriptor(); + if (descriptor->options().HasExtension(ink::proto::message_min_version)) { + absl::StatusOr message_version = DecodeBrushFamilyVersion( + descriptor->options().GetExtension(ink::proto::message_min_version)); + ASSERT_THAT(message_version, IsOk()); + EXPECT_THAT(ValidateVersion(message_version.value()), IsOk()); + max_version_out = std::max(max_version_out, message_version.value()); + } + + const ::google::protobuf::Reflection* reflection = message.GetReflection(); + std::vector fields; + reflection->ListFields(message, &fields); + + for (const auto* field : fields) { + if (field->options().HasExtension(ink::proto::field_min_version)) { + absl::StatusOr field_version = DecodeBrushFamilyVersion( + field->options().GetExtension(ink::proto::field_min_version)); + ASSERT_THAT(field_version, IsOk()); + EXPECT_THAT(ValidateVersion(field_version.value()), IsOk()); + max_version_out = std::max(max_version_out, field_version.value()); + } + + if (field->cpp_type() == ::google::protobuf::FieldDescriptor::CPPTYPE_ENUM) { + if (field->is_repeated()) { + for (int i = 0; i < reflection->FieldSize(message, field); ++i) { + const ::google::protobuf::EnumValueDescriptor* enum_value = + reflection->GetRepeatedEnum(message, field, i); + if (enum_value->options().HasExtension( + ink::proto::enum_value_min_version)) { + absl::StatusOr enum_value_version = + DecodeBrushFamilyVersion(enum_value->options().GetExtension( + ink::proto::enum_value_min_version)); + ASSERT_THAT(enum_value_version, IsOk()); + EXPECT_THAT(ValidateVersion(enum_value_version.value()), IsOk()); + max_version_out = + std::max(max_version_out, enum_value_version.value()); + } + } + } else { + const ::google::protobuf::EnumValueDescriptor* enum_value = + reflection->GetEnum(message, field); + if (enum_value->options().HasExtension( + ink::proto::enum_value_min_version)) { + absl::StatusOr enum_value_version = + DecodeBrushFamilyVersion(enum_value->options().GetExtension( + ink::proto::enum_value_min_version)); + ASSERT_THAT(enum_value_version, IsOk()); + EXPECT_THAT(ValidateVersion(enum_value_version.value()), IsOk()); + max_version_out = + std::max(max_version_out, enum_value_version.value()); + } + } + } else if (field->cpp_type() == + ::google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + if (field->is_repeated()) { + for (int i = 0; i < reflection->FieldSize(message, field); ++i) { + GetMaxProtoVersion(reflection->GetRepeatedMessage(message, field, i), + max_version_out); + } + } else { + GetMaxProtoVersion(reflection->GetMessage(message, field), + max_version_out); + } + } + } +} + +// Tests that CalculateMinimumRequiredVersion returns the same version as is +// annotated in the proto options corresponding to the messages, fields, and +// enums in the serialized form of the BrushFamily. +void CalculateMinimumRequiredVersionMatchesProtoOptions( + const BrushFamily& family) { + // Encoding uses CalculateMinimumRequiredVersion to set the min version. + proto::BrushFamily family_proto; + EncodeBrushFamily(family, family_proto); + absl::StatusOr min_version_calculated = + DecodeBrushFamilyVersion(family_proto.min_version()); + ASSERT_THAT(min_version_calculated, IsOk()); + EXPECT_THAT(ValidateVersion(min_version_calculated.value()), IsOk()); + + Version min_version_from_options = + Version(0, 0, 0, Version::Cycle::kStable, 0); + GetMaxProtoVersion(family_proto, min_version_from_options); + // Using reflection, examine the minimum required version of each + // field/message/enum in the proto, and find the maximum. + EXPECT_THAT(min_version_calculated.value(), Eq(min_version_from_options)); +} +FUZZ_TEST(BrushTest, CalculateMinimumRequiredVersionMatchesProtoOptions) + .WithDomains(SerializableBrushFamily()); + +void CheckMinVersionExistsAndIsValid( + const ::google::protobuf::Descriptor* descriptor, + absl::flat_hash_set& visited_messages, + absl::flat_hash_set& visited_enums) { + if (visited_messages.contains(descriptor) || + descriptor->file()->name() != + "third_party/ink/storage/proto/brush_family.proto" || + descriptor->options().map_entry()) { + // Ignore messages that have already been visited, that are not in the brush + // family proto file, or that are map entries (impossible to add options to, + // since they are generated by the map field). + return; + } + visited_messages.insert(descriptor); + + EXPECT_TRUE( + descriptor->options().HasExtension(ink::proto::message_min_version)) + << "Message " << descriptor->full_name() + << " is missing message_min_version option."; + absl::StatusOr message_version = DecodeBrushFamilyVersion( + descriptor->options().GetExtension(ink::proto::message_min_version)); + ASSERT_THAT(message_version, IsOk()); + EXPECT_THAT(ValidateVersion(message_version.value()), IsOk()); + + for (int i = 0; i < descriptor->field_count(); ++i) { + const ::google::protobuf::FieldDescriptor* field = descriptor->field(i); + EXPECT_TRUE(field->options().HasExtension(ink::proto::field_min_version)) + << "Field " << field->full_name() + << " is missing field_min_version option."; + absl::StatusOr field_version = DecodeBrushFamilyVersion( + field->options().GetExtension(ink::proto::field_min_version)); + ASSERT_THAT(field_version, IsOk()); + EXPECT_THAT(ValidateVersion(field_version.value()), IsOk()); + if (field->cpp_type() == ::google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + CheckMinVersionExistsAndIsValid(field->message_type(), visited_messages, + visited_enums); + } else if (field->cpp_type() == ::google::protobuf::FieldDescriptor::CPPTYPE_ENUM) { + const ::google::protobuf::EnumDescriptor* enum_descriptor = field->enum_type(); + if (enum_descriptor->file()->name() != + "third_party/ink/storage/proto/brush_family.proto") { + continue; + } + if (visited_enums.contains(enum_descriptor)) continue; + visited_enums.insert(enum_descriptor); + for (int j = 0; j < enum_descriptor->value_count(); ++j) { + const ::google::protobuf::EnumValueDescriptor* enum_value = + enum_descriptor->value(j); + EXPECT_TRUE(enum_value->options().HasExtension( + ink::proto::enum_value_min_version)) + << "Enum value " << enum_value->full_name() + << " is missing enum_value_min_version option."; + absl::StatusOr enum_value_version = + DecodeBrushFamilyVersion(enum_value->options().GetExtension( + ink::proto::enum_value_min_version)); + ASSERT_THAT(enum_value_version, IsOk()); + EXPECT_THAT(ValidateVersion(enum_value_version.value()), IsOk()); + } + } + } +} + +TEST(BrushTest, + AllBrushFamilyProtoMessagesFieldsAndEnumsHaveValidMinimumVersion) { + // Ensure that all BrushFamily proto messages, fields, and + // enums have a `minimum_version` option set, and it is valid: + // - Lower than or equal to version::kMax + // - Greater than or equal to version::k1_0_0 + // - All release values must be greater than 0 + // - Stable cycles must have a release value of 1 + // This ensures that they are well documented, valid to be loaded, and will be + // covered by the `CalculateMinimumRequiredVersionMatchesProtoOptions` test. + absl::flat_hash_set visited_messages; + absl::flat_hash_set visited_enums; + CheckMinVersionExistsAndIsValid(proto::BrushFamily::descriptor(), + visited_messages, visited_enums); +} +TEST(BrushTest, DecodeBrushFamilyFailsWithOutOfRangeMinVersion) { + // First an absurdly high version: + proto::BrushFamily family_proto; + proto::Version* min_version = family_proto.mutable_min_version(); + min_version->set_major(google::protobuf::Syntax_INT_MAX_SENTINEL_DO_NOT_USE_); + min_version->set_minor(google::protobuf::Syntax_INT_MAX_SENTINEL_DO_NOT_USE_); + min_version->set_bug(google::protobuf::Syntax_INT_MAX_SENTINEL_DO_NOT_USE_); + min_version->set_cycle(proto::Version::CYCLE_STABLE); + min_version->set_release( + google::protobuf::Syntax_INT_MAX_SENTINEL_DO_NOT_USE_); + + EXPECT_THAT( + DecodeBrushFamily(family_proto), + StatusIs(absl::StatusCode::kInvalidArgument, + absl::StrCat("Version must be less than or equal to ", + ToFormattedString(version::kMax), ", but was ", + "2147483647.2147483647.2147483647"))); + + // Now a version as close to version::kMax as possible. + Version max_plus_one = version::kMax; + max_plus_one.release++; + min_version->set_major(max_plus_one.major); + min_version->set_minor(max_plus_one.minor); + min_version->set_bug(max_plus_one.bug); + switch (max_plus_one.cycle) { + case Version::Cycle::kAlpha: + min_version->set_cycle(proto::Version::CYCLE_ALPHA); + break; + case Version::Cycle::kBeta: + min_version->set_cycle(proto::Version::CYCLE_BETA); + break; + case Version::Cycle::kReleaseCandidate: + min_version->set_cycle(proto::Version::CYCLE_RELEASE_CANDIDATE); + break; + case Version::Cycle::kStable: + min_version->set_cycle(proto::Version::CYCLE_STABLE); + break; + } + min_version->set_release(max_plus_one.release); + + EXPECT_THAT( + DecodeBrushFamily(family_proto), + StatusIs(absl::StatusCode::kInvalidArgument, + absl::StrCat("Version must be less than or equal to ", + ToFormattedString(version::kMax), ", but was ", + ToFormattedString(max_plus_one)))); + + // Now a version as close to version::kMin as possible. + Version min_minus_one = version::kMin; + min_minus_one.release--; + min_version->set_major(min_minus_one.major); + min_version->set_minor(min_minus_one.minor); + min_version->set_bug(min_minus_one.bug); + switch (min_minus_one.cycle) { + case Version::Cycle::kAlpha: + min_version->set_cycle(proto::Version::CYCLE_ALPHA); + break; + case Version::Cycle::kBeta: + min_version->set_cycle(proto::Version::CYCLE_BETA); + break; + case Version::Cycle::kReleaseCandidate: + min_version->set_cycle(proto::Version::CYCLE_RELEASE_CANDIDATE); + break; + case Version::Cycle::kStable: + min_version->set_cycle(proto::Version::CYCLE_STABLE); + break; + } + min_version->set_release(min_minus_one.release); + EXPECT_THAT( + DecodeBrushFamily(family_proto), + StatusIs(absl::StatusCode::kInvalidArgument, + absl::StrCat("Version must be greater than or equal to ", + ToFormattedString(version::kMin), ", but was ", + ToFormattedString(min_minus_one)))); + + // Now a version with a release value of 0. + min_version->set_major(1); + min_version->set_minor(1); + min_version->set_bug(0); + min_version->set_cycle(proto::Version::CYCLE_ALPHA); + min_version->set_release(0); + EXPECT_THAT(DecodeBrushFamily(family_proto), + StatusIs(absl::StatusCode::kInvalidArgument, + "Version::release must be greater than 0, but was 0")); + + // Now a version with a stable cycle and a release value of 2. + min_version->set_major(1); + min_version->set_minor(0); + min_version->set_bug(0); + min_version->set_cycle(proto::Version::CYCLE_STABLE); + min_version->set_release(2); + EXPECT_THAT( + DecodeBrushFamily(family_proto), + StatusIs(absl::StatusCode::kInvalidArgument, + "Version::release must be 1 for stable cycle, but was 2")); +} + +TEST(BrushTest, DecodeBrushFamilyFailsWithUnrecognizedVersionCycle) { + proto::BrushFamily family_proto; + proto::Version* min_version = family_proto.mutable_min_version(); + min_version->set_major(1); + min_version->set_minor(0); + min_version->set_bug(0); + min_version->set_cycle(proto::Version::Cycle( + proto::Version::Cycle:: + Version_Cycle_Version_Cycle_INT_MAX_SENTINEL_DO_NOT_USE_)); + min_version->set_release(1); + + EXPECT_THAT(DecodeBrushFamily(family_proto), + StatusIs(absl::StatusCode::kInvalidArgument, + "Failed to decode BrushFamily version: unrecognized " + "cycle 2147483647")); + + min_version->set_cycle(proto::Version::CYCLE_UNSPECIFIED); + EXPECT_THAT(DecodeBrushFamily(family_proto), + StatusIs(absl::StatusCode::kInvalidArgument, + "Failed to decode BrushFamily version: unrecognized " + "cycle 0")); +} + +TEST(BrushTest, DecodeBrushFamilyIsOkWithNoMinVersionSet) { + proto::BrushFamily family_proto; + EXPECT_THAT(DecodeBrushFamily(family_proto), IsOk()); +} + } // namespace } // namespace ink diff --git a/ink/storage/proto/BUILD.bazel b/ink/storage/proto/BUILD.bazel index 4550861d..d155cbdb 100644 --- a/ink/storage/proto/BUILD.bazel +++ b/ink/storage/proto/BUILD.bazel @@ -18,6 +18,7 @@ load("@protobuf//bazel:cc_proto_library.bzl", "cc_proto_library") load("@protobuf//bazel:proto_library.bzl", "proto_library") +load("//tools/build_defs/proto:descriptor_set.bzl", "transitive_descriptor_set") package( default_visibility = ["//visibility:public"], @@ -37,6 +38,7 @@ proto_library( srcs = ["brush_family.proto"], deps = [ ":color_proto", + ":options_proto", ], ) @@ -45,6 +47,14 @@ proto_library( srcs = ["color.proto"], ) +proto_library( + name = "options_proto", + srcs = ["options.proto"], + deps = [ + "//net/proto2/proto:descriptor", + ], +) + proto_library( name = "stroke_input_batch_proto", srcs = ["stroke_input_batch.proto"], @@ -94,6 +104,11 @@ cc_proto_library( deps = [":color_proto"], ) +cc_proto_library( + name = "options_cc_proto", + deps = [":options_proto"], +) + cc_proto_library( name = "stroke_input_batch_cc_proto", deps = [":stroke_input_batch_proto"], @@ -108,3 +123,8 @@ cc_proto_library( name = "coded_numeric_run_cc_proto", deps = [":coded_numeric_run_proto"], ) + +transitive_descriptor_set( + name = "brush_family_proto_descriptor_set", + deps = [":brush_family_proto"], +) diff --git a/ink/storage/proto/brush_family.proto b/ink/storage/proto/brush_family.proto index a07c3a2f..921b8924 100644 --- a/ink/storage/proto/brush_family.proto +++ b/ink/storage/proto/brush_family.proto @@ -19,44 +19,121 @@ syntax = "proto2"; package ink.proto; import "ink/storage/proto/color.proto"; +import "ink/storage/proto/options.proto"; // Specifies a list of `BrushCoat`s that determine the stroke shape and dynamic // input response and the shading of the stroke geometry. The `BrushFamily` can // optionally be identified by setting a non-empty `client_brush_family_id`. message BrushFamily { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; // Spring-based input modeler. Stored in the `InputModel` variant below to // allow future input models to be added without changing the shape of // existing strokes. - message SpringModel {} + message SpringModel { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + } // A naive model that passes through raw inputs mostly unchanged. This is an // experimental configuration which may be adjusted or removed later. Strokes // generated with this input model might change shape if read with a later // version of the code that has removed this feature. - message ExperimentalNaiveModel {} + message ExperimentalNaiveModel { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + } // Averages nearby inputs together within a sliding time window. message SlidingWindowModel { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // The duration over which to average together nearby raw inputs. Typically // this should be somewhere in the 1 ms to 100 ms range. The default value // is 20 ms. - optional float window_size_seconds = 1 [default = 0.02]; + optional float window_size_seconds = 1 [ + default = 0.02, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // The maximum duration between modeled inputs; if raw inputs are spaced // more than this far apart in time, then additional modeled inputs will be // inserted between them. Set this to infinity to disable upsampling. The // default value is 1/180 seconds. // // This is an experimental field which may be removed later. - optional float experimental_upsampling_period_seconds = 2 - [default = 0.00555555555]; + optional float experimental_upsampling_period_seconds = 2 [ + default = 0.00555555555, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; } message InputModel { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + oneof input_model { - SpringModel spring_model = 2; - ExperimentalNaiveModel experimental_naive_model = 4; - SlidingWindowModel sliding_window_model = 5; + SpringModel spring_model = 2 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + ExperimentalNaiveModel experimental_naive_model = 4 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + SlidingWindowModel sliding_window_model = 5 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Removed InputModel types go here (reserved needs to be outside oneof). reserved 1; @@ -67,7 +144,13 @@ message BrushFamily { // drawn by a multi-coat brush is rendered, each coat of paint will be drawn // entirely atop the previous coat, even if the stroke crosses over itself, as // though each coat were painted in its entirety one at a time. - repeated BrushCoat coats = 4; + repeated BrushCoat coats = 4 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Was `tip` and `paint`, use `coats[0].tip` and `coats[0].paint` // instead. @@ -75,24 +158,59 @@ message BrushFamily { // The ID for this brush family specified by the client that created it. The // meaning of this ID is determined by that client. - optional string client_brush_family_id = 3; + optional string client_brush_family_id = 3 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Specifies a model for turning a sequence of raw hardware inputs (e.g. from // a stylus, touchscreen, or mouse) into a sequence of smoothed, modeled // inputs. Raw hardware inputs tend to be noisy, and must be smoothed before // being passed into a brush's behaviors and extruded into a mesh in order to // get a good-looking stroke. - optional InputModel input_model = 5; + optional InputModel input_model = 5 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // A mapping of texture IDs (as used in `BrushPaint.TextureLayer`) to bitmaps // in PNG format. - map texture_id_to_bitmap = 6; + map texture_id_to_bitmap = 6 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // A multi-line, human-readable string with a description of the brush and how // it works, with the intended audience being designers/developers who are // editing the brush definition. This string is not generally intended to be // displayed to end users. - optional string developer_comment = 7; + optional string developer_comment = 7 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + + // The minimum version of the Ink API that this brush family is compatible + // with. This field should not be set by clients, only by the Ink serializer, + // as it is computed based on the features used in the brush family. + optional Version min_version = 8 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // A `BrushCoat` represents one coat of ink applied by a brush. It includes a @@ -105,11 +223,40 @@ message BrushFamily { // the previous coat, even if the stroke crosses over itself, as though each // coat were painted in its entirety one at a time. message BrushCoat { - optional BrushTip tip = 1; - optional BrushPaint paint = 2 [deprecated = true]; + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + + optional BrushTip tip = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + optional BrushPaint paint = 2 [ + deprecated = true, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // If empty, the `paint` field above will be used as the only entry. If that // is also not set, the default `BrushPaint` will be used. - repeated BrushPaint paint_preferences = 3; + repeated BrushPaint paint_preferences = 3 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Parameters that control how stroke inputs are used to model the tip shape and @@ -126,20 +273,55 @@ message BrushCoat { // color when drawing. The default values produce a static circular tip shape // with diameter equal to the `Brush` size and no color shift. message BrushTip { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // Scale used to calculate the baseline width of the tip shape relative to the // brush size prior to applying `slant` and `rotation`. The baseline width of // the tip will be equal to the brush size multiplied by `scale_x`. Valid // values must be finite and non-negative, with at least one of `scale_x` and // `scale_y` greater than zero. - optional float scale_x = 1 [default = 1.0]; + optional float scale_x = 1 [ + default = 1.0, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // Same as `scale_x`, but for baseline height. - optional float scale_y = 2 [default = 1.0]; + optional float scale_y = 2 [ + default = 1.0, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // A normalized value in the range [0, 1] that is used to calculate the // baseline radius of curvature for the tip's corners. A value of 0 results in // sharp corners and a value of 1 results in the maximum radius of curvature // given the current tip dimensions. - optional float corner_rounding = 3 [default = 1.0]; + optional float corner_rounding = 3 [ + default = 1.0, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // Angle used to calculate the baseline slant of the tip shape prior to // applying `rotation` and `pinch`. @@ -152,7 +334,13 @@ message BrushTip { // The value should be in the range [-π/2, π/2] radians, and represents the // angle by which "vertical" lines of the tip shape will appear rotated about // their intersection with the x-axis. - optional float slant_radians = 4; + optional float slant_radians = 4 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // A unitless parameter in the range [0, 1] that controls the baseline // separation between two of the shape's corners prior to applying `rotation`. @@ -169,11 +357,23 @@ message BrushTip { // in a (possibly slanted) trapezoidal shape. // - A value of 1 will make the two corners coincide and result in a // triangular shape. - optional float pinch = 5; + optional float pinch = 5 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Angle specifying the baseline rotation of the tip shape after applying // `scale`, `pinch`, and `slant`. - optional float rotation_radians = 6; + optional float rotation_radians = 6 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Was `opacity_multiplier`; use brush paint color functions instead. reserved 8; @@ -186,7 +386,14 @@ message BrushTip { // Otherwise, the stroke will be made up of particles. A new particle will be // emitted after at least `particle_gap_distance_scale * brush_size` distance // has been traveled by the stoke inputs. - optional float particle_gap_distance_scale = 9; + optional float particle_gap_distance_scale = 9 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Parameter controlling emission of particles as a function of time elapsed // along the stroke. The value must be finite and non-negative. @@ -195,34 +402,87 @@ message BrushTip { // be continuous, unless gaps are introduced dynamically by `BrushBehavior`s. // Otherwise, the stroke will be made up of particles. Particles will be // emitted at most once every `particle_gap_duration`. - optional float particle_gap_duration_seconds = 10; + optional float particle_gap_duration_seconds = 10 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Behaviors affecting this tip. - repeated BrushBehavior behaviors = 7; + repeated BrushBehavior behaviors = 7 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Parameters that describe how a stroke mesh should be rendered. message BrushPaint { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + message TextureLayer { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // LINT.IfChange(texture_wrap) // Texture wrapping modes for specifying `TextureLayer::wrap_x` and // `wrap_y`. enum Wrap { - WRAP_UNSPECIFIED = 0; + WRAP_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Repeats texture image horizontally/vertically. - WRAP_REPEAT = 1; + WRAP_REPEAT = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Repeats texture image horizontally/vertically, alternating mirror // images so that adjacent edges always match. - WRAP_MIRROR = 2; + WRAP_MIRROR = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Points outside of the texture have the color of the nearest texture // edge point. This mode is typically most useful when the edge pixels of // the texture image are all the same, e.g. either transparent or a single // solid color. - WRAP_CLAMP = 3; + WRAP_CLAMP = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_paint.h:texture_wrap) @@ -230,13 +490,31 @@ message BrushPaint { // Units for specifying `TextureLayer::size`. enum SizeUnit { - SIZE_UNIT_UNSPECIFIED = 0; + SIZE_UNIT_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // In the same units as the provided `StrokeInput` position. - SIZE_UNIT_STROKE_COORDINATES = 1; + SIZE_UNIT_STROKE_COORDINATES = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // As multiples of brush size. - SIZE_UNIT_BRUSH_SIZE = 2; + SIZE_UNIT_BRUSH_SIZE = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; reserved 3; } @@ -246,19 +524,43 @@ message BrushPaint { // Specification of the origin point to use for the texture. enum Origin { - ORIGIN_UNSPECIFIED = 0; + ORIGIN_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The texture origin is the origin of stroke space, however that happens // to be defined for a given stroke. - ORIGIN_STROKE_SPACE_ORIGIN = 1; + ORIGIN_STROKE_SPACE_ORIGIN = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The texture origin is the first input position for the stroke. - ORIGIN_FIRST_STROKE_INPUT = 2; + ORIGIN_FIRST_STROKE_INPUT = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The texture origin is the last input position (including predicted // inputs) for the stroke. Note that this means that the texture origin // for an in-progress stroke will move as more inputs are added. - ORIGIN_LAST_STROKE_INPUT = 3; + ORIGIN_LAST_STROKE_INPUT = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_paint.h:texture_origin) @@ -266,18 +568,36 @@ message BrushPaint { // Specification of how the texture should be applied to the stroke. enum Mapping { - MAPPING_UNSPECIFIED = 0; + MAPPING_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The texture will repeat according to a 2D affine transformation of // vertex positions. Each copy of the texture will have the same size and // shape modulo reflections. - MAPPING_TILING = 1; + MAPPING_TILING = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // This mode is intended for use with particle brush coats (i.e. with a // brush tip with a nonzero particle gap). A copy of the texture (or one // animation frame thereof) will be "stamped" onto each particle of the // stroke, scaled or rotated appropriately to cover the whole particle. - MAPPING_STAMPING = 2; + MAPPING_STAMPING = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_paint.h:texture_mapping) @@ -286,7 +606,13 @@ message BrushPaint { // Setting for how an incoming ("source" / "src") color should be combined // with the already present ("destination" / "dst") color at a given pixel. enum BlendMode { - BLEND_MODE_UNSPECIFIED = 0; + BLEND_MODE_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Source and destination are component-wise multiplied, including // opacity. @@ -294,7 +620,13 @@ message BrushPaint { // Alpha = Alpha_src * Alpha_dst // // Color = Color_src * Color_dst - BLEND_MODE_MODULATE = 1; + BLEND_MODE_MODULATE = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Keeps destination pixels that cover source pixels. Discards remaining // source and destination pixels. @@ -302,7 +634,13 @@ message BrushPaint { // Alpha = Alpha_src * Alpha_dst // // Color = Alpha_src * Color_dst - BLEND_MODE_DST_IN = 2; + BLEND_MODE_DST_IN = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Keeps the destination pixels not covered by source pixels. Discards // destination pixels that are covered by source pixels and all source @@ -311,7 +649,13 @@ message BrushPaint { // Alpha = (1 - Alpha_src) * Alpha_dst // // Color = (1 - Alpha_src) * Color_dst - BLEND_MODE_DST_OUT = 3; + BLEND_MODE_DST_OUT = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Discards source pixels that do not cover destination pixels. // Draws remaining pixels over destination pixels. @@ -319,7 +663,13 @@ message BrushPaint { // Alpha = Alpha_dst // // Color = Alpha_dst * Color_src + (1 - Alpha_src) * Color_dst - BLEND_MODE_SRC_ATOP = 4; + BLEND_MODE_SRC_ATOP = 4 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Keeps the source pixels that cover destination pixels. Discards // remaining source and destination pixels. @@ -327,21 +677,39 @@ message BrushPaint { // Alpha = Alpha_src * Alpha_dst // // Color = Color_src * Alpha_dst - BLEND_MODE_SRC_IN = 5; + BLEND_MODE_SRC_IN = 5 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The source pixels are drawn over the destination pixels. // // Alpha = Alpha_src + (1 - Alpha_src) * Alpha_dst // // Color = Color_src + (1 - Alpha_src) * Color_dst - BLEND_MODE_SRC_OVER = 6; + BLEND_MODE_SRC_OVER = 6 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The source pixels are drawn behind the destination pixels. // // Alpha = Alpha_dst + (1 - Alpha_dst) * Alpha_src // // Color = Color_dst + (1 - Alpha_dst) * Color_src - BLEND_MODE_DST_OVER = 7; + BLEND_MODE_DST_OVER = 7 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Keeps the source pixels and discards the destination pixels. // When used on the last `TextureLayer`, this effectively causes the @@ -351,7 +719,13 @@ message BrushPaint { // Alpha = Alpha_src // // Color = Color_src - BLEND_MODE_SRC = 8; + BLEND_MODE_SRC = 8 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Keeps the destination pixels and discards the source pixels. // This mode is unlikely to be useful, since it effectively causes the @@ -361,7 +735,13 @@ message BrushPaint { // Alpha = Alpha_dst // // Color = Color_dst - BLEND_MODE_DST = 9; + BLEND_MODE_DST = 9 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Keeps the source pixels that do not cover destination pixels. Discards // destination pixels and all source pixels that cover destination pixels. @@ -369,7 +749,13 @@ message BrushPaint { // Alpha = (1 - Alpha_dst) * Alpha_src // // Color = (1 - Alpha_dst) * Color_src - BLEND_MODE_SRC_OUT = 10; + BLEND_MODE_SRC_OUT = 10 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Discards destination pixels that aren't covered by source // pixels. Remaining destination pixels are drawn over source pixels. @@ -377,7 +763,13 @@ message BrushPaint { // Alpha = Alpha_src // // Color = Alpha_src * Color_dst + (1 - Alpha_dst) * Color_src - BLEND_MODE_DST_ATOP = 11; + BLEND_MODE_DST_ATOP = 11 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Discards source and destination pixels that intersect; keeps source and // destination pixels that do not intersect. @@ -385,37 +777,103 @@ message BrushPaint { // Alpha = (1 - Alpha_dst) * Alpha_src + (1 - Alpha_src) * Alpha_dst // // Color = (1 - Alpha_dst) * Color_src + (1 - Alpha_src) * Color_dst - BLEND_MODE_XOR = 12; + BLEND_MODE_XOR = 12 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_paint.h:blend_mode) // String id that will be used by renderers to retrieve the color texture. - optional string client_texture_id = 1; + optional string client_texture_id = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The x-dimension of size of (one animation frame of) the texture, // specified in `size_unit`s - optional float size_x = 2 [default = 1]; + optional float size_x = 2 [ + default = 1, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // The y-dimension of size of (one animation frame of) the texture, // specified in `size_unit`s - optional float size_y = 3 [default = 1]; + optional float size_y = 3 [ + default = 1, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // The unit for size_x and size_y. - optional SizeUnit size_unit = 4 [default = SIZE_UNIT_STROKE_COORDINATES]; + optional SizeUnit size_unit = 4 [ + default = SIZE_UNIT_STROKE_COORDINATES, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // How the texture should be applied to the stroke. - optional Mapping mapping = 5 [default = MAPPING_TILING]; + optional Mapping mapping = 5 [ + default = MAPPING_TILING, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // An x-offset into the texture, specified as fractions of the texture size. - optional float offset_x = 6; + optional float offset_x = 6 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // A y-offset into the texture, specified as fractions of the texture size. - optional float offset_y = 7; + optional float offset_y = 7 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Angle in radians specifying the rotation of the texture. The rotation is // carried out about the center of the texture's first repetition along both // axes. - optional float rotation_in_radians = 8; + optional float rotation_in_radians = 8 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; reserved 9 to 15; @@ -425,18 +883,54 @@ message BrushPaint { // combine "src", which is the result of blending layers [0..index], with // "dst", which is the layer at index + 1. If index refers to the last // texture layer, then the layer at "index + 1" is the brush color layer. - optional BlendMode blend_mode = 16 [default = BLEND_MODE_MODULATE]; + optional BlendMode blend_mode = 16 [ + default = BLEND_MODE_MODULATE, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // The origin point to use for the texture - optional Origin origin = 17 [default = ORIGIN_STROKE_SPACE_ORIGIN]; + optional Origin origin = 17 [ + default = ORIGIN_STROKE_SPACE_ORIGIN, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // How points outside the texture in the x direction are treated for this // texture layer - optional Wrap wrap_x = 18 [default = WRAP_REPEAT]; + optional Wrap wrap_x = 18 [ + default = WRAP_REPEAT, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // How points outside the texture in the y direction are treated for this // texture layer - optional Wrap wrap_y = 19 [default = WRAP_REPEAT]; + optional Wrap wrap_y = 19 [ + default = WRAP_REPEAT, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; reserved 20; reserved 21; @@ -455,28 +949,73 @@ message BrushPaint { // with `TextureMapping::kStamping`). // LINT.IfChange(self_overlap) enum SelfOverlap { - SELF_OVERLAP_UNSPECIFIED = 0; - SELF_OVERLAP_ANY = 1; - SELF_OVERLAP_ACCUMULATE = 2; - SELF_OVERLAP_DISCARD = 3; + SELF_OVERLAP_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + SELF_OVERLAP_ANY = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + SELF_OVERLAP_ACCUMULATE = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + SELF_OVERLAP_DISCARD = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_paint.h:self_overlap) // Zero or more textures to blend together to affect this coat's appearance. // Each layer is blended into the next one, and finally into the color of the // paint, according to each layer's `blend_mode`. - repeated TextureLayer texture_layers = 1; + repeated TextureLayer texture_layers = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Transforms the brush color to be used as an alternative base color for any // effects or textures in a `BrushCoat`. Each function is applied in the order // they are specified. // // If this list is empty, the brush color will be used unchanged. - repeated ColorFunction color_functions = 2; + repeated ColorFunction color_functions = 2 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // How parts of the stroke that intersect itself should be treated during the // rendering process. See `SelfOverlap` for more details. - optional SelfOverlap self_overlap = 3 [default = SELF_OVERLAP_ANY]; + optional SelfOverlap self_overlap = 3 [ + default = SELF_OVERLAP_ANY, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; } // A behavior describing how stroke input properties should affect the shape and @@ -549,6 +1088,14 @@ message BrushPaint { // may be constrained to keep them from changing too rapidly with respect to // distance traveled from one input to the next. message BrushBehavior { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // LINT.IfChange(source) // A stroke input property, along with its units, that can act as a source for @@ -565,112 +1112,269 @@ message BrushBehavior { // upsampled, denoised, or otherwise transformed from the raw stroke input // (see `BrushFamily::InputModel`). enum Source { - SOURCE_UNSPECIFIED = 0; + SOURCE_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Stylus or touch pressure with values reported in the range [0, 1]. - SOURCE_NORMALIZED_PRESSURE = 1; + SOURCE_NORMALIZED_PRESSURE = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Stylus tilt with values reported in the range [0, π/2] radians. - SOURCE_TILT_IN_RADIANS = 2; + SOURCE_TILT_IN_RADIANS = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Stylus tilt along the x axis in the range [-π/2, π/2], with a positive // value corresponding to tilt toward the positive x-axis. In order for this // value to be reported, both tilt and orientation have to be populated on // the StrokeInput. - SOURCE_TILT_X_IN_RADIANS = 3; + SOURCE_TILT_X_IN_RADIANS = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Stylus tilt along the y axis in the range [-π/2, π/2], with a positive // value corresponding to tilt toward the positive y-axis. In order for this // value to be reported, both tilt and orientation have to be populated on // the StrokeInput. - SOURCE_TILT_Y_IN_RADIANS = 4; + SOURCE_TILT_Y_IN_RADIANS = 4 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Stylus orientation with values reported in the range [0, 2π). - SOURCE_ORIENTATION_IN_RADIANS = 5; + SOURCE_ORIENTATION_IN_RADIANS = 5 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Stylus orientation with values reported in the range (-π, π]. - SOURCE_ORIENTATION_ABOUT_ZERO_IN_RADIANS = 6; + SOURCE_ORIENTATION_ABOUT_ZERO_IN_RADIANS = 6 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Absolute speed of the modeled stroke input in multiples of the brush size // per second. Note that this value doesn't take into account brush // behaviors that offset the position of the visual tip of the stroke. - SOURCE_SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND = 7; + SOURCE_SPEED_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND = 7 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed x component of the velocity of the modeled stroke input in // multiples of the brush size per second. Note that this value doesn't take // into account brush behaviors that offset the visible position of that // point in the stroke. - SOURCE_VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND = 8; + SOURCE_VELOCITY_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND = 8 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed y component of the velocity of the modeled stroke input in // multiples of the brush size per second. Note that this value doesn't take // into account brush behaviors that offset the visible position of that // point in the stroke. - SOURCE_VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND = 9; + SOURCE_VELOCITY_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND = 9 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed x component of the modeled stroke input's current direction of // travel in stroke coordinate space, normalized to the range [-1, 1]. - SOURCE_NORMALIZED_DIRECTION_X = 10; + SOURCE_NORMALIZED_DIRECTION_X = 10 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed y component of the modeled stroke input's current direction of // travel in stroke coordinate space, normalized to the range [-1, 1]. - SOURCE_NORMALIZED_DIRECTION_Y = 11; + SOURCE_NORMALIZED_DIRECTION_Y = 11 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Distance traveled by the inputs of the current stroke, starting at 0 at // the first input, where one distance unit is equal to the brush size. - SOURCE_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE = 12; + SOURCE_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE = 12 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The time elapsed (in seconds) from when the stroke started to when this // part of the stroke was drawn. The value remains fixed for any given part // of the stroke once drawn. - SOURCE_TIME_OF_INPUT_IN_SECONDS = 13; + SOURCE_TIME_OF_INPUT_IN_SECONDS = 13 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The time elapsed (in milliseconds) from when the stroke started to when // this part of the stroke was drawn. This is deprecated; use // SOURCE_TIME_OF_INPUT_IN_SECONDS instead. - SOURCE_TIME_OF_INPUT_IN_MILLIS = 14 [deprecated = true]; + SOURCE_TIME_OF_INPUT_IN_MILLIS = 14 [ + deprecated = true, + (ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // Distance traveled by the inputs of the current prediction, starting at 0 // at the last non-predicted input, in multiples of the brush size. Zero for // inputs before the predicted portion of the stroke. - SOURCE_PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE = 15; + SOURCE_PREDICTED_DISTANCE_TRAVELED_IN_MULTIPLES_OF_BRUSH_SIZE = 15 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Elapsed time (in seconds) of the prediction, starting at 0 at the last // non-predicted input. Zero for inputs before the predicted portion of the // stroke. - SOURCE_PREDICTED_TIME_ELAPSED_IN_SECONDS = 16; + SOURCE_PREDICTED_TIME_ELAPSED_IN_SECONDS = 16 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Elapsed time (in milliseconds) of the prediction. This is deprecated; use // SOURCE_PREDICTED_TIME_ELAPSED_IN_SECONDS instead. - SOURCE_PREDICTED_TIME_ELAPSED_IN_MILLIS = 17 [deprecated = true]; + SOURCE_PREDICTED_TIME_ELAPSED_IN_MILLIS = 17 [ + deprecated = true, + (ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // The distance left to be traveled from a given modeled input to the // current last modeled input of the stroke in multiples of the brush size. // This value changes for each input as the stroke is drawn. - SOURCE_DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE = 18; + SOURCE_DISTANCE_REMAINING_IN_MULTIPLES_OF_BRUSH_SIZE = 18 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Time elapsed (in seconds) since the modeled stroke input. This continues // to increase even after all stroke inputs have completed, and can be used // to drive stroke animations. These enumerators are only compatible with a // `source_out_of_range_behavior` of `OUT_OF_RANGE_CLAMP`, to ensure that // the animation will eventually end. - SOURCE_TIME_SINCE_INPUT_IN_SECONDS = 19; + SOURCE_TIME_SINCE_INPUT_IN_SECONDS = 19 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Time elapsed (in milliseconds) since the modeled stroke input. This // is deprecated; use SOURCE_TIME_SINCE_INPUT_IN_SECONDS instead. - SOURCE_TIME_SINCE_INPUT_IN_MILLIS = 20 [deprecated = true]; + SOURCE_TIME_SINCE_INPUT_IN_MILLIS = 20 [ + deprecated = true, + (ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; // Angle of the modeled stroke input's current direction of travel in stroke // coordinate space, normalized to the range [0, 2π). A value of 0 indicates // the direction of the positive x-axis; a value of π/2 indicates the // direction of the positive y-axis. - SOURCE_DIRECTION_IN_RADIANS = 21; + SOURCE_DIRECTION_IN_RADIANS = 21 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Angle of the modeled stroke input's current direction of travel in stroke // coordinate space, normalized to the range (-π, π]. A value of 0 indicates // the direction of the positive x-axis; a value of π/2 indicates the // direction of the positive y-axis. - SOURCE_DIRECTION_ABOUT_ZERO_IN_RADIANS = 22; + SOURCE_DIRECTION_ABOUT_ZERO_IN_RADIANS = 22 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; reserved 23; @@ -678,26 +1382,53 @@ message BrushBehavior { // brush size per second squared. Note that this value doesn't take into // account brush behaviors that offset the position of that visible point in // the stroke. - SOURCE_ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = 24; + SOURCE_ACCELERATION_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = 24 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed x component of the acceleration of the modeled stroke input // in multiples of the brush size per second squared. Note that this value // doesn't take into account brush behaviors that offset the position of // that visible point in the stroke. - SOURCE_ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = 25; + SOURCE_ACCELERATION_X_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = 25 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed y component of the acceleration of the modeled stroke input // in multiples of the brush size per second squared. Note that this value // doesn't take into account brush behaviors that offset the position of // that visible point in the stroke. - SOURCE_ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = 26; + SOURCE_ACCELERATION_Y_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = 26 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed component of acceleration of the modeled stroke input in the // direction of its velocity in multiples of the brush size per second // squared. Note that this value doesn't take into account brush behaviors // that offset the position of that visible point in the stroke. SOURCE_ACCELERATION_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = - 27; + 27 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed component of acceleration of the modeled stroke input // perpendicular to its velocity, rotated 90 degrees in the direction from @@ -706,55 +1437,138 @@ message BrushBehavior { // account brush behaviors that offset the position of that visible point // in the stroke. SOURCE_ACCELERATION_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE_PER_SECOND_SQUARED = - 28; + 28 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Absolute speed of the modeled stroke input pointer in centimeters per // second. - SOURCE_INPUT_SPEED_IN_CENTIMETERS_PER_SECOND = 29; + SOURCE_INPUT_SPEED_IN_CENTIMETERS_PER_SECOND = 29 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed x component of the modeled stroke input pointer velocity // in centimeters per second. - SOURCE_INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND = 30; + SOURCE_INPUT_VELOCITY_X_IN_CENTIMETERS_PER_SECOND = 30 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed y component of the modeled stroke input pointer velocity // in centimeters per second. - SOURCE_INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND = 31; + SOURCE_INPUT_VELOCITY_Y_IN_CENTIMETERS_PER_SECOND = 31 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Distance in centimeters traveled by the modeled stroke input pointer // along the input path from the start of the stroke. - SOURCE_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS = 32; + SOURCE_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS = 32 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Distance in centimeters along the input path from the real portion of // the modeled stroke to this input. Zero for inputs before the predicted // portion of the stroke. - SOURCE_PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS = 33; + SOURCE_PREDICTED_INPUT_DISTANCE_TRAVELED_IN_CENTIMETERS = 33 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Absolute acceleration of the modeled stroke input pointer in centimeters // per second squared. - SOURCE_INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED = 34; + SOURCE_INPUT_ACCELERATION_IN_CENTIMETERS_PER_SECOND_SQUARED = 34 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed x component of the acceleration of the modeled stroke input // pointer in centimeters per second squared. - SOURCE_INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED = 35; + SOURCE_INPUT_ACCELERATION_X_IN_CENTIMETERS_PER_SECOND_SQUARED = 35 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed y component of the acceleration of the modeled stroke input // pointer in centimeters per second squared. - SOURCE_INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED = 36; + SOURCE_INPUT_ACCELERATION_Y_IN_CENTIMETERS_PER_SECOND_SQUARED = 36 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed component of acceleration of the modeled stroke input pointer in // the direction of its velocity in centimeters per second squared. - SOURCE_INPUT_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED = 37; + SOURCE_INPUT_ACCELERATION_FORWARD_IN_CENTIMETERS_PER_SECOND_SQUARED = 37 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Signed component of acceleration of the modeled stroke input pointer // perpendicular to its velocity, rotated 90 degrees in the direction from // the positive x-axis towards the positive y-axis, in centimeters per // second squared. - SOURCE_INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED = 38; + SOURCE_INPUT_ACCELERATION_LATERAL_IN_CENTIMETERS_PER_SECOND_SQUARED = 38 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Distance from the current modeled input to the end of the stroke along // the input path, as a fraction of the current total length of the stroke. // This value changes for each input as inputs are added. - SOURCE_DISTANCE_REMAINING_AS_FRACTION_OF_STROKE_LENGTH = 39; + SOURCE_DISTANCE_REMAINING_AS_FRACTION_OF_STROKE_LENGTH = 39 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_behavior.h:source) @@ -762,7 +1576,13 @@ message BrushBehavior { // `BrushTip` properties that can be modified by a `BrushBehavior`. enum Target { - TARGET_UNSPECIFIED = 0; + TARGET_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Scales the brush-tip width, starting from the value calculated using // `BrushTip::scale_x`. The final brush width is clamped to a maximum of @@ -770,35 +1590,78 @@ message BrushBehavior { // `TARGET_SIZE_MULTIPLIER`, they aggregate multiplicatively. (Therefore, if // one behavior scales the width down to zero over time, it "wins out" over // all other width-modifying behaviors.) - TARGET_WIDTH_MULTIPLIER = 1; + TARGET_WIDTH_MULTIPLIER = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Same as `TARGET_WIDTH_MULTIPLIER` but for height. Clamping and // aggregation work the same way as for width. - TARGET_HEIGHT_MULTIPLIER = 2; + TARGET_HEIGHT_MULTIPLIER = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // A convenience target that affects both width and height at once, in the // same way that `TARGET_WIDTH_MULTIPLIER` does. - TARGET_SIZE_MULTIPLIER = 3; + TARGET_SIZE_MULTIPLIER = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Adds the target modifier to `BrushTip::slant`. The final brush slant // value is clamped to [-π/2, π/2]. If multiple behaviors have this target, // they stack additively. - TARGET_SLANT_OFFSET_IN_RADIANS = 4; + TARGET_SLANT_OFFSET_IN_RADIANS = 4 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Adds the target modifier to `BrushTip::pinch`. The final brush pinch // value is clamped to [0, 1]. If multiple behaviors have this target, they // stack additively. - TARGET_PINCH_OFFSET = 5; + TARGET_PINCH_OFFSET = 5 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Adds the target modifier to `BrushTip::rotation`. The final brush // rotation angle is effectively normalized (mod 2π). If multiple behaviors // have this target, they stack additively. - TARGET_ROTATION_OFFSET_IN_RADIANS = 6; + TARGET_ROTATION_OFFSET_IN_RADIANS = 6 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Adds the target modifier to `BrushTip::corner_rounding`. The final brush // corner rounding value is clamped to [0, 1]. If multiple behaviors have // this target, they stack additively. - TARGET_CORNER_ROUNDING_OFFSET = 7; + TARGET_CORNER_ROUNDING_OFFSET = 7 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Shifts the hue of the base brush color. A positive offset shifts around // the hue wheel from red towards orange, while a negative offset shifts the @@ -809,7 +1672,13 @@ message BrushBehavior { // This target is for tip color adjustments. Renderers can apply it to the // brush color when a stroke is drawn to contribute to the local color of // each part of the stroke. - TARGET_HUE_OFFSET_IN_RADIANS = 8; + TARGET_HUE_OFFSET_IN_RADIANS = 8 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Scales the saturation of the base brush color. If multiple behaviors // have one of these targets, they stack multiplicatively. The final @@ -818,7 +1687,13 @@ message BrushBehavior { // This target is for tip color adjustments. Renderers can apply it to the // brush color when a stroke is drawn to contribute to the local color of // each part of the stroke. - TARGET_SATURATION_MULTIPLIER = 9; + TARGET_SATURATION_MULTIPLIER = 9 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Target the luminosity of the color. An offset of +/-100% corresponds to // changing the luminosity by up to +/-100%. @@ -826,7 +1701,13 @@ message BrushBehavior { // This target is for tip color adjustments. Renderers can apply it to the // brush color when a stroke is drawn to contribute to the local color of // each part of the stroke. - TARGET_LUMINOSITY = 10; + TARGET_LUMINOSITY = 10 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Scales the opacity of the base brush color. If multiple behaviors have // one of these targets, they stack multiplicatively. The final opacity @@ -835,25 +1716,59 @@ message BrushBehavior { // This target is for tip color adjustments. Renderers can apply it to the // brush color when a stroke is drawn to contribute to the local color of // each part of the stroke. - TARGET_OPACITY_MULTIPLIER = 11; + TARGET_OPACITY_MULTIPLIER = 11 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Adds the target modifier to the brush tip x position in multiples of // the brush size. - TARGET_POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE = 12; + TARGET_POSITION_OFFSET_X_IN_MULTIPLES_OF_BRUSH_SIZE = 12 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Adds the target modifier to the brush tip y position in multiples of // the brush size. - TARGET_POSITION_OFFSET_Y_IN_MULTIPLES_OF_BRUSH_SIZE = 13; + TARGET_POSITION_OFFSET_Y_IN_MULTIPLES_OF_BRUSH_SIZE = 13 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Moves the brush tip by the target modifier times the brush size in the // direction of the modeled stroke input's velocity (the opposite direction // if the value is negative). - TARGET_POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE = 14; + TARGET_POSITION_OFFSET_FORWARD_IN_MULTIPLES_OF_BRUSH_SIZE = 14 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Moves the brush tip by the target modifier times the brush size // perpendicular to the modeled stroke input's velocity, rotated 90 degrees // in the direction from the positive x-axis to the positive y-axis. - TARGET_POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE = 15; + TARGET_POSITION_OFFSET_LATERAL_IN_MULTIPLES_OF_BRUSH_SIZE = 15 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; reserved 16; reserved 17; @@ -864,14 +1779,27 @@ message BrushBehavior { // Like `Target`, but for vector values. enum PolarTarget { - POLAR_UNSPECIFIED = 0; + POLAR_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Adds the vector to the brush tip's absolute x/y position in stroke space, // where the angle input is measured in radians and the magnitude input is // measured in units equal to the brush size. An angle of zero indicates an // offset in the direction of the positive X-axis in stroke space; an angle // of π/2 indicates the direction of the positive Y-axis in stroke space. - POLAR_POSITION_OFFSET_ABSOLUTE_IN_RADIANS_AND_MULTIPLES_OF_BRUSH_SIZE = 1; + POLAR_POSITION_OFFSET_ABSOLUTE_IN_RADIANS_AND_MULTIPLES_OF_BRUSH_SIZE = 1 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Adds the vector to the brush tip's forward/lateral position relative to // the current direction of input travel, where the angle input is measured @@ -883,7 +1811,14 @@ message BrushBehavior { // an angle of π/2 would indicate a lateral offset towards the positive // Y-axis, and an angle of -π/2 would indicate a lateral offset towards the // negative Y-axis. - POLAR_POSITION_OFFSET_RELATIVE_IN_RADIANS_AND_MULTIPLES_OF_BRUSH_SIZE = 2; + POLAR_POSITION_OFFSET_RELATIVE_IN_RADIANS_AND_MULTIPLES_OF_BRUSH_SIZE = 2 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_behavior.h:polar_target) @@ -892,10 +1827,22 @@ message BrushBehavior { // The desired behavior when an input value is outside the bounds of // `source_value_range`. enum OutOfRange { - OUT_OF_RANGE_UNSPECIFIED = 0; + OUT_OF_RANGE_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Values outside the range will be clamped to not exceed the bounds. - OUT_OF_RANGE_CLAMP = 1; + OUT_OF_RANGE_CLAMP = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Values will be shifted by an integer multiple of the range size so that // they fall within the bounds. @@ -903,14 +1850,26 @@ message BrushBehavior { // In this case, the range will be treated as a half-open interval, with a // value exactly at `source_value_range[1]` being treated as though it was // `source_value_range[0]`. - OUT_OF_RANGE_REPEAT = 2; + OUT_OF_RANGE_REPEAT = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Similar to `OUT_OF_RANGE_REPEAT`, but every other repetition of the // bounds will be mirrored, as though the two elements of // `source_value_range` were swapped. This means the range does not need to // be treated as a half-open interval like in the case of // `OUT_OF_RANGE_REPEAT`. - OUT_OF_RANGE_MIRROR = 3; + OUT_OF_RANGE_MIRROR = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_behavior.h:out_of_range) @@ -918,13 +1877,43 @@ message BrushBehavior { // List of input properties that might not be reported by `StrokeInput`. enum OptionalInputProperty { - OPTIONAL_INPUT_UNSPECIFIED = 0; - OPTIONAL_INPUT_PRESSURE = 1; - OPTIONAL_INPUT_TILT = 2; - OPTIONAL_INPUT_ORIENTATION = 3; + OPTIONAL_INPUT_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + OPTIONAL_INPUT_PRESSURE = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + OPTIONAL_INPUT_TILT = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + OPTIONAL_INPUT_ORIENTATION = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Tilt-x and tilt-y require both tilt and orientation to be reported. - OPTIONAL_INPUT_TILT_X_AND_Y = 4; + OPTIONAL_INPUT_TILT_X_AND_Y = 4 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_behavior.h:optional_input_property) @@ -932,28 +1921,76 @@ message BrushBehavior { // A binary operation for combining two values in a `BinaryOpNode`. enum BinaryOp { - BINARY_OP_UNSPECIFIED = 0; + BINARY_OP_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // A * B, or null if either is null - BINARY_OP_PRODUCT = 1; + BINARY_OP_PRODUCT = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // A + B, or null if either is null - BINARY_OP_SUM = 2; + BINARY_OP_SUM = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // min(A, B), or null if either is null - BINARY_OP_MIN = 3; + BINARY_OP_MIN = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }]; // max(A, B), or null if either is null - BINARY_OP_MAX = 4; + BINARY_OP_MAX = 4 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }]; // null if A is null, or B otherwise - BINARY_OP_AND_THEN = 5; + BINARY_OP_AND_THEN = 5 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }]; // A if A isn't null, or B otherwise - BINARY_OP_OR_ELSE = 6; + BINARY_OP_OR_ELSE = 6 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }]; // A if B is null, or B if A is null, or null if neither is null - BINARY_OP_XOR_ELSE = 7; + BINARY_OP_XOR_ELSE = 7 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_behavior.h:binary_op) @@ -961,11 +1998,23 @@ message BrushBehavior { // Dimensions/units for measuring the `damping_gap` field of a `DampingNode`. enum ProgressDomain { - PROGRESS_DOMAIN_UNSPECIFIED = 0; + PROGRESS_DOMAIN_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Value damping occurs over time, and the `damping_gap` is measured in // seconds. - PROGRESS_DOMAIN_TIME_IN_SECONDS = 1; + PROGRESS_DOMAIN_TIME_IN_SECONDS = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Value damping occurs over distance traveled by the input pointer, and the // `damping_gap` is measured in centimeters. If the input data does not @@ -973,11 +2022,25 @@ message BrushBehavior { // (e.g. as may be the case for programmatically-generated inputs), then no // damping will be performed (i.e. the `damping_gap` will be treated as // zero). - PROGRESS_DOMAIN_DISTANCE_IN_CENTIMETERS = 2; + PROGRESS_DOMAIN_DISTANCE_IN_CENTIMETERS = 2 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Value damping occurs over distance traveled by the input pointer, and the // `damping_gap` is measured in multiples of the brush size. - PROGRESS_DOMAIN_DISTANCE_IN_MULTIPLES_OF_BRUSH_SIZE = 3; + PROGRESS_DOMAIN_DISTANCE_IN_MULTIPLES_OF_BRUSH_SIZE = 3 + [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_behavior.h:progress_domain) @@ -986,15 +2049,33 @@ message BrushBehavior { // An interpolation function for combining three values in an // `InterpolationNode`. enum Interpolation { - INTERPOLATION_UNSPECIFIED = 0; + INTERPOLATION_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Linear interpolation. Uses parameter A to interpolate between B (when // A=0) and C (when A=1). - INTERPOLATION_LERP = 1; + INTERPOLATION_LERP = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Inverse linear interpolation. Outputs 0 when A=B and 1 when A=C, // interpolating linearly in between. Outputs null if B=C. - INTERPOLATION_INVERSE_LERP = 2; + INTERPOLATION_INVERSE_LERP = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/brush_behavior.h:interpolation) @@ -1004,19 +2085,102 @@ message BrushBehavior { // applies some effect to the brush tip (but does not produce any output // value). message Node { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + oneof node { - SourceNode source_node = 1; - ConstantNode constant_node = 2; - FallbackFilterNode fallback_filter_node = 3; - ToolTypeFilterNode tool_type_filter_node = 4; - DampingNode damping_node = 5; - ResponseNode response_node = 6; - BinaryOpNode binary_op_node = 7; - TargetNode target_node = 8; - InterpolationNode interpolation_node = 9; - NoiseNode noise_node = 10; - PolarTargetNode polar_target_node = 11; - IntegralNode integral_node = 12; + SourceNode source_node = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + ConstantNode constant_node = 2 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + FallbackFilterNode fallback_filter_node = 3 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + ToolTypeFilterNode tool_type_filter_node = 4 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + DampingNode damping_node = 5 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + ResponseNode response_node = 6 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + BinaryOpNode binary_op_node = 7 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + TargetNode target_node = 8 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + InterpolationNode interpolation_node = 9 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + NoiseNode noise_node = 10 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + PolarTargetNode polar_target_node = 11 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + IntegralNode integral_node = 12 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } } @@ -1035,19 +2199,53 @@ message BrushBehavior { // * `source_out_of_range_behavior` must be a valid `OutOfRange` enumerator. // * The endpoints of `source_value_range` must be finite and distinct. message SourceNode { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // What property of the stroke input to use for source values for this node. - optional Source source = 1; + optional Source source = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // What to do with source values outside the source value range. - optional OutOfRange source_out_of_range_behavior = 2; + optional OutOfRange source_out_of_range_behavior = 2 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The source value that maps to 0.0 in the output. Below this value, // `out_of_range_behavior` determines the output value. - optional float source_value_range_start = 3; + optional float source_value_range_start = 3 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The source value that maps to 1.0 in the output. Above this value, // `out_of_range_behavior` determines the output value. - optional float source_value_range_end = 4; + optional float source_value_range_end = 4 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Value node for producing a constant value. @@ -1058,7 +2256,21 @@ message BrushBehavior { // // To be valid: `value` must be finite. message ConstantNode { - optional float value = 1; + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + + optional float value = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Value node for producing a continuous random noise function with values @@ -1072,16 +2284,42 @@ message BrushBehavior { // * `vary_over` must be a valid `ProgressDomain` enumerator. // * `base_period` must be finite and strictly positive. message NoiseNode { - optional fixed32 seed = 1; + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + + optional fixed32 seed = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The domain units over which random noise is generated. - optional ProgressDomain vary_over = 2; + optional ProgressDomain vary_over = 2 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The period (in `vary_over` units) over which the output value smoothly // varies from one random value to another uncorrelated random value. (In // other words, if two points in the input are separated by at least this // period, their output values are uncorrelated.) - optional float base_period = 3; + optional float base_period = 3 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Value node for filtering out a branch of a behavior graph unless a @@ -1095,7 +2333,22 @@ message BrushBehavior { // To be valid: `is_fallback_for` must be a valid `OptionalInputProperty` // enumerator. message FallbackFilterNode { - optional OptionalInputProperty is_fallback_for = 1; + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + + optional OptionalInputProperty is_fallback_for = 1 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Value node for filtering out a branch of a behavior graph unless this @@ -1108,11 +2361,25 @@ message BrushBehavior { // // To be valid: At least one tool type must be enabled. message ToolTypeFilterNode { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // A bitset of tool types, using ink.proto.CodedStrokeInputBatch.ToolType // enum values as bit numbers for each tool type. For example, if only // touch and stylus are enabled, then the value of this field should be: // ((1 << ToolType.TOUCH) | (1 << ToolType.STYLUS)) - optional uint32 enabled_tool_types = 1; + optional uint32 enabled_tool_types = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Value node for damping changes in an input value, causing the output value @@ -1129,13 +2396,34 @@ message BrushBehavior { // * `damping_source` must be a valid `ProgressDomain` enumerator. // * `damping_gap` must be finite and non-negative. message DampingNode { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // The domain units over which damping is applied. - optional ProgressDomain damping_source = 1; + optional ProgressDomain damping_source = 1 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // A scaling factor, in `damping_source` units, for the damping. Smaller // gaps result in less damping, so the output follows the input more // closely. - optional float damping_gap = 2; + optional float damping_gap = 2 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Value node for mapping a value through a response curve. @@ -1147,11 +2435,47 @@ message BrushBehavior { // // To be valid: `response_curve` must be a valid `EasingFunction`. message ResponseNode { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + oneof response_curve { - PredefinedEasingFunction predefined_response_curve = 1; - CubicBezierEasingFunction cubic_bezier_response_curve = 2; - LinearEasingFunction linear_response_curve = 3; - StepsEasingFunction steps_response_curve = 4; + PredefinedEasingFunction predefined_response_curve = 1 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + CubicBezierEasingFunction cubic_bezier_response_curve = 2 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + LinearEasingFunction linear_response_curve = 3 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + StepsEasingFunction steps_response_curve = 4 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } } @@ -1165,7 +2489,21 @@ message BrushBehavior { // // To be valid: `operation` must be a valid `BinaryOp` enumerator. message BinaryOpNode { - optional BinaryOp operation = 1; + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + + optional BinaryOp operation = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Value node for interpolating to/from a range of two values. @@ -1178,7 +2516,21 @@ message BrushBehavior { // // To be valid: `interpolation` must be a valid `Interpolation` enumerator. message InterpolationNode { - optional Interpolation interpolation = 1; + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + + optional Interpolation interpolation = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Value node for integrating an input value over time or distance. @@ -1198,20 +2550,56 @@ message BrushBehavior { // * `integral_out_of_range_behavior` must be a valid `OutOfRange` // enumerator. message IntegralNode { + option (ink.proto.message_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }; + // The variable and units (e.g. time or distance) over which the input value // is integrated. - optional ProgressDomain integrate_over = 1; + optional ProgressDomain integrate_over = 1 + [(ink.proto.field_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }]; // The integral value that maps to 0.0 in the output. Below this value, // `out_of_range_behavior` determines the output value. - optional float integral_value_range_start = 2; + optional float integral_value_range_start = 2 + [(ink.proto.field_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }]; // The integral value that maps to 1.0 in the output. Above this value, // `out_of_range_behavior` determines the output value. - optional float integral_value_range_end = 3; + optional float integral_value_range_end = 3 + [(ink.proto.field_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }]; // What to do with integral values outside the integral value range. - optional OutOfRange integral_out_of_range_behavior = 4; + optional OutOfRange integral_out_of_range_behavior = 4 + [(ink.proto.field_min_version) = { + major: 1, + minor: 1, + bug: 0, + cycle: CYCLE_ALPHA, + release: 1 + }]; } // Terminal node that consumes a single input value to modify a scalar brush @@ -1228,14 +2616,42 @@ message BrushBehavior { // * `target` must be a valid `Target` enumerator. // * The endpoints of `target_modifier_range` must be finite and distinct. message TargetNode { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // What aspect of the brush to affect, and how. - optional Target target = 1; + optional Target target = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The output value produced by an input value of 0.0. - optional float target_modifier_range_start = 2; + optional float target_modifier_range_start = 2 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The output value produced by an input value of 1.0. - optional float target_modifier_range_end = 3; + optional float target_modifier_range_end = 3 + [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Terminal node that consumes two input values (angle and magnitude), forming @@ -1254,24 +2670,68 @@ message BrushBehavior { // * The endpoints of `angle_range` and of `magnitude_range` must be finite // and distinct. message PolarTargetNode { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // What aspect of the brush to affect, and how. - optional PolarTarget target = 1; + optional PolarTarget target = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The output angle produced by a value of 0.0 for the first input. - optional float angle_range_start = 2; + optional float angle_range_start = 2 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The output angle produced by a value of 1.0 for the first input. - optional float angle_range_end = 3; + optional float angle_range_end = 3 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The output magnitude produced by a value of 0.0 for the second input. - optional float magnitude_range_start = 4; + optional float magnitude_range_start = 4 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The output magnitude produced by a value of 1.0 for the second input. - optional float magnitude_range_end = 5; + optional float magnitude_range_end = 5 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // A post-order traversal of the graph of Nodes. - repeated Node nodes = 15; + repeated Node nodes = 15 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Were fields controlling brush behavior, use `nodes` instead. reserved 1 to 14; @@ -1280,15 +2740,41 @@ message BrushBehavior { // behavior and its purpose within the brush, with the intended audience being // designers/developers who are editing the brush definition. This string is // not generally intended to be displayed to end users. - optional string developer_comment = 16; + optional string developer_comment = 16 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Transforms the brush color to be used as an alternative base color for any // effects or textures in a `BrushCoat`. message ColorFunction { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + oneof function { - float opacity_multiplier = 1; - Color replace_color = 2; + float opacity_multiplier = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + Color replace_color = 2 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } } @@ -1296,38 +2782,86 @@ message ColorFunction { // Specifies a predefined easing function. enum PredefinedEasingFunction { - PREDEFINED_EASING_UNSPECIFIED = 0; + PREDEFINED_EASING_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The linear identity function: accepts and returns values outside [0, 1]. - PREDEFINED_EASING_LINEAR = 1; + PREDEFINED_EASING_LINEAR = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Predefined cubic Bezier function: // https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions (see note // on `CubicBezier` about input values outside [0, 1]) - PREDEFINED_EASING_EASE = 2; + PREDEFINED_EASING_EASE = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Predefined cubic Bezier function: // https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions (see note // on `CubicBezier` about input values outside [0, 1]) - PREDEFINED_EASING_EASE_IN = 3; + PREDEFINED_EASING_EASE_IN = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Predefined cubic Bezier function: // https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions (see note // on `CubicBezier` about input values outside [0, 1]) - PREDEFINED_EASING_EASE_OUT = 4; + PREDEFINED_EASING_EASE_OUT = 4 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Predefined cubic Bezier function: // https://www.w3.org/TR/css-easing-1/#cubic-bezier-easing-functions (see note // on `CubicBezier` about input values outside [0, 1]) - PREDEFINED_EASING_EASE_IN_OUT = 5; + PREDEFINED_EASING_EASE_IN_OUT = 5 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Predefined step functions: // https://www.w3.org/TR/css-easing-1/#step-easing-functions - PREDEFINED_EASING_STEP_START = 6; + PREDEFINED_EASING_STEP_START = 6 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Predefined step functions: // https://www.w3.org/TR/css-easing-1/#step-easing-functions - PREDEFINED_EASING_STEP_END = 7; + PREDEFINED_EASING_STEP_END = 7 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/easing_function.h:predefined) @@ -1348,10 +2882,42 @@ enum PredefinedEasingFunction { // cubic Bezier that allows extrapolated values outside x in [0, 1] by // following end-point tangents. message CubicBezierEasingFunction { - optional float x1 = 1; - optional float y1 = 2; - optional float x2 = 3; - optional float y2 = 4; + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + + optional float x1 = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + optional float y1 = 2 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + optional float x2 = 3 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; + optional float y2 = 4 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // Parameters for a custom piecewise-linear easing function. @@ -1375,9 +2941,35 @@ message CubicBezierEasingFunction { // If the input x-value is outside the interval [0, 1], the output will be // extrapolated from the first/last line segment. message LinearEasingFunction { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // These two lists must have the same length. - repeated float x = 1 [packed = true]; - repeated float y = 2 [packed = true]; + repeated float x = 1 [ + packed = true, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; + repeated float y = 2 [ + packed = true, + (ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + } + ]; } // LINT.IfChange(step_position) @@ -1385,27 +2977,57 @@ message LinearEasingFunction { // Setting to determine the desired output value of the first and last // step of [0, 1) for the Steps EasingFunction. See below for more context. enum StepPosition { - STEP_POSITION_UNSPECIFIED = 0; + STEP_POSITION_UNSPECIFIED = 0 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The step function "jumps" at the start of [0, 1): // * for x in [0, 1/step_count) => y = 1/step_count // * for x in [1 - 1/step_count, 1) => y = 1 - STEP_POSITION_JUMP_START = 1; + STEP_POSITION_JUMP_START = 1 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The step function "jumps" at the end of [0, 1): // * for x in [0, 1/step_count) => y = 0 // * for x in [1 - 1/step_count, 1) => y = 1 - 1/step_count - STEP_POSITION_JUMP_END = 2; + STEP_POSITION_JUMP_END = 2 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The step function does not "jump" at either boundary: // * for x in [0, 1/step_count) => y = 0 // * for x in [1 - 1/step_count, 1) => y = 1 - STEP_POSITION_JUMP_NONE = 3; + STEP_POSITION_JUMP_NONE = 3 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // The step function "jumps" at both the start and the end: // * for x in [0, 1/step_count) => y = 1/(step_count + 1) // * for x in [1 - 1/step_count, 1) => y = 1 - 1/(step_count + 1) - STEP_POSITION_JUMP_BOTH = 4; + STEP_POSITION_JUMP_BOTH = 4 [(ink.proto.enum_value_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } // LINT.ThenChange(../../brush/easing_function.h:step_position) @@ -1420,12 +3042,32 @@ enum StepPosition { // The behavior and naming follows the CSS steps() specification at: // https:www.w3.org/TR/css-easing-1/#step-easing-functions message StepsEasingFunction { + option (ink.proto.message_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }; + // The number of steps. // // Must always be greater than 0, and must be greater than 1 if // `step_position` is `kJumpNone`. - optional int32 step_count = 1; + optional int32 step_count = 1 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; // Position of the step(s) - optional StepPosition step_position = 2; + optional StepPosition step_position = 2 [(ink.proto.field_min_version) = { + major: 1, + minor: 0, + bug: 0, + cycle: CYCLE_STABLE, + release: 1 + }]; } diff --git a/ink/storage/proto/options.proto b/ink/storage/proto/options.proto new file mode 100644 index 00000000..724e9ed2 --- /dev/null +++ b/ink/storage/proto/options.proto @@ -0,0 +1,92 @@ +// Copyright 2026 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. + +edition = "2024"; + +package ink.proto; + +import "net/proto2/proto/descriptor.proto"; + +option java_package = "com.google.ink.proto"; +option java_outer_classname = "OptionsProto"; + +message Version { + // Version represents a Jetpack release of Ink, mapping directly to the + // values associated with releases at: + // https://developer.android.com/jetpack/androidx/releases/ink + // + // For example, Ink 1.0.0-alpha04 would be represented as: + // ``` + // major: 1 + // minor: 0 + // bug: 0 + // cycle: CYCLE_ALPHA + // release: 4 + // ``` + // Ink 1.0.0 would be represented as: + // ``` + // major: 1 + // minor: 0 + // bug: 0 + // cycle: CYCLE_STABLE + // release: 1 + // ``` + // Note that the release value is 1-based, to stay consistent with the + // Jetpack release values. Stable releases should always have a release value + // of 1, since we don't release multiple iterations of a stable build for a + // given version (there is no 1.0.0-stable01, 1.0.0-stable02, etc. + // only 1.0.0). + // + // Version must be compared in lexicographical order, including all 5 fields + // (major, minor, bug, cycle, release) in that order, to establish version + // precedence. + // + // Cycle and release are used to couch against instability between various + // alpha and beta releases which change the custom brush API. Earlier releases + // provide a guarantee that they will not load a brush containing a feature + // from a later release. Cycle and release are necessary for this guarantee + // to work between builds of the same major, minor, and bug valued version. + // + // The reverse guarantee, however, is not provided. If a brush feature is + // introduced in an alpha release, and removed in a beta release, the beta + // release will not reject the brush on the basis of version, instead it + // will attempt to load the brush as-is, making a best effort with the code + // that it has to deserialize the brush. + int32 major = 1; + int32 minor = 2; + int32 bug = 3; + + enum Cycle { + CYCLE_UNSPECIFIED = 0; + CYCLE_ALPHA = 1; + CYCLE_BETA = 2; + CYCLE_RELEASE_CANDIDATE = 3; + CYCLE_STABLE = 4; + } + + Cycle cycle = 4; + int32 release = 5; +} + +// Extend FieldOptions, MessageOptions, EnumOptions, and EnumValueOptions to +// include Version. +extend proto2.FieldOptions { + Version field_min_version = 525000068; +} +extend proto2.MessageOptions { + Version message_min_version = 525000037; +} +extend proto2.EnumValueOptions { + Version enum_value_min_version = 525000143; +}