Skip to content

Commit 3263651

Browse files
committed
Replace color with element paint
1 parent b472fe3 commit 3263651

30 files changed

Lines changed: 1671 additions & 670 deletions

api/lib/src/converter/note.dart

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,5 +207,125 @@ NoteData _migrate(NoteData noteData, FileMetadata metadata) {
207207
);
208208
}
209209
}
210+
if (version < 13) {
211+
Map<String, dynamic> solidPaint(
212+
dynamic color, [
213+
int fallback = 0xFF000000,
214+
]) {
215+
final value = color is int ? color : fallback;
216+
return {'type': 'solid', 'color': value};
217+
}
218+
219+
void updateShapeFill(Map? shape) {
220+
if (shape == null) return;
221+
if (!const ['circle', 'rectangle', 'triangle'].contains(shape['type'])) {
222+
return;
223+
}
224+
shape['fillPaint'] ??= solidPaint(shape['fillColor'], 0x00000000);
225+
}
226+
227+
void updateProperty(
228+
Map? property, {
229+
bool hasFill = false,
230+
bool hasShape = false,
231+
}) {
232+
if (property == null) return;
233+
property['paint'] ??= solidPaint(property['color']);
234+
if (hasFill) {
235+
property['fillPaint'] ??= solidPaint(property['fill'], 0x00000000);
236+
}
237+
if (hasShape) {
238+
updateShapeFill(property['shape'] as Map?);
239+
}
240+
}
241+
242+
void updateElement(Map? item) {
243+
if (item == null) return;
244+
final property = item['property'] as Map?;
245+
switch (item['type']) {
246+
case 'pen':
247+
updateProperty(property, hasFill: true);
248+
case 'polygon':
249+
updateProperty(property, hasFill: true);
250+
case 'shape':
251+
updateProperty(property, hasShape: true);
252+
}
253+
}
254+
255+
void updateTool(Map? item) {
256+
if (item == null) return;
257+
final property = item['property'] as Map?;
258+
switch (item['type']) {
259+
case 'pen':
260+
updateProperty(property, hasFill: true);
261+
case 'polygon':
262+
updateProperty(property, hasFill: true);
263+
case 'shape':
264+
updateProperty(property, hasShape: true);
265+
}
266+
}
267+
268+
for (final page in noteData.getAssets('$kPagesArchiveDirectory/')) {
269+
final data = noteData.getAsset('$kPagesArchiveDirectory/$page');
270+
if (data == null) continue;
271+
final pageData = json.decode(utf8.decode(data)) as Map<String, dynamic>;
272+
final layers = pageData['layers'] as List?;
273+
for (final layer in layers ?? []) {
274+
final content = (layer as Map?)?['content'] as List?;
275+
for (final item in content ?? []) {
276+
updateElement(item as Map?);
277+
}
278+
}
279+
noteData = noteData.setAsset(
280+
'$kPagesArchiveDirectory/$page',
281+
utf8.encode(json.encode(pageData)),
282+
);
283+
}
284+
285+
final info = noteData.getAsset(kInfoArchiveFile);
286+
if (info != null) {
287+
final infoData = json.decode(utf8.decode(info)) as Map<String, dynamic>;
288+
final tools = infoData['tools'] as List?;
289+
for (final item in tools ?? []) {
290+
updateTool(item as Map?);
291+
}
292+
noteData = noteData.setAsset(
293+
kInfoArchiveFile,
294+
utf8.encode(json.encode(infoData)),
295+
);
296+
}
297+
298+
for (final component in noteData.getAssets(
299+
'$kComponentsArchiveDirectory/',
300+
)) {
301+
final data = noteData.getAsset('$kComponentsArchiveDirectory/$component');
302+
if (data == null) continue;
303+
final componentData =
304+
json.decode(utf8.decode(data)) as Map<String, dynamic>;
305+
final elements = componentData['elements'] as List?;
306+
for (final item in elements ?? []) {
307+
updateElement(item as Map?);
308+
}
309+
noteData = noteData.setAsset(
310+
'$kComponentsArchiveDirectory/$component',
311+
utf8.encode(json.encode(componentData)),
312+
);
313+
}
314+
315+
for (final toolbar in noteData.getAssets('$kToolbarsArchiveDirectory/')) {
316+
final data = noteData.getAsset('$kToolbarsArchiveDirectory/$toolbar');
317+
if (data == null) continue;
318+
final toolbarData =
319+
json.decode(utf8.decode(data)) as Map<String, dynamic>;
320+
final tools = toolbarData['tools'] as List?;
321+
for (final item in tools ?? []) {
322+
updateTool(item as Map?);
323+
}
324+
noteData = noteData.setAsset(
325+
'$kToolbarsArchiveDirectory/$toolbar',
326+
utf8.encode(json.encode(toolbarData)),
327+
);
328+
}
329+
}
210330
return noteData;
211331
}

