From d415b6a958f78415201502d77d8c8d795283174b Mon Sep 17 00:00:00 2001 From: Luigi Rosso Date: Fri, 25 Feb 2022 19:41:59 -0800 Subject: [PATCH] Mesh deform for flutter runtime. --- lib/src/core/core.dart | 3 + .../core/importers/file_asset_importer.dart | 4 +- .../assets/file_asset_contents_base.dart | 14 +- lib/src/generated/rive_core_context.dart | 141 +++- .../shapes/contour_mesh_vertex_base.dart | 22 + .../shapes/cubic_asymmetric_vertex_base.dart | 2 + .../shapes/cubic_detached_vertex_base.dart | 2 + .../shapes/cubic_mirrored_vertex_base.dart | 2 + .../generated/shapes/cubic_vertex_base.dart | 2 + .../generated/shapes/forced_edge_base.dart | 67 ++ lib/src/generated/shapes/mesh_base.dart | 47 ++ .../generated/shapes/mesh_vertex_base.dart | 74 ++ .../generated/shapes/path_vertex_base.dart | 61 +- .../shapes/straight_vertex_base.dart | 2 + lib/src/generated/shapes/text_base.dart | 53 ++ lib/src/generated/shapes/text_run_base.dart | 80 ++ lib/src/generated/shapes/vertex_base.dart | 73 ++ .../rive_core/animation/blend_animation.dart | 1 - .../animation/blend_state_instance.dart | 1 - .../animation/cubic_interpolator.dart | 2 - .../animation/transition_value_condition.dart | 4 +- lib/src/rive_core/assets/asset.dart | 1 + lib/src/rive_core/assets/file_asset.dart | 2 - lib/src/rive_core/assets/image_asset.dart | 6 + lib/src/rive_core/bones/skin.dart | 4 +- lib/src/rive_core/bones/skinnable.dart | 8 +- lib/src/rive_core/component.dart | 1 - lib/src/rive_core/component_dirt.dart | 3 + lib/src/rive_core/container_component.dart | 4 +- lib/src/rive_core/event.dart | 1 - .../rive_core/shapes/contour_mesh_vertex.dart | 4 + lib/src/rive_core/shapes/image.dart | 67 +- lib/src/rive_core/shapes/mesh.dart | 147 ++++ lib/src/rive_core/shapes/mesh_vertex.dart | 26 + lib/src/rive_core/shapes/path_vertex.dart | 69 +- lib/src/rive_core/shapes/points_path.dart | 2 +- lib/src/rive_core/shapes/text.dart | 81 +++ lib/src/rive_core/shapes/text_run.dart | 85 +++ lib/src/rive_core/shapes/vertex.dart | 63 ++ lib/src/rive_core/src/text/built_in_font.dart | 2 + lib/src/rive_core/src/text/paragraph.dart | 684 ++++++++++++++++++ lib/src/rive_core/src/text/raw_path.dart | 149 ++++ lib/src/rive_core/src/text/rive_font.dart | 255 +++++++ lib/src/rive_core/src/text/shape_text.dart | 252 +++++++ .../rive_core/state_machine_controller.dart | 1 - lib/src/rive_file.dart | 1 - lib/src/runtime_artboard.dart | 1 - 47 files changed, 2396 insertions(+), 180 deletions(-) create mode 100644 lib/src/generated/shapes/contour_mesh_vertex_base.dart create mode 100644 lib/src/generated/shapes/forced_edge_base.dart create mode 100644 lib/src/generated/shapes/mesh_base.dart create mode 100644 lib/src/generated/shapes/mesh_vertex_base.dart create mode 100644 lib/src/generated/shapes/text_base.dart create mode 100644 lib/src/generated/shapes/text_run_base.dart create mode 100644 lib/src/generated/shapes/vertex_base.dart create mode 100644 lib/src/rive_core/shapes/contour_mesh_vertex.dart create mode 100644 lib/src/rive_core/shapes/mesh.dart create mode 100644 lib/src/rive_core/shapes/mesh_vertex.dart create mode 100644 lib/src/rive_core/shapes/text.dart create mode 100644 lib/src/rive_core/shapes/text_run.dart create mode 100644 lib/src/rive_core/shapes/vertex.dart create mode 100644 lib/src/rive_core/src/text/built_in_font.dart create mode 100644 lib/src/rive_core/src/text/paragraph.dart create mode 100644 lib/src/rive_core/src/text/raw_path.dart create mode 100644 lib/src/rive_core/src/text/rive_font.dart create mode 100644 lib/src/rive_core/src/text/shape_text.dart diff --git a/lib/src/core/core.dart b/lib/src/core/core.dart index ef6fc9d..d604b76 100644 --- a/lib/src/core/core.dart +++ b/lib/src/core/core.dart @@ -3,6 +3,9 @@ import 'dart:collection'; import 'package:flutter/foundation.dart'; import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart'; +export 'dart:typed_data'; +export 'package:flutter/foundation.dart'; + export 'package:rive/src/animation_list.dart'; export 'package:rive/src/asset_list.dart'; export 'package:rive/src/blend_animations.dart'; diff --git a/lib/src/core/importers/file_asset_importer.dart b/lib/src/core/importers/file_asset_importer.dart index 4a600c9..91e0cbb 100644 --- a/lib/src/core/importers/file_asset_importer.dart +++ b/lib/src/core/importers/file_asset_importer.dart @@ -1,5 +1,3 @@ -import 'dart:typed_data'; - import 'package:rive/src/core/core.dart'; import 'package:rive/src/rive_core/assets/file_asset.dart'; import 'package:rive/src/rive_core/assets/file_asset_contents.dart'; @@ -21,7 +19,7 @@ class FileAssetImporter extends ImportStackObject { void loadContents(FileAssetContents contents) { _loadedContents = true; - fileAsset.decode(contents.bytes as Uint8List); + fileAsset.decode(contents.bytes); } @override diff --git a/lib/src/generated/assets/file_asset_contents_base.dart b/lib/src/generated/assets/file_asset_contents_base.dart index 75f9a9f..7d9a766 100644 --- a/lib/src/generated/assets/file_asset_contents_base.dart +++ b/lib/src/generated/assets/file_asset_contents_base.dart @@ -13,27 +13,27 @@ abstract class FileAssetContentsBase extends Core { /// -------------------------------------------------------------------------- /// Bytes field with key 212. - static const List bytesInitialValue = []; - List _bytes = bytesInitialValue; + static final Uint8List bytesInitialValue = Uint8List(0); + Uint8List _bytes = bytesInitialValue; static const int bytesPropertyKey = 212; /// Byte data of the file. - List get bytes => _bytes; + Uint8List get bytes => _bytes; /// Change the [_bytes] field value. /// [bytesChanged] will be invoked only if the field's value has changed. - set bytes(List value) { - if (_bytes == value) { + set bytes(Uint8List value) { + if (listEquals(_bytes, value)) { return; } - List from = _bytes; + Uint8List from = _bytes; _bytes = value; if (hasValidated) { bytesChanged(from, value); } } - void bytesChanged(List from, List to); + void bytesChanged(Uint8List from, Uint8List to); @override void copy(covariant FileAssetContentsBase source) { diff --git a/lib/src/generated/rive_core_context.dart b/lib/src/generated/rive_core_context.dart index 674c24d..6bbf018 100644 --- a/lib/src/generated/rive_core_context.dart +++ b/lib/src/generated/rive_core_context.dart @@ -27,7 +27,7 @@ import 'package:rive/src/generated/nested_animation_base.dart'; import 'package:rive/src/generated/shapes/paint/shape_paint_base.dart'; import 'package:rive/src/generated/shapes/parametric_path_base.dart'; import 'package:rive/src/generated/shapes/path_base.dart'; -import 'package:rive/src/generated/shapes/path_vertex_base.dart'; +import 'package:rive/src/generated/shapes/vertex_base.dart'; import 'package:rive/src/generated/transform_component_base.dart'; import 'package:rive/src/generated/world_transform_component_base.dart'; import 'package:rive/src/rive_core/animation/animation.dart'; @@ -82,11 +82,14 @@ import 'package:rive/src/rive_core/draw_target.dart'; import 'package:rive/src/rive_core/nested_artboard.dart'; import 'package:rive/src/rive_core/node.dart'; import 'package:rive/src/rive_core/shapes/clipping_shape.dart'; +import 'package:rive/src/rive_core/shapes/contour_mesh_vertex.dart'; import 'package:rive/src/rive_core/shapes/cubic_asymmetric_vertex.dart'; import 'package:rive/src/rive_core/shapes/cubic_detached_vertex.dart'; import 'package:rive/src/rive_core/shapes/cubic_mirrored_vertex.dart'; import 'package:rive/src/rive_core/shapes/ellipse.dart'; import 'package:rive/src/rive_core/shapes/image.dart'; +import 'package:rive/src/rive_core/shapes/mesh.dart'; +import 'package:rive/src/rive_core/shapes/mesh_vertex.dart'; import 'package:rive/src/rive_core/shapes/paint/fill.dart'; import 'package:rive/src/rive_core/shapes/paint/gradient_stop.dart'; import 'package:rive/src/rive_core/shapes/paint/linear_gradient.dart'; @@ -100,6 +103,8 @@ import 'package:rive/src/rive_core/shapes/rectangle.dart'; import 'package:rive/src/rive_core/shapes/shape.dart'; import 'package:rive/src/rive_core/shapes/star.dart'; import 'package:rive/src/rive_core/shapes/straight_vertex.dart'; +import 'package:rive/src/rive_core/shapes/text.dart'; +import 'package:rive/src/rive_core/shapes/text_run.dart'; import 'package:rive/src/rive_core/shapes/triangle.dart'; // ignore: avoid_classes_with_only_static_members @@ -198,6 +203,8 @@ class RiveCoreContext { return TrimPath(); case FillBase.typeKey: return Fill(); + case MeshVertexBase.typeKey: + return MeshVertex(); case ShapeBase.typeKey: return Shape(); case WeightBase.typeKey: @@ -208,8 +215,14 @@ class RiveCoreContext { return CubicWeight(); case CubicAsymmetricVertexBase.typeKey: return CubicAsymmetricVertex(); + case MeshBase.typeKey: + return Mesh(); + case TextRunBase.typeKey: + return TextRun(); case PointsPathBase.typeKey: return PointsPath(); + case ContourMeshVertexBase.typeKey: + return ContourMeshVertex(); case RectangleBase.typeKey: return Rectangle(); case CubicMirroredVertexBase.typeKey: @@ -226,6 +239,8 @@ class RiveCoreContext { return Star(); case ImageBase.typeKey: return Image(); + case TextBase.typeKey: + return Text(); case CubicDetachedVertexBase.typeKey: return CubicDetachedVertex(); case DrawRulesBase.typeKey: @@ -730,21 +745,31 @@ class RiveCoreContext { object.fillRule = value; } break; - case PathBase.pathFlagsPropertyKey: - if (object is PathBase && value is int) { - object.pathFlags = value; - } - break; - case PathVertexBase.xPropertyKey: - if (object is PathVertexBase && value is double) { + case VertexBase.xPropertyKey: + if (object is VertexBase && value is double) { object.x = value; } break; - case PathVertexBase.yPropertyKey: - if (object is PathVertexBase && value is double) { + case VertexBase.yPropertyKey: + if (object is VertexBase && value is double) { object.y = value; } break; + case MeshVertexBase.uPropertyKey: + if (object is MeshVertexBase && value is double) { + object.u = value; + } + break; + case MeshVertexBase.vPropertyKey: + if (object is MeshVertexBase && value is double) { + object.v = value; + } + break; + case PathBase.pathFlagsPropertyKey: + if (object is PathBase && value is int) { + object.pathFlags = value; + } + break; case WeightBase.valuesPropertyKey: if (object is WeightBase && value is int) { object.values = value; @@ -795,6 +820,21 @@ class RiveCoreContext { object.outDistance = value; } break; + case MeshBase.triangleIndexBytesPropertyKey: + if (object is MeshBase && value is Uint8List) { + object.triangleIndexBytes = value; + } + break; + case TextRunBase.pointSizePropertyKey: + if (object is TextRunBase && value is double) { + object.pointSize = value; + } + break; + case TextRunBase.textLengthPropertyKey: + if (object is TextRunBase && value is int) { + object.textLength = value; + } + break; case PointsPathBase.isClosedPropertyKey: if (object is PointsPathBase && value is bool) { object.isClosed = value; @@ -890,6 +930,11 @@ class RiveCoreContext { object.assetId = value; } break; + case TextBase.valuePropertyKey: + if (object is TextBase && value is String) { + object.value = value; + } + break; case CubicDetachedVertexBase.inRotationPropertyKey: if (object is CubicDetachedVertexBase && value is double) { object.inRotation = value; @@ -1051,7 +1096,7 @@ class RiveCoreContext { } break; case FileAssetContentsBase.bytesPropertyKey: - if (object is FileAssetContentsBase && value is List) { + if (object is FileAssetContentsBase && value is Uint8List) { object.bytes = value; } break; @@ -1069,6 +1114,7 @@ class RiveCoreContext { case ComponentBase.namePropertyKey: case AnimationBase.namePropertyKey: case StateMachineComponentBase.namePropertyKey: + case TextBase.valuePropertyKey: case AssetBase.namePropertyKey: return stringType; case ComponentBase.parentIdPropertyKey: @@ -1117,6 +1163,7 @@ class RiveCoreContext { case CubicWeightBase.inIndicesPropertyKey: case CubicWeightBase.outValuesPropertyKey: case CubicWeightBase.outIndicesPropertyKey: + case TextRunBase.textLengthPropertyKey: case ClippingShapeBase.sourceIdPropertyKey: case ClippingShapeBase.fillRulePropertyKey: case PolygonBase.pointsPropertyKey: @@ -1161,12 +1208,15 @@ class RiveCoreContext { case TrimPathBase.startPropertyKey: case TrimPathBase.endPropertyKey: case TrimPathBase.offsetPropertyKey: - case PathVertexBase.xPropertyKey: - case PathVertexBase.yPropertyKey: + case VertexBase.xPropertyKey: + case VertexBase.yPropertyKey: + case MeshVertexBase.uPropertyKey: + case MeshVertexBase.vPropertyKey: case StraightVertexBase.radiusPropertyKey: case CubicAsymmetricVertexBase.rotationPropertyKey: case CubicAsymmetricVertexBase.inDistancePropertyKey: case CubicAsymmetricVertexBase.outDistancePropertyKey: + case TextRunBase.pointSizePropertyKey: case ParametricPathBase.widthPropertyKey: case ParametricPathBase.heightPropertyKey: case ParametricPathBase.originXPropertyKey: @@ -1230,6 +1280,7 @@ class RiveCoreContext { case SolidColorBase.colorValuePropertyKey: case GradientStopBase.colorValuePropertyKey: return colorType; + case MeshBase.triangleIndexBytesPropertyKey: case FileAssetContentsBase.bytesPropertyKey: return bytesType; default: @@ -1245,6 +1296,8 @@ class RiveCoreContext { return (object as AnimationBase).name; case StateMachineComponentBase.namePropertyKey: return (object as StateMachineComponentBase).name; + case TextBase.valuePropertyKey: + return (object as TextBase).value; case AssetBase.namePropertyKey: return (object as AssetBase).name; } @@ -1345,6 +1398,8 @@ class RiveCoreContext { return (object as CubicWeightBase).outValues; case CubicWeightBase.outIndicesPropertyKey: return (object as CubicWeightBase).outIndices; + case TextRunBase.textLengthPropertyKey: + return (object as TextRunBase).textLength; case ClippingShapeBase.sourceIdPropertyKey: return (object as ClippingShapeBase).sourceId; case ClippingShapeBase.fillRulePropertyKey: @@ -1437,10 +1492,14 @@ class RiveCoreContext { return (object as TrimPathBase).end; case TrimPathBase.offsetPropertyKey: return (object as TrimPathBase).offset; - case PathVertexBase.xPropertyKey: - return (object as PathVertexBase).x; - case PathVertexBase.yPropertyKey: - return (object as PathVertexBase).y; + case VertexBase.xPropertyKey: + return (object as VertexBase).x; + case VertexBase.yPropertyKey: + return (object as VertexBase).y; + case MeshVertexBase.uPropertyKey: + return (object as MeshVertexBase).u; + case MeshVertexBase.vPropertyKey: + return (object as MeshVertexBase).v; case StraightVertexBase.radiusPropertyKey: return (object as StraightVertexBase).radius; case CubicAsymmetricVertexBase.rotationPropertyKey: @@ -1449,6 +1508,8 @@ class RiveCoreContext { return (object as CubicAsymmetricVertexBase).inDistance; case CubicAsymmetricVertexBase.outDistancePropertyKey: return (object as CubicAsymmetricVertexBase).outDistance; + case TextRunBase.pointSizePropertyKey: + return (object as TextRunBase).pointSize; case ParametricPathBase.widthPropertyKey: return (object as ParametricPathBase).width; case ParametricPathBase.heightPropertyKey: @@ -1585,12 +1646,14 @@ class RiveCoreContext { return 0; } - static List getBytes(Core object, int propertyKey) { + static Uint8List getBytes(Core object, int propertyKey) { switch (propertyKey) { + case MeshBase.triangleIndexBytesPropertyKey: + return (object as MeshBase).triangleIndexBytes; case FileAssetContentsBase.bytesPropertyKey: return (object as FileAssetContentsBase).bytes; } - return []; + return Uint8List(0); } static void setString(Core object, int propertyKey, String value) { @@ -1610,6 +1673,11 @@ class RiveCoreContext { object.name = value; } break; + case TextBase.valuePropertyKey: + if (object is TextBase) { + object.value = value; + } + break; case AssetBase.namePropertyKey: if (object is AssetBase) { object.name = value; @@ -1850,6 +1918,11 @@ class RiveCoreContext { object.outIndices = value; } break; + case TextRunBase.textLengthPropertyKey: + if (object is TextRunBase) { + object.textLength = value; + } + break; case ClippingShapeBase.sourceIdPropertyKey: if (object is ClippingShapeBase) { object.sourceId = value; @@ -2070,16 +2143,26 @@ class RiveCoreContext { object.offset = value; } break; - case PathVertexBase.xPropertyKey: - if (object is PathVertexBase) { + case VertexBase.xPropertyKey: + if (object is VertexBase) { object.x = value; } break; - case PathVertexBase.yPropertyKey: - if (object is PathVertexBase) { + case VertexBase.yPropertyKey: + if (object is VertexBase) { object.y = value; } break; + case MeshVertexBase.uPropertyKey: + if (object is MeshVertexBase) { + object.u = value; + } + break; + case MeshVertexBase.vPropertyKey: + if (object is MeshVertexBase) { + object.v = value; + } + break; case StraightVertexBase.radiusPropertyKey: if (object is StraightVertexBase) { object.radius = value; @@ -2100,6 +2183,11 @@ class RiveCoreContext { object.outDistance = value; } break; + case TextRunBase.pointSizePropertyKey: + if (object is TextRunBase) { + object.pointSize = value; + } + break; case ParametricPathBase.widthPropertyKey: if (object is ParametricPathBase) { object.width = value; @@ -2413,8 +2501,13 @@ class RiveCoreContext { } } - static void setBytes(Core object, int propertyKey, List value) { + static void setBytes(Core object, int propertyKey, Uint8List value) { switch (propertyKey) { + case MeshBase.triangleIndexBytesPropertyKey: + if (object is MeshBase) { + object.triangleIndexBytes = value; + } + break; case FileAssetContentsBase.bytesPropertyKey: if (object is FileAssetContentsBase) { object.bytes = value; diff --git a/lib/src/generated/shapes/contour_mesh_vertex_base.dart b/lib/src/generated/shapes/contour_mesh_vertex_base.dart new file mode 100644 index 0000000..72e9864 --- /dev/null +++ b/lib/src/generated/shapes/contour_mesh_vertex_base.dart @@ -0,0 +1,22 @@ +/// Core automatically generated +/// lib/src/generated/shapes/contour_mesh_vertex_base.dart. +/// Do not modify manually. + +import 'package:rive/src/generated/component_base.dart'; +import 'package:rive/src/generated/container_component_base.dart'; +import 'package:rive/src/generated/shapes/vertex_base.dart'; +import 'package:rive/src/rive_core/shapes/mesh_vertex.dart'; + +abstract class ContourMeshVertexBase extends MeshVertex { + static const int typeKey = 111; + @override + int get coreType => ContourMeshVertexBase.typeKey; + @override + Set get coreTypes => { + ContourMeshVertexBase.typeKey, + MeshVertexBase.typeKey, + VertexBase.typeKey, + ContainerComponentBase.typeKey, + ComponentBase.typeKey + }; +} diff --git a/lib/src/generated/shapes/cubic_asymmetric_vertex_base.dart b/lib/src/generated/shapes/cubic_asymmetric_vertex_base.dart index b04164d..0e4ba52 100644 --- a/lib/src/generated/shapes/cubic_asymmetric_vertex_base.dart +++ b/lib/src/generated/shapes/cubic_asymmetric_vertex_base.dart @@ -5,6 +5,7 @@ import 'package:rive/src/generated/component_base.dart'; import 'package:rive/src/generated/container_component_base.dart'; import 'package:rive/src/generated/shapes/path_vertex_base.dart'; +import 'package:rive/src/generated/shapes/vertex_base.dart'; import 'package:rive/src/rive_core/shapes/cubic_vertex.dart'; abstract class CubicAsymmetricVertexBase extends CubicVertex { @@ -16,6 +17,7 @@ abstract class CubicAsymmetricVertexBase extends CubicVertex { CubicAsymmetricVertexBase.typeKey, CubicVertexBase.typeKey, PathVertexBase.typeKey, + VertexBase.typeKey, ContainerComponentBase.typeKey, ComponentBase.typeKey }; diff --git a/lib/src/generated/shapes/cubic_detached_vertex_base.dart b/lib/src/generated/shapes/cubic_detached_vertex_base.dart index 9bc3a58..abf8796 100644 --- a/lib/src/generated/shapes/cubic_detached_vertex_base.dart +++ b/lib/src/generated/shapes/cubic_detached_vertex_base.dart @@ -5,6 +5,7 @@ import 'package:rive/src/generated/component_base.dart'; import 'package:rive/src/generated/container_component_base.dart'; import 'package:rive/src/generated/shapes/path_vertex_base.dart'; +import 'package:rive/src/generated/shapes/vertex_base.dart'; import 'package:rive/src/rive_core/shapes/cubic_vertex.dart'; abstract class CubicDetachedVertexBase extends CubicVertex { @@ -16,6 +17,7 @@ abstract class CubicDetachedVertexBase extends CubicVertex { CubicDetachedVertexBase.typeKey, CubicVertexBase.typeKey, PathVertexBase.typeKey, + VertexBase.typeKey, ContainerComponentBase.typeKey, ComponentBase.typeKey }; diff --git a/lib/src/generated/shapes/cubic_mirrored_vertex_base.dart b/lib/src/generated/shapes/cubic_mirrored_vertex_base.dart index 1f0b34c..525738d 100644 --- a/lib/src/generated/shapes/cubic_mirrored_vertex_base.dart +++ b/lib/src/generated/shapes/cubic_mirrored_vertex_base.dart @@ -5,6 +5,7 @@ import 'package:rive/src/generated/component_base.dart'; import 'package:rive/src/generated/container_component_base.dart'; import 'package:rive/src/generated/shapes/path_vertex_base.dart'; +import 'package:rive/src/generated/shapes/vertex_base.dart'; import 'package:rive/src/rive_core/shapes/cubic_vertex.dart'; abstract class CubicMirroredVertexBase extends CubicVertex { @@ -16,6 +17,7 @@ abstract class CubicMirroredVertexBase extends CubicVertex { CubicMirroredVertexBase.typeKey, CubicVertexBase.typeKey, PathVertexBase.typeKey, + VertexBase.typeKey, ContainerComponentBase.typeKey, ComponentBase.typeKey }; diff --git a/lib/src/generated/shapes/cubic_vertex_base.dart b/lib/src/generated/shapes/cubic_vertex_base.dart index d88a74a..e4d8fed 100644 --- a/lib/src/generated/shapes/cubic_vertex_base.dart +++ b/lib/src/generated/shapes/cubic_vertex_base.dart @@ -4,6 +4,7 @@ import 'package:rive/src/generated/component_base.dart'; import 'package:rive/src/generated/container_component_base.dart'; +import 'package:rive/src/generated/shapes/vertex_base.dart'; import 'package:rive/src/rive_core/bones/cubic_weight.dart'; import 'package:rive/src/rive_core/shapes/path_vertex.dart'; @@ -15,6 +16,7 @@ abstract class CubicVertexBase extends PathVertex { Set get coreTypes => { CubicVertexBase.typeKey, PathVertexBase.typeKey, + VertexBase.typeKey, ContainerComponentBase.typeKey, ComponentBase.typeKey }; diff --git a/lib/src/generated/shapes/forced_edge_base.dart b/lib/src/generated/shapes/forced_edge_base.dart new file mode 100644 index 0000000..b8a36c2 --- /dev/null +++ b/lib/src/generated/shapes/forced_edge_base.dart @@ -0,0 +1,67 @@ +/// Core automatically generated lib/src/generated/shapes/forced_edge_base.dart. +/// Do not modify manually. + +import 'package:rive/src/rive_core/component.dart'; + +abstract class ForcedEdgeBase extends Component { + static const int typeKey = 112; + @override + int get coreType => ForcedEdgeBase.typeKey; + @override + Set get coreTypes => {ForcedEdgeBase.typeKey, ComponentBase.typeKey}; + + /// -------------------------------------------------------------------------- + /// FromId field with key 219. + static const int fromIdInitialValue = 0; + int _fromId = fromIdInitialValue; + static const int fromIdPropertyKey = 219; + + /// Identifier used to track MeshVertex the force edge extends from. + int get fromId => _fromId; + + /// Change the [_fromId] field value. + /// [fromIdChanged] will be invoked only if the field's value has changed. + set fromId(int value) { + if (_fromId == value) { + return; + } + int from = _fromId; + _fromId = value; + if (hasValidated) { + fromIdChanged(from, value); + } + } + + void fromIdChanged(int from, int to); + + /// -------------------------------------------------------------------------- + /// ToId field with key 220. + static const int toIdInitialValue = 0; + int _toId = toIdInitialValue; + static const int toIdPropertyKey = 220; + + /// Identifier used to track MeshVertex the force edge extends to. + int get toId => _toId; + + /// Change the [_toId] field value. + /// [toIdChanged] will be invoked only if the field's value has changed. + set toId(int value) { + if (_toId == value) { + return; + } + int from = _toId; + _toId = value; + if (hasValidated) { + toIdChanged(from, value); + } + } + + void toIdChanged(int from, int to); + + @override + void copy(covariant ForcedEdgeBase source) { + super.copy(source); + _fromId = source._fromId; + _toId = source._toId; + } +} diff --git a/lib/src/generated/shapes/mesh_base.dart b/lib/src/generated/shapes/mesh_base.dart new file mode 100644 index 0000000..9841ed3 --- /dev/null +++ b/lib/src/generated/shapes/mesh_base.dart @@ -0,0 +1,47 @@ +/// Core automatically generated lib/src/generated/shapes/mesh_base.dart. +/// Do not modify manually. + +import 'package:rive/src/core/core.dart'; +import 'package:rive/src/generated/component_base.dart'; +import 'package:rive/src/generated/container_component_base.dart'; +import 'package:rive/src/rive_core/container_component.dart'; + +abstract class MeshBase extends ContainerComponent { + static const int typeKey = 109; + @override + int get coreType => MeshBase.typeKey; + @override + Set get coreTypes => + {MeshBase.typeKey, ContainerComponentBase.typeKey, ComponentBase.typeKey}; + + /// -------------------------------------------------------------------------- + /// TriangleIndexBytes field with key 223. + static final Uint8List triangleIndexBytesInitialValue = Uint8List(0); + Uint8List _triangleIndexBytes = triangleIndexBytesInitialValue; + static const int triangleIndexBytesPropertyKey = 223; + + /// Byte data for the triangle indices. + Uint8List get triangleIndexBytes => _triangleIndexBytes; + + /// Change the [_triangleIndexBytes] field value. + /// [triangleIndexBytesChanged] will be invoked only if the field's value has + /// changed. + set triangleIndexBytes(Uint8List value) { + if (listEquals(_triangleIndexBytes, value)) { + return; + } + Uint8List from = _triangleIndexBytes; + _triangleIndexBytes = value; + if (hasValidated) { + triangleIndexBytesChanged(from, value); + } + } + + void triangleIndexBytesChanged(Uint8List from, Uint8List to); + + @override + void copy(covariant MeshBase source) { + super.copy(source); + _triangleIndexBytes = source._triangleIndexBytes; + } +} diff --git a/lib/src/generated/shapes/mesh_vertex_base.dart b/lib/src/generated/shapes/mesh_vertex_base.dart new file mode 100644 index 0000000..23383e7 --- /dev/null +++ b/lib/src/generated/shapes/mesh_vertex_base.dart @@ -0,0 +1,74 @@ +/// Core automatically generated lib/src/generated/shapes/mesh_vertex_base.dart. +/// Do not modify manually. + +import 'package:rive/src/generated/component_base.dart'; +import 'package:rive/src/generated/container_component_base.dart'; +import 'package:rive/src/rive_core/shapes/vertex.dart'; + +abstract class MeshVertexBase extends Vertex { + static const int typeKey = 108; + @override + int get coreType => MeshVertexBase.typeKey; + @override + Set get coreTypes => { + MeshVertexBase.typeKey, + VertexBase.typeKey, + ContainerComponentBase.typeKey, + ComponentBase.typeKey + }; + + /// -------------------------------------------------------------------------- + /// U field with key 215. + static const double uInitialValue = 0; + double _u = uInitialValue; + static const int uPropertyKey = 215; + + /// U value for the texture coordinate of the vertex. + double get u => _u; + + /// Change the [_u] field value. + /// [uChanged] will be invoked only if the field's value has changed. + set u(double value) { + if (_u == value) { + return; + } + double from = _u; + _u = value; + if (hasValidated) { + uChanged(from, value); + } + } + + void uChanged(double from, double to); + + /// -------------------------------------------------------------------------- + /// V field with key 216. + static const double vInitialValue = 0; + double _v = vInitialValue; + static const int vPropertyKey = 216; + + /// V value for the texture coordinate of the vertex. + double get v => _v; + + /// Change the [_v] field value. + /// [vChanged] will be invoked only if the field's value has changed. + set v(double value) { + if (_v == value) { + return; + } + double from = _v; + _v = value; + if (hasValidated) { + vChanged(from, value); + } + } + + void vChanged(double from, double to); + + @override + void copy(covariant MeshVertexBase source) { + super.copy(source); + _u = source._u; + _v = source._v; + } +} diff --git a/lib/src/generated/shapes/path_vertex_base.dart b/lib/src/generated/shapes/path_vertex_base.dart index 9f5fee4..96daf1b 100644 --- a/lib/src/generated/shapes/path_vertex_base.dart +++ b/lib/src/generated/shapes/path_vertex_base.dart @@ -3,71 +3,18 @@ import 'package:rive/src/generated/component_base.dart'; import 'package:rive/src/generated/container_component_base.dart'; -import 'package:rive/src/rive_core/container_component.dart'; +import 'package:rive/src/rive_core/bones/weight.dart'; +import 'package:rive/src/rive_core/shapes/vertex.dart'; -abstract class PathVertexBase extends ContainerComponent { +abstract class PathVertexBase extends Vertex { static const int typeKey = 14; @override int get coreType => PathVertexBase.typeKey; @override Set get coreTypes => { PathVertexBase.typeKey, + VertexBase.typeKey, ContainerComponentBase.typeKey, ComponentBase.typeKey }; - - /// -------------------------------------------------------------------------- - /// X field with key 24. - static const double xInitialValue = 0; - double _x = xInitialValue; - static const int xPropertyKey = 24; - - /// X value for the translation of the vertex. - double get x => _x; - - /// Change the [_x] field value. - /// [xChanged] will be invoked only if the field's value has changed. - set x(double value) { - if (_x == value) { - return; - } - double from = _x; - _x = value; - if (hasValidated) { - xChanged(from, value); - } - } - - void xChanged(double from, double to); - - /// -------------------------------------------------------------------------- - /// Y field with key 25. - static const double yInitialValue = 0; - double _y = yInitialValue; - static const int yPropertyKey = 25; - - /// Y value for the translation of the vertex. - double get y => _y; - - /// Change the [_y] field value. - /// [yChanged] will be invoked only if the field's value has changed. - set y(double value) { - if (_y == value) { - return; - } - double from = _y; - _y = value; - if (hasValidated) { - yChanged(from, value); - } - } - - void yChanged(double from, double to); - - @override - void copy(covariant PathVertexBase source) { - super.copy(source); - _x = source._x; - _y = source._y; - } } diff --git a/lib/src/generated/shapes/straight_vertex_base.dart b/lib/src/generated/shapes/straight_vertex_base.dart index f686ad4..07607dc 100644 --- a/lib/src/generated/shapes/straight_vertex_base.dart +++ b/lib/src/generated/shapes/straight_vertex_base.dart @@ -4,6 +4,7 @@ import 'package:rive/src/generated/component_base.dart'; import 'package:rive/src/generated/container_component_base.dart'; +import 'package:rive/src/generated/shapes/vertex_base.dart'; import 'package:rive/src/rive_core/bones/weight.dart'; import 'package:rive/src/rive_core/shapes/path_vertex.dart'; @@ -15,6 +16,7 @@ abstract class StraightVertexBase extends PathVertex { Set get coreTypes => { StraightVertexBase.typeKey, PathVertexBase.typeKey, + VertexBase.typeKey, ContainerComponentBase.typeKey, ComponentBase.typeKey }; diff --git a/lib/src/generated/shapes/text_base.dart b/lib/src/generated/shapes/text_base.dart new file mode 100644 index 0000000..8b4033d --- /dev/null +++ b/lib/src/generated/shapes/text_base.dart @@ -0,0 +1,53 @@ +/// Core automatically generated lib/src/generated/shapes/text_base.dart. +/// Do not modify manually. + +import 'package:rive/src/generated/component_base.dart'; +import 'package:rive/src/generated/container_component_base.dart'; +import 'package:rive/src/generated/transform_component_base.dart'; +import 'package:rive/src/generated/world_transform_component_base.dart'; +import 'package:rive/src/rive_core/node.dart'; + +abstract class TextBase extends Node { + static const int typeKey = 110; + @override + int get coreType => TextBase.typeKey; + @override + Set get coreTypes => { + TextBase.typeKey, + NodeBase.typeKey, + TransformComponentBase.typeKey, + WorldTransformComponentBase.typeKey, + ContainerComponentBase.typeKey, + ComponentBase.typeKey + }; + + /// -------------------------------------------------------------------------- + /// Value field with key 218. + static const String valueInitialValue = ''; + String _value = valueInitialValue; + static const int valuePropertyKey = 218; + + /// The value stored in the Text object. + String get value => _value; + + /// Change the [_value] field value. + /// [valueChanged] will be invoked only if the field's value has changed. + set value(String value) { + if (_value == value) { + return; + } + String from = _value; + _value = value; + if (hasValidated) { + valueChanged(from, value); + } + } + + void valueChanged(String from, String to); + + @override + void copy(covariant TextBase source) { + super.copy(source); + _value = source._value; + } +} diff --git a/lib/src/generated/shapes/text_run_base.dart b/lib/src/generated/shapes/text_run_base.dart new file mode 100644 index 0000000..6632081 --- /dev/null +++ b/lib/src/generated/shapes/text_run_base.dart @@ -0,0 +1,80 @@ +/// Core automatically generated lib/src/generated/shapes/text_run_base.dart. +/// Do not modify manually. + +import 'package:rive/src/generated/component_base.dart'; +import 'package:rive/src/generated/container_component_base.dart'; +import 'package:rive/src/generated/node_base.dart'; +import 'package:rive/src/generated/transform_component_base.dart'; +import 'package:rive/src/generated/world_transform_component_base.dart'; +import 'package:rive/src/rive_core/drawable.dart'; + +abstract class TextRunBase extends Drawable { + static const int typeKey = 113; + @override + int get coreType => TextRunBase.typeKey; + @override + Set get coreTypes => { + TextRunBase.typeKey, + DrawableBase.typeKey, + NodeBase.typeKey, + TransformComponentBase.typeKey, + WorldTransformComponentBase.typeKey, + ContainerComponentBase.typeKey, + ComponentBase.typeKey + }; + + /// -------------------------------------------------------------------------- + /// PointSize field with key 221. + static const double pointSizeInitialValue = 16; + double _pointSize = pointSizeInitialValue; + static const int pointSizePropertyKey = 221; + + /// The point size for text styled by this run. + double get pointSize => _pointSize; + + /// Change the [_pointSize] field value. + /// [pointSizeChanged] will be invoked only if the field's value has changed. + set pointSize(double value) { + if (_pointSize == value) { + return; + } + double from = _pointSize; + _pointSize = value; + if (hasValidated) { + pointSizeChanged(from, value); + } + } + + void pointSizeChanged(double from, double to); + + /// -------------------------------------------------------------------------- + /// TextLength field with key 222. + static const int textLengthInitialValue = 0; + int _textLength = textLengthInitialValue; + static const int textLengthPropertyKey = 222; + + /// The length of the text styled by this run. + int get textLength => _textLength; + + /// Change the [_textLength] field value. + /// [textLengthChanged] will be invoked only if the field's value has changed. + set textLength(int value) { + if (_textLength == value) { + return; + } + int from = _textLength; + _textLength = value; + if (hasValidated) { + textLengthChanged(from, value); + } + } + + void textLengthChanged(int from, int to); + + @override + void copy(covariant TextRunBase source) { + super.copy(source); + _pointSize = source._pointSize; + _textLength = source._textLength; + } +} diff --git a/lib/src/generated/shapes/vertex_base.dart b/lib/src/generated/shapes/vertex_base.dart new file mode 100644 index 0000000..55a942b --- /dev/null +++ b/lib/src/generated/shapes/vertex_base.dart @@ -0,0 +1,73 @@ +/// Core automatically generated lib/src/generated/shapes/vertex_base.dart. +/// Do not modify manually. + +import 'package:rive/src/generated/component_base.dart'; +import 'package:rive/src/generated/container_component_base.dart'; +import 'package:rive/src/rive_core/container_component.dart'; + +abstract class VertexBase extends ContainerComponent { + static const int typeKey = 107; + @override + int get coreType => VertexBase.typeKey; + @override + Set get coreTypes => { + VertexBase.typeKey, + ContainerComponentBase.typeKey, + ComponentBase.typeKey + }; + + /// -------------------------------------------------------------------------- + /// X field with key 24. + static const double xInitialValue = 0; + double _x = xInitialValue; + static const int xPropertyKey = 24; + + /// X value for the translation of the vertex. + double get x => _x; + + /// Change the [_x] field value. + /// [xChanged] will be invoked only if the field's value has changed. + set x(double value) { + if (_x == value) { + return; + } + double from = _x; + _x = value; + if (hasValidated) { + xChanged(from, value); + } + } + + void xChanged(double from, double to); + + /// -------------------------------------------------------------------------- + /// Y field with key 25. + static const double yInitialValue = 0; + double _y = yInitialValue; + static const int yPropertyKey = 25; + + /// Y value for the translation of the vertex. + double get y => _y; + + /// Change the [_y] field value. + /// [yChanged] will be invoked only if the field's value has changed. + set y(double value) { + if (_y == value) { + return; + } + double from = _y; + _y = value; + if (hasValidated) { + yChanged(from, value); + } + } + + void yChanged(double from, double to); + + @override + void copy(covariant VertexBase source) { + super.copy(source); + _x = source._x; + _y = source._y; + } +} diff --git a/lib/src/rive_core/animation/blend_animation.dart b/lib/src/rive_core/animation/blend_animation.dart index 786cf3b..68f2b8e 100644 --- a/lib/src/rive_core/animation/blend_animation.dart +++ b/lib/src/rive_core/animation/blend_animation.dart @@ -1,6 +1,5 @@ import 'package:rive/src/core/core.dart'; import 'package:rive/src/generated/animation/blend_animation_base.dart'; -import 'package:rive/src/generated/artboard_base.dart'; import 'package:rive/src/rive_core/animation/layer_state.dart'; import 'package:rive/src/rive_core/animation/linear_animation.dart'; import 'package:rive/src/rive_core/artboard.dart'; diff --git a/lib/src/rive_core/animation/blend_state_instance.dart b/lib/src/rive_core/animation/blend_state_instance.dart index d25e594..66abf24 100644 --- a/lib/src/rive_core/animation/blend_state_instance.dart +++ b/lib/src/rive_core/animation/blend_state_instance.dart @@ -1,6 +1,5 @@ import 'dart:collection'; -import 'package:flutter/foundation.dart'; import 'package:rive/src/core/core.dart'; import 'package:rive/src/rive_core/animation/blend_animation.dart'; import 'package:rive/src/rive_core/animation/blend_state.dart'; diff --git a/lib/src/rive_core/animation/cubic_interpolator.dart b/lib/src/rive_core/animation/cubic_interpolator.dart index 0e69d9c..ee7d3dd 100644 --- a/lib/src/rive_core/animation/cubic_interpolator.dart +++ b/lib/src/rive_core/animation/cubic_interpolator.dart @@ -1,5 +1,3 @@ -import 'dart:typed_data'; - import 'package:rive/src/core/core.dart'; import 'package:rive/src/generated/animation/cubic_interpolator_base.dart'; import 'package:rive/src/rive_core/animation/interpolator.dart'; diff --git a/lib/src/rive_core/animation/transition_value_condition.dart b/lib/src/rive_core/animation/transition_value_condition.dart index 62ef412..76979d5 100644 --- a/lib/src/rive_core/animation/transition_value_condition.dart +++ b/lib/src/rive_core/animation/transition_value_condition.dart @@ -7,7 +7,5 @@ abstract class TransitionValueCondition extends TransitionValueConditionBase { TransitionConditionOp get op => TransitionConditionOp.values[opValue]; @override - void opValueChanged(int from, int to) { - // TODO: implement opValueChanged - } + void opValueChanged(int from, int to) {} } diff --git a/lib/src/rive_core/assets/asset.dart b/lib/src/rive_core/assets/asset.dart index ea2e17d..c92dc5f 100644 --- a/lib/src/rive_core/assets/asset.dart +++ b/lib/src/rive_core/assets/asset.dart @@ -1,4 +1,5 @@ import 'package:rive/src/generated/assets/asset_base.dart'; + export 'package:rive/src/generated/assets/asset_base.dart'; class Asset extends AssetBase { diff --git a/lib/src/rive_core/assets/file_asset.dart b/lib/src/rive_core/assets/file_asset.dart index 0418810..e1947d9 100644 --- a/lib/src/rive_core/assets/file_asset.dart +++ b/lib/src/rive_core/assets/file_asset.dart @@ -1,5 +1,3 @@ -import 'dart:typed_data'; - import 'package:rive/src/core/core.dart'; import 'package:rive/src/generated/assets/file_asset_base.dart'; import 'package:rive/src/rive_core/backboard.dart'; diff --git a/lib/src/rive_core/assets/image_asset.dart b/lib/src/rive_core/assets/image_asset.dart index 70f6b1b..2ec6db6 100644 --- a/lib/src/rive_core/assets/image_asset.dart +++ b/lib/src/rive_core/assets/image_asset.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'package:flutter/foundation.dart'; import 'package:rive/src/generated/assets/image_asset_base.dart'; import 'package:rive/src/rive_core/shapes/image.dart'; @@ -11,6 +12,11 @@ class ImageAsset extends ImageAssetBase { ui.Image? _image; ui.Image? get image => _image; + ImageAsset(); + + @visibleForTesting + ImageAsset.fromTestImage(this._image); + @override Future decode(Uint8List bytes) { var completer = Completer(); diff --git a/lib/src/rive_core/bones/skin.dart b/lib/src/rive_core/bones/skin.dart index 7f13afe..e05cad3 100644 --- a/lib/src/rive_core/bones/skin.dart +++ b/lib/src/rive_core/bones/skin.dart @@ -6,7 +6,7 @@ import 'package:rive/src/rive_core/bones/skinnable.dart'; import 'package:rive/src/rive_core/bones/tendon.dart'; import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/math/mat2d.dart'; -import 'package:rive/src/rive_core/shapes/path_vertex.dart'; +import 'package:rive/src/rive_core/shapes/vertex.dart'; export 'package:rive/src/generated/bones/skin_base.dart'; @@ -61,7 +61,7 @@ class Skin extends SkinBase { } } - void deform(List vertices) { + void deform(List vertices) { for (final vertex in vertices) { vertex.deform(_worldTransform, _boneTransforms); } diff --git a/lib/src/rive_core/bones/skinnable.dart b/lib/src/rive_core/bones/skinnable.dart index 36fd0a5..67487a6 100644 --- a/lib/src/rive_core/bones/skinnable.dart +++ b/lib/src/rive_core/bones/skinnable.dart @@ -1,9 +1,11 @@ import 'package:rive/src/rive_core/bones/skin.dart'; import 'package:rive/src/rive_core/component.dart'; +import 'package:rive/src/rive_core/shapes/vertex.dart'; + /// An abstraction to give a common interface to any container component that /// can contain a skin to bind bones to. -abstract class Skinnable { +abstract class Skinnable { // _skin is null when this object isn't connected to bones. Skin? _skin; Skin? get skin => _skin; @@ -28,3 +30,7 @@ abstract class Skinnable { void markSkinDirty(); } + +abstract class SkinnableProvider { + Skinnable? get skinnable; +} diff --git a/lib/src/rive_core/component.dart b/lib/src/rive_core/component.dart index 2c131af..221a616 100644 --- a/lib/src/rive_core/component.dart +++ b/lib/src/rive_core/component.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:rive/src/core/core.dart'; import 'package:rive/src/generated/component_base.dart'; import 'package:rive/src/rive_core/artboard.dart'; diff --git a/lib/src/rive_core/component_dirt.dart b/lib/src/rive_core/component_dirt.dart index f30bd03..ab122d9 100644 --- a/lib/src/rive_core/component_dirt.dart +++ b/lib/src/rive_core/component_dirt.dart @@ -14,6 +14,9 @@ class ComponentDirt { /// Path is dirty and needs to be rebuilt. static const int path = 1 << 4; + /// Text shape is dirty, the shaper needs to re-run. + static const int textShape = 1 << 4; + /// Vertices have changed, re-order cached lists. static const int vertices = 1 << 5; diff --git a/lib/src/rive_core/container_component.dart b/lib/src/rive_core/container_component.dart index c3d2250..8f027c1 100644 --- a/lib/src/rive_core/container_component.dart +++ b/lib/src/rive_core/container_component.dart @@ -1,4 +1,3 @@ -import 'package:flutter/material.dart'; import 'package:rive/src/core/core.dart'; import 'package:rive/src/generated/container_component_base.dart'; import 'package:rive/src/rive_core/component.dart'; @@ -9,6 +8,9 @@ typedef bool DescentCallback(Component component); abstract class ContainerComponent extends ContainerComponentBase { final ContainerChildren children = ContainerChildren(); + + /// Adds the child to the children list and re-wires the parent reference of + /// the child to the parent. Effectively detach and append. void appendChild(Component child) { child.parent = this; } diff --git a/lib/src/rive_core/event.dart b/lib/src/rive_core/event.dart index d369c86..78bb434 100644 --- a/lib/src/rive_core/event.dart +++ b/lib/src/rive_core/event.dart @@ -1,5 +1,4 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; // Just a way to get around the protected notifyListeners so we can use trigger // multiple events from a single object. diff --git a/lib/src/rive_core/shapes/contour_mesh_vertex.dart b/lib/src/rive_core/shapes/contour_mesh_vertex.dart new file mode 100644 index 0000000..9597526 --- /dev/null +++ b/lib/src/rive_core/shapes/contour_mesh_vertex.dart @@ -0,0 +1,4 @@ +import 'package:rive/src/generated/shapes/contour_mesh_vertex_base.dart'; +export 'package:rive/src/generated/shapes/contour_mesh_vertex_base.dart'; + +class ContourMeshVertex extends ContourMeshVertexBase {} diff --git a/lib/src/rive_core/shapes/image.dart b/lib/src/rive_core/shapes/image.dart index 4283f9c..ef81121 100644 --- a/lib/src/rive_core/shapes/image.dart +++ b/lib/src/rive_core/shapes/image.dart @@ -4,13 +4,27 @@ import 'package:rive/src/core/core.dart'; import 'package:rive/src/generated/shapes/image_base.dart'; import 'package:rive/src/rive_core/assets/file_asset.dart'; import 'package:rive/src/rive_core/assets/image_asset.dart'; +import 'package:rive/src/rive_core/bones/skinnable.dart'; +import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/math/aabb.dart'; +import 'package:rive/src/rive_core/math/mat2d.dart'; +import 'package:rive/src/rive_core/shapes/mesh.dart'; +import 'package:rive/src/rive_core/shapes/mesh_vertex.dart'; export 'package:rive/src/generated/shapes/image_base.dart'; -class Image extends ImageBase with FileAssetReferencer { +class Image extends ImageBase + with FileAssetReferencer, SkinnableProvider { + ui.Image? get image => asset?.image; + Mesh? _mesh; + Mesh? get mesh => _mesh; + bool get hasMesh => _mesh != null; + @override AABB get localBounds { + if (hasMesh && _mesh!.draws) { + return _mesh!.bounds; + } if (asset == null) { return AABB.empty(); } @@ -36,8 +50,34 @@ class Image extends ImageBase with FileAssetReferencer { final height = asset!.height; canvas.save(); - canvas.transform(worldTransform.mat4); - canvas.drawImage(uiImage, ui.Offset(-width / 2, -height / 2), paint); + canvas.transform(renderTransform.mat4); + if (_mesh == null || !_mesh!.draws) { + canvas.drawImage(uiImage, ui.Offset(-width / 2, -height / 2), paint); + } else { + paint.shader = ui.ImageShader( + uiImage, + ui.TileMode.clamp, + ui.TileMode.clamp, + Float64List.fromList([ + 1 / width, + 0.0, + 0.0, + 0.0, + 0.0, + 1 / height, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ])); + _mesh!.draw(canvas, paint); + } canvas.restore(); if (clipped) { @@ -62,4 +102,25 @@ class Image extends ImageBase with FileAssetReferencer { super.copy(source); asset = source.asset; } + + @override + void childAdded(Component child) { + super.childAdded(child); + if (child is Mesh) { + _mesh = child; + } + } + + @override + void childRemoved(Component child) { + super.childRemoved(child); + if (child is Mesh && _mesh == child) { + _mesh = null; + } + } + + @override + Skinnable? get skinnable => _mesh; + + Mat2D get renderTransform => _mesh?.worldTransform ?? worldTransform; } diff --git a/lib/src/rive_core/shapes/mesh.dart b/lib/src/rive_core/shapes/mesh.dart new file mode 100644 index 0000000..0ada65d --- /dev/null +++ b/lib/src/rive_core/shapes/mesh.dart @@ -0,0 +1,147 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:rive/src/generated/shapes/mesh_base.dart'; +import 'package:rive/src/rive_core/bones/skinnable.dart'; +import 'package:rive/src/rive_core/component.dart'; +import 'package:rive/src/rive_core/component_dirt.dart'; +import 'package:rive/src/rive_core/drawable.dart'; +import 'package:rive/src/rive_core/math/aabb.dart'; +import 'package:rive/src/rive_core/math/mat2d.dart'; +import 'package:rive/src/rive_core/shapes/contour_mesh_vertex.dart'; +import 'package:rive/src/rive_core/shapes/image.dart'; +import 'package:rive/src/rive_core/shapes/mesh_vertex.dart'; +import 'package:rive/src/rive_core/transform_component.dart'; +import 'package:rive/src/utilities/binary_buffer/binary_reader.dart'; + +export 'package:rive/src/generated/shapes/mesh_base.dart'; + +class Mesh extends MeshBase with Skinnable { + // When bound to bones pathTransform should be the identity as it'll already + // be in world space. + + final List _vertices = []; + @override + List get vertices => _vertices; + + ui.Vertices? _uiVertices; + Uint16List _triangleIndices = Uint16List(0); + Uint16List get triangleIndices => _triangleIndices; + + int get contourVertexCount { + int i = 0; + while (i < _vertices.length && _vertices[i] is ContourMeshVertex) { + i++; + } + return i; + } + + bool get isValid => _uiVertices != null; + + bool get draws { + return isValid; + } + + TransformComponent get transformComponent => parent as Image; + Drawable get drawable => parent as Drawable; + + @override + bool validate() => super.validate() && parent is TransformComponent; + + @override + void childAdded(Component child) { + super.childAdded(child); + if (child is MeshVertex && !_vertices.contains(child)) { + _vertices.add(child); + } + } + + void markDrawableDirty() => addDirt(ComponentDirt.vertices); + + @override + void childRemoved(Component child) { + super.childRemoved(child); + if (child is MeshVertex) { + _vertices.remove(child); + } + } + + @override + void buildDependencies() { + super.buildDependencies(); + parent?.addDependent(this); + skin?.addDependent(this); + } + + AABB bounds = AABB.empty(); + + @override + void update(int dirt) { + if (dirt & ComponentDirt.vertices != 0) { + skin?.deform(_vertices); + + bounds = AABB.empty(); + var vertices = []; + var uv = []; + for (final vertex in _vertices) { + var point = vertex.renderTranslation; + + vertices.add(ui.Offset(point[0], point[1])); + bounds.expandToPoint(point); + uv.add(ui.Offset(vertex.u, vertex.v)); + } + if (_triangleIndices.isEmpty) { + _uiVertices = null; + } else { + _uiVertices = ui.Vertices( + ui.VertexMode.triangles, + vertices, + textureCoordinates: uv, + indices: _triangleIndices, + ); + } + } + } + + void draw(ui.Canvas canvas, ui.Paint paint) { + assert(_uiVertices != null); + canvas.drawVertices(_uiVertices!, ui.BlendMode.srcOver, paint); + } + + void _deserializeTriangleIndices() { + var reader = BinaryReader.fromList(triangleIndexBytes); + List triangles = []; + while (!reader.isEOF) { + triangles.add(reader.readVarUint()); + } + _triangleIndices = Uint16List.fromList(triangles); + + markDrawableDirty(); + } + + @override + void onAdded() { + super.onAdded(); + + _deserializeTriangleIndices(); + } + + @override + void markSkinDirty() => addDirt(ComponentDirt.vertices); + + @override + bool addDirt(int value, {bool recurse = false}) { + if (value == ComponentDirt.vertices) { + // throw 'why'; + } + return super.addDirt(value, recurse: recurse); + } + + @override + Mat2D get worldTransform => + skin != null ? Mat2D.identity : transformComponent.worldTransform; + + @override + void triangleIndexBytesChanged(List from, List to) => + _deserializeTriangleIndices(); +} diff --git a/lib/src/rive_core/shapes/mesh_vertex.dart b/lib/src/rive_core/shapes/mesh_vertex.dart new file mode 100644 index 0000000..3362729 --- /dev/null +++ b/lib/src/rive_core/shapes/mesh_vertex.dart @@ -0,0 +1,26 @@ +import 'package:rive/src/generated/shapes/mesh_vertex_base.dart'; +import 'package:rive/src/rive_core/node.dart'; +import 'package:rive/src/rive_core/shapes/mesh.dart'; +import 'package:rive/src/rive_core/transform_component.dart'; + +export 'package:rive/src/generated/shapes/mesh_vertex_base.dart'; + +class MeshVertex extends MeshVertexBase { + Mesh? get mesh => parent as Mesh?; + + @override + TransformComponent get transformComponent => + mesh?.transformComponent ?? Node(); + + @override + bool validate() => super.validate() && parent is Mesh; + + @override + void markGeometryDirty() => mesh?.markDrawableDirty(); + + @override + void uChanged(double from, double to) {} + + @override + void vChanged(double from, double to) {} +} diff --git a/lib/src/rive_core/shapes/path_vertex.dart b/lib/src/rive_core/shapes/path_vertex.dart index 02de297..4da5585 100644 --- a/lib/src/rive_core/shapes/path_vertex.dart +++ b/lib/src/rive_core/shapes/path_vertex.dart @@ -1,77 +1,12 @@ -import 'dart:typed_data'; - import 'package:rive/src/generated/shapes/path_vertex_base.dart'; import 'package:rive/src/rive_core/bones/weight.dart'; -import 'package:rive/src/rive_core/component.dart'; -import 'package:rive/src/rive_core/math/mat2d.dart'; -import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/shapes/path.dart'; export 'package:rive/src/generated/shapes/path_vertex_base.dart'; -abstract class PathVertex extends PathVertexBase { - T? _weight; - T? get weight => _weight; - +abstract class PathVertex extends PathVertexBase { Path? get path => parent as Path?; @override - void update(int dirt) {} - - final Vec2D _renderTranslation = Vec2D(); - Vec2D get translation => Vec2D.fromValues(x, y); - Vec2D get renderTranslation => _renderTranslation; - - set translation(Vec2D value) { - x = value[0]; - y = value[1]; - } - - @override - void onAddedDirty() { - super.onAddedDirty(); - _renderTranslation[0] = x; - _renderTranslation[1] = y; - } - - @override - void xChanged(double from, double to) { - _renderTranslation[0] = to; - - path?.markPathDirty(); - } - - @override - void yChanged(double from, double to) { - _renderTranslation[1] = to; - - path?.markPathDirty(); - } - - @override - String toString() { - return translation.toString(); - } - - @override - void childAdded(Component component) { - super.childAdded(component); - if (component is T) { - _weight = component; - } - } - - @override - void childRemoved(Component component) { - super.childRemoved(component); - if (_weight == component) { - _weight = null; - } - } - - /// Deform only gets called when we are weighted. - void deform(Mat2D world, Float32List boneTransforms) { - Weight.deform(x, y, weight!.indices, weight!.values, world, boneTransforms, - _weight!.translation); - } + void markGeometryDirty() => path?.markPathDirty(); } diff --git a/lib/src/rive_core/shapes/points_path.dart b/lib/src/rive_core/shapes/points_path.dart index f8cf887..32656ee 100644 --- a/lib/src/rive_core/shapes/points_path.dart +++ b/lib/src/rive_core/shapes/points_path.dart @@ -7,7 +7,7 @@ import 'package:rive/src/rive_core/shapes/path_vertex.dart'; export 'package:rive/src/generated/shapes/points_path_base.dart'; -class PointsPath extends PointsPathBase with Skinnable { +class PointsPath extends PointsPathBase with Skinnable { final List _vertices = []; PointsPath() { diff --git a/lib/src/rive_core/shapes/text.dart b/lib/src/rive_core/shapes/text.dart new file mode 100644 index 0000000..e0f7611 --- /dev/null +++ b/lib/src/rive_core/shapes/text.dart @@ -0,0 +1,81 @@ +import 'dart:ui'; + +import 'package:rive/src/generated/shapes/text_base.dart'; +import 'package:rive/src/rive_core/component_dirt.dart'; +import 'package:rive/src/rive_core/shapes/text_run.dart'; +import 'package:rive/src/rive_core/src/text/paragraph.dart' as rive; + +export 'package:rive/src/generated/shapes/text_base.dart'; + +class Text extends TextBase with rive.Paragraph { + List _textRuns = []; + List _textCodes = []; + Text() { + frame = const Rect.fromLTWH(0, 0, 200, 200); + } + @override + List get textRuns => _textRuns; + + @override + List get textCodes => _textCodes; + + @override + void removeRun(TextRun run, int index) { + run.remove(); + sortChildren(); + } + + @override + bool validate() => super.validate() && areTextRunsValid; + @override + void onAddedDirty() { + super.onAddedDirty(); + _textCodes = value.codeUnits.toList(growable: true); + } + + @override + void textCodesChanged() { + value = String.fromCharCodes(textCodes); + markTextShapeDirty(); + } + + @override + void modifyRuns(int start, int end, rive.TextRunMutator mutator) { + super.modifyRuns(start, end, mutator); + markTextShapeDirty(); + } + + + @override + void sortChildren() { + // Cache the runs. + _textRuns = children.whereType().toList(); + markTextShapeDirty(); + } + + + @override + void valueChanged(String from, String to) { + _textCodes = value.codeUnits.toList(growable: true); + super.textCodesChanged(); + markTextShapeDirty(); + } + + void markTextShapeDirty() { + addDirt(ComponentDirt.textShape); + } + + @override + void update(int dirt) { + super.update(dirt); + if ((dirt & ComponentDirt.textShape) != 0) { + for (final textRun in _textRuns) { + textRun.clearGlyphRuns(); + } + visitGlyphs((glyphRun, start, end, originX, originY) { + var textRun = glyphRun.textRun as TextRun; + textRun.addGlyphRun(glyphRun, start, end, originX, originY); + }); + } + } +} diff --git a/lib/src/rive_core/shapes/text_run.dart b/lib/src/rive_core/shapes/text_run.dart new file mode 100644 index 0000000..fda3cd4 --- /dev/null +++ b/lib/src/rive_core/shapes/text_run.dart @@ -0,0 +1,85 @@ +import 'dart:ui'; + +import 'package:rive/src/generated/shapes/text_run_base.dart'; +import 'package:rive/src/rive_core/shapes/text.dart'; +import 'package:rive/src/rive_core/src/text/raw_path.dart' as rive; +import 'package:rive/src/rive_core/src/text/rive_font.dart'; +import 'package:rive/src/rive_core/src/text/shape_text.dart' as rive; + +export 'package:rive/src/generated/shapes/text_run_base.dart'; + +class _DrawableGlyphRun { + final rive.GlyphRun run; + final int start; + final int end; + final double ox; + final double oy; + + _DrawableGlyphRun(this.run, this.start, this.end, this.ox, this.oy); +} + +class TextRun extends TextRunBase implements rive.TextRun { + @override + RiveFont font = RiveFont.builtIn; + final List<_DrawableGlyphRun> _glyphRuns = []; + + void clearGlyphRuns() => _glyphRuns.clear(); + + void addGlyphRun( + rive.GlyphRun glyphRun, int start, int end, double x, double y) { + _glyphRuns.add(_DrawableGlyphRun(glyphRun, start, end, x, y)); + } + + Object? attr; + @override + bool validate() { + return super.validate() && parent is Text; + } + + Text get text => parent as Text; + + @override + void draw(Canvas canvas) { + canvas.save(); + canvas.transform(text.worldTransform.mat4); + for (final drawableRun in _glyphRuns) { + _visitRawPaths(drawableRun.run, drawableRun.start, drawableRun.end, + drawableRun.ox, drawableRun.oy, (rive.RawPath rp) { + canvas.drawPath(_toPath(rp), Paint()..color = const Color(0xFFFFFFFF)); + }); + } + canvas.restore(); + } + + @override + void pointSizeChanged(double from, double to) { + text.markTextShapeDirty(); + } + + @override + void textLengthChanged(int from, int to) {} + + @override + rive.TextRun cloneRun() { + var cloned = super.clone(); + assert(cloned is TextRun); + return cloned as TextRun; + } +} + +// Should generalize all of this... +Path _toPath(rive.RawPath rp) { + Path p = Path(); + rp.sinker(p.moveTo, p.lineTo, p.quadraticBezierTo, p.cubicTo, p.close); + return p; +} + +void _visitRawPaths(rive.GlyphRun run, int start, int end, double x, double y, + Function(rive.RawPath) visitor) { + for (int i = start; i < end; ++i) { + final rive.RawPath? p = run.font.getRawPath(run.glyphs[i]); + if (p != null) { + visitor(p.scalexy(run.pointSize, run.pointSize, x + run.xpos[i], y)); + } + } +} diff --git a/lib/src/rive_core/shapes/vertex.dart b/lib/src/rive_core/shapes/vertex.dart new file mode 100644 index 0000000..696f2f6 --- /dev/null +++ b/lib/src/rive_core/shapes/vertex.dart @@ -0,0 +1,63 @@ +import 'package:rive/src/core/core.dart'; +import 'package:rive/src/generated/shapes/vertex_base.dart'; +import 'package:rive/src/rive_core/bones/weight.dart'; +import 'package:rive/src/rive_core/component.dart'; +import 'package:rive/src/rive_core/math/mat2d.dart'; +import 'package:rive/src/rive_core/math/vec2d.dart'; + +export 'package:rive/src/generated/shapes/vertex_base.dart'; + +abstract class Vertex extends VertexBase { + T? _weight; + T? get weight => _weight; + + Vec2D get translation => Vec2D.fromValues(x, y); + Vec2D get renderTranslation => weight?.translation ?? translation; + + set translation(Vec2D value) { + x = value[0]; + y = value[1]; + } + + @override + void xChanged(double from, double to) { + markGeometryDirty(); + } + + void markGeometryDirty(); + + @override + void yChanged(double from, double to) { + markGeometryDirty(); + } + + @override + String toString() { + return translation.toString(); + } + + @override + void childAdded(Component component) { + super.childAdded(component); + if (component is T) { + _weight = component; + } + } + + @override + void childRemoved(Component component) { + super.childRemoved(component); + if (_weight == component) { + _weight = null; + } + } + + /// Deform only gets called when we are weighted. + void deform(Mat2D world, Float32List boneTransforms) { + Weight.deform(x, y, weight!.indices, weight!.values, world, boneTransforms, + _weight!.translation); + } + + @override + void update(int dirt) {} +} diff --git a/lib/src/rive_core/src/text/built_in_font.dart b/lib/src/rive_core/src/text/built_in_font.dart new file mode 100644 index 0000000..e6a3bcc --- /dev/null +++ b/lib/src/rive_core/src/text/built_in_font.dart @@ -0,0 +1,2 @@ +const String builtInFontBase64 = + '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'; diff --git a/lib/src/rive_core/src/text/paragraph.dart b/lib/src/rive_core/src/text/paragraph.dart new file mode 100644 index 0000000..114c1db --- /dev/null +++ b/lib/src/rive_core/src/text/paragraph.dart @@ -0,0 +1,684 @@ +import 'dart:math' as math; +import 'dart:ui'; + +import 'raw_path.dart'; +import 'shape_text.dart'; + +bool contains(Rect r, double x, double y) { + return r.left <= x && x <= r.right && r.top <= y && y <= r.bottom; +} + +Rect outset(Rect r, double dx, double dy) { + return Rect.fromLTRB(r.left - dx, r.top - dy, r.right + dx, r.bottom + dy); +} + +double pin(double min, double max, double value) { + return math.min(math.max(value, min), max); +} + +// DO NOT MODIFY the run +typedef TextRunVisitor = void Function(TextRun); + +// Returns either the original, or a new (cloned+modified) run. +// DO NOT MODIFY run.textLength +typedef TextRunMutator = TextRun Function(TextRun); + +// Given a shaped run and lineIndex (e.g. for drawing) +// DO NOT MODIFY the GlyphRun +typedef GlyphRunVisitor = void Function( + GlyphRun, int start, int end, double originX, double originY); + +enum LinePref { + start, + end, +} + +class VBar { + double x, top, bottom; + + VBar(this.x, this.top, this.bottom); +} + +abstract class Paragraph { + Rect _frame = const Rect.fromLTRB(0, 0, 0, 0); + List get textCodes; + List get textRuns; +// cached output + List _runs = []; + List _lines = []; + bool _dirty = true; + + bool get areTextRunsValid { + int len = 0; + textRuns.forEach((run) => len += run.textLength); + return len == textCodes.length; + } + + void _validateTextRuns() { + assert(areTextRunsValid); + } + + String get text => String.fromCharCodes(textCodes); + + void _validateShaping() { + if (textCodes.isEmpty) { + assert(_lines.length == 1); + assert(_runs.length == 1); + // ignore: prefer_is_empty + assert(_runs[0].glyphs.length == 0); + assert(_runs[0].xpos.length == 1); + return; + } + + GlyphLine line = _lines[0]; + line.validate(); + for (int i = 1; i < _lines.length; ++i) { + final GlyphLine nextLine = _lines[i]; + nextLine.validate(); + assert(line.textEnd == nextLine.textStart); + line = nextLine; + } + assert(line.textEnd == textCodes.length); + } + + void _makeAligned() { + double Y = _frame.top; + for (final line in _lines) { + double asc = 0; + double des = 0; + for (int i = line.startRun; i <= line.wsRun; ++i) { + final run = _runs[i]; + asc = math.min(asc, run.font.ascent * run.pointSize); + des = math.max(des, run.font.descent * run.pointSize); + } + line.top = Y; + Y -= asc; + line.baseline = Y; + Y += des; + line.bottom = Y; + } + // TODO: good place to perform left/center/right alignment + } + + void _ensureShapedLines() { + if (_dirty) { + _validateTextRuns(); + if (textCodes.isEmpty) { + return; + // _runs = [ + // GlyphRun(_defaultRun.font, _defaultRun.size, null, 0, + // Uint16List.fromList([]), Float32List.fromList([0])) + // ]; + // _lines = [GlyphLine(_runs, 0, 0, 0, 0, 0, 0, 0)]; + } else { + final breaks = GlyphRun.findWordBreaks(textCodes); + _runs = GlyphRun.shapeText(textCodes, textRuns); + _lines = GlyphLine.lineBreak(textCodes, breaks, _runs, _frame.width); + _makeAligned(); + } + _validateShaping(); + _dirty = false; + } + } + + void trunsChanged() { + _dirty = true; + } + + void textCodesChanged() { + _dirty = true; + } + + void _layoutChanged() { + _dirty = true; + } + + bool get isEmpty { + return textLength == 0; + } + + bool get isNotEmpty { + return textLength != 0; + } + + int get textLength { + return textCodes.length; + } + + Rect get bounds { + _ensureShapedLines(); + if (_lines.isEmpty) { + return Rect.zero; + } else { + return Rect.fromLTRB( + _frame.left, _frame.top, _frame.right, _lines.last.bottom); + } + } + + Rect get frame { + return _frame; + } + + set frame(Rect rect) { + if (rect == _frame) { + return; + } + if (_frame.width != rect.width) { + _layoutChanged(); + } + _frame = rect; + } + + List get lines { + _ensureShapedLines(); + return _lines; + } + + double offsetToFrameX(GlyphLine line, int textOffset) { + return _frame.left + line.offsetToX(textOffset); + } + + int offsetToLineIndex(int textOffset, LinePref pref) { + assert(textOffset >= 0 && textOffset <= textLength); + _ensureShapedLines(); + for (int i = 0; i < _lines.length; ++i) { + final line = _lines[i]; + assert(line.textStart <= textOffset); + if (textOffset <= line.textEnd) { + if (pref == LinePref.start && textOffset == line.textEnd) { + continue; + } + return i; + } + } + return _lines.length - 1; + } + + GlyphLine offsetToLine(int textOffset, LinePref pref) { + int index = offsetToLineIndex(textOffset, pref); + return _lines[index]; + } + +// [ index, offset ] + List _findRunIndexOffset(int offset) { + assert(offset >= 0 && offset <= textCodes.length); + + int prevLenths = 0; + for (int i = 0; i < textRuns.length; ++i) { + final int len = textRuns[i].textLength; + if (offset < len) { + return [i, prevLenths]; + } + prevLenths += len; + // ignore: parameter_assignments + offset -= len; + } + return [textRuns.length - 1, textCodes.length]; // last run + } + + int insert(int offset, String str) { + if (str.isEmpty) { + return 0; + } + assert(textRuns.isNotEmpty); + final pair = _findRunIndexOffset(offset); + final int index = pair[0]; + textRuns[index].textLength += str.length; + trunsChanged(); + final units = str.codeUnits; + textCodes.insertAll(offset, units); + textCodesChanged(); + return units.length; + } + + void delete(int start, int end) { + assert(start <= end); + assert(start >= 0); + assert(end <= textLength); + if (start == end) { + return; + } + + textCodes.removeRange(start, end); + textCodesChanged(); + + int i; + for (i = 0;; ++i) { + final run = textRuns[i]; + if (start < run.textLength) { + break; + } + // ignore: parameter_assignments + start -= run.textLength; + // ignore: parameter_assignments + end -= run.textLength; + } + TextRun run = textRuns[i]; + assert(start >= 0 && start < run.textLength); + if (start > 0) { + // trim leading run + final int amount = math.min(end, run.textLength) - start; + assert(amount > 0 && amount < run.textLength); + run.textLength -= amount; + // ignore: parameter_assignments + start += amount; + assert(start <= end); + // ignore: parameter_assignments + end = end - start; // now end is just a length + i += 1; + } + // remove whole runs + while (end > 0 && end >= textRuns[i].textLength) { + var run = textRuns[i]; + // ignore: parameter_assignments + end -= run.textLength; + _removeRunAt(i); + } + if (end > 0) { + textRuns[i].textLength -= end; + } + trunsChanged(); + } + + void setRun(int start, int end, TextRun newRun) { + modifyRuns(start, end, (TextRun orig) { + return newRun; + }); + } + + void removeRun(covariant TextRun run, int index) { + textRuns.removeAt(index); + } + + void insertRun(covariant TextRun run, covariant TextRun? before, + covariant TextRun? after, int index) { + textRuns.insert(index, run); + } + + void _removeRunAt(int index) { + var run = textRuns[index]; + removeRun(run, index); + } + + void _insertRun(int index, TextRun run) { + TextRun? before, after; + if (index > 0 && index <= textRuns.length) { + before = textRuns[index - 1]; + } + if (index < textRuns.length) { + after = textRuns[index]; + } + + insertRun(run, before, after, index); + } + + /// Resets everything to just the [text] with the [run]. + void setAll(String text, TextRun run) { + run.textLength = text.length; + while (textRuns.isNotEmpty) { + _removeRunAt(0); + } + _insertRun(0, run); + textCodes.clear(); + textCodes.insertAll(0, text.codeUnits); + _validateTextRuns(); + _dirty = true; + } + +// Lambda returns a new TextRun, or null to indicated that the run +// assed in need not be changed. + void modifyRuns(int start, int end, TextRunMutator mutator) { + assert(start >= 0 && start <= end); + assert(end <= textCodes.length); + int modLength = end - start; + if (modLength == 0) { + return; // snothing to modify + } + + final first = _findRunIndexOffset(start); + int trunIndex = first[0]; + int globalOffset = first[1]; + assert(globalOffset < textCodes.length); + + // Handle splitting the first run + if (start > globalOffset) { + TextRun origRun = textRuns[trunIndex]; + TextRun newRun = mutator(origRun); + if (origRun != newRun) { + final int skipLength = start - globalOffset; + newRun.textLength = origRun.textLength - skipLength; + origRun.textLength = skipLength; + _insertRun(++trunIndex, newRun); + trunsChanged(); + if (newRun.textLength > modLength) { + // oops, need to trim and readd oldRun afterwards + origRun = origRun.cloneRun(); + origRun.textLength = newRun.textLength - modLength; + newRun.textLength = modLength; + _insertRun(++trunIndex, origRun); + return; + } + modLength -= newRun.textLength; + } + trunIndex += 1; + } + + // Replace whole runs? + while (modLength > 0 && modLength >= textRuns[trunIndex].textLength) { + TextRun origRun = textRuns[trunIndex]; + modLength -= origRun.textLength; + TextRun newRun = mutator(origRun); + if (origRun != newRun) { + removeRun(origRun, trunIndex); + _insertRun(trunIndex, newRun); + trunsChanged(); + } + trunIndex += 1; + } + + // Trim the last run? + if (modLength > 0) { + TextRun origRun = textRuns[trunIndex]; + assert(modLength < origRun.textLength); + TextRun newRun = mutator(origRun); + if (origRun != newRun) { + newRun.textLength = modLength; + origRun.textLength -= modLength; + _insertRun(trunIndex, newRun); + trunsChanged(); + } + } + +// HOW CAN WE COMPARE RUNS? + if (false) { + // consolidate equal runs... + for (int i = 1; i < textRuns.length; ++i) { + if (textRuns[i - 1] == textRuns[i]) { + textRuns[i - 1].textLength += textRuns[i].textLength; + var run = textRuns[i - 1]; + _removeRunAt(i); + i -= 1; // to undo the loop's ++i + } + } + } + } + + void visitGlyphs(GlyphRunVisitor visitor) { + _ensureShapedLines(); + + for (final GlyphLine line in _lines) { + int start = line.startIndex; + for (int i = line.startRun; i <= line.wsRun; ++i) { + final run = _runs[i]; + int wsEnd = run.glyphs.length; + if (i == line.wsRun) { + // last run on this line + wsEnd = line.wsIndex; + } + visitor(run, start, wsEnd, _frame.left - line.startX, line.baseline); + start = 0; + } + } + } + + List rawGlyphRuns() { + _ensureShapedLines(); + return _runs; + } + + void visitText(TextRunVisitor visitor) { + textRuns.forEach(visitor); + } + + Rect offsetsToRect(GlyphLine line, int start, int end) { + assert(start >= line.textStart); + assert(end <= line.textEnd); + final double left = offsetToFrameX(line, start); + final double right = offsetToFrameX(line, end); + return Rect.fromLTRB(left, line.top, right, line.bottom); + } + + int xyToOffset(double x, double y) { + // ignore: parameter_assignments + x = pin(_frame.left, _frame.right, x); + // ignore: parameter_assignments + y = pin(_lines.first.top, _lines.last.bottom, y); + + // move x into xpos coordinates + // ignore: parameter_assignments + x -= _frame.left; + + for (final GlyphLine line in _lines) { + if (y <= line.bottom) { + // ignore: parameter_assignments + x += line.startX; // now we're relative to the xpos in this line + GlyphRun run = line.runs.last; + for (final GlyphRun r in line.runs) { + if (x < r.right) { + run = r; + break; + } + } + int index = run.xpos.length - 1; + for (int i = 1; i < run.xpos.length; ++i) { + if (x < run.xpos[i]) { + final mid = (run.xpos[i] + run.xpos[i - 1]) * 0.5; + index = x < mid ? i - 1 : i; + break; + } + } + return run.textStart + index; + } + } + assert(false); + return 0; + } + + VBar getVBar(int textOffset, LinePref pref) { + final line = offsetToLine(textOffset, pref); + final x = offsetToFrameX(line, textOffset); + return VBar(x, line.top, line.bottom); + } +} + +///////////////////// + +enum CursorType { + line, + path, +} + +class Cursor { + Paragraph para; + // these are logical, not necessarily sorted + int _start = 0; + int _end = 0; + + Cursor(this.para); + + CursorType get type { + return _start == _end ? CursorType.line : CursorType.path; + } + + String get text => + String.fromCharCodes(para.textCodes.sublist(fromIndex, toIndex)); + + int get start { + return _start; + } + + int get end { + return _end; + } + + int get fromIndex { + return math.min(_start, _end); + } + + int get toIndex { + return math.max(_start, _end); + } + + bool get isCollapsed => _start == _end; + + int _legalize(int index) { + return math.max(math.min(index, para.textLength), 0); + } + + void setIndex(int index) { + setRange(index, index); + } + + void setRange(int start, int end) { + _start = _legalize(start); + _end = _legalize(end); + } + + void insert(String str) { + final int from = fromIndex; + para.delete(from, toIndex); + _start = _end = from + para.insert(from, str); + } + + void delete() { + if (_start == _end) { + if (_start > 0) { + para.delete(_start - 1, _start); + _start = _end = _start - 1; + } + } else { + para.delete(fromIndex, toIndex); + _start = _end = fromIndex; + } + } + + void arrowLeft(bool extend) { + if (extend) { + _end = _legalize(_end - 1); + } else { + if (_start == _end) { + _start = _end = _legalize(_end - 1); + } + _start = _end = fromIndex; + } + } + // todo: need to not pre-swap _start and _end + + void arrowRight(bool extend) { + if (extend) { + _end = _legalize(_end + 1); + } else { + if (_start == _end) { + _start = _end = _legalize(_end + 1); + } else { + _start = _end = toIndex; + } + } + } + + void arrowUp(bool extend) { + LinePref pref = LinePref.end; // store this somehow? + + int index = para.offsetToLineIndex(_end, pref); + if (index == 0) { + _end = 0; + } else { + final double currX = para.offsetToFrameX(para._lines[index], _end); + { + int i = para.xyToOffset(currX, para._lines[index].baseline); + assert(i == _end); + } + index -= 1; + _end = para.xyToOffset(currX, para._lines[index].baseline); + } + if (!extend) { + _start = _end; + } + } + + void arrowDown(bool extend) { + LinePref pref = LinePref.end; // store this somehow? + + int index = para.offsetToLineIndex(_end, pref); + if (index == para._lines.length - 1) { + _end = para.textLength; + } else { + final double currX = para.offsetToFrameX(para._lines[index], _end); + index += 1; + _end = para.xyToOffset(currX, para._lines[index].baseline); + } + if (!extend) { + _start = _end; + } + } + + void pointerDown(double x, double y) { + // ignore: parameter_assignments + x = pin(para.frame.left, para.frame.right, x); + // ignore: parameter_assignments + y = pin(para.frame.top, para.frame.bottom, y); + _start = _end = para.xyToOffset(x, y); + } + + bool pointerMove(double x, double y) { + final newEnd = para.xyToOffset(x, y); + final changed = _end != newEnd; + _end = newEnd; + return changed; + } + + void pointerUp(double x, double y) {} + + // PointerTracker pointerTracker(double x, double y) { + // final t = _CursorPointerTracker(this); + // t.down(x, y); + // return t; + // } + + void modifyRuns(TextRunMutator mutator) { + para.modifyRuns(fromIndex, toIndex, mutator); + } + + VBar getVBar(LinePref pref) { + return para.getVBar(_end, pref); + } + + RawPath getRawPath([LinePref pref = LinePref.end]) { + final b = RawPathBuilder(); + if (type == CursorType.line) { + final bar = getVBar(pref); + const width = 1.0; + b.addLTRB(bar.x, bar.top, bar.x + width, bar.bottom); + } else { + final int from = fromIndex; + final int to = toIndex; + int index = para.offsetToLineIndex(from, LinePref.start); + int last = para.offsetToLineIndex(to, LinePref.end); + if (index == last) { + GlyphLine line = para.lines[index]; + b.addRect(para.offsetsToRect(line, from, to)); + } else { + GlyphLine line = para.lines[index]; + b.addRect(para.offsetsToRect(line, from, line.textEnd)); + while (++index < last) { + line = para.lines[index]; + b.addRect(para.offsetsToRect(line, line.textStart, line.textEnd)); + } + line = para.lines[last]; + b.addRect(para.offsetsToRect(line, line.textStart, to)); + } + } + return b.detach(); + } +} + +class SimpleParagraph extends Paragraph { + final List _textCodes = []; + final List _truns = []; + + @override + List get textCodes => _textCodes; + + @override + List get textRuns => _truns; +} diff --git a/lib/src/rive_core/src/text/raw_path.dart b/lib/src/rive_core/src/text/raw_path.dart new file mode 100644 index 0000000..697b9fe --- /dev/null +++ b/lib/src/rive_core/src/text/raw_path.dart @@ -0,0 +1,149 @@ +import 'dart:typed_data'; +import 'package:flutter/material.dart'; + +const int kMove_PathVerb = 0; +const int kLine_PathVerb = 1; +const int kQuad_PathVerb = 2; +const int kConic_PathVerb = 3; +const int kCubic_PathVerb = 4; +const int kClose_PathVerb = 5; + +class IPathSink { + void moveTo(double x, double y) {} + void lineTo(double x, double y) {} + void quadTo(double x, double y, double x1, double y1) {} + void cubicTo( + double x, double y, double x1, double y1, double x2, double y2) {} + void close() {} +} + +class RawPath { + Float32List pts; + Uint8List verbs; + + RawPath(this.pts, this.verbs); + + void sinker( + Function(double, double) moveTo, + Function(double, double) lineTo, + Function(double, double, double, double) quadTo, + Function(double, double, double, double, double, double) cubicTo, + Function close) { + int i = 0; + for (final int verb in verbs) { + switch (verb) { + case kMove_PathVerb: + moveTo(pts[i + 0], pts[i + 1]); + i += 2; + break; + case kLine_PathVerb: + lineTo(pts[i + 0], pts[i + 1]); + i += 2; + break; + case kQuad_PathVerb: + quadTo(pts[i + 0], pts[i + 1], pts[i + 2], pts[i + 3]); + i += 4; + break; + case kConic_PathVerb: // not supported + assert(false); + break; + case kCubic_PathVerb: + cubicTo(pts[i + 0], pts[i + 1], pts[i + 2], pts[i + 3], pts[i + 4], + pts[i + 5]); + i += 6; + break; + case kClose_PathVerb: + close(); + break; + default: + throw 'unknown verb $verb'; + } + } + assert(i == pts.length); + } + + RawPath scalexy(double sx, double sy, double tx, double ty) { + final newp = Float32List(pts.length); + for (int i = 0; i < pts.length; i += 2) { + newp[i + 0] = pts[i + 0] * sx + tx; + newp[i + 1] = pts[i + 1] * sy + ty; + } + // we share verbs to save memory -- so don't modify them! + return RawPath(Float32List.fromList(newp), verbs); + } +} + +class RawPathBuilder { + List pts = []; + List vbs = []; + + void reset() { + pts = []; + vbs = []; + } + + double get lastX { + return pts[pts.length - 2]; + } + + double get lastY { + return pts[pts.length - 1]; + } + + void moveTo(double x, double y) { + pts.add(x); + pts.add(y); + vbs.add(kMove_PathVerb); + } + + void lineTo(double x, double y) { + pts.add(x); + pts.add(y); + vbs.add(kLine_PathVerb); + } + + void quadTo(double x, double y, double x1, double y1) { + pts.add(x); + pts.add(y); + pts.add(x1); + pts.add(y1); + vbs.add(kQuad_PathVerb); + } + + void cubicTo(double x, double y, double x1, double y1, double x2, double y2) { + pts.add(x); + pts.add(y); + pts.add(x1); + pts.add(y1); + pts.add(x2); + pts.add(y2); + vbs.add(kCubic_PathVerb); + } + + void close() { + vbs.add(kClose_PathVerb); + } + + void addLine(double x0, double y0, double x1, double y1) { + moveTo(x0, y0); + lineTo(x1, y1); + } + + void addLTRB(double l, double t, double r, double b) { + moveTo(l, t); + lineTo(r, t); + lineTo(r, b); + lineTo(l, b); + close(); + } + + void addRect(Rect r) { + addLTRB(r.left, r.top, r.right, r.bottom); + } + + RawPath detach() { + final rp = RawPath(Float32List.fromList(pts), Uint8List.fromList(vbs)); + reset(); + return rp; + } +} diff --git a/lib/src/rive_core/src/text/rive_font.dart b/lib/src/rive_core/src/text/rive_font.dart new file mode 100644 index 0000000..5611141 --- /dev/null +++ b/lib/src/rive_core/src/text/rive_font.dart @@ -0,0 +1,255 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:rive/src/rive_core/src/text/built_in_font.dart'; + +import 'raw_path.dart'; + +// for diagnostic/debugging purposes +const bool enableFontDump = false; + +class Reader { + ByteData data; + int offset = 0; + + Reader(this.data); + + int u8() { + offset += 1; + return data.getUint8(offset - 1); + } + + int s16() { + offset += 2; + return data.getInt16(offset - 2, Endian.little); + } + + int u16() { + offset += 2; + return data.getUint16(offset - 2, Endian.little); + } + + int u32() { + offset += 4; + return data.getUint32(offset - 4, Endian.little); + } + + void skipPad16() { + offset += offset & 1; + } + + Uint8List u8list(int count) { + offset += count; + return Uint8List.sublistView(data, offset - count, offset); + } + + int available() { + return data.lengthInBytes - offset; + } + + bool eof() { + return offset == data.lengthInBytes; + } +} + +int Tag(int a, int b, int c, int d) { + return (a << 24) | (b << 16) | (c << 8) | d; +} + +String fromTag(int tag) { + final int a = (tag >> 24) & 0xFF; + final int b = (tag >> 16) & 0xFF; + final int c = (tag >> 8) & 0xFF; + final int d = (tag >> 0) & 0xFF; + return '' + + String.fromCharCode(a) + + String.fromCharCode(b) + + String.fromCharCode(c) + + String.fromCharCode(d); +} + +const int kCMap_TableTag = 1668112752; // cmap +const int kAdvances_TableTag = 1751213174; // hadv +const int kInfo_TableTag = 1768842863; // info +const int kPaths_TableTag = 1885434984; // path +const int kOffsets_TableTag = 1886348902; // poff + +ByteData find_table(List dir, int tag, ByteData data) { + for (int i = 0; i < dir.length; i += 3) { + if (dir[i] == tag) { + return ByteData.view(data.buffer, dir[i + 1], dir[i + 2]); + } + } + throw 'missing ${fromTag(tag)} table'; +} + +class RiveFont { + final double _ascent = -0.9; // TODO: get from file + final double _descent = 0.2; // TODO: get from file + + final _cmap = {}; + final _advances = []; + final _rawpaths = []; + + RiveFont() { + _advances.add(0); + _rawpaths.add(null); + } + + double get ascent { + return _ascent; + } + + double get descent { + return _descent; + } + + double get height { + return _descent - _ascent; + } + + int charToGlyph(int charCode) { + int? glyph = _cmap[charCode]; + return glyph ?? 0; + } + + double getAdvance(int glyph) { + return _advances[glyph]; + } + + Path? getPath(int glyph) { + final rp = getRawPath(glyph); + if (rp != null) { + Path p = Path(); + rp.sinker(p.moveTo, p.lineTo, p.quadraticBezierTo, p.cubicTo, p.close); + return p; + } + return null; + } + + RawPath? getRawPath(int glyph) { + return _rawpaths[glyph]; + } + + Uint16List textToGlyphs(List chars, int start, int end) { + final int n = end - start; + Uint16List glyphs = Uint16List(n); + for (int i = 0; i < n; ++i) { + glyphs[i] = charToGlyph(chars[i + start]); + } + return glyphs; + } + + Float32List getAdvances(Uint16List glyphs) { + Float32List advances = Float32List(glyphs.length); + for (int i = 0; i < glyphs.length; ++i) { + advances[i] = getAdvance(glyphs[i]); + } + return advances; + } + + void _build_cmap(int glyphCount, ByteData cmapD) { + final reader = Reader(cmapD); + final int count = reader.u16(); + if (enableFontDump) { + print('cmap has $count entries'); + } + for (int i = 0; i < count; ++i) { + final int charCode = reader.u16(); + final int glyphID = reader.u16(); + assert(glyphID < glyphCount); + _cmap.putIfAbsent(charCode, () => glyphID); + } + } + + void _build_advances(int glyphCount, double scale, ByteData advD) { + assert(advD.lengthInBytes == glyphCount * 2); + final advances = Reader(advD); + for (int i = 0; i < glyphCount; ++i) { + _advances.add(advances.u16() * scale); + } + } + + RawPath _build_rawpath(double scale, ByteData pathD) { + final reader = Reader(pathD); + + final int verbCount = reader.u16(); + assert(verbCount > 0); + final int pointCount = reader.u16(); + final Float32List pts = Float32List(pointCount * 2); + + final verbs = reader.u8list(verbCount); + reader.skipPad16(); + for (int i = 0; i < pointCount * 2; ++i) { + pts[i] = reader.s16() * scale; + } + assert(reader.eof()); + return RawPath(pts, verbs); + } + + void _build_rawpaths( + int glyphCount, double scale, ByteData offD, ByteData pathD) { + final offsets = Reader(offD); + int start = offsets.u32(); + for (int i = 0; i < glyphCount; ++i) { + int end = offsets.u32(); + assert(start <= end); + + RawPath? path; + if (start < end) { + path = _build_rawpath(scale, ByteData.sublistView(pathD, start, end)); + } + _rawpaths.add(path); + + start = end; + } + } + + RiveFont.fromBinary(ByteData data) { + final Reader reader = Reader(data); + + { + int signature = reader.u32(); + int version = reader.u32(); + assert(signature == 0x23581321); + assert(version == 1); + } + final int tableCount = reader.u32(); + if (enableFontDump) { + print('tables $tableCount'); + } + + final List dir = []; + for (int i = 0; i < tableCount; ++i) { + int tag = reader.u32(); + int off = reader.u32(); + int len = reader.u32(); + if (enableFontDump) { + print('tag: ${fromTag(tag)} offset:$off length:$len'); + } + dir.add(tag); // tag + dir.add(off); // offset + dir.add(len); // length + } + + final infoReader = Reader(find_table(dir, kInfo_TableTag, data)); + final int glyphCount = infoReader.u16(); + final int upem = infoReader.u16(); + final double scale = 1.0 / upem; + if (enableFontDump) { + print('glyphs $glyphCount, upem $upem'); + } + + _build_cmap(glyphCount, find_table(dir, kCMap_TableTag, data)); + + _build_advances( + glyphCount, scale, find_table(dir, kAdvances_TableTag, data)); + + _build_rawpaths(glyphCount, scale, find_table(dir, kOffsets_TableTag, data), + find_table(dir, kPaths_TableTag, data)); + } + + static final builtIn = RiveFont.fromBinary( + ByteData.view(const Base64Decoder().convert(builtInFontBase64).buffer)); +} diff --git a/lib/src/rive_core/src/text/shape_text.dart b/lib/src/rive_core/src/text/shape_text.dart new file mode 100644 index 0000000..8e646bf --- /dev/null +++ b/lib/src/rive_core/src/text/shape_text.dart @@ -0,0 +1,252 @@ +import 'dart:typed_data'; + +import 'rive_font.dart'; + +abstract class TextRun { + RiveFont get font; + double get pointSize; + int get textLength; + set textLength(int value); + + TextRun cloneRun(); +} + +class GlyphRun { + TextRun textRun; // the run this glyph run was generated from + RiveFont font; // possibly different from TextRun (substitution) + double pointSize; // possibly different from TextRun + + int textOffset; + Uint16List glyphs; + Float32List xpos; // #glyphs + 1 + + GlyphRun( + this.textRun, + this.font, + this.pointSize, + this.textOffset, + this.glyphs, + this.xpos, + ) { + assert(glyphs.length + 1 == xpos.length); + } + + double get left { + return xpos.first; + } + + double get right { + return xpos.last; + } + + double get width { + return right - left; + } + + int get textStart { + return textOffset; + } + + int get textEnd { + return textOffset + glyphs.length; + } + + static bool ws(int code) { + return code <= 32; + } + + static List shapeText(List chars, List textRuns) { + final glyphRuns = []; + int offset = 0; + double x = 0; + + for (final TextRun run in textRuns) { + if (run.textLength > 0) { + final glyphs = + run.font.textToGlyphs(chars, offset, offset + run.textLength); + final advances = run.font.getAdvances(glyphs); + final int n = glyphs.length; + final xpos = Float32List(n + 1); + for (int i = 0; i < n; ++i) { + xpos[i] = x; + x += advances[i] * run.pointSize; + } + xpos[n] = x; + glyphRuns + .add(GlyphRun(run, run.font, run.pointSize, offset, glyphs, xpos)); + offset += run.textLength; + } + } + return glyphRuns; + } + + static List findWordBreaks(List chars) { + List breaks = []; + for (int i = 0; i < chars.length;) { + // skip ws + while (i < chars.length && ws(chars[i])) { + ++i; + } + breaks.add(i); // word start + // skip non-ws + while (i < chars.length && !ws(chars[i])) { + ++i; + } + breaks.add(i); // word end + } + assert(breaks.last == chars.length); + return breaks; + } + + int offsetToIndex(int offset) { + assert(textOffset <= offset); + assert(offset - textOffset <= glyphs.length); + return offset - textOffset; + } +} + +class GlyphLine { + List runs; + int startRun; + int startIndex; + int endRun; + int endIndex; + int wsRun; + int wsIndex; + double startX; + double top = 0, baseline = 0, bottom = 0; + + void validate() { + void val(int r, int i) { + final run = runs[r]; + assert(i >= 0 && i <= run.glyphs.length); + } + + val(startRun, startIndex); + val(endRun, endIndex); + val(wsRun, wsIndex); + assert(startRun < endRun || (startRun == endRun && startIndex <= endIndex)); + assert(endRun < wsRun || (endRun == wsRun && endIndex <= wsIndex)); + } + + GlyphLine(this.runs, this.startRun, this.startIndex, this.endRun, + this.endIndex, this.wsRun, this.wsIndex, this.startX); + + int get textStart { + return runs[startRun].textStart + startIndex; + } + + int get textEnd { + return runs[wsRun].textStart + wsIndex; + } + + double offsetToX(int textOffset) { + assert(textOffset <= runs[endRun].textEnd); + for (int i = startRun; i <= endRun; ++i) { + final run = runs[i]; + if (textOffset <= run.textEnd) { + return run.xpos[textOffset - run.textStart] - startX; + } + } + assert(false); + return 0; + } + + static int _offsetToRunIndex( + List runs, int offset, int charLength) { + assert(offset >= 0); + for (int i = 1; i < runs.length; ++i) { + final run = runs[i]; + if (run.textOffset >= offset) { + return i - 1; + } + } + return runs.length - 1; + } + + static List lineBreak( + List chars, List breaks, List runs, double width) { + List lines = []; + int startRun = 0; + int startIndex = 0; + double xlimit = width; + + int prevRun = 0; + int prevIndex = 0; + + int wordStart = breaks[0]; + int wordEnd = breaks[1]; + int nextBreakIndex = 2; + int lineStartTextOffset = wordStart; + + for (;;) { + assert(wordStart <= wordEnd); // == means trailing spaces? + + int endRun = _offsetToRunIndex(runs, wordEnd, chars.length); + int endIndex = runs[endRun].offsetToIndex(wordEnd); + double pos = runs[endRun].xpos[endIndex]; + bool bumpBreakIndex = true; + if (pos > xlimit) { + int wsRun = _offsetToRunIndex(runs, wordStart, chars.length); + int wsIndex = runs[wsRun].offsetToIndex(wordStart); + + bumpBreakIndex = false; + // does just one word not fit? + if (lineStartTextOffset == wordStart) { + // walk backwards a letter at a time until we fit, stopping at + // 1 letter. + int wend = wordEnd; + while (pos > xlimit && wend - 1 > wordStart) { + wend -= 1; + prevRun = _offsetToRunIndex(runs, wend, chars.length); + prevIndex = runs[prevRun].offsetToIndex(wend); + pos = runs[prevRun].xpos[prevIndex]; + } + assert(wend < wordEnd || wend == wordEnd && wordStart + 1 == wordEnd); + if (wend == wordEnd) { + bumpBreakIndex = true; + } + + // now reset our "whitespace" marker to just be prev, since + // by defintion we have no extra whitespace on this line + wsRun = prevRun; + wsIndex = prevIndex; + wordStart = wend; + } + + // bulid the line + final lineStartX = runs[startRun].xpos[startIndex]; + lines.add(GlyphLine(runs, startRun, startIndex, prevRun, prevIndex, + wsRun, wsIndex, lineStartX)); + + // update for the next line + xlimit = runs[wsRun].xpos[wsIndex] + width; + startRun = prevRun = wsRun; + startIndex = prevIndex = wsIndex; + lineStartTextOffset = wordStart; + } else { + // we didn't go too far, so remember this word-end boundary + prevRun = endRun; + prevIndex = endIndex; + } + + if (bumpBreakIndex) { + if (nextBreakIndex < breaks.length) { + wordStart = breaks[nextBreakIndex++]; + wordEnd = breaks[nextBreakIndex++]; + } else { + break; // bust out of the loop + } + } + } + // scoop up the last line (if present) + final int tailRun = runs.length - 1; + final int tailIndex = runs[tailRun].glyphs.length; + if (startRun != tailRun || startIndex != tailIndex) { + final double startX = runs[startRun].xpos[startIndex]; + lines.add(GlyphLine(runs, startRun, startIndex, tailRun, tailIndex, + tailRun, tailIndex, startX)); + } + return lines; + } +} diff --git a/lib/src/rive_core/state_machine_controller.dart b/lib/src/rive_core/state_machine_controller.dart index 93e09db..12378d6 100644 --- a/lib/src/rive_core/state_machine_controller.dart +++ b/lib/src/rive_core/state_machine_controller.dart @@ -1,6 +1,5 @@ import 'dart:collection'; -import 'package:flutter/foundation.dart'; import 'package:flutter/scheduler.dart'; import 'package:rive/src/core/core.dart'; import 'package:rive/src/rive_core/animation/animation_state.dart'; diff --git a/lib/src/rive_file.dart b/lib/src/rive_file.dart index e2819b3..0853f8e 100644 --- a/lib/src/rive_file.dart +++ b/lib/src/rive_file.dart @@ -1,5 +1,4 @@ import 'dart:collection'; -import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; diff --git a/lib/src/runtime_artboard.dart b/lib/src/runtime_artboard.dart index 4cddb23..5803386 100644 --- a/lib/src/runtime_artboard.dart +++ b/lib/src/runtime_artboard.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart'; import 'package:rive/rive.dart'; import 'package:rive/src/core/core.dart'; import 'package:rive/src/rive_core/component.dart';