Skip to content

Commit f3b5c35

Browse files
Merge pull request #2 from jonahwilliams/runtime
add basic vector graphics asset
2 parents abe1c17 + 7b3feee commit f3b5c35

File tree

5 files changed

+402
-12
lines changed

5 files changed

+402
-12
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## 0.0.1
4+
* Added `VectorGraphic` which consumes encoded vector graphics assets using
5+
a `BytesLoader`.
6+
* Added `AssetBytesLoader` and `NetworkBytesLoader` as example loader
7+
implementations.
8+
39
## 0.0.0
410

511
* Create repository
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import 'dart:ui' as ui;
2+
import 'dart:typed_data';
3+
4+
import 'package:vector_graphics_codec/vector_graphics_codec.dart';
5+
6+
/// A listener implementation for the vector graphics codec that converts the
7+
/// format into a [ui.Picture].
8+
class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener {
9+
/// Create a new [FlutterVectorGraphicsListener].
10+
factory FlutterVectorGraphicsListener() {
11+
final ui.PictureRecorder recorder = ui.PictureRecorder();
12+
final ui.Canvas canvas = ui.Canvas(recorder);
13+
return FlutterVectorGraphicsListener._(canvas, recorder);
14+
}
15+
16+
FlutterVectorGraphicsListener._(this._canvas, this._recorder);
17+
18+
final ui.PictureRecorder _recorder;
19+
final ui.Canvas _canvas;
20+
final List<ui.Paint> _paints = <ui.Paint>[];
21+
final List<ui.Path> _paths = <ui.Path>[];
22+
ui.Path? _currentPath;
23+
bool _done = false;
24+
25+
static final _emptyPaint = ui.Paint();
26+
27+
/// Convert the vector graphics asset this listener decoded into a [ui.Picture].
28+
///
29+
/// This method can only be called once for a given listener instance.
30+
ui.Picture toPicture() {
31+
assert(!_done);
32+
_done = true;
33+
return _recorder.endRecording();
34+
}
35+
36+
@override
37+
void onDrawPath(int pathId, int? paintId) {
38+
final ui.Path path = _paths[pathId];
39+
ui.Paint? paint;
40+
if (paintId != null) {
41+
paint = _paints[paintId];
42+
}
43+
_canvas.drawPath(path, paint ?? _emptyPaint);
44+
}
45+
46+
@override
47+
void onDrawVertices(Float32List vertices, Uint16List? indices, int? paintId) {
48+
final ui.Vertices vextexData =
49+
ui.Vertices.raw(ui.VertexMode.triangles, vertices, indices: indices);
50+
ui.Paint? paint;
51+
if (paintId != null) {
52+
paint = _paints[paintId];
53+
}
54+
_canvas.drawVertices(
55+
vextexData, ui.BlendMode.srcOver, paint ?? _emptyPaint);
56+
}
57+
58+
@override
59+
void onPaintObject({
60+
required int color,
61+
required int? strokeCap,
62+
required int? strokeJoin,
63+
required int blendMode,
64+
required double? strokeMiterLimit,
65+
required double? strokeWidth,
66+
required int paintStyle,
67+
required int id,
68+
}) {
69+
assert(_paints.length == id, 'Expect ID to be ${_paints.length}');
70+
final ui.Paint paint = ui.Paint()..color = ui.Color(color);
71+
72+
if (blendMode != 0) {
73+
paint.blendMode = ui.BlendMode.values[blendMode];
74+
}
75+
76+
if (paintStyle == 1) {
77+
paint.style = ui.PaintingStyle.stroke;
78+
if (strokeCap != null && strokeCap != 0) {
79+
paint.strokeCap = ui.StrokeCap.values[strokeCap];
80+
}
81+
if (strokeJoin != null && strokeJoin != 0) {
82+
paint.strokeJoin = ui.StrokeJoin.values[strokeJoin];
83+
}
84+
if (strokeMiterLimit != null && strokeMiterLimit != 4.0) {
85+
paint.strokeMiterLimit = strokeMiterLimit;
86+
}
87+
if (strokeWidth != null && strokeWidth != 1.0) {
88+
paint.strokeWidth = strokeWidth;
89+
}
90+
}
91+
_paints.add(paint);
92+
}
93+
94+
@override
95+
void onPathClose() {
96+
_currentPath!.close();
97+
}
98+
99+
@override
100+
void onPathCubicTo(
101+
double x1, double y1, double x2, double y2, double x3, double y3) {
102+
_currentPath!.cubicTo(x1, y1, x2, y2, x3, y3);
103+
}
104+
105+
@override
106+
void onPathFinished() {
107+
_currentPath = null;
108+
}
109+
110+
@override
111+
void onPathLineTo(double x, double y) {
112+
_currentPath!.lineTo(x, y);
113+
}
114+
115+
@override
116+
void onPathMoveTo(double x, double y) {
117+
_currentPath!.moveTo(x, y);
118+
}
119+
120+
@override
121+
void onPathStart(int id, int fillType) {
122+
assert(_currentPath == null);
123+
assert(_paths.length == id, 'Expected Id to be $id');
124+
125+
final ui.Path path = ui.Path();
126+
path.fillType = ui.PathFillType.values[fillType];
127+
_paths.add(path);
128+
_currentPath = path;
129+
}
130+
}
Lines changed: 207 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,209 @@
1-
library vector_graphics;
1+
import 'dart:typed_data';
2+
import 'dart:ui' as ui;
3+
import 'dart:io';
24

