Skip to content

Commit 93b8070

Browse files
authored
Merge pull request #1 from code3743/feat/core-1.0-integration
Feat/core 0.1.0 integration
2 parents c389235 + 5ec550c commit 93b8070

32 files changed

Lines changed: 1892 additions & 183 deletions

.pubignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
/doc/hero.png
66
/build/
77
.git/
8-
.github/
8+
.github/
9+
/doc/

CHANGELOG.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,59 @@
1+
## 0.1.0
2+
3+
Targets `layer_canvas: ^0.1.0` (was `^0.1.0-beta.4`) and surfaces everything the
4+
core gained since — still with only Flutter types.
5+
6+
* **Stroke cap/join/miter/dash**`Layers.rectangle`/`Layers.path` gain
7+
`strokeCap`/`strokeJoin`/`strokeMiterLimit`, using `dart:ui`'s own
8+
`StrokeCap`/`StrokeJoin`; `Layers.path` additionally accepts
9+
`dashArray`/`dashOffset` (scaled by `pixelRatio` like every other length),
10+
since dashing only applies to a `PathLayer`'s stroke in the core.
11+
* **`clipBehavior`**`Layers.rectangle`/`text`/`image`/`path` accept a
12+
`Clip clipBehavior` (any value but `Clip.none` clips the layer to its
13+
`size`), mapping to the core's `Layer.clipToBounds`. Not on `Layers.group`/
14+
`Layers.svg`: the core expands a `Group` into its concrete descendants
15+
before rendering, leaving no single surface to clip.
16+
* **`scale`/`alignment`** — every `Layers` factory now accepts `scale`
17+
(uniform, via the core's `LayerTransform.scale`) and `alignment` (via the
18+
existing `AlignmentGeometryX.toFractionalPoint2D()`, so `rotation`/`scale`
19+
can pivot around a corner instead of the center), alongside `position`/
20+
`rotation`.
21+
* **Word-wrap**`Layers.text` word-wraps into a `size` with a width set
22+
(greedily, only at spaces), vertically centered within `size`'s height —
23+
no new parameter, this was already reachable, just newly documented.
24+
* **Exact `FontWeight` mapping**`FontWeightX.toTextWeight()` now uses the
25+
core's `TextWeight.fromValue` (added in `layer_canvas` 0.1.0-beta.6) for an
26+
exact 1:1 conversion, instead of snapping to the nearest of 7 named
27+
constants.
28+
* **`AssetImageSource`** — a `LayerImageSource` that lazily loads a Flutter
29+
asset by key (optionally from a `package`) instead of eagerly reading bytes
30+
like `ImageSources.asset`, so it's cheap to build ahead of time and
31+
compact to serialize (a short string instead of a base64 blob). Every
32+
widget in this package (and the new `Scenes.encode`/`saveToFile`) resolves
33+
it against the ambient `AssetBundle` before rendering; registers its own
34+
`LayerRegistry` decoder automatically, so `Scene.fromJson` reconstructs it
35+
too.
36+
* **Scene persistence**`Scene.toJson()`/`Scene.fromJson()` (added in
37+
`layer_canvas` 0.1.0-beta.6) work as-is through this package's re-exported
38+
`Scene`; only documented here, no new API.
39+
* **Export**`Scenes.encode(scene, {format})` and
40+
`Scenes.saveToFile(scene, path, {format})` rasterize a `Scene` to
41+
`png`/`bmp`/`qoi` bytes or a file (via the re-exported `OutputFormat`),
42+
resolving any `AssetImageSource` first — for exporting a scene instead of
43+
displaying it, which every widget still does as PNG.
44+
* Re-exports `OutputFormat` and `LayerRegistry` (+ its `LayerFromJson`/
45+
`ImageSourceFromJson` typedefs) alongside the previously re-exported core
46+
types.
47+
* **Rendering never blocks the UI isolate**`LayerCanvas`, `SceneWidget`,
48+
`SvgLayer`, and `Scenes.encode`/`saveToFile` now run the native render
49+
call on a background isolate internally (`renderOffMainIsolate`), instead
50+
of on the calling isolate. The core `layer_canvas` package's own
51+
`Renderer.render` stays synchronous-under-the-hood on purpose — it's
52+
plain Dart with no UI thread to protect, so a CLI/batch consumer isn't
53+
charged isolate-spawn overhead for a benefit only a Flutter app needs;
54+
this package is where that trade makes sense, so it's where the offload
55+
lives.
56+
157
## 0.1.0-beta.1
258

