diff --git a/src/eink_feed_render/api/efa/models.zig b/src/eink_feed_render/api/efa/models.zig new file mode 100644 index 0000000..37c51e0 --- /dev/null +++ b/src/eink_feed_render/api/efa/models.zig @@ -0,0 +1,90 @@ +pub const TransportationProductClass = enum(u32) { + zug = 0, + s_bahn = 1, + u_bahn = 2, + stadtbahn = 3, + strassen_trambahn = 4, + stadtbus = 5, + regionalbus = 6, + schnellbus = 7, + seil_zahnradbahn = 8, + schiff = 9, + anruf_sammel_taxi = 10, + sonstige = 11, + flugzeug = 12, + zug_nv = 13, + zug_fv = 14, + zug_fv_m_zuschlag = 15, + zug_fv_m_spez_fpr = 16, + sev = 17, + zug_shuttle = 18, + buergerbus = 19, + rufbus_liniengebunden = 20, + rufbus = 21, + _, + + pub fn toString(self: TransportationProductClass) ?[]const u8 { + return switch (self) { + .zug => "Zug", + .s_bahn => "S-Bahn", + .u_bahn => "U-Bahn", + .stadtbahn => "Stadtbahn", + .strassen_trambahn => "Straßen-/Trambahn", + .stadtbus => "Stadtbus", + .regionalbus => "Regionalbus", + .schnellbus => "Schnellbus", + .seil_zahnradbahn => "Seil-/Zahnradbahn", + .schiff => "Schiff", + .anruf_sammel_taxi => "Anruf-Sammel-Taxi", + .sonstige => "Sonstige", + .flugzeug => "Flugzeug", + .zug_nv => "Zug (Nahverkehr)", + .zug_fv => "Zug (Fernverkehr)", + .zug_fv_m_zuschlag => "Zug (Fernverkehr) mit Zuschlag", + .zug_fv_m_spez_fpr => "Zug (Fernverkehr) mit spezifischer Fpr", + .sev => "SEV Schnienenersatzverkehr", + .zug_shuttle => "Zug Shuttle", + .buergerbus => "Bürgerbus", + .rufbus_liniengebunden => "Rufbus (liniengebunden)", + .rufbus => "Rufbus", + _ => null, + }; + } +}; + +pub const Location = struct { + id: []const u8, + name: []const u8, + properties: struct { + platform: ?[]const u8 = null, + }, +}; + +pub const StopEvent = struct { + location: Location, + departureTimePlanned: []const u8, + departureTimeBaseTimetable: []const u8, + departureTimeEstimated: ?[]const u8 = null, + transportation: struct { + name: []const u8, + product: struct { + class: TransportationProductClass, + }, + operator: ?struct { + name: []const u8, + } = null, + destination: struct { + name: []const u8, + }, + }, + hints: ?[]const struct { + content: []const u8, + type: []const u8, + } = null, + isCancelled: bool = false, +}; + +pub const DmResponse = struct { + locations: []const Location, + stopEvents: []const StopEvent, +}; diff --git a/src/eink_feed_render/apps/Departures/efa.zig b/src/eink_feed_render/api/efa/root.zig similarity index 61% rename from src/eink_feed_render/apps/Departures/efa.zig rename to src/eink_feed_render/api/efa/root.zig index b184d70..0d02df0 100644 --- a/src/eink_feed_render/apps/Departures/efa.zig +++ b/src/eink_feed_render/api/efa/root.zig @@ -2,99 +2,10 @@ const std = @import("std"); const escaper = @import("../../escaper.zig"); +pub const models = @import("models.zig"); + const log = std.log.scoped(.efa); -pub const ApiTransportationProductClass = enum(u32) { - zug = 0, - s_bahn = 1, - u_bahn = 2, - stadtbahn = 3, - strassen_trambahn = 4, - stadtbus = 5, - regionalbus = 6, - schnellbus = 7, - seil_zahnradbahn = 8, - schiff = 9, - anruf_sammel_taxi = 10, - sonstige = 11, - flugzeug = 12, - zug_nv = 13, - zug_fv = 14, - zug_fv_m_zuschlag = 15, - zug_fv_m_spez_fpr = 16, - sev = 17, - zug_shuttle = 18, - buergerbus = 19, - rufbus_liniengebunden = 20, - rufbus = 21, - _, - - pub fn toString(self: ApiTransportationProductClass) ?[]const u8 { - return switch (self) { - .zug => "Zug", - .s_bahn => "S-Bahn", - .u_bahn => "U-Bahn", - .stadtbahn => "Stadtbahn", - .strassen_trambahn => "Straßen-/Trambahn", - .stadtbus => "Stadtbus", - .regionalbus => "Regionalbus", - .schnellbus => "Schnellbus", - .seil_zahnradbahn => "Seil-/Zahnradbahn", - .schiff => "Schiff", - .anruf_sammel_taxi => "Anruf-Sammel-Taxi", - .sonstige => "Sonstige", - .flugzeug => "Flugzeug", - .zug_nv => "Zug (Nahverkehr)", - .zug_fv => "Zug (Fernverkehr)", - .zug_fv_m_zuschlag => "Zug (Fernverkehr) mit Zuschlag", - .zug_fv_m_spez_fpr => "Zug (Fernverkehr) mit spezifischer Fpr", - .sev => "SEV Schnienenersatzverkehr", - .zug_shuttle => "Zug Shuttle", - .buergerbus => "Bürgerbus", - .rufbus_liniengebunden => "Rufbus (liniengebunden)", - .rufbus => "Rufbus", - _ => null, - }; - } -}; - -pub const ApiLocation = struct { - id: []const u8, - name: []const u8, - properties: struct { - platform: ?[]const u8 = null, - }, -}; - -pub const ApiStopEvent = struct { - location: ApiLocation, - departureTimePlanned: []const u8, - departureTimeBaseTimetable: []const u8, - departureTimeEstimated: ?[]const u8 = null, - transportation: struct { - name: []const u8, - product: struct { - class: ApiTransportationProductClass, - }, - operator: ?struct { - name: []const u8, - } = null, - destination: struct { - name: []const u8, - }, - }, - hints: ?[]const struct { - content: []const u8, - type: []const u8, - } = null, - isCancelled: bool = false, -}; - -pub const DmResponse = struct { - locations: []const ApiLocation, - stopEvents: []const ApiStopEvent, -}; - pub const Client = struct { pub const Error = error{ InvalidResponse, @@ -170,7 +81,7 @@ pub const Client = struct { try std.io.getStdOut().writeAll(body); } - pub fn getDepartures(self: *const Self, stop_id: []const u8) !std.json.Parsed(DmResponse) { + pub fn getDepartures(self: *const Self, stop_id: []const u8) !std.json.Parsed(models.DmResponse) { const escaped_stop_id = try escaper.escapeUriComponent(self.allocator, stop_id); defer if (escaped_stop_id) |s| self.allocator.free(s); const url_stop_id = escaped_stop_id orelse stop_id; @@ -204,7 +115,7 @@ pub const Client = struct { defer self.allocator.free(body); const response = try std.json.parseFromSlice( - DmResponse, + models.DmResponse, self.allocator, body, .{ .allocate = .alloc_always, .ignore_unknown_fields = true }, diff --git a/src/eink_feed_render/api/root.zig b/src/eink_feed_render/api/root.zig new file mode 100644 index 0000000..8d90898 --- /dev/null +++ b/src/eink_feed_render/api/root.zig @@ -0,0 +1 @@ +pub const efa = @import("efa/root.zig"); diff --git a/src/eink_feed_render/apps/Departures/root.zig b/src/eink_feed_render/apps/Departures/root.zig index bb6f620..460cacc 100644 --- a/src/eink_feed_render/apps/Departures/root.zig +++ b/src/eink_feed_render/apps/Departures/root.zig @@ -5,7 +5,7 @@ const zdt = @import("zdt"); const Dimensions = @import("../../Dimensions.zig"); const renderer = @import("../../renderer.zig"); -pub const efa = @import("efa.zig"); +pub const api = @import("../../api/root.zig"); const template = @import("template/root.zig"); @@ -28,7 +28,7 @@ pub const RenderOptions = struct { show_operator: bool, }; -pub fn render(self: *const Self, efa_dm_resp: *const efa.DmResponse, options: RenderOptions) ![]const u8 { +pub fn render(self: *const Self, efa_dm_resp: *const api.efa.models.DmResponse, options: RenderOptions) ![]const u8 { const now_local = try options.now.tzConvert(.{ .tz = options.tz }); var arena = std.heap.ArenaAllocator.init(self.allocator); diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/boat.png b/src/eink_feed_render/apps/Departures/template/icons/img/boat.png deleted file mode 100644 index f49f410..0000000 Binary files a/src/eink_feed_render/apps/Departures/template/icons/img/boat.png and /dev/null differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/bus.png b/src/eink_feed_render/apps/Departures/template/icons/img/bus.png deleted file mode 100644 index 5598cff..0000000 Binary files a/src/eink_feed_render/apps/Departures/template/icons/img/bus.png and /dev/null differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/flight.png b/src/eink_feed_render/apps/Departures/template/icons/img/flight.png deleted file mode 100644 index f6416af..0000000 Binary files a/src/eink_feed_render/apps/Departures/template/icons/img/flight.png and /dev/null differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/gondola.png b/src/eink_feed_render/apps/Departures/template/icons/img/gondola.png deleted file mode 100644 index ead5e99..0000000 Binary files a/src/eink_feed_render/apps/Departures/template/icons/img/gondola.png and /dev/null differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/no_transfer.png b/src/eink_feed_render/apps/Departures/template/icons/img/no_transfer.png deleted file mode 100644 index ee12871..0000000 Binary files a/src/eink_feed_render/apps/Departures/template/icons/img/no_transfer.png and /dev/null differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/shuttle.png b/src/eink_feed_render/apps/Departures/template/icons/img/shuttle.png deleted file mode 100644 index bc9b2e9..0000000 Binary files a/src/eink_feed_render/apps/Departures/template/icons/img/shuttle.png and /dev/null differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/subway.png b/src/eink_feed_render/apps/Departures/template/icons/img/subway.png deleted file mode 100644 index ad532af..0000000 Binary files a/src/eink_feed_render/apps/Departures/template/icons/img/subway.png and /dev/null differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/train.png b/src/eink_feed_render/apps/Departures/template/icons/img/train.png deleted file mode 100644 index d482e8d..0000000 Binary files a/src/eink_feed_render/apps/Departures/template/icons/img/train.png and /dev/null differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/img/tram.png b/src/eink_feed_render/apps/Departures/template/icons/img/tram.png deleted file mode 100644 index 718586e..0000000 Binary files a/src/eink_feed_render/apps/Departures/template/icons/img/tram.png and /dev/null differ diff --git a/src/eink_feed_render/apps/Departures/template/icons/root.zig b/src/eink_feed_render/apps/Departures/template/icons/root.zig index bfd53c1..e4e554a 100644 --- a/src/eink_feed_render/apps/Departures/template/icons/root.zig +++ b/src/eink_feed_render/apps/Departures/template/icons/root.zig @@ -1,8 +1,31 @@ const std = @import("std"); -const efa = @import("../../efa.zig"); +const api = @import("../../../../api/root.zig"); +const embed = @import("../../../../embed.zig"); -pub fn iconFromTransportationProductClass(class: efa.ApiTransportationProductClass) ?[]const u8 { +pub const data = embed.embedBase64Files( + (struct { + fn inner(comptime path: []const u8) []const u8 { + return @embedFile(path); + } + }).inner, + "image/svg+xml", + "svg", + "svg", + &.{ + "train", + "subway", + "tram", + "bus", + "shuttle", + "gondola", + "flight", + "boat", + "no_transfer", + }, +){}; + +pub fn iconFromTransportationProductClass(class: api.efa.models.TransportationProductClass) ?embed.EncodedFile { return switch (class) { .zug, .s_bahn, @@ -11,11 +34,11 @@ pub fn iconFromTransportationProductClass(class: efa.ApiTransportationProductCla .zug_fv_m_zuschlag, .zug_fv_m_spez_fpr, .zug_shuttle, - => train, + => data.train, - .u_bahn => subway, + .u_bahn => data.subway, - .stadtbahn, .strassen_trambahn => tram, + .stadtbahn, .strassen_trambahn => data.tram, .stadtbus, .regionalbus, @@ -24,40 +47,18 @@ pub fn iconFromTransportationProductClass(class: efa.ApiTransportationProductCla .rufbus_liniengebunden, .rufbus, .anruf_sammel_taxi, - => bus, + => data.bus, - .seil_zahnradbahn => gondola, + .seil_zahnradbahn => data.gondola, - .schiff => boat, + .schiff => data.boat, .sonstige => null, - .flugzeug => flight, + .flugzeug => data.flight, - .sev => no_transfer, + .sev => data.no_transfer, _ => null, }; } - -fn encodePng(comptime path: []const u8) []const u8 { - const raw = @embedFile(path); - const len = std.base64.standard.Encoder.calcSize(raw.len); - var buf: [len]u8 = undefined; - { - @setEvalBranchQuota(1_000_000); - _ = std.base64.standard.Encoder.encode(&buf, raw); - } - const final = buf; - return &final; -} - -pub const train = encodePng("img/train.png"); -pub const subway = encodePng("img/subway.png"); -pub const tram = encodePng("img/tram.png"); -pub const bus = encodePng("img/bus.png"); -pub const shuttle = encodePng("img/shuttle.png"); -pub const gondola = encodePng("img/gondola.png"); -pub const flight = encodePng("img/flight.png"); -pub const boat = encodePng("img/boat.png"); -pub const no_transfer = encodePng("img/no_transfer.png"); diff --git a/src/eink_feed_render/apps/Departures/template/root.zig b/src/eink_feed_render/apps/Departures/template/root.zig index 862dc59..382bcfb 100644 --- a/src/eink_feed_render/apps/Departures/template/root.zig +++ b/src/eink_feed_render/apps/Departures/template/root.zig @@ -1,7 +1,7 @@ const std = @import("std"); const escaper = @import("../../../escaper.zig"); -const efa = @import("../efa.zig"); +const api = @import("../../../api/root.zig"); const icons = @import("icons/root.zig"); @@ -9,7 +9,7 @@ pub const Departure = struct { time: []const u8, time_unix: i128, line: []const u8, - transportation_product_class: efa.ApiTransportationProductClass, + transportation_product_class: api.efa.models.TransportationProductClass, operator: ?[]const u8, destination: []const u8, platform: ?[]const u8, @@ -188,13 +188,16 @@ pub fn render(context: *const Context, writer: anytype) !void { ); } - if (icons.iconFromTransportationProductClass(departure.transportation_product_class)) |img| { + if (icons.iconFromTransportationProductClass(departure.transportation_product_class)) |file| { try writer.print( \\ - \\ + \\ \\ , - .{ .img = img }, + .{ + .content_type = file.content_type, + .data = file.data, + }, ); } else { try writer.writeAll( diff --git a/src/eink_feed_render/embed.zig b/src/eink_feed_render/embed.zig new file mode 100644 index 0000000..2b6b983 --- /dev/null +++ b/src/eink_feed_render/embed.zig @@ -0,0 +1,67 @@ +const std = @import("std"); + +pub const EncodedFile = struct { + content_type: []const u8, + data: []const u8, +}; + +pub const EmbedFn = *const fn (comptime path: []const u8) []const u8; + +pub fn encodeFileBase64( + comptime embed_fn: EmbedFn, + comptime content_type: []const u8, + comptime path: []const u8, +) EncodedFile { + const raw = embed_fn(path); + const len = std.base64.standard.Encoder.calcSize(raw.len); + var buf: [len]u8 = undefined; + { + @setEvalBranchQuota(1_000_000); + _ = std.base64.standard.Encoder.encode(&buf, raw); + } + const final = buf; + + return .{ + .content_type = content_type, + .data = &final, + }; +} + +pub fn embedBase64Files( + comptime embed_fn: EmbedFn, + comptime content_type: []const u8, + comptime extension: []const u8, + comptime path: []const u8, + comptime names: []const []const u8, +) type { + var fields: [names.len]std.builtin.Type.StructField = undefined; + + for (names, 0..) |name, i| { + const encoded = encodeFileBase64( + embed_fn, + content_type, + std.fmt.comptimePrint( + "{s}/{s}.{s}", + .{ path, name, extension }, + ), + ); + + var name_z: [name.len:0]u8 = undefined; + @memcpy(&name_z, name); + + fields[i] = std.builtin.Type.StructField{ + .name = &name_z, + .type = EncodedFile, + .is_comptime = true, + .alignment = 0, + .default_value_ptr = &encoded, + }; + } + + return @Type(.{ .@"struct" = .{ + .layout = .auto, + .is_tuple = false, + .fields = &fields, + .decls = &[_]std.builtin.Type.Declaration{}, + } }); +} diff --git a/src/eink_feed_render/main/departures.zig b/src/eink_feed_render/main/departures.zig index 38bc120..53a6cee 100644 --- a/src/eink_feed_render/main/departures.zig +++ b/src/eink_feed_render/main/departures.zig @@ -52,7 +52,7 @@ pub fn main() !void { return clap.help(writer, clap.Help, ¶ms, .{}); } - const efa = try render.apps.Departures.efa.Client.init(allocator, res.args.efa.?); + const efa = try render.api.efa.Client.init(allocator, res.args.efa.?); defer efa.deinit(); if (res.args.stopfinder) |query| { diff --git a/src/eink_feed_render/root.zig b/src/eink_feed_render/root.zig index 7fa8232..f7b04eb 100644 --- a/src/eink_feed_render/root.zig +++ b/src/eink_feed_render/root.zig @@ -1,3 +1,4 @@ +pub const api = @import("api/root.zig"); pub const apps = @import("apps/root.zig"); pub const renderer = @import("renderer.zig"); pub const FeedClient = @import("FeedClient.zig");