3-
/// A Calculator.
4-
class Calculator {
5-
/// Returns [value] plus 1.
6-
int addOne(int value) => value + 1;
5+
import 'package:flutter/foundation.dart';
6+
import 'package:flutter/rendering.dart';
7+
import 'package:flutter/widgets.dart';
8+
9+
import 'package:vector_graphics_codec/vector_graphics_codec.dart';
10+
11+
import 'src/listener.dart';
12+
13+
const VectorGraphicsCodec _codec = VectorGraphicsCodec();
14+
15+
/// Decode a vector graphics binary asset into a [ui.Picture].
16+
///
17+
/// Throws a [StateError] if the data is invalid.
18+
ui.Picture decodeVectorGraphics(ByteData data) {
19+
final FlutterVectorGraphicsListener listener =
20+
FlutterVectorGraphicsListener();
21+
_codec.decode(data, listener);
22+
return listener.toPicture();
23+
}
24+
25+
/// A widget that displays a vector_graphics formatted asset.
26+
///
27+
/// A bytes loader class should not be constructed directly in a build method,
28+
/// if this is done the corresponding [VectorGraphic] widget may repeatedly
29+
/// reload the bytes.
30+
///
31+
/// ```dart
32+
/// class MyVectorGraphic extends StatefulWidget {
33+
///
34+
/// State<MyVectorGraphic> createState() =>
35+
/// }
36+
///
37+
/// class _MyVectorGraphicState extends State<MyVectorGraphic> {
38+
/// BytesLoader? loader;
39+
///
40+
/// @override
41+
/// void initState() {
42+
/// super.initState();
43+
/// loader = AssetBytesLoader(assetName: 'foobar', assetBundle: DefaultAssetBundle.of(context));
44+
/// }
45+
///
46+
/// @override
47+
/// Widget build(BuildContext context) {
48+
/// return VectorGraphic(bytesLoader: loader!);
49+
/// }
50+
/// }
51+
/// ```
52+
class VectorGraphic extends StatefulWidget {
53+
const VectorGraphic({Key? key, required this.bytesLoader}) : super(key: key);
54+
55+
final BytesLoader bytesLoader;
56+
57+
@override
58+
State<VectorGraphic> createState() => _VectorGraphicsWidgetState();
59+
}
60+
61+
class _VectorGraphicsWidgetState extends State<VectorGraphic> {
62+
ui.Picture? _picture;
63+
64+
@override
65+
void initState() {
66+
_loadAssetBytes();
67+
super.initState();
68+
}
69+
70+
@override
71+
void didUpdateWidget(covariant VectorGraphic oldWidget) {
72+
if (oldWidget.bytesLoader != widget.bytesLoader) {
73+
_loadAssetBytes();
74+
}
75+
super.didUpdateWidget(oldWidget);
76+
}
77+
78+
@override
79+
void dispose() {
80+
_picture?.dispose();
81+
_picture = null;
82+
super.dispose();
83+
}
84+
85+
void _loadAssetBytes() {
86+
widget.bytesLoader.loadBytes().then((ByteData data) {
87+
final ui.Picture picture = decodeVectorGraphics(data);
88+
setState(() {
89+
_picture?.dispose();
90+
_picture = picture;
91+
});
92+
});
93+
}
94+
95+
@override
96+
Widget build(BuildContext context) {
97+
final ui.Picture? picture = _picture;
98+
if (picture == null) {
99+
return const SizedBox();
100+
}
101+
return _RawVectorGraphicsWidget(picture: picture);
102+
}
103+
}
104+
105+
/// An interface that can be implemented to support decoding vector graphic
106+
/// binary assets from different byte sources.
107+
///
108+
/// A bytes loader class should not be constructed directly in a build method,
109+
/// if this is done the corresponding [VectorGraphic] widget may repeatedly
110+
/// reload the bytes.
111+
///
112+
/// See also:
113+
/// * [AssetBytesLoader], for loading from the asset bundle.
114+
/// * [NetworkBytesLoader], for loading network bytes.
115+
abstract class BytesLoader {
116+
/// const constructor to allow subtypes to be const.
117+
const BytesLoader();
118+
119+
/// Load the byte data for a vector graphic binary asset.
120+
Future<ByteData> loadBytes();
121+
}
122+
123+
/// A controller for loading vector graphics data from an asset bundle.
124+
class AssetBytesLoader extends BytesLoader {
125+
/// Create a new [VectorGraphicsAssetController].
126+
///
127+
/// The default asset bundle can be acquired using [DefaultAssetBundle.of].
128+
const AssetBytesLoader({
129+
required this.assetName,
130+
this.packageName,
131+
required this.assetBundle,
132+
});
133+
134+
final String assetName;
135+
final String? packageName;
136+
final AssetBundle assetBundle;
137+
138+
@override
139+
Future<ByteData> loadBytes() {
140+
return assetBundle.load(assetName);
141+
}
142+
}
143+
144+
/// A controller for loading vector graphics data from over the network.
145+
class NetworkBytesLoader extends BytesLoader {
146+
const NetworkBytesLoader({
147+
required this.url,
148+
this.headers,
149+
this.client,
150+
});
151+
152+
final Map<String, String>? headers;
153+
final Uri url;
154+
final HttpClient? client;
155+
156+
@override
157+
Future<ByteData> loadBytes() async {
158+
final HttpClient currentClient = client ?? HttpClient();
159+
final HttpClientRequest request = await currentClient.getUrl(url);
160+
headers?.forEach(request.headers.add);
161+
162+
final HttpClientResponse response = await request.close();
163+
if (response.statusCode != HttpStatus.ok) {
164+
await response.drain<List<int>>(<int>[]);
165+
throw Exception('Failed to load VectorGraphic: ${response.statusCode}');
166+
}
167+
final Uint8List bytes = await consolidateHttpClientResponseBytes(
168+
response,
169+
);
170+
return bytes.buffer.asByteData();
171+
}
172+
}
173+
174+
class _RawVectorGraphicsWidget extends SingleChildRenderObjectWidget {
175+
const _RawVectorGraphicsWidget({Key? key, required this.picture})
176+
: super(key: key);
177+
178+
final ui.Picture picture;
179+
180+
@override
181+
RenderObject createRenderObject(BuildContext context) {
182+
return _RenderVectorGraphics(picture);
183+
}
184+
185+
@override
186+
void updateRenderObject(
187+
BuildContext context, covariant _RenderVectorGraphics renderObject) {
188+
renderObject.picture = picture;
189+
}
190+
}
191+
192+
class _RenderVectorGraphics extends RenderProxyBox {
193+
_RenderVectorGraphics(this._picture);
194+
195+
ui.Picture get picture => _picture;
196+
ui.Picture _picture;
197+
set picture(ui.Picture value) {
198+
if (identical(value, _picture)) {
199+
return;
200+
}
201+
_picture = value;
202+
markNeedsPaint();
203+
}
204+
205+
@override
206+
void paint(PaintingContext context, ui.Offset offset) {
207+
context.canvas.drawPicture(picture);
208+
}
7209
}

packages/vector_graphics/pubspec.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: vector_graphics
22
description: A vector graphics rendering package for Flutter.
3-
version: 0.0.0
3+
version: 0.0.1
44
homepage: https://github.com/dnfield/vector_graphics
55

66
environment:
@@ -10,8 +10,14 @@ environment:
1010
dependencies:
1111
flutter:
1212
sdk: flutter
13+
vector_graphics_codec: ^0.0.1
1314

1415
dev_dependencies:
1516
flutter_test:
1617
sdk: flutter
1718
flutter_lints: ^1.0.0
19+
20+
# Comment out before publishing
21+
dependency_overrides:
22+
vector_graphics_codec:
23+
path: ../vector_graphics_codec

0 commit comments

Comments
 (0)