Skip to content

Commit

Permalink
Merge pull request #2 from jonahwilliams/runtime
Browse files Browse the repository at this point in the history
add basic vector graphics asset
  • Loading branch information
jonahwilliams authored Mar 2, 2022
2 parents abe1c17 + 7b3feee commit f3b5c35
Show file tree
Hide file tree
Showing 5 changed files with 402 additions and 12 deletions.
6 changes: 6 additions & 0 deletions packages/vector_graphics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# CHANGELOG

## 0.0.1
* Added `VectorGraphic` which consumes encoded vector graphics assets using
a `BytesLoader`.
* Added `AssetBytesLoader` and `NetworkBytesLoader` as example loader
implementations.

## 0.0.0

* Create repository
130 changes: 130 additions & 0 deletions packages/vector_graphics/lib/src/listener.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import 'dart:ui' as ui;
import 'dart:typed_data';

import 'package:vector_graphics_codec/vector_graphics_codec.dart';

/// A listener implementation for the vector graphics codec that converts the
/// format into a [ui.Picture].
class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener {
/// Create a new [FlutterVectorGraphicsListener].
factory FlutterVectorGraphicsListener() {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder);
return FlutterVectorGraphicsListener._(canvas, recorder);
}

FlutterVectorGraphicsListener._(this._canvas, this._recorder);

final ui.PictureRecorder _recorder;
final ui.Canvas _canvas;
final List<ui.Paint> _paints = <ui.Paint>[];
final List<ui.Path> _paths = <ui.Path>[];
ui.Path? _currentPath;
bool _done = false;

static final _emptyPaint = ui.Paint();

/// Convert the vector graphics asset this listener decoded into a [ui.Picture].
///
/// This method can only be called once for a given listener instance.
ui.Picture toPicture() {
assert(!_done);
_done = true;
return _recorder.endRecording();
}

@override
void onDrawPath(int pathId, int? paintId) {
final ui.Path path = _paths[pathId];
ui.Paint? paint;
if (paintId != null) {
paint = _paints[paintId];
}
_canvas.drawPath(path, paint ?? _emptyPaint);
}

@override
void onDrawVertices(Float32List vertices, Uint16List? indices, int? paintId) {
final ui.Vertices vextexData =
ui.Vertices.raw(ui.VertexMode.triangles, vertices, indices: indices);
ui.Paint? paint;
if (paintId != null) {
paint = _paints[paintId];
}
_canvas.drawVertices(
vextexData, ui.BlendMode.srcOver, paint ?? _emptyPaint);
}

@override
void onPaintObject({
required int color,
required int? strokeCap,
required int? strokeJoin,
required int blendMode,
required double? strokeMiterLimit,
required double? strokeWidth,
required int paintStyle,
required int id,
}) {
assert(_paints.length == id, 'Expect ID to be ${_paints.length}');
final ui.Paint paint = ui.Paint()..color = ui.Color(color);

if (blendMode != 0) {
paint.blendMode = ui.BlendMode.values[blendMode];
}

if (paintStyle == 1) {
paint.style = ui.PaintingStyle.stroke;
if (strokeCap != null && strokeCap != 0) {
paint.strokeCap = ui.StrokeCap.values[strokeCap];
}
if (strokeJoin != null && strokeJoin != 0) {
paint.strokeJoin = ui.StrokeJoin.values[strokeJoin];
}
if (strokeMiterLimit != null && strokeMiterLimit != 4.0) {
paint.strokeMiterLimit = strokeMiterLimit;
}
if (strokeWidth != null && strokeWidth != 1.0) {
paint.strokeWidth = strokeWidth;
}
}
_paints.add(paint);
}

@override
void onPathClose() {
_currentPath!.close();
}

@override
void onPathCubicTo(
double x1, double y1, double x2, double y2, double x3, double y3) {
_currentPath!.cubicTo(x1, y1, x2, y2, x3, y3);
}

@override
void onPathFinished() {
_currentPath = null;
}

@override
void onPathLineTo(double x, double y) {
_currentPath!.lineTo(x, y);
}

@override
void onPathMoveTo(double x, double y) {
_currentPath!.moveTo(x, y);
}

@override
void onPathStart(int id, int fillType) {
assert(_currentPath == null);
assert(_paths.length == id, 'Expected Id to be $id');

final ui.Path path = ui.Path();
path.fillType = ui.PathFillType.values[fillType];
_paths.add(path);
_currentPath = path;
}
}
212 changes: 207 additions & 5 deletions packages/vector_graphics/lib/vector_graphics.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,209 @@
library vector_graphics;
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:io';

/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

import 'package:vector_graphics_codec/vector_graphics_codec.dart';

import 'src/listener.dart';

const VectorGraphicsCodec _codec = VectorGraphicsCodec();

/// Decode a vector graphics binary asset into a [ui.Picture].
///
/// Throws a [StateError] if the data is invalid.
ui.Picture decodeVectorGraphics(ByteData data) {
final FlutterVectorGraphicsListener listener =
FlutterVectorGraphicsListener();
_codec.decode(data, listener);
return listener.toPicture();
}

/// A widget that displays a vector_graphics formatted asset.
///
/// A bytes loader class should not be constructed directly in a build method,
/// if this is done the corresponding [VectorGraphic] widget may repeatedly
/// reload the bytes.
///
/// ```dart
/// class MyVectorGraphic extends StatefulWidget {
///
/// State<MyVectorGraphic> createState() =>
/// }
///
/// class _MyVectorGraphicState extends State<MyVectorGraphic> {
/// BytesLoader? loader;
///
/// @override
/// void initState() {
/// super.initState();
/// loader = AssetBytesLoader(assetName: 'foobar', assetBundle: DefaultAssetBundle.of(context));
/// }
///
/// @override
/// Widget build(BuildContext context) {
/// return VectorGraphic(bytesLoader: loader!);
/// }
/// }
/// ```
class VectorGraphic extends StatefulWidget {
const VectorGraphic({Key? key, required this.bytesLoader}) : super(key: key);

final BytesLoader bytesLoader;

@override
State<VectorGraphic> createState() => _VectorGraphicsWidgetState();
}

class _VectorGraphicsWidgetState extends State<VectorGraphic> {
ui.Picture? _picture;

@override
void initState() {
_loadAssetBytes();
super.initState();
}

@override
void didUpdateWidget(covariant VectorGraphic oldWidget) {
if (oldWidget.bytesLoader != widget.bytesLoader) {
_loadAssetBytes();
}
super.didUpdateWidget(oldWidget);
}

@override
void dispose() {
_picture?.dispose();
_picture = null;
super.dispose();
}

void _loadAssetBytes() {
widget.bytesLoader.loadBytes().then((ByteData data) {
final ui.Picture picture = decodeVectorGraphics(data);
setState(() {
_picture?.dispose();
_picture = picture;
});
});
}

@override
Widget build(BuildContext context) {
final ui.Picture? picture = _picture;
if (picture == null) {
return const SizedBox();
}
return _RawVectorGraphicsWidget(picture: picture);
}
}

/// An interface that can be implemented to support decoding vector graphic
/// binary assets from different byte sources.
///
/// A bytes loader class should not be constructed directly in a build method,
/// if this is done the corresponding [VectorGraphic] widget may repeatedly
/// reload the bytes.
///
/// See also:
/// * [AssetBytesLoader], for loading from the asset bundle.
/// * [NetworkBytesLoader], for loading network bytes.
abstract class BytesLoader {
/// const constructor to allow subtypes to be const.
const BytesLoader();

/// Load the byte data for a vector graphic binary asset.
Future<ByteData> loadBytes();
}

/// A controller for loading vector graphics data from an asset bundle.
class AssetBytesLoader extends BytesLoader {
/// Create a new [VectorGraphicsAssetController].
///
/// The default asset bundle can be acquired using [DefaultAssetBundle.of].
const AssetBytesLoader({
required this.assetName,
this.packageName,
required this.assetBundle,
});

final String assetName;
final String? packageName;
final AssetBundle assetBundle;

@override
Future<ByteData> loadBytes() {
return assetBundle.load(assetName);
}
}

/// A controller for loading vector graphics data from over the network.
class NetworkBytesLoader extends BytesLoader {
const NetworkBytesLoader({
required this.url,
this.headers,
this.client,
});

final Map<String, String>? headers;
final Uri url;
final HttpClient? client;

@override
Future<ByteData> loadBytes() async {
final HttpClient currentClient = client ?? HttpClient();
final HttpClientRequest request = await currentClient.getUrl(url);
headers?.forEach(request.headers.add);

final HttpClientResponse response = await request.close();
if (response.statusCode != HttpStatus.ok) {
await response.drain<List<int>>(<int>[]);
throw Exception('Failed to load VectorGraphic: ${response.statusCode}');
}
final Uint8List bytes = await consolidateHttpClientResponseBytes(
response,
);
return bytes.buffer.asByteData();
}
}

class _RawVectorGraphicsWidget extends SingleChildRenderObjectWidget {
const _RawVectorGraphicsWidget({Key? key, required this.picture})
: super(key: key);

final ui.Picture picture;

@override
RenderObject createRenderObject(BuildContext context) {
return _RenderVectorGraphics(picture);
}

@override
void updateRenderObject(
BuildContext context, covariant _RenderVectorGraphics renderObject) {
renderObject.picture = picture;
}
}

class _RenderVectorGraphics extends RenderProxyBox {
_RenderVectorGraphics(this._picture);

ui.Picture get picture => _picture;
ui.Picture _picture;
set picture(ui.Picture value) {
if (identical(value, _picture)) {
return;
}
_picture = value;
markNeedsPaint();
}

@override
void paint(PaintingContext context, ui.Offset offset) {
context.canvas.drawPicture(picture);
}
}
8 changes: 7 additions & 1 deletion packages/vector_graphics/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: vector_graphics
description: A vector graphics rendering package for Flutter.
version: 0.0.0
version: 0.0.1
homepage: https://github.com/dnfield/vector_graphics

environment:
Expand All @@ -10,8 +10,14 @@ environment:
dependencies:
flutter:
sdk: flutter
vector_graphics_codec: ^0.0.1

dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0

# Comment out before publishing
dependency_overrides:
vector_graphics_codec:
path: ../vector_graphics_codec
Loading

0 comments on commit f3b5c35

Please sign in to comment.