From 06c721c5bb6cd06fc35bd0a36a98415758501df8 Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Mon, 5 Oct 2020 23:01:24 +0100 Subject: add simple chart generator and use it from the imagecache --- content/fetchers/about/Makefile | 1 + content/fetchers/about/about.c | 9 + content/fetchers/about/chart.c | 628 ++++++++++++++++++++++++++++++++++++ content/fetchers/about/chart.h | 37 +++ content/fetchers/about/imagecache.c | 18 +- 5 files changed, 691 insertions(+), 2 deletions(-) create mode 100644 content/fetchers/about/chart.c create mode 100644 content/fetchers/about/chart.h (limited to 'content/fetchers') diff --git a/content/fetchers/about/Makefile b/content/fetchers/about/Makefile index 10c9a6a72..4f12a0626 100644 --- a/content/fetchers/about/Makefile +++ b/content/fetchers/about/Makefile @@ -4,6 +4,7 @@ S_FETCHER_ABOUT := \ about.c \ blank.c \ certificate.c \ + chart.c \ choices.c \ config.c \ imagecache.c \ diff --git a/content/fetchers/about/about.c b/content/fetchers/about/about.c index fe9eba7a4..651894249 100644 --- a/content/fetchers/about/about.c +++ b/content/fetchers/about/about.c @@ -49,6 +49,7 @@ #include "blank.h" #include "certificate.h" #include "config.h" +#include "chart.h" #include "choices.h" #include "imagecache.h" #include "nscolours.h" @@ -441,6 +442,14 @@ struct about_handlers about_handler_list[] = { fetch_about_certificate_handler, true }, + { + /* chart generator */ + "chart", + SLEN("chart"), + NULL, + fetch_about_chart_handler, + true + }, { "query/auth", SLEN("query/auth"), diff --git a/content/fetchers/about/chart.c b/content/fetchers/about/chart.c new file mode 100644 index 000000000..1565c54d8 --- /dev/null +++ b/content/fetchers/about/chart.c @@ -0,0 +1,628 @@ +/* + * Copyright 2020 Vincent Sanders + * + * This file is part of NetSurf. + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * content generator for the about scheme chart page + * + * A chart consists of the figure area in which a chart a title and a + * key are placed. + * + * + */ + +#include +#include +#include +#include +#include +#include + +#include "netsurf/inttypes.h" +#include "utils/utils.h" +#include "utils/errors.h" +#include "utils/nsurl.h" + +#include "private.h" +#include "chart.h" + +/** minimum figure dimension */ +#define FIGURE_MIN_WIDTH 150 +#define FIGURE_MIN_HEIGHT 100 + +enum chart_type { + CHART_TYPE_UNKNOWN, + CHART_TYPE_PIE, +}; + +/* type of chart key */ +enum key_type { + CHART_KEY_UNSET, + CHART_KEY_NONE, + CHART_KEY_LEFT, + CHART_KEY_RIGHT, + CHART_KEY_TOP, + CHART_KEY_BOT, + CHART_KEY_END +}; + + +struct chart_label { + char *title; /* label title */ + unsigned int colour; /* colour */ +}; + +struct chart_series { + unsigned int len; /* number of values in the series */ + float *value; /* array of values */ +}; + +#define MAX_SERIES 4 + +struct chart_data { + unsigned int series_len; + struct chart_series series[MAX_SERIES]; + + unsigned int label_len; /* number of labels */ + struct chart_label *label; + +}; + +/** + * parameters for a chart figure + */ +struct chart_param { + enum chart_type type; + enum key_type key; /* what type of key to use */ + unsigned int width; /* width of figure */ + unsigned int height; /* height of figure */ + char *title; /* title */ + struct { + unsigned int x; + unsigned int y; + unsigned int width; + unsigned int height; + } area; /* chart area within figure */ + struct chart_data data; +}; + +#define DEF_COLOUR_NUM 8 +/** default colour series */ +static unsigned int colour_series[DEF_COLOUR_NUM] = + { + 0x00ff00, /* green */ + 0x0000ff, /* blue */ + 0xff0000, /* red */ + 0xffff00, /* yellow */ + 0x00ffff, /* cyan */ + 0xff00ff, /* pink */ + 0x777777, /* grey */ + 0x000000, /* black */ + }; + + +/* ensures there are labels present for every value */ +static nserror ensure_label_count(struct chart_param *chart, unsigned int count) +{ + unsigned int lidx; + int deltac; + struct chart_label *nlabels; + + deltac = count - chart->data.label_len; + if (deltac <= 0) { + /* there are enough labels */ + return NSERROR_OK; + } + + nlabels = realloc(chart->data.label, + count * sizeof(struct chart_label)); + if (nlabels == NULL) { + return NSERROR_NOMEM; + } + chart->data.label = nlabels; + + for (lidx = chart->data.label_len; lidx < count; lidx++) { + chart->data.label[lidx].title = calloc(1, 20); + snprintf(chart->data.label[lidx].title, 19, "item %d", lidx + 1); + chart->data.label[lidx].colour = colour_series[lidx % DEF_COLOUR_NUM]; + } + + chart->data.label_len = count; + + return NSERROR_OK; +} + +/** + * extract values for a series + */ +static nserror +extract_series_values(struct chart_param *chart, + unsigned int series_num, + const char *valstr, + size_t valstrlen) +{ + nserror res; + unsigned int valcur; + size_t valstart;/* value start in valstr */ + size_t vallen; /* value end in valstr */ + struct chart_series *series; + + series = chart->data.series + series_num; + + /* ensure we do not leak any data in this series */ + if (series->value != NULL) { + free(series->value); + } + + /* count how many values present */ + for (series->len = 1, valstart=0; valstart < valstrlen; valstart++) { + if (valstr[valstart] == ',') { + series->len++; + } + } + + /* allocate storage for values */ + series->value = calloc(series->len, sizeof(float)); + if (series->value == NULL) { + return NSERROR_NOMEM; + } + + /* extract values from query string */ + for (valcur = 0, vallen = 0, valstart = 0; + (valstart < valstrlen) && (valcur < series->len); + valstart += vallen, valcur++) { + /* get query section length */ + vallen = 0; + while (((valstart + vallen) < valstrlen) && + (valstr[valstart + vallen] != ',')) { + vallen++; + } + + series->value[valcur] = strtof(valstr + valstart, NULL); + vallen++; /* account for , separator */ + } + + res = ensure_label_count(chart, series->len); + + return res; +} + + +/** + * extract values for next series + */ +static nserror +extract_next_series_values(struct chart_param *chart, + const char *valstr, + size_t valstrlen) +{ + nserror res; + + if (chart->data.series_len >= MAX_SERIES) { + return NSERROR_NOSPACE; + } + + res = extract_series_values(chart, + chart->data.series_len, + valstr, + valstrlen); + if (res == NSERROR_OK) { + chart->data.series_len++; + } + + return res; +} + + +/** + * extract label title + */ +static nserror +extract_series_labels(struct chart_param *chart, + const char *valstr, + size_t valstrlen) +{ + nserror res; + unsigned int valcount; /* count of values in valstr */ + unsigned int valcur; + size_t valstart;/* value start in valstr */ + size_t vallen; /* value end in valstr */ + + for (valcount = 1, valstart=0; valstart < valstrlen; valstart++) { + if (valstr[valstart] == ',') { + valcount++; + } + } + + res = ensure_label_count(chart, valcount); + if (res != NSERROR_OK) { + return res; + } + + + for (valcur = 0, vallen = 0, valstart = 0; + (valstart < valstrlen) && (valcur < chart->data.label_len); + valstart += vallen, valcur++) { + /* get query section length */ + vallen = 0; + while (((valstart + vallen) < valstrlen) && + (valstr[valstart + vallen] != ',')) { + vallen++; + } + + chart->data.label[valcur].title = strndup(valstr + valstart, vallen); + vallen++; /* account for , separator */ + } + return NSERROR_OK; +} + + +/** + * extract labels colour + */ +static nserror +extract_series_colours(struct chart_param *chart, + const char *valstr, + size_t valstrlen) +{ + return NSERROR_OK; +} + +/** + * process a part of a query + */ +static nserror +process_query_section(const char *str, size_t len, struct chart_param *chart) +{ + nserror res = NSERROR_OK; + + if ((len > 6) && + (strncmp(str, "width=", 6) == 0)) { + /* figure width */ + chart->width = strtoul(str + 6, NULL, 10); + } else if ((len > 7) && + (strncmp(str, "height=", 7) == 0)) { + /* figure height */ + chart->height = strtoul(str + 7, NULL, 10); + } else if ((len > 8) && + (strncmp(str, "cawidth=", 8) == 0)) { + /* chart area width */ + chart->area.width = strtoul(str + 8, NULL, 10); + } else if ((len > 9) && + (strncmp(str, "caheight=", 9) == 0)) { + /* chart area height */ + chart->area.height = strtoul(str + 9, NULL, 10); + } else if ((len > 4) && + (strncmp(str, "key=", 4) == 0)) { + /* figure has key */ + chart->key = strtoul(str + 4, NULL, 10); + } else if ((len > 6) && + (strncmp(str, "title=", 6) == 0)) { + chart->title = strndup(str + 6, len - 6); + } else if ((len > 5) && + (strncmp(str, "type=", 5) == 0)) { + if (strncmp(str + 5, "pie", len - 5) == 0) { + chart->type = CHART_TYPE_PIE; + } else { + chart->type = CHART_TYPE_UNKNOWN; + } + } else if ((len > 7) && + (strncmp(str, "values=", 7) == 0)) { + res = extract_next_series_values(chart, str + 7, len - 7); + } else if ((len > 7) && + (strncmp(str, "labels=", 7) == 0)) { + res = extract_series_labels(chart, str + 7, len - 7); + } else if ((len > 8) && + (strncmp(str, "colours=", 8) == 0)) { + res = extract_series_colours(chart, str + 8, len - 8); + } + + return res; +} + + + +static nserror +chart_from_query(struct nsurl *url, struct chart_param *chart) +{ + nserror res; + char *querystr; + size_t querylen; + size_t kvstart;/* key value start */ + size_t kvlen; /* key value end */ + + res = nsurl_get(url, NSURL_QUERY, &querystr, &querylen); + if (res != NSERROR_OK) { + return res; + } + + for (kvlen = 0, kvstart = 0; kvstart < querylen; kvstart += kvlen) { + /* get query section length */ + kvlen = 0; + while (((kvstart + kvlen) < querylen) && + (querystr[kvstart + kvlen] != '&')) { + kvlen++; + } + + res = process_query_section(querystr + kvstart, kvlen, chart); + if (res != NSERROR_OK) { + break; + } + kvlen++; /* account for & separator */ + } + free(querystr); + + /* sanity check dimensions */ + if (chart->width < FIGURE_MIN_WIDTH) { + /* bad width - check height */ + if (chart->height < FIGURE_MIN_HEIGHT) { + /* both bad set to defaults */ + chart->width = FIGURE_MIN_WIDTH; + chart->height = FIGURE_MIN_HEIGHT; + } else { + /* base width on valid height */ + chart->width = (chart->height * 3) / 2; + } + } else { + /* good width check height */ + if (chart->height < FIGURE_MIN_HEIGHT) { + /* base height on valid width */ + chart->height = (chart->width * 2) / 3; + } + } + + /* ensure legend type correct */ + if ((chart->key == CHART_KEY_UNSET) || + (chart->key >= CHART_KEY_END )) { + /* default to putting key on right */ + chart->key = CHART_KEY_RIGHT; + } + + return NSERROR_OK; +} + + +static nserror +output_pie_legend(struct fetch_about_context *ctx, struct chart_param *chart) +{ + nserror res; + unsigned int lblidx; + unsigned int legend_width; + unsigned int legend_height; + unsigned int vertical_spacing; + + switch (chart->key) { + + case CHART_KEY_NONE: + break; + case CHART_KEY_RIGHT: + legend_width = chart->width - chart->area.width - chart->area.x; + legend_width -= 10; /* margin */ + legend_height = chart->height; + vertical_spacing = legend_height / (chart->data.label_len + 1); + + for(lblidx = 0; lblidx < chart->data.label_len ; lblidx++) { + res = fetch_about_ssenddataf(ctx, + "", + chart->width - legend_width, + (vertical_spacing * lblidx) + (vertical_spacing/2), + vertical_spacing * 2 / 3, + vertical_spacing * 2 / 3, + chart->data.label[lblidx].colour); + if (res != NSERROR_OK) { + return res; + } + res = fetch_about_ssenddataf(ctx, + "%s", + chart->width - legend_width + vertical_spacing, + vertical_spacing * (lblidx+1), + chart->data.label[lblidx].colour, + chart->data.label[lblidx].title); + if (res != NSERROR_OK) { + return res; + } + } + break; + default: + break; + } + + return NSERROR_OK; +} + +static float +compute_series_total(struct chart_param *chart, unsigned int series) +{ + float total; + unsigned int curdata; + + for (total = 0, curdata = 0; + curdata < chart->data.series[series].len; + curdata++) { + total += chart->data.series[series].value[curdata]; + } + return total; +} + +/** + * render the data as a pie chart svg + */ +static bool +pie_chart(struct fetch_about_context *ctx, struct chart_param *chart) +{ + nserror res; + float ra; /* pie a radius */ + float rb; /* pie b radius */ + float series_total; + unsigned int curdata; /* current data point index */ + float last_x, last_y; + float end_x, end_y; + float start; + float extent; + bool large; + float circle_centre_x, circle_centre_y; + + /* ensure there is data to render */ + if ((chart->data.series_len < 1) || (chart->data.series[0].len < 2)) { + return NSERROR_BAD_PARAMETER; + } + + /* get the first series total value */ + series_total = compute_series_total(chart, 0); + if (series_total == 0) { + /* dividing by zero is embarasing */ + return NSERROR_BAD_PARAMETER; + } + + /* + * need to ensure the chart area is setup correctly + * + * this is left to each chart type as different charts + * have differnt requirements + */ + if ((chart->area.width == 0) || (chart->area.height == 0)) { + /* + * pie chart defaults to square of smaller of figure + * width and height + */ + if (chart->width > chart->height) { + chart->area.width = chart->area.height = (chart->height - chart->area.x); + } else { + chart->area.width = chart->area.height = (chart->width - chart->area.y); + } + } + + /* content is going to return ok */ + fetch_about_set_http_code(ctx, 200); + + /* content type */ + if (fetch_about_send_header(ctx, + "Content-Type: image/svg; charset=utf-8")) { + goto aborted; + } + + /* get the pie charts elipse radii */ + ra = chart->area.width / 2; + rb = chart->area.height / 2; + + /* get the offset to the circle centre */ + circle_centre_x = chart->area.x + ra; + circle_centre_y = chart->area.y + rb; + + + /* svg header */ + res = fetch_about_ssenddataf(ctx, + "\n", + chart->width, chart->height); + if (res != NSERROR_OK) { + goto aborted; + } + + /* generate the legend */ + res = output_pie_legend(ctx, chart); + if (res != NSERROR_OK) { + goto aborted; + } + + /* plot the arcs */ + start = -M_PI_2; + last_x = (ra * cos(start)); + last_y = (rb * sin(start)); + + /* iterate over each data point creating a slice o pie */ + for (curdata=0; curdata < chart->data.series[0].len; curdata++) { + extent = ((chart->data.series[0].value[curdata] / series_total) * 2 * M_PI); + end_x = (ra * cos(start + extent)); + end_y = (rb * sin(start + extent)); + + if (extent > M_PI) { + large = true; + } else { + large = false; + } + + res = fetch_about_ssenddataf( + ctx, + "\n", + circle_centre_x + last_x, + circle_centre_y + last_y, + ra, rb, large?1:0, + circle_centre_x + end_x, + circle_centre_y + end_y, + circle_centre_x, + circle_centre_y, + chart->data.label[curdata].colour); + if (res != NSERROR_OK) { + goto aborted; + } + last_x = end_x; + last_y = end_y; + start +=extent; + } + + res = fetch_about_ssenddataf(ctx, "\n"); + if (res != NSERROR_OK) { + goto aborted; + } + + fetch_about_send_finished(ctx); + + return true; + + aborted: + + return false; + +} + +/** + * Handler to generate about scheme chart page. + * + * generates an svg chart + * + * \param ctx The fetcher context. + * \return true if handled false if aborted. + */ +bool fetch_about_chart_handler(struct fetch_about_context *ctx) +{ + nserror res; + struct chart_param chart; + memset(&chart, 0, sizeof(struct chart_param)); + + res = chart_from_query(fetch_about_get_url(ctx), &chart); + if (res != NSERROR_OK) { + goto aborted; + } + + switch (chart.type) { + case CHART_TYPE_PIE: + return pie_chart(ctx, &chart); + + + default: + break; + } + +aborted: + + return false; + +} diff --git a/content/fetchers/about/chart.h b/content/fetchers/about/chart.h new file mode 100644 index 000000000..4eb7e7898 --- /dev/null +++ b/content/fetchers/about/chart.h @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Vincent Sanders + * + * This file is part of NetSurf. + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file + * about scheme chart handler interface + */ + +#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_CHART_H +#define NETSURF_CONTENT_FETCHERS_ABOUT_CHART_H + +/** + * Handler to generate about scheme chart page. + * + * generates an svg chart + * + * \param ctx The fetcher context. + * \return true if handled false if aborted. + */ +bool fetch_about_chart_handler(struct fetch_about_context *ctx); + +#endif diff --git a/content/fetchers/about/imagecache.c b/content/fetchers/about/imagecache.c index 5e2abcb1e..23d3ef41c 100644 --- a/content/fetchers/about/imagecache.c +++ b/content/fetchers/about/imagecache.c @@ -70,9 +70,23 @@ bool fetch_about_imagecache_handler(struct fetch_about_context *ctx) "

Peak size %f (in %g)

\n" "

Peak image count %h (size %i)

\n" "

Cache total/hit/miss/fail (counts) %j/%k/%l/%m " - "(%pj%%/%pk%%/%pl%%/%pm%%)

\n" + "(%pj%%/%pk%%/%pl%%/%pm%%)" + "" + "

\n"); + if (slen >= (int) (sizeof(buffer))) { + goto fetch_about_imagecache_handler_aborted; /* overflow */ + } + + res = fetch_about_senddata(ctx, (const uint8_t *)buffer, slen); + if (res != NSERROR_OK) { + goto fetch_about_imagecache_handler_aborted; + } + + /* image cache summary */ + slen = image_cache_snsummaryf(buffer, sizeof(buffer), "

Cache total/hit/miss/fail (size) %n/%o/%q/%r " - "(%pn%%/%po%%/%pq%%/%pr%%)

\n" + "(%pn%%/%po%%/%pq%%/%pr%%)" + "

\n" "

Total images never rendered: %s " "(includes %t that were converted)

\n" "

Total number of excessive conversions: %u " -- cgit v1.2.3