diff --git a/src/persistence/document_json.zig b/src/persistence/document_json.zig index 87d0b11..28395f4 100644 --- a/src/persistence/document_json.zig +++ b/src/persistence/document_json.zig @@ -1,16 +1,5 @@ const std = @import("std"); const Document = @import("../models/Document.zig"); -const Object = Document.Object; -const Property = @import("../models/Property.zig").Property; -const PropertyData = @import("../models/Property.zig").Data; -const basic_models = @import("../models/basic_models.zig"); -const Point2_f = basic_models.Point2_f; -const Size_f = basic_models.Size_f; -const Scale2_f = basic_models.Scale2_f; -const Radii_f = basic_models.Radii_f; - -pub const json_version: u32 = 1; -const JsonError = std.json.Stringify.Error; pub fn saveToFile(doc: *const Document, path: []const u8) !void { var file = try std.fs.cwd().createFile(path, .{ .truncate = true }); @@ -35,331 +24,7 @@ pub fn loadFromFile(allocator: std.mem.Allocator, path: []const u8) !Document { const data = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); defer allocator.free(data); - var parsed = try std.json.parseFromSlice(std.json.Value, allocator, data, .{ .parse_numbers = true }); - defer parsed.deinit(); + const parsed = try std.json.parseFromSlice(Document, allocator, data, .{ .parse_numbers = true }); - return try parseDocumentValue(allocator, parsed.value); -} - -fn writeDocument(jw: *std.json.Stringify, doc: *const Document) JsonError!void { - try jw.beginObject(); - try jw.objectField("version"); - try jw.write(json_version); - try jw.objectField("size"); - try writeSize(jw, doc.size); - try jw.objectField("next_object_id"); - try jw.write(doc.next_object_id); - try jw.objectField("objects"); - try writeObjectArray(jw, doc.objects.items); - try jw.endObject(); -} - -fn writeSize(jw: *std.json.Stringify, size: Size_f) JsonError!void { - try jw.beginObject(); - try jw.objectField("w"); - try jw.write(size.w); - try jw.objectField("h"); - try jw.write(size.h); - try jw.endObject(); -} - -fn writePoint(jw: *std.json.Stringify, pt: Point2_f) JsonError!void { - try jw.beginObject(); - try jw.objectField("x"); - try jw.write(pt.x); - try jw.objectField("y"); - try jw.write(pt.y); - try jw.endObject(); -} - -fn writeScale(jw: *std.json.Stringify, scale: Scale2_f) JsonError!void { - try jw.beginObject(); - try jw.objectField("scale_x"); - try jw.write(scale.scale_x); - try jw.objectField("scale_y"); - try jw.write(scale.scale_y); - try jw.endObject(); -} - -fn writeRadii(jw: *std.json.Stringify, radii: Radii_f) JsonError!void { - try jw.beginObject(); - try jw.objectField("x"); - try jw.write(radii.x); - try jw.objectField("y"); - try jw.write(radii.y); - try jw.endObject(); -} - -fn writeObjectArray(jw: *std.json.Stringify, objects: []const Object) JsonError!void { - try jw.beginArray(); - for (objects) |obj| { - try writeObject(jw, obj); - } - try jw.endArray(); -} - -fn writeObject(jw: *std.json.Stringify, obj: Object) JsonError!void { - try jw.beginObject(); - try jw.objectField("id"); - try jw.write(obj.id); - try jw.objectField("shape"); - try jw.write(@tagName(obj.shape)); - try jw.objectField("properties"); - try writeProperties(jw, obj.properties.items); - try jw.objectField("children"); - try writeObjectArray(jw, obj.children.items); - try jw.endObject(); -} - -fn writeProperties(jw: *std.json.Stringify, props: []const Property) JsonError!void { - try jw.beginArray(); - for (props) |prop| { - try writeProperty(jw, prop); - } - try jw.endArray(); -} - -fn writeProperty(jw: *std.json.Stringify, prop: Property) JsonError!void { - try jw.beginObject(); - try jw.objectField("tag"); - try jw.write(@tagName(prop.data)); - try jw.objectField("value"); - switch (prop.data) { - .position => |p| try writePoint(jw, p), - .angle => |v| try jw.write(v), - .scale => |s| try writeScale(jw, s), - .visible => |v| try jw.write(v), - .opacity => |v| try jw.write(v), - .locked => |v| try jw.write(v), - .size => |s| try writeSize(jw, s), - .radii => |r| try writeRadii(jw, r), - .end_point => |p| try writePoint(jw, p), - .points => |list| { - try jw.beginArray(); - for (list.items) |pt| { - try writePoint(jw, pt); - } - try jw.endArray(); - }, - .fill_rgba => |v| try jw.write(v), - .stroke_rgba => |v| try jw.write(v), - .thickness => |v| try jw.write(v), - } - try jw.endObject(); -} - -fn parseDocumentValue(allocator: std.mem.Allocator, value: std.json.Value) !Document { - const obj = try expectObject(value); - const version_val = try getField(obj, "version"); - const version = try valueToU32(version_val); - if (version != json_version) return error.UnsupportedVersion; - - const size_val = try getField(obj, "size"); - const size = try parseSize(size_val); - - var document = Document{ - .size = size, - .objects = std.ArrayList(Object).empty, - .next_object_id = 1, - }; - errdefer document.deinit(allocator); - - const objects_val = try getField(obj, "objects"); - const objects_arr = try expectArray(objects_val); - for (objects_arr.items) |item| { - const obj_item = try parseObject(allocator, item); - try document.objects.append(allocator, obj_item); - } - - if (obj.get("next_object_id")) |next_val| { - const next_id = try valueToU64(next_val); - document.next_object_id = next_id; - } else { - document.next_object_id = computeNextObjectId(document.objects.items); - } - - return document; -} - -fn parseObject(allocator: std.mem.Allocator, value: std.json.Value) !Object { - const obj = try expectObject(value); - const id = try valueToU64(try getField(obj, "id")); - const shape_str = try valueToString(try getField(obj, "shape")); - const shape = std.meta.stringToEnum(Object.ShapeKind, shape_str) orelse return error.InvalidJson; - - var properties = std.ArrayList(Property).empty; - errdefer { - for (properties.items) |*p| p.deinit(allocator); - properties.deinit(allocator); - } - var children = std.ArrayList(Object).empty; - errdefer { - for (children.items) |*c| c.deinit(allocator); - children.deinit(allocator); - } - - const props_val = try getField(obj, "properties"); - const props_arr = try expectArray(props_val); - for (props_arr.items) |item| { - try properties.append(allocator, try parseProperty(allocator, item)); - } - - const children_val = try getField(obj, "children"); - const children_arr = try expectArray(children_val); - for (children_arr.items) |item| { - try children.append(allocator, try parseObject(allocator, item)); - } - - return .{ - .id = id, - .shape = shape, - .properties = properties, - .children = children, - }; -} - -fn parseProperty(allocator: std.mem.Allocator, value: std.json.Value) !Property { - const obj = try expectObject(value); - const tag = try valueToString(try getField(obj, "tag")); - const val = try getField(obj, "value"); - - const data: PropertyData = if (std.mem.eql(u8, tag, "position")) blk: { - break :blk .{ .position = try parsePoint(val) }; - } else if (std.mem.eql(u8, tag, "angle")) blk: { - break :blk .{ .angle = try valueToF32(val) }; - } else if (std.mem.eql(u8, tag, "scale")) blk: { - break :blk .{ .scale = try parseScale(val) }; - } else if (std.mem.eql(u8, tag, "visible")) blk: { - break :blk .{ .visible = try valueToBool(val) }; - } else if (std.mem.eql(u8, tag, "opacity")) blk: { - break :blk .{ .opacity = try valueToF32(val) }; - } else if (std.mem.eql(u8, tag, "locked")) blk: { - break :blk .{ .locked = try valueToBool(val) }; - } else if (std.mem.eql(u8, tag, "size")) blk: { - break :blk .{ .size = try parseSize(val) }; - } else if (std.mem.eql(u8, tag, "radii")) blk: { - break :blk .{ .radii = try parseRadii(val) }; - } else if (std.mem.eql(u8, tag, "end_point")) blk: { - break :blk .{ .end_point = try parsePoint(val) }; - } else if (std.mem.eql(u8, tag, "points")) blk: { - const arr = try expectArray(val); - var list = std.ArrayList(Point2_f).empty; - errdefer list.deinit(allocator); - for (arr.items) |item| { - try list.append(allocator, try parsePoint(item)); - } - break :blk .{ .points = list }; - } else if (std.mem.eql(u8, tag, "fill_rgba")) blk: { - break :blk .{ .fill_rgba = try valueToU32(val) }; - } else if (std.mem.eql(u8, tag, "stroke_rgba")) blk: { - break :blk .{ .stroke_rgba = try valueToU32(val) }; - } else if (std.mem.eql(u8, tag, "thickness")) blk: { - break :blk .{ .thickness = try valueToF32(val) }; - } else { - return error.InvalidJson; - }; - - return .{ .data = data }; -} - -fn parsePoint(value: std.json.Value) !Point2_f { - const obj = try expectObject(value); - const x = try valueToF32(try getField(obj, "x")); - const y = try valueToF32(try getField(obj, "y")); - return .{ .x = x, .y = y }; -} - -fn parseSize(value: std.json.Value) !Size_f { - const obj = try expectObject(value); - const w = try valueToF32(try getField(obj, "w")); - const h = try valueToF32(try getField(obj, "h")); - return .{ .w = w, .h = h }; -} - -fn parseScale(value: std.json.Value) !Scale2_f { - const obj = try expectObject(value); - const sx = try valueToF32(try getField(obj, "scale_x")); - const sy = try valueToF32(try getField(obj, "scale_y")); - return .{ .scale_x = sx, .scale_y = sy }; -} - -fn parseRadii(value: std.json.Value) !Radii_f { - const obj = try expectObject(value); - const x = try valueToF32(try getField(obj, "x")); - const y = try valueToF32(try getField(obj, "y")); - return .{ .x = x, .y = y }; -} - -fn expectObject(value: std.json.Value) !std.json.ObjectMap { - return switch (value) { - .object => |o| o, - else => error.InvalidJson, - }; -} - -fn expectArray(value: std.json.Value) !std.json.Array { - return switch (value) { - .array => |a| a, - else => error.InvalidJson, - }; -} - -fn getField(obj: std.json.ObjectMap, key: []const u8) !std.json.Value { - return obj.get(key) orelse error.InvalidJson; -} - -fn valueToString(value: std.json.Value) ![]const u8 { - return switch (value) { - .string => |s| s, - else => error.InvalidJson, - }; -} - -fn valueToBool(value: std.json.Value) !bool { - return switch (value) { - .bool => |b| b, - else => error.InvalidJson, - }; -} - -fn valueToF32(value: std.json.Value) !f32 { - return switch (value) { - .float => |f| @floatCast(f), - .integer => |i| @floatFromInt(i), - else => error.InvalidJson, - }; -} - -fn valueToU32(value: std.json.Value) !u32 { - const v = switch (value) { - .integer => |i| i, - else => return error.InvalidJson, - }; - if (v < 0 or v > std.math.maxInt(u32)) return error.InvalidJson; - return @intCast(v); -} - -fn valueToU64(value: std.json.Value) !u64 { - const v = switch (value) { - .integer => |i| i, - else => return error.InvalidJson, - }; - if (v < 0) return error.InvalidJson; - return @intCast(v); -} - -fn computeNextObjectId(objects: []const Object) u64 { - var max_id: u64 = 0; - for (objects) |obj| { - max_id = @max(max_id, maxIdInObject(obj)); - } - return max_id + 1; -} - -fn maxIdInObject(obj: Object) u64 { - var max_id = obj.id; - for (obj.children.items) |child| { - max_id = @max(max_id, maxIdInObject(child)); - } - return max_id; + return parsed.value; }