api/lib/src/converter/xopp.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ String _exportColor(SRGBColor value) {
3939
case 'stroke':
4040
return PenElement(
4141
property: PenProperty(
42-
color: _importColor(element.getAttribute('color')!),
42+
paint: ElementPaint.solid(
43+
color: _importColor(element.getAttribute('color')!),
44+
),
4345
strokeWidth: double.parse(
4446
element.getAttribute('width')!.split(' ').first,
4547
),
@@ -187,7 +189,7 @@ Uint8List xoppExporter(NoteData document) {
187189
builder.element(
188190
'stroke',
189191
attributes: {
190-
'color': _exportColor(e.property.color),
192+
'color': _exportColor(e.property.paint.previewColor),
191193
'width': e.property.strokeWidth.toString(),
192194
'tool': 'pen',
193195
},

api/lib/src/models/archive.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const kThumbnailArchiveFile = '$kBflyArchiveDirectory/thumbnail.png';
1212
const kInfoArchiveFile = '$kBflyArchiveDirectory/info.json';
1313
const kFontsArchiveDirectory = '$kBflyArchiveDirectory/fonts';
1414
const kImagesArchiveDirectory = '$kBflyArchiveDirectory/images';
15+
const kTexturesArchiveDirectory = '$kBflyArchiveDirectory/textures';
1516
const kPdfArchiveDirectory = '$kBflyArchiveDirectory/pdf';
1617
const kPacksArchiveDirectory = '$kBflyArchiveDirectory/packs';
1718
const kPagesArchiveDirectory = '$kBflyArchiveDirectory/pages';

api/lib/src/models/data.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'page.dart';
1818

1919
final Set<String> validAssetPaths = {
2020
kImagesArchiveDirectory,
21+
kTexturesArchiveDirectory,
2122
kPdfArchiveDirectory,
2223
};
2324

@@ -510,6 +511,12 @@ final class NoteData extends NoteDisplay<NoteData> {
510511
bool containsImage(Uint8List data, String fileExtension) =>
511512
containsAsset(kImagesArchiveDirectory, data, fileExtension);
512513

514+
(NoteData, String) importTexture(Uint8List data, String fileExtension) =>
515+
importAsset(kTexturesArchiveDirectory, data, fileExtension);
516+
517+
bool containsTexture(Uint8List data, String fileExtension) =>
518+
containsAsset(kTexturesArchiveDirectory, data, fileExtension);
519+
513520
(NoteData, String) importPdf(Uint8List data) =>
514521
importAsset(kPdfArchiveDirectory, data, 'pdf');
515522

@@ -590,6 +597,22 @@ final class NoteData extends NoteDisplay<NoteData> {
590597
NoteData removeComponent(String name) =>
591598
removeAsset('$kComponentsArchiveDirectory/$name.json');
592599

600+
@useResult
601+
Iterable<String> getTextures() =>
602+
getAssets('$kTexturesArchiveDirectory/', true);
603+
604+
@useResult
605+
Uint8List? getTexture(String textureName) =>
606+
getAsset('$kTexturesArchiveDirectory/$textureName');
607+
608+
@useResult
609+
NoteData setTexture(String name, Uint8List texture) =>
610+
setAsset('$kTexturesArchiveDirectory/$name', texture);
611+
612+
@useResult
613+
NoteData removeTexture(String name) =>
614+
removeAsset('$kTexturesArchiveDirectory/$name');
615+
593616
@useResult
594617
Iterable<String> getStyles() => getAssets('$kStylesArchiveDirectory/', true);
595618

api/lib/src/models/meta.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import '../converter/core.dart';
55
part 'meta.freezed.dart';
66
part 'meta.g.dart';
77

8-
const kFileVersion = 12;
8+
const kFileVersion = 13;
99
const kBreakingChangesVersion = 7;
1010

1111
@freezed

api/lib/src/models/property.dart

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,81 @@ enum VerticalAlignment { top, center, bottom }
2020

2121
enum StrokeStyle { solid, dotted }
2222

23+
@freezed
24+
sealed class ElementPaint with _$ElementPaint {
25+
const ElementPaint._();
26+
27+
const factory ElementPaint.solid({
28+
@Default(SRGBColor.black) @ColorJsonConverter() SRGBColor color,
29+
}) = SolidElementPaint;
30+
31+
const factory ElementPaint.texture({
32+
required String source,
33+
@Default(SRGBColor.white) @ColorJsonConverter() SRGBColor tint,
34+
@Default(1) double scale,
35+
}) = TextureElementPaint;
36+
37+
const factory ElementPaint.gradient({
38+
@Default(SRGBColor.black) @ColorJsonConverter() SRGBColor start,
39+
@Default(SRGBColor.white) @ColorJsonConverter() SRGBColor end,
40+
@Default(0) double angle,
41+
}) = GradientElementPaint;
42+
43+
factory ElementPaint.fromJson(Map<String, dynamic> json) =>
44+
_$ElementPaintFromJson(json);
45+
46+
SRGBColor get previewColor => switch (this) {
47+
SolidElementPaint(:final color) => color,
48+
TextureElementPaint(:final tint) => tint,
49+
GradientElementPaint(:final start) => start,
50+
};
51+
52+
ElementPaint withAlpha(int alpha) => switch (this) {
53+
SolidElementPaint(:final color) => ElementPaint.solid(
54+
color: color.withValues(a: alpha),
55+
),
56+
TextureElementPaint(:final source, :final tint, :final scale) =>
57+
ElementPaint.texture(
58+
source: source,
59+
tint: tint.withValues(a: alpha),
60+
scale: scale,
61+
),
62+
GradientElementPaint(:final start, :final end, :final angle) =>
63+
ElementPaint.gradient(
64+
start: start.withValues(a: alpha),
65+
end: end.withValues(a: alpha),
66+
angle: angle,
67+
),
68+
};
69+
}
70+
2371
@freezed
2472
sealed class Property with _$Property {
2573
@Implements<PathProperty>()
2674
const factory Property.pen({
2775
@Default(5) double strokeWidth,
2876
@Default(0.4) double thinning,
29-
@Default(SRGBColor.black) @ColorJsonConverter() SRGBColor color,
30-
@Default(SRGBColor.transparent) @ColorJsonConverter() SRGBColor fill,
77+
@Default(ElementPaint.solid()) ElementPaint paint,
78+
@Default(ElementPaint.solid(color: SRGBColor.transparent))
79+
ElementPaint fillPaint,
3180
@Default(0.5) double smoothing,
3281
@Default(0.3) double streamline,
3382
}) = PenProperty;
3483

3584
const factory Property.shape({
3685
@Default(5) double strokeWidth,
3786
required PathShape shape,
38-
@Default(SRGBColor.black) @ColorJsonConverter() SRGBColor color,
87+
@Default(ElementPaint.solid()) ElementPaint paint,
3988
@Default(StrokeStyle.solid) StrokeStyle strokeStyle,
4089
@Default(1.0) double dashMultiplier,
4190
@Default(1.0) double gapMultiplier,
4291
}) = ShapeProperty;
4392

4493
const factory Property.polygon({
4594
@Default(5) double strokeWidth,
46-
@Default(SRGBColor.black) @ColorJsonConverter() SRGBColor color,
47-
@Default(SRGBColor.transparent) @ColorJsonConverter() SRGBColor fill,
95+
@Default(ElementPaint.solid()) ElementPaint paint,
96+
@Default(ElementPaint.solid(color: SRGBColor.transparent))
97+
ElementPaint fillPaint,
4898
}) = PolygonProperty;
4999

50100
factory Property.fromJson(Map<String, dynamic> json) =>
@@ -55,18 +105,21 @@ sealed class Property with _$Property {
55105
sealed class PathShape with _$PathShape {
56106
const PathShape._();
57107
const factory PathShape.circle({
58-
@Default(SRGBColor.transparent) @ColorJsonConverter() SRGBColor fillColor,
108+
@Default(ElementPaint.solid(color: SRGBColor.transparent))
109+
ElementPaint fillPaint,
59110
}) = CircleShape;
60111
const factory PathShape.rectangle({
61-
@Default(SRGBColor.transparent) @ColorJsonConverter() SRGBColor fillColor,
112+
@Default(ElementPaint.solid(color: SRGBColor.transparent))
113+
ElementPaint fillPaint,
62114
@Default(0) double topLeftCornerRadius,
63115
@Default(0) double topRightCornerRadius,
64116
@Default(0) double bottomLeftCornerRadius,
65117
@Default(0) double bottomRightCornerRadius,
66118
}) = RectangleShape;
67119
const factory PathShape.line() = LineShape;
68120
const factory PathShape.triangle({
69-
@Default(SRGBColor.transparent) @ColorJsonConverter() SRGBColor fillColor,
121+
@Default(ElementPaint.solid(color: SRGBColor.transparent))
122+
ElementPaint fillPaint,
70123
}) = TriangleShape;
71124

72125
factory PathShape.fromJson(Map<String, dynamic> json) =>

0 commit comments

Comments
 (0)