359
Initial release.

README.md

Lines changed: 161 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,25 @@ other `Image` — no `Color32`, no `Point2D`, nothing from the core package
4242
imported directly. See [Usage](#usage) below for gradients, custom shapes,
4343
SVG, and tap handling.
4444

45+
## Gallery
46+
47+
A few scenes built entirely from `Layers`/`Scenes.of`/`LayerPathBuilder`
48+
gradients, hand-drawn vector paths, SVG, dashed strokes, and wrapped text —
49+
to give a sense of what a `LayerCanvas` can render.
50+
51+
<table>
52+
<tr>
53+
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/gradient-burst.png" alt="Overlapping radial, linear, and sweep gradients"></td>
54+
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/vector-blob.png" alt="An organic blob drawn with LayerPathBuilder cubic and quadratic curves"></td>
55+
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/bauhaus-grid.png" alt="An abstract Bauhaus-style geometric composition"></td>
56+
</tr>
57+
<tr>
58+
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/svg-pattern.png" alt="A parsed SVG document placed as a scattered pattern"></td>
59+
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/constellation.png" alt="A dashed-stroke constellation/network diagram"></td>
60+
<td width="33%"><img src="https://raw.githubusercontent.com/code3743/layer_canvas_flutter/main/doc/gallery/editorial-card.png" alt="An editorial card with wrapped text, a clipped image badge, and a gradient background"></td>
61+
</tr>
62+
</table>
63+
4564
## Features
4665

4766
* **`Layers`** — static factories (`rectangle`, `text`, `image`, `path`,
@@ -51,6 +70,13 @@ SVG, and tap handling.
5170
* **Gradients** — pass a Flutter `LinearGradient`/`RadialGradient`/
5271
`SweepGradient` as `Layers.rectangle`/`Layers.path`'s `gradient:`, no core
5372
gradient types involved.
73+
* **Stroke cap/join/miter/dash**`strokeCap`/`strokeJoin` (`dart:ui`'s own
74+
enums), `strokeMiterLimit`, and (on `Layers.path`) `dashArray`/
75+
`dashOffset` for a dashed stroke.
76+
* **`clipBehavior`** — clips a sized layer to its own box, same idea as
77+
`Container`'s `clipBehavior`.
78+
* **`scale`/`alignment`** — every factory's `rotation` gets a uniform
79+
`scale` and an `alignment` to pivot around, instead of always the center.
5480
* **`LayerPathBuilder`** — draws a `Layers.path` shape with the same method
5581
names as `dart:ui`'s own `Path` (`moveTo`, `lineTo`, `cubicTo`,
5682
`arcToPoint`, `close`...), so it reads like drawing on a `Canvas`.
@@ -60,6 +86,12 @@ SVG, and tap handling.
6086
widget; a different rendering path entirely).
6187
* **`Scenes.of`** — builds a `Scene` from a `children` list instead of the
6288
core's mutate-after-construction `Scene(...)..add(...)..add(...)`.
89+
* **`AssetImageSource`** — an image layer/background source that lazily
90+
loads a Flutter asset by key, resolved automatically before every render.
91+
* **Scene persistence**`Scene.toJson()`/`fromJson()` save and restore a
92+
whole scene, `AssetImageSource` included.
93+
* **`Scenes.encode`/`Scenes.saveToFile`** — export a `Scene` to
94+
`png`/`bmp`/`qoi` bytes or a file, instead of displaying it.
6395
* **`LayerCanvas`** — a widget that renders a `Scene` (fixed, or built from
6496
the widget's measured size and device pixel ratio) as an `Image`, with
6597
render caching, a placeholder while it's rendering, an error builder, and
@@ -82,7 +114,7 @@ cover the whole surface you need from Flutter code.
82114

83115
```yaml
84116
dependencies:
85-
layer_canvas_flutter: ^0.1.0-beta.1
117+
layer_canvas_flutter: ^0.1.0
86118
```
87119
88120
`layer_canvas` embeds a default font (Roboto) in its native library so text
@@ -181,6 +213,66 @@ Layers.rectangle(
181213
)
182214
```
183215

216+
### Strokes: cap, join, miter, dash
217+
218+
`strokeCap`/`strokeJoin` take `dart:ui`'s own enums — the same ones a
219+
`Paint` would — and `strokeMiterLimit` controls how far a `StrokeJoin.miter`
220+
corner may extend before it's clamped to a bevel. `Layers.path` additionally
221+
takes `dashArray`/`dashOffset` for a dashed stroke (only `PathLayer`s dash —
222+
a `RectangleLayer` has no path geometry of its own to dash):
223+
224+
```dart
225+
Layers.path(
226+
path: LayerPathBuilder()
227+
..moveTo(const Offset(0, 50))
228+
..lineTo(const Offset(300, 50)),
229+
color: const Color(0xFF4C6EF5),
230+
style: PaintingStyle.stroke,
231+
strokeWidth: 4,
232+
strokeCap: StrokeCap.round,
233+
dashArray: const [12, 8],
234+
pixelRatio: pixelRatio,
235+
)
236+
```
237+
238+
### Clipping with clipBehavior
239+
240+
`clipBehavior` (any value but the default `Clip.none`) clips a sized layer
241+
to its own box — same idea as `Container`'s `clipBehavior`. The natural
242+
case is cropping a `cover`-fit image, exactly like `Image` inside a clipped
243+
box:
244+
245+
```dart
246+
Layers.image(
247+
source: MemoryImageSource(bytes),
248+
size: const Size(200, 120),
249+
fit: BoxFit.cover,
250+
clipBehavior: Clip.hardEdge,
251+
pixelRatio: pixelRatio,
252+
)
253+
```
254+
255+
Not available on `Layers.group`/`Layers.svg`: the core expands a `Group`
256+
into its concrete descendants before rendering, leaving no single surface
257+
to clip — clip an individual child via its own factory instead.
258+
259+
### scale and alignment
260+
261+
Every factory's `rotation` gets two companions: `scale` (uniform) and
262+
`alignment` (where `rotation`/`scale` pivot from — `Alignment.center` by
263+
default, same as the core):
264+
265+
```dart
266+
Layers.rectangle(
267+
size: const Size(80, 80),
268+
color: const Color(0xFFFF6B6B),
269+
rotation: 0.3,
270+
scale: 1.2,
271+
alignment: Alignment.topLeft, // pivot from the corner, not the center
272+
pixelRatio: pixelRatio,
273+
)
274+
```
275+
184276
### Custom shapes with LayerPathBuilder
185277

186278
`LayerPathBuilder` mirrors `dart:ui`'s `Path` — the same method names, in
@@ -248,6 +340,25 @@ the document at its own natural size and lets Flutter's ordinary layout
248340
scale/position that result, the same way it would any other
249341
fixed-aspect-ratio child.
250342

343+
### Word-wrap
344+
345+
`Layers.text` word-wraps into a `size` with a width set — greedily,
346+
breaking only at spaces (a single word wider than the box overflows on its
347+
own line rather than being split mid-word) — and the wrapped block is
348+
vertically centered within `size`'s height:
349+
350+
```dart
351+
Layers.text(
352+
text: 'A longer caption that should wrap across a few lines.',
353+
size: const Size(220, 80),
354+
fontSize: 16,
355+
pixelRatio: pixelRatio,
356+
)
357+
```
358+
359+
Leave `size` unset (or give it no width) for a single, possibly overflowing
360+
line — the same as before this existed.
361+
251362
### Tap handling with onLayerTap
252363

253364
`LayerCanvas.onLayerTap` reports which `Layer` (if any) was under a tap,
@@ -310,21 +421,63 @@ one in place, pass a changing `rebuildKey` to force a re-render:
310421
LayerCanvas(scene: scene, rebuildKey: generation)
311422
```
312423

424+
### Saving and loading a Scene
425+
426+
`Scene` round-trips through JSON as-is — `toJson()`/`fromJson` recurse
427+
through every layer, paint, gradient, transform, and image source:
428+
429+
```dart
430+
final json = jsonEncode(scene.toJson());
431+
// ...later, or on another device:
432+
final restored = Scene.fromJson(jsonDecode(json) as Map<String, Object?>);
433+
```
434+
435+
For an image that should serialize as a short asset key instead of a
436+
base64 blob, use `AssetImageSource` instead of `ImageSources.asset`
437+
(which reads the bytes immediately):
438+
439+
```dart
440+
Layers.image(
441+
source: AssetImageSource('assets/logo.png'),
442+
size: const Size(120, 40),
443+
)
444+
```
445+
446+
Every widget in this package resolves an `AssetImageSource` against the
447+
ambient `AssetBundle` right before rendering, and its `LayerRegistry`
448+
decoder is registered automatically the first time one is built or
449+
deserialized — no setup call needed.
450+
451+
### Exporting a Scene
452+
453+
Every widget in this package always displays a `Scene` as PNG. To export
454+
one instead — to a file, or as bytes in another format — use `Scenes`:
455+
456+
```dart
457+
final pngBytes = await Scenes.encode(scene); // png by default
458+
await Scenes.saveToFile(scene, '/path/to/export.qoi', format: OutputFormat.qoi);
459+
```
460+
461+
Both resolve any `AssetImageSource` in `scene` first, same as `LayerCanvas`.
462+
313463
## Additional information
314464

315465
This package only depends on `layer_canvas` and re-exports only the pieces
316466
of its API that this package's own public API surfaces as parameters or
317467
return types: `Scene`, `Layer` and its subclasses (`RectangleLayer`,
318468
`TextLayer`, `ImageLayer`, `PathLayer`, `Group`), `LayerImageSource` (with
319469
`FileImageSource`/`MemoryImageSource`), `SvgDocument`/`SvgParseException`,
320-
`Renderer`/`RenderException`, and `FontRegistry`/`FontRegistrationException`.
321-
Value types the core exposes that `Layers` and the adapters exist
322-
specifically to shield you from (`Color32`, `Point2D`/`Size2D`,
323-
`TextWeight`, `TextAlignment`, `ImageFit`, `LayerPaint`, `LayerTransform`,
324-
`FillRule`, and the core's own `Gradient`/`LinearGradient`/
470+
`Renderer`/`RenderException`, `OutputFormat`,
471+
`FontRegistry`/`FontRegistrationException`, and `LayerRegistry` (with its
472+
`LayerFromJson`/`ImageSourceFromJson` typedefs, for registering a custom
473+
`Layer`/`LayerImageSource` subclass of your own). Value types the core
474+
exposes that `Layers` and the adapters exist specifically to shield you
475+
from (`Color32`, `Point2D`/`Size2D`, `TextWeight`, `TextAlignment`,
476+
`ImageFit`, `LayerPaint`, `LayerTransform`, `FillRule`, the core's own
477+
`StrokeCap`/`StrokeJoin`, and its `Gradient`/`LinearGradient`/
325478
`RadialGradient`/`ConicGradient`, which would otherwise collide with
326-
Flutter's own same-named gradient types) are intentionally not re-exported
327-
building UI with this package should never require importing
479+
Flutter's own same-named types) are intentionally not re-exported
480+
building UI with this package should never require importing
328481
`package:layer_canvas` directly.
329482

330483
See [`layer_canvas`](https://pub.dev/packages/layer_canvas) and its

doc/gallery/bauhaus-grid.png

48.2 KB
Loading

doc/gallery/constellation.png

68.8 KB
Loading

doc/gallery/editorial-card.png

158 KB
Loading

doc/gallery/gradient-burst.png

351 KB
Loading

doc/gallery/svg-pattern.png

54.5 KB
Loading

doc/gallery/vector-blob.png

66.3 KB
Loading

example/assets/images/logo.png

13.8 KB
Loading

0 commit comments

Comments
 (0)