Mesh deform for flutter runtime.

This commit is contained in:
Luigi Rosso
2022-02-25 19:41:59 -08:00
committed by Luigi Rosso
parent 671d6ce6a2
commit d415b6a958
47 changed files with 2396 additions and 180 deletions

View File

@ -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';

View File

@ -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

View File

@ -13,27 +13,27 @@ abstract class FileAssetContentsBase<T extends CoreContext> extends Core<T> {
/// --------------------------------------------------------------------------
/// Bytes field with key 212.
static const List<int> bytesInitialValue = [];
List<int> _bytes = bytesInitialValue;
static final Uint8List bytesInitialValue = Uint8List(0);
Uint8List _bytes = bytesInitialValue;
static const int bytesPropertyKey = 212;
/// Byte data of the file.
List<int> 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<int> value) {
if (_bytes == value) {
set bytes(Uint8List value) {
if (listEquals(_bytes, value)) {
return;
}
List<int> from = _bytes;
Uint8List from = _bytes;
_bytes = value;
if (hasValidated) {
bytesChanged(from, value);
}
}
void bytesChanged(List<int> from, List<int> to);
void bytesChanged(Uint8List from, Uint8List to);
@override
void copy(covariant FileAssetContentsBase source) {

View File

@ -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<int>) {
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<int> 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<int> 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;

View File

@ -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<int> get coreTypes => {
ContourMeshVertexBase.typeKey,
MeshVertexBase.typeKey,
VertexBase.typeKey,
ContainerComponentBase.typeKey,
ComponentBase.typeKey
};
}

View File

@ -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
};

View File

@ -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
};

View File

@ -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
};

View File

@ -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<CubicWeight> {
Set<int> get coreTypes => {
CubicVertexBase.typeKey,
PathVertexBase.typeKey,
VertexBase.typeKey,
ContainerComponentBase.typeKey,
ComponentBase.typeKey
};

View File

@ -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<int> 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;
}
}

View File

@ -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<int> 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;
}
}

View File

@ -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<int> 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;
}
}

View File

@ -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<T extends Weight> extends Vertex<T> {
static const int typeKey = 14;
@override
int get coreType => PathVertexBase.typeKey;
@override
Set<int> 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;
}
}

View File

@ -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<Weight> {
Set<int> get coreTypes => {
StraightVertexBase.typeKey,
PathVertexBase.typeKey,
VertexBase.typeKey,
ContainerComponentBase.typeKey,
ComponentBase.typeKey
};

View File

@ -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<int> 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;
}
}

View File

@ -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<int> 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;
}
}

View File

@ -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<int> 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;
}
}

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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) {}
}

View File

@ -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 {

View File

@ -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';

View File

@ -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<void> decode(Uint8List bytes) {
var completer = Completer<void>();

View File

@ -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<PathVertex> vertices) {
void deform(List<Vertex> vertices) {
for (final vertex in vertices) {
vertex.deform(_worldTransform, _boneTransforms);
}

View File

@ -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<T extends Vertex> {
// _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<T extends Vertex> {
Skinnable<T>? get skinnable;
}

View File

@ -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';

View File

@ -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;

View File

@ -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;
}

View File

@ -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.

View File

@ -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 {}

View File

@ -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<ImageAsset> {
class Image extends ImageBase
with FileAssetReferencer<ImageAsset>, SkinnableProvider<MeshVertex> {
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<ImageAsset> {
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(<double>[
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<ImageAsset> {
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<MeshVertex>? get skinnable => _mesh;
Mat2D get renderTransform => _mesh?.worldTransform ?? worldTransform;
}

View File

@ -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<MeshVertex> {
// When bound to bones pathTransform should be the identity as it'll already
// be in world space.
final List<MeshVertex> _vertices = [];
@override
List<MeshVertex> 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 = <ui.Offset>[];
var uv = <ui.Offset>[];
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<int> 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<int> from, List<int> to) =>
_deserializeTriangleIndices();
}

View File

@ -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) {}
}

View File

@ -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<T extends Weight> extends PathVertexBase {
T? _weight;
T? get weight => _weight;
abstract class PathVertex<T extends Weight> extends PathVertexBase<T> {
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();
}

View File

@ -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<PathVertex> {
final List<PathVertex> _vertices = [];
PointsPath() {

View File

@ -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<TextRun> _textRuns = [];
List<int> _textCodes = [];
Text() {
frame = const Rect.fromLTWH(0, 0, 200, 200);
}
@override
List<TextRun> get textRuns => _textRuns;
@override
List<int> 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<TextRun>().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);
});
}
}
}

View File

@ -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));
}
}
}

View File

@ -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<T extends Weight> 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) {}
}

File diff suppressed because one or more lines are too long

View File

@ -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<int> get textCodes;
List<TextRun> get textRuns;
// cached output
List<GlyphRun> _runs = [];
List<GlyphLine> _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<GlyphLine> 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<int> _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<GlyphRun> 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<int> _textCodes = [];
final List<TextRun> _truns = [];
@override
List<int> get textCodes => _textCodes;
@override
List<TextRun> get textRuns => _truns;
}

View File

@ -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<double> pts = [];
List<int> 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;
}
}

View File

@ -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<int> 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 = <int, int>{};
final _advances = <double>[];
final _rawpaths = <RawPath?>[];
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<int> 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<int> 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));
}

View File

@ -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<GlyphRun> shapeText(List<int> chars, List<TextRun> textRuns) {
final glyphRuns = <GlyphRun>[];
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<int> findWordBreaks(List<int> chars) {
List<int> 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<GlyphRun> 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<GlyphRun> 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<GlyphLine> lineBreak(
List<int> chars, List<int> breaks, List<GlyphRun> runs, double width) {
List<GlyphLine> 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;
}
}

View File

@ -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';

View File

@ -1,5 +1,4 @@
import 'dart:collection';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:flutter/services.dart';

View File

@ -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';