partial changes for png generation
This commit is contained in:
parent
148bd862b5
commit
d0f08aacfa
4 changed files with 114 additions and 39 deletions
25
build.zig
25
build.zig
|
|
@ -16,6 +16,22 @@ pub fn build(b: *std.Build) void {
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const zigimg = b.dependency("zigimg", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const freetype = b.dependency("ghostty", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
}).builder.dependency("freetype", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const jetbrains_mono = b.dependency("jetbrains_mono", .{});
|
||||||
|
const nerd_fonts = b.dependency("nerd_fonts_symbols_only", .{});
|
||||||
|
|
||||||
const openflights = b.dependency("openflights", .{});
|
const openflights = b.dependency("openflights", .{});
|
||||||
|
|
||||||
const maxminddb_upstream = b.dependency("maxminddb", .{});
|
const maxminddb_upstream = b.dependency("maxminddb", .{});
|
||||||
|
|
@ -113,9 +129,17 @@ pub fn build(b: *std.Build) void {
|
||||||
});
|
});
|
||||||
root_module.addImport("httpz", httpz.module("httpz"));
|
root_module.addImport("httpz", httpz.module("httpz"));
|
||||||
root_module.addImport("zeit", zeit.module("zeit"));
|
root_module.addImport("zeit", zeit.module("zeit"));
|
||||||
|
root_module.addImport("zigimg", zigimg.module("zigimg"));
|
||||||
|
root_module.addImport("freetype", freetype.module("freetype"));
|
||||||
root_module.addAnonymousImport("airports.dat", .{
|
root_module.addAnonymousImport("airports.dat", .{
|
||||||
.root_source_file = openflights.path("data/airports.dat"),
|
.root_source_file = openflights.path("data/airports.dat"),
|
||||||
});
|
});
|
||||||
|
root_module.addAnonymousImport("JetBrainsMono-Regular.ttf", .{
|
||||||
|
.root_source_file = jetbrains_mono.path("fonts/ttf/JetBrainsMono-Regular.ttf"),
|
||||||
|
});
|
||||||
|
root_module.addAnonymousImport("SymbolsNerdFont-Regular.ttf", .{
|
||||||
|
.root_source_file = nerd_fonts.path("SymbolsNerdFont-Regular.ttf"),
|
||||||
|
});
|
||||||
root_module.addOptions("build_options", build_options);
|
root_module.addOptions("build_options", build_options);
|
||||||
root_module.addIncludePath(maxminddb_upstream.path("include"));
|
root_module.addIncludePath(maxminddb_upstream.path("include"));
|
||||||
root_module.addIncludePath(b.path("libs/phoon_14Aug2014"));
|
root_module.addIncludePath(b.path("libs/phoon_14Aug2014"));
|
||||||
|
|
@ -125,6 +149,7 @@ pub fn build(b: *std.Build) void {
|
||||||
maxminddb,
|
maxminddb,
|
||||||
phoon,
|
phoon,
|
||||||
sunriset,
|
sunriset,
|
||||||
|
freetype.artifact("freetype"),
|
||||||
};
|
};
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "wttr",
|
.name = "wttr",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,22 @@
|
||||||
},
|
},
|
||||||
.phoon = .{ .path = "libs/phoon_14Aug2014" },
|
.phoon = .{ .path = "libs/phoon_14Aug2014" },
|
||||||
.sunriset = .{ .path = "libs/sunriset" },
|
.sunriset = .{ .path = "libs/sunriset" },
|
||||||
|
.ghostty = .{
|
||||||
|
.url = "git+https://github.com/ghostty-org/ghostty#ec2912dbafe50cc32b786d2327dcd0213c83ecc6",
|
||||||
|
.hash = "ghostty-1.3.0-dev-5UdBC_y2RASwYWn5fjn71WsP-arlg8wSICLc0rYiozdf",
|
||||||
|
},
|
||||||
|
.zigimg = .{
|
||||||
|
.url = "git+https://github.com/zigimg/zigimg#9714df09f76891323c7fdbbbf23a17b79024fffb",
|
||||||
|
.hash = "zigimg-0.1.0-8_eo2j4mFwCU7tWnqvkYtzqe-OPRn_bxEql_IJhW85LT",
|
||||||
|
},
|
||||||
|
.jetbrains_mono = .{
|
||||||
|
.url = "https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz",
|
||||||
|
.hash = "N-V-__8AAIC5lwAVPJJzxnCAahSvZTIlG-HhtOvnM1uh-66x",
|
||||||
|
},
|
||||||
|
.nerd_fonts_symbols_only = .{
|
||||||
|
.url = "https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz",
|
||||||
|
.hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO26s",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
.fingerprint = 0x710c2b57e81aa678,
|
.fingerprint = 0x710c2b57e81aa678,
|
||||||
.minimum_zig_version = "0.15.2",
|
.minimum_zig_version = "0.15.2",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ format: ?[]const u8 = null,
|
||||||
lang: ?[]const u8 = null,
|
lang: ?[]const u8 = null,
|
||||||
location: ?[]const u8 = null,
|
location: ?[]const u8 = null,
|
||||||
transparency: ?u8 = null,
|
transparency: ?u8 = null,
|
||||||
|
background: ?[]const u8 = null,
|
||||||
|
add_frame: bool = false,
|
||||||
/// A: Ignore user agent and force ansi mode
|
/// A: Ignore user agent and force ansi mode
|
||||||
ansi: bool = false,
|
ansi: bool = false,
|
||||||
/// T: Avoid terminal sequences and just output plain text
|
/// T: Avoid terminal sequences and just output plain text
|
||||||
|
|
@ -60,6 +62,7 @@ pub fn parse(allocator: std.mem.Allocator, query_string: []const u8) !QueryParam
|
||||||
'A' => params.ansi = true,
|
'A' => params.ansi = true,
|
||||||
'T' => params.text_only = true,
|
'T' => params.text_only = true,
|
||||||
't' => params.transparency = 150,
|
't' => params.transparency = 150,
|
||||||
|
'p' => params.add_frame = true,
|
||||||
else => continue,
|
else => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +80,8 @@ pub fn parse(allocator: std.mem.Allocator, query_string: []const u8) !QueryParam
|
||||||
if (value) |v| {
|
if (value) |v| {
|
||||||
params.transparency = try std.fmt.parseInt(u8, v, 10);
|
params.transparency = try std.fmt.parseInt(u8, v, 10);
|
||||||
}
|
}
|
||||||
|
} else if (std.mem.eql(u8, key, "background")) {
|
||||||
|
params.background = if (value) |v| try allocator.dupe(u8, v) else null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ const Json = @import("../render/Json.zig");
|
||||||
const V2 = @import("../render/V2.zig");
|
const V2 = @import("../render/V2.zig");
|
||||||
const Custom = @import("../render/Custom.zig");
|
const Custom = @import("../render/Custom.zig");
|
||||||
const Prometheus = @import("../render/Prometheus.zig");
|
const Prometheus = @import("../render/Prometheus.zig");
|
||||||
|
const Png = @import("../render/Png.zig");
|
||||||
const help = @import("help.zig");
|
const help = @import("help.zig");
|
||||||
|
const types = @import("../weather/types.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.handler);
|
const log = std.log.scoped(.handler);
|
||||||
|
|
||||||
|
|
@ -33,12 +35,14 @@ pub fn handleWeather(
|
||||||
defer {
|
defer {
|
||||||
if (params.format) |f| req.arena.free(f);
|
if (params.format) |f| req.arena.free(f);
|
||||||
if (params.lang) |l| req.arena.free(l);
|
if (params.lang) |l| req.arena.free(l);
|
||||||
|
if (params.background) |b| req.arena.free(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.location) |loc| {
|
if (params.location) |loc| {
|
||||||
break :blk loc;
|
break :blk loc;
|
||||||
} else break :blk client_ip; // no location, just use client ip instead
|
} else break :blk client_ip; // no location, just use client ip instead
|
||||||
};
|
};
|
||||||
|
|
||||||
if (std.mem.eql(u8, "favicon.ico", location)) {
|
if (std.mem.eql(u8, "favicon.ico", location)) {
|
||||||
res.header("Content-Type", "image/x-icon");
|
res.header("Content-Type", "image/x-icon");
|
||||||
res.body = @embedFile("favicon.ico");
|
res.body = @embedFile("favicon.ico");
|
||||||
|
|
@ -75,16 +79,20 @@ fn handleWeatherInternal(
|
||||||
) !void {
|
) !void {
|
||||||
const req_alloc = req.arena;
|
const req_alloc = req.arena;
|
||||||
|
|
||||||
|
// Check for PNG request
|
||||||
|
const is_png = std.mem.endsWith(u8, location_query, ".png");
|
||||||
|
const location_str = if (is_png) location_query[0 .. location_query.len - 4] else location_query;
|
||||||
|
|
||||||
// Resolve location. By the time we get here, we really
|
// Resolve location. By the time we get here, we really
|
||||||
// should have a location from the path, query string, or
|
// should have a location from the path, query string, or
|
||||||
// client IP lookup. So if we have an empty location parameter, it
|
// client IP lookup. So if we have an empty location parameter, it
|
||||||
// is better to 404 than to fake it with a London response
|
// is better to 404 than to fake it with a London response
|
||||||
if (location_query.len == 0) {
|
if (location_str.len == 0) {
|
||||||
res.status = 404;
|
res.status = 404;
|
||||||
res.body = "Location not found\n";
|
res.body = "Location not found\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const location = opts.resolver.resolve(location_query) catch |err| {
|
const location = opts.resolver.resolve(location_str) catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
error.LocationNotFound => {
|
error.LocationNotFound => {
|
||||||
log.debug("Location not found for query {s}", .{location_query});
|
log.debug("Location not found for query {s}", .{location_query});
|
||||||
|
|
@ -118,6 +126,7 @@ fn handleWeatherInternal(
|
||||||
defer {
|
defer {
|
||||||
if (params.format) |f| req_alloc.free(f);
|
if (params.format) |f| req_alloc.free(f);
|
||||||
if (params.lang) |l| req_alloc.free(l);
|
if (params.lang) |l| req_alloc.free(l);
|
||||||
|
if (params.background) |b| req_alloc.free(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
var render_options = params.render_options;
|
var render_options = params.render_options;
|
||||||
|
|
@ -140,50 +149,70 @@ fn handleWeatherInternal(
|
||||||
const coords_header = try std.fmt.allocPrint(res.arena, "{d:.4},{d:.4}", .{ location.coords.latitude, location.coords.longitude });
|
const coords_header = try std.fmt.allocPrint(res.arena, "{d:.4},{d:.4}", .{ location.coords.latitude, location.coords.longitude });
|
||||||
res.headers.add("X-Location-Coordinates", coords_header);
|
res.headers.add("X-Location-Coordinates", coords_header);
|
||||||
|
|
||||||
|
// Render weather data
|
||||||
|
if (is_png) {
|
||||||
|
res.content_type = .PNG;
|
||||||
|
var png_renderer = Png.init(req_alloc);
|
||||||
|
defer png_renderer.deinit();
|
||||||
|
|
||||||
|
var png_buffer: [1024 * 1024]u8 = undefined;
|
||||||
|
var png_writer_impl = std.Io.Writer.fixed(&png_buffer);
|
||||||
|
const png_writer = &png_writer_impl;
|
||||||
|
|
||||||
|
render_options.format = .ansi; // Force ANSI for PNG
|
||||||
|
try renderWeatherData(png_writer, weather, params, render_options);
|
||||||
|
|
||||||
|
const text_output = png_buffer[0..png_writer_impl.end];
|
||||||
|
try png_renderer.buffer.appendSlice(req_alloc, text_output);
|
||||||
|
|
||||||
|
const png_options = Png.PngOptions{
|
||||||
|
.transparency = params.transparency orelse 150,
|
||||||
|
.background = params.background,
|
||||||
|
.add_frame = params.add_frame,
|
||||||
|
};
|
||||||
|
try png_renderer.render(res.writer(), png_options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Set content type based on format
|
||||||
if (params.format) |fmt| {
|
if (params.format) |fmt| {
|
||||||
// Anything except the json will be plain text
|
res.content_type = if (std.mem.eql(u8, fmt, "j1")) .JSON else .TEXT;
|
||||||
res.content_type = .TEXT;
|
|
||||||
if (std.mem.eql(u8, fmt, "1")) {
|
|
||||||
try Line.render(res.writer(), weather, .@"1", render_options.use_imperial);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, fmt, "2")) {
|
|
||||||
try Line.render(res.writer(), weather, .@"2", render_options.use_imperial);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, fmt, "3")) {
|
|
||||||
try Line.render(res.writer(), weather, .@"3", render_options.use_imperial);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, fmt, "4")) {
|
|
||||||
try Line.render(res.writer(), weather, .@"4", render_options.use_imperial);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, fmt, "j1")) {
|
|
||||||
res.content_type = .JSON; // reset to json
|
|
||||||
try Json.render(res.writer(), weather);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, fmt, "p1")) {
|
|
||||||
try Prometheus.render(res.writer(), weather);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, fmt, "v2")) {
|
|
||||||
try V2.render(res.writer(), weather, render_options.use_imperial);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Everything else goes to Custom renderer
|
|
||||||
try Custom.render(res.writer(), weather, fmt, render_options.use_imperial);
|
|
||||||
} else {
|
} else {
|
||||||
// No specific format selected, we'll provide Formatted output in either
|
|
||||||
// text (ansi/plain) or html
|
|
||||||
render_options.format = determineFormat(params, req.headers.get("user-agent"));
|
render_options.format = determineFormat(params, req.headers.get("user-agent"));
|
||||||
log.debug(
|
log.debug(
|
||||||
"Format: {}. params.ansi {}, params.text {}, user agent: {?s}",
|
"Format: {}. params.ansi {}, params.text {}, user agent: {?s}",
|
||||||
.{ render_options.format, params.ansi, params.text_only, req.headers.get("user-agent") },
|
.{ render_options.format, params.ansi, params.text_only, req.headers.get("user-agent") },
|
||||||
);
|
);
|
||||||
if (render_options.format != .html) res.content_type = .TEXT else res.content_type = .HTML;
|
res.content_type = if (render_options.format == .html) .HTML else .TEXT;
|
||||||
try Formatted.render(res.writer(), weather, render_options);
|
}
|
||||||
|
try renderWeatherData(res.writer(), weather, params, render_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderWeatherData(
|
||||||
|
writer: *std.Io.Writer,
|
||||||
|
weather: types.WeatherData,
|
||||||
|
params: QueryParams,
|
||||||
|
render_options: Formatted.RenderOptions,
|
||||||
|
) !void {
|
||||||
|
if (params.format) |fmt| {
|
||||||
|
if (std.mem.eql(u8, fmt, "1")) {
|
||||||
|
try Line.render(writer, weather, .@"1", render_options.use_imperial);
|
||||||
|
} else if (std.mem.eql(u8, fmt, "2")) {
|
||||||
|
try Line.render(writer, weather, .@"2", render_options.use_imperial);
|
||||||
|
} else if (std.mem.eql(u8, fmt, "3")) {
|
||||||
|
try Line.render(writer, weather, .@"3", render_options.use_imperial);
|
||||||
|
} else if (std.mem.eql(u8, fmt, "4")) {
|
||||||
|
try Line.render(writer, weather, .@"4", render_options.use_imperial);
|
||||||
|
} else if (std.mem.eql(u8, fmt, "j1")) {
|
||||||
|
try Json.render(writer, weather);
|
||||||
|
} else if (std.mem.eql(u8, fmt, "p1")) {
|
||||||
|
try Prometheus.render(writer, weather);
|
||||||
|
} else if (std.mem.eql(u8, fmt, "v2")) {
|
||||||
|
try V2.render(writer, weather, render_options.use_imperial);
|
||||||
|
} else {
|
||||||
|
try Custom.render(writer, weather, fmt, render_options.use_imperial);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try Formatted.render(writer, weather, render_options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue