Gitweb links:

...log 
http://git.netsurf-browser.org/netsurf.git/shortlog/06c721c5bb6cd06fc35bd0a36a98415758501df8
...commit 
http://git.netsurf-browser.org/netsurf.git/commit/06c721c5bb6cd06fc35bd0a36a98415758501df8
...tree 
http://git.netsurf-browser.org/netsurf.git/tree/06c721c5bb6cd06fc35bd0a36a98415758501df8

The branch, master has been updated
       via  06c721c5bb6cd06fc35bd0a36a98415758501df8 (commit)
      from  32d52bf055dabb33e777a9ffa5ef918c4916adb9 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commitdiff 
http://git.netsurf-browser.org/netsurf.git/commit/?id=06c721c5bb6cd06fc35bd0a36a98415758501df8
commit 06c721c5bb6cd06fc35bd0a36a98415758501df8
Author: Vincent Sanders <vi...@kyllikki.org>
Commit: Vincent Sanders <vi...@kyllikki.org>

    add simple chart generator and use it from the imagecache

diff --git a/content/fetchers/about/Makefile b/content/fetchers/about/Makefile
index 10c9a6a..4f12a06 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 fe9eba7..6518942 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"
@@ -442,6 +443,14 @@ struct about_handlers about_handler_list[] = {
                true
        },
        {
+               /* chart generator */
+               "chart",
+               SLEN("chart"),
+               NULL,
+               fetch_about_chart_handler,
+               true
+       },
+       {
                "query/auth",
                SLEN("query/auth"),
                NULL,
diff --git a/content/fetchers/about/chart.c b/content/fetchers/about/chart.c
new file mode 100644
index 0000000..1565c54
--- /dev/null
+++ b/content/fetchers/about/chart.c
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2020 Vincent Sanders <vi...@netsurf-browser.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \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 <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+
+#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,
+                               "<rect  x=\"%d\" y=\"%d\" width=\"%d\" 
height=\"%d\" fill=\"#%06x\" />",
+                               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,
+                               "<text x=\"%d\" y=\"%d\" fill=\"#%06x\" 
>%s</text>",
+                               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,
+                       "<svg width=\"%u\" height=\"%u\" "
+                       "xmlns=\"http://www.w3.org/2000/svg\";>\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,
+                       "<path d=\"M %g %g\n"
+                       "A %g %g 0 %d 1 %g %g\n"
+                       "L %g %g Z\" fill=\"#%06x\" />\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, "</svg>\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 0000000..4eb7e78
--- /dev/null
+++ b/content/fetchers/about/chart.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 Vincent Sanders <vi...@netsurf-browser.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \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 5e2abcb..23d3ef4 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)
                "<p>Peak size %f (in %g)</p>\n"
                "<p>Peak image count %h (size %i)</p>\n"
                "<p>Cache total/hit/miss/fail (counts) %j/%k/%l/%m "
-                               "(%pj%%/%pk%%/%pl%%/%pm%%)</p>\n"
+               "(%pj%%/%pk%%/%pl%%/%pm%%)"
+               "<img width=200 height=100 
src=\"about:chart?type=pie&width=200&height=100&labels=hit,miss,fail&values=%k,%l,%m\"
 />"
+               "</p>\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),
                "<p>Cache total/hit/miss/fail (size) %n/%o/%q/%r "
-                               "(%pn%%/%po%%/%pq%%/%pr%%)</p>\n"
+                               "(%pn%%/%po%%/%pq%%/%pr%%)"
+               "<img width=200 height=100 
src=\"about:chart?type=pie&width=200&height=100&labels=hit,miss,fail&values=%o,%q,%r\"
 /></p>\n"
                "<p>Total images never rendered: %s "
                                "(includes %t that were converted)</p>\n"
                "<p>Total number of excessive conversions: %u "


-----------------------------------------------------------------------

Summary of changes:
 content/fetchers/about/Makefile             |    1 +
 content/fetchers/about/about.c              |    9 +
 content/fetchers/about/chart.c              |  628 +++++++++++++++++++++++++++
 content/fetchers/about/{blank.h => chart.h} |   12 +-
 content/fetchers/about/imagecache.c         |   18 +-
 5 files changed, 661 insertions(+), 7 deletions(-)
 create mode 100644 content/fetchers/about/chart.c
 copy content/fetchers/about/{blank.h => chart.h} (74%)

diff --git a/content/fetchers/about/Makefile b/content/fetchers/about/Makefile
index 10c9a6a..4f12a06 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 fe9eba7..6518942 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"
@@ -442,6 +443,14 @@ struct about_handlers about_handler_list[] = {
                true
        },
        {
+               /* chart generator */
+               "chart",
+               SLEN("chart"),
+               NULL,
+               fetch_about_chart_handler,
+               true
+       },
+       {
                "query/auth",
                SLEN("query/auth"),
                NULL,
diff --git a/content/fetchers/about/chart.c b/content/fetchers/about/chart.c
new file mode 100644
index 0000000..1565c54
--- /dev/null
+++ b/content/fetchers/about/chart.c
@@ -0,0 +1,628 @@
+/*
+ * Copyright 2020 Vincent Sanders <vi...@netsurf-browser.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \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 <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdio.h>
+
+#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,
+                               "<rect  x=\"%d\" y=\"%d\" width=\"%d\" 
height=\"%d\" fill=\"#%06x\" />",
+                               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,
+                               "<text x=\"%d\" y=\"%d\" fill=\"#%06x\" 
>%s</text>",
+                               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,
+                       "<svg width=\"%u\" height=\"%u\" "
+                       "xmlns=\"http://www.w3.org/2000/svg\";>\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,
+                       "<path d=\"M %g %g\n"
+                       "A %g %g 0 %d 1 %g %g\n"
+                       "L %g %g Z\" fill=\"#%06x\" />\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, "</svg>\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/blank.h b/content/fetchers/about/chart.h
similarity index 74%
copy from content/fetchers/about/blank.h
copy to content/fetchers/about/chart.h
index 09dcc1f..4eb7e78 100644
--- a/content/fetchers/about/blank.h
+++ b/content/fetchers/about/chart.h
@@ -18,18 +18,20 @@
 
 /**
  * \file
- * about scheme blank handler interface
+ * about scheme chart handler interface
  */
 
-#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_BLANK_H
-#define NETSURF_CONTENT_FETCHERS_ABOUT_BLANK_H
+#ifndef NETSURF_CONTENT_FETCHERS_ABOUT_CHART_H
+#define NETSURF_CONTENT_FETCHERS_ABOUT_CHART_H
 
 /**
- * Handler to generate about scheme blank page.
+ * 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_blank_handler(struct fetch_about_context *ctx);
+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 5e2abcb..23d3ef4 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)
                "<p>Peak size %f (in %g)</p>\n"
                "<p>Peak image count %h (size %i)</p>\n"
                "<p>Cache total/hit/miss/fail (counts) %j/%k/%l/%m "
-                               "(%pj%%/%pk%%/%pl%%/%pm%%)</p>\n"
+               "(%pj%%/%pk%%/%pl%%/%pm%%)"
+               "<img width=200 height=100 
src=\"about:chart?type=pie&width=200&height=100&labels=hit,miss,fail&values=%k,%l,%m\"
 />"
+               "</p>\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),
                "<p>Cache total/hit/miss/fail (size) %n/%o/%q/%r "
-                               "(%pn%%/%po%%/%pq%%/%pr%%)</p>\n"
+                               "(%pn%%/%po%%/%pq%%/%pr%%)"
+               "<img width=200 height=100 
src=\"about:chart?type=pie&width=200&height=100&labels=hit,miss,fail&values=%o,%q,%r\"
 /></p>\n"
                "<p>Total images never rendered: %s "
                                "(includes %t that were converted)</p>\n"
                "<p>Total number of excessive conversions: %u "


-- 
NetSurf Browser
_______________________________________________
netsurf-commits mailing list -- netsurf-commits@netsurf-browser.org
To unsubscribe send an email to netsurf-commits-le...@netsurf-browser.org

Reply via email to