From 9169bd3e776aa2f343f54726f92824f0ecf5485a Mon Sep 17 00:00:00 2001 From: Christian Felder Date: Wed, 12 Jun 2024 15:20:55 +0200 Subject: [PATCH 01/48] ffmpeg: update to 5.1.4 e.g. in use at epel-9 --- 3rdparty/ffmpeg/Makefile | 2 +- packaging/debian/debian.rules | 2 +- packaging/redhat/gr.spec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/3rdparty/ffmpeg/Makefile b/3rdparty/ffmpeg/Makefile index 4be3dc20e..b081a187b 100644 --- a/3rdparty/ffmpeg/Makefile +++ b/3rdparty/ffmpeg/Makefile @@ -2,7 +2,7 @@ ifeq ($(strip $(PREFIX)),) override PREFIX = $(abspath $(CURDIR)/../build) endif -VERSION = 4.2.1 +VERSION = 5.1.4 FFMPEG_EXTRA_CONFIGURE_FLAGS ?= ifeq ($(shell uname),Darwin) FFMPEG_EXTRA_CONFIGURE_FLAGS += --extra-cflags=-mmacosx-version-min=10.15 diff --git a/packaging/debian/debian.rules b/packaging/debian/debian.rules index a847e034f..ea55c33e8 100644 --- a/packaging/debian/debian.rules +++ b/packaging/debian/debian.rules @@ -25,7 +25,7 @@ override_dh_auto_configure: cp ../SOURCES/libogg-1.3.2.tar.gz ${THIRDPARTY_SRC} cp ../SOURCES/libtheora-1.1.1.tar.gz ${THIRDPARTY_SRC} cp ../SOURCES/libvpx-1.4.0.tar.bz2 ${THIRDPARTY_SRC} - cp ../SOURCES/ffmpeg-4.2.1.tar.gz ${THIRDPARTY_SRC} + cp ../SOURCES/ffmpeg-5.1.4.tar.gz ${THIRDPARTY_SRC} cp ../SOURCES/glfw-3.3.3.tar.gz ${THIRDPARTY_SRC} cp ../SOURCES/zeromq-4.3.4.tar.gz ${THIRDPARTY_SRC} cp ../SOURCES/cairo-1.16.0.tar.xz ${THIRDPARTY_SRC} diff --git a/packaging/redhat/gr.spec b/packaging/redhat/gr.spec index 2190e6f54..0bc25dc9e 100644 --- a/packaging/redhat/gr.spec +++ b/packaging/redhat/gr.spec @@ -24,7 +24,7 @@ Source: gr-%{fixedversion}.tar%{?compression:.%{compression}} Source1: https://gr-framework.org/downloads/3rdparty/libogg-1.3.2.tar.gz Source2: https://gr-framework.org/downloads/3rdparty/libtheora-1.1.1.tar.gz Source3: https://gr-framework.org/downloads/3rdparty/libvpx-1.4.0.tar.bz2 -Source4: https://gr-framework.org/downloads/3rdparty/ffmpeg-4.2.1.tar.gz +Source4: https://ffmpeg.org/releases/ffmpeg-5.1.4.tar.gz Source5: https://gr-framework.org/downloads/3rdparty/glfw-3.3.3.tar.gz Source6: https://gr-framework.org/downloads/3rdparty/zeromq-4.3.4.tar.gz Source7: https://gr-framework.org/downloads/3rdparty/cmake-3.6.3-Linux-x86_64.tar.gz From 88bae5521223019179a8b71f738777300fe10ce0 Mon Sep 17 00:00:00 2001 From: Christian Felder Date: Wed, 12 Jun 2024 16:40:05 +0200 Subject: [PATCH 02/48] Update write_pixeldensity patch Writes pixeldensity metdata for HiDPI videos in QT --- 3rdparty/ffmpeg/mov_pixeldensity.patch | 188 +++++++++++++------------ 1 file changed, 97 insertions(+), 91 deletions(-) diff --git a/3rdparty/ffmpeg/mov_pixeldensity.patch b/3rdparty/ffmpeg/mov_pixeldensity.patch index 241062526..e48ddfcb7 100644 --- a/3rdparty/ffmpeg/mov_pixeldensity.patch +++ b/3rdparty/ffmpeg/mov_pixeldensity.patch @@ -1,91 +1,97 @@ -diff --git a/movenc.c b/movenc.c -index a961390..c5eb12d 100644 ---- a/movenc.c -+++ b/movenc.c -@@ -76,6 +76,7 @@ static const AVOption options[] = { - { "delay_moov", "Delay writing the initial moov until the first fragment is cut, or until the first fragment flush", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_DELAY_MOOV}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, - { "global_sidx", "Write a global sidx index at the start of the file", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_GLOBAL_SIDX}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, - { "skip_sidx", "Skip writing of sidx atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SKIP_SIDX}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, -+ { "write_pixeldensity", "Write pixeldensity metdata for HiDPI videos in QT", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_PIXELDENSITY}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, - { "write_colr", "Write colr atom (Experimental, may be renamed or changed, do not use from scripts)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_COLR}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, - { "write_gama", "Write deprecated gama atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_GAMA}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, - { "use_metadata_tags", "Use mdta atom for metadata.", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_USE_MDTA}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, -@@ -3118,6 +3119,56 @@ static int mov_write_track_udta_tag(AVIOContext *pb, MOVMuxContext *mov, - return 0; - } - -+static int mov_write_pixeldensity_meta_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track, AVFormatContext *s) -+{ -+ int size = 0; -+ int64_t pos = avio_tell(pb); -+ avio_wb32(pb, 0); /* meta atom size */ -+ ffio_wfourcc(pb, "meta"); -+ -+ /* Metadata atom information as described in -+ * https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW9 -+ */ -+ avio_wb32(pb, 33); /* hdlr atom size: size (4) + 'hdlr' (4) + version/flags (4) + predefined (4) + 'mdta' (4) + -+ reserved (3*4) + name (1) = 33 */ -+ ffio_wfourcc(pb, "hdlr"); -+ avio_wb32(pb, 0); /* version (1 Byte) and flags (3 Bytes), must be zero */ -+ avio_wb32(pb, 0); /* "predefined", must be zero */ -+ ffio_wfourcc(pb, "mdta"); -+ avio_wb32(pb, 0); /* Reseverved, uint32_t[3] */ -+ avio_wb32(pb, 0); -+ avio_wb32(pb, 0); -+ avio_w8(pb, 0); /* Empty name (NULL-terminated) */ -+ -+ avio_wb32(pb, 56); /* keys atom size: size (4) + 'keys' (4) + version/flasgs (4) + entry_count (4) + -+ keys (32+8) = 56 */ -+ ffio_wfourcc(pb, "keys"); -+ avio_wb32(pb, 0); /* version (1 Byte) and flags (3 Bytes), must be zero */ -+ avio_wb32(pb, 1); /* entry count */ -+ avio_wb32(pb, 32 + 8); /* key size: size (4) + 'mdta' (4) + strlen(key) */ -+ ffio_wfourcc(pb, "mdta"); -+ avio_write(pb, "com.apple.quicktime.pixeldensity", 32); -+ -+ avio_wb32(pb, 48); /* ilst atom size: size (4) + 'ilst' (4) + value atom size (40) = 48 */ -+ ffio_wfourcc(pb, "ilst"); -+ avio_wb32(pb, 40); /* value atom size: size (4) + key (4) + data atom (32) = 40 */ -+ avio_wb32(pb, 1); /* metadata key index */ -+ -+ avio_wb32(pb, 32); /* data atom size: size (4) + 'data' (4) + data_type (4) + locale (4) + value (4 * 4) = 32 */ -+ ffio_wfourcc(pb, "data"); -+ avio_wb32(pb, 0x1e); /* data type */ -+ avio_wb32(pb, 0); /* locale */ -+ -+ /* actual data (value): consisting of 4 uint32_t: pixel width, pixel height, display width, display height */ -+ avio_wb32(pb, track->par->width); -+ avio_wb32(pb, track->par->height); -+ avio_wb32(pb, track->par->width / 2); -+ avio_wb32(pb, track->par->height / 2); -+ -+ size = update_size(pb, pos); -+ return size; -+} -+ - static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, - MOVTrack *track, AVStream *st) - { -@@ -3165,6 +3216,9 @@ static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext - mov_write_tapt_tag(pb, track); - } - } -+ if (mov->flags & FF_MOV_FLAG_PIXELDENSITY) { -+ mov_write_pixeldensity_meta_tag(pb, mov, track, st); -+ } - mov_write_track_udta_tag(pb, mov, st); - track->entry = entry_backup; - track->chunkCount = chunk_backup; -diff --git a/movenc.h b/movenc.h -index 68d6f23..ed7ea41 100644 ---- a/movenc.h -+++ b/movenc.h -@@ -258,6 +258,7 @@ typedef struct MOVMuxContext { - #define FF_MOV_FLAG_NEGATIVE_CTS_OFFSETS (1 << 19) - #define FF_MOV_FLAG_FRAG_EVERY_FRAME (1 << 20) - #define FF_MOV_FLAG_SKIP_SIDX (1 << 21) -+#define FF_MOV_FLAG_PIXELDENSITY (1 << 22) - - int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt); - +diff --color -crB a/movenc.c b/movenc.c +*** a/movenc.c Fri Nov 10 00:38:51 2023 +--- b/movenc.c Wed Jun 12 15:36:00 2024 +*************** +*** 89,94 **** +--- 89,95 ---- + { "global_sidx", "Write a global sidx index at the start of the file", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_GLOBAL_SIDX}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "skip_sidx", "Skip writing of sidx atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SKIP_SIDX}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "write_colr", "Write colr atom even if the color info is unspecified (Experimental, may be renamed or changed, do not use from scripts)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_COLR}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, ++ { "write_pixeldensity", "Write pixeldensity metdata for HiDPI videos in QT", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_PIXELDENSITY}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "prefer_icc", "If writing colr atom prioritise usage of ICC profile if it exists in stream packet side data", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_PREFER_ICC}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "write_gama", "Write deprecated gama atom", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_WRITE_GAMA}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, + { "use_metadata_tags", "Use mdta atom for metadata.", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_USE_MDTA}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" }, +*************** +*** 3638,3643 **** +--- 3639,3694 ---- + return 0; + } + ++ static int mov_write_pixeldensity_meta_tag(AVIOContext *pb, MOVMuxContext *mov, MOVTrack *track, AVFormatContext *s) ++ { ++ int size = 0; ++ int64_t pos = avio_tell(pb); ++ avio_wb32(pb, 0); /* meta atom size */ ++ ffio_wfourcc(pb, "meta"); ++ ++ /* Metadata atom information as described in ++ * https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW9 ++ */ ++ avio_wb32(pb, 33); /* hdlr atom size: size (4) + 'hdlr' (4) + version/flags (4) + predefined (4) + 'mdta' (4) + ++ reserved (3*4) + name (1) = 33 */ ++ ffio_wfourcc(pb, "hdlr"); ++ avio_wb32(pb, 0); /* version (1 Byte) and flags (3 Bytes), must be zero */ ++ avio_wb32(pb, 0); /* "predefined", must be zero */ ++ ffio_wfourcc(pb, "mdta"); ++ avio_wb32(pb, 0); /* Reseverved, uint32_t[3] */ ++ avio_wb32(pb, 0); ++ avio_wb32(pb, 0); ++ avio_w8(pb, 0); /* Empty name (NULL-terminated) */ ++ ++ avio_wb32(pb, 56); /* keys atom size: size (4) + 'keys' (4) + version/flasgs (4) + entry_count (4) + ++ keys (32+8) = 56 */ ++ ffio_wfourcc(pb, "keys"); ++ avio_wb32(pb, 0); /* version (1 Byte) and flags (3 Bytes), must be zero */ ++ avio_wb32(pb, 1); /* entry count */ ++ avio_wb32(pb, 32 + 8); /* key size: size (4) + 'mdta' (4) + strlen(key) */ ++ ffio_wfourcc(pb, "mdta"); ++ avio_write(pb, "com.apple.quicktime.pixeldensity", 32); ++ ++ avio_wb32(pb, 48); /* ilst atom size: size (4) + 'ilst' (4) + value atom size (40) = 48 */ ++ ffio_wfourcc(pb, "ilst"); ++ avio_wb32(pb, 40); /* value atom size: size (4) + key (4) + data atom (32) = 40 */ ++ avio_wb32(pb, 1); /* metadata key index */ ++ ++ avio_wb32(pb, 32); /* data atom size: size (4) + 'data' (4) + data_type (4) + locale (4) + value (4 * 4) = 32 */ ++ ffio_wfourcc(pb, "data"); ++ avio_wb32(pb, 0x1e); /* data type */ ++ avio_wb32(pb, 0); /* locale */ ++ ++ /* actual data (value): consisting of 4 uint32_t: pixel width, pixel height, display width, display height */ ++ avio_wb32(pb, track->par->width); ++ avio_wb32(pb, track->par->height); ++ avio_wb32(pb, track->par->width / 2); ++ avio_wb32(pb, track->par->height / 2); ++ ++ size = update_size(pb, pos); ++ return size; ++ } ++ + static int mov_write_trak_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext *mov, + MOVTrack *track, AVStream *st) + { +*************** +*** 3684,3689 **** +--- 3735,3743 ---- + if (is_clcp_track(track) && st->sample_aspect_ratio.num) { + mov_write_tapt_tag(pb, track); + } ++ } ++ if (mov->flags & FF_MOV_FLAG_PIXELDENSITY) { ++ mov_write_pixeldensity_meta_tag(pb, mov, track, st); + } + mov_write_track_udta_tag(pb, mov, st); + track->entry = entry_backup; +diff --color -crB a/movenc.h b/movenc.h +*** a/movenc.h Wed Jun 12 15:40:11 2024 +--- b/movenc.h Wed Jun 12 15:36:54 2024 +*************** +*** 275,280 **** +--- 275,281 ---- + #define FF_MOV_FLAG_SKIP_SIDX (1 << 21) + #define FF_MOV_FLAG_CMAF (1 << 22) + #define FF_MOV_FLAG_PREFER_ICC (1 << 23) ++ #define FF_MOV_FLAG_PIXELDENSITY (1 << 24) + + int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt); + From 081ce44d61417365e4f3f110ad648384e92cd95d Mon Sep 17 00:00:00 2001 From: Christian Felder Date: Wed, 12 Jun 2024 17:09:28 +0200 Subject: [PATCH 03/48] ffmpeg/Makefile: download from ffmpeg website --- 3rdparty/ffmpeg/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/ffmpeg/Makefile b/3rdparty/ffmpeg/Makefile index b081a187b..ea5f9b0fe 100644 --- a/3rdparty/ffmpeg/Makefile +++ b/3rdparty/ffmpeg/Makefile @@ -37,7 +37,7 @@ default: install $(PREFIX)/src/ffmpeg-$(VERSION).tar.gz: mkdir -p $(PREFIX)/src - cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://gr-framework.org/downloads/3rdparty/ffmpeg-$(VERSION).tar.gz + cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://ffmpeg.org/releases/ffmpeg-$(VERSION).tar.gz $(PREFIX)/src/ffmpeg-$(VERSION)/configure: $(PREFIX)/src/ffmpeg-$(VERSION).tar.gz cd $(PREFIX)/src/ && tar -xf ffmpeg-$(VERSION).tar.gz From b0b7004e159e694d8bf86637edf3aca0aaa9d6a0 Mon Sep 17 00:00:00 2001 From: Daniel Kaiser Date: Wed, 19 Jun 2024 06:41:09 +0000 Subject: [PATCH 04/48] Apply 2 suggestion(s) to 2 file(s) --- 3rdparty/ffmpeg/Makefile | 2 +- packaging/redhat/gr.spec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/3rdparty/ffmpeg/Makefile b/3rdparty/ffmpeg/Makefile index ea5f9b0fe..b081a187b 100644 --- a/3rdparty/ffmpeg/Makefile +++ b/3rdparty/ffmpeg/Makefile @@ -37,7 +37,7 @@ default: install $(PREFIX)/src/ffmpeg-$(VERSION).tar.gz: mkdir -p $(PREFIX)/src - cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://ffmpeg.org/releases/ffmpeg-$(VERSION).tar.gz + cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://gr-framework.org/downloads/3rdparty/ffmpeg-$(VERSION).tar.gz $(PREFIX)/src/ffmpeg-$(VERSION)/configure: $(PREFIX)/src/ffmpeg-$(VERSION).tar.gz cd $(PREFIX)/src/ && tar -xf ffmpeg-$(VERSION).tar.gz diff --git a/packaging/redhat/gr.spec b/packaging/redhat/gr.spec index 0bc25dc9e..47fc7475e 100644 --- a/packaging/redhat/gr.spec +++ b/packaging/redhat/gr.spec @@ -24,7 +24,7 @@ Source: gr-%{fixedversion}.tar%{?compression:.%{compression}} Source1: https://gr-framework.org/downloads/3rdparty/libogg-1.3.2.tar.gz Source2: https://gr-framework.org/downloads/3rdparty/libtheora-1.1.1.tar.gz Source3: https://gr-framework.org/downloads/3rdparty/libvpx-1.4.0.tar.bz2 -Source4: https://ffmpeg.org/releases/ffmpeg-5.1.4.tar.gz +Source4: https://gr-framework.org/downloads/3rdparty/ffmpeg-5.1.4.tar.gz Source5: https://gr-framework.org/downloads/3rdparty/glfw-3.3.3.tar.gz Source6: https://gr-framework.org/downloads/3rdparty/zeromq-4.3.4.tar.gz Source7: https://gr-framework.org/downloads/3rdparty/cmake-3.6.3-Linux-x86_64.tar.gz From 31789892d7f3f376a4639c9111c4684ae6743092 Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Wed, 19 Jun 2024 15:45:40 +0200 Subject: [PATCH 05/48] GR: change default tick value --- lib/gr/gr.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gr/gr.c b/lib/gr/gr.c index eb460cf49..7fbef0360 100644 --- a/lib/gr/gr.c +++ b/lib/gr/gr.c @@ -5428,8 +5428,7 @@ void gr_axis(char which, axis_t *axis) } else { - if (is_nan(axis->tick)) axis->tick = gr_tick(axis->min, axis->max); - if (axis->major_count > 0) axis->tick /= axis->major_count; + if (is_nan(axis->tick)) axis->tick = gr_tick(axis->min, axis->max) / 5; axis->num_ticks = (int)((axis->max - axis->min) / axis->tick + 0.5) + 1; axis->ticks = (double *)xcalloc(axis->num_ticks, sizeof(tick_t)); if (axis->major_count > 0) From 0842951d4cce5ecaf3644286e81d5f41d9c0f3f6 Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Tue, 25 Jun 2024 19:10:24 +0200 Subject: [PATCH 06/48] GR: change drawing order of axis elements --- lib/gr/gr.c | 70 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/lib/gr/gr.c b/lib/gr/gr.c index 7fbef0360..28f8d466d 100644 --- a/lib/gr/gr.c +++ b/lib/gr/gr.c @@ -5514,24 +5514,12 @@ void gr_drawaxis(char which, axis_t *axis) tick = axis->tick_size * (wn[3] - wn[2]) / (vp[3] - vp[2]); minor_tick = y_log(y_lin(axis->position) + tick); major_tick = y_log(y_lin(axis->position) + 2 * tick); - if (axis->draw_axis_line) - { - start_pline(axis->min, axis->position); - pline(axis->max, axis->position); - end_pline(); - } } else { tick = axis->tick_size * (wn[1] - wn[0]) / (vp[1] - vp[0]); minor_tick = x_log(x_lin(axis->position) + tick); major_tick = x_log(x_lin(axis->position) + 2 * tick); - if (axis->draw_axis_line) - { - start_pline(axis->position, axis->min); - pline(axis->position, axis->max); - end_pline(); - } } for (i = 0; i < axis->num_ticks; i++) @@ -5551,6 +5539,22 @@ void gr_drawaxis(char which, axis_t *axis) } } + if (axis->draw_axis_line) + { + if (which == 'X') + { + start_pline(axis->min, axis->position); + pline(axis->max, axis->position); + end_pline(); + } + else + { + start_pline(axis->position, axis->min); + pline(axis->position, axis->max); + end_pline(); + } + } + if (axis->major_count > 0) { if (axis->num_tick_labels > 0) @@ -5595,7 +5599,7 @@ static void draw_axis_grid(char which, axis_t *axis) { int errind, tnr, color; double wn[4], vp[4], width, epsilon; - int i; + int pass, i; /* inquire current normalization transformation */ @@ -5611,26 +5615,32 @@ static void draw_axis_grid(char which, axis_t *axis) epsilon = FEPS * (axis->max - axis->min); - for (i = 0; i < axis->num_ticks; i++) + for (pass = 0; pass <= 1; pass++) { - if (color != 0) - gks_set_pline_color_index(axis->ticks[i].is_major ? 88 : 90); - else - gks_set_pline_linewidth(axis->ticks[i].is_major ? 2.0 : 1.0); - - if (fabs(axis->ticks[i].value - axis->org) > epsilon) + for (i = 0; i < axis->num_ticks; i++) { - if (which == 'X') + if (axis->ticks[i].is_major == pass) { - pline(axis->ticks[i].value, wn[2]); - pline(axis->ticks[i].value, wn[3]); - end_pline(); - } - else - { - pline(wn[0], axis->ticks[i].value); - pline(wn[1], axis->ticks[i].value); - end_pline(); + if (color != 0) + gks_set_pline_color_index(axis->ticks[i].is_major ? 88 : 90); + else + gks_set_pline_linewidth(axis->ticks[i].is_major ? 2.0 : 1.0); + + if (fabs(axis->ticks[i].value - axis->org) > epsilon) + { + if (which == 'X') + { + pline(axis->ticks[i].value, wn[2]); + pline(axis->ticks[i].value, wn[3]); + end_pline(); + } + else + { + pline(wn[0], axis->ticks[i].value); + pline(wn[1], axis->ticks[i].value); + end_pline(); + } + } } } } From 653436b26eb4c1d1bc876f2668901e710f873768 Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Thu, 27 Jun 2024 10:01:23 +0200 Subject: [PATCH 07/48] GR: change drawing order in axes/grid functions --- lib/gr/gr.c | 286 +++++++++++++++++++++++++--------------------------- 1 file changed, 138 insertions(+), 148 deletions(-) diff --git a/lib/gr/gr.c b/lib/gr/gr.c index 28f8d466d..104ec5b51 100644 --- a/lib/gr/gr.c +++ b/lib/gr/gr.c @@ -5029,12 +5029,8 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma /* draw Y-axis */ - start_pline(x_org, y_min); - while (yi <= y_max) { - pline(x_org, yi); - xi = minor_tick; if (i == 0) { @@ -5058,8 +5054,9 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma if (i == 0 || abs(major_y) == 1) { + start_pline(x_org, yi); pline(xi, yi); - pline(x_org, yi); + end_pline(); } if (i == 9 || lx.basey < 10) @@ -5073,10 +5070,6 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma yi = y0 + i * y0; } - - if (yi > y_max) pline(x_org, y_max); - - end_pline(); } else { @@ -5089,14 +5082,10 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma /* draw Y-axis */ - start_pline(x_org, y_min); - gr_getformat(&format_reference, y_org, yi, y_max, y_tick, major_y); while (yi <= y_max + feps) { - pline(x_org, yi); - if (major_y != 0) { if (i % major_y == 0) @@ -5113,17 +5102,18 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma else xi = major_tick; + start_pline(x_org, yi); pline(xi, yi); - pline(x_org, yi); + end_pline(); i++; yi = i * y_tick; } - - if (yi > y_max + feps) pline(x_org, y_max); - - end_pline(); } + + start_pline(x_org, y_min); + pline(x_org, y_max); + end_pline(); } if (x_tick != 0) @@ -5159,12 +5149,8 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma /* draw X-axis */ - start_pline(x_min, y_org); - while (xi <= x_max) { - pline(xi, y_org); - yi = minor_tick; if (i == 0) { @@ -5188,8 +5174,9 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma if (i == 0 || abs(major_x) == 1) { + start_pline(xi, y_org); pline(xi, yi); - pline(xi, y_org); + end_pline(); } if (i == 9 || lx.basex < 10) @@ -5203,10 +5190,6 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma xi = x0 + i * x0; } - - if (xi > x_max) pline(x_max, y_org); - - end_pline(); } else { @@ -5221,12 +5204,8 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma /* draw X-axis */ - start_pline(x_min, y_org); - while (xi <= x_max + feps) { - pline(xi, y_org); - if (major_x != 0) { if (i % major_x == 0) @@ -5243,17 +5222,18 @@ void gr_axeslbl(double x_tick, double y_tick, double x_org, double y_org, int ma else yi = major_tick; + start_pline(xi, y_org); pline(xi, yi); - pline(xi, y_org); + end_pline(); i++; xi = i * x_tick; } - - if (xi > x_max + feps) pline(x_max, y_org); - - end_pline(); } + + start_pline(x_min, y_org); + pline(x_max, y_org); + end_pline(); } /* restore linetype, text alignment, character-up vector @@ -5595,11 +5575,11 @@ void gr_drawaxis(char which, axis_t *axis) gks_set_clipping(clsw); } -static void draw_axis_grid(char which, axis_t *axis) +static void draw_axis_grid(char which, axis_t *axis, int pass) { int errind, tnr, color; double wn[4], vp[4], width, epsilon; - int pass, i; + int i; /* inquire current normalization transformation */ @@ -5615,31 +5595,28 @@ static void draw_axis_grid(char which, axis_t *axis) epsilon = FEPS * (axis->max - axis->min); - for (pass = 0; pass <= 1; pass++) + for (i = 0; i < axis->num_ticks; i++) { - for (i = 0; i < axis->num_ticks; i++) + if (axis->ticks[i].is_major == pass) { - if (axis->ticks[i].is_major == pass) + if (color != 0) + gks_set_pline_color_index(axis->ticks[i].is_major ? 88 : 90); + else + gks_set_pline_linewidth(axis->ticks[i].is_major ? 2.0 : 1.0); + + if (fabs(axis->ticks[i].value - axis->org) > epsilon) { - if (color != 0) - gks_set_pline_color_index(axis->ticks[i].is_major ? 88 : 90); + if (which == 'X') + { + pline(axis->ticks[i].value, wn[2]); + pline(axis->ticks[i].value, wn[3]); + end_pline(); + } else - gks_set_pline_linewidth(axis->ticks[i].is_major ? 2.0 : 1.0); - - if (fabs(axis->ticks[i].value - axis->org) > epsilon) { - if (which == 'X') - { - pline(axis->ticks[i].value, wn[2]); - pline(axis->ticks[i].value, wn[3]); - end_pline(); - } - else - { - pline(wn[0], axis->ticks[i].value); - pline(wn[1], axis->ticks[i].value); - end_pline(); - } + pline(wn[0], axis->ticks[i].value); + pline(wn[1], axis->ticks[i].value); + end_pline(); } } } @@ -5656,7 +5633,7 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) int errind, tnr, ltype, clsw; double wn[4], vp[4], clrt[4]; double tick, minor_tick, major_tick; - int i; + int i, pass; double epsilon; check_autoinit; @@ -5676,27 +5653,20 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) if ((options & GR_AXES_WITH_GRID) != 0) { - if (x_axis != NULL) draw_axis_grid('X', x_axis); - if (y_axis != NULL) draw_axis_grid('Y', y_axis); + for (pass = 0; pass <= 1; pass++) + { + if (y_axis != NULL) draw_axis_grid('Y', y_axis, pass); + if (x_axis != NULL) draw_axis_grid('X', x_axis, pass); + } } - if (x_axis != NULL) gr_drawaxis('X', x_axis); if (y_axis != NULL) gr_drawaxis('Y', y_axis); + if (x_axis != NULL) gr_drawaxis('X', x_axis); if ((options & GR_AXES_WITH_FRAME) != 0) { axis_t axis; - if (x_axis != NULL) - { - memcpy(&axis, x_axis, sizeof(axis_t)); - x_axis->position = wn[3]; - x_axis->tick_size = -x_axis->tick_size; - x_axis->major_count = -x_axis->major_count; - gr_drawaxis('X', x_axis); - memcpy(x_axis, &axis, sizeof(axis_t)); - } - if (y_axis != NULL) { memcpy(&axis, y_axis, sizeof(axis_t)); @@ -5706,6 +5676,16 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) gr_drawaxis('Y', y_axis); memcpy(y_axis, &axis, sizeof(axis_t)); } + + if (x_axis != NULL) + { + memcpy(&axis, x_axis, sizeof(axis_t)); + x_axis->position = wn[3]; + x_axis->tick_size = -x_axis->tick_size; + x_axis->major_count = -x_axis->major_count; + gr_drawaxis('X', x_axis); + memcpy(x_axis, &axis, sizeof(axis_t)); + } } /* restore linetype and clipping indicator */ @@ -5774,6 +5754,7 @@ void gr_grid(double x_tick, double y_tick, double x_org, double y_org, int major double x0, y0, xi, yi; int64_t i; + int pass; if (x_tick < 0 || y_tick < 0) { @@ -5803,114 +5784,123 @@ void gr_grid(double x_tick, double y_tick, double x_org, double y_org, int major gks_set_pline_linetype(GKS_K_LINETYPE_SOLID); gks_set_clipping(GKS_K_NOCLIP); - if (y_tick != 0) + for (pass = 0; pass <= 1; pass++) { - if (GR_OPTION_Y_LOG & lx.scale_options) + if (y_tick != 0) { - y0 = pow(lx.basey, gauss(blog(lx.basey, y_min))); + if (GR_OPTION_Y_LOG & lx.scale_options) + { + y0 = pow(lx.basey, gauss(blog(lx.basey, y_min))); - i = ipred(y_min / y0); - yi = y0 + i * y0; + i = ipred(y_min / y0); + yi = y0 + i * y0; - /* draw horizontal grid lines */ + /* draw horizontal grid lines */ - while (yi <= y_max) - { - if (i == 0 || major_y == 1) + while (yi <= y_max) { - major = i == 0; - if (yi != y_min) grid_line(x_min, yi, x_max, yi, color, major); - } + if (i == 0 || major_y == 1) + { + major = i == 0 ? 1 : 0; + if (yi != y_min) + { + if (pass == major) grid_line(x_min, yi, x_max, yi, color, major); + } + } - if (i == 9 || lx.basey < 10) - { - y0 = y0 * lx.basey; - i = 0; - } - else - i++; + if (i == 9 || lx.basey < 10) + { + y0 = y0 * lx.basey; + i = 0; + } + else + i++; - yi = y0 + i * y0; + yi = y0 + i * y0; + } } - } - else - { - feps = FEPS * (y_max - y_min); + else + { + feps = FEPS * (y_max - y_min); - check_tick_marks(y_min, y_max, y_tick, 'Y'); + check_tick_marks(y_min, y_max, y_tick, 'Y'); - i = isucc((y_min - y_org) / y_tick); - yi = y_org + i * y_tick; + i = isucc((y_min - y_org) / y_tick); + yi = y_org + i * y_tick; - /* draw horizontal grid lines */ + /* draw horizontal grid lines */ - while (yi <= y_max + feps) - { - if (major_y > 0) - major = i % major_y == 0 && major_y > 1; - else - major = 0; + while (yi <= y_max + feps) + { + if (major_y > 0) + major = (i % major_y == 0 && major_y > 1) ? 1 : 0; + else + major = 0; - grid_line(x_min, yi, x_max, yi, color, major); + if (pass == major) grid_line(x_min, yi, x_max, yi, color, major); - i++; - yi = y_org + i * y_tick; + i++; + yi = y_org + i * y_tick; + } } } - } - if (x_tick != 0) - { - if (GR_OPTION_X_LOG & lx.scale_options) + if (x_tick != 0) { - x0 = pow(lx.basex, gauss(blog(lx.basex, x_min))); + if (GR_OPTION_X_LOG & lx.scale_options) + { + x0 = pow(lx.basex, gauss(blog(lx.basex, x_min))); - i = ipred(x_min / x0); - xi = x0 + i * x0; + i = ipred(x_min / x0); + xi = x0 + i * x0; - /* draw vertical grid lines */ + /* draw vertical grid lines */ - while (xi <= x_max) - { - if (i == 0 || major_x == 1) + while (xi <= x_max) { - major = i == 0; - if (xi != x_min) grid_line(xi, y_min, xi, y_max, color, major); - } + if (i == 0 || major_x == 1) + { + major = i == 0 ? 1 : 0; + if (xi != x_min) + { + if (pass == major) grid_line(xi, y_min, xi, y_max, color, major); + } + } - if (i == 9 || lx.basex < 10) - { - x0 = x0 * lx.basex; - i = 0; - } - else - i++; + if (i == 9 || lx.basex < 10) + { + x0 = x0 * lx.basex; + i = 0; + } + else + i++; - xi = x0 + i * x0; + xi = x0 + i * x0; + } } - } - else - { - feps = FEPS * (x_max - x_min); + else + { + feps = FEPS * (x_max - x_min); - check_tick_marks(x_min, x_max, x_tick, 'X'); + check_tick_marks(x_min, x_max, x_tick, 'X'); - i = isucc((x_min - x_org) / x_tick); - xi = x_org + i * x_tick; + i = isucc((x_min - x_org) / x_tick); + xi = x_org + i * x_tick; - /* draw vertical grid lines */ + /* draw vertical grid lines */ - while (xi <= x_max + feps) - { - if (major_x > 0) - major = i % major_x == 0 && major_x > 1; - else - major = 0; + while (xi <= x_max + feps) + { + if (major_x > 0) + major = (i % major_x == 0 && major_x > 1) ? 1 : 0; + else + major = 0; - grid_line(xi, y_min, xi, y_max, color, major); + if (pass == major) grid_line(xi, y_min, xi, y_max, color, major); - i++; - xi = x_org + i * x_tick; + i++; + xi = x_org + i * x_tick; + } } } } From ea4db4a940655e23b142b1bf261c83b5d6c32596 Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Tue, 2 Jul 2024 07:34:39 +0200 Subject: [PATCH 08/48] GR: don't skip grid line at origin --- lib/gr/gr.c | 54 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/lib/gr/gr.c b/lib/gr/gr.c index 104ec5b51..9597b6a8f 100644 --- a/lib/gr/gr.c +++ b/lib/gr/gr.c @@ -5502,20 +5502,23 @@ void gr_drawaxis(char which, axis_t *axis) major_tick = x_log(x_lin(axis->position) + 2 * tick); } - for (i = 0; i < axis->num_ticks; i++) + if (axis->tick_size != 0) { - tick = axis->ticks[i].is_major ? major_tick : minor_tick; - if (which == 'X') - { - pline(axis->ticks[i].value, axis->position); - pline(axis->ticks[i].value, tick); - end_pline(); - } - else + for (i = 0; i < axis->num_ticks; i++) { - pline(axis->position, axis->ticks[i].value); - pline(tick, axis->ticks[i].value); - end_pline(); + tick = axis->ticks[i].is_major ? major_tick : minor_tick; + if (which == 'X') + { + pline(axis->ticks[i].value, axis->position); + pline(axis->ticks[i].value, tick); + end_pline(); + } + else + { + pline(axis->position, axis->ticks[i].value); + pline(tick, axis->ticks[i].value); + end_pline(); + } } } @@ -5578,7 +5581,7 @@ void gr_drawaxis(char which, axis_t *axis) static void draw_axis_grid(char which, axis_t *axis, int pass) { int errind, tnr, color; - double wn[4], vp[4], width, epsilon; + double wn[4], vp[4], width; int i; /* inquire current normalization transformation */ @@ -5593,8 +5596,6 @@ static void draw_axis_grid(char which, axis_t *axis, int pass) gks_inq_pline_color_index(&errind, &color); - epsilon = FEPS * (axis->max - axis->min); - for (i = 0; i < axis->num_ticks; i++) { if (axis->ticks[i].is_major == pass) @@ -5604,20 +5605,17 @@ static void draw_axis_grid(char which, axis_t *axis, int pass) else gks_set_pline_linewidth(axis->ticks[i].is_major ? 2.0 : 1.0); - if (fabs(axis->ticks[i].value - axis->org) > epsilon) + if (which == 'X') { - if (which == 'X') - { - pline(axis->ticks[i].value, wn[2]); - pline(axis->ticks[i].value, wn[3]); - end_pline(); - } - else - { - pline(wn[0], axis->ticks[i].value); - pline(wn[1], axis->ticks[i].value); - end_pline(); - } + pline(axis->ticks[i].value, wn[2]); + pline(axis->ticks[i].value, wn[3]); + end_pline(); + } + else + { + pline(wn[0], axis->ticks[i].value); + pline(wn[1], axis->ticks[i].value); + end_pline(); } } } From c48988b0c7975f509a427b8e45d39bded35a98ca Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Mon, 8 Jul 2024 15:26:16 +0200 Subject: [PATCH 09/48] GR: change drawing order for tick marks --- lib/gr/gr.c | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/lib/gr/gr.c b/lib/gr/gr.c index 9597b6a8f..f7ed72ff1 100644 --- a/lib/gr/gr.c +++ b/lib/gr/gr.c @@ -5471,7 +5471,7 @@ void gr_drawaxis(char which, axis_t *axis) int errind, tnr, ltype, clsw, halign, valign; double wn[4], vp[4], clrt[4]; double tick, minor_tick, major_tick; - int i; + int i, pass; double epsilon; check_autoinit; @@ -5504,20 +5504,26 @@ void gr_drawaxis(char which, axis_t *axis) if (axis->tick_size != 0) { - for (i = 0; i < axis->num_ticks; i++) + for (pass = 0; pass <= 1; pass++) { - tick = axis->ticks[i].is_major ? major_tick : minor_tick; - if (which == 'X') + for (i = 0; i < axis->num_ticks; i++) { - pline(axis->ticks[i].value, axis->position); - pline(axis->ticks[i].value, tick); - end_pline(); - } - else - { - pline(axis->position, axis->ticks[i].value); - pline(tick, axis->ticks[i].value); - end_pline(); + if (pass != axis->ticks[i].is_major) + { + tick = axis->ticks[i].is_major ? major_tick : minor_tick; + if (which == 'X') + { + pline(axis->ticks[i].value, axis->position); + pline(axis->ticks[i].value, tick); + end_pline(); + } + else + { + pline(axis->position, axis->ticks[i].value); + pline(tick, axis->ticks[i].value); + end_pline(); + } + } } } } @@ -5633,6 +5639,7 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) double tick, minor_tick, major_tick; int i, pass; double epsilon; + axis_t axis; check_autoinit; @@ -5658,13 +5665,8 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) } } - if (y_axis != NULL) gr_drawaxis('Y', y_axis); - if (x_axis != NULL) gr_drawaxis('X', x_axis); - if ((options & GR_AXES_WITH_FRAME) != 0) { - axis_t axis; - if (y_axis != NULL) { memcpy(&axis, y_axis, sizeof(axis_t)); @@ -5674,7 +5676,11 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) gr_drawaxis('Y', y_axis); memcpy(y_axis, &axis, sizeof(axis_t)); } + } + if (y_axis != NULL) gr_drawaxis('Y', y_axis); + if ((options & GR_AXES_WITH_FRAME) != 0) + { if (x_axis != NULL) { memcpy(&axis, x_axis, sizeof(axis_t)); @@ -5685,6 +5691,7 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) memcpy(x_axis, &axis, sizeof(axis_t)); } } + if (x_axis != NULL) gr_drawaxis('X', x_axis); /* restore linetype and clipping indicator */ From a3e7594074911b3d287579c0e0b8860b2c15ea45 Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Wed, 10 Jul 2024 07:05:50 -0700 Subject: [PATCH 10/48] GR: allow drawing of individual axes --- lib/gr/gr.c | 14 ++++++++++---- lib/gr/gr.h | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/gr/gr.c b/lib/gr/gr.c index f7ed72ff1..63c8c56ed 100644 --- a/lib/gr/gr.c +++ b/lib/gr/gr.c @@ -5665,7 +5665,7 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) } } - if ((options & GR_AXES_WITH_FRAME) != 0) + if ((options & GR_AXES_TWIN_AXES) != 0) { if (y_axis != NULL) { @@ -5677,9 +5677,12 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) memcpy(y_axis, &axis, sizeof(axis_t)); } } - if (y_axis != NULL) gr_drawaxis('Y', y_axis); + if ((options & GR_AXES_SIMPLE_AXES) != 0) + { + if (y_axis != NULL) gr_drawaxis('Y', y_axis); + } - if ((options & GR_AXES_WITH_FRAME) != 0) + if ((options & GR_AXES_TWIN_AXES) != 0) { if (x_axis != NULL) { @@ -5691,7 +5694,10 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) memcpy(x_axis, &axis, sizeof(axis_t)); } } - if (x_axis != NULL) gr_drawaxis('X', x_axis); + if ((options & GR_AXES_SIMPLE_AXES) != 0) + { + if (x_axis != NULL) gr_drawaxis('X', x_axis); + } /* restore linetype and clipping indicator */ diff --git a/lib/gr/gr.h b/lib/gr/gr.h index 962ffa95e..a435d6ff2 100644 --- a/lib/gr/gr.h +++ b/lib/gr/gr.h @@ -208,8 +208,9 @@ typedef struct int draw_axis_line; } axis_t; -#define GR_AXES_WITH_GRID (1 << 0) -#define GR_AXES_WITH_FRAME (1 << 1) +#define GR_AXES_SIMPLE_AXES (1 << 0) +#define GR_AXES_TWIN_AXES (1 << 1) +#define GR_AXES_WITH_GRID (1 << 2) DLLEXPORT void gr_initgr(void); DLLEXPORT int gr_debug(void); From 1a5b3f96ef944c1868fdf9174117ca64aed99118 Mon Sep 17 00:00:00 2001 From: Verbov Date: Wed, 10 Jul 2024 14:33:41 +0200 Subject: [PATCH 11/48] fixed flip for new axes --- lib/gr/gr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gr/gr.c b/lib/gr/gr.c index f7ed72ff1..f8309b45d 100644 --- a/lib/gr/gr.c +++ b/lib/gr/gr.c @@ -5671,6 +5671,7 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) { memcpy(&axis, y_axis, sizeof(axis_t)); y_axis->position = wn[1]; + if (lx.scale_options & GR_OPTION_FLIP_X) y_axis->position = wn[0]; y_axis->tick_size = -y_axis->tick_size; y_axis->major_count = -y_axis->major_count; gr_drawaxis('Y', y_axis); @@ -5685,6 +5686,7 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) { memcpy(&axis, x_axis, sizeof(axis_t)); x_axis->position = wn[3]; + if (lx.scale_options & GR_OPTION_FLIP_Y) x_axis->position = wn[2]; x_axis->tick_size = -x_axis->tick_size; x_axis->major_count = -x_axis->major_count; gr_drawaxis('X', x_axis); From f7d339024ffad3e3d96dcc66011d436a88c1e723 Mon Sep 17 00:00:00 2001 From: Verbov Date: Tue, 11 Jun 2024 10:01:22 +0200 Subject: [PATCH 12/48] use new GR functions for more editable axes, fixed quiver log, fixed flip for colorbar, changed the position of central_region inside the tree after swap to marginal_heatmap and back - fixes wrong colorbar labels, improved some part of the updateFilter code, some function name clearup, added new specification if ticks should get flipped cause of a specific series or if gridlines can be removed for barplot series, keep orientation when kind is updated, adjustments for barplot - no unneeded gridline for axis, if style is default and there are more than 20 bars lower the amount of major ticks, no adjust_x_lim for barplot, overworked font precision and font routine, fixed text color is set to 0 after update, added first version of conditions for label breaks or convert into scientific format, added scientific format option, small code cleanup --- lib/grm/include/grm/dom_render/render.hxx | 25 +- .../grm/dom_render/graphics_tree/schema.xsd | 115 +- lib/grm/src/grm/dom_render/render.cxx | 2410 ++++++++++------- lib/grm/src/grm/interaction.cxx | 5 +- lib/grm/src/grm/plot.cxx | 66 +- lib/grm/src/grm/utilcpp.cxx | 14 +- lib/grm/src/grm/utilcpp_int.hxx | 4 +- 7 files changed, 1511 insertions(+), 1128 deletions(-) diff --git a/lib/grm/include/grm/dom_render/render.hxx b/lib/grm/include/grm/dom_render/render.hxx index ba535e322..699d608a4 100644 --- a/lib/grm/include/grm/dom_render/render.hxx +++ b/lib/grm/include/grm/dom_render/render.hxx @@ -82,6 +82,8 @@ #define PLOT_DEFAULT_AXES_TICK_SIZE 0.0075 #define DEFAULT_ASPECT_RATIO_FOR_SCALING 4.0 / 3.0 #define PLOT_DEFAULT_ONLY_QUADRATIC_ASPECT_RATIO 0 +#define MIRRORED_AXIS_DEFAULT 1 +#define SCIENTIFIC_FORMAT_OPTION 2 /* ~~~~~~~~~~~~~~~~~~~~~~~~~ util ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -242,14 +244,21 @@ public: const std::shared_ptr &extContext = nullptr, const std::shared_ptr &extElement = nullptr); - std::shared_ptr createAxes(double x_tick, double y_tick, double x_org, double y_org, int x_major, - int y_major, int tick_orientation, + std::shared_ptr createEmptyAxis(const std::shared_ptr &ext_element = nullptr); + + std::shared_ptr createAxis(double min_val, double max_val, double tick, double org, double pos, + int major_count, int num_ticks, int num_tick_labels, double tick_size, + int tick_orientation, double label_pos, const std::shared_ptr &ext_element = nullptr); - std::shared_ptr createEmptyAxes(int tick_orientation, - const std::shared_ptr &ext_element = nullptr); + std::shared_ptr createTickGroup(int is_major, std::string tick_label, double value, double width, + const std::shared_ptr &ext_element = nullptr); + + std::shared_ptr createTick(int is_major, double value, + const std::shared_ptr &ext_element = nullptr); - std::shared_ptr createEmptyDoubleAxes(); + std::shared_ptr createGridLine(int is_major, double value, + const std::shared_ptr &ext_element = nullptr); std::shared_ptr createLegend(const std::string &labels_key, std::optional> labels, const std::string &specs_key, std::optional> specs, @@ -275,12 +284,6 @@ public: const double linewidth = -1, const std::string &text = "", const std::shared_ptr &extElement = nullptr); - std::shared_ptr createGrid(double x_tick, double y_tick, double x_org, double y_org, int major_x, - int major_y, const std::shared_ptr &extElement = nullptr); - - std::shared_ptr createEmptyGrid(bool x_grid, bool y_grid, - const std::shared_ptr &extElement = nullptr); - std::shared_ptr createSeries(const std::string &name); std::shared_ptr createDrawImage(double xmin, double ymin, double xmax, double ymax, int width, int height, diff --git a/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd b/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd index 0ca4d9264..458f96af5 100644 --- a/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd +++ b/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd @@ -295,15 +295,12 @@ - - + - - @@ -326,18 +323,6 @@ - - - - - - - - - - - - @@ -353,57 +338,89 @@ - - + + - + + - - + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + - - + + - - - - - - - + + + + - - + + - - - - - - - + + + + + + + @@ -420,6 +437,7 @@ + @@ -574,6 +592,7 @@ + @@ -629,7 +648,7 @@ - + diff --git a/lib/grm/src/grm/dom_render/render.cxx b/lib/grm/src/grm/dom_render/render.cxx index 800b319c1..e78ed0dea 100644 --- a/lib/grm/src/grm/dom_render/render.cxx +++ b/lib/grm/src/grm/dom_render/render.cxx @@ -67,8 +67,8 @@ ManageCustomColorIndex custom_color_index_manager; //! This vector is used for storing element types which children get processed. Other types' children will be ignored static std::set parent_types = { - "axes", "axes_text_group", + "axis", "bar", "central_region", "colorbar", @@ -119,35 +119,22 @@ static std::set parent_types = { "side_region", "side_plot_region", "text_region", - "x_tick_label_group", - "y_tick_label_group", + "tick_group", }; static std::set drawable_types = { - "axes", - "axes_3d", - "cellarray", - "draw_arc", - "draw_graphics", - "draw_image", - "draw_rect", - "fill_arc", - "fill_area", - "fill_rect", - "grid", - "grid_3d", - "isosurface_render", - "layout_grid", - "layout_grid_element", - "legend", - "nonuniformcellarray", - "panzoom", - "polyline", - "polyline_3d", - "polymarker", - "polymarker_3d", - "text", - "titles_3d", + "axes_3d", "cellarray", + "draw_arc", "draw_graphics", + "draw_image", "draw_rect", + "fill_arc", "fill_area", + "fill_rect", "grid", + "grid_3d", "isosurface_render", + "layout_grid", "layout_grid_element", + "legend", "nonuniformcellarray", + "panzoom", "polyline", + "polyline_3d", "polymarker", + "polymarker_3d", "text", + "tick", "titles_3d", }; static std::set drawable_kinds = { @@ -197,10 +184,8 @@ static std::set valid_context_keys = {"absolute_downwards", "weights", "x", "xi", - "x_tick_labels", "y", "y_labels", - "y_tick_labels", "z", "z_dims"}; @@ -623,7 +608,7 @@ static void clearOldChildren(del_values *del, const std::shared_ptr coordinate_system_children = {"x_tick_label_group", "y_tick_label_group", "titles_3d"}; + std::vector coordinate_system_children = {"titles_3d"}; for (const auto &child : element->children()) { if (element->localName() == "coordinate_system" && @@ -691,7 +676,7 @@ static void legendSize(const std::shared_ptr &element, double *w, if (auto render = std::dynamic_pointer_cast(element->ownerDocument())) { auto context = render->getContext(); - std::string key = static_cast(element->getAttribute("labels")); + auto key = static_cast(element->getAttribute("labels")); labels = GRM::get>((*context)[key]); } @@ -713,7 +698,7 @@ static void legendSize(const std::vector &labels, double *w, double if (!labels.empty()) { - for (std::string current_label : labels) + for (auto current_label : labels) { gr_inqtext(0, 0, current_label.data(), tbx, tby); *w = grm_max(*w, tbx[2] - tbx[0]); @@ -747,6 +732,7 @@ static void sidePlotMargin(const std::shared_ptr side_region, doub static void capSidePlotMarginInNonKeepAspectRatio(const std::shared_ptr side_region, double *margin, std::string kind) { + // TODO: Overwork max value workaround if (side_region->querySelectors("side_plot_region")) { if (str_equals_any(kind, "surface", "volume", "trisurface")) @@ -876,7 +862,6 @@ static void calculateCentralRegionMarginOrDiagFactor(const std::shared_ptr &element) if (element->hasAttribute("width") && !element->hasAttribute("marginal_heatmap_side_plot")) width = static_cast(element->getAttribute("width")); - // TODO: Change this later when other elements than texts can be inside the side_regions which isn't displayed by - // the if condition - if (!element->hasAttribute("marginal_heatmap_side_plot") && element->querySelectors("colorbar") == nullptr) + // side_region only has a text_region child - no offset or width is needed + if (element->querySelectors("side_plot_region") == nullptr && + !element->hasAttribute("marginal_heatmap_side_plot")) { offset = 0.0; width = 0.0; @@ -1578,20 +1563,21 @@ static void applyMoveTransformation(const std::shared_ptr &element bool disable_x_trans = false, disable_y_trans = false, any_xform = false; // only for wc std::shared_ptr parent_element = element->parentElement(); std::string post_fix = "_wc"; - std::vector ndc_transformation_elems = {"figure", - "plot", - "colorbar", - "labels_group", - "titles_3d", - "text", - "y_tick_label_group", - "x_tick_label_group", - "layout_grid_element", - "layout_grid", - "central_region", - "side_region", - "marginal_heatmap_plot", - "legend"}; + std::vector ndc_transformation_elems = { + "figure", + "plot", + "colorbar", + "labels_group", + "titles_3d", + "text", + "layout_grid_element", + "layout_grid", + "central_region", + "side_region", + "marginal_heatmap_plot", + "legend", + "axis", + }; if (std::find(ndc_transformation_elems.begin(), ndc_transformation_elems.end(), element->localName()) != ndc_transformation_elems.end()) @@ -1846,8 +1832,9 @@ PushDrawableToZQueue::PushDrawableToZQueue( void PushDrawableToZQueue::operator()(const std::shared_ptr &element, const std::shared_ptr &context) { - auto parent = element->parentElement(); int context_id; + auto parent = element->parentElement(); + if (auto search = parent_to_context.find(parent); search != parent_to_context.end()) { context_id = search->second; @@ -1865,7 +1852,7 @@ void PushDrawableToZQueue::operator()(const std::shared_ptr &eleme z_queue.push(drawable); } -static double auto_tick(double amin, double amax) +static double autoTick(double amin, double amax) { double tick_size[] = {5.0, 2.0, 1.0, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01}; double scale, tick; @@ -1886,7 +1873,7 @@ static double auto_tick(double amin, double amax) return tick; } -static double auto_tick_rings_polar(double rmax, int &rings, const std::string &norm) +static double autoTickRingsPolar(double rmax, int &rings, const std::string &norm) { double scale; bool is_decimal = false; @@ -1924,10 +1911,7 @@ static double auto_tick_rings_polar(double rmax, int &rings, const std::string & { if (static_cast(rmax) % i == 0) { - if (is_decimal) - { - rmax = rmax / pow(10.0, scale); - } + if (is_decimal) rmax = rmax / pow(10.0, scale); rings = i; return rmax / rings; } @@ -1938,10 +1922,7 @@ static double auto_tick_rings_polar(double rmax, int &rings, const std::string & } // given rings - if (norm == "cdf") - { - return 1.0 / rings; - } + if (norm == "cdf") return 1.0 / rings; if (rmax > rings) { @@ -1965,6 +1946,18 @@ static double auto_tick_rings_polar(double rmax, int &rings, const std::string & return rmax / rings; } +static void clearAxisAttributes(const std::shared_ptr &axis) +{ + if (axis->hasAttribute("min_value")) axis->removeAttribute("min_value"); + if (axis->hasAttribute("max_value")) axis->removeAttribute("max_value"); + if (axis->hasAttribute("org")) axis->removeAttribute("org"); + if (axis->hasAttribute("pos")) axis->removeAttribute("pos"); + if (axis->hasAttribute("tick")) axis->removeAttribute("tick"); + if (axis->hasAttribute("major_count")) axis->removeAttribute("major_count"); + if (axis->hasAttribute("tick_size")) axis->removeAttribute("tick_size"); + if (axis->hasAttribute("tick_orientation")) axis->removeAttribute("tick_orientation"); +} + /*! * Convert an RGB triple to a luminance value following the CCIR 601 format. * @@ -1973,7 +1966,7 @@ static double auto_tick_rings_polar(double rmax, int &rings, const std::string & * \param[in] b The blue component of the RGB triple in the range [0.0, 1.0]. * \return The luminance of the given RGB triple in the range [0.0, 1.0]. */ -static double get_lightness_from_rbg(double r, double g, double b) +static double getLightnessFromRGB(double r, double g, double b) { return 0.299 * r + 0.587 * g + 0.114 * b; } @@ -1981,16 +1974,13 @@ static double get_lightness_from_rbg(double r, double g, double b) /* * mixes gr color maps with size = size * size. If x and or y < 0 */ -void create_colormap(int x, int y, int size, std::vector &colormap) +void createColormap(int x, int y, int size, std::vector &colormap) { int r, g, b, a; int outer, inner; int r1, g1, b1; int r2, g2, b2; - if (x > 47 || y > 47) - { - logger((stderr, "values for the keyword \"colormap\" can not be greater than 47\n")); - } + if (x > 47 || y > 47) logger((stderr, "values for the keyword \"colormap\" can not be greater than 47\n")); colormap.resize(size * size); if (x >= 0 && y < 0) @@ -2027,10 +2017,7 @@ void create_colormap(int x, int y, int size, std::vector &colormap) } else if ((x >= 0 && y >= 0) || (x < 0 && y < 0)) { - if (x < 0 && y < 0) - { - x = y = 0; - } + if (x < 0 && y < 0) x = y = 0; gr_setcolormap(x); for (outer = 0; outer < size; outer++) { @@ -2066,12 +2053,12 @@ static void markerHelper(const std::shared_ptr &element, const std */ std::vector type, color_ind; std::vector size; - + std::string x_key, y_key, z_key; + int skip_color_ind = -1000; auto parent = element->parentElement(); bool group = parent_types.count(parent->localName()); - int skip_color_ind = -1000; - auto attr = element->getAttribute("marker_types"); + if (attr.isString()) { type = GRM::get>((*context)[static_cast(attr)]); @@ -2113,21 +2100,14 @@ static void markerHelper(const std::shared_ptr &element, const std } } - auto x = static_cast(element->getAttribute("x")); - auto y = static_cast(element->getAttribute("y")); - std::string z; - if (element->hasAttribute("z")) - { - z = static_cast(element->getAttribute("z")); - } + x_key = static_cast(element->getAttribute("x")); + y_key = static_cast(element->getAttribute("y")); + if (element->hasAttribute("z")) z_key = static_cast(element->getAttribute("z")); - std::vector x_vec = GRM::get>((*context)[x]); - std::vector y_vec = GRM::get>((*context)[y]); + std::vector x_vec = GRM::get>((*context)[x_key]); + std::vector y_vec = GRM::get>((*context)[y_key]); std::vector z_vec; - if (auto z_ptr = GRM::get_if>((*context)[z])) - { - z_vec = *z_ptr; - } + if (auto z_ptr = GRM::get_if>((*context)[z_key])) z_vec = *z_ptr; auto n = std::min((int)x_vec.size(), (int)y_vec.size()); @@ -2202,7 +2182,7 @@ static void lineHelper(const std::shared_ptr &element, const std:: */ std::vector type, color_ind; std::vector width; - std::string z; + std::string x_key, y_key, z_key; auto parent = element->parentElement(); bool group = parent_types.count(parent->localName()); @@ -2249,21 +2229,15 @@ static void lineHelper(const std::shared_ptr &element, const std:: } } - auto x = static_cast(element->getAttribute("x")); - auto y = static_cast(element->getAttribute("y")); - if (element->hasAttribute("z")) - { - z = static_cast(element->getAttribute("z")); - } + x_key = static_cast(element->getAttribute("x")); + y_key = static_cast(element->getAttribute("y")); + if (element->hasAttribute("z")) z_key = static_cast(element->getAttribute("z")); - std::vector x_vec = GRM::get>((*context)[x]); - std::vector y_vec = GRM::get>((*context)[y]); + std::vector x_vec = GRM::get>((*context)[x_key]); + std::vector y_vec = GRM::get>((*context)[y_key]); std::vector z_vec; - if (auto z_ptr = GRM::get_if>((*context)[z])) - { - z_vec = *z_ptr; - } + if (auto z_ptr = GRM::get_if>((*context)[z_key])) z_vec = *z_ptr; auto n = std::min((int)x_vec.size(), (int)y_vec.size()); for (int i = 0; i < n; ++i) @@ -2336,7 +2310,7 @@ static std::shared_ptr getSubplotElement(const std::shared_ptr &element, double &tick_size) { - if (element->hasAttribute("tick_size")) + if (element->hasAttribute("tick_size") && element->parentElement()->localName() == "colorbar") { double plot_viewport[2], tick_size_rel; double metric_width, metric_height; @@ -2481,7 +2455,6 @@ static void getAxesInformation(const std::shared_ptr &element, con int major_count; std::shared_ptr central_region, central_region_parent; - auto draw_axes_group = element->parentElement(); auto subplot_element = getSubplotElement(element); auto kind = static_cast(subplot_element->getAttribute("kind")); @@ -2516,13 +2489,26 @@ static void getAxesInformation(const std::shared_ptr &element, con } else { - if (draw_axes_group->hasAttribute("x_tick_labels")) - { - x_major = 0; - } - else if (kind == "barplot") + if (kind == "barplot") { - x_major = 1; + bool problematic_bar_num = false; + auto context = global_render->getContext(); + auto barplots = central_region->querySelectorsAll("series_barplot"); + for (const auto &barplot : barplots) + { + if (!barplot->hasAttribute("style") || + static_cast(barplot->getAttribute("style")) == "default") + { + auto y_key = static_cast(barplot->getAttribute("y")); + std::vector y_vec = GRM::get>((*context)[y_key]); + if (size(y_vec) > 20) + { + problematic_bar_num = true; + break; + } + } + } + x_major = problematic_bar_num ? major_count : 1; } else { @@ -2553,7 +2539,7 @@ static void getAxesInformation(const std::shared_ptr &element, con { if (x_major != 0) { - x_tick = auto_tick(xmin, xmax) / x_major; + x_tick = autoTick(xmin, xmax) / x_major; } else { @@ -2613,14 +2599,7 @@ static void getAxesInformation(const std::shared_ptr &element, con } else { - if (draw_axes_group->hasAttribute("y_tick_labels")) - { - y_major = 0; - } - else - { - y_major = major_count; - } + y_major = major_count; element->setAttribute("y_major", y_major); } } @@ -2641,7 +2620,7 @@ static void getAxesInformation(const std::shared_ptr &element, con { if (y_major != 0) { - y_tick = auto_tick(ymin, ymax) / y_major; + y_tick = autoTick(ymin, ymax) / y_major; } else { @@ -2748,7 +2727,7 @@ static void getAxes3dInformation(const std::shared_ptr &element, c { if (z_major != 0) { - z_tick = auto_tick(zmin, zmax) / z_major; + z_tick = autoTick(zmin, zmax) / z_major; } else { @@ -2794,227 +2773,6 @@ static void getAxes3dInformation(const std::shared_ptr &element, c } } -/*! - * Draw an x tick label at the specified position while trying to stay in the available space. - * Every space character (' ') is seen as a possible position to break the label into the next line. - * The label will not break into the next line when enough space is available. - * If a label or a part of it does not fit in the available space but doesnt have a space character to break it up - * it will get drawn anyway. - * - * \param[in] x The X coordinate of starting position of the label. - * \param[in] y The Y coordinate of starting position of the label. - * \param[in] label The label to be drawn. - * \param[in] available_width The available space in X direction around the starting position. - */ -void drawXTickLabel(double x, double y, const char *label, double available_width, - const std::shared_ptr &element, bool init, int child_id) -{ - char new_label[256]; - int breakpoint_positions[128]; - int cur_num_breakpoints = 0; - int i = 0; - int cur_start = 0; - double tbx[4], tby[4]; - double width; - double char_height; - del_values del = del_values::update_without_default; - std::shared_ptr text; - - gr_inqcharheight(&char_height); - - del = del_values(static_cast(element->getAttribute("_delete_children"))); - - for (i = 0; i == 0 || label[i - 1] != '\0'; ++i) - { - if (label[i] == ' ' || label[i] == '\0') - { - /* calculate width of the next part of the label to be drawn */ - new_label[i] = '\0'; - gr_inqtext(x, y, new_label + cur_start, tbx, tby); - gr_wctondc(&tbx[0], &tby[0]); - gr_wctondc(&tbx[1], &tby[1]); - width = tbx[1] - tbx[0]; - new_label[i] = ' '; - - /* add possible breakpoint */ - breakpoint_positions[cur_num_breakpoints++] = i; - - if (width > available_width) - { - /* part is too big but doesn't have a breakpoint in it */ - if (cur_num_breakpoints == 1) - { - new_label[i] = '\0'; - } - else /* part is too big and has breakpoints in it */ - { - /* break label at last breakpoint that still allowed the text to fit */ - new_label[breakpoint_positions[cur_num_breakpoints - 2]] = '\0'; - } - - if ((del != del_values::update_without_default && del != del_values::update_with_default) || init) - { - text = global_render->createText(x, y, new_label + cur_start); - element->append(text); - text->setAttribute("_child_id", child_id++); - } - else - { - text = element->querySelectors("text[_child_id=" + std::to_string(child_id++) + "]"); - if (text != nullptr) - global_render->createText(x, y, new_label + cur_start, CoordinateSpace::NDC, text); - } - if (text != nullptr) text->setAttribute("name", "x_tick"); - - if (cur_num_breakpoints == 1) - { - cur_start = i + 1; - cur_num_breakpoints = 0; - } - else - { - cur_start = breakpoint_positions[cur_num_breakpoints - 2] + 1; - breakpoint_positions[0] = breakpoint_positions[cur_num_breakpoints - 1]; - cur_num_breakpoints = 1; - } - y -= char_height * 1.5; - } - } - else - { - new_label[i] = label[i]; - } - } - - /* 0-terminate the new label */ - new_label[i] = '\0'; - - /* draw the rest */ - if ((del != del_values::update_without_default && del != del_values::update_with_default) || init) - { - text = global_render->createText(x, y, std::string(new_label + cur_start)); - element->append(text); - text->setAttribute("_child_id", child_id++); - } - else - { - text = element->querySelectors("text[_child_id=" + std::to_string(child_id++) + "]"); - if (text != nullptr) - global_render->createText(x, y, std::string(new_label + cur_start), CoordinateSpace::NDC, text); - } - if (text != nullptr) text->setAttribute("name", "x_tick"); -} - -/*! - * Draw an y_tick_label at the specified position while trying to stay in the available space. - * Every space character (' ') is seen as a possible position to break the label into the next line. - * The label will not break into the next line when enough space is available. - * If a label or a part of it does not fit in the available space but doesnt have a space character to break it up - * it will get drawn anyway. - * - * \param[in] x The X coordinate of starting position of the label. - * \param[in] y The Y coordinate of starting position of the label. - * \param[in] label The label to be drawn. - * \param[in] available_height The available space in y-direction around the starting position. - */ -void drawYTickLabel(double x, double y, const char *label, double available_height, - const std::shared_ptr &element, bool init, int child_id) -{ - char new_label[256]; - int breakpoint_positions[128]; - int cur_num_breakpoints = 0; - int i = 0; - int cur_start = 0; - double tbx[4], tby[4]; - double height; - double char_height; - del_values del = del_values::update_without_default; - std::shared_ptr text; - - gr_inqcharheight(&char_height); - - del = del_values(static_cast(element->getAttribute("_delete_children"))); - - for (i = 0; i == 0 || label[i - 1] != '\0'; ++i) - { - if (label[i] == ' ' || label[i] == '\0') - { - /* calculate width of the next part of the label to be drawn */ - new_label[i] = '\0'; - gr_inqtext(x, y, new_label + cur_start, tbx, tby); - gr_wctondc(&tbx[0], &tby[0]); - gr_wctondc(&tbx[1], &tby[1]); - height = tby[1] - tby[0]; - new_label[i] = ' '; - - /* add possible breakpoint */ - breakpoint_positions[cur_num_breakpoints++] = i; - - if (height > available_height) - { - /* part is too big but doesn't have a breakpoint in it */ - if (cur_num_breakpoints == 1) - { - new_label[i] = '\0'; - } - else /* part is too big and has breakpoints in it */ - { - /* break label at last breakpoint that still allowed the text to fit */ - new_label[breakpoint_positions[cur_num_breakpoints - 2]] = '\0'; - } - - if ((del != del_values::update_without_default && del != del_values::update_with_default) || init) - { - text = global_render->createText(x, y, new_label + cur_start); - element->append(text); - text->setAttribute("_child_id", child_id++); - } - else - { - text = element->querySelectors("text[_child_id=" + std::to_string(child_id++) + "]"); - if (text != nullptr) - global_render->createText(x, y, new_label + cur_start, CoordinateSpace::NDC, text); - } - if (text != nullptr) text->setAttribute("name", "y_tick"); - - if (cur_num_breakpoints == 1) - { - cur_start = i + 1; - cur_num_breakpoints = 0; - } - else - { - cur_start = breakpoint_positions[cur_num_breakpoints - 2] + 1; - breakpoint_positions[0] = breakpoint_positions[cur_num_breakpoints - 1]; - cur_num_breakpoints = 1; - } - } - } - else - { - new_label[i] = label[i]; - } - } - - /* 0-terminate the new label */ - new_label[i] = '\0'; - - /* draw the rest */ - if ((del != del_values::update_without_default && del != del_values::update_with_default) || init) - { - text = global_render->createText(x, y, std::string(new_label + cur_start)); - element->append(text); - text->setAttribute("_child_id", child_id++); - } - else - { - text = element->querySelectors("text[_child_id=" + std::to_string(child_id++) + "]"); - if (text != nullptr) - global_render->createText(x, y, std::string(new_label + cur_start), CoordinateSpace::NDC, text); - } - if (text != nullptr) text->setAttribute("name", "y_tick"); -} - void GRM::Render::getFigureSize(int *pixel_width, int *pixel_height, double *metric_width, double *metric_height) { double display_metric_width, display_metric_height; @@ -3602,39 +3360,21 @@ static void processFlip(const std::shared_ptr &element) bool y_flip = static_cast(element->getAttribute("y_flip")); gr_inqscale(&options); - if (element->localName() == "colorbar") + if (x_flip) { - if (x_flip) - { - options = (options | GR_OPTION_FLIP_Y) & ~GR_OPTION_FLIP_X; - } - else if (y_flip) - { - options = options & ~GR_OPTION_FLIP_Y & ~GR_OPTION_FLIP_X; - } - else - { - options = options & ~GR_OPTION_FLIP_X; - } + options = options | GR_OPTION_FLIP_X; } else { - if (x_flip) - { - options = options | GR_OPTION_FLIP_X; - } - else - { - options = options & ~GR_OPTION_FLIP_X; - } - if (y_flip) - { - options = options | GR_OPTION_FLIP_Y; - } - else - { - options = options & ~GR_OPTION_FLIP_Y; - } + options = options & ~GR_OPTION_FLIP_X; + } + if (y_flip) + { + options = options | GR_OPTION_FLIP_Y; + } + else + { + options = options & ~GR_OPTION_FLIP_Y; } gr_setscale(options); } @@ -3673,19 +3413,11 @@ std::string fontPrecisionIntToString(int font_precision) static void processFont(const std::shared_ptr &element) { - int font, font_precision; + int font = PLOT_DEFAULT_FONT, font_precision = PLOT_DEFAULT_FONT_PRECISION; /* `font` and `font_precision` are always set */ if (element->hasAttribute("font_precision")) { - if (element->getAttribute("font").isInt()) - { - font = static_cast(element->getAttribute("font")); - } - else if (element->getAttribute("font").isString()) - { - font = fontStringToInt(static_cast(element->getAttribute("font"))); - } if (element->getAttribute("font_precision").isInt()) { font_precision = static_cast(element->getAttribute("font_precision")); @@ -3694,13 +3426,28 @@ static void processFont(const std::shared_ptr &element) { font_precision = fontPrecisionStringToInt(static_cast(element->getAttribute("font_precision"))); } - logger((stderr, "Using font: %d with precision %d\n", font, font_precision)); - gr_settextfontprec(font, font_precision); } else { - if (element->hasAttribute("font")) logger((stderr, "Font precision is missing\n")); + logger((stderr, "Use default font precision\n")); + } + if (element->hasAttribute("font")) + { + if (element->getAttribute("font").isInt()) + { + font = static_cast(element->getAttribute("font")); + } + else if (element->getAttribute("font").isString()) + { + font = fontStringToInt(static_cast(element->getAttribute("font"))); + } + } + else + { + logger((stderr, "Use default font\n")); } + logger((stderr, "Using font: %d with precision %d\n", font, font_precision)); + gr_settextfontprec(font, font_precision); /* TODO: Implement other datatypes for `font` and `font_precision` */ } @@ -4076,9 +3823,9 @@ static void processMarginalHeatmapKind(const std::shared_ptr &elem } else { - for (const auto &childchild : series->children()) + for (const auto &child : series->children()) { - if (childchild->localName() == "polyline") + if (child->localName() == "polyline") { std::string x_key = "x" + id_str; std::string y_key = "y" + id_str; @@ -4092,27 +3839,27 @@ static void processMarginalHeatmapKind(const std::shared_ptr &elem (*context)[y_key] = y_step_values; (*context)[x_key] = x_step_boundaries; } - childchild->setAttribute("x", x_key); - childchild->setAttribute("y", y_key); + child->setAttribute("x", x_key); + child->setAttribute("y", y_key); } - else if (childchild->localName() == "polymarker") + else if (child->localName() == "polymarker") { if (location == "right") { x_pos = (x_step_boundaries[y_ind * 2] + x_step_boundaries[y_ind * 2 + 1]) / 2; y_pos = y[y_ind]; - childchild->setAttribute("x", y_pos); - childchild->setAttribute("y", x_pos); + child->setAttribute("x", y_pos); + child->setAttribute("y", x_pos); } else { x_pos = (x_step_boundaries[x_ind * 2] + x_step_boundaries[x_ind * 2 + 1]) / 2; y_pos = y[x_ind]; - childchild->setAttribute("x", x_pos); - childchild->setAttribute("y", y_pos); + child->setAttribute("x", x_pos); + child->setAttribute("y", y_pos); } global_render->setMarkerSize( - childchild, 1.5 * (len / ((location == "right") ? (ymax - ymin) : (xmax - xmin)))); + child, 1.5 * (len / ((location == "right") ? (ymax - ymin) : (xmax - xmin)))); } } } @@ -4192,9 +3939,10 @@ void GRM::Render::processLimits(const std::shared_ptr &element) * \param[in] element The GRM::Element that contains the attributes */ int adjust_x_lim, adjust_y_lim; - std::string kind = static_cast(element->getAttribute("kind")); int scale = 0; + bool plot_reset_ranges = false; std::shared_ptr central_region; + auto kind = static_cast(element->getAttribute("kind")); gr_inqscale(&scale); if (kind != "pie" && kind != "polar" && kind != "polar_histogram" && kind != "polar_heatmap" && @@ -4237,26 +3985,27 @@ void GRM::Render::processLimits(const std::shared_ptr &element) element->removeAttribute("_original_adjust_x_lim"); element->removeAttribute("_original_adjust_y_lim"); } + plot_reset_ranges = true; element->removeAttribute("reset_ranges"); } // reset rotation - for (const auto &child : element->children()) + for (const auto &plot_child : element->children()) { - if (child->localName() == "marginal_heatmap_plot") + if (plot_child->localName() == "marginal_heatmap_plot") { - for (const auto &childchild : child->children()) + for (const auto &child : plot_child->children()) { - if (childchild->localName() == "central_region") + if (child->localName() == "central_region") { - central_region = childchild; + central_region = child; if (central_region->hasAttribute("reset_rotation")) processResetRotation(central_region); break; } } } - if (child->localName() == "central_region") + if (plot_child->localName() == "central_region") { - central_region = child; + central_region = plot_child; if (central_region->hasAttribute("reset_rotation")) processResetRotation(central_region); processWindow(central_region); processScale(element); @@ -4326,6 +4075,7 @@ void GRM::Render::processLimits(const std::shared_ptr &element) element->removeAttribute("panzoom"); element->removeChild(panzoom_element); + central_region->setAttribute("_zoomed", true); for (const auto &child : central_region->children()) { @@ -4389,6 +4139,7 @@ void GRM::Render::processLimits(const std::shared_ptr &element) logger((stderr, "Storing window (%lf, %lf, %lf, %lf)\n", xmin, xmax, ymin, ymax)); global_render->setWindow(central_region, xmin, xmax, ymin, ymax); } + if (plot_reset_ranges) central_region->setAttribute("_zoomed", true); processWindow(central_region); processScale(element); } @@ -4813,13 +4564,16 @@ static void processTextColorForBackground(const std::shared_ptr &e int color_ind, inq_color; unsigned char color_rgb[4]; std::string plot = "pie"; + std::shared_ptr plot_parent = element; + getPlotParent(plot_parent); + if (!static_cast(element->getAttribute("set_text_color_for_background"))) return; if (element->hasAttribute("stcfb_plot")) { plot = static_cast(element->getAttribute("stcfb_plot")); } - if (plot == "pie") + if (plot == "pie" && static_cast(plot_parent->getAttribute("kind")) == "pie") { double r, g, b; double color_lightness; @@ -4844,7 +4598,7 @@ static void processTextColorForBackground(const std::shared_ptr &e g = color_rgb[1] / 255.0; b = color_rgb[2] / 255.0; - color_lightness = get_lightness_from_rbg(r, g, b); + color_lightness = getLightnessFromRGB(r, g, b); if (color_lightness < 0.4) { gr_settextcolorind(0); @@ -4915,6 +4669,305 @@ static void processTransparency(const std::shared_ptr &element) gr_settransparency(static_cast(element->getAttribute("transparency"))); } +static void axisArgumentsConvertedIntoTickGroups(tick_t *ticks, tick_label_t *tick_labels, + const std::shared_ptr &axis, del_values del) +{ + int child_id = 1, label_ind = 0; + std::shared_ptr tick_group; + std::vector x_tick_labels_vec, y_tick_labels_vec; + int x_tick_labels_len = 0, y_tick_labels_len = 0; + auto num_ticks = static_cast(axis->getAttribute("num_ticks")); + auto num_labels = static_cast(axis->getAttribute("num_tick_labels")); + auto axis_type = static_cast(axis->getAttribute("axis_type")); + + auto context = global_render->getContext(); + if (axis->parentElement()->hasAttribute("_x_tick_labels")) + { + auto tick_key = static_cast(axis->parentElement()->getAttribute("_x_tick_labels")); + x_tick_labels_vec = GRM::get>((*context)[tick_key]); + x_tick_labels_len = x_tick_labels_vec.size(); + } + if (axis->parentElement()->hasAttribute("_y_tick_labels")) + { + auto tick_key = static_cast(axis->parentElement()->getAttribute("_y_tick_labels")); + y_tick_labels_vec = GRM::get>((*context)[tick_key]); + y_tick_labels_len = y_tick_labels_vec.size(); + } + + if (static_cast(axis->getAttribute("mirrored_axis"))) child_id += 1; + for (int i = 0; i < num_ticks; i++) + { + std::string label = ""; + double width = 0.0; + if (label_ind < num_labels && tick_labels[label_ind].tick == ticks[i].value) + { + if (tick_labels[label_ind].label) label = tick_labels[label_ind].label; + if (tick_labels[label_ind].width) width = tick_labels[label_ind].width; + if (axis_type == "x" && x_tick_labels_len > 0) + { + label = ""; + if (label_ind <= x_tick_labels_len && label_ind >= 1) label = x_tick_labels_vec[label_ind - 1]; + } + if (axis_type == "y" && y_tick_labels_len > 0) + { + label = ""; + if (label_ind <= y_tick_labels_len && label_ind >= 1) label = y_tick_labels_vec[label_ind - 1]; + } + label_ind += 1; + } + + if (del != del_values::update_without_default && del != del_values::update_with_default) + { + tick_group = global_render->createTickGroup(ticks[i].is_major, label, ticks[i].value, width); + tick_group->setAttribute("_child_id", child_id++); + axis->append(tick_group); + } + else + { + tick_group = axis->querySelectors("tick_group[_child_id=" + std::to_string(child_id++) + "]"); + if (tick_group != nullptr) + tick_group = global_render->createTickGroup(ticks[i].is_major, label, ticks[i].value, width, tick_group); + } + } +} + +static void processAxis(const std::shared_ptr &element, const std::shared_ptr &context) +{ + /*! + * Processing function for axes + * + * \param[in] element The GRM::Element that contains the attributes and data keys + * \param[in] context The GRM::Context that contains the actual data + */ + double x_tick, x_org; + double y_tick, y_org; + int x_major, y_major, major_count = 1; + int tick_orientation = 1, child_id = 0; + double tick_size = NAN, tick = NAN; + double min_val = NAN, max_val = NAN; + double org = NAN, pos = NAN; + double line_x_min = 0.0, line_x_max = 0.0, line_y_min = 0.0, line_y_max = 0.0; + std::string x_org_pos = PLOT_DEFAULT_ORG_POS, y_org_pos = PLOT_DEFAULT_ORG_POS; + std::shared_ptr plot_parent = element, line, axis_elem = element, central_region; + double window[4], old_window[4] = {NAN, NAN, NAN, NAN}; + bool mirrored_axis = MIRRORED_AXIS_DEFAULT, x_flip = false, y_flip = false; + std::string kind, axis_type; + del_values del = del_values::update_without_default; + int scientific_format = SCIENTIFIC_FORMAT_OPTION, scale = 0; + + del = del_values(static_cast(element->getAttribute("_delete_children"))); + clearOldChildren(&del, element); + getAxesInformation(element, x_org_pos, y_org_pos, x_org, y_org, x_major, y_major, x_tick, y_tick); + getPlotParent(plot_parent); + + window[0] = static_cast(element->parentElement()->parentElement()->getAttribute("window_x_min")); + window[1] = static_cast(element->parentElement()->parentElement()->getAttribute("window_x_max")); + window[2] = static_cast(element->parentElement()->parentElement()->getAttribute("window_y_min")); + window[3] = static_cast(element->parentElement()->parentElement()->getAttribute("window_y_max")); + if (element->hasAttribute("_window_old_x_min")) + old_window[0] = static_cast(element->getAttribute("_window_old_x_min")); + if (element->hasAttribute("_window_old_x_max")) + old_window[1] = static_cast(element->getAttribute("_window_old_x_max")); + if (element->hasAttribute("_window_old_y_min")) + old_window[2] = static_cast(element->getAttribute("_window_old_y_min")); + if (element->hasAttribute("_window_old_y_max")) + old_window[3] = static_cast(element->getAttribute("_window_old_y_max")); + element->setAttribute("_window_old_x_min", window[0]); + element->setAttribute("_window_old_x_max", window[1]); + element->setAttribute("_window_old_y_min", window[2]); + element->setAttribute("_window_old_y_max", window[3]); + + kind = static_cast(plot_parent->getAttribute("kind")); + if (element->hasAttribute("mirrored_axis")) mirrored_axis = static_cast(element->getAttribute("mirrored_axis")); + axis_type = static_cast(element->getAttribute("axis_type")); + if (element->hasAttribute("scientific_format")) // TODO: maybe need to be changed if Du Kim's MR gets merged + scientific_format = static_cast(element->getAttribute("scientific_format")); + if (plot_parent->hasAttribute("font")) processFont(plot_parent); + if (plot_parent->hasAttribute("x_flip")) x_flip = static_cast(plot_parent->getAttribute("x_flip")); + if (plot_parent->hasAttribute("y_flip")) y_flip = static_cast(plot_parent->getAttribute("y_flip")); + + if (element->hasAttribute("scale")) + { + global_render->processScale(element); + scale = static_cast(element->getAttribute("scale")); + } + + if (axis_type == "x") + { + min_val = window[0]; + max_val = window[1]; + tick = x_tick; + pos = window[2]; + major_count = x_major; + } + else if (axis_type == "y") + { + min_val = window[2]; + max_val = window[3]; + tick = y_tick; + pos = window[0]; + major_count = y_major; + } + getTickSize(element, tick_size); // GRM calculated tick_size + + if (element->parentElement()->localName() == "colorbar" || + (axis_type == "x" && ((std::isnan(old_window[0]) || old_window[0] == window[0]) && + (std::isnan(old_window[1]) || old_window[1] == window[1]))) || + (axis_type == "y" && ((std::isnan(old_window[2]) || old_window[2] == window[2]) && + (std::isnan(old_window[3]) || old_window[3] == window[3])))) + { + if (element->hasAttribute("min_value")) min_val = static_cast(element->getAttribute("min_value")); + if (element->hasAttribute("max_value")) max_val = static_cast(element->getAttribute("max_value")); + if (element->hasAttribute("org")) org = static_cast(element->getAttribute("org")); + if (element->hasAttribute("pos")) pos = static_cast(element->getAttribute("pos")); + if (element->hasAttribute("tick")) tick = static_cast(element->getAttribute("tick")); + if (element->hasAttribute("major_count")) major_count = static_cast(element->getAttribute("major_count")); + if (element->hasAttribute("tick_orientation")) + tick_orientation = static_cast(element->getAttribute("tick_orientation")); + } + + // special cases for x- and y-flip + if ((scale & GR_OPTION_FLIP_X || x_flip) && axis_type == "y") pos = window[1]; + if ((scale & GR_OPTION_FLIP_Y || y_flip) && axis_type == "x") pos = window[3]; + + // axis + if (element->parentElement()->localName() != "colorbar") + { + central_region = element->parentElement()->parentElement(); + // ticks need to flipped cause a heatmap or shade series is part of the central_region + for (const auto &series : central_region->children()) + { + if (starts_with(series->localName(), "series_") && + str_equals_any(series->localName(), "series_heatmap", "series_shade")) + { + tick_orientation = -1; + break; + } + } + } + else + { + if ((scale & GR_OPTION_FLIP_X || x_flip) && axis_type == "y") pos = window[0]; + processFlip(element->parentElement()); + } + tick_size *= tick_orientation; + axis_t axis = {min_val, max_val, tick, org, pos, major_count, 0, nullptr, tick_size, 0, nullptr, NAN, 1}; + if (axis_type == "x") + gr_axis('X', &axis); + else if (axis_type == "y") + gr_axis('Y', &axis); + tick_orientation = axis.tick_size < 0 ? -1 : 1; + + axis_elem = global_render->createAxis(axis.min, axis.max, axis.tick, axis.org, axis.position, axis.major_count, + axis.num_ticks, axis.num_tick_labels, abs(tick_size), tick_orientation, + axis.label_position, axis_elem); + if (del != del_values::update_without_default) + { + if (!axis_elem->hasAttribute("draw_grid")) axis_elem->setAttribute("draw_grid", true); + if (kind == "barplot") + { + bool only_barplot = true; + std::string orientation = PLOT_DEFAULT_ORIENTATION; + auto barplot = plot_parent->querySelectors("series_barplot"); + for (const auto &series : central_region->children()) + { + if (starts_with(series->localName(), "series_") && series->localName() != "series_barplot") + { + only_barplot = false; + break; + } + } + + if (only_barplot) + { + if (barplot != nullptr) orientation = static_cast(barplot->getAttribute("orientation")); + if (axis_type == "x" && orientation == "horizontal") axis_elem->setAttribute("draw_grid", false); + if (axis_type == "y" && orientation == "vertical") axis_elem->setAttribute("draw_grid", false); + } + } + if (kind == "shade") axis_elem->setAttribute("draw_grid", false); + axis_elem->setAttribute("mirrored_axis", mirrored_axis); + } + // create tick_group elements + if (!element->querySelectors("tick_group") || + (axis_type == "x" && ((!std::isnan(old_window[0]) && old_window[0] != window[0]) || + (!std::isnan(old_window[1]) && old_window[1] != window[1]))) || + (axis_type == "y" && ((!std::isnan(old_window[2]) && old_window[2] != window[2]) || + (!std::isnan(old_window[3]) && old_window[3] != window[3])))) + { + for (const auto &child : element->children()) + { + if (child->localName() != "polyline" && child->hasAttribute("_child_id")) child->remove(); + } + element->setAttribute("scientific_format", scientific_format); + axisArgumentsConvertedIntoTickGroups(axis.ticks, axis.tick_labels, axis_elem, del_values::recreate_own_children); + } + // polyline for axis-line + if (axis_type == "x") + { + line_x_min = axis.min; + line_x_max = axis.max; + line_y_min = line_y_max = axis.position; + } + else if (axis_type == "y") + { + line_x_min = line_x_max = axis.position; + line_y_min = axis.min; + line_y_max = axis.max; + } + if (del != del_values::update_without_default && del != del_values::update_with_default) + { + line = global_render->createPolyline(line_x_min, line_x_max, line_y_min, line_y_max); + line->setAttribute("_child_id", 0); + axis_elem->append(line); + } + else + { + line = element->querySelectors("polyline[_child_id=0]"); + if (line != nullptr) + global_render->createPolyline(line_x_min, line_x_max, line_y_min, line_y_max, 0, 0.0, 0, line); + } + if (line != nullptr && del != del_values::update_without_default) + { + int z_index = axis_type == "x" ? 0 : -2; + line->setAttribute("name", axis_type + "-axis-line"); + line->setAttribute("z_index", element->parentElement()->localName() == "colorbar" ? 2 : z_index); + } + if (axis_type == "x") + { + line_y_min = line_y_max = window[3]; + if (scale & GR_OPTION_FLIP_Y || y_flip) line_y_min = line_y_max = window[2]; + } + else if (axis_type == "y") + { + line_x_min = line_x_max = window[1]; + if (scale & GR_OPTION_FLIP_X || x_flip) line_x_min = line_x_max = window[0]; + } + if (mirrored_axis) + { + if (del != del_values::update_without_default && del != del_values::update_with_default) + { + line = global_render->createPolyline(line_x_min, line_x_max, line_y_min, line_y_max); + line->setAttribute("_child_id", 1); + axis_elem->append(line); + } + else + { + line = element->querySelectors("polyline[_child_id=1]"); + if (line != nullptr) + global_render->createPolyline(line_x_min, line_x_max, line_y_min, line_y_max, 0, 0.0, 0, line); + } + if (line != nullptr && del != del_values::update_without_default) + { + line->setAttribute("name", axis_type + "-axis-line mirrored"); + line->setAttribute("z_index", axis_type == "x" ? -1 : -3); + } + } + + applyMoveTransformation(element); + gr_freeaxis(&axis); +} + void GRM::Render::processWindow(const std::shared_ptr &element) { auto xmin = static_cast(element->getAttribute("window_x_min")); @@ -4945,6 +4998,15 @@ void GRM::Render::processWindow(const std::shared_ptr &element) gr_setwindow3d(xmin, xmax, ymin, ymax, zmin, zmax); } + if (element->hasAttribute("_zoomed") && static_cast(element->getAttribute("_zoomed"))) + { + for (const auto axis : element->querySelectorsAll("axis")) + { + clearAxisAttributes(axis); + processAxis(axis, global_render->context); + } + element->setAttribute("_zoomed", false); + } } else { @@ -5121,175 +5183,12 @@ void GRM::Render::calculateCharHeight(const std::shared_ptr &eleme processCharHeight(plot_parent); } -static void processXTickLabels(const std::shared_ptr &element) -{ - double viewport[4], char_height; - std::vector x_tick_labels; - del_values del = del_values::update_without_default; - int child_id = 0; - bool init = false; - auto plot_element = getSubplotElement(element); - std::shared_ptr central_region, central_region_parent; - auto kind = static_cast(plot_element->getAttribute("kind")); - - central_region_parent = plot_element; - if (kind == "marginal_heatmap") central_region_parent = plot_element->children()[0]; - for (const auto &child : central_region_parent->children()) - { - if (child->localName() == "central_region") - { - central_region = child; - break; - } - } - - gr_inqcharheight(&char_height); - viewport[0] = static_cast(central_region->getAttribute("viewport_x_min")); - viewport[1] = static_cast(central_region->getAttribute("viewport_x_max")); - viewport[2] = static_cast(central_region->getAttribute("viewport_y_min")); - viewport[3] = static_cast(central_region->getAttribute("viewport_y_max")); - - if (auto render = std::dynamic_pointer_cast(element->ownerDocument())) - { - double x, y; - double x_left = 0, x_right = 1, null; - double available_width; - std::shared_ptr x_tick_element; - std::shared_ptr context = render->getContext(); - auto tick_key = static_cast(element->getAttribute("x_tick_labels")); - x_tick_labels = GRM::get>((*context)[tick_key]); - - x_tick_element = element->querySelectors("x_tick_label_group"); - if (x_tick_element == nullptr) - { - x_tick_element = render->createElement("x_tick_label_group"); - render->setTextAlign(x_tick_element, GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_TOP); - x_tick_element->setAttribute("name", "x_tick_labels"); - element->append(x_tick_element); - } - - del = del_values(static_cast(x_tick_element->getAttribute("_delete_children"))); - if (del != del_values::update_without_default) - { - for (const auto &child : x_tick_element->children()) - { - if (del == del_values::recreate_own_children) - { - if (child->hasAttribute("_child_id")) child->remove(); - } - else if (del == del_values::recreate_all_children) - { - child->remove(); - } - } - } - else if (!x_tick_element->hasChildNodes()) - init = true; - - int offset = 1; - kind = static_cast(plot_element->getAttribute("kind")); - - /* calculate width available for x_tick notations */ - gr_wctondc(&x_left, &null); - gr_wctondc(&x_right, &null); - available_width = x_right - x_left; - if (str_equals_any(kind, "barplot", "hist", "stem", "stairs")) offset = 0; - for (int i = 1; i <= x_tick_labels.size(); i++) - { - x = i - offset; - gr_wctondc(&x, &y); - y = viewport[2] - 0.5 * char_height; - drawXTickLabel(x, y, x_tick_labels[i - 1].c_str(), available_width, x_tick_element, init, child_id++); - } - } -} - -static void processYTickLabels(const std::shared_ptr &element) -{ - double viewport[4], char_height; - std::vector y_tick_labels; - del_values del = del_values::update_without_default; - int child_id = 0; - bool init = false; - auto plot_element = getSubplotElement(element); - std::shared_ptr central_region, central_region_parent; - auto kind = static_cast(plot_element->getAttribute("kind")); - - central_region_parent = plot_element; - if (kind == "marginal_heatmap") central_region_parent = plot_element->children()[0]; - for (const auto &child : central_region_parent->children()) - { - if (child->localName() == "central_region") - { - central_region = child; - break; - } - } - - gr_inqcharheight(&char_height); - viewport[0] = static_cast(central_region->getAttribute("viewport_x_min")); - viewport[1] = static_cast(central_region->getAttribute("viewport_x_max")); - viewport[2] = static_cast(central_region->getAttribute("viewport_y_min")); - viewport[3] = static_cast(central_region->getAttribute("viewport_y_max")); - - if (auto render = std::dynamic_pointer_cast(element->ownerDocument())) - { - double x, y; - double y_left = 0, y_right = 1, null; - double available_height; - std::shared_ptr y_tick_element; - std::shared_ptr context = render->getContext(); - auto key = static_cast(element->getAttribute("y_tick_labels")); - y_tick_labels = GRM::get>((*context)[key]); - - y_tick_element = element->querySelectors("y_tick_label_group"); - if (y_tick_element == nullptr) - { - y_tick_element = render->createElement("y_tick_label_group"); - render->setTextAlign(y_tick_element, GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_BOTTOM); - y_tick_element->setAttribute("name", "y_tick_labels"); - element->append(y_tick_element); - } - - del = del_values(static_cast(y_tick_element->getAttribute("_delete_children"))); - if (del != del_values::update_without_default) - { - for (const auto &child : y_tick_element->children()) - { - if (del == del_values::recreate_own_children) - { - if (child->hasAttribute("_child_id")) child->remove(); - } - else if (del == del_values::recreate_all_children) - { - child->remove(); - } - } - } - else if (!y_tick_element->hasChildNodes()) - init = true; - - /* calculate width available for y_tick notations */ - gr_wctondc(&null, &y_left); - gr_wctondc(&null, &y_right); - available_height = y_right - y_left; - for (int i = 1; i <= y_tick_labels.size(); i++) - { - y = i; - gr_wctondc(&x, &y); - y -= 0.55 * char_height; - x = viewport[0] - 1.5 * char_height; // correct would be char_width here - drawYTickLabel(x, y, y_tick_labels[i - 1].c_str(), available_height, y_tick_element, init, child_id++); - } - } -} - static void processZIndex(const std::shared_ptr &element) { if (!z_queue_is_being_rendered) { - int zIndex = static_cast(element->getAttribute("z_index")); - z_index_manager.setZIndex(zIndex); + auto z_index = static_cast(element->getAttribute("z_index")); + z_index_manager.setZIndex(z_index); } } @@ -5375,8 +5274,6 @@ void GRM::Render::processAttributes(const std::shared_ptr &element processWSViewport}, // the xmin element can be used here cause all 4 are required {std::string("ws_window_x_min"), processWSWindow}, // the xmin element can be used here cause all 4 are required {std::string("x_flip"), processFlip}, // y_flip is also set - {std::string("x_tick_labels"), processXTickLabels}, - {std::string("y_tick_labels"), processYTickLabels}, {std::string("z_index"), processZIndex}, }; @@ -5514,46 +5411,6 @@ static void drawYLine(const std::shared_ptr &element, const std::s } } -static void processAxes(const std::shared_ptr &element, const std::shared_ptr &context) -{ - /*! - * Processing function for axes - * - * \param[in] element The GRM::Element that contains the attributes and data keys - * \param[in] context The GRM::Context that contains the actual data - */ - double x_tick, x_org; - double y_tick, y_org; - int x_major, y_major; - int tick_orientation = 1; - double tick_size; - std::string x_org_pos = PLOT_DEFAULT_ORG_POS, y_org_pos = PLOT_DEFAULT_ORG_POS; - - if (element->hasAttribute("x_org_pos")) x_org_pos = static_cast(element->getAttribute("x_org_pos")); - if (element->hasAttribute("y_org_pos")) y_org_pos = static_cast(element->getAttribute("y_org_pos")); - - getAxesInformation(element, x_org_pos, y_org_pos, x_org, y_org, x_major, y_major, x_tick, y_tick); - - auto draw_axes_group = element->parentElement(); - auto subplot_element = getSubplotElement(element); - - if (element->hasAttribute("tick_orientation")) - { - tick_orientation = static_cast(element->getAttribute("tick_orientation")); - } - - getTickSize(element, tick_size); - tick_size *= tick_orientation; - - if (element->hasAttribute("scale")) - { - auto scale = static_cast(element->getAttribute("scale")); - gr_setscale(scale); - } - applyMoveTransformation(element); - if (redraw_ws) gr_axes(x_tick, y_tick, x_org, y_org, x_major, y_major, tick_size); -} - static void processAxes3d(const std::shared_ptr &element, const std::shared_ptr &context) { /*! @@ -5562,9 +5419,8 @@ static void processAxes3d(const std::shared_ptr &element, const st * \param[in] element The GRM::Element that contains the attributes and data keys * \param[in] context The GRM::Context that contains the actual data */ - double x_tick, x_org; - double y_tick, y_org; - double z_tick, z_org; + double x_tick, y_tick, z_tick; + double x_org, y_org, z_org; int x_major, y_major, z_major; int tick_orientation = 1; double tick_size; @@ -5621,8 +5477,9 @@ static void processColorbar(const std::shared_ptr &element, const double c_min, c_max; int data, i, options; int z_log = 0; + int child_id = 0; del_values del = del_values::update_without_default; - std::shared_ptr cellArray = nullptr, axes = nullptr; + std::shared_ptr cell_array = nullptr, axis = nullptr; auto colors = static_cast(element->getAttribute("color_ind")); if (!getLimitsForColorbar(element, c_min, c_max)) @@ -5658,78 +5515,113 @@ static void processColorbar(const std::shared_ptr &element, const del = del_values(static_cast(element->getAttribute("_delete_children"))); clearOldChildren(&del, element); - cellArray = element->querySelectors("cellarray"); - - if (del != del_values::update_without_default && del != del_values::update_with_default && cellArray == nullptr) + if (del != del_values::update_without_default && del != del_values::update_with_default) { - cellArray = + cell_array = global_render->createCellArray(0, 1, c_max, c_min, 1, colors, 1, 1, 1, colors, "data" + str, data_vec); - element->append(cellArray); + cell_array->setAttribute("_child_id", 0); + element->append(cell_array); } - else if (cellArray != nullptr) + else { - global_render->createCellArray(0, 1, c_max, c_min, 1, colors, 1, 1, 1, colors, "data" + str, data_vec, context, - cellArray); + cell_array = element->querySelectors("cellarray[_child_id=0]"); + if (cell_array != nullptr) + global_render->createCellArray(0, 1, c_max, c_min, 1, colors, 1, 1, 1, colors, "data" + str, data_vec, context, + cell_array); } - if (cellArray != nullptr) cellArray->setAttribute("name", "colorbar"); + if (cell_array != nullptr) cell_array->setAttribute("name", "colorbar"); /* create axes */ gr_inqscale(&options); if (options & GR_OPTION_Z_LOG || z_log) { - if ((del != del_values::update_without_default && del != del_values::update_with_default && axes == nullptr) || + axis_t y_axis = {c_min, c_max, 2, c_min, 1, 1, 0, nullptr, NAN, 0, nullptr, NAN, 1}; + gr_axis('Y', &y_axis); + + if ((del != del_values::update_without_default && del != del_values::update_with_default && axis == nullptr) || !element->hasChildNodes()) { - axes = global_render->createAxes(0, 2, 1, c_min, 0, 1, 1); - global_render->setLineColorInd(axes, 1); - element->append(axes); - } - else if (axes != nullptr) - { - if (del == del_values::update_without_default) - { - axes->setAttribute("y_org", c_min); - } - else + axis = global_render->createAxis(y_axis.min, y_axis.max, y_axis.tick, y_axis.org, y_axis.position, + y_axis.major_count, y_axis.num_ticks, y_axis.num_tick_labels, + abs(y_axis.tick_size), y_axis.tick_size > 0 ? 1 : -1, y_axis.label_position); + axis->setAttribute("_child_id", 1); + global_render->setLineColorInd(axis, 1); + element->append(axis); + } + else if (axis != nullptr) + { + axis = element->querySelectors("axis[_child_id=1]"); + if (axis != nullptr) { - global_render->createAxes(0, 2, 1, c_min, 0, 1, 1, axes); + auto tick_size = y_axis.tick_size; + if (axis->hasAttribute("tick_size")) tick_size = static_cast(axis->getAttribute("tick_size")); + + global_render->createAxis(y_axis.min, y_axis.max, y_axis.tick, y_axis.org, y_axis.position, + y_axis.major_count, y_axis.num_ticks, y_axis.num_tick_labels, abs(tick_size), + tick_size > 0 ? 1 : -1, y_axis.label_position, axis); } } - if (axes != nullptr) + if (axis != nullptr) { - global_render->setScale(axes, GR_OPTION_Y_LOG); - global_render->processScale(axes); + global_render->setScale(axis, GR_OPTION_Y_LOG); + global_render->processScale(axis); + axis->setAttribute("name", "colorbar y-axis"); + axis->setAttribute("axis_type", "y"); + axis->setAttribute("draw_grid", false); + axis->setAttribute("mirrored_axis", false); + if (del == del_values::update_without_default) axis->setAttribute("min_value", c_min); } + gr_freeaxis(&y_axis); } else { - double c_tick = auto_tick(c_min, c_max); - if (del != del_values::update_without_default && del != del_values::update_with_default && axes == nullptr) + double c_tick = autoTick(c_min, c_max); + axis_t y_axis = {c_min, c_max, c_tick, c_min, 1, 1, 0, nullptr, NAN, 0, nullptr, NAN, 1}; + gr_axis('Y', &y_axis); + + if (del != del_values::update_without_default && del != del_values::update_with_default) { - axes = global_render->createAxes(0, c_tick, 1, c_min, 0, 1, 1); - global_render->setLineColorInd(axes, 1); - element->append(axes); + axis = global_render->createAxis(y_axis.min, y_axis.max, y_axis.tick, y_axis.org, y_axis.position, + y_axis.major_count, y_axis.num_ticks, y_axis.num_tick_labels, + abs(y_axis.tick_size), y_axis.tick_size > 0 ? 1 : -1, y_axis.label_position); + axis->setAttribute("_child_id", 1); + global_render->setLineColorInd(axis, 1); + element->append(axis); } - else if (axes != nullptr) + else { - if (del == del_values::update_without_default) + axis = element->querySelectors("axis[_child_id=1]"); + if (axis != nullptr) { - axes->setAttribute("y_tick", c_tick); - axes->setAttribute("y_org", c_min); + auto tick_size = y_axis.tick_size; + if (axis->hasAttribute("tick_size")) tick_size = static_cast(axis->getAttribute("tick_size")); + global_render->createAxis(y_axis.min, y_axis.max, y_axis.tick, y_axis.org, y_axis.position, + y_axis.major_count, y_axis.num_ticks, y_axis.num_tick_labels, abs(tick_size), + tick_size > 0 ? 1 : -1, y_axis.label_position, axis); } - else + } + if (axis != nullptr) + { + axis->setAttribute("scale", 0); + if (del == del_values::update_without_default) { - global_render->createAxes(0, c_tick, 1, c_min, 0, 1, 1, axes); + axis->setAttribute("tick", c_tick); + axis->setAttribute("min_value", c_min); } } processFlip(element); + gr_freeaxis(&y_axis); } - if (del != del_values::update_without_default && axes != nullptr) + if (axis != nullptr) { - double tick_size = PLOT_DEFAULT_COLORBAR_TICK_SIZE; - - axes->setAttribute("tick_size", tick_size); - axes->setAttribute("name", "colorbar"); + axis->setAttribute("tick_size", PLOT_DEFAULT_COLORBAR_TICK_SIZE); + if (del != del_values::update_without_default) + { + axis->setAttribute("name", "colorbar y-axis"); + axis->setAttribute("axis_type", "y"); + axis->setAttribute("draw_grid", false); + axis->setAttribute("mirrored_axis", false); + } } applyMoveTransformation(element); } @@ -6281,7 +6173,7 @@ static void processContour(const std::shared_ptr &element, const s } global_render->setSpace(element->parentElement(), z_min, z_max, 0, - 90); // not plot_parent cause it should be now on central_region + 90); // not plot_parent because it should be now on central_region processSpace(element->parentElement()); px_vec = std::vector(gridit_x, gridit_x + PLOT_CONTOUR_GRIDIT_N); @@ -6479,21 +6371,21 @@ static void processDrawArc(const std::shared_ptr &element, const s * \param[in] element The GRM::Element that contains the attributes and data keys * \param[in] context The GRM::Context that contains the actual data */ - auto xmin = static_cast(element->getAttribute("x_min")); - auto xmax = static_cast(element->getAttribute("x_max")); - auto ymin = static_cast(element->getAttribute("y_min")); - auto ymax = static_cast(element->getAttribute("y_max")); + auto x_min = static_cast(element->getAttribute("x_min")); + auto x_max = static_cast(element->getAttribute("x_max")); + auto y_min = static_cast(element->getAttribute("y_min")); + auto y_max = static_cast(element->getAttribute("y_max")); auto start_angle = static_cast(element->getAttribute("start_angle")); auto end_angle = static_cast(element->getAttribute("end_angle")); applyMoveTransformation(element); - if (redraw_ws) gr_drawarc(xmin, xmax, ymin, ymax, start_angle, end_angle); + if (redraw_ws) gr_drawarc(x_min, x_max, y_min, y_max, start_angle, end_angle); } static void processDrawGraphics(const std::shared_ptr &element, const std::shared_ptr &context) { std::vector char_vec; - auto key = (std::string)element->getAttribute("data"); + auto key = static_cast(element->getAttribute("data")); auto data_vec = GRM::get>((*context)[key]); char_vec.reserve(data_vec.size()); @@ -6544,13 +6436,12 @@ static void processErrorBars(const std::shared_ptr &element, const std::vector absolute_upwards_vec, absolute_downwards_vec, relative_upwards_vec, relative_downwards_vec; std::string absolute_upwards, absolute_downwards, relative_upwards, relative_downwards; double absolute_upwards_flt, relative_upwards_flt, absolute_downwards_flt, relative_downwards_flt; - unsigned int i; int scale_options, color_upwards_cap, color_downwards_cap, color_error_bar; double marker_size, x_min, x_max, y_min, y_max, tick, a, b, e_upwards, e_downwards, x_value; double line_x[2], line_y[2]; std::vector x_vec, y_vec; unsigned int x_length; - std::string x, y; + std::string x_key, y_key; std::shared_ptr series; del_values del = del_values::update_without_default; int child_id = 0; @@ -6566,12 +6457,12 @@ static void processErrorBars(const std::shared_ptr &element, const } if (!element->hasAttribute("x")) throw NotFoundError("Error-bars are missing required attribute x-data.\n"); - x = static_cast(element->getAttribute("x")); + x_key = static_cast(element->getAttribute("x")); if (!element->hasAttribute("y")) throw NotFoundError("Error-bars are missing required attribute y-data.\n"); - y = static_cast(element->getAttribute("y")); + y_key = static_cast(element->getAttribute("y")); - x_vec = GRM::get>((*context)[x]); - y_vec = GRM::get>((*context)[y]); + x_vec = GRM::get>((*context)[x_key]); + y_vec = GRM::get>((*context)[y_key]); x_length = x_vec.size(); kind = static_cast(series->parentElement()->getAttribute("kind")); if (element->parentElement()->hasAttribute("orientation")) @@ -6651,7 +6542,7 @@ static void processErrorBars(const std::shared_ptr &element, const /* Actual drawing of bars */ e_upwards = e_downwards = FLT_MAX; - for (i = 0; i < x_length; i++) + for (int i = 0; i < x_length; i++) { std::shared_ptr error_bar; if (!absolute_upwards.empty() || !relative_upwards.empty() || absolute_upwards_flt != FLT_MAX || @@ -6814,8 +6705,8 @@ static void processIsosurface(const std::shared_ptr &element, std::vector z_vec, temp_colors; unsigned int i, z_length, dims; int strides[3]; - double c_min, c_max, isovalue; - float foreground_colors[3]; + double c_min, c_max, isovalue = 0.5; + float foreground_colors[3] = {0.0, 0.5, 0.8}; if (!element->hasAttribute("z")) throw NotFoundError("Isosurface series is missing required attribute z-data.\n"); auto z_key = static_cast(element->getAttribute("z")); @@ -6833,10 +6724,6 @@ static void processIsosurface(const std::shared_ptr &element, throw std::length_error("For isosurface series shape[0] * shape[1] * shape[2] must be c_length.\n"); if (z_length <= 0) throw NotFoundError("For isosurface series the size of c has to be greater than 0.\n"); - isovalue = 0.5; - foreground_colors[0] = 0.0; - foreground_colors[1] = 0.5; - foreground_colors[2] = 0.8; if (element->hasAttribute("isovalue")) isovalue = static_cast(element->getAttribute("isovalue")); element->setAttribute("isovalue", isovalue); /* We need to convert the double values to floats, as GR3 expects floats, but an argument can only contain doubles. */ @@ -6938,7 +6825,7 @@ static void processLegend(const std::shared_ptr &element, const st if (static_cast(plot_parent->getAttribute("kind")) != "pie") { double legend_symbol_x[2], legend_symbol_y[2]; - int i; + int i, legend_elems = 0; double viewport[4]; std::shared_ptr fr, dr; @@ -6956,13 +6843,12 @@ static void processLegend(const std::shared_ptr &element, const st del = del_values(static_cast(element->getAttribute("_delete_children"))); /* get the amount of series which should be displayed inside the legend */ - int legend_elems = 0; - for (const auto &child : element->parentElement()->children()) // central_region childs + for (const auto &series : element->parentElement()->children()) // central_region children { - if (child->localName() != "series_line" && child->localName() != "series_scatter") continue; - for (const auto &childchild : child->children()) + if (series->localName() != "series_line" && series->localName() != "series_scatter") continue; + for (const auto &child : series->children()) { - if (childchild->localName() != "polyline" && childchild->localName() != "polymarker") continue; + if (child->localName() != "polyline" && child->localName() != "polymarker") continue; legend_elems += 1; } } @@ -7030,12 +6916,12 @@ static void processLegend(const std::shared_ptr &element, const st for (const auto &plot_child : element->parentElement()->children()) // central_region childs { if (plot_child->localName() != "central_region") continue; - for (const auto &child : plot_child->children()) + for (const auto &series : plot_child->children()) { int mask; double dy; - if (child->localName() != "series_line" && child->localName() != "series_scatter") continue; + if (series->localName() != "series_line" && series->localName() != "series_scatter") continue; if (i <= labels.size()) { @@ -7054,10 +6940,10 @@ static void processLegend(const std::shared_ptr &element, const st legend_symbol_x[1] = viewport[0] + 0.07 * scale_factor; legend_symbol_y[0] = viewport[3] - 0.03 * scale_factor; legend_symbol_y[1] = viewport[3] - 0.03 * scale_factor; - for (const auto &childchild : child->children()) + for (const auto &child : series->children()) { std::shared_ptr pl; - if (childchild->localName() == "polyline") + if (child->localName() == "polyline") { if (del != del_values::update_without_default && del != del_values::update_with_default) { @@ -7076,27 +6962,26 @@ static void processLegend(const std::shared_ptr &element, const st if (pl != nullptr) { render->setLineSpec(pl, specs[spec_i]); - if (childchild->hasAttribute("line_color_ind")) + if (child->hasAttribute("line_color_ind")) { - render->setLineColorInd(pl, - static_cast(childchild->getAttribute("line_color_ind"))); + render->setLineColorInd(pl, static_cast(child->getAttribute("line_color_ind"))); } else { - render->setLineColorInd(pl, static_cast(child->getAttribute("line_color_ind"))); + render->setLineColorInd(pl, static_cast(series->getAttribute("line_color_ind"))); } } } - else if (childchild->localName() == "polymarker") + else if (child->localName() == "polymarker") { int markertype; - if (childchild->hasAttribute("marker_type")) + if (child->hasAttribute("marker_type")) { - markertype = static_cast(childchild->getAttribute("marker_type")); + markertype = static_cast(child->getAttribute("marker_type")); } else { - markertype = static_cast(child->getAttribute("marker_type")); + markertype = static_cast(series->getAttribute("marker_type")); } if (del != del_values::update_without_default && del != del_values::update_with_default) { @@ -7114,10 +6999,10 @@ static void processLegend(const std::shared_ptr &element, const st } if (pl != nullptr) { - render->setMarkerColorInd(pl, - (child->hasAttribute("marker_color_ind") - ? static_cast(child->getAttribute("marker_color_ind")) - : 989)); + render->setMarkerColorInd( + pl, (series->hasAttribute("marker_color_ind") + ? static_cast(series->getAttribute("marker_color_ind")) + : 989)); processMarkerColorInd(pl); } } @@ -7129,10 +7014,10 @@ static void processLegend(const std::shared_ptr &element, const st legend_symbol_x[1] = viewport[0] + 0.06 * scale_factor; legend_symbol_y[0] = viewport[3] - 0.03 * scale_factor; legend_symbol_y[1] = viewport[3] - 0.03 * scale_factor; - for (const auto &childchild : child->children()) + for (const auto &child : series->children()) { std::shared_ptr pl; - if (childchild->localName() == "polyline") + if (child->localName() == "polyline") { if (del != del_values::update_without_default && del != del_values::update_with_default) { @@ -7151,27 +7036,26 @@ static void processLegend(const std::shared_ptr &element, const st if (pl != nullptr) { render->setLineSpec(pl, specs[spec_i]); - if (childchild->hasAttribute("line_color_ind")) + if (child->hasAttribute("line_color_ind")) { - render->setLineColorInd(pl, - static_cast(childchild->getAttribute("line_color_ind"))); + render->setLineColorInd(pl, static_cast(child->getAttribute("line_color_ind"))); } else { - render->setLineColorInd(pl, static_cast(child->getAttribute("line_color_ind"))); + render->setLineColorInd(pl, static_cast(series->getAttribute("line_color_ind"))); } } } - else if (childchild->localName() == "polymarker") + else if (child->localName() == "polymarker") { int markertype; - if (childchild->hasAttribute("marker_type")) + if (child->hasAttribute("marker_type")) { - markertype = static_cast(childchild->getAttribute("marker_type")); + markertype = static_cast(child->getAttribute("marker_type")); } else { - markertype = static_cast(child->getAttribute("marker_type")); + markertype = static_cast(series->getAttribute("marker_type")); } if (del != del_values::update_without_default && del != del_values::update_with_default) { @@ -7189,10 +7073,10 @@ static void processLegend(const std::shared_ptr &element, const st } if (pl != nullptr) { - render->setMarkerColorInd(pl, - (child->hasAttribute("marker_color_ind") - ? static_cast(child->getAttribute("marker_color_ind")) - : 989)); + render->setMarkerColorInd( + pl, (series->hasAttribute("marker_color_ind") + ? static_cast(series->getAttribute("marker_color_ind")) + : 989)); processMarkerColorInd(pl); } } @@ -7284,28 +7168,28 @@ static void processLegend(const std::shared_ptr &element, const st render->setLineColorInd(element, 1); render->setLineWidth(element, 1); - auto subsubGroup = element->querySelectors("labels_group"); - if (subsubGroup == nullptr) + auto labels_group = element->querySelectors("labels_group"); + if (labels_group == nullptr) { - subsubGroup = render->createElement("labels_group"); - element->append(subsubGroup); + labels_group = render->createElement("labels_group"); + element->append(labels_group); } - render->setTextAlign(subsubGroup, GKS_K_TEXT_HALIGN_LEFT, GKS_K_TEXT_VALIGN_HALF); + render->setTextAlign(labels_group, GKS_K_TEXT_HALIGN_LEFT, GKS_K_TEXT_VALIGN_HALF); for (auto ¤t_label : labels) { - std::shared_ptr labelGroup; + std::shared_ptr label_elem; if (del != del_values::update_without_default && del != del_values::update_with_default) { - labelGroup = render->createElement("label"); - labelGroup->setAttribute("_child_id", label_child_id++); - subsubGroup->append(labelGroup); + label_elem = render->createElement("label"); + label_elem->setAttribute("_child_id", label_child_id++); + labels_group->append(label_elem); } else { - labelGroup = subsubGroup->querySelectors("label[_child_id=" + std::to_string(label_child_id++) + "]"); + label_elem = labels_group->querySelectors("label[_child_id=" + std::to_string(label_child_id++) + "]"); } - if (labelGroup != nullptr) + if (label_elem != nullptr) { if (del != del_values::update_without_default && del != del_values::update_with_default) { @@ -7313,11 +7197,11 @@ static void processLegend(const std::shared_ptr &element, const st viewport[2] + (0.5 * h + 0.01) * scale_factor, viewport[2] + (0.5 * h + 0.03) * scale_factor); fr->setAttribute("_child_id", 0); - labelGroup->append(fr); + label_elem->append(fr); } else { - fr = labelGroup->querySelectors("fill_rect[_child_id=0]"); + fr = label_elem->querySelectors("fill_rect[_child_id=0]"); if (fr != nullptr) render->createFillRect(viewport[0] + 0.02 * scale_factor, viewport[0] + 0.04 * scale_factor, viewport[2] + (0.5 * h + 0.01) * scale_factor, @@ -7338,11 +7222,11 @@ static void processLegend(const std::shared_ptr &element, const st if (pie_segment != nullptr) { int color_ind = static_cast(pie_segment->getAttribute("fill_color_ind")); - auto colorrep = static_cast( + auto color_rep = static_cast( pie_segment->getAttribute("colorrep." + std::to_string(color_ind))); fr->setAttribute("fill_color_ind", color_ind); - if (!colorrep.empty()) - fr->setAttribute("colorrep." + std::to_string(color_ind), colorrep); + if (!color_rep.empty()) + fr->setAttribute("colorrep." + std::to_string(color_ind), color_rep); } break; } @@ -7356,11 +7240,11 @@ static void processLegend(const std::shared_ptr &element, const st viewport[2] + (0.5 * h + 0.01) * scale_factor, viewport[2] + (0.5 * h + 0.03) * scale_factor); dr->setAttribute("_child_id", 1); - labelGroup->append(dr); + label_elem->append(dr); } else { - dr = labelGroup->querySelectors("draw_rect[_child_id=1]"); + dr = label_elem->querySelectors("draw_rect[_child_id=1]"); if (dr != nullptr) render->createDrawRect(viewport[0] + 0.02 * scale_factor, viewport[0] + 0.04 * scale_factor, viewport[2] + (0.5 * h + 0.01) * scale_factor, @@ -7371,11 +7255,11 @@ static void processLegend(const std::shared_ptr &element, const st text = render->createText(viewport[0] + 0.05 * scale_factor, viewport[2] + (0.5 * h + 0.02) * scale_factor, current_label); text->setAttribute("_child_id", 2); - labelGroup->append(text); + label_elem->append(text); } else { - text = labelGroup->querySelectors("text[_child_id=2]"); + text = label_elem->querySelectors("text[_child_id=2]"); if (text != nullptr) render->createText(viewport[0] + 0.05 * scale_factor, viewport[2] + (0.5 * h + 0.02) * scale_factor, current_label, CoordinateSpace::NDC, text); @@ -7453,7 +7337,7 @@ static void processPolarAxes(const std::shared_ptr &element, const rings = -1; norm = static_cast(element->getAttribute("norm")); - tick = auto_tick_rings_polar(max, rings, norm); + tick = autoTickRingsPolar(max, rings, norm); central_region->setAttribute("tick", tick); max = tick * rings; @@ -7473,7 +7357,7 @@ static void processPolarAxes(const std::shared_ptr &element, const } else { - tick = auto_tick(r_min, r_max); + tick = autoTick(r_min, r_max); } } @@ -7677,27 +7561,6 @@ static void processFillArea(const std::shared_ptr &element, const if (redraw_ws) gr_fillarea(n, (double *)&(x_vec[0]), (double *)&(y_vec[0])); } -static void processGrid(const std::shared_ptr &element, const std::shared_ptr &context) -{ - /*! - * Processing function for grid - * - * \param[in] element The GRM::Element that contains the attributes and data keys - * \param[in] context The GRM::Context that contains the actual data - */ - double x_tick, y_tick, x_org, y_org; - int x_major, y_major; - std::string x_org_pos = PLOT_DEFAULT_ORG_POS, y_org_pos = PLOT_DEFAULT_ORG_POS; - - if (element->hasAttribute("x_org_pos")) x_org_pos = static_cast(element->getAttribute("x_org_pos")); - if (element->hasAttribute("y_org_pos")) y_org_pos = static_cast(element->getAttribute("y_org_pos")); - - getAxesInformation(element, x_org_pos, y_org_pos, x_org, y_org, x_major, y_major, x_tick, y_tick); - applyMoveTransformation(element); - - if (redraw_ws) gr_grid(x_tick, y_tick, x_org, y_org, abs(x_major), abs(y_major)); -} - static void processGrid3d(const std::shared_ptr &element, const std::shared_ptr &context) { /*! @@ -7706,9 +7569,8 @@ static void processGrid3d(const std::shared_ptr &element, const st * \param[in] element The GRM::Element that contains the attributes and data keys * \param[in] context The GRM::Context that contains the actual data */ - double x_tick, x_org; - double y_tick, y_org; - double z_tick, z_org; + double x_tick, y_tick, z_tick; + double x_org, y_org, z_org; int x_major, y_major, z_major; std::string x_org_pos = PLOT_DEFAULT_ORG_POS, y_org_pos = PLOT_DEFAULT_ORG_POS, z_org_pos = PLOT_DEFAULT_ORG_POS; if (element->hasAttribute("x_org_pos")) x_org_pos = static_cast(element->getAttribute("x_org_pos")); @@ -7722,6 +7584,41 @@ static void processGrid3d(const std::shared_ptr &element, const st if (redraw_ws) gr_grid3d(x_tick, y_tick, z_tick, x_org, y_org, z_org, abs(x_major), abs(y_major), abs(z_major)); } +static void processGridLine(const std::shared_ptr &element, const std::shared_ptr &context) +{ + std::shared_ptr axis_elem = element->parentElement()->parentElement(); + std::shared_ptr plot_parent = element; + getPlotParent(plot_parent); + + auto coordinate_system = plot_parent->querySelectors("coordinate_system"); + bool hide = + (coordinate_system->hasAttribute("hide")) ? static_cast(coordinate_system->getAttribute("hide")) : false; + auto coordinate_system_type = static_cast(coordinate_system->getAttribute("plot_type")); + auto axis_type = static_cast(axis_elem->getAttribute("axis_type")); + auto min_val = static_cast(axis_elem->getAttribute("min_value")); + auto max_val = static_cast(axis_elem->getAttribute("max_value")); + auto org = static_cast(axis_elem->getAttribute("org")); + auto pos = static_cast(axis_elem->getAttribute("pos")); + auto tick = static_cast(axis_elem->getAttribute("tick")); + auto major_count = static_cast(axis_elem->getAttribute("major_count")); + auto value = static_cast(element->getAttribute("value")); + auto is_major = static_cast(element->getAttribute("is_major")); + + tick_t g = {value, is_major}; + axis_t grid = {min_val, max_val, tick, org, pos, major_count, 1, &g, 0.0, 0, nullptr, NAN, false}; + if (redraw_ws && !hide && (coordinate_system_type == "2d" || axis_elem->parentElement()->localName() == "colorbar")) + { + if (axis_type == "x") + { + gr_drawaxes(&grid, nullptr, 4); + } + else + { + gr_drawaxes(nullptr, &grid, 4); + } + } +} + static void processHeatmap(const std::shared_ptr &element, const std::shared_ptr &context) { /*! @@ -8172,7 +8069,6 @@ static void processHist(const std::shared_ptr &element, const std: std::string orientation = PLOT_DEFAULT_ORIENTATION; bool is_horizontal; - if (element->hasAttribute("fill_color_rgb")) { auto bar_color_rgb = static_cast(element->getAttribute("fill_color_rgb")); @@ -8342,7 +8238,7 @@ static void processBar(const std::shared_ptr &element, const std:: std::shared_ptr fill_rect, draw_rect, text_elem; std::string orientation = PLOT_DEFAULT_ORIENTATION, text; del_values del = del_values::update_without_default; - double linewidth = NAN, y_lightness = NAN; + double line_width = NAN, y_lightness = NAN; std::vector bar_color_rgb, edge_color_rgb; int child_id = 0; @@ -8353,7 +8249,7 @@ static void processBar(const std::shared_ptr &element, const std:: bar_color_index = static_cast(element->getAttribute("fill_color_ind")); edge_color_index = static_cast(element->getAttribute("line_color_ind")); if (element->hasAttribute("text")) text = static_cast(element->getAttribute("text")); - if (element->hasAttribute("line_width")) linewidth = static_cast(element->getAttribute("line_width")); + if (element->hasAttribute("line_width")) line_width = static_cast(element->getAttribute("line_width")); if (element->hasAttribute("fill_color_rgb")) { @@ -8433,7 +8329,7 @@ static void processBar(const std::shared_ptr &element, const std:: element->parentElement()->setAttribute("line_color_ind", edge_color_index); global_render->setLineColorInd(draw_rect, edge_color_index); processLineColorInd(draw_rect); - if (!std::isnan(linewidth)) global_render->setLineWidth(draw_rect, linewidth); + if (!std::isnan(line_width)) global_render->setLineWidth(draw_rect, line_width); } if (!text.empty()) @@ -8598,6 +8494,10 @@ static void processPolyline(const std::shared_ptr &element, const * \param[in] context The GRM::Context that contains the actual data */ applyMoveTransformation(element); + + auto name = static_cast(element->getAttribute("name")); + + if (starts_with(name, "x-axis-line") || starts_with(name, "y-axis-line")) gr_setclip(0); if (element->getAttribute("x").isString() && element->getAttribute("y").isString()) { auto x = static_cast(element->getAttribute("x")); @@ -8606,7 +8506,7 @@ static void processPolyline(const std::shared_ptr &element, const std::vector x_vec = GRM::get>((*context)[x]); std::vector y_vec = GRM::get>((*context)[y]); - int n = std::min((int)x_vec.size(), (int)y_vec.size()); + auto n = std::min((int)x_vec.size(), (int)y_vec.size()); auto group = element->parentElement(); if ((element->hasAttribute("line_types") || element->hasAttribute("line_widths") || element->hasAttribute("line_color_indices")) || @@ -8631,6 +8531,7 @@ static void processPolyline(const std::shared_ptr &element, const if (redraw_ws) gr_polyline(2, x, y); } + if (starts_with(name, "x-axis-line") || starts_with(name, "y-axis-line")) gr_setclip(1); } static void processPolyline3d(const std::shared_ptr &element, @@ -8688,7 +8589,7 @@ static void processPolymarker(const std::shared_ptr &element, std::vector x_vec = GRM::get>((*context)[x]); std::vector y_vec = GRM::get>((*context)[y]); - int n = std::min((int)x_vec.size(), (int)y_vec.size()); + auto n = std::min((int)x_vec.size(), (int)y_vec.size()); auto group = element->parentElement(); if ((element->hasAttribute("marker_types") || element->hasAttribute("marker_sizes") || element->hasAttribute("marker_color_indices")) || @@ -8705,8 +8606,8 @@ static void processPolymarker(const std::shared_ptr &element, } else if (element->getAttribute("x").isDouble() && element->getAttribute("y").isDouble()) { - double x = static_cast(element->getAttribute("x")); - double y = static_cast(element->getAttribute("y")); + auto x = static_cast(element->getAttribute("x")); + auto y = static_cast(element->getAttribute("y")); if (redraw_ws) gr_polymarker(1, &x, &y); } } @@ -8764,7 +8665,7 @@ static void processQuiver(const std::shared_ptr &element, const st auto u = static_cast(element->getAttribute("u")); if (!element->hasAttribute("v")) throw NotFoundError("Quiver series is missing required attribute v-data.\n"); auto v = static_cast(element->getAttribute("v")); - int color = static_cast(element->getAttribute("color_ind")); + auto color = static_cast(element->getAttribute("color_ind")); std::vector x_vec = GRM::get>((*context)[x]); std::vector y_vec = GRM::get>((*context)[y]); @@ -8820,7 +8721,7 @@ static void processPolar(const std::shared_ptr &element, const std } element->setAttribute("r_min", r_min); element->setAttribute("r_max", r_max); - tick = 0.5 * auto_tick(r_min, r_max); + tick = 0.5 * autoTick(r_min, r_max); n = (int)ceil((r_max - r_min) / tick); r_max = r_min + n * tick; @@ -8835,9 +8736,7 @@ static void processPolar(const std::shared_ptr &element, const std if (rho_length != theta_length) throw std::length_error("For polar series y(rho)- and x(theta)-data must have the same size.\n"); - std::vector x(rho_length); - std::vector y(rho_length); - + std::vector x(rho_length), y(rho_length); for (i = 0; i < rho_length; ++i) { double current_rho = rho_vec[i] / r_max; @@ -9093,8 +8992,8 @@ static void preBarplot(const std::shared_ptr &element, const std:: auto y_key = static_cast(series->getAttribute("y")); auto y_vec = GRM::get>((*context)[y_key]); indices_vec = std::vector(y_vec.size(), 1); - int id = static_cast(global_root->getAttribute("_id")); - std::string id_str = std::to_string(id); + auto id = static_cast(global_root->getAttribute("_id")); + auto id_str = std::to_string(id); (*context)["indices" + id_str] = indices_vec; series->setAttribute("indices", "indices" + id_str); @@ -9176,7 +9075,7 @@ static void prePolarHistogram(const std::shared_ptr &element, { if (!group->hasAttribute("num_bins")) { - num_bins = grm_min(12, (int)(length * 1.0 / 2) - 1); + num_bins = grm_min(12, (int)(length / 2.0) - 1); group->setAttribute("num_bins", static_cast(num_bins)); } else @@ -9184,7 +9083,7 @@ static void prePolarHistogram(const std::shared_ptr &element, num_bins = static_cast(group->getAttribute("num_bins")); if (num_bins <= 0 || num_bins > 200) { - num_bins = grm_min(12, (int)(length * 1.0 / 2) - 1); + num_bins = grm_min(12, (int)(length / 2.0) - 1); group->setAttribute("num_bins", static_cast(num_bins)); } } @@ -9203,7 +9102,7 @@ static void prePolarHistogram(const std::shared_ptr &element, } else /* with bin_edges */ { - int temp = 0, i; + int cnt = 0; bin_edges_key = static_cast(group->getAttribute("bin_edges")); bin_edges = GRM::get>((*context)[bin_edges_key]); @@ -9211,14 +9110,13 @@ static void prePolarHistogram(const std::shared_ptr &element, /* filter bin_edges */ new_edges.resize(num_bin_edges); - for (i = 0; i < num_bin_edges; ++i) + for (int i = 0; i < num_bin_edges; ++i) { if (phi_lim == nullptr) /* no phi_lim */ { if (0.0 <= bin_edges[i] && bin_edges[i] <= 2 * M_PI) { - new_edges[temp] = bin_edges[i]; - temp++; + new_edges[cnt++] = bin_edges[i]; } else { @@ -9229,15 +9127,14 @@ static void prePolarHistogram(const std::shared_ptr &element, { if (phi_lim[0] <= bin_edges[i] && bin_edges[i] <= phi_lim[1]) { - new_edges[temp] = bin_edges[i]; - temp++; + new_edges[cnt++] = bin_edges[i]; } } } - if (num_bin_edges > temp) + if (num_bin_edges > cnt) { - num_bin_edges = temp; - bin_edges.resize(temp); + num_bin_edges = cnt; + bin_edges.resize(cnt); } else { @@ -9294,18 +9191,17 @@ static void prePolarHistogram(const std::shared_ptr &element, } else /* bin_width is given */ { - int n = 0, temp; + int n = 0; bin_width = static_cast(group->getAttribute("bin_width")); if (num_bin_edges > 0 && phi_lim == nullptr) { - int i; logger((stderr, "\"bin_width\" is not compatible with \"bin_edges\"\n")); bin_widths.resize(num_bins); - for (i = 1; i <= num_bin_edges - 1; ++i) + for (int i = 1; i <= num_bin_edges - 1; ++i) { bin_widths[i - 1] = bin_edges[i] - bin_edges[i - 1]; } @@ -9363,9 +9259,9 @@ static void prePolarHistogram(const std::shared_ptr &element, group->setAttribute("bin_width", bin_width); bin_widths.resize(num_bins); - for (temp = 0; temp < num_bins; ++temp) + for (int i = 0; i < num_bins; ++i) { - bin_widths[temp] = bin_width; + bin_widths[i] = bin_width; } group->setAttribute("bin_widths", bin_widths_key); (*context)[bin_widths_key] = bin_widths; @@ -9427,8 +9323,6 @@ static void prePolarHistogram(const std::shared_ptr &element, } else /* no is_bin_counts */ { - int x; - max = 0.0; classes.resize(num_bins); @@ -9452,7 +9346,7 @@ static void prePolarHistogram(const std::shared_ptr &element, } // calc classes - for (x = 0; x < num_bins; ++x) + for (int x = 0; x < num_bins; ++x) { int observations = 0; @@ -9488,9 +9382,9 @@ static void prePolarHistogram(const std::shared_ptr &element, // temporary maximum respecting norm temp_max = classes[i]; if (norm == "pdf") - temp_max /= total_observations * bin_widths[x]; + temp_max /= total_observations * bin_widths[i]; else if (norm == "countdensity") - temp_max /= bin_widths[x]; + temp_max /= bin_widths[i]; if (temp_max > max) max = temp_max; } @@ -9574,7 +9468,7 @@ static void processPolarHistogram(const std::shared_ptr &element, if (element->hasAttribute("stairs")) { /* Set default stairs line color width and alpha values */ - if (!element->hasAttribute("face_alpha")) face_alpha = 1.0; + if (!element->hasAttribute("face_alpha")) element->setAttribute("face_alpha", 1.0); stairs = static_cast(element->getAttribute("stairs")); if (stairs) @@ -10197,7 +10091,7 @@ static void processPolarBar(const std::shared_ptr &element, const lineardata.resize(image_size * image_size); bin_counts.resize(num_bins); - create_colormap(xcolormap, ycolormap, colormap_size, colormap); + createColormap(xcolormap, ycolormap, colormap_size, colormap); if (num_bin_edges == 0) { @@ -10999,7 +10893,6 @@ static void processStairs(const std::shared_ptr &element, const st if (element->parentElement()->parentElement()->hasAttribute("marginal_heatmap_side_plot")) { double y_max = 0; - unsigned int z_length = 0; int x_offset = 0, y_offset = 0; std::vector z_vec; @@ -11022,7 +10915,6 @@ static void processStairs(const std::shared_ptr &element, const st auto z = static_cast(element_context->getAttribute("z")); z_vec = GRM::get>((*context)[z]); - z_length = z_vec.size(); std::vector xi_vec((is_vertical ? y_length - y_offset : x_length - x_offset)); (*context)["xi" + str] = xi_vec; @@ -11285,18 +11177,14 @@ static void processStem(const std::shared_ptr &element, const std: static void processShade(const std::shared_ptr &element, const std::shared_ptr &context) { int xform = 5, x_bins = 1200, y_bins = 1200, n; - unsigned int point_count; - unsigned int x_length, y_length; std::vector x_vec, y_vec; double *x_p, *y_p; - auto x = static_cast(element->getAttribute("x")); - auto y = static_cast(element->getAttribute("y")); + auto x_key = static_cast(element->getAttribute("x")); + auto y_key = static_cast(element->getAttribute("y")); - x_vec = GRM::get>((*context)[x]); - y_vec = GRM::get>((*context)[y]); - x_length = x_vec.size(); - y_length = y_vec.size(); + x_vec = GRM::get>((*context)[x_key]); + y_vec = GRM::get>((*context)[y_key]); if (element->hasAttribute("transformation")) xform = static_cast(element->getAttribute("transformation")); if (element->hasAttribute("x_bins")) x_bins = static_cast(element->getAttribute("x_bins")); @@ -11318,7 +11206,7 @@ static void processSurface(const std::shared_ptr &element, const s * \param[in] element The GRM::Element that contains the attributes and data keys * \param[in] context The GRM::Context that contains the actual data */ - int accelerate = PLOT_DEFAULT_ACCELERATE; /* this argument decides if GR3 or GRM is used to plot the surface */ + int accelerate = PLOT_DEFAULT_ACCELERATE; /* this argument decides if GR3 or GR is used to plot the surface */ std::vector x_vec, y_vec, z_vec; unsigned int x_length, y_length, z_length; double x_min, x_max, y_min, y_max; @@ -11492,6 +11380,7 @@ static void processLine(const std::shared_ptr &element, const std: del_values del = del_values::update_without_default; int child_id = 0; std::shared_ptr line, marker; + int mask; if (element->hasAttribute("orientation")) { @@ -11507,12 +11396,10 @@ static void processLine(const std::shared_ptr &element, const std: y_vec = GRM::get>((*context)[y]); y_length = y_vec.size(); - int mask; if (!element->hasAttribute("x")) { - int i; x_length = y_length; - for (i = 0; i < y_length; ++i) /* julia starts with 1, so GRM starts with 1 to be consistent */ + for (int i = 0; i < y_length; ++i) /* julia starts with 1, so GRM starts with 1 to be consistent */ { x_vec.push_back(i + 1); } @@ -11542,8 +11429,8 @@ static void processLine(const std::shared_ptr &element, const std: if (int_equals_any(mask, 5, 0, 1, 3, 4, 5)) { - int current_line_colorind; - gr_inqlinecolorind(¤t_line_colorind); + int current_line_color_ind; + gr_inqlinecolorind(¤t_line_color_ind); auto id = static_cast(global_root->getAttribute("_id")); auto str = std::to_string(id); @@ -11566,12 +11453,12 @@ static void processLine(const std::shared_ptr &element, const std: } global_root->setAttribute("_id", ++id); - if (line != nullptr) line->setAttribute("line_color_ind", current_line_colorind); + if (line != nullptr) line->setAttribute("line_color_ind", current_line_color_ind); } if (mask & 2) { - int current_marker_colorind; - gr_inqmarkercolorind(¤t_marker_colorind); + int current_marker_color_ind; + gr_inqmarkercolorind(¤t_marker_color_ind); auto id = static_cast(global_root->getAttribute("_id")); auto str = std::to_string(id); @@ -11595,7 +11482,7 @@ static void processLine(const std::shared_ptr &element, const std: if (marker != nullptr) { - marker->setAttribute("marker_color_ind", current_marker_colorind); + marker->setAttribute("marker_color_ind", current_marker_color_ind); marker->setAttribute("z_index", 2); if (element->hasAttribute("marker_type")) @@ -11971,7 +11858,7 @@ static int set_next_color(const std::string &key, gr_color_type_t color_type, int current_array_index = last_array_index + 1; int color_index = 0; int reset = (color_type == GR_COLOR_RESET); - int gks_errind = GKS_K_NO_ERROR; + int gks_err_ind = GKS_K_NO_ERROR; if (reset || !key.empty()) { @@ -12021,7 +11908,7 @@ static int set_next_color(const std::string &key, gr_color_type_t color_type, if (last_array_index < 0 && !color_rgb_values.empty()) { - gks_inq_color_rep(1, PLOT_CUSTOM_COLOR_INDEX, GKS_K_VALUE_SET, &gks_errind, &saved_color[0], &saved_color[1], + gks_inq_color_rep(1, PLOT_CUSTOM_COLOR_INDEX, GKS_K_VALUE_SET, &gks_err_ind, &saved_color[0], &saved_color[1], &saved_color[2]); } @@ -12070,7 +11957,6 @@ static void processPieSegment(const std::shared_ptr &element, del_values del = del_values::update_without_default; int child_id = 0; std::shared_ptr arc, text_elem; - int color_index; double text_pos[2]; std::string text; @@ -12080,7 +11966,6 @@ static void processPieSegment(const std::shared_ptr &element, start_angle = static_cast(element->getAttribute("start_angle")); end_angle = static_cast(element->getAttribute("end_angle")); - color_index = static_cast(element->getAttribute("color_ind")); text = static_cast(element->getAttribute("text")); if (del != del_values::update_without_default && del != del_values::update_with_default) @@ -12414,7 +12299,7 @@ static void processText(const std::shared_ptr &element, const std: */ gr_savestate(); double tbx[4], tby[4]; - int text_color_ind = 1; + int text_color_ind = 1, scientific_format = 0; bool text_fits = true; auto x = static_cast(element->getAttribute("x")); auto y = static_cast(element->getAttribute("y")); @@ -12424,6 +12309,8 @@ static void processText(const std::shared_ptr &element, const std: auto space = static_cast(static_cast(element->getAttribute("space"))); if (element->hasAttribute("text_color_ind")) text_color_ind = static_cast(element->getAttribute("text_color_ind")); + if (element->hasAttribute("scientific_format")) + scientific_format = static_cast(element->getAttribute("scientific_format")); applyMoveTransformation(element); if (space == CoordinateSpace::WC) @@ -12452,95 +12339,451 @@ static void processText(const std::shared_ptr &element, const std: } else if (height < available_width && width < available_height) { - gr_setcharup(-1.0, 0.0); - gr_settextalign(2, 3); + gr_setcharup(-1.0, 0.0); + gr_settextalign(2, 3); + } + else + { + text_fits = false; + } + } + } + + if (text_fits && redraw_ws && scientific_format == 2) + { + gr_settextcolorind(text_color_ind); // needed to have a visible text after update + gr_textext(x, y, &str[0]); + } + else if (text_fits && redraw_ws && scientific_format == 3) + { + gr_settextcolorind(text_color_ind); // needed to have a visible text after update + gr_mathtex(x, y, &str[0]); + } + else if (text_fits && redraw_ws) + { + gr_settextcolorind(text_color_ind); // needed to have a visible text after update + gr_text(x, y, &str[0]); + } + gr_restorestate(); +} + +static void processTextRegion(const std::shared_ptr &element, + const std::shared_ptr &context) +{ + double viewport[4], char_height; + double x, y; + std::string kind, location, text; + bool is_title; + del_values del = del_values::update_without_default; + std::shared_ptr plot_parent = element->parentElement(), side_region = element->parentElement(), + text_elem; + getPlotParent(plot_parent); + + del = del_values(static_cast(element->getAttribute("_delete_children"))); + clearOldChildren(&del, element); + + gr_inqcharheight(&char_height); + calculateViewport(element); + applyMoveTransformation(element); + + viewport[0] = static_cast(element->getAttribute("viewport_x_min")); + viewport[1] = static_cast(element->getAttribute("viewport_x_max")); + viewport[2] = static_cast(element->getAttribute("viewport_y_min")); + viewport[3] = static_cast(element->getAttribute("viewport_y_max")); + kind = static_cast(plot_parent->getAttribute("kind")); + location = static_cast(side_region->getAttribute("location")); + is_title = side_region->hasAttribute("text_is_title") && static_cast(side_region->getAttribute("text_is_title")); + text = static_cast(side_region->getAttribute("text_content")); + + if (location == "left") + { + x = viewport[0] + 0.5 * char_height; + y = 0.5 * (viewport[2] + viewport[3]); + } + else if (location == "right") + { + x = viewport[1] - 0.5 * char_height; + y = 0.5 * (viewport[2] + viewport[3]); + } + else if (location == "bottom") + { + x = 0.5 * (viewport[0] + viewport[1]); + y = viewport[2] + 0.5 * char_height; + } + else if (location == "top") + { + x = 0.5 * (viewport[0] + viewport[1]); + y = viewport[3]; + if (!is_title) y -= 0.5 * char_height; + } + + if ((del != del_values::update_without_default && del != del_values::update_with_default) && !text.empty()) + { + text_elem = global_render->createText(x, y, text); + text_elem->setAttribute("_child_id", 0); + element->appendChild(text_elem); + } + else + { + if (!text.empty()) + { + text_elem = element->querySelectors("text[_child_id=\"0\"]"); + if (text_elem) global_render->createText(x, y, text, CoordinateSpace::NDC, text_elem); + } + } + if (text_elem) + { + if (location == "left" || location == "top") + global_render->setTextAlign(text_elem, GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_TOP); + if (location == "bottom" || location == "bottom") + global_render->setTextAlign(text_elem, GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_BOTTOM); + if (location == "top" && is_title) text_elem->setAttribute("z_index", 2); + if (location == "left" || location == "right") global_render->setCharUp(text_elem, -1, 0); + } +} + +static void tickLabelAdjustment(const std::shared_ptr &tick_group, int child_id, del_values del) +{ + double char_height; + double available_width, available_height; + double x, y; + double null, x_left = 0, x_right = 1; // todo: change x_left and x_right + double window[4]; + int scientific_format = 0; + std::shared_ptr text_elem, plot_parent = tick_group; + char new_label[256]; + int cur_start = 0, scale = 0; + bool x_flip, y_flip; + + getPlotParent(plot_parent); + gr_inqcharheight(&char_height); + auto text = static_cast(tick_group->getAttribute("tick_label")); + auto width = static_cast(tick_group->getAttribute("width")); + auto axis_type = static_cast(tick_group->parentElement()->getAttribute("axis_type")); + auto value = static_cast(tick_group->getAttribute("value")); + auto label_pos = static_cast(tick_group->parentElement()->getAttribute("label_pos")); + auto pos = static_cast(tick_group->parentElement()->getAttribute("pos")); + // todo: window should come from axis if axis has window defined on it + window[0] = + static_cast(tick_group->parentElement()->parentElement()->parentElement()->getAttribute("window_x_min")); + window[1] = + static_cast(tick_group->parentElement()->parentElement()->parentElement()->getAttribute("window_x_max")); + window[2] = + static_cast(tick_group->parentElement()->parentElement()->parentElement()->getAttribute("window_y_min")); + window[3] = + static_cast(tick_group->parentElement()->parentElement()->parentElement()->getAttribute("window_y_max")); + + if (tick_group->parentElement()->hasAttribute("scale")) + scale = static_cast(tick_group->parentElement()->getAttribute("scale")); + if (plot_parent->hasAttribute("x_flip")) x_flip = static_cast(plot_parent->getAttribute("x_flip")); + if (plot_parent->hasAttribute("y_flip")) y_flip = static_cast(plot_parent->getAttribute("y_flip")); + + gr_wctondc(&x_left, &null); + gr_wctondc(&x_right, &null); + + if (text.empty()) return; + if (axis_type == "x") + { + available_height = 0.8; // Todo: change this number + available_width = x_right - x_left; + x = value; + y = label_pos; + } + else if (axis_type == "y") + { + available_height = x_right - x_left; + available_width = 0.8; // Todo: change this number + x = label_pos; + y = value; + } + + if (is_number(text)) + { + if (text.size() > 7) + { + char text_c[256]; + format_reference_t reference = {1, 1}; + + gr_setscientificformat(3); + snprintf(text_c, 256, "%s", text.c_str()); + text = gr_ftoa(text_c, atof(text.c_str()), &reference); + scientific_format = 3; + } + } + else + { + if (width > available_width) + { + int breakpoint_positions[128]; + int cur_num_breakpoints = 0, i; + double tbx[4], tby[4]; + const char *label = text.c_str(); + for (i = 0; i == 0 || label[i - 1] != '\0'; ++i) + { + if (label[i] == ' ' || label[i] == '\0') + { + /* calculate width of the next part of the label to be drawn */ + new_label[i] = '\0'; + gr_inqtext(x, y, new_label + cur_start, tbx, tby); + gr_wctondc(&tbx[0], &tby[0]); + gr_wctondc(&tbx[1], &tby[1]); + width = tbx[1] - tbx[0]; + new_label[i] = ' '; + + /* add possible breakpoint */ + breakpoint_positions[cur_num_breakpoints++] = i; + + if (width > available_width) + { + /* part is too big but doesn't have a breakpoint in it */ + if (cur_num_breakpoints == 1) + { + new_label[i] = '\0'; + } + else /* part is too big and has breakpoints in it */ + { + /* break label at last breakpoint that still allowed the text to fit */ + new_label[breakpoint_positions[cur_num_breakpoints - 2]] = '\0'; + } + + if ((del != del_values::update_without_default && del != del_values::update_with_default)) + { + text_elem = global_render->createText(x, y, new_label + cur_start); + tick_group->append(text_elem); + text_elem->setAttribute("_child_id", child_id++); + } + else + { + text_elem = tick_group->querySelectors("text[_child_id=" + std::to_string(child_id++) + "]"); + if (text_elem != nullptr) + global_render->createText(x, y, new_label + cur_start, CoordinateSpace::NDC, text_elem); + } + + if (cur_num_breakpoints == 1) + { + cur_start = i + 1; + cur_num_breakpoints = 0; + } + else + { + cur_start = breakpoint_positions[cur_num_breakpoints - 2] + 1; + breakpoint_positions[0] = breakpoint_positions[cur_num_breakpoints - 1]; + cur_num_breakpoints = 1; + } + y -= char_height * 1.5; + } + } + else + { + new_label[i] = label[i]; + } + } + /* 0-terminate the new label */ + new_label[i] = '\0'; + + text = new_label + cur_start; + } + } + + if (del != del_values::update_without_default && del != del_values::update_with_default) + { + text_elem = global_render->createText(x, y, text, CoordinateSpace::WC); + text_elem->setAttribute("_child_id", child_id); + tick_group->append(text_elem); + } + else + { + text_elem = tick_group->querySelectors("text[_child_id=" + std::to_string(child_id) + "]"); + if (text_elem != nullptr) global_render->createText(x, y, text, CoordinateSpace::WC, text_elem); + } + if (text_elem != nullptr && del != del_values::update_without_default) + { + text_elem->setAttribute("text_color_ind", 1); + // set text align if not set by user + if (!(tick_group->hasAttribute("text_align_vertical") && tick_group->hasAttribute("text_align_horizontal"))) + { + if (axis_type == "x") + { + text_elem->setAttribute("text_align_horizontal", GKS_K_TEXT_HALIGN_CENTER); + if (pos <= 0.5 * (window[2] + window[3]) || + ((scale & GR_OPTION_FLIP_Y || y_flip) && pos > 0.5 * (window[2] + window[3]))) + { + text_elem->setAttribute("text_align_vertical", GKS_K_TEXT_VALIGN_TOP); + } + else + { + text_elem->setAttribute("text_align_vertical", GKS_K_TEXT_VALIGN_BOTTOM); + } } - else + else if (axis_type == "y") { - text_fits = false; + text_elem->setAttribute("text_align_vertical", GKS_K_TEXT_VALIGN_HALF); + if ((pos <= 0.5 * (window[0] + window[1]) && !(scale & GR_OPTION_FLIP_X || x_flip)) || + ((scale & GR_OPTION_FLIP_X || x_flip) && pos > 0.5 * (window[0] + window[1]) && + tick_group->parentElement()->parentElement()->localName() != "colorbar")) + { + text_elem->setAttribute("text_align_horizontal", GKS_K_TEXT_HALIGN_RIGHT); + } + else + { + text_elem->setAttribute("text_align_horizontal", GKS_K_TEXT_HALIGN_LEFT); + } } } + if (scientific_format == 3 || tick_group->parentElement()->hasAttribute("scientific_format")) + { + if (scientific_format != 3) + scientific_format = static_cast(tick_group->parentElement()->getAttribute("scientific_format")); + text_elem->setAttribute("scientific_format", scientific_format); + gr_setscientificformat(scientific_format); + } } - if (text_fits && redraw_ws) - { - gr_settextcolorind(text_color_ind); // needed to have a visible text after update - gr_text(x, y, &str[0]); - } - gr_restorestate(); } -static void processTextRegion(const std::shared_ptr &element, - const std::shared_ptr &context) +static void processTickGroup(const std::shared_ptr &element, const std::shared_ptr &context) { - double viewport[4], char_height; + int child_id = 0, z_index = 0; double x, y; - std::string kind, location, text; - bool is_title; + double window[4]; + std::shared_ptr tick_elem, text, grid_line; del_values del = del_values::update_without_default; - std::shared_ptr plot_parent = element->parentElement(), side_region = element->parentElement(), - text_elem; - getPlotParent(plot_parent); + + auto value = static_cast(element->getAttribute("value")); + auto is_major = static_cast(element->getAttribute("is_major")); + auto tick_label = static_cast(element->getAttribute("tick_label")); + auto axis_type = static_cast(element->parentElement()->getAttribute("axis_type")); + auto draw_grid = static_cast(element->parentElement()->getAttribute("draw_grid")); + bool mirrored_axis = element->parentElement()->hasAttribute("mirrored_axis") && + static_cast(element->parentElement()->getAttribute("mirrored_axis")); + // todo: window should come from axis if axis has window defined on it + window[0] = + static_cast(element->parentElement()->parentElement()->parentElement()->getAttribute("window_x_min")); + window[1] = + static_cast(element->parentElement()->parentElement()->parentElement()->getAttribute("window_x_max")); + window[2] = + static_cast(element->parentElement()->parentElement()->parentElement()->getAttribute("window_y_min")); + window[3] = + static_cast(element->parentElement()->parentElement()->parentElement()->getAttribute("window_y_max")); del = del_values(static_cast(element->getAttribute("_delete_children"))); clearOldChildren(&del, element); - gr_inqcharheight(&char_height); - calculateViewport(element); - applyMoveTransformation(element); - - viewport[0] = static_cast(element->getAttribute("viewport_x_min")); - viewport[1] = static_cast(element->getAttribute("viewport_x_max")); - viewport[2] = static_cast(element->getAttribute("viewport_y_min")); - viewport[3] = static_cast(element->getAttribute("viewport_y_max")); - kind = static_cast(plot_parent->getAttribute("kind")); - location = static_cast(side_region->getAttribute("location")); - is_title = side_region->hasAttribute("text_is_title") && static_cast(side_region->getAttribute("text_is_title")); - text = static_cast(side_region->getAttribute("text_content")); - - if (location == "left") - { - x = viewport[0] + 0.5 * char_height; - y = 0.5 * (viewport[2] + viewport[3]); - } - else if (location == "right") + // tick + if (del != del_values::update_without_default && del != del_values::update_with_default) { - x = viewport[1] - 0.5 * char_height; - y = 0.5 * (viewport[2] + viewport[3]); + tick_elem = global_render->createTick(is_major, value); + tick_elem->setAttribute("_child_id", child_id++); + element->append(tick_elem); } - else if (location == "bottom") + else { - x = 0.5 * (viewport[0] + viewport[1]); - y = viewport[2] + 0.5 * char_height; + tick_elem = element->querySelectors("tick[_child_id=" + std::to_string(child_id++) + "]"); + if (tick_elem != nullptr) global_render->createTick(is_major, value, tick_elem); } - else if (location == "top") + if (tick_elem != nullptr) { - x = 0.5 * (viewport[0] + viewport[1]); - y = viewport[3]; - if (!is_title) y -= 0.5 * char_height; + if (!is_major) + z_index = -8; + else + z_index = -4; + if (axis_type == "y") z_index -= 2; + if (element->parentElement()->parentElement()->localName() == "colorbar") z_index = 1; + tick_elem->setAttribute("z_index", z_index); } - if ((del != del_values::update_without_default && del != del_values::update_with_default) && !text.empty()) + // mirrored tick + if (mirrored_axis) { - text_elem = global_render->createText(x, y, text); - text_elem->setAttribute("_child_id", 0); - element->appendChild(text_elem); + if (del != del_values::update_without_default && del != del_values::update_with_default) + { + tick_elem = global_render->createTick(is_major, value); + tick_elem->setAttribute("_child_id", child_id++); + element->append(tick_elem); + } + else + { + tick_elem = element->querySelectors("tick[_child_id=" + std::to_string(child_id++) + "]"); + if (tick_elem != nullptr) global_render->createTick(is_major, value, tick_elem); + } + if (tick_elem != nullptr) + { + if (!is_major) + z_index = -9; + else + z_index = -5; + if (axis_type == "y") z_index -= 2; + tick_elem->setAttribute("z_index", z_index); + tick_elem->setAttribute("is_mirrored", true); + } } - else + + // grid + if (draw_grid) { - if (!text.empty()) + if (del != del_values::update_without_default && del != del_values::update_with_default) { - text_elem = element->querySelectors("text[_child_id=\"0\"]"); - if (text_elem) global_render->createText(x, y, text, CoordinateSpace::NDC, text_elem); + grid_line = global_render->createGridLine(is_major, value); + grid_line->setAttribute("_child_id", child_id++); + element->append(grid_line); + } + else + { + grid_line = element->querySelectors("grid_line[_child_id=" + std::to_string(child_id++) + "]"); + if (grid_line != nullptr) global_render->createGridLine(is_major, value, grid_line); + } + if (grid_line != nullptr) + { + if (!is_major) + z_index = -14; + else + z_index = -12; + if (axis_type == "y") z_index -= 1; + grid_line->setAttribute("z_index", z_index); } } - if (text_elem) - { - if (location == "left" || location == "top") - global_render->setTextAlign(text_elem, GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_TOP); - if (location == "bottom" || location == "bottom") - global_render->setTextAlign(text_elem, GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_BOTTOM); - if (location == "top" && is_title) text_elem->setAttribute("z_index", 2); - if (location == "left" || location == "right") global_render->setCharUp(text_elem, -1, 0); + + tickLabelAdjustment(element, child_id, del); +} + +static void processTick(const std::shared_ptr &element, const std::shared_ptr &context) +{ + int mask = 0; + std::shared_ptr axis_elem = element->parentElement()->parentElement(); + std::shared_ptr plot_parent = element; + getPlotParent(plot_parent); + + auto coordinate_system = plot_parent->querySelectors("coordinate_system"); + bool hide = + (coordinate_system->hasAttribute("hide")) ? static_cast(coordinate_system->getAttribute("hide")) : false; + auto coordinate_system_type = static_cast(coordinate_system->getAttribute("plot_type")); + auto axis_type = static_cast(axis_elem->getAttribute("axis_type")); + auto min_val = static_cast(axis_elem->getAttribute("min_value")); + auto max_val = static_cast(axis_elem->getAttribute("max_value")); + auto org = static_cast(axis_elem->getAttribute("org")); + auto pos = static_cast(axis_elem->getAttribute("pos")); + auto tick = static_cast(axis_elem->getAttribute("tick")); + auto major_count = static_cast(axis_elem->getAttribute("major_count")); + auto tick_size = static_cast(axis_elem->getAttribute("tick_size")); + auto tick_orientation = static_cast(axis_elem->getAttribute("tick_orientation")); + auto value = static_cast(element->getAttribute("value")); + auto is_major = static_cast(element->getAttribute("is_major")); + auto label_pos = static_cast(axis_elem->getAttribute("label_pos")); + bool mirrored_axis = element->hasAttribute("is_mirrored") && static_cast(element->getAttribute("is_mirrored")); + + tick_t t = {value, is_major}; + axis_t drawn_tick = {min_val, max_val, tick, org, pos, major_count, 1, &t, tick_size * tick_orientation, + 0, nullptr, label_pos, false}; + if (redraw_ws && !hide && (coordinate_system_type == "2d" || axis_elem->parentElement()->localName() == "colorbar")) + { + mask = mirrored_axis ? 2 : 1; + if (axis_type == "x") + { + gr_drawaxes(&drawn_tick, nullptr, mask); + } + else + { + gr_drawaxes(nullptr, &drawn_tick, mask); + } } } @@ -12556,7 +12799,7 @@ static void processTitles3d(const std::shared_ptr &element, const auto coordinate_system = element->parentElement(); bool hide = (coordinate_system->hasAttribute("hide")) ? static_cast(coordinate_system->getAttribute("hide")) : false; - std::string coordinate_systemType = static_cast(coordinate_system->getAttribute("plot_type")); + auto coordinate_systemType = static_cast(coordinate_system->getAttribute("plot_type")); xlabel = static_cast(element->getAttribute("x_label_3d")); ylabel = static_cast(element->getAttribute("y_label_3d")); zlabel = static_cast(element->getAttribute("z_label_3d")); @@ -13171,12 +13414,26 @@ static void plotCoordinateRanges(const std::shared_ptr &element, { if (kind == "quiver") { + bool x_log = false, y_log = false; + if (element->hasAttribute("x_log")) x_log = static_cast(element->getAttribute("x_log")); + if (element->hasAttribute("y_log")) y_log = static_cast(element->getAttribute("y_log")); + step = grm_max(findMaxStep(current_point_count, current_component), step); if (step > 0.0) { min_component -= step; max_component += step; } + if (static_cast(current_range_keys->subplot) == "x_lim" && x_log) + { + min_component = (min_component > 0) ? min_component : 1; + max_component = (max_component > 0) ? max_component : min_component + 1; + } + if (static_cast(current_range_keys->subplot) == "y_lim" && y_log) + { + min_component = (min_component > 0) ? min_component : 1; + max_component = (max_component > 0) ? max_component : min_component + 1; + } } // TODO: Support mixed orientations std::string orientation = PLOT_DEFAULT_ORIENTATION; @@ -13241,8 +13498,8 @@ static void plotCoordinateRanges(const std::shared_ptr &element, /* For quiver plots use u^2 + v^2 as z value */ if (kind == "quiver") { - double min_component = DBL_MAX; - double max_component = -DBL_MAX; + double z_min = DBL_MAX, z_max = -DBL_MAX; + if (!element->hasAttribute("z_lim_min") || !element->hasAttribute("z_lim_max")) { for (const auto &series : central_region->children()) @@ -13281,17 +13538,17 @@ static void plotCoordinateRanges(const std::shared_ptr &element, current_min_component = static_cast(series->getAttribute("z_range_min")); current_max_component = static_cast(series->getAttribute("z_range_max")); } - min_component = grm_min(current_min_component, min_component); - max_component = grm_max(current_max_component, max_component); + z_min = grm_min(current_min_component, z_min); + z_max = grm_max(current_max_component, z_max); } } else { - min_component = static_cast(element->getAttribute("z_lim_min")); - max_component = static_cast(element->getAttribute("z_lim_max")); + z_min = static_cast(element->getAttribute("z_lim_min")); + z_max = static_cast(element->getAttribute("z_lim_max")); } - element->setAttribute("_z_lim_min", min_component); - element->setAttribute("_z_lim_max", max_component); + element->setAttribute("_z_lim_min", z_min); + element->setAttribute("_z_lim_max", z_max); } else if (str_equals_any(kind, "imshow", "isosurface", "volume")) { @@ -13510,11 +13767,6 @@ static void plotCoordinateRanges(const std::shared_ptr &element, element->setAttribute("_y_lim_max", x_max); } } - else - { - y_min = static_cast(element->getAttribute("y_lim_min")); - y_max = static_cast(element->getAttribute("y_lim_max")); - } } else if (str_equals_any(kind, "stem", "stairs")) { @@ -13616,7 +13868,7 @@ static void processCoordinateSystem(const std::shared_ptr &element { int child_id = 0; del_values del = del_values::update_without_default; - std::shared_ptr polar_axes, axes, grid, grid_3d, axes_3d, titles_3d; + std::shared_ptr polar_axes, axis, grid_3d, axes_3d, titles_3d; std::string type; auto plot_parent = element->parentElement(); getPlotParent(plot_parent); @@ -13856,70 +14108,40 @@ static void processCoordinateSystem(const std::shared_ptr &element } else { - if (str_equals_any(kind, "heatmap", "shade", "marginal_heatmap")) - { - tick_orientation = -1; - } - if (kind != "shade") - { - if (del != del_values::update_without_default && del != del_values::update_with_default) - { - grid = global_render->createEmptyGrid(x_grid, y_grid); - grid->setAttribute("_child_id", child_id++); - element->append(grid); - } - else - { - grid = element->querySelectors("grid[_child_id=" + std::to_string(child_id++) + "]"); - if (grid != nullptr) global_render->createEmptyGrid(x_grid, y_grid, grid); - } - if (grid != nullptr) - { - grid->setAttribute("x_org", 0); - grid->setAttribute("y_org", 0); - } - } + // y-axis if (del != del_values::update_without_default && del != del_values::update_with_default) { - axes = global_render->createEmptyAxes(tick_orientation); - axes->setAttribute("_child_id", child_id++); - element->append(axes); + axis = global_render->createEmptyAxis(); + axis->setAttribute("_child_id", child_id++); + element->append(axis); } else { - axes = element->querySelectors("axes[_child_id=" + std::to_string(child_id++) + "]"); - if (axes != nullptr) - { - if (axes->hasAttribute("tick_orientation") && del != del_values::update_with_default) - tick_orientation = static_cast(axes->getAttribute("tick_orientation")); - global_render->createEmptyAxes(tick_orientation, axes); - } + axis = element->querySelectors("axis[_child_id=" + std::to_string(child_id++) + "]"); + if (axis != nullptr) axis = global_render->createEmptyAxis(axis); } - if (axes != nullptr) + if (axis != nullptr && del != del_values::update_without_default) { - global_render->setOriginPosition(axes, "low", "low"); + axis->setAttribute("axis_type", "y"); + axis->setAttribute("name", "y-axis mirrored"); } + // x-axis if (del != del_values::update_without_default && del != del_values::update_with_default) { - axes = global_render->createEmptyAxes(-tick_orientation); - axes->setAttribute("_child_id", child_id++); - element->append(axes); + axis = global_render->createEmptyAxis(); + axis->setAttribute("_child_id", child_id++); + element->append(axis); } else { - axes = element->querySelectors("axes[_child_id=" + std::to_string(child_id++) + "]"); - if (axes != nullptr) - { - tick_orientation = -tick_orientation; - if (axes->hasAttribute("tick_orientation") && del != del_values::update_with_default) - tick_orientation = static_cast(axes->getAttribute("tick_orientation")); - global_render->createEmptyAxes(tick_orientation, axes); - } + axis = element->querySelectors("axis[_child_id=" + std::to_string(child_id++) + "]"); + if (axis != nullptr) axis = global_render->createEmptyAxis(axis); } - if (axes != nullptr) + if (axis != nullptr && del != del_values::update_without_default) { - global_render->setOriginPosition(axes, "high", "high"); + axis->setAttribute("axis_type", "x"); + axis->setAttribute("name", "x-axis mirrored"); } } } @@ -14263,8 +14485,8 @@ static void processElement(const std::shared_ptr &element, const s static std::map, const std::shared_ptr)>> elemStringToFunc{ - {std::string("axes"), PushDrawableToZQueue(processAxes)}, {std::string("axes_3d"), PushDrawableToZQueue(processAxes3d)}, + {std::string("axis"), processAxis}, {std::string("bar"), processBar}, {std::string("cellarray"), PushDrawableToZQueue(processCellArray)}, {std::string("colorbar"), processColorbar}, @@ -14280,8 +14502,8 @@ static void processElement(const std::shared_ptr &element, const s {std::string("fill_arc"), PushDrawableToZQueue(processFillArc)}, {std::string("fill_area"), PushDrawableToZQueue(processFillArea)}, {std::string("fill_rect"), PushDrawableToZQueue(processFillRect)}, - {std::string("grid"), PushDrawableToZQueue(processGrid)}, {std::string("grid_3d"), PushDrawableToZQueue(processGrid3d)}, + {std::string("grid_line"), PushDrawableToZQueue(processGridLine)}, {std::string("integral"), processIntegral}, {std::string("integral_group"), processIntegralGroup}, {std::string("isosurface_render"), PushDrawableToZQueue(processIsosurfaceRender)}, @@ -14302,13 +14524,15 @@ static void processElement(const std::shared_ptr &element, const s {std::string("side_plot_region"), processSidePlotRegion}, {std::string("text"), PushDrawableToZQueue(processText)}, {std::string("text_region"), processTextRegion}, + {std::string("tick"), PushDrawableToZQueue(processTick)}, + {std::string("tick_group"), processTickGroup}, {std::string("titles_3d"), PushDrawableToZQueue(processTitles3d)}, }; /* Modifier */ - if (str_equals_any(element->localName(), "axes_text_group", "central_region", "figure", "plot", "label", - "labels_group", "root", "x_tick_label_group", "y_tick_label_group", "layout_grid_element", - "side_region", "text_region", "side_plot_region")) + if (str_equals_any(element->localName(), "axes_text_group", "axis", "central_region", "figure", "plot", "label", + "labels_group", "root", "layout_grid_element", "side_region", "text_region", "side_plot_region", + "tick_group")) { bool old_state = automatic_update; automatic_update = false; @@ -14347,6 +14571,8 @@ static void processElement(const std::shared_ptr &element, const s if (element->localName() == "side_region") processSideRegion(element, context); if (element->localName() == "text_region") processTextRegion(element, context); if (element->localName() == "side_plot_region") processSidePlotRegion(element, context); + if (element->localName() == "axis") processAxis(element, context); + if (element->localName() == "tick_group") processTickGroup(element, context); GRM::Render::processAttributes(element); automatic_update = old_state; if (element->localName() != "root") applyMoveTransformation(element); @@ -14364,7 +14590,7 @@ static void processElement(const std::shared_ptr &element, const s // TODO: something like series_contour shouldn't be in this list if (!automatic_update || ((static_cast(global_root->getAttribute("_modified")) && - (str_equals_any(element->localName(), "axes", "axes_3d", "cellarray", "colorbar", "draw_arc", "draw_image", + (str_equals_any(element->localName(), "axes_3d", "cellarray", "colorbar", "draw_arc", "draw_image", "draw_rect", "fill_arc", "fill_area", "fill_rect", "grid", "grid_3d", "legend", "nonuniform_polarcellarray", "nonuniformcellarray", "polarcellarray", "polyline", "polyline_3d", "polymarker", "polymarker_3d", "series_contour", "series_contourf", "text", @@ -14411,8 +14637,7 @@ static void processElement(const std::shared_ptr &element, const s for (const auto &child : element->children()) { if (!str_equals_any(element->localName(), "axes_text_group", "figure", "plot", "label", - "labels_group", "root", "x_tick_label_group", "y_tick_label_group", - "layout_grid_element")) + "labels_group", "root", "layout_grid_element")) { child->setAttribute("_update_required", true); resetOldBoundingBoxes(child); @@ -14703,7 +14928,7 @@ static void applyPlotDefaults(const std::shared_ptr &plot) : false; if (!plot->hasAttribute("adjust_x_lim") || overwrite) { - if (kind == "heatmap" || kind == "marginal_heatmap") + if (kind == "heatmap" || kind == "marginal_heatmap" || kind == "barplot") { plot->setAttribute("adjust_x_lim", 0); } @@ -15192,8 +15417,12 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("stop_row"), std::vector{"None", "Stop row"}}, {std::string("style"), std::vector{"default", "The barplot style (default, lined, stacked)"}}, {std::string("text"), std::vector{"None", "The text displayed by this element"}}, - {std::string("text_align_horizontal"), std::vector{"None", "The horizontal text alignment"}}, - {std::string("text_align_vertical"), std::vector{"None", "The vertical text alignment"}}, + {std::string("text_align_horizontal"), + std::vector{ + "None", "The horizontal text alignment. Defines where the horizontal anker point of the test is placed."}}, + {std::string("text_align_vertical"), + std::vector{ + "None", "The vertical text alignment. Defines where the vertical anker point of the test is placed."}}, {std::string("text_color_ind"), std::vector{"None", "The index of the text-color"}}, {std::string("text_encoding"), std::vector{"utf8", "The internal text encoding"}}, {std::string("theta"), std::vector{"None", "References the theta-values stored in the context"}}, @@ -15270,8 +15499,6 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr std::vector{"0", "The x-direction shift for movable transformation in wc-space"}}, {std::string("x_tick"), std::vector{"1", "The interval between minor tick marks on the x-axis"}}, {std::string("xi"), std::vector{"None", "References the xi-values stored in the context"}}, - {std::string("x_tick_labels"), - std::vector{"None", "References the custom x-tick labels stored in the context"}}, {std::string("x1"), std::vector{"None", "The beginning x-coordinate"}}, {std::string("x2"), std::vector{"None", "The ending x-coordinate"}}, {std::string("y"), std::vector{"None", "References the y-values stored in the context"}}, @@ -15311,8 +15538,6 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("y_shift_wc"), std::vector{"0", "The y-direction shift for movable transformation in wc-space"}}, {std::string("y_tick"), std::vector{"1", "The interval between minor tick marks on the y-axis"}}, - {std::string("y_tick_labels"), - std::vector{"None", "References the custom y-tick labels stored in the context"}}, {std::string("y1"), std::vector{"None", "The beginning y-coordinate"}}, {std::string("y2"), std::vector{"None", "The ending y-coordinate"}}, {std::string("z"), std::vector{"None", "References the z-values stored in the context"}}, @@ -15624,42 +15849,59 @@ std::shared_ptr GRM::Render::createCellArray(double xmin, double x return element; } -std::shared_ptr GRM::Render::createAxes(double x_tick, double y_tick, double x_org, double y_org, - int x_major, int y_major, int tick_orientation, +std::shared_ptr GRM::Render::createEmptyAxis(const std::shared_ptr &ext_element) +{ + std::shared_ptr element = (ext_element == nullptr) ? createElement("axis") : ext_element; + return element; +} + +std::shared_ptr GRM::Render::createAxis(double min_val, double max_val, double tick, double org, + double pos, int major_count, int num_ticks, int num_tick_labels, + double tick_size, int tick_orientation, double label_pos, const std::shared_ptr &ext_element) { - /*! - * This function can be used for creating an Axes GRM::Element - * - * \param[in] x_tick A double value - * \param[in] y_tick A double value - * \param[in] x_org A double value - * \param[in] y_org A double value - * \param[in] x_major An Integer value - * \param[in] y_major An Integer value - * \param[in] tick_orientation A Double value - */ - std::shared_ptr element = (ext_element == nullptr) ? createElement("axes") : ext_element; - element->setAttribute("x_tick", x_tick); - element->setAttribute("y_tick", y_tick); - element->setAttribute("x_org", x_org); - element->setAttribute("y_org", y_org); - element->setAttribute("x_major", x_major); - element->setAttribute("y_major", y_major); + std::shared_ptr element = (ext_element == nullptr) ? createElement("axis") : ext_element; + element->setAttribute("min_value", min_val); + element->setAttribute("max_value", max_val); + element->setAttribute("tick", tick); + element->setAttribute("org", org); + element->setAttribute("pos", pos); + element->setAttribute("major_count", major_count); + element->setAttribute("num_ticks", num_ticks); + element->setAttribute("num_tick_labels", num_tick_labels); + element->setAttribute("tick_size", tick_size); element->setAttribute("tick_orientation", tick_orientation); + element->setAttribute("label_pos", label_pos); return element; } -std::shared_ptr GRM::Render::createEmptyAxes(int tick_orientation, +std::shared_ptr GRM::Render::createTickGroup(int is_major, std::string tick_label, double value, + double width, const std::shared_ptr &ext_element) { - /*! - * This function can be used for creating an Axes GRM::Element with missing information - * - * \param[in] tick_orientation A Int value specifying the direction of the ticks - */ - std::shared_ptr element = (ext_element == nullptr) ? createElement("axes") : ext_element; - element->setAttribute("tick_orientation", tick_orientation); + std::shared_ptr element = (ext_element == nullptr) ? createElement("tick_group") : ext_element; + element->setAttribute("is_major", is_major); + element->setAttribute("tick_label", tick_label); + element->setAttribute("value", value); + element->setAttribute("width", width); + return element; +} + +std::shared_ptr GRM::Render::createTick(int is_major, double value, + const std::shared_ptr &ext_element) +{ + std::shared_ptr element = (ext_element == nullptr) ? createElement("tick") : ext_element; + element->setAttribute("is_major", is_major); + element->setAttribute("value", value); + return element; +} + +std::shared_ptr GRM::Render::createGridLine(int is_major, double value, + const std::shared_ptr &ext_element) +{ + std::shared_ptr element = (ext_element == nullptr) ? createElement("grid_line") : ext_element; + element->setAttribute("is_major", is_major); + element->setAttribute("value", value); return element; } @@ -15677,7 +15919,7 @@ std::shared_ptr GRM::Render::createLegend(const std::string &label * * \param[in] labels_key A std::string for the labels vector * \param[in] labels May be an std::vector> containing the labels - * \param[in] spec An std::string + * \param[in] spec A std::string */ std::shared_ptr element = (ext_element == nullptr) ? createElement("legend") : ext_element; @@ -15773,34 +16015,6 @@ std::shared_ptr GRM::Render::createBar(const double x1, const doub return element; } -std::shared_ptr GRM::Render::createGrid(double x_tick, double y_tick, double x_org, double y_org, - int major_x, int major_y, - const std::shared_ptr &ext_element) -{ - std::shared_ptr element = (ext_element == nullptr) ? createElement("grid") : ext_element; - element->setAttribute("x_tick", x_tick); - element->setAttribute("y_tick", y_tick); - element->setAttribute("x_org", x_org); - element->setAttribute("y_org", y_org); - element->setAttribute("major_x", major_x); - element->setAttribute("major_y", major_y); - return element; -} - -std::shared_ptr GRM::Render::createEmptyGrid(bool x_grid, bool y_grid, - const std::shared_ptr &extElement) -{ - std::shared_ptr element = (extElement == nullptr) ? createElement("grid") : extElement; - if (!x_grid) - { - element->setAttribute("x_tick", 0); - } - if (!y_grid) - { - element->setAttribute("y_tick", 0); - } - return element; -} std::shared_ptr GRM::Render::createSeries(const std::string &name) { @@ -17088,15 +17302,13 @@ void GRM::Render::setXTickLabels(const std::shared_ptr &element, c * \param[in] ext_context A GRM::Context that is used for storing the vectors. By default it uses GRM::Render's * GRM::Context object but an external GRM::Context can be used */ - std::shared_ptr use_context = (ext_context == nullptr) ? context : ext_context; if (x_tick_labels != std::nullopt) { (*use_context)[key] = *x_tick_labels; } - element->setAttribute("x_tick_labels", key); + element->setAttribute("_x_tick_labels", key); } - void GRM::Render::setYTickLabels(const std::shared_ptr &element, const std::string &key, std::optional> y_tick_labels, const std::shared_ptr &ext_context) @@ -17109,13 +17321,12 @@ void GRM::Render::setYTickLabels(const std::shared_ptr &element, c * \param[in] ext_context A GRM::Context that is used for storing the vectors. By default it uses GRM::Render's * GRM::Context object but an external GRM::Context can be used */ - std::shared_ptr use_context = (ext_context == nullptr) ? context : ext_context; if (y_tick_labels != std::nullopt) { (*use_context)[key] = *y_tick_labels; } - element->setAttribute("y_tick_labels", key); + element->setAttribute("_y_tick_labels", key); } void GRM::Render::setNextColor(const std::shared_ptr &element, const std::string &color_indices_key, @@ -17202,8 +17413,7 @@ void GRM::Render::getAutoUpdate(bool *update) *update = automatic_update; } -/* ~~~~~~~~~~~~~~~~~~~~~~~~~ filter functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~ filter functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ static void setRanges(const std::shared_ptr &element, const std::shared_ptr &new_series) { if (element->hasAttribute("x_range_min")) @@ -17444,6 +17654,8 @@ void updateFilter(const std::shared_ptr &element, const std::strin new_series->setAttribute("x", element->getAttribute("x")); new_series->setAttribute("y", element->getAttribute("y")); new_series->setAttribute("_bbox_id", -1); + if (element->hasAttribute("orientation")) + new_series->setAttribute("orientation", static_cast(element->getAttribute("orientation"))); if (static_cast(central_region->getAttribute("keep_window"))) setRanges(element, new_series); for (const auto &child : element->children()) { @@ -17477,18 +17689,26 @@ void updateFilter(const std::shared_ptr &element, const std::strin new_element = new_series; if (value == "marginal_heatmap") { + std::shared_ptr first_side_region = nullptr; // kind was 'marginal_heatmap' so the marginal_heatmap_plot must be removed and a new series created for (const auto &child : element->children()) { if (child->localName() == "side_region") { element->parentElement()->append(child); + if (first_side_region == nullptr) first_side_region = child; for (const auto &side_region_child : child->children()) { - side_region_child->remove(); + if (side_region_child->localName() == "side_plot_region" && + child->hasAttribute("marginal_heatmap_side_plot")) + { + side_region_child->remove(); + break; + } } if (child->hasAttribute("marginal_heatmap_side_plot")) child->removeAttribute("marginal_heatmap_side_plot"); + continue; } if (child->localName() == "central_region") { @@ -17501,16 +17721,18 @@ void updateFilter(const std::shared_ptr &element, const std::strin } } } - element->parentElement()->append(central_region); + element->parentElement()->insertBefore(central_region, first_side_region); central_region->append(new_series); } else if (static_cast(element->getAttribute("kind")) == "marginal_heatmap") { + std::shared_ptr first_side_region = nullptr; // move the side_regions into the marginal_heatmap_plot for (const auto &side_region_child : central_region->parentElement()->children()) { if (side_region_child->localName() == "side_region") { + if (first_side_region == nullptr) first_side_region = side_region_child; new_series->append(side_region_child); if (side_region_child->querySelectors("colorbar")) { @@ -17522,7 +17744,7 @@ void updateFilter(const std::shared_ptr &element, const std::strin } // create marginal_heatmap_plot as central_region father central_region->parentElement()->insertBefore(new_series, central_region); - new_series->append(central_region); + new_series->insertBefore(central_region, first_side_region); // declare which side_region contains the marginal_heatmap side_plot new_series->querySelectors("side_region[location=\"top\"]") ->setAttribute("marginal_heatmap_side_plot", 1); @@ -17630,6 +17852,8 @@ void updateFilter(const std::shared_ptr &element, const std::strin plot_parent->setAttribute("kind", static_cast(element->getAttribute("kind"))); new_series->setAttribute("x", element->getAttribute("x")); new_series->setAttribute("_bbox_id", -1); + if (element->hasAttribute("orientation")) + new_series->setAttribute("orientation", static_cast(element->getAttribute("orientation"))); if (static_cast(central_region->getAttribute("keep_window"))) setRanges(element, new_series); if (static_cast(element->getAttribute("kind")) == "hist") { @@ -17809,16 +18033,83 @@ void updateFilter(const std::shared_ptr &element, const std::strin coordinate_system->setAttribute("_update_required", true); coordinate_system->setAttribute("_delete_children", static_cast(del_values::recreate_all_children)); + + for (const auto &child : coordinate_system->children()) + { + if (child->localName() == "axis") + { + if (new_kind == "shade") child->setAttribute("draw_grid", false); + if (new_kind == "hexbin") child->setAttribute("draw_grid", true); + } + } } if (grplot && (new_kind == "barplot" || old_kind == "barplot")) { + // barplot has different x_major value int major_count, x_major = 1; for (const auto &child : coordinate_system->children()) { - if (child->localName() == "grid" || child->localName() == "axes") + if (child->localName() == "axis") { + auto axis_type = static_cast(child->getAttribute("axis_type")); + std::string orientation = PLOT_DEFAULT_ORIENTATION; + if (new_element != nullptr) + orientation = static_cast(new_element->getAttribute("orientation")); + getMajorCount(child, new_kind, major_count); - if (new_kind != "barplot") x_major = major_count; + if (new_kind == "barplot") + { + bool problematic_bar_num = false, only_barplot = true; + auto context = global_render->getContext(); + auto barplots = central_region->querySelectorsAll("series_barplot"); + for (const auto &barplot : barplots) + { + if (!barplot->hasAttribute("style") || + static_cast(barplot->getAttribute("style")) == "default") + { + auto y_key = static_cast(barplot->getAttribute("y")); + std::vector y_vec = GRM::get>((*context)[y_key]); + if (size(y_vec) > 20) + { + problematic_bar_num = true; + break; + } + } + } + x_major = problematic_bar_num ? major_count : 1; + + // barplot has some special default which can only be applied if no other kind is + // included inside the central_region + for (const auto &series : central_region->children()) + { + if (starts_with(series->localName(), "series_") && + series->localName() != "series_barplot") + { + only_barplot = false; + break; + } + } + if (only_barplot) + { + plot_parent->setAttribute("adjust_x_lim", false); + if (axis_type == "x" && orientation == "horizontal") + child->setAttribute("draw_grid", false); + if (axis_type == "y" && orientation == "vertical") + child->setAttribute("draw_grid", false); + } + } + else + { + x_major = major_count; + plot_parent->setAttribute("adjust_x_lim", true); + if (axis_type == "x" && orientation == "horizontal") + child->setAttribute("draw_grid", true); + if (axis_type == "y" && orientation == "vertical") + child->setAttribute("draw_grid", true); + } + // reset attributes on axis so the axis gets recreated which is needed cause the barplot + // has a different window + clearAxisAttributes(child); child->setAttribute("x_major", x_major); } } @@ -17831,6 +18122,66 @@ void updateFilter(const std::shared_ptr &element, const std::strin { if (coordinate_system->hasAttribute("y_line")) coordinate_system->setAttribute("y_line", false); } + // heatmap and marginal_heatmap have a different default behaviour when it comes to adjust lims + if (grplot && (new_kind == "heatmap" || new_kind == "marginal_heatmap")) + { + bool no_other_kind = true; + for (const auto &series : central_region->children()) + { + if (starts_with(series->localName(), "series_") && series->localName() != "series_heatmap") + { + no_other_kind = false; + break; + } + } + + if (no_other_kind) + { + for (const auto &child : coordinate_system->children()) + { + if (child->localName() == "axis") clearAxisAttributes(child); + } + plot_parent->setAttribute("adjust_x_lim", false); + plot_parent->setAttribute("adjust_y_lim", false); + plot_parent->setAttribute("adjust_z_lim", false); + } + } + else if (grplot && (old_kind == "heatmap" || old_kind == "marginal_heatmap")) + { + for (const auto &child : coordinate_system->children()) + { + if (child->localName() == "axis") clearAxisAttributes(child); + } + plot_parent->setAttribute("adjust_x_lim", true); + plot_parent->setAttribute("adjust_y_lim", true); + plot_parent->setAttribute("adjust_z_lim", true); + } + // need to change tick_orientation if old_kind was heatmap, marginal_heatmap or shade and no other + // series of these kinds is left + if (grplot && (str_equals_any(old_kind, "heatmap", "marginal_heatmap", "shade"))) + { + bool one_of_these_kinds_left = true; + for (const auto &series : central_region->children()) + { + if (starts_with(series->localName(), "series_") && + !str_equals_any(series->localName(), "series_heatmap", "series_shade")) + { + one_of_these_kinds_left = false; + break; + } + } + if (!one_of_these_kinds_left) + { + for (const auto &child : coordinate_system->children()) + { + if (child->localName() == "axis") + { + if (child->hasAttribute("tick_orientation")) + child->setAttribute("tick_orientation", 1); + } + } + } + } } else if (new_kind != "imshow" && new_kind != "isosurface") { @@ -17859,12 +18210,13 @@ void updateFilter(const std::shared_ptr &element, const std::strin double offset; int colors; std::shared_ptr side_region = plot->querySelectors("side_region[location=\"right\"]"), - colorbar = plot->querySelectors("colorbar"), - side_plot = side_region->querySelectors("side_plot_region"); + colorbar = plot->querySelectors("colorbar"), side_plot = nullptr; std::tie(offset, colors) = getColorbarAttributes(new_kind, plot); if (side_region == nullptr) side_region = global_render->createSideRegion(PLOT_DEFAULT_SIDEREGION_LOCATION); + else + side_plot = side_region->querySelectors("side_plot_region"); side_region->setAttribute("offset", offset + PLOT_DEFAULT_COLORBAR_OFFSET); side_region->setAttribute("width", PLOT_DEFAULT_COLORBAR_WIDTH); side_region->setAttribute("_update_required", true); diff --git a/lib/grm/src/grm/interaction.cxx b/lib/grm/src/grm/interaction.cxx index 627557c54..3f3396073 100644 --- a/lib/grm/src/grm/interaction.cxx +++ b/lib/grm/src/grm/interaction.cxx @@ -371,14 +371,13 @@ static void moveTransformationHelper(const std::shared_ptr &elemen "labels_group", "titles_3d", "text", - "y_tick_label_group", - "x_tick_label_group", "layout_grid_element", "layout_grid", "central_region", "side_region", "marginal_heatmap_plot", - "legend"}; + "legend", + "axis"}; GRM::Render::getFigureSize(&width, &height, nullptr, nullptr); max_width_height = grm_max(width, height); diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index b7d879b0d..9add9adc2 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -447,9 +447,9 @@ static string_map_entry_t key_to_formats[] = {{"a", "A"}, {"x_grid", "i"}, {"x_label", "s"}, {"x_lim", "D"}, + {"x_log", "i"}, {"x_ind", "i"}, {"x_range", "D"}, - {"x_log", "i"}, {"y", "D"}, {"y_bins", "i"}, {"y_colormap", "i"}, @@ -458,9 +458,9 @@ static string_map_entry_t key_to_formats[] = {{"a", "A"}, {"y_grid", "i"}, {"y_label", "s"}, {"y_lim", "D"}, + {"y_log", "i"}, {"y_ind", "i"}, {"y_range", "D"}, - {"y_log", "i"}, {"z", "D"}, {"z_dims", "I"}, {"z_flip", "i"}, @@ -3585,38 +3585,7 @@ err_t plot_draw_axes(grm_args_t *args, unsigned int pass) } else { - if ((strcmp(kind, "barplot") != 0 && strcmp(kind, "imshow") != 0) || pass == 2) - { - /* xticklabels */ - char **x_tick_labels = nullptr; - unsigned int x_tick_labels_length; - - if (grm_args_first_value(args, "x_tick_labels", "S", &x_tick_labels, &x_tick_labels_length)) - { - std::vector x_tick_labels_vec(x_tick_labels, x_tick_labels + x_tick_labels_length); - int id = static_cast(global_root->getAttribute("_id")); - std::string key = "x_tick_labels" + std::to_string(id); - global_root->setAttribute("_id", ++id); - global_render->setXTickLabels(group, key, x_tick_labels_vec); - } - - /* y_tick_labels */ - char **y_tick_labels = nullptr; - unsigned int y_tick_labels_length; - - if (grm_args_first_value(args, "y_tick_labels", "S", &y_tick_labels, &y_tick_labels_length)) - { - std::vector y_tick_labels_vec(y_tick_labels, y_tick_labels + y_tick_labels_length); - int id = static_cast(global_root->getAttribute("_id")); - std::string key = "y_tick_labels" + std::to_string(id); - global_root->setAttribute("_id", ++id); - global_render->setYTickLabels(group, key, y_tick_labels_vec); - } - } - else if (strcmp(kind, "imshow") == 0) - { - group->setAttribute("hide", 1); - } + if (strcmp(kind, "imshow") == 0) group->setAttribute("hide", 1); } group->setAttribute("plot_type", type); @@ -3669,6 +3638,33 @@ err_t plot_draw_axes(grm_args_t *args, unsigned int pass) group->setAttribute("z_label", z_label); } + /* xticklabels */ + char **x_tick_labels = nullptr; + unsigned int x_tick_labels_length; + + if (grm_args_first_value(args, "x_tick_labels", "S", &x_tick_labels, &x_tick_labels_length)) + { + std::vector x_tick_labels_vec(x_tick_labels, x_tick_labels + x_tick_labels_length); + int id = static_cast(global_root->getAttribute("_id")); + std::string key = "x_tick_labels" + std::to_string(id); + global_root->setAttribute("_id", ++id); + global_render->setXTickLabels(group, key, x_tick_labels_vec); + } + + /* y_tick_labels */ + char **y_tick_labels = nullptr; + unsigned int y_tick_labels_length; + + if (grm_args_first_value(args, "y_tick_labels", "S", &y_tick_labels, &y_tick_labels_length)) + { + std::vector y_tick_labels_vec(y_tick_labels, y_tick_labels + y_tick_labels_length); + int id = static_cast(global_root->getAttribute("_id")); + std::string key = "y_tick_labels" + std::to_string(id); + global_root->setAttribute("_id", ++id); + global_render->setYTickLabels(group, key, y_tick_labels_vec); + } + + return ERROR_NONE; } @@ -3814,7 +3810,7 @@ err_t plot_draw_colorbar(grm_args_t *subplot_args, double off, unsigned int colo { colorbar->setAttribute("x_flip", flip); } - else if (grm_args_values(subplot_args, "y_flip", "i", &flip) && flip) + if (grm_args_values(subplot_args, "y_flip", "i", &flip) && flip) { colorbar->setAttribute("y_flip", flip); } diff --git a/lib/grm/src/grm/utilcpp.cxx b/lib/grm/src/grm/utilcpp.cxx index 26ff49dc2..f9331a2b2 100644 --- a/lib/grm/src/grm/utilcpp.cxx +++ b/lib/grm/src/grm/utilcpp.cxx @@ -38,7 +38,7 @@ bool file_exists(const std::string &name) return (access(name.c_str(), F_OK) != -1); } -bool starts_with(const std::string &str, const std::string &prefix) +bool starts_with(std::string_view str, std::string_view prefix) { return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix); } @@ -94,3 +94,15 @@ std::complex moivre(double r, int x, int n) return {1.0, 0.0}; } } + +bool is_number(std::string_view str) +{ + const std::string em_dash = u8"—"; // gr minus sign + size_t start_pos = 0; + if (starts_with(str, em_dash)) + { + start_pos = em_dash.size(); + } + auto pos = str.find_first_not_of(".-0123456789", start_pos); + return pos == std::string::npos; +} diff --git a/lib/grm/src/grm/utilcpp_int.hxx b/lib/grm/src/grm/utilcpp_int.hxx index c26288a83..131012efa 100644 --- a/lib/grm/src/grm/utilcpp_int.hxx +++ b/lib/grm/src/grm/utilcpp_int.hxx @@ -23,7 +23,7 @@ std::string ltrim(const std::string &s); std::string rtrim(const std::string &s); std::string trim(const std::string &s); bool file_exists(const std::string &name); -bool starts_with(const std::string &str, const std::string &prefix); +bool starts_with(std::string_view str, std::string_view prefix); bool ends_with(const std::string &str, const std::string &suffix); void linspace(double start, double end, int n, std::vector &x); @@ -38,4 +38,6 @@ template constexpr bool str_equals_any(std::string_view targe return ((target == args) || ...); } +bool is_number(std::string_view str); + #endif // GRM_UTIL_INT_HXX_INCLUDED From fb0f856bd2164d0ba9b090cd55565ff4d637b036 Mon Sep 17 00:00:00 2001 From: Verbov Date: Mon, 15 Jul 2024 15:00:44 +0200 Subject: [PATCH 13/48] allow utf-8 encoding inside code-style-check --- .gitlab-ci/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci/test.yml b/.gitlab-ci/test.yml index f1a634371..05b748c4e 100644 --- a/.gitlab-ci/test.yml +++ b/.gitlab-ci/test.yml @@ -71,7 +71,7 @@ code-style-check: [[ "${file}" != 3rdparty* ]] && [[ "${file}" != apps* ]] || continue; [[ "${file}" =~ \.(c|cpp|cxx|m|h|hpp|hxx)$ ]] || continue; grep -vr $'\r' "${file}" >/dev/null || { echo "${file} must not contain carriage return as line endings."; exit 1; }; - file --mime "${file}" | grep "charset=us-ascii" >/dev/null || { echo "${file} must be encoded as ASCII text."; exit 1; }; + file --mime "${file}" | grep -E "charset=(us-ascii|utf-8)" >/dev/null || { echo "${file} must be either encoded in ASCII or UTF-8."; exit 1; }; clang-format -verbose -style=file "${file}" > "${file}.formatted"; if ! diff -q "${file}" "${file}.formatted"; then diff "${file}" "${file}.formatted"; @@ -84,7 +84,7 @@ code-style-check: [[ "${file}" != 3rdparty* ]] && [[ "${file}" != apps* ]] || continue; [[ "${file}" =~ (^|/)CMakeLists.txt$ || "${file}" =~ \.cmake$ ]] || continue; grep -vr $'\r' "${file}" >/dev/null || { echo "${file} must not contain carriage return as line endings."; exit 1; }; - file --mime "${file}" | grep "charset=us-ascii" >/dev/null || { echo "${file} must be encoded as ASCII text."; exit 1; }; + file --mime "${file}" | grep -E "charset=(us-ascii|utf-8)" >/dev/null || { echo "${file} must be either encoded in ASCII or UTF-8."; exit 1; }; echo "Formatting ${file}"; cmake-format "${file}" > "${file}.formatted"; if ! diff -q "${file}" "${file}.formatted"; then From ec0d97d49cd1563cd3464ab06022cea20af5a38b Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Fri, 12 Jul 2024 06:55:34 -0700 Subject: [PATCH 14/48] GR: change ticks drawing order --- lib/gr/gr.c | 215 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 123 insertions(+), 92 deletions(-) diff --git a/lib/gr/gr.c b/lib/gr/gr.c index 7cea59641..cb20170f2 100644 --- a/lib/gr/gr.c +++ b/lib/gr/gr.c @@ -5292,9 +5292,10 @@ void gr_axis(char which, axis_t *axis) int errind, tnr; double wn[4], vp[4]; double x_min, x_max, y_min, y_max; - int scale_option, base, decade, exponent; + int scale_option = 0, base, decade, exponent; double tick, epsilon; - int i, j, k; + int64_t i; + int j, k; double a, a0, tbx[4], tby[4]; char *s; format_reference_t formatReference; @@ -5382,7 +5383,7 @@ void gr_axis(char which, axis_t *axis) { exponent = iround(blog(base, a)); s = (char *)xcalloc(256, sizeof(char)); - snprintf(s, 255, "%d^{%d}", base, exponent); + snprintf(s, 256, "%d^{%d}", base, exponent); axis->tick_labels[k].tick = a; axis->tick_labels[k].label = replace_minus_sign(s); gr_inqtext(0, 0, axis->tick_labels[k].label, tbx, tby); @@ -5410,7 +5411,7 @@ void gr_axis(char which, axis_t *axis) { if (is_nan(axis->tick)) axis->tick = gr_tick(axis->min, axis->max) / 5; axis->num_ticks = (int)((axis->max - axis->min) / axis->tick + 0.5) + 1; - axis->ticks = (double *)xcalloc(axis->num_ticks, sizeof(tick_t)); + axis->ticks = (tick_t *)xcalloc(axis->num_ticks, sizeof(tick_t)); if (axis->major_count > 0) { axis->num_tick_labels = (int)(axis->num_ticks / axis->major_count + 0.5) + 1; @@ -5466,13 +5467,12 @@ void gr_axis(char which, axis_t *axis) } } -void gr_drawaxis(char which, axis_t *axis) +static void draw_axis(char which, axis_t *axis, int pass) { - int errind, tnr, ltype, clsw, halign, valign; - double wn[4], vp[4], clrt[4]; + int errind, tnr, halign, valign; + double wn[4], vp[4]; double tick, minor_tick, major_tick; - int i, pass; - double epsilon; + int i; check_autoinit; @@ -5481,14 +5481,6 @@ void gr_drawaxis(char which, axis_t *axis) gks_inq_current_xformno(&errind, &tnr); gks_inq_xform(tnr, &errind, wn, vp); - /* save linetype and clipping indicator */ - - gks_inq_pline_linetype(&errind, <ype); - gks_inq_clip(&errind, &clsw, clrt); - - gks_set_pline_linetype(GKS_K_LINETYPE_SOLID); - gks_set_clipping(GKS_K_NOCLIP); - if (which == 'X') { tick = axis->tick_size * (wn[3] - wn[2]) / (vp[3] - vp[2]); @@ -5502,9 +5494,9 @@ void gr_drawaxis(char which, axis_t *axis) major_tick = x_log(x_lin(axis->position) + 2 * tick); } - if (axis->tick_size != 0) + if (pass == 0 || pass == 1) { - for (pass = 0; pass <= 1; pass++) + if (axis->tick_size != 0) { for (i = 0; i < axis->num_ticks; i++) { @@ -5527,56 +5519,87 @@ void gr_drawaxis(char which, axis_t *axis) } } } - - if (axis->draw_axis_line) - { - if (which == 'X') - { - start_pline(axis->min, axis->position); - pline(axis->max, axis->position); - end_pline(); - } - else - { - start_pline(axis->position, axis->min); - pline(axis->position, axis->max); - end_pline(); - } - } - - if (axis->major_count > 0) + else if (pass == 2) { - if (axis->num_tick_labels > 0) + if (axis->draw_axis_line) { - /* save text alignment */ - gks_inq_text_align(&errind, &halign, &valign); - if (which == 'X') { - if (axis->position <= wn[2]) - gks_set_text_align(GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_TOP); - else - gks_set_text_align(GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_BOTTOM); + start_pline(axis->min, axis->position); + pline(axis->max, axis->position); + end_pline(); } else { - if (axis->position <= wn[0]) - gks_set_text_align(GKS_K_TEXT_HALIGN_RIGHT, GKS_K_TEXT_VALIGN_HALF); - else - gks_set_text_align(GKS_K_TEXT_HALIGN_LEFT, GKS_K_TEXT_VALIGN_HALF); + start_pline(axis->position, axis->min); + pline(axis->position, axis->max); + end_pline(); } - for (i = 0; i < axis->num_tick_labels; i++) + } + } + else if (pass == 3) + { + if (axis->major_count > 0) + { + if (axis->num_tick_labels > 0) { + /* save text alignment */ + gks_inq_text_align(&errind, &halign, &valign); + if (which == 'X') - text2d(axis->tick_labels[i].tick, axis->label_position, axis->tick_labels[i].label); + { + if (axis->position <= wn[2]) + gks_set_text_align(GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_TOP); + else + gks_set_text_align(GKS_K_TEXT_HALIGN_CENTER, GKS_K_TEXT_VALIGN_BOTTOM); + } else - text2d(axis->label_position, axis->tick_labels[i].tick, axis->tick_labels[i].label); - } + { + if (axis->position <= wn[0]) + gks_set_text_align(GKS_K_TEXT_HALIGN_RIGHT, GKS_K_TEXT_VALIGN_HALF); + else + gks_set_text_align(GKS_K_TEXT_HALIGN_LEFT, GKS_K_TEXT_VALIGN_HALF); + } + for (i = 0; i < axis->num_tick_labels; i++) + { + if (which == 'X') + text2d(axis->tick_labels[i].tick, axis->label_position, axis->tick_labels[i].label); + else + text2d(axis->label_position, axis->tick_labels[i].tick, axis->tick_labels[i].label); + } - /* restore text alignment */ - gks_set_text_align(halign, valign); + /* restore text alignment */ + gks_set_text_align(halign, valign); + } } } +} + +void gr_drawaxis(char which, axis_t *axis) +{ + int errind, tnr, ltype, clsw, halign, valign; + double wn[4], vp[4], clrt[4]; + int i, pass; + + check_autoinit; + + /* inquire current normalization transformation */ + + gks_inq_current_xformno(&errind, &tnr); + gks_inq_xform(tnr, &errind, wn, vp); + + /* save linetype and clipping indicator */ + + gks_inq_pline_linetype(&errind, <ype); + gks_inq_clip(&errind, &clsw, clrt); + + gks_set_pline_linetype(GKS_K_LINETYPE_SOLID); + gks_set_clipping(GKS_K_NOCLIP); + + for (pass = 0; pass <= 3; pass++) + { + draw_axis(which, axis, pass); + } /* restore linetype and clipping indicator */ @@ -5637,8 +5660,7 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) int errind, tnr, ltype, clsw; double wn[4], vp[4], clrt[4]; double tick, minor_tick, major_tick; - int i, pass; - double epsilon; + int pass; axis_t axis; check_autoinit; @@ -5665,40 +5687,43 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) } } - if ((options & GR_AXES_TWIN_AXES) != 0) + for (pass = 0; pass <= 3; pass++) { - if (y_axis != NULL) + if ((options & GR_AXES_TWIN_AXES) != 0) { - memcpy(&axis, y_axis, sizeof(axis_t)); - y_axis->position = wn[1]; - if (lx.scale_options & GR_OPTION_FLIP_X) y_axis->position = wn[0]; - y_axis->tick_size = -y_axis->tick_size; - y_axis->major_count = -y_axis->major_count; - gr_drawaxis('Y', y_axis); - memcpy(y_axis, &axis, sizeof(axis_t)); + if (y_axis != NULL) + { + memcpy(&axis, y_axis, sizeof(axis_t)); + y_axis->position = wn[1]; + if (lx.scale_options & GR_OPTION_FLIP_X) y_axis->position = wn[0]; + y_axis->tick_size = -y_axis->tick_size; + y_axis->major_count = -y_axis->major_count; + draw_axis('Y', y_axis, pass); + memcpy(y_axis, &axis, sizeof(axis_t)); + } + } + if ((options & GR_AXES_SIMPLE_AXES) != 0) + { + if (y_axis != NULL) draw_axis('Y', y_axis, pass); } - } - if ((options & GR_AXES_SIMPLE_AXES) != 0) - { - if (y_axis != NULL) gr_drawaxis('Y', y_axis); - } - if ((options & GR_AXES_TWIN_AXES) != 0) - { - if (x_axis != NULL) + if ((options & GR_AXES_TWIN_AXES) != 0) { - memcpy(&axis, x_axis, sizeof(axis_t)); - x_axis->position = wn[3]; - if (lx.scale_options & GR_OPTION_FLIP_Y) x_axis->position = wn[2]; - x_axis->tick_size = -x_axis->tick_size; - x_axis->major_count = -x_axis->major_count; - gr_drawaxis('X', x_axis); - memcpy(x_axis, &axis, sizeof(axis_t)); + if (x_axis != NULL) + { + memcpy(&axis, x_axis, sizeof(axis_t)); + x_axis->position = wn[3]; + if (lx.scale_options & GR_OPTION_FLIP_Y) x_axis->position = wn[2]; + x_axis->tick_size = -x_axis->tick_size; + x_axis->major_count = -x_axis->major_count; + draw_axis('X', x_axis, pass); + memcpy(x_axis, &axis, sizeof(axis_t)); + } + } + if ((options & GR_AXES_SIMPLE_AXES) != 0) + { + if (x_axis != NULL) draw_axis('X', x_axis, pass); } - } - if ((options & GR_AXES_SIMPLE_AXES) != 0) - { - if (x_axis != NULL) gr_drawaxis('X', x_axis); } /* restore linetype and clipping indicator */ @@ -5710,15 +5735,21 @@ void gr_drawaxes(axis_t *x_axis, axis_t *y_axis, int options) void gr_freeaxis(axis_t *axis) { int i; - for (i = 0; i < axis->num_tick_labels; i++) + if (axis != NULL) { - free(axis->tick_labels[i].label); - } - if (axis->tick_labels != NULL) - { - free(axis->tick_labels); + if (axis->tick_labels != NULL) + { + for (i = 0; i < axis->num_tick_labels; i++) + { + free(axis->tick_labels[i].label); + } + free(axis->tick_labels); + } + if (axis->ticks != NULL) + { + free(axis->ticks); + } } - free(axis->ticks); } static void grid_line(double x0, double y0, double x1, double y1, int color, int major) From ed884bc738a982e5448ada5440aa1422d973e522 Mon Sep 17 00:00:00 2001 From: Josef Heinen Date: Tue, 16 Jul 2024 10:09:30 +0200 Subject: [PATCH 15/48] GR: fix calculation of number of ticks --- lib/gr/gr.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gr/gr.c b/lib/gr/gr.c index cb20170f2..ea4e8f13e 100644 --- a/lib/gr/gr.c +++ b/lib/gr/gr.c @@ -5363,9 +5363,8 @@ void gr_axis(char which, axis_t *axis) { axis->num_tick_labels = igauss(blog(base, axis->max / axis->min)) + 2; axis->tick_labels = (tick_label_t *)xcalloc(axis->num_tick_labels, sizeof(tick_label_t)); - axis->num_ticks = axis->num_tick_labels * base; + axis->num_ticks = (axis->num_tick_labels + 1) * base; axis->ticks = (tick_t *)xcalloc(axis->num_ticks, sizeof(tick_t)); - a0 = pow(base, gauss(blog(base, axis->min))); i = ipred(axis->min / a0); a = a0 + i * a0; @@ -5392,10 +5391,11 @@ void gr_axis(char which, axis_t *axis) } } } - if (i == 8 || base < 10) + if (i == 9 || base < 10) { a0 = a0 * base; i = 0; + if (j > 0) j--; decade++; } else From 3cbd4a3914254c00565174a1e44b00369302bf23 Mon Sep 17 00:00:00 2001 From: Verbov Date: Wed, 17 Jul 2024 09:57:08 +0200 Subject: [PATCH 16/48] added new structure which includes all axis changes made by the user so they can be restored every time, updated tooltip list, updated grplot comboboxes, fixed is_number and tickLabelAdjustment for negative values --- lib/grm/grplot/README.md | 1 - lib/grm/grplot/grplot_widget.cxx | 34 +- lib/grm/include/grm/dom_render/render.hxx | 14 +- .../grm/dom_render/graphics_tree/schema.xsd | 59 +-- lib/grm/src/grm/dom_render/render.cxx | 231 ++++++++---- lib/grm/src/grm/import.cxx | 22 -- lib/grm/src/grm/plot.cxx | 351 ++++++++++++++++-- lib/grm/src/grm/utilcpp.cxx | 4 +- 8 files changed, 556 insertions(+), 160 deletions(-) diff --git a/lib/grm/grplot/README.md b/lib/grm/grplot/README.md index f920b2b55..427d79fe4 100644 --- a/lib/grm/grplot/README.md +++ b/lib/grm/grplot/README.md @@ -103,7 +103,6 @@ Valid keys are: 7. `phi_lim`, `r_lim`: defines which part of the specific polar axis should be displayed 8. `x_lim`, `y_lim`, `z_lim`: defines which part of the respective axis should be displayed 9. `x_range`, `y_range`, `z_range`: defines the range of the values on the respective axis -10. `x_tick_labels`, `y_tick_labels`: sets the custom labels for the x-, y-axis ticks Values are seperated through commas (`,`), e.g. `3, 5`. diff --git a/lib/grm/grplot/grplot_widget.cxx b/lib/grm/grplot/grplot_widget.cxx index b78e14c20..a1e473085 100644 --- a/lib/grm/grplot/grplot_widget.cxx +++ b/lib/grm/grplot/grplot_widget.cxx @@ -107,6 +107,7 @@ GRPlotWidget::GRPlotWidget(QMainWindow *parent, int argc, char **argv) combo_box_attr = QStringList{ "algorithm", + "axis_type", "colormap", "font", "font_precision", @@ -121,6 +122,7 @@ GRPlotWidget::GRPlotWidget(QMainWindow *parent, int argc, char **argv) "orientation", "projection_type", "resample_method", + "scientific_format", "size_x_type", "size_y_type", "size_x_unit", @@ -142,11 +144,15 @@ GRPlotWidget::GRPlotWidget(QMainWindow *parent, int argc, char **argv) "adjust_z_lim", "disable_x_trans", "disable_y_trans", + "draw_grid", "grplot", "hide", + "is_major", + "is_mirrored", "keep_aspect_ratio", "keep_window", "marginal_heatmap_side_plot", + "mirrored_axis", "movable", "only_quadratic_aspect_ratio", "phi_flip", @@ -519,6 +525,10 @@ void GRPlotWidget::attributeComboBoxHandler(const std::string &cur_attr_name, st model_list.push_back(i.c_str()); } + QStringList axis_type_list{ + "x", + "y", + }; QStringList orientation_list{ "vertical", "horizontal", @@ -570,12 +580,17 @@ void GRPlotWidget::attributeComboBoxHandler(const std::string &cur_attr_name, st "low", "high", }; + QStringList scientific_format_list{ + "textex", + "mathtex", + }; QStringList tick_orientation_list{ "up", "down", }; QStringList side_region_location_list{"top", "right", "bottom", "left"}; static std::map attributeToList{ + {"axis_type", axis_type_list}, {"size_x_unit", size_unit_list}, {"size_y_unit", size_unit_list}, {"colormap", colormap_list}, @@ -592,6 +607,7 @@ void GRPlotWidget::attributeComboBoxHandler(const std::string &cur_attr_name, st {"plot_type", plot_type_list}, {"projection_type", projection_type_list}, {"resample_method", resample_method_list}, + {"scientific_format", scientific_format_list}, {"size_x_type", size_type_list}, {"size_y_type", size_type_list}, {"style", style_list}, @@ -781,6 +797,11 @@ void GRPlotWidget::advancedAttributeComboBoxHandler(const std::string &cur_attr_ { current_text = lineTypeIntToString(static_cast(current_selection->get_ref()->getAttribute(cur_attr_name))); } + else if (cur_attr_name == "scientific_format" && current_selection->get_ref()->getAttribute(cur_attr_name).isInt()) + { + current_text = + scientificFormatIntToString(static_cast(current_selection->get_ref()->getAttribute(cur_attr_name))); + } else if (cur_attr_name == "tick_orientation" && current_selection->get_ref()->getAttribute(cur_attr_name).isInt()) { current_text = @@ -852,6 +873,10 @@ void GRPlotWidget::attributeSetForComboBox(const std::string &attr_type, std::sh { element->setAttribute(label, tickOrientationStringToInt(value)); } + else if (label == "scientific_format") + { + element->setAttribute(label, scientificFormatStringToInt(value)); + } else { element->setAttribute(label, std::stoi(value)); @@ -1212,7 +1237,10 @@ void GRPlotWidget::AttributeEditEvent() amount_scrolled = 0; tree_update = true; clicked.clear(); - std::cerr << GRM::toXML(grm_get_document_root()) << std::endl; + if (getenv("GRM_DEBUG")) + { + std::cerr << toXML(grm_get_document_root(), GRM::SerializerOptions{std::string(2, ' '), true}) << "\n"; + } reset_pixmap(); } else @@ -2520,7 +2548,7 @@ void GRPlotWidget::show_bounding_boxes_slot() void GRPlotWidget::load_file_slot() { - if (enable_editor) + if (getenv("GRDISPLAY") && strcmp(getenv("GRDISPLAY"), "edit") == 0) { #ifndef NO_LIBXML2 std::string path = @@ -2552,7 +2580,7 @@ void GRPlotWidget::load_file_slot() void GRPlotWidget::save_file_slot() { - if (enable_editor) + if (getenv("GRDISPLAY") && strcmp(getenv("GRDISPLAY"), "edit") == 0) { if (grm_get_render() == nullptr) { diff --git a/lib/grm/include/grm/dom_render/render.hxx b/lib/grm/include/grm/dom_render/render.hxx index 699d608a4..c2091f45c 100644 --- a/lib/grm/include/grm/dom_render/render.hxx +++ b/lib/grm/include/grm/dom_render/render.hxx @@ -120,6 +120,7 @@ EXPORT int locationStringToInt(const std::string &location_str); EXPORT int markerTypeStringToInt(const std::string &marker_type_str); EXPORT int projectionTypeStringToInt(const std::string &projection_type_str); EXPORT int modelStringToInt(const std::string &model_str); +EXPORT int scientificFormatStringToInt(const std::string &scientific_format_str); EXPORT int textAlignHorizontalStringToInt(const std::string &text_align_horizontal_str); EXPORT int textAlignVerticalStringToInt(const std::string &text_align_vertical_str); EXPORT int textEncodingStringToInt(const std::string &text_encoding_str); @@ -136,6 +137,7 @@ EXPORT std::string locationIntToString(int location); EXPORT std::string markerTypeIntToString(int marker_type); EXPORT std::string projectionTypeIntToString(int projection_type); EXPORT std::string modelIntToString(int model); +EXPORT std::string scientificFormatIntToString(int scientific_format); EXPORT std::string textAlignHorizontalIntToString(int text_align_horizontal); EXPORT std::string textAlignVerticalIntToString(int text_align_vertical); EXPORT std::string textEncodingIntToString(int text_encoding); @@ -494,14 +496,6 @@ public: void setSubplot(const std::shared_ptr &element, double xmin, double xmax, double ymin, double ymax); - void setXTickLabels(const std::shared_ptr &element, const std::string &key, - std::optional> x_tick_labels, - const std::shared_ptr &extContext = nullptr); - - void setYTickLabels(const std::shared_ptr &element, const std::string &key, - std::optional> y_tick_labels, - const std::shared_ptr &extContext = nullptr); - void setOriginPosition(const std::shared_ptr &element, const std::string &x_org_pos, const std::string &y_org_pos); @@ -523,6 +517,10 @@ public: static void getFigureSize(int *pixel_width, int *pixel_height, double *metric_width, double *metric_height); + std::map>> *getTickModificationMap(); + + int getAxisId(); + void render(); // render doc and render context void render(const std::shared_ptr &extContext); // render doc and external context void render(const std::shared_ptr &document); // external doc and render context diff --git a/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd b/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd index 458f96af5..950418fea 100644 --- a/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd +++ b/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd @@ -173,6 +173,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -360,7 +381,7 @@ - + @@ -382,9 +403,14 @@ + + + + + @@ -394,10 +420,10 @@ - - - - + + + + @@ -437,7 +463,7 @@ - + @@ -770,27 +796,6 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/grm/src/grm/dom_render/render.cxx b/lib/grm/src/grm/dom_render/render.cxx index e78ed0dea..925d63fbf 100644 --- a/lib/grm/src/grm/dom_render/render.cxx +++ b/lib/grm/src/grm/dom_render/render.cxx @@ -222,10 +222,11 @@ static int plot_scatter_markertypes[] = { static int *previous_scatter_marker_type = plot_scatter_markertypes; static int *previous_line_marker_type = plot_scatter_markertypes; -static int bounding_id = 0; +static int bounding_id = 0, axis_id = 0; static bool automatic_update = false; static bool redraw_ws = false; static std::map> bounding_map; +static std::map>> tick_modification_map; static string_map_entry_t kind_to_fmt[] = {{"line", "xys"}, {"hexbin", "xys"}, {"polar", "xys"}, {"shade", "xys"}, @@ -364,6 +365,10 @@ static std::map marker_type_string_to_int{ {"hline", -31}, {"omark", -32}, }; +static std::map scientific_format_string_to_int{ + {"textex", 2}, + {"mathtex", 3}, +}; static std::map text_align_horizontal_string_to_int{ {"normal", 0}, {"left", 1}, @@ -384,6 +389,11 @@ static std::map model_string_to_int{ {"hsv", 1}, }; +/* ~~~~~~~~~~~~~~~~~~~~~~~~~ static function header ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void processTickGroup(const std::shared_ptr &element, + const std::shared_ptr &context); + /* ~~~~~~~~~~~~~~~~~~~~~~~~~ utility functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ static void getPlotParent(std::shared_ptr &element) @@ -1801,6 +1811,30 @@ std::string algorithmIntToString(int algorithm) throw std::logic_error("For volume series the given algorithm is unknown.\n"); } +int scientificFormatStringToInt(const std::string &scientific_format_str) +{ + if (scientific_format_string_to_int.count(scientific_format_str)) + return scientific_format_string_to_int[scientific_format_str]; + else + { + logger((stderr, "Got unknown scientific_format \"%s\"\n", scientific_format_str.c_str())); + throw std::logic_error("Given scientific_format is unknown.\n"); + } +} + +std::string scientificFormatIntToString(int scientific_format) +{ + for (auto const &map_elem : scientific_format_string_to_int) + { + if (map_elem.second == scientific_format) + { + return map_elem.first; + } + } + logger((stderr, "Got unknown scientific_format \"%i\"\n", scientific_format)); + throw std::logic_error("Given scientific_format is unknown.\n"); +} + int getVolumeAlgorithm(const std::shared_ptr &element) { int algorithm; @@ -4674,26 +4708,10 @@ static void axisArgumentsConvertedIntoTickGroups(tick_t *ticks, tick_label_t *ti { int child_id = 1, label_ind = 0; std::shared_ptr tick_group; - std::vector x_tick_labels_vec, y_tick_labels_vec; - int x_tick_labels_len = 0, y_tick_labels_len = 0; auto num_ticks = static_cast(axis->getAttribute("num_ticks")); auto num_labels = static_cast(axis->getAttribute("num_tick_labels")); auto axis_type = static_cast(axis->getAttribute("axis_type")); - auto context = global_render->getContext(); - if (axis->parentElement()->hasAttribute("_x_tick_labels")) - { - auto tick_key = static_cast(axis->parentElement()->getAttribute("_x_tick_labels")); - x_tick_labels_vec = GRM::get>((*context)[tick_key]); - x_tick_labels_len = x_tick_labels_vec.size(); - } - if (axis->parentElement()->hasAttribute("_y_tick_labels")) - { - auto tick_key = static_cast(axis->parentElement()->getAttribute("_y_tick_labels")); - y_tick_labels_vec = GRM::get>((*context)[tick_key]); - y_tick_labels_len = y_tick_labels_vec.size(); - } - if (static_cast(axis->getAttribute("mirrored_axis"))) child_id += 1; for (int i = 0; i < num_ticks; i++) { @@ -4703,16 +4721,6 @@ static void axisArgumentsConvertedIntoTickGroups(tick_t *ticks, tick_label_t *ti { if (tick_labels[label_ind].label) label = tick_labels[label_ind].label; if (tick_labels[label_ind].width) width = tick_labels[label_ind].width; - if (axis_type == "x" && x_tick_labels_len > 0) - { - label = ""; - if (label_ind <= x_tick_labels_len && label_ind >= 1) label = x_tick_labels_vec[label_ind - 1]; - } - if (axis_type == "y" && y_tick_labels_len > 0) - { - label = ""; - if (label_ind <= y_tick_labels_len && label_ind >= 1) label = y_tick_labels_vec[label_ind - 1]; - } label_ind += 1; } @@ -12503,11 +12511,25 @@ static void tickLabelAdjustment(const std::shared_ptr &tick_group, { char text_c[256]; format_reference_t reference = {1, 1}; + int sc_format = 2; + const char minus[3] = {(char)0xe2, (char)0x88, (char)0x92}; // gr minus sign + auto em_dash = std::string(minus); + size_t start_pos = 0; - gr_setscientificformat(3); - snprintf(text_c, 256, "%s", text.c_str()); - text = gr_ftoa(text_c, atof(text.c_str()), &reference); - scientific_format = 3; + if (tick_group->parentElement()->hasAttribute("scientific_format")) + sc_format = static_cast(tick_group->parentElement()->getAttribute("scientific_format")); + gr_setscientificformat(sc_format); + + if (starts_with(text, em_dash)) + { + start_pos = em_dash.size(); + } + auto without_minus = text.substr(start_pos); + + snprintf(text_c, 256, "%s", without_minus.c_str()); + text = gr_ftoa(text_c, atof(without_minus.c_str()), &reference); + text = em_dash + text; + scientific_format = sc_format; } } else @@ -12630,9 +12652,9 @@ static void tickLabelAdjustment(const std::shared_ptr &tick_group, } } } - if (scientific_format == 3 || tick_group->parentElement()->hasAttribute("scientific_format")) + if (scientific_format == 2 || tick_group->parentElement()->hasAttribute("scientific_format")) { - if (scientific_format != 3) + if (scientific_format != 2) scientific_format = static_cast(tick_group->parentElement()->getAttribute("scientific_format")); text_elem->setAttribute("scientific_format", scientific_format); gr_setscientificformat(scientific_format); @@ -12640,6 +12662,56 @@ static void tickLabelAdjustment(const std::shared_ptr &tick_group, } } +static void applyTickModificationMap(const std::shared_ptr &tick_group, + const std::shared_ptr &context, int child_id) +{ + std::shared_ptr text_elem = nullptr; + bool tick_group_attr_changed = false, old_automatic_update = automatic_update; + + auto value = static_cast(tick_group->getAttribute("value")); + auto map_idx = static_cast(tick_group->parentElement()->getAttribute("_axis_id")); + for (const auto &child : tick_group->children()) + { + if (child->localName() == "text") + { + text_elem = child; + break; + } + } + + automatic_update = false; + if (tick_modification_map.find(map_idx) != tick_modification_map.end()) + { + auto tick_value_to_map = tick_modification_map[map_idx]; + if (tick_value_to_map.find(value) != tick_value_to_map.end()) + { + auto key_value_map = tick_value_to_map[value]; + for (auto const &[attr, val] : key_value_map) + { + if (str_equals_any(attr, "is_major", "line_color_ind", "line_spec", "line_type", "line_width", + "text_align_horizontal", "text_align_vertical", "tick_label", "tick_size", "value")) + { + tick_group->setAttribute(attr, val); + if (text_elem != nullptr && attr == "tick_label") + text_elem->setAttribute("text", val); + else if (text_elem == nullptr && attr == "tick_label") + tickLabelAdjustment(tick_group, child_id, del_values::recreate_own_children); + else if (str_equals_any(attr, "is_major", "value")) + tick_group->setAttribute("_update_required", true); + tick_group_attr_changed = true; + } + else if (str_equals_any(attr, "font", "font_precision", "scientific_format", "text", "text_color_ind", + "text_align_horizontal", "text_align_vertical", "x", "y")) + { + text_elem->setAttribute(attr, val); + } + } + if (tick_group_attr_changed) GRM::Render::processAttributes(tick_group); + } + } + automatic_update = old_automatic_update; +} + static void processTickGroup(const std::shared_ptr &element, const std::shared_ptr &context) { int child_id = 0, z_index = 0; @@ -12743,6 +12815,7 @@ static void processTickGroup(const std::shared_ptr &element, const } tickLabelAdjustment(element, child_id, del); + applyTickModificationMap(element, context, child_id); } static void processTick(const std::shared_ptr &element, const std::shared_ptr &context) @@ -12764,6 +12837,8 @@ static void processTick(const std::shared_ptr &element, const std: auto tick = static_cast(axis_elem->getAttribute("tick")); auto major_count = static_cast(axis_elem->getAttribute("major_count")); auto tick_size = static_cast(axis_elem->getAttribute("tick_size")); + if (element->parentElement()->hasAttribute("tick_size")) + tick_size = static_cast(element->parentElement()->getAttribute("tick_size")); auto tick_orientation = static_cast(axis_elem->getAttribute("tick_orientation")); auto value = static_cast(element->getAttribute("value")); auto is_major = static_cast(element->getAttribute("is_major")); @@ -15219,6 +15294,7 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("ambient"), std::vector{"0.2", "The ambient light"}}, {std::string("angle_ticks"), std::vector{"None", "The interval between minor tick marks on the angle-axis"}}, + {std::string("axis_type"), std::vector{"None", "Defines if the axis is getting used for x or y"}}, {std::string("bar_width"), std::vector{"None", "The width of all bars"}}, {std::string("bin_counts"), std::vector{"None", "References the bin-counts stored in the context"}}, {std::string("bin_edges"), std::vector{"None", "References the bin-edges stored in the context"}}, @@ -15261,6 +15337,7 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("hide"), std::vector{"1", "Determines if the element will be visible or not"}}, {std::string("draw_edges"), std::vector{"0", "Used in combination with x- and y-colormap to set if edges are drawn"}}, + {std::string("draw_grid"), std::vector{"None", "Defines if the axis has grid lines or not"}}, {std::string("e_downwards"), std::vector{"None", "The x-value for the downward error"}}, {std::string("e_upwards"), std::vector{"None", "The x-value for the upward error"}}, {std::string("edge_width"), std::vector{"None", "The width of all edges"}}, @@ -15295,6 +15372,8 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr std::vector{"None", "References the upper integral limit-values stored in the context"}}, {std::string("int_limits_low"), std::vector{"None", "References the lower integral limit-values stored in the context"}}, + {std::string("is_major"), std::vector{"None", "Defines if the tick is a major tick"}}, + {std::string("is_mirrored"), std::vector{"None", "Defines if the tick is mirrored"}}, {std::string("isovalue"), std::vector{"0.5", "The used isovalue"}}, {std::string("keep_aspect_ratio"), std::vector{"1", "Sets if the aspect ratio is kept"}}, {std::string("keep_window"), @@ -15303,6 +15382,8 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr std::vector{ "None", "Defines which kind the displayed series has. Depending on the set kind the kind can be changed."}}, {std::string("labels"), std::vector{"None", "The labels which are displayed in the legend"}}, + {std::string("label_pos"), + std::vector{"None", "The offset from the axis where the label should be placed"}}, {std::string("levels"), std::vector{"20", "Number of contour levels"}}, {std::string("line_color_ind"), std::vector{"1", "The line-color index"}}, {std::string("line_color_rgb"), std::vector{"None", "Color for the edges in rgb format"}}, @@ -15310,6 +15391,7 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("line_type"), std::vector{"None", "The type of the line"}}, {std::string("line_width"), std::vector{"None", "The width of the line"}}, {std::string("location"), std::vector{"None", "The elements location"}}, + {std::string("major_count"), std::vector{"None", "Defines the how many tick is a major tick"}}, {std::string("major_h"), std::vector{"0 or 1000", "Defines if and which contour lines gets their value as a label. A offset " "of 1000 to this parameter will color the contour lines"}}, @@ -15325,7 +15407,10 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr std::vector{"None", "References the marker-sizes stored in the context"}}, {std::string("marker_type"), std::vector{"None", "Sets the marker type"}}, {std::string("max_char_height"), std::vector{"0.012", "The maximum height of the chars"}}, + {std::string("max_value"), std::vector{"None", "The maximum value of the axis"}}, {std::string("max_y_length"), std::vector{"None", "The maximum y length inside the barplot"}}, + {std::string("min_value"), std::vector{"None", "The minimum value of the axis"}}, + {std::string("mirrored_axis"), std::vector{"0", "Defines if the axis should be mirrored"}}, {std::string("model"), std::vector{"None", "The used model for the image"}}, {std::string("movable"), std::vector{ @@ -15335,10 +15420,13 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("num_bins"), std::vector{"None", "Number of bins"}}, {std::string("num_col"), std::vector{"None", "Number of columns"}}, {std::string("num_row"), std::vector{"None", "Number of rows"}}, + {std::string("num_tick_labels"), std::vector{"None", "Number of tick labels"}}, + {std::string("num_ticks"), std::vector{"None", "Number of ticks"}}, {std::string("norm"), std::vector{"None", "Specify the used normalisation"}}, {std::string("offset"), std::vector{"None", "The offset for the side region viewport"}}, {std::string("only_quadratic_aspect_ratio"), std::vector{"0", "Sets if the aspect ratio is forced to be quadratic and kept this way"}}, + {std::string("org"), std::vector{"None", "The org of the axis. Needed if org != min_value"}}, {std::string("orientation"), std::vector{"horizontal", "The orientation of the element"}}, {std::string("phi"), std::vector{"None", "References the phi-angles stored in the context"}}, {std::string("phi_dim"), std::vector{"None", "The dimension of the phi-angles"}}, @@ -15355,6 +15443,9 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("plot_x_min"), std::vector{"None", "The beginning x-coordinate of the plot"}}, {std::string("plot_y_max"), std::vector{"None", "The ending y-coordinate of the plot"}}, {std::string("plot_y_min"), std::vector{"None", "The beginning y-coordinate of the plot"}}, + {std::string("pos"), + std::vector{ + "None", "F.e. where the x-axis should be placed in relation to the y-axis (position on the y-axis)"}}, {std::string("projection_type"), std::vector{"None", "The used projection type"}}, {std::string("px"), std::vector{"None", "References the px-values stored in the context. The " "px-values are the modified version of the x-values"}}, @@ -15383,6 +15474,9 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("resample_method"), std::vector{"None", "The used resample method"}}, {std::string("rings"), std::vector{"None", "The number of rings for polar coordinate systems"}}, {std::string("scale"), std::vector{"None", "The set scale"}}, + {std::string("scientific_format"), + std::vector{"None", "Set the used format which will determine how a specific text will be drawn. " + "The text can be plain or for example interpreted with LaTeX."}}, {std::string("select_specific_xform"), std::vector{ "None", "Selects a predefined transformation from world coordinates to normalized device coordinates"}}, @@ -15426,7 +15520,8 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("text_color_ind"), std::vector{"None", "The index of the text-color"}}, {std::string("text_encoding"), std::vector{"utf8", "The internal text encoding"}}, {std::string("theta"), std::vector{"None", "References the theta-values stored in the context"}}, - {std::string("tick"), std::vector{"None", "The polar ticks"}}, + {std::string("tick"), std::vector{"None", "The polar ticks or the interval between minor ticks"}}, + {std::string("tick_label"), std::vector{"", "The label which will be placed next to the tick"}}, {std::string("tick_orientation"), std::vector{"None", "The orientation of the axes ticks"}}, {std::string("tick_size"), std::vector{"0.005", "The size of the ticks"}}, {std::string("title"), std::vector{"None", "The plot title"}}, @@ -15436,6 +15531,7 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr {std::string("u"), std::vector{"None", "References the u-values stored in the context"}}, {std::string("upwards_cap_color"), std::vector{"None", "The color value for the upwards caps"}}, {std::string("v"), std::vector{"None", "References the v-values stored in the context"}}, + {std::string("value"), std::vector{"None", "The value/number of the tick"}}, {std::string("viewport_x_max"), std::vector{"None", "The ending viewport x-coordinate"}}, {std::string("viewport_x_min"), std::vector{"None", "The beginning viewport x-coordinate"}}, {std::string("viewport_y_max"), std::vector{"None", "The ending viewport y-coordinate"}}, @@ -15566,6 +15662,7 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr { if (element->localName() == "text") { + if (attribute_name == "width") return std::vector{"None", "The width of the text"}; if (attribute_name == "x") return std::vector{"None", "x-position of the text"}; if (attribute_name == "y") return std::vector{"None", "y-position of the text"}; } @@ -15577,8 +15674,7 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr } } -/* ~~~~~~~~~~~~~~~~~~~~~~~~~ create functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~ create functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ std::shared_ptr GRM::Render::createPlot(int plotId, const std::shared_ptr &ext_element) { @@ -15852,6 +15948,7 @@ std::shared_ptr GRM::Render::createCellArray(double xmin, double x std::shared_ptr GRM::Render::createEmptyAxis(const std::shared_ptr &ext_element) { std::shared_ptr element = (ext_element == nullptr) ? createElement("axis") : ext_element; + if (!element->hasAttribute("_axis_id")) element->setAttribute("_axis_id", axis_id++); return element; } @@ -15872,6 +15969,7 @@ std::shared_ptr GRM::Render::createAxis(double min_val, double max element->setAttribute("tick_size", tick_size); element->setAttribute("tick_orientation", tick_orientation); element->setAttribute("label_pos", label_pos); + if (!element->hasAttribute("_axis_id")) element->setAttribute("_axis_id", axis_id++); return element; } @@ -17290,45 +17388,6 @@ void GRM::Render::setSubplot(const std::shared_ptr &element, doubl element->setAttribute("plot_y_max", ymax); } -void GRM::Render::setXTickLabels(const std::shared_ptr &element, const std::string &key, - std::optional> x_tick_labels, - const std::shared_ptr &ext_context) -{ - /*! - * This function can be used to create a XTickLabel GRM::Element - * - * \param[in] key A string used for storing the x_tick_labels in GRM::Context - * \param[in] x_tick_labels A vector containing string values representing x_tick_labels - * \param[in] ext_context A GRM::Context that is used for storing the vectors. By default it uses GRM::Render's - * GRM::Context object but an external GRM::Context can be used - */ - std::shared_ptr use_context = (ext_context == nullptr) ? context : ext_context; - if (x_tick_labels != std::nullopt) - { - (*use_context)[key] = *x_tick_labels; - } - element->setAttribute("_x_tick_labels", key); -} -void GRM::Render::setYTickLabels(const std::shared_ptr &element, const std::string &key, - std::optional> y_tick_labels, - const std::shared_ptr &ext_context) -{ - /*! - * This function can be used to create a YTickLabel GRM::Element - * - * \param[in] key A string used for storing the y_tick_labels in GRM::Context - * \param[in] y_tick_labels A vector containing string values representing y_tick_labels - * \param[in] ext_context A GRM::Context that is used for storing the vectors. By default it uses GRM::Render's - * GRM::Context object but an external GRM::Context can be used - */ - std::shared_ptr use_context = (ext_context == nullptr) ? context : ext_context; - if (y_tick_labels != std::nullopt) - { - (*use_context)[key] = *y_tick_labels; - } - element->setAttribute("_y_tick_labels", key); -} - void GRM::Render::setNextColor(const std::shared_ptr &element, const std::string &color_indices_key, const std::vector &color_indices, const std::shared_ptr &ext_context) { @@ -17413,6 +17472,16 @@ void GRM::Render::getAutoUpdate(bool *update) *update = automatic_update; } +std::map>> *GRM::Render::getTickModificationMap() +{ + return &tick_modification_map; +} + +int GRM::Render::getAxisId() +{ + return axis_id; +} + /* ~~~~~~~~~~~~~~~~~~~~~~~~~ filter functions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ static void setRanges(const std::shared_ptr &element, const std::shared_ptr &new_series) { @@ -18499,6 +18568,18 @@ void updateFilter(const std::shared_ptr &element, const std::strin old_shift + static_cast(element->getAttribute(attr)) - std::stod(value)); element->setAttribute("_update_required", true); } + else if (element->localName() == "tick_group") + { + auto val = static_cast(element->getAttribute("value")); + auto map_idx = static_cast(element->parentElement()->getAttribute("_axis_id")); + tick_modification_map[map_idx][val].emplace(attr, element->getAttribute(attr)); + } + else if (element->localName() == "text" && element->parentElement()->localName() == "tick_group") + { + auto val = static_cast(element->parentElement()->getAttribute("value")); + auto map_idx = static_cast(element->parentElement()->parentElement()->getAttribute("_axis_id")); + tick_modification_map[map_idx][val].emplace(attr, element->getAttribute(attr)); + } } global_root->setAttribute("_modified", true); diff --git a/lib/grm/src/grm/import.cxx b/lib/grm/src/grm/import.cxx index 56fed4a56..7fee0a6f9 100644 --- a/lib/grm/src/grm/import.cxx +++ b/lib/grm/src/grm/import.cxx @@ -75,7 +75,6 @@ static std::map key_to_types{ {"x_lim", "dd"}, {"x_log", "i"}, {"x_range", "dd"}, - {"x_tick_labels", "nS"}, {"xye_file", "i"}, {"xyz_file", "i"}, {"y_bins", "i"}, @@ -87,7 +86,6 @@ static std::map key_to_types{ {"y_lim", "dd"}, {"y_log", "i"}, {"y_range", "dd"}, - {"y_tick_labels", "nS"}, {"z_grid", "i"}, {"z_label", "s"}, {"z_lim", "dd"}, @@ -274,26 +272,6 @@ err_t read_data_file(const std::string &path, std::vector tick_labels; - std::stringstream sv(value); - int tmp_size; - const char *tmp; - for (size_t col = 0; std::getline(sv, token, ',') && token.length(); col++) - { - tick_labels.push_back(token); - } - - const int num = static_cast(tick_labels.size()); - std::vector c_tick_label(num); - for (int i = 0; i < tick_labels.size(); i++) - { - c_tick_label[i] = (const char *)tick_labels[i].c_str(); - } - if (!grm_args_values(args, key.c_str(), "nS", &tmp_size, &tmp)) - grm_args_push(args, key.c_str(), "nS", tick_labels.size(), (const char *)c_tick_label.data()); - } else if (str_equals_any(key, "location", "x_log", "y_log", "z_log", "x_grid", "y_grid", "z_grid")) { try diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 9add9adc2..81027b68a 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -258,6 +258,7 @@ const char *valid_subplot_keys[] = {"abs_height", "alpha", "angle_ticks", "aspect_ratio", + "axes_mod", "background_color", "bar_color", "bar_width", @@ -306,7 +307,6 @@ const char *valid_subplot_keys[] = {"abs_height", "x_lim", "x_log", "x_ind", - "x_tick_labels", "y_bins", "y_flip", "y_grid", @@ -314,7 +314,6 @@ const char *valid_subplot_keys[] = {"abs_height", "y_lim", "y_log", "y_ind", - "y_tick_labels", "z_flip", "z_grid", "z_label", @@ -382,6 +381,7 @@ static string_map_entry_t key_to_formats[] = {{"a", "A"}, {"alpha", "d"}, {"aspect_ratio", "d"}, {"append_plots", "i"}, + {"axes_mod", "a"}, {"background_color", "i"}, {"bar_color", "D|i"}, {"c", "D|I"}, @@ -3638,32 +3638,337 @@ err_t plot_draw_axes(grm_args_t *args, unsigned int pass) group->setAttribute("z_label", z_label); } - /* xticklabels */ - char **x_tick_labels = nullptr; - unsigned int x_tick_labels_length; + /* axes modifications */ + grm_args_t *axes_mod = nullptr; + auto tick_modification_map = global_render->getTickModificationMap(); - if (grm_args_first_value(args, "x_tick_labels", "S", &x_tick_labels, &x_tick_labels_length)) + if (grm_args_values(args, "axes_mod", "a", &axes_mod)) { - std::vector x_tick_labels_vec(x_tick_labels, x_tick_labels + x_tick_labels_length); - int id = static_cast(global_root->getAttribute("_id")); - std::string key = "x_tick_labels" + std::to_string(id); - global_root->setAttribute("_id", ++id); - global_render->setXTickLabels(group, key, x_tick_labels_vec); - } + grm_args_iterator_t *axis_it = grm_args_iter(axes_mod); + arg_t *axis_arg; + int axis_id; + while ((axis_arg = axis_it->next(axis_it)) != nullptr) + { + logger((stderr, "Got axis name \"%s\" in \"axes_mod\"\n", axis_arg->key)); + if (!str_equals_any(axis_arg->key, "x", "y")) + { + logger((stderr, "Ignoring invalid axis name \"%s\" in \"axes_mod\"\n", axis_arg->key)); + continue; + } + if (strcmp(axis_arg->key, "x") == 0) + axis_id = global_render->getAxisId() + 1; + else if (strcmp(axis_arg->key, "y") == 0) + axis_id = global_render->getAxisId(); - /* y_tick_labels */ - char **y_tick_labels = nullptr; - unsigned int y_tick_labels_length; + grm_args_t **axis_mods; + if (!grm_args_values(axes_mod, axis_arg->key, "A", &axis_mods)) + { + logger((stderr, "Expected an array of sub containers for axis \"%s\" in \"axes_mod\"\n", axis_arg->key)); + continue; + } + grm_args_t **current_axis_mod = axis_mods; + while (*current_axis_mod != nullptr) + { + double tick_value; + if (!grm_args_values(*current_axis_mod, "tick_value", "d", &tick_value)) + { + int int_tick_value; + if (grm_args_values(*current_axis_mod, "tick_value", "i", &int_tick_value)) + { + tick_value = int_tick_value; + } + else + { + logger((stderr, "Expected a number (integer or double) for \"tick_value\".\n")); + ++current_axis_mod; + continue; + } + } + logger((stderr, "Got tick value \"%lf\" for axis \"%s\"\n", tick_value, axis_arg->key)); + grm_args_iterator_t *tick_it = grm_args_iter(*current_axis_mod); + arg_t *tick_arg; + while ((tick_arg = tick_it->next(tick_it)) != nullptr) + { + // `tick_value` was read before, so ignore it in this loop + if (strcmp(tick_arg->key, "tick_value") == 0) continue; + logger((stderr, "Next tick_arg: \"%s\" on axis \"%s\"\n", tick_arg->key, axis_arg->key)); + if (strcmp(tick_arg->key, "tick_color") == 0) + { + if (strcmp(tick_arg->value_format, "i") != 0) + { + logger((stderr, "Invalid value format \"%s\" for axis modification \"%s\", expected \"i\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + int tick_color; + tick_color = *reinterpret_cast(tick_arg->value_ptr); - if (grm_args_first_value(args, "y_tick_labels", "S", &y_tick_labels, &y_tick_labels_length)) - { - std::vector y_tick_labels_vec(y_tick_labels, y_tick_labels + y_tick_labels_length); - int id = static_cast(global_root->getAttribute("_id")); - std::string key = "y_tick_labels" + std::to_string(id); - global_root->setAttribute("_id", ++id); - global_render->setYTickLabels(group, key, y_tick_labels_vec); - } + (*tick_modification_map)[axis_id][tick_value].emplace("line_color_ind", tick_color); + logger((stderr, "Got tick_color \"%i\"\n", tick_color)); + } + else if (strcmp(tick_arg->key, "line_spec") == 0) + { + if (strcmp(tick_arg->value_format, "s") != 0) + { + logger((stderr, "Invalid value format \"%s\" for axis modification \"%s\", expected \"s\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + const char *line_spec; + line_spec = *reinterpret_cast(tick_arg->value_ptr); + (*tick_modification_map)[axis_id][tick_value].emplace("line_spec", std::string(line_spec)); + logger((stderr, "Got line_spec \"%s\"\n", line_spec)); + } + else if (strcmp(tick_arg->key, "line_type") == 0) + { + if (str_equals_any(tick_arg->value_format, "s", "i") != 0) + { + logger((stderr, + "Invalid value format \"%s\" for axis modification \"%s\", expected \"s\" or " + "\"i\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + if (strcmp(tick_arg->value_format, "s") == 0) + { + const char *line_type; + line_type = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("line_type", std::string(line_type)); + logger((stderr, "Got line_type \"%s\"\n", line_type)); + } + else + { + int line_type; + line_type = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("line_type", line_type); + logger((stderr, "Got line_type \"%i\"\n", line_type)); + } + } + else if (strcmp(tick_arg->key, "tick_length") == 0) + { + if (strcmp(tick_arg->value_format, "d") != 0) + { + logger((stderr, "Invalid value format \"%s\" for axis modification \"%s\", expected \"d\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + double tick_length; + tick_length = *reinterpret_cast(tick_arg->value_ptr); + (*tick_modification_map)[axis_id][tick_value].emplace("tick_size", tick_length); + logger((stderr, "Got tick_length \"%lf\"\n", tick_length)); + } + else if (strcmp(tick_arg->key, "text_align_horizontal") == 0) + { + if (str_equals_any(tick_arg->value_format, "s", "i") != 0) + { + logger((stderr, + "Invalid value format \"%s\" for axis modification \"%s\", expected \"s\" or " + "\"i\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + if (strcmp(tick_arg->value_format, "s") == 0) + { + const char *text_align_horizontal; + text_align_horizontal = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("text_align_horizontal", + std::string(text_align_horizontal)); + logger((stderr, "Got text_align_horizontal \"%s\"\n", text_align_horizontal)); + } + else + { + int text_align_horizontal; + text_align_horizontal = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("text_align_horizontal", + text_align_horizontal); + logger((stderr, "Got text_align_horizontal \"%i\"\n", text_align_horizontal)); + } + } + else if (strcmp(tick_arg->key, "text_align_vertical") == 0) + { + if (str_equals_any(tick_arg->value_format, "s", "i") != 0) + { + logger((stderr, + "Invalid value format \"%s\" for axis modification \"%s\", expected \"s\" or " + "\"i\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + if (strcmp(tick_arg->value_format, "s") == 0) + { + const char *text_align_vertical; + text_align_vertical = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("text_align_vertical", + std::string(text_align_vertical)); + logger((stderr, "Got text_align_vertical \"%s\"\n", text_align_vertical)); + } + else + { + int text_align_vertical; + text_align_vertical = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("text_align_vertical", + text_align_vertical); + logger((stderr, "Got text_align_vertical \"%i\"\n", text_align_vertical)); + } + } + else if (strcmp(tick_arg->key, "tick_label") == 0) + { + if (strcmp(tick_arg->value_format, "s") != 0) + { + logger((stderr, "Invalid value format \"%s\" for axis modification \"%s\", expected \"s\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + const char *tick_label; + tick_label = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("tick_label", std::string(tick_label)); + logger((stderr, "Got tick_label \"%s\"\n", tick_label)); + } + else if (strcmp(tick_arg->key, "tick_width") == 0) + { + if (strcmp(tick_arg->value_format, "d") != 0) + { + logger((stderr, "Invalid value format \"%s\" for axis modification \"%s\", expected \"d\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + double tick_width; + tick_width = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("tick_size", tick_width); + logger((stderr, "Got tick_width \"%lf\"\n", tick_width)); + } + else if (strcmp(tick_arg->key, "new_tick_value") == 0) + { + if (strcmp(tick_arg->value_format, "d") != 0) + { + logger((stderr, "Invalid value format \"%s\" for axis modification \"%s\", expected \"d\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + double new_tick_value; + new_tick_value = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("value", new_tick_value); + logger((stderr, "Got new_tick_value \"%lf\"\n", new_tick_value)); + } + else if (strcmp(tick_arg->key, "tick_is_major") == 0) + { + if (strcmp(tick_arg->value_format, "i") != 0) + { + logger((stderr, "Invalid value format \"%s\" for axis modification \"%s\", expected \"i\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + int tick_is_major; + tick_is_major = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("is_major", tick_is_major); + logger((stderr, "Got tick_is_major \"%i\"\n", tick_is_major)); + } + else if (strcmp(tick_arg->key, "tick_label_color") == 0) + { + if (strcmp(tick_arg->value_format, "i") != 0) + { + logger((stderr, "Invalid value format \"%s\" for axis modification \"%s\", expected \"i\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + int tick_label_color; + tick_label_color = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("text_color_ind", tick_label_color); + logger((stderr, "Got tick_label_color \"%i\"\n", tick_label_color)); + } + else if (strcmp(tick_arg->key, "font") == 0) + { + if (str_equals_any(tick_arg->value_format, "s", "i") != 0) + { + logger((stderr, + "Invalid value format \"%s\" for axis modification \"%s\", expected \"s\" or " + "\"i\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + if (strcmp(tick_arg->value_format, "s") == 0) + { + const char *font; + font = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("font", std::string(font)); + logger((stderr, "Got font \"%s\"\n", font)); + } + else + { + int font; + font = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("font", font); + logger((stderr, "Got font \"%i\"\n", font)); + } + } + else if (strcmp(tick_arg->key, "font_precision") == 0) + { + if (str_equals_any(tick_arg->value_format, "s", "i") != 0) + { + logger((stderr, + "Invalid value format \"%s\" for axis modification \"%s\", expected \"s\" or " + "\"i\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + if (strcmp(tick_arg->value_format, "s") == 0) + { + const char *font_precision; + font_precision = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("font_precision", + std::string(font_precision)); + logger((stderr, "Got font_precision \"%s\"\n", font_precision)); + } + else + { + int font_precision; + font_precision = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("font_precision", font_precision); + logger((stderr, "Got font_precision \"%i\"\n", font_precision)); + } + } + else if (strcmp(tick_arg->key, "scientific_format") == 0) + { + if (strcmp(tick_arg->value_format, "i") != 0) + { + logger((stderr, "Invalid value format \"%s\" for axis modification \"%s\", expected \"i\"\n", + tick_arg->value_format, tick_arg->key)); + continue; + } + int scientific_format; + scientific_format = *reinterpret_cast(tick_arg->value_ptr); + + (*tick_modification_map)[axis_id][tick_value].emplace("scientific_format", scientific_format); + logger((stderr, "Got scientific_format \"%i\"\n", scientific_format)); + } + else + { + logger((stderr, "Ignoring unknown axis modification \"%s\" for tick \"%lf\" on axis \"%s\"\n", + tick_arg->key, tick_value, axis_arg->key)); + } + } + args_iterator_delete(tick_it); + ++current_axis_mod; + } + } + args_iterator_delete(axis_it); + } return ERROR_NONE; } diff --git a/lib/grm/src/grm/utilcpp.cxx b/lib/grm/src/grm/utilcpp.cxx index f9331a2b2..b63d55b75 100644 --- a/lib/grm/src/grm/utilcpp.cxx +++ b/lib/grm/src/grm/utilcpp.cxx @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef _WIN64 #include @@ -97,7 +98,8 @@ std::complex moivre(double r, int x, int n) bool is_number(std::string_view str) { - const std::string em_dash = u8"—"; // gr minus sign + const char minus[3] = {(char)0xe2, (char)0x88, (char)0x92}; // gr minus sign + auto em_dash = std::string(minus); size_t start_pos = 0; if (starts_with(str, em_dash)) { From c3cc2834994eed8e3c8ea6faeac19e30c976bf16 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 19 Jul 2024 10:19:29 +0200 Subject: [PATCH 17/48] [GRM] Fix stack buffer overflow in the `is_number` util function --- lib/grm/src/grm/dom_render/render.cxx | 4 ++-- lib/grm/src/grm/utilcpp.cxx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/grm/src/grm/dom_render/render.cxx b/lib/grm/src/grm/dom_render/render.cxx index 925d63fbf..ac05015df 100644 --- a/lib/grm/src/grm/dom_render/render.cxx +++ b/lib/grm/src/grm/dom_render/render.cxx @@ -12512,7 +12512,7 @@ static void tickLabelAdjustment(const std::shared_ptr &tick_group, char text_c[256]; format_reference_t reference = {1, 1}; int sc_format = 2; - const char minus[3] = {(char)0xe2, (char)0x88, (char)0x92}; // gr minus sign + const char minus[] = {(char)0xe2, (char)0x88, (char)0x92, '\0'}; // gr minus sign auto em_dash = std::string(minus); size_t start_pos = 0; @@ -12528,7 +12528,7 @@ static void tickLabelAdjustment(const std::shared_ptr &tick_group, snprintf(text_c, 256, "%s", without_minus.c_str()); text = gr_ftoa(text_c, atof(without_minus.c_str()), &reference); - text = em_dash + text; + if (start_pos != 0) text = em_dash + text; scientific_format = sc_format; } } diff --git a/lib/grm/src/grm/utilcpp.cxx b/lib/grm/src/grm/utilcpp.cxx index b63d55b75..410d324b2 100644 --- a/lib/grm/src/grm/utilcpp.cxx +++ b/lib/grm/src/grm/utilcpp.cxx @@ -98,7 +98,7 @@ std::complex moivre(double r, int x, int n) bool is_number(std::string_view str) { - const char minus[3] = {(char)0xe2, (char)0x88, (char)0x92}; // gr minus sign + const char minus[] = {(char)0xe2, (char)0x88, (char)0x92, '\0'}; // gr minus sign auto em_dash = std::string(minus); size_t start_pos = 0; if (starts_with(str, em_dash)) From df9a85442e0311bc9a84eb69315732b19b75fd03 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 17 Nov 2023 17:27:44 +0100 Subject: [PATCH 18/48] Add Xerces/ICU as Expat / LibXml2 replacement to the build scripts --- .gitlab-ci/build-darwin.yml | 2 +- .gitlab-ci/build-debian.yml | 18 +- .gitlab-ci/build-windows.yml | 53 ++---- 3rdparty/Makefile | 2 +- 3rdparty/expat/Makefile | 59 ------ 3rdparty/glfw/Makefile | 1 + 3rdparty/icu/Makefile | 74 ++++++++ 3rdparty/icu/Makefile.macos_x86_64_cross | 100 ++++++++++ 3rdparty/icu/Makefile.mingw | 134 ++++++++++++++ 3rdparty/libxml2/Makefile | 61 ------ 3rdparty/makeself.sh | 2 +- 3rdparty/xerces-c/Makefile | 192 +++++++++++++++++++ CMakeLists.txt | 44 +---- cmake/FindExpat.cmake | 73 -------- cmake/FindXercesC.cmake | 86 +++++++++ js/Makefile | 224 +++++++++++------------ lib/Preflight | 64 +++---- lib/grm/Makefile | 19 +- lib/grm/grplot/grplot.pro | 2 +- lib/grm/grplot/grplot_widget.cxx | 4 +- lib/grm/grplot/makefile.mingw | 10 +- lib/grm/include/grm/plot.h | 2 + lib/grm/makefile.mingw | 10 +- lib/grm/src/grm/plot.cxx | 16 ++ lib/grm/src/grm/util.c | 1 + packaging/debian/debian.rules | 2 +- packaging/redhat/gr.spec | 2 +- 27 files changed, 800 insertions(+), 457 deletions(-) delete mode 100644 3rdparty/expat/Makefile create mode 100644 3rdparty/icu/Makefile create mode 100644 3rdparty/icu/Makefile.macos_x86_64_cross create mode 100644 3rdparty/icu/Makefile.mingw delete mode 100644 3rdparty/libxml2/Makefile create mode 100644 3rdparty/xerces-c/Makefile delete mode 100644 cmake/FindExpat.cmake create mode 100644 cmake/FindXercesC.cmake diff --git a/.gitlab-ci/build-darwin.yml b/.gitlab-ci/build-darwin.yml index 66543a346..50080b283 100644 --- a/.gitlab-ci/build-darwin.yml +++ b/.gitlab-ci/build-darwin.yml @@ -176,12 +176,12 @@ darwin-self-contained: CAIRO_EXTRA_CONFIGURE_FLAGS="--host=x86_64-apple-darwin" FFMPEG_EXTRA_CONFIGURE_FLAGS="--arch=x86_64 --cc=\"cc -arch x86_64\" --cxx=\"c++ -arch x86_64\" --enable-cross-compile" GLFW_EXTRA_CMAKE_FLAGS="-DCMAKE_OSX_ARCHITECTURES=x86_64" - LIBXML2_EXTRA_CONFIGURE_FLAGS="--host=x86_64-apple-darwin" OGG_EXTRA_CONFIGURE_FLAGS="--host=x86_64-apple-darwin" OPENH264_EXTRA_MAKE_FLAGS="ARCH=x86_64" PIXMAN_EXTRA_CONFIGURE_FLAGS="--host=x86_64-apple-darwin" THEORA_EXTRA_CONFIGURE_FLAGS="--host=x86_64-apple-darwin --disable-asm" TIFF_EXTRA_CONFIGURE_FLAGS="--host=x86_64-apple-darwin" + XERCES_C_EXTRA_CMAKE_FLAGS="-DCMAKE_OSX_ARCHITECTURES=x86_64" ZEROMQ_EXTRA_CONFIGURE_FLAGS="--host=x86_64-apple-darwin" - make self ARCH="x86_64" diff --git a/.gitlab-ci/build-debian.yml b/.gitlab-ci/build-debian.yml index b44a487b3..0c42429c6 100644 --- a/.gitlab-ci/build-debian.yml +++ b/.gitlab-ci/build-debian.yml @@ -58,6 +58,7 @@ debian-self-contained-armhf: STRIP=arm-linux-gnueabihf-strip \ PNG_EXTRA_CFLAGS="-DPNG_ARM_NEON_OPT=0" \ GLFW_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/armhf-linux-gnu.cmake \ + XERCES_C_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/armhf-linux-gnu.cmake \ OGG_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf \ THEORA_EXTRA_CONFIGURE_FLAGS="--host=arm-linux --disable-asm" \ FFMPEG_EXTRA_CONFIGURE_FLAGS="--cross-prefix=arm-linux-gnueabihf- --arch=armhf --target-os=linux --pkg-config=pkg-config" \ @@ -65,8 +66,7 @@ debian-self-contained-armhf: CAIRO_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihfnu \ TIFF_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf \ OPENH264_EXTRA_MAKE_FLAGS="OS=linux ARCH=armhf" \ - ZEROMQ_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf \ - LIBXML2_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf + ZEROMQ_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf - make self \ CC=arm-linux-gnueabihf-gcc \ LD=arm-linux-gnueabihf-ld \ @@ -109,6 +109,7 @@ debian-self-contained-aarch64: STRIP=aarch64-linux-gnu-strip \ PNG_EXTRA_CFLAGS="-DPNG_ARM_NEON_OPT=0" \ GLFW_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/aarch64-linux-gnu.cmake \ + XERCES_C_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/aarch64-linux-gnu.cmake \ OGG_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu \ THEORA_EXTRA_CONFIGURE_FLAGS="--host=arm-linux --disable-asm" \ FFMPEG_EXTRA_CONFIGURE_FLAGS="--cross-prefix=aarch64-linux-gnu- --arch=aarch64 --target-os=linux --pkg-config=pkg-config" \ @@ -116,8 +117,7 @@ debian-self-contained-aarch64: CAIRO_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu \ TIFF_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu \ OPENH264_EXTRA_MAKE_FLAGS="OS=linux ARCH=aarch64" \ - ZEROMQ_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu \ - LIBXML2_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu + ZEROMQ_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu - make self \ CC=aarch64-linux-gnu-gcc \ LD=aarch64-linux-gnu-ld \ @@ -159,6 +159,7 @@ debian-cmake-self-contained-aarch64: STRIP=aarch64-linux-gnu-strip \ PNG_EXTRA_CFLAGS="-DPNG_ARM_NEON_OPT=0" \ GLFW_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/aarch64-linux-gnu.cmake \ + XERCES_C_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/aarch64-linux-gnu.cmake \ OGG_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu \ THEORA_EXTRA_CONFIGURE_FLAGS="--host=arm-linux --disable-asm" \ FFMPEG_EXTRA_CONFIGURE_FLAGS="--cross-prefix=aarch64-linux-gnu- --arch=aarch64 --target-os=linux --pkg-config=pkg-config" \ @@ -166,8 +167,7 @@ debian-cmake-self-contained-aarch64: CAIRO_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu \ TIFF_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu \ OPENH264_EXTRA_MAKE_FLAGS="OS=linux ARCH=aarch64" \ - ZEROMQ_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu \ - LIBXML2_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu + ZEROMQ_EXTRA_CONFIGURE_FLAGS=--host=aarch64-linux-gnu - wget https://gr-framework.org/downloads/3rdparty/cmake-3.23.0-linux-x86_64.tar.gz - tar xf cmake-3.23.0-linux-x86_64.tar.gz - export CMAKE_CMD=`pwd`/cmake-3.23.0-linux-x86_64/bin/cmake @@ -180,7 +180,7 @@ debian-cmake-self-contained-aarch64: artifacts: expire_in: 1 week paths: - - artifacts-debian10-cmake-aarch64/ + - artifacts-debian10-cmake-aarch64/ debian-cmake-self-contained-armhf: stage: build @@ -196,6 +196,7 @@ debian-cmake-self-contained-armhf: STRIP=arm-linux-gnueabihf-strip \ PNG_EXTRA_CFLAGS="-DPNG_ARM_NEON_OPT=0" \ GLFW_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/armhf-linux-gnu.cmake \ + XERCES_C_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/armhf-linux-gnu.cmake \ OGG_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf \ THEORA_EXTRA_CONFIGURE_FLAGS="--host=arm-linux --disable-asm" \ FFMPEG_EXTRA_CONFIGURE_FLAGS="--cross-prefix=arm-linux-gnueabihf- --arch=armhf --target-os=linux --pkg-config=pkg-config" \ @@ -203,8 +204,7 @@ debian-cmake-self-contained-armhf: CAIRO_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf \ TIFF_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf \ OPENH264_EXTRA_MAKE_FLAGS="OS=linux ARCH=armhf" \ - ZEROMQ_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf \ - LIBXML2_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf + ZEROMQ_EXTRA_CONFIGURE_FLAGS=--host=arm-linux-gnueabihf - wget https://gr-framework.org/downloads/3rdparty/cmake-3.23.0-linux-x86_64.tar.gz - tar xf cmake-3.23.0-linux-x86_64.tar.gz - export CMAKE_CMD=`pwd`/cmake-3.23.0-linux-x86_64/bin/cmake diff --git a/.gitlab-ci/build-windows.yml b/.gitlab-ci/build-windows.yml index 25a1d0e79..843ca238a 100644 --- a/.gitlab-ci/build-windows.yml +++ b/.gitlab-ci/build-windows.yml @@ -6,7 +6,7 @@ windows-32bit-cross: - tar xf cmake-3.23.0-linux-x86_64.tar.gz - export CMAKE_CMD=`pwd`/cmake-3.23.0-linux-x86_64/bin/cmake - make -C 3rdparty default extras - EXTRAS="tiff ogg theora vpx ffmpeg pixman cairo agg libxml2" + EXTRAS="tiff ogg theora vpx ffmpeg pixman cairo agg xerces-c" HOST=i686-w64-mingw32 ARCHITECTURE=i686 OS=w64_x86-cross-mingw32 @@ -14,13 +14,13 @@ windows-32bit-cross: CXX=i686-w64-mingw32-g++ AR=i686-w64-mingw32-ar GLFW_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/i686-w64-mingw32.cmake + XERCES_C_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/i686-w64-mingw32.cmake OGG_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 THEORA_EXTRA_CONFIGURE_FLAGS="--host=i686-w64-mingw32 --disable-asm" FFMPEG_EXTRA_CONFIGURE_FLAGS="--cross-prefix=i686-w64-mingw32- --arch=i686 --target-os=mingw32 --pkg-config=pkg-config" PIXMAN_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 CAIRO_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 TIFF_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 - LIBXML2_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 OPENH264_EXTRA_MAKE_FLAGS="OS=mingw_nt ARCH=i386 CFLAGS=-fno-exceptions CXXFLAGS=-fno-exceptions LDFLAGS=-fno-exceptions" - MAKE="make -f makefile.mingw" make -f makefile.mingw GRDIR=./ @@ -73,7 +73,7 @@ windows-32bit-cmake-cross: - tar xf cmake-3.23.0-linux-x86_64.tar.gz - export CMAKE_CMD=`pwd`/cmake-3.23.0-linux-x86_64/bin/cmake - make -C 3rdparty default extras - EXTRAS="tiff ogg theora vpx ffmpeg pixman cairo agg libxml2" + EXTRAS="tiff ogg theora vpx ffmpeg pixman cairo agg xerces-c" HOST=i686-w64-mingw32 ARCHITECTURE=i686 OS=w64_x86-cross-mingw32 @@ -82,30 +82,14 @@ windows-32bit-cmake-cross: AR=i686-w64-mingw32-ar OPENJP2_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/i686-w64-mingw32.cmake GLFW_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/i686-w64-mingw32.cmake + XERCES_C_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/i686-w64-mingw32.cmake OGG_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 THEORA_EXTRA_CONFIGURE_FLAGS="--host=i686-w64-mingw32 --disable-asm" FFMPEG_EXTRA_CONFIGURE_FLAGS="--cross-prefix=i686-w64-mingw32- --arch=i686 --target-os=mingw32 --pkg-config=pkg-config" PIXMAN_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 CAIRO_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 TIFF_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 - LIBXML2_EXTRA_CONFIGURE_FLAGS=--host=i686-w64-mingw32 OPENH264_EXTRA_MAKE_FLAGS="OS=mingw_nt ARCH=i386 CFLAGS=-fno-exceptions CXXFLAGS=-fno-exceptions LDFLAGS=-fno-exceptions" - - cd 3rdparty/build/lib/cmake/libxml2 - - |- - patch -N <<'EOF' - --- libxml2-config.cmake 2023-05-24 10:50:21.099243441 +0000 - +++ libxml2-config.cmake.patched 2023-05-24 10:50:42.289604090 +0000 - @@ -29,7 +29,7 @@ - set(LIBXML2_VERSION_MINOR 10) - set(LIBXML2_VERSION_MICRO 4) - set(LIBXML2_VERSION_STRING "2.10.4") - -set(LIBXML2_DEFINITIONS " -DLIBXML_STATIC") - +set(LIBXML2_DEFINITIONS -DLIBXML_STATIC) - set(LIBXML2_INSTALL_PREFIX ${_libxml2_rootdir}) - set(LIBXML2_INCLUDE_DIR ${_libxml2_rootdir}/include/libxml2) - set(LIBXML2_LIBRARY_DIR ${_libxml2_rootdir}/lib) - EOF - - cd - - mkdir build - cd build - $CMAKE_CMD .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CI_PROJECT_DIR}/install -DCMAKE_TOOLCHAIN_FILE=`pwd`/../cmake/i686-w64-mingw32.cmake -DCMAKE_MODULE_PATH=${CI_PROJECT_DIR}/qt5-runtime-Windows-i686/cmake -DGR_MANUAL_MOC_AND_RCC=ON -DGR_USE_BUNDLED_LIBRARIES=ON @@ -129,7 +113,7 @@ windows-64bit-cross: - tar xf cmake-3.23.0-linux-x86_64.tar.gz - export CMAKE_CMD=`pwd`/cmake-3.23.0-linux-x86_64/bin/cmake - make -C 3rdparty default extras - EXTRAS="tiff ogg theora vpx openh264 ffmpeg pixman cairo agg libxml2" + EXTRAS="tiff ogg theora vpx openh264 ffmpeg pixman cairo agg xerces-c" HOST=x86_64-w64-mingw32 ARCHITECTURE=x86_64 OS=w64_amd64-cross-mingw32 @@ -137,13 +121,13 @@ windows-64bit-cross: CXX=x86_64-w64-mingw32-g++ AR=x86_64-w64-mingw32-ar GLFW_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/x86_64-w64-mingw32.cmake + XERCES_C_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/x86_64-w64-mingw32.cmake OGG_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 THEORA_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 FFMPEG_EXTRA_CONFIGURE_FLAGS="--cross-prefix=x86_64-w64-mingw32- --arch=x86_64 --target-os=mingw32 --pkg-config=pkg-config" PIXMAN_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 CAIRO_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 TIFF_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 - LIBXML2_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 OPENH264_EXTRA_MAKE_FLAGS=OS=mingw_nt - MAKE="make -f makefile.mingw" make -f makefile.mingw GRDIR=./ @@ -196,7 +180,7 @@ windows-64bit-cmake-cross: - tar xf cmake-3.23.0-linux-x86_64.tar.gz - export CMAKE_CMD=`pwd`/cmake-3.23.0-linux-x86_64/bin/cmake - make -C 3rdparty default extras - EXTRAS="tiff ogg theora vpx openh264 ffmpeg pixman cairo agg libxml2" + EXTRAS="tiff ogg theora vpx openh264 ffmpeg pixman cairo agg xerces-c" HOST=x86_64-w64-mingw32 ARCHITECTURE=x86_64 OS=w64_amd64-cross-mingw32 @@ -205,30 +189,14 @@ windows-64bit-cmake-cross: AR=x86_64-w64-mingw32-ar OPENJP2_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/x86_64-w64-mingw32.cmake GLFW_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/x86_64-w64-mingw32.cmake + XERCES_C_EXTRA_CMAKE_FLAGS=-DCMAKE_TOOLCHAIN_FILE=`pwd`/cmake/x86_64-w64-mingw32.cmake OGG_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 THEORA_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 FFMPEG_EXTRA_CONFIGURE_FLAGS="--cross-prefix=x86_64-w64-mingw32- --arch=x86_64 --target-os=mingw32 --pkg-config=pkg-config" PIXMAN_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 CAIRO_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 TIFF_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 - LIBXML2_EXTRA_CONFIGURE_FLAGS=--host=x86_64-w64-mingw32 OPENH264_EXTRA_MAKE_FLAGS=OS=mingw_nt - - cd 3rdparty/build/lib/cmake/libxml2 - - |- - patch -N <<'EOF' - --- libxml2-config.cmake 2023-05-24 10:50:21.099243441 +0000 - +++ libxml2-config.cmake.patched 2023-05-24 10:50:42.289604090 +0000 - @@ -29,7 +29,7 @@ - set(LIBXML2_VERSION_MINOR 10) - set(LIBXML2_VERSION_MICRO 4) - set(LIBXML2_VERSION_STRING "2.10.4") - -set(LIBXML2_DEFINITIONS " -DLIBXML_STATIC") - +set(LIBXML2_DEFINITIONS -DLIBXML_STATIC) - set(LIBXML2_INSTALL_PREFIX ${_libxml2_rootdir}) - set(LIBXML2_INCLUDE_DIR ${_libxml2_rootdir}/include/libxml2) - set(LIBXML2_LIBRARY_DIR ${_libxml2_rootdir}/lib) - EOF - - cd - - mkdir build - cd build - $CMAKE_CMD .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CI_PROJECT_DIR}/install -DCMAKE_TOOLCHAIN_FILE=`pwd`/../cmake/x86_64-w64-mingw32.cmake -DCMAKE_MODULE_PATH=${CI_PROJECT_DIR}/qt5-runtime-Windows-x86_64/cmake -DGR_MANUAL_MOC_AND_RCC=ON -DGR_USE_BUNDLED_LIBRARIES=ON @@ -242,7 +210,7 @@ windows-64bit-cmake-cross: artifacts: expire_in: 1 week paths: - - artifacts-windows-64bit-cmake/ + - artifacts-windows-64bit-cmake/ windows-64bit-cmake-msvc: stage: build @@ -261,7 +229,7 @@ windows-64bit-cmake-msvc: - cp -v /c/Qt/5.15.2/msvc2019_64/plugins/platforms/qwindows.dll platforms/ - cd - - cd /c/local/bin - - cp -v cairo.dll freetype.dll jpeg62.dll libpng16.dll libxml2.dll pixman-1-0.dll qhull_r.dll tiff.dll tiffxx.dll turbojpeg.dll zlib.dll /c/gr/bin/ + - cp -v cairo.dll freetype.dll jpeg62.dll libpng16.dll pixman-1-0.dll qhull_r.dll tiff.dll tiffxx.dll turbojpeg.dll xerces-c_3_2.dll zlib.dll /c/gr/bin/ - cd - - cp -v "/c/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Redist/MSVC/14.31.31103/x64/Microsoft.VC143.CRT/vcruntime140.dll" /c/gr/bin/ - cp -v "/c/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/VC/Redist/MSVC/14.31.31103/x64/Microsoft.VC143.CRT/vcruntime140_1.dll" /c/gr/bin/ @@ -272,3 +240,4 @@ windows-64bit-cmake-msvc: expire_in: 1 week paths: - artifacts-windows-64bit-cmake-msvc/ + diff --git a/3rdparty/Makefile b/3rdparty/Makefile index f3180004a..3e2540fd7 100644 --- a/3rdparty/Makefile +++ b/3rdparty/Makefile @@ -4,7 +4,7 @@ UNAME := $(shell uname) ifeq ($(UNAME), Darwin) TARGETS += zeromq endif - EXTRAS = agg tiff ogg theora vpx openh264 ffmpeg glfw zeromq pixman cairo libxml2 + EXTRAS = agg tiff ogg theora vpx openh264 ffmpeg glfw zeromq pixman cairo icu xerces-c DIR = default: diff --git a/3rdparty/expat/Makefile b/3rdparty/expat/Makefile deleted file mode 100644 index 4be0f22bb..000000000 --- a/3rdparty/expat/Makefile +++ /dev/null @@ -1,59 +0,0 @@ -ifeq ($(strip $(PREFIX)),) -override PREFIX = $(abspath $(CURDIR)/../build) -endif - -VERSION = 2.4.8 -EXPAT_EXTRA_CONFIGURE_FLAGS ?= -ifeq ($(shell uname),Darwin) -EXPAT_EXTRA_CONFIGURE_FLAGS += CFLAGS=-mmacosx-version-min=10.15 -endif - -ifeq ($(DOWNLOAD_CMD),) -ifneq ($(shell curl --version 2>/dev/null),) -DOWNLOAD_CMD := curl -f -k -OL -endif -endif -ifeq ($(DOWNLOAD_CMD),) -ifneq ($(shell wget --version 2>/dev/null),) -DOWNLOAD_CMD := wget --no-check-certificate -endif -endif -ifeq ($(DOWNLOAD_CMD),) -DOWNLOAD_CMD := echo "Error: Unable to find curl or wget."; exit 1; \# -endif - -default: install - -$(PREFIX)/src/expat-$(VERSION).tar.xz: - mkdir -p $(PREFIX)/src - cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://gr-framework.org/downloads/3rdparty/expat-$(VERSION).tar.xz - -$(PREFIX)/src/expat-$(VERSION)/configure: $(PREFIX)/src/expat-$(VERSION).tar.xz - cd $(PREFIX)/src/ && tar -xf expat-$(VERSION).tar.xz - touch $@ - -$(PREFIX)/src/expat-$(VERSION)/Makefile: $(PREFIX)/src/expat-$(VERSION)/configure - cd $(PREFIX)/src/expat-$(VERSION) && \ - ./configure \ - --prefix=$(PREFIX) \ - --libdir=$(PREFIX)/lib \ - --enable-static \ - --disable-shared \ - --with-pic \ - $(EXPAT_EXTRA_CONFIGURE_FLAGS) - # configure doesn't update the Makefile mtime correctly - touch $@ - -$(PREFIX)/lib/libexpat.a: $(PREFIX)/src/expat-$(VERSION)/Makefile - $(MAKE) -C $(PREFIX)/src/expat-$(VERSION) -j4 && \ - $(MAKE) -C $(PREFIX)/src/expat-$(VERSION) install - -libexpat.a: $(PREFIX)/lib/libexpat.a - cp $(PREFIX)/lib/libexpat.a libexpat.a - -clean: - rm -f libexpat.a - -install: $(PREFIX)/lib/libexpat.a - -.PHONY: default clean install diff --git a/3rdparty/glfw/Makefile b/3rdparty/glfw/Makefile index fab2f044a..c0620c1af 100644 --- a/3rdparty/glfw/Makefile +++ b/3rdparty/glfw/Makefile @@ -48,6 +48,7 @@ $(PREFIX)/build/glfw-$(VERSION)/Makefile: $(PREFIX)/src/glfw-$(VERSION)/CMakeLis cd $(PREFIX)/build/glfw-$(VERSION) && \ $(CMAKE_CMD) \ -DCMAKE_INSTALL_PREFIX:PATH=${PREFIX} \ + -DCMAKE_INSTALL_LIBDIR=lib \ -DGLFW_USE_RETINA=OFF \ -DGLFW_BUILD_EXAMPLES=OFF \ -DGLFW_BUILD_DOCS=OFF \ diff --git a/3rdparty/icu/Makefile b/3rdparty/icu/Makefile new file mode 100644 index 000000000..1c553ba91 --- /dev/null +++ b/3rdparty/icu/Makefile @@ -0,0 +1,74 @@ +SHELL := /bin/bash + +ifeq ($(strip $(PREFIX)),) +override PREFIX = $(abspath $(CURDIR)/../build) +endif + +VERSION = 74.2 +ICU_EXTRA_CONFIGURE_FLAGS ?= +ifeq ($(shell uname),Darwin) +ICU_EXTRA_CONFIGURE_FLAGS += CFLAGS=-mmacosx-version-min=10.15 +ICU_EXTRA_CONFIGURE_FLAGS += CXXFLAGS=-mmacosx-version-min=10.15 +endif + +ifeq ($(DOWNLOAD_CMD),) +ifneq ($(shell curl --version 2>/dev/null),) +DOWNLOAD_CMD := curl -f -k -OL +endif +endif +ifeq ($(DOWNLOAD_CMD),) +ifneq ($(shell wget --version 2>/dev/null),) +DOWNLOAD_CMD := wget --no-check-certificate +endif +endif +ifeq ($(DOWNLOAD_CMD),) +DOWNLOAD_CMD := echo "Error: Unable to find curl or wget."; exit 1; \# +endif + +ifeq ($(shell [[ "$(ARCH)" == "x86_64" || "$(ARCHS)" == "-arch x86_64" ]] && echo macos_cross),macos_cross) +default: run_macos_x86_64_cross_makefile +else ifeq ($(shell [[ "$(CC)" =~ -mingw ]] && echo mingw),mingw) +default: run_mingw_makefile +else +default: install +endif + +run_macos_x86_64_cross_makefile: + $(MAKE) -f Makefile.macos_x86_64_cross + +run_mingw_makefile: + $(MAKE) -f Makefile.mingw + +$(PREFIX)/src/icu4c-$(VERSION).tar.gz: + mkdir -p $(PREFIX)/src + cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://gr-framework.org/downloads/3rdparty/icu4c-$(VERSION).tar.gz + +$(PREFIX)/src/icu/source/configure: $(PREFIX)/src/icu4c-$(VERSION).tar.gz + cd $(PREFIX)/src/ && tar -xf icu4c-$(VERSION).tar.gz + +$(PREFIX)/src/icu/source/Makefile: $(PREFIX)/src/icu/source/configure + cd $(PREFIX)/src/icu/source && \ + CFLAGS="-fPIC" \ + CXXFLAGS="-fPIC" \ + ./configure \ + --prefix=$(PREFIX) \ + --enable-static \ + --disable-shared \ + $(ICU_EXTRA_CONFIGURE_FLAGS) + touch $@ + +$(PREFIX)/lib/libicuuc.a: $(PREFIX)/src/icu/source/Makefile + $(MAKE) -C $(PREFIX)/src/icu/source -j4 + $(MAKE) -C $(PREFIX)/src/icu/source install + +libicuuc.a: $(PREFIX)/lib/libicuuc.a + cp $(PREFIX)/lib/libicuuc.a libicuuc.a + cp $(PREFIX)/lib/libicudata.a libicudata.a + +clean: + rm -f libicuuc.a + rm -f libicudata.a + +install: $(PREFIX)/lib/libicuuc.a + +.PHONY: default clean install run_macos_x86_64_cross_makefile run_mingw_makefile diff --git a/3rdparty/icu/Makefile.macos_x86_64_cross b/3rdparty/icu/Makefile.macos_x86_64_cross new file mode 100644 index 000000000..3256365b7 --- /dev/null +++ b/3rdparty/icu/Makefile.macos_x86_64_cross @@ -0,0 +1,100 @@ +ifeq ($(strip $(PREFIX)),) +override PREFIX = $(abspath $(CURDIR)/../build) +endif + +VERSION = 74.2 +HOST = x86_64-apple-darwin +ICU_EXTRA_CONFIGURE_FLAGS += CFLAGS=-mmacosx-version-min=10.15 +ICU_EXTRA_CONFIGURE_FLAGS += CXXFLAGS=-mmacosx-version-min=10.15 + +ifeq ($(DOWNLOAD_CMD),) +ifneq ($(shell curl --version 2>/dev/null),) +DOWNLOAD_CMD := curl -f -k -OL +endif +endif +ifeq ($(DOWNLOAD_CMD),) +ifneq ($(shell wget --version 2>/dev/null),) +DOWNLOAD_CMD := wget --no-check-certificate +endif +endif +ifeq ($(DOWNLOAD_CMD),) +DOWNLOAD_CMD := echo "Error: Unable to find curl or wget."; exit 1; \# +endif + +default: install + +$(PREFIX)/src/icu4c-$(VERSION).tar.gz: + mkdir -p $(PREFIX)/src + cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://gr-framework.org/downloads/3rdparty/icu4c-$(VERSION).tar.gz + +$(PREFIX)/src/icu/source/configure: $(PREFIX)/src/icu4c-$(VERSION).tar.gz + cd $(PREFIX)/src/ && tar -xf icu4c-$(VERSION).tar.gz + touch $@ + +$(PREFIX)/src/icu-cross/source/configure: $(PREFIX)/src/icu4c-$(VERSION).tar.gz + mkdir -p $(PREFIX)/src/icu-cross + cd $(PREFIX)/src && tar -C icu-cross --strip-components=1 -xf icu4c-$(VERSION).tar.gz + touch $@ + +$(PREFIX)/build/icu/build/Makefile: $(PREFIX)/src/icu/source/configure + mkdir -p $(PREFIX)/build/icu/build && \ + cd $(PREFIX)/build/icu/build && \ + ARCHS= \ + CC=cc \ + CXX=c++ \ + CFLAGS="-fPIC" \ + CXXFLAGS="-fPIC" \ + $(PREFIX)/src/icu/source/configure + touch $@ + +$(PREFIX)/build/icu/build/lib/libicuuc.dylib: $(PREFIX)/build/icu/build/Makefile + $(MAKE) -C $(PREFIX)/build/icu/build -j4 ARCHS= CC=cc CXX=c++ + +$(PREFIX)/build/icu/build-cross/Makefile: $(PREFIX)/build/icu/build/lib/libicuuc.dylib $(PREFIX)/src/icu-cross/source/configure + mkdir -p $(PREFIX)/build/icu/build-cross && \ + cd $(PREFIX)/src/icu-cross/source && \ + ( \ + echo '--- runConfigureICU 2024-07-11 17:16:00.516208356 +0200'; \ + echo '+++ runConfigureICU.patched 2024-07-11 17:14:02.226097097 +0200'; \ + echo '@@ -325,6 +325,16 @@'; \ + echo ' CC=gcc; export CC'; \ + echo ' CXX=g++; export CXX'; \ + echo ' ;;'; \ + echo '+ macOS/cross/x86_64)'; \ + echo '+ THE_OS="macOS (Darwin)"'; \ + echo '+ THE_COMP="the default"'; \ + echo '+ RELEASE_CFLAGS="-O2"'; \ + echo '+ RELEASE_CXXFLAGS="-O2"'; \ + echo '+ DEBUG_CFLAGS="-g -O0"'; \ + echo '+ DEBUG_CXXFLAGS="-g -O0"'; \ + echo '+ CC="cc -arch x86_64"; export CC'; \ + echo '+ CXX="c++ -arch x86_64"; export CXX'; \ + echo '+ ;;'; \ + echo ' MinGW)'; \ + echo ' THE_OS="MinGW"'; \ + echo ' THE_COMP="the GNU C++"'; \ + ) | patch -Np1 && \ + cd $(PREFIX)/build/icu/build-cross && \ + CFLAGS="-fPIC" \ + CXXFLAGS="-fPIC" \ + $(PREFIX)/src/icu-cross/source/runConfigureICU macOS/cross/x86_64 \ + --host=$(HOST) \ + --prefix=$(PREFIX) \ + --with-cross-build="$$(cd ../build && pwd)" \ + --enable-static \ + --disable-shared \ + --disable-strict \ + --disable-tools \ + --disable-tests \ + --disable-samples \ + --disable-extras \ + $(ICU_EXTRA_CONFIGURE_FLAGS) + touch $@ + +$(PREFIX)/lib/libicuuc.a: $(PREFIX)/build/icu/build-cross/Makefile + $(MAKE) -C $(PREFIX)/build/icu/build-cross -j4 + $(MAKE) -C $(PREFIX)/build/icu/build-cross install + +install: $(PREFIX)/lib/libicuuc.a + +.PHONY: default install diff --git a/3rdparty/icu/Makefile.mingw b/3rdparty/icu/Makefile.mingw new file mode 100644 index 000000000..060568595 --- /dev/null +++ b/3rdparty/icu/Makefile.mingw @@ -0,0 +1,134 @@ +ifeq ($(strip $(PREFIX)),) +override PREFIX = $(abspath $(CURDIR)/../build) +endif + +VERSION = 74.2 +MINGW_STD_THREADS_COMMIT_SHORT_HASH = c931bac2 +HOST = x86_64-w64-mingw32 +ICU_EXTRA_CONFIGURE_FLAGS ?= + +ifeq ($(DOWNLOAD_CMD),) +ifneq ($(shell curl --version 2>/dev/null),) +DOWNLOAD_CMD := curl -f -k -OL +endif +endif +ifeq ($(DOWNLOAD_CMD),) +ifneq ($(shell wget --version 2>/dev/null),) +DOWNLOAD_CMD := wget --no-check-certificate +endif +endif +ifeq ($(DOWNLOAD_CMD),) +DOWNLOAD_CMD := echo "Error: Unable to find curl or wget."; exit 1; \# +endif + +default: install + +$(PREFIX)/src/icu4c-$(VERSION).tar.gz: + mkdir -p $(PREFIX)/src + cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://gr-framework.org/downloads/3rdparty/icu4c-$(VERSION).tar.gz + +$(PREFIX)/src/mingw-std-threads-g$(MINGW_STD_THREADS_COMMIT_SHORT_HASH).tar.gz: + mkdir -p $(PREFIX)/src + cd $(PREFIX)/src/ && \ + $(DOWNLOAD_CMD) https://gr-framework.org/downloads/3rdparty/mingw-std-threads-g$(MINGW_STD_THREADS_COMMIT_SHORT_HASH).tar.gz + +$(PREFIX)/src/icu/source/configure: $(PREFIX)/src/icu4c-$(VERSION).tar.gz + cd $(PREFIX)/src/ && tar -xf icu4c-$(VERSION).tar.gz + touch $@ + +$(PREFIX)/src/icu-cross/source/configure: $(PREFIX)/src/icu4c-$(VERSION).tar.gz + mkdir -p $(PREFIX)/src/icu-cross + cd $(PREFIX)/src && tar -C icu-cross --strip-components=1 -xf icu4c-$(VERSION).tar.gz + touch $@ + cd $(PREFIX)/src/icu-cross && \ + ( \ + echo 'diff --git a/source/common/umutex.h b/source/common/umutex.h'; \ + echo 'index 1b83324..ea0d1e2 100644'; \ + echo '--- a/source/common/umutex.h'; \ + echo '+++ b/source/common/umutex.h'; \ + echo '@@ -21,8 +21,8 @@'; \ + echo ' #define UMUTEX_H'; \ + echo; \ + echo ' #include '; \ + echo '-#include '; \ + echo '-#include '; \ + echo '+#include "mingw.condition_variable.h"'; \ + echo '+#include "mingw.mutex.h"'; \ + echo ' #include '; \ + echo; \ + echo ' #include "unicode/utypes.h"'; \ + echo 'diff --git a/source/common/unifiedcache.cpp b/source/common/unifiedcache.cpp'; \ + echo 'index 1284c03..025d64c 100644'; \ + echo '--- a/source/common/unifiedcache.cpp'; \ + echo '+++ b/source/common/unifiedcache.cpp'; \ + echo '@@ -13,7 +13,7 @@'; \ + echo ' #include "unifiedcache.h"'; \ + echo; \ + echo ' #include // For std::max()'; \ + echo '-#include '; \ + echo '+#include "mingw.mutex.h"'; \ + echo; \ + echo ' #include "uassert.h"'; \ + echo ' #include "uhash.h"'; \ + ) | patch -Np1 + +$(PREFIX)/src/mingw-std-threads/mingw.thread.h: $(PREFIX)/src/mingw-std-threads-g$(MINGW_STD_THREADS_COMMIT_SHORT_HASH).tar.gz + mkdir -p $(PREFIX)/src/mingw-std-threads + cd $(PREFIX)/src/ && tar -C mingw-std-threads --strip-components=1 -xf mingw-std-threads-g$(MINGW_STD_THREADS_COMMIT_SHORT_HASH).tar.gz + +$(PREFIX)/build/icu/build/Makefile: $(PREFIX)/src/mingw-std-threads/mingw.thread.h $(PREFIX)/src/icu/source/configure + mkdir -p $(PREFIX)/build/icu/build && \ + cd $(PREFIX)/build/icu/build && \ + AR=ar \ + CC=cc \ + CXX=c++ \ + HOST= \ + OS= \ + CFLAGS="-fPIC" \ + CXXFLAGS="-fPIC" \ + CPPFLAGS="-I$(PREFIX)/src/mingw-std-threads" \ + $(PREFIX)/src/icu/source/configure + touch $@ + +$(PREFIX)/build/icu/build/lib/libicuuc.so: $(PREFIX)/build/icu/build/Makefile + $(MAKE) -C $(PREFIX)/build/icu/build -j4 AR=ar CC=cc CXX=c++ HOST= OS= + +$(PREFIX)/build/icu/build-cross/Makefile: $(PREFIX)/build/icu/build/lib/libicuuc.so $(PREFIX)/src/mingw-std-threads/mingw.thread.h $(PREFIX)/src/icu-cross/source/configure + mkdir -p $(PREFIX)/build/icu/build-cross && \ + cd $(PREFIX)/build/icu/build-cross && \ + CFLAGS="-fPIC" \ + CXXFLAGS="-fPIC" \ + CPPFLAGS="-I$(PREFIX)/src/mingw-std-threads" \ + $(PREFIX)/src/icu-cross/source/runConfigureICU MinGW \ + --host=$(HOST) \ + --prefix=/usr/$(HOST) \ + --with-cross-build="$$(cd ../build && pwd)" \ + --enable-static \ + --disable-shared \ + --disable-strict \ + --disable-tools \ + --disable-tests \ + --disable-samples \ + --disable-extras \ + $(ICU_EXTRA_CONFIGURE_FLAGS) + touch $@ + +/usr/$(HOST)/lib/libsicuuc.a: $(PREFIX)/build/icu/build-cross/Makefile + $(MAKE) -C $(PREFIX)/build/icu/build-cross -j4 + $(MAKE) -C $(PREFIX)/build/icu/build-cross install + +/usr/$(HOST)/lib/libicuuc.a: /usr/$(HOST)/lib/libsicuuc.a + for component in dt in io uc; do \ + ln -sf libsicu$${component}.a "/usr/$(HOST)/lib/libicu$${component}.a"; \ + done + ln -sf libicudt.a /usr/$(HOST)/lib/libicudata.a + +$(PREFIX)/lib/libicuuc.a: /usr/$(HOST)/lib/libicuuc.a + for component in dt in io uc; do \ + cp "/usr/$(HOST)/lib/libicu$${component}.a" "$(PREFIX)/lib/libicu$${component}.a"; \ + done + ln -sf libicudt.a "$(PREFIX)/lib/libicudata.a" + +install: $(PREFIX)/lib/libicuuc.a + +.PHONY: default install diff --git a/3rdparty/libxml2/Makefile b/3rdparty/libxml2/Makefile deleted file mode 100644 index 803c5c12e..000000000 --- a/3rdparty/libxml2/Makefile +++ /dev/null @@ -1,61 +0,0 @@ -ifeq ($(strip $(PREFIX)),) -override PREFIX = $(abspath $(CURDIR)/../build) -endif - -VERSION = 2.10.4 -LIBXML2_EXTRA_CONFIGURE_FLAGS ?= -ifeq ($(shell uname),Darwin) -LIBXML2_EXTRA_CONFIGURE_FLAGS += CFLAGS=-mmacosx-version-min=10.15 -endif - -ifeq ($(DOWNLOAD_CMD),) -ifneq ($(shell curl --version 2>/dev/null),) -DOWNLOAD_CMD := curl -f -k -OL -endif -endif -ifeq ($(DOWNLOAD_CMD),) -ifneq ($(shell wget --version 2>/dev/null),) -DOWNLOAD_CMD := wget --no-check-certificate -endif -endif -ifeq ($(DOWNLOAD_CMD),) -DOWNLOAD_CMD := echo "Error: Unable to find curl or wget."; exit 1; \# -endif - -default: install - -$(PREFIX)/src/libxml2-$(VERSION).tar.xz: - mkdir -p $(PREFIX)/src - cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://gr-framework.org/downloads/3rdparty/libxml2-$(VERSION).tar.xz - -$(PREFIX)/src/libxml2-$(VERSION)/configure: $(PREFIX)/src/libxml2-$(VERSION).tar.xz - cd $(PREFIX)/src/ && tar -xf libxml2-$(VERSION).tar.xz - touch $@ - -$(PREFIX)/src/libxml2-$(VERSION)/Makefile: $(PREFIX)/src/libxml2-$(VERSION)/configure - cd $(PREFIX)/src/libxml2-$(VERSION) && \ - ./configure \ - --prefix=$(PREFIX) \ - --libdir=$(PREFIX)/lib \ - --enable-static \ - --disable-shared \ - --with-pic \ - --with-zlib=$(PREFIX) \ - --without-python \ - $(LIBXML2_EXTRA_CONFIGURE_FLAGS) - # configure doesn't update the Makefile mtime correctly - touch $@ - -$(PREFIX)/lib/libxml2.a: $(PREFIX)/src/libxml2-$(VERSION)/Makefile - $(MAKE) -C $(PREFIX)/src/libxml2-$(VERSION) -j4 && \ - $(MAKE) -C $(PREFIX)/src/libxml2-$(VERSION) install - -libxml2.a: $(PREFIX)/lib/libxml2.a - cp $(PREFIX)/lib/libxml2.a libxml2.a - -clean: - rm -f libxml2.a - -install: $(PREFIX)/lib/libxml2.a - -.PHONY: default clean install diff --git a/3rdparty/makeself.sh b/3rdparty/makeself.sh index 4a0fa71ae..bc2ccb7ae 100644 --- a/3rdparty/makeself.sh +++ b/3rdparty/makeself.sh @@ -37,7 +37,7 @@ if [ -z "${QTDIR}" ]; then done fi -opts="${opts} USE_STATIC_CAIRO_LIBS=1 USE_STATIC_AGG_LIBS=1" +opts="${opts} USE_STATIC_CAIRO_LIBS=1 USE_STATIC_AGG_LIBS=1 USE_STATIC_XERCESC_LIBS=1" extras=`pwd`/3rdparty/build extras_lib=${extras}/lib diff --git a/3rdparty/xerces-c/Makefile b/3rdparty/xerces-c/Makefile new file mode 100644 index 000000000..b683dc0dd --- /dev/null +++ b/3rdparty/xerces-c/Makefile @@ -0,0 +1,192 @@ +SHELL := /bin/bash + +ifeq ($(strip $(PREFIX)),) +override PREFIX = $(abspath $(CURDIR)/../build) +endif + +VERSION = 3.2.4 +XERCES_C_EXTRA_CMAKE_FLAGS ?= +ifeq ($(shell uname),Darwin) +XERCES_C_EXTRA_CMAKE_FLAGS += -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 +endif + +ifeq ($(DOWNLOAD_CMD),) +ifneq ($(shell curl --version 2>/dev/null),) +DOWNLOAD_CMD := curl -f -k -OL +endif +endif +ifeq ($(DOWNLOAD_CMD),) +ifneq ($(shell wget --version 2>/dev/null),) +DOWNLOAD_CMD := wget --no-check-certificate +endif +endif +ifeq ($(DOWNLOAD_CMD),) +DOWNLOAD_CMD := echo "Error: Unable to find curl or wget."; exit 1; \# +endif + +ifeq ($(CMAKE_CMD),) +ifneq ($(shell cmake --version 2>/dev/null),) +CMAKE_CMD := cmake +CMAKE_DEP := +endif +endif +ifeq ($(CMAKE_CMD),) +CMAKE_CMD := $(PREFIX)/bin/cmake +CMAKE_DEP := $(CMAKE_CMD) +endif + +ifeq ($(notdir $(CC)),emcc) +# If building with Emscripten, ICU must not be built or linked explicitly. Instead, patch the Xerces-C++ +# `CMakeLists.txt` to include the Emscripten port of ICU (`-s USE_ICU=1`) +ICU_LIBS = +else +ICU_LIBS = $(PREFIX)/lib/libicuuc.a +endif + +default: install + +$(PREFIX)/src/xerces-c-$(VERSION).tar.gz: + mkdir -p $(PREFIX)/src + cd $(PREFIX)/src/ && $(DOWNLOAD_CMD) https://gr-framework.org/downloads/3rdparty/xerces-c-$(VERSION).tar.gz + +$(PREFIX)/src/xerces-c-$(VERSION)/CMakeLists.txt: $(PREFIX)/src/xerces-c-$(VERSION).tar.gz + cd $(PREFIX)/src/ && tar -xf xerces-c-$(VERSION).tar.gz + cd $(PREFIX)/src/xerces-c-$(VERSION) && \ + ( \ + echo 'diff --git a/CMakeLists.txt b/CMakeLists.txt'; \ + echo 'index 33bc40f..5de3dee 100644'; \ + echo '--- a/CMakeLists.txt'; \ + echo '+++ b/CMakeLists.txt'; \ + echo '@@ -164,10 +164,7 @@ install('; \ + echo ' COMPONENT "development")'; \ + echo; \ + echo ' # Process subdirectories'; \ + echo '-add_subdirectory(doc)'; \ + echo ' add_subdirectory(src)'; \ + echo '-add_subdirectory(tests)'; \ + echo '-add_subdirectory(samples)'; \ + echo; \ + echo ' # Display configuration summary'; \ + echo ' message(STATUS "")'; \ + echo 'diff --git a/cmake/XercesNetAccessorSelection.cmake b/cmake/XercesNetAccessorSelection.cmake'; \ + echo 'index 7a63f1f..723c394 100644'; \ + echo '--- a/cmake/XercesNetAccessorSelection.cmake'; \ + echo '+++ b/cmake/XercesNetAccessorSelection.cmake'; \ + echo '@@ -31,7 +31,6 @@ if(network)'; \ + echo ''; \ + echo ' # Requires select() which is UNIX only'; \ + echo ' if(UNIX)'; \ + echo '- find_package(CURL)'; \ + echo ' if(CURL_FOUND)'; \ + echo ' list(APPEND netaccessors curl)'; \ + echo ' endif()'; \ + echo 'diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt'; \ + echo 'index a168db1..50043ee 100644'; \ + echo '--- a/src/CMakeLists.txt'; \ + echo '+++ b/src/CMakeLists.txt'; \ + echo '@@ -1271,6 +1271,7 @@ set_target_properties(xerces-c-headers PROPERTIES FOLDER "Library")'; \ + echo ' add_library(xerces-c'; \ + echo ' $${libxerces_c_SOURCES}'; \ + echo ' $${libxerces_c_RESOURCES})'; \ + echo '+set_target_properties(xerces-c PROPERTIES POSITION_INDEPENDENT_CODE ON)'; \ + echo ' target_link_libraries(xerces-c PRIVATE $${libxerces_c_DEPS})'; \ + echo ' if(XERCES_USE_NETACCESSOR_CURL)'; \ + echo ' target_include_directories(xerces-c SYSTEM PRIVATE $${CURL_INCLUDE_DIRS})'; \ + ) | patch -Np1 +ifeq ($(notdir $(CC)),emcc) + cd $(PREFIX)/src/xerces-c-$(VERSION) && \ + ( \ + echo 'diff --git a/cmake/XercesICU.cmake b/cmake/XercesICU.cmake'; \ + echo 'index aecde10..dcef358 100644'; \ + echo '--- a/cmake/XercesICU.cmake'; \ + echo '+++ b/cmake/XercesICU.cmake'; \ + echo '@@ -19,4 +19,16 @@'; \ + echo; \ + echo ' # Determine if ICU is available'; \ + echo; \ + echo '-find_package(ICU COMPONENTS uc data)'; \ + echo '+set(USE_FLAGS -s USE_ICU=1)'; \ + echo '+'; \ + echo '+add_library(icu-uc INTERFACE)'; \ + echo '+target_compile_options(icu-uc INTERFACE $${USE_FLAGS})'; \ + echo '+target_link_options(icu-uc INTERFACE $${USE_FLAGS})'; \ + echo '+add_library(ICU::uc ALIAS icu-uc)'; \ + echo '+'; \ + echo '+add_library(icu-data INTERFACE)'; \ + echo '+target_compile_options(icu-data INTERFACE $${USE_FLAGS})'; \ + echo '+target_link_options(icu-data INTERFACE $${USE_FLAGS})'; \ + echo '+add_library(ICU::data ALIAS icu-data)'; \ + echo '+'; \ + echo '+set(ICU_FOUND "1")'; \ + echo 'diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt'; \ + echo 'index a168db1..bfde8bf 100644'; \ + echo '--- a/src/CMakeLists.txt'; \ + echo '+++ b/src/CMakeLists.txt'; \ + echo '@@ -1316,7 +1316,7 @@ else()'; \ + echo ' set(xerces_config_dir "$${CMAKE_INSTALL_LIBDIR}/cmake/XercesC")'; \ + echo ' endif()'; \ + echo; \ + echo '-install(TARGETS xerces-c'; \ + echo '+install(TARGETS xerces-c icu-uc icu-data'; \ + echo ' EXPORT XercesCConfigInternal'; \ + echo ' RUNTIME DESTINATION $${CMAKE_INSTALL_BINDIR}'; \ + echo ' LIBRARY DESTINATION $${CMAKE_INSTALL_LIBDIR}'; \ + ) | patch -Np1 +endif + +$(PREFIX)/build/xerces-c-$(VERSION)/Makefile: $(PREFIX)/src/xerces-c-$(VERSION)/CMakeLists.txt $(CMAKE_DEP) $(ICU_LIBS) + mkdir -p $(PREFIX)/build/xerces-c-$(VERSION) + cd $(PREFIX)/build/xerces-c-$(VERSION) && \ + $(CMAKE_CMD) \ + -DCMAKE_INSTALL_PREFIX=${PREFIX} \ + -DCMAKE_INSTALL_LIBDIR=lib \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=Release \ + -DICU_ROOT=${PREFIX} \ + $(XERCES_C_EXTRA_CMAKE_FLAGS) \ + $(PREFIX)/src/xerces-c-$(VERSION)/ + +$(PREFIX)/lib/libxerces-c.a: $(PREFIX)/build/xerces-c-$(VERSION)/Makefile + $(MAKE) -C $(PREFIX)/build/xerces-c-$(VERSION) -j4 + $(MAKE) -C $(PREFIX)/build/xerces-c-$(VERSION) install + if [[ -f "$(PREFIX)/lib/cmake/XercesC/XercesCConfigInternal.cmake" ]]; then \ + cd "$(PREFIX)/lib/cmake/XercesC"; \ + elif [[ -f "$(PREFIX)/cmake/XercesCConfigInternal.cmake" ]]; then \ + cd "$(PREFIX)/cmake"; \ + else \ + exit 1; \ + fi; \ + ( \ + echo '--- XercesCConfigInternal.cmake 2024-01-04 14:51:30.000000000 +0000'; \ + echo '+++ XercesCConfigInternal.cmake.patched 2024-01-04 15:20:05.000235499 +0000'; \ + echo '@@ -50,6 +50,16 @@'; \ + echo ' set(_IMPORT_PREFIX "")'; \ + echo ' endif()'; \ + echo; \ + echo '+add_library(ICU::uc STATIC IMPORTED)'; \ + echo '+set_target_properties(ICU::uc PROPERTIES'; \ + echo '+ IMPORTED_LOCATION "$${_IMPORT_PREFIX}/lib/libicuuc.a"'; \ + echo '+)'; \ + echo '+add_library(ICU::data STATIC IMPORTED)'; \ + echo '+set_target_properties(ICU::data PROPERTIES'; \ + echo '+ IMPORTED_LOCATION "$${_IMPORT_PREFIX}/lib/libicudata.a"'; \ + echo '+)'; \ + echo '+find_package(Threads)'; \ + echo '+'; \ + echo ' # Create imported target xerces_xerces-c'; \ + echo ' add_library(xerces_xerces-c STATIC IMPORTED)'; \ + echo; \ + ) | patch -N + +libxerces-c.a: $(PREFIX)/lib/libxerces-c.a + cp $(PREFIX)/lib/libxerces-c.a libxerces-c.a + +clean: + rm -f libxerces-c.a + +install: $(PREFIX)/lib/libxerces-c.a + +$(PREFIX)/lib/libicuuc.a: + $(MAKE) -C ../icu PREFIX="$(PREFIX)" + +.PHONY: default clean install diff --git a/CMakeLists.txt b/CMakeLists.txt index ed36d99d4..028aa207e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,22 +109,6 @@ find_package( PrintSupport ) find_package(Qt4) -# CMake ships with a `FindLibXml2.cmake` module which does not configure needed libxml2 dependencies. -# Thus, use the `libxml2-config.cmake` config file shipped with libxml which configures dependencies correctly by -# skipping module search mode. -if(GR_USE_BUNDLED_LIBRARIES) - # Find `LibXml2` only in 3rdparty but allow to find its dependencies in system directories - # (`ONLY_CMAKE_FIND_ROOT_PATH` option is not inherited to `find_package` calls within the LibXml2 config file) - find_package(LibXml2 NO_MODULE ONLY_CMAKE_FIND_ROOT_PATH) -else() - # On RHEL 9 based Linux distributions, `libxml2` is found in `/lib64` by default since the `PATH` variable is - # inspected by CMake to find potential prefix paths (`/bin` is present in `PATH` before `/usr/bin`). This causes - # errors because no `/include` directory is present on Linux and this is not checked by the LibXml2 config file. - # Therefore, disable the inspection of the `PATH` variable by setting `NO_SYSTEM_ENVIRONMENT_PATH` option. - # See for more details. - find_package(LibXml2 NO_MODULE NO_SYSTEM_ENVIRONMENT_PATH) -endif() # Find the following packages only in 3rdparty, if `GR_USE_BUNDLED_LIBRARIES` is set if(GR_USE_BUNDLED_LIBRARIES) @@ -162,7 +146,7 @@ endif() find_package(Cairo) find_package(Agg) find_package(Gs) -find_package(Expat) +find_package(XercesC) if(APPLE) set(INSTALL_RPATH "${GR_DIRECTORY}/lib/;@loader_path/.") @@ -584,16 +568,10 @@ foreach(LIBRARY grm_static grm_shared grm_shared_internal) target_link_libraries(${LIBRARY} ${GRM_LINK_MODE} GR::GKS) target_link_libraries(${LIBRARY} ${GRM_LINK_MODE} GR::GR) target_link_libraries(${LIBRARY} ${GRM_LINK_MODE} GR::GR3) - if(TARGET LibXml2::LibXml2) - target_link_libraries(${LIBRARY} ${GRM_LINK_MODE} LibXml2::LibXml2) - elseif(EXPAT_FOUND) - target_link_libraries(${LIBRARY} ${GRM_LINK_MODE} Expat::Expat) - endif() - if(NOT TARGET LibXml2::LibXml2) - target_compile_definitions(${LIBRARY} PRIVATE NO_LIBXML2) - endif() - if(NOT EXPAT_FOUND OR TARGET LibXml2::LibXml2) - target_compile_definitions(${LIBRARY} PRIVATE NO_EXPAT) + if(TARGET XercesC::XercesC) + target_link_libraries(${LIBRARY} ${GRM_LINK_MODE} XercesC::XercesC) + else() + target_compile_definitions(${LIBRARY} PRIVATE NO_XERCES_C) endif() if(NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")) target_link_libraries(${LIBRARY} ${GRM_LINK_MODE} m) @@ -1279,8 +1257,8 @@ if((Qt6Widgets_FOUND set(grplot_INSTALL_RPATH "${INSTALL_RPATH};${Qt5_LIBRARY_DIR}") endif() target_compile_definitions(grplot PRIVATE GRDIR="${GR_DIRECTORY}") - if(NOT TARGET LibXml2::LibXml2) - target_compile_definitions(grplot PRIVATE NO_LIBXML2) + if(NOT TARGET XercesC::XercesC) + target_compile_definitions(grplot PRIVATE NO_XERCES_C) endif() set_target_properties( grplot PROPERTIES CXX_STANDARD 17 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON INSTALL_RPATH @@ -1416,12 +1394,10 @@ else() endif() string(APPEND GR_REPORT "\nGRM integrations:\n") -if(TARGET LibXml2::LibXml2) - string(APPEND GR_REPORT "- libxml2: Yes\n- Expat: No\n") -elseif(EXPAT_FOUND) - string(APPEND GR_REPORT "- libxml2: No\n- Expat: Yes\n") +if(TARGET XercesC::XercesC) + string(APPEND GR_REPORT "- Xerces-C++: Yes\n") else() - string(APPEND GR_REPORT "- libxml2: No\n- Expat: No\n") + string(APPEND GR_REPORT "- Xerces-C++: No\n") endif() if(GR_BUILD_DEMOS) diff --git a/cmake/FindExpat.cmake b/cmake/FindExpat.cmake deleted file mode 100644 index 6d6b0015f..000000000 --- a/cmake/FindExpat.cmake +++ /dev/null @@ -1,73 +0,0 @@ -#.rst: -# FindExpat -# --------- -# -# Find the Expat XML Parser library. -# -# Imported targets -# ^^^^^^^^^^^^^^^^ -# -# This module defines the following :prop_tgt:`IMPORTED` target: -# -# ``Expat::Expat`` -# The Expat library, if found. -# -# Result variables -# ^^^^^^^^^^^^^^^^ -# -# This module will set the following variables in your project: -# -# ``EXPAT_INCLUDE_DIRS`` -# where to find expat.h, etc. -# ``EXPAT_LIBRARIES`` -# the libraries to link against to use Expat. -# ``EXPAT_FOUND`` -# If false, do not try to use Expat. - -if(NOT EXPAT_INCLUDE_DIR) - find_path(EXPAT_INCLUDE_DIR expat.h) -endif() - -if(NOT EXPAT_LIBRARY) - find_library(EXPAT_LIBRARY NAMES ${GR_THIRDPARTY_LIBRARY_PREFIX}expat${GR_THIRDPARTY_LIBRARY_SUFFIX} expat libexpat) -endif() - -if(EXPAT_INCLUDE_DIR) - if(NOT EXPAT_VERSION_STRING) - file(READ ${EXPAT_INCLUDE_DIR}/expat.h EXPAT_H_TEXT) - string(REGEX REPLACE ".*#define XML_MAJOR_VERSION[ \t]*([0-9]+).*" "\\1" EXPAT_VERSION_MAJOR ${EXPAT_H_TEXT}) - string(REGEX REPLACE ".*#define XML_MINOR_VERSION[ \t]*([0-9]+).*" "\\1" EXPAT_VERSION_MINOR ${EXPAT_H_TEXT}) - string(REGEX REPLACE ".*#define XML_MICRO_VERSION[ \t]*([0-9]+).*" "\\1" EXPAT_VERSION_MICRO ${EXPAT_H_TEXT}) - string(CONCAT EXPAT_VERSION_STRING "${EXPAT_VERSION_MAJOR}.${EXPAT_VERSION_MINOR}.${EXPAT_VERSION_MICRO}") - endif() -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - Expat - VERSION_VAR - EXPAT_VERSION_STRING - REQUIRED_VARS - EXPAT_LIBRARY - EXPAT_INCLUDE_DIR - EXPAT_VERSION_STRING -) - -if(EXPAT_FOUND) - set(EXPAT_INCLUDE_DIRS ${EXPAT_INCLUDE_DIR}) - set(EXPAT_LIBRARY ${EXPAT_LIBRARY}) - - if(NOT TARGET Expat::Expat) - add_library(Expat::Expat UNKNOWN IMPORTED) - set_target_properties( - Expat::Expat - PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${EXPAT_INCLUDE_DIRS}" - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${EXPAT_LIBRARY}" - ) - endif() -elseif(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) - message(FATAL_ERROR "${CMAKE_FIND_PACKAGE_NAME} was required but could not be found.") -endif() - -mark_as_advanced(EXPAT_INCLUDE_DIR EXPAT_LIBRARY) diff --git a/cmake/FindXercesC.cmake b/cmake/FindXercesC.cmake new file mode 100644 index 000000000..d3ea2fe46 --- /dev/null +++ b/cmake/FindXercesC.cmake @@ -0,0 +1,86 @@ +#.rst: +# FindXercesC +# ----------- +# +# Find the Xerces-C++ XML Parser library. +# +# Imported targets +# ^^^^^^^^^^^^^^^^ +# +# This module defines the following :prop_tgt:`IMPORTED` target: +# +# ``XercesC::XercesC`` +# The Xerces-C++ library, if found. +# +# Result variables +# ^^^^^^^^^^^^^^^^ +# +# This module will set the following variables in your project: +# +# ``XERCES_C_INCLUDE_DIRS`` +# where to find Xerces-C++ header files, etc. +# ``XERCES_C_LIBRARIES`` +# the libraries to link against to use Xerces-C++. +# ``XERCES_C_FOUND`` +# If false, do not try to use Xerces-C++. + +if(NOT XERCES_C_INCLUDE_DIR) + find_path(XERCES_C_UTIL_DIR XercesVersion.hpp PATH_SUFFIXES xercesc/util) + if(XERCES_C_UTIL_DIR) + get_filename_component(XERCES_C_INCLUDE_DIR "${XERCES_C_UTIL_DIR}/../.." ABSOLUTE) + endif() +endif() + +if(NOT XERCES_C_LIBRARY) + find_library( + XERCES_C_LIBRARY NAMES ${GR_THIRDPARTY_LIBRARY_PREFIX}xerces-c${GR_THIRDPARTY_LIBRARY_SUFFIX} xerces-c libxerces-c + ) +endif() + +if(XERCES_C_INCLUDE_DIR) + if(NOT XERCES_C_VERSION_STRING) + file(READ ${XERCES_C_INCLUDE_DIR}/xercesc/util/XercesVersion.hpp XERCES_VERSION_H_TEXT) + string(REGEX REPLACE ".*#define XERCES_VERSION_MAJOR[ \t]*([0-9]+).*" "\\1" XERCES_C_VERSION_MAJOR + ${XERCES_VERSION_H_TEXT} + ) + string(REGEX REPLACE ".*#define XERCES_VERSION_MINOR[ \t]*([0-9]+).*" "\\1" XERCES_C_VERSION_MINOR + ${XERCES_VERSION_H_TEXT} + ) + string(REGEX REPLACE ".*#define XERCES_VERSION_REVISION[ \t]*([0-9]+).*" "\\1" XERCES_C_VERSION_REVISION + ${XERCES_VERSION_H_TEXT} + ) + string(CONCAT XERCES_C_VERSION_STRING + "${XERCES_C_VERSION_MAJOR}.${XERCES_C_VERSION_MINOR}.${XERCES_C_VERSION_REVISION}" + ) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + XERCES_C + VERSION_VAR + XERCES_C_VERSION_STRING + REQUIRED_VARS + XERCES_C_LIBRARY + XERCES_C_INCLUDE_DIR + XERCES_C_VERSION_STRING +) + +if(XERCES_C_FOUND) + set(XERCES_C_INCLUDE_DIRS ${XERCES_C_INCLUDE_DIR}) + set(XERCES_C_LIBRARY ${XERCES_C_LIBRARY}) + + if(NOT TARGET XercesC::XercesC) + add_library(XercesC::XercesC UNKNOWN IMPORTED) + set_target_properties( + XercesC::XercesC + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${XERCES_C_INCLUDE_DIRS}" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${XERCES_C_LIBRARY}" + ) + endif() +elseif(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED) + message(FATAL_ERROR "${CMAKE_FIND_PACKAGE_NAME} was required but could not be found.") +endif() + +mark_as_advanced(XERCES_C_INCLUDE_DIRS XERCES_C_LIBRARY) diff --git a/js/Makefile b/js/Makefile index 0e1dc48f9..3ed83a28a 100755 --- a/js/Makefile +++ b/js/Makefile @@ -18,118 +18,118 @@ OPTS = \ -s ALLOW_MEMORY_GROWTH=1 \ -s RESERVED_FUNCTION_POINTERS=4 \ -s ALLOW_TABLE_GROWTH=1 \ - --no-heap-copy + --no-heap-copy \ + -s USE_ICU=1 ifndef DEBUG - OPTS += -O3 + OPTS += -O3 else - OPTS += -s ASSERTIONS=2 -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2 + OPTS += -s ASSERTIONS=2 -s SAFE_HEAP=1 -s STACK_OVERFLOW_CHECK=2 endif - DEFINES = -DEMSCRIPTEN -D__SVR4 -DGRDIR=\"\" -DNO_GL -DNO_THREADS -DNO_LIBXML2 - CFLAGS = $(DEFINES) $(OPTS) $(EXTRA_CFLAGS) -CXXFLAGS = $(DEFINES) $(OPTS) -std=c++17 $(EXTRA_CXXFLAGS) - LDFLAGS = $(DEFINES) $(OPTS) $(EXTRA_LDFLAGS) - JPEGDIR = ../3rdparty/jpeg - PNGDIR = ../3rdparty/libpng16 - ZLIBDIR = ../3rdparty/zlib -QHULLDIR = ../3rdparty/qhull - FTDIR = ../3rdparty/freetype -EXPATDIR = ../3rdparty/expat - GKSDIR = ../lib/gks - GRDIR = ../lib/gr - GR3DIR = ../lib/gr3 - GRMDIR = ../lib/grm -INCLUDES = -I$(THIRDPARTYDIR)/include/ \ - -I$(GRMDIR)/include \ - -I$(GRMDIR)/src \ - -I$(GR3DIR) \ - -I$(GRDIR) \ - -I$(GKSDIR) \ - -I$(JPEGDIR) \ - -I$(PNGDIR) \ - -I$(ZLIBDIR) \ - -I$(QHULLDIR) \ - -I$(FTDIR) \ - -I$(EXPATDIR) - GKSOBJS = jsplugin.o \ - $(GKSDIR)/afm.o \ - $(GKSDIR)/error.o \ - $(GKSDIR)/font.o \ - $(GKSDIR)/ft.o \ - $(GKSDIR)/gks.o \ - $(GKSDIR)/io.o \ - $(GKSDIR)/malloc.o \ - $(GKSDIR)/util.o \ - $(GKSDIR)/wiss.o - GROBJS = $(GRDIR)/contour.o \ - $(GRDIR)/contourf.o \ - $(GRDIR)/delaunay.o \ - $(GRDIR)/gr.o \ - $(GRDIR)/grforbnd.o \ - $(GRDIR)/gridit.o \ - $(GRDIR)/image.o \ - $(GRDIR)/import.o \ - $(GRDIR)/interp2.o \ - $(GRDIR)/mathtex2.o \ - $(GRDIR)/mathtex2.tab.o \ - $(GRDIR)/mathtex2_kerning.o \ - $(GRDIR)/md5.o \ - $(GRDIR)/shade.o \ - $(GRDIR)/spline.o \ - $(GRDIR)/stream.o \ - $(GRDIR)/strlib.o \ - $(GRDIR)/text.o - GR3OBJS = $(GR3DIR)/gr3.o \ - $(GR3DIR)/gr3_convenience.o \ - $(GR3DIR)/gr3_gr.o \ - $(GR3DIR)/gr3_html.o \ - $(GR3DIR)/gr3_jpeg.o \ - $(GR3DIR)/gr3_mc.o \ - $(GR3DIR)/gr3_png.o \ - $(GR3DIR)/gr3_povray.o \ - $(GR3DIR)/gr3_sr.o - GRMOBJS = $(GRMDIR)/src/grm/args.o \ - $(GRMDIR)/src/grm/backtrace.o \ - $(GRMDIR)/src/grm/base64.o \ - $(GRMDIR)/src/grm/dump.o \ - $(GRMDIR)/src/grm/dynamic_args_array.o \ - $(GRMDIR)/src/grm/error.o \ - $(GRMDIR)/src/grm/event.o \ - $(GRMDIR)/src/grm/interaction.o \ - $(GRMDIR)/src/grm/json.o \ - $(GRMDIR)/src/grm/layout.o \ - $(GRMDIR)/src/grm/layout_c.o \ - $(GRMDIR)/src/grm/layout_error.o \ - $(GRMDIR)/src/grm/logging.o \ - $(GRMDIR)/src/grm/memwriter.o \ - $(GRMDIR)/src/grm/net.o \ - $(GRMDIR)/src/grm/plot.o \ - $(GRMDIR)/src/grm/util.o \ - $(GRMDIR)/src/grm/utilcpp.o \ - $(GRMDIR)/src/grm/import.o \ - $(GRMDIR)/src/grm/datatype/double_map.o \ - $(GRMDIR)/src/grm/datatype/string_array_map.o \ - $(GRMDIR)/src/grm/datatype/string_list.o \ - $(GRMDIR)/src/grm/datatype/string_map.o \ - $(GRMDIR)/src/grm/datatype/uint_map.o \ - $(GRMDIR)/src/grm/dom_render/context.o \ - $(GRMDIR)/src/grm/dom_render/Drawable.o \ - $(GRMDIR)/src/grm/dom_render/ManageCustomColorIndex.o \ - $(GRMDIR)/src/grm/dom_render/ManageGRContextIds.o \ - $(GRMDIR)/src/grm/dom_render/ManageZIndex.o \ - $(GRMDIR)/src/grm/dom_render/render.o \ - $(GRMDIR)/src/grm/dom_render/graphics_tree/Comment.o \ - $(GRMDIR)/src/grm/dom_render/graphics_tree/Document.o \ - $(GRMDIR)/src/grm/dom_render/graphics_tree/Element.o \ - $(GRMDIR)/src/grm/dom_render/graphics_tree/Node.o \ - $(GRMDIR)/src/grm/dom_render/graphics_tree/Value.o \ - $(GRMDIR)/src/grm/dom_render/graphics_tree/util.o - OBJS = $(GRMOBJS) $(GR3OBJS) $(GROBJS) $(GKSOBJS) - LIBS = $(JPEGDIR)/libjpeg.a \ - $(PNGDIR)/libpng.a \ - $(ZLIBDIR)/libz.a \ - $(QHULLDIR)/libqhull_r.a \ - $(FTDIR)/libfreetype.a \ - $(EXPATDIR)/libexpat.a + DEFINES = -DEMSCRIPTEN -D__SVR4 -DGRDIR=\"\" -DNO_GL -DNO_THREADS + CFLAGS = $(DEFINES) $(OPTS) $(EXTRA_CFLAGS) + CXXFLAGS = $(DEFINES) $(OPTS) -std=c++17 $(EXTRA_CXXFLAGS) + LDFLAGS = $(DEFINES) $(OPTS) $(EXTRA_LDFLAGS) + JPEGDIR = ../3rdparty/jpeg + PNGDIR = ../3rdparty/libpng16 + ZLIBDIR = ../3rdparty/zlib + QHULLDIR = ../3rdparty/qhull + FTDIR = ../3rdparty/freetype +XERCESCDIR = ../3rdparty/xerces-c + GKSDIR = ../lib/gks + GRDIR = ../lib/gr + GR3DIR = ../lib/gr3 + GRMDIR = ../lib/grm + INCLUDES = -I$(THIRDPARTYDIR)/include/ \ + -I$(GRMDIR)/include \ + -I$(GRMDIR)/src \ + -I$(GR3DIR) \ + -I$(GRDIR) \ + -I$(GKSDIR) \ + -I$(JPEGDIR) \ + -I$(PNGDIR) \ + -I$(ZLIBDIR) \ + -I$(QHULLDIR) \ + -I$(FTDIR) + GKSOBJS = jsplugin.o \ + $(GKSDIR)/afm.o \ + $(GKSDIR)/error.o \ + $(GKSDIR)/font.o \ + $(GKSDIR)/ft.o \ + $(GKSDIR)/gks.o \ + $(GKSDIR)/io.o \ + $(GKSDIR)/malloc.o \ + $(GKSDIR)/util.o \ + $(GKSDIR)/wiss.o + GROBJS = $(GRDIR)/contour.o \ + $(GRDIR)/contourf.o \ + $(GRDIR)/delaunay.o \ + $(GRDIR)/gr.o \ + $(GRDIR)/grforbnd.o \ + $(GRDIR)/gridit.o \ + $(GRDIR)/image.o \ + $(GRDIR)/import.o \ + $(GRDIR)/interp2.o \ + $(GRDIR)/mathtex2.o \ + $(GRDIR)/mathtex2.tab.o \ + $(GRDIR)/mathtex2_kerning.o \ + $(GRDIR)/md5.o \ + $(GRDIR)/shade.o \ + $(GRDIR)/spline.o \ + $(GRDIR)/stream.o \ + $(GRDIR)/strlib.o \ + $(GRDIR)/text.o + GR3OBJS = $(GR3DIR)/gr3.o \ + $(GR3DIR)/gr3_convenience.o \ + $(GR3DIR)/gr3_gr.o \ + $(GR3DIR)/gr3_html.o \ + $(GR3DIR)/gr3_jpeg.o \ + $(GR3DIR)/gr3_mc.o \ + $(GR3DIR)/gr3_png.o \ + $(GR3DIR)/gr3_povray.o \ + $(GR3DIR)/gr3_sr.o + GRMOBJS = $(GRMDIR)/src/grm/args.o \ + $(GRMDIR)/src/grm/backtrace.o \ + $(GRMDIR)/src/grm/base64.o \ + $(GRMDIR)/src/grm/dump.o \ + $(GRMDIR)/src/grm/dynamic_args_array.o \ + $(GRMDIR)/src/grm/error.o \ + $(GRMDIR)/src/grm/event.o \ + $(GRMDIR)/src/grm/interaction.o \ + $(GRMDIR)/src/grm/json.o \ + $(GRMDIR)/src/grm/layout.o \ + $(GRMDIR)/src/grm/layout_c.o \ + $(GRMDIR)/src/grm/layout_error.o \ + $(GRMDIR)/src/grm/logging.o \ + $(GRMDIR)/src/grm/memwriter.o \ + $(GRMDIR)/src/grm/net.o \ + $(GRMDIR)/src/grm/plot.o \ + $(GRMDIR)/src/grm/util.o \ + $(GRMDIR)/src/grm/utilcpp.o \ + $(GRMDIR)/src/grm/import.o \ + $(GRMDIR)/src/grm/datatype/double_map.o \ + $(GRMDIR)/src/grm/datatype/string_array_map.o \ + $(GRMDIR)/src/grm/datatype/string_list.o \ + $(GRMDIR)/src/grm/datatype/string_map.o \ + $(GRMDIR)/src/grm/datatype/uint_map.o \ + $(GRMDIR)/src/grm/dom_render/context.o \ + $(GRMDIR)/src/grm/dom_render/Drawable.o \ + $(GRMDIR)/src/grm/dom_render/ManageCustomColorIndex.o \ + $(GRMDIR)/src/grm/dom_render/ManageGRContextIds.o \ + $(GRMDIR)/src/grm/dom_render/ManageZIndex.o \ + $(GRMDIR)/src/grm/dom_render/render.o \ + $(GRMDIR)/src/grm/dom_render/graphics_tree/Comment.o \ + $(GRMDIR)/src/grm/dom_render/graphics_tree/Document.o \ + $(GRMDIR)/src/grm/dom_render/graphics_tree/Element.o \ + $(GRMDIR)/src/grm/dom_render/graphics_tree/Node.o \ + $(GRMDIR)/src/grm/dom_render/graphics_tree/Value.o \ + $(GRMDIR)/src/grm/dom_render/graphics_tree/util.o + OBJS = $(GRMOBJS) $(GR3OBJS) $(GROBJS) $(GKSOBJS) + LIBS = $(JPEGDIR)/libjpeg.a \ + $(PNGDIR)/libpng.a \ + $(ZLIBDIR)/libz.a \ + $(QHULLDIR)/libqhull_r.a \ + $(FTDIR)/libfreetype.a \ + $(XERCESCDIR)/libxerces-c.a .PHONY: default clean .SECONDARY .FORCE @@ -167,8 +167,8 @@ $(QHULLDIR)/libqhull_r.a: $(FTDIR)/libfreetype.a: $(MAKE) -C $(FTDIR) -$(EXPATDIR)/libexpat.a: - $(MAKE) -C $(EXPATDIR) libexpat.a +$(XERCESCDIR)/libxerces-c.a: + $(MAKE) -C $(XERCESCDIR) libxerces-c.a $(GRDIR)/gr.o: ../lib/gr/gr_version.h @@ -193,5 +193,5 @@ clean: cd $(ZLIBDIR) && make clean cd $(QHULLDIR) && make clean cd $(FTDIR) && make clean - cd $(EXPATDIR) && make clean + cd $(XERCESCDIR) && make clean rm -rf $(OBJS) diff --git a/lib/Preflight b/lib/Preflight index b2093e9fb..667cd3170 100755 --- a/lib/Preflight +++ b/lib/Preflight @@ -715,64 +715,54 @@ fi printf "%12s: %s\n" "agg" "$info" >&2 rm -f $tmpsrc $tmpout $tmpver -if [ "$libxml2" != "no" ] -then - libxml2config=xml2-config - if [ "$LIBXML2_CONFIG" != "" ] - then - libxml2config=$LIBXML2_CONFIG - fi - if [ "`which $libxml2config 2>/dev/null`" = "" ] - then - libxml2defs="LIBXML2_CONFIG=false LIBXML2DEFS=-DNO_LIBXML2 LIBXML2INC= LIBXML2LIBS=" - info="${red} no${normal} [xml2-config not found]" - ret=1 - else - libxml2defs="LIBXML2_CONFIG=$libxml2config" - info="${green}yes${normal} [version `xml2-config --version`]" - fi -else - libxml2defs="LIBXML2_CONFIG=false LIBXML2DEFS=-DNO_LIBXML2 LIBXML2INC= LIBXML2LIBS=" - info="${yellow} no${normal} [disabled]" - ret=1 -fi -printf "%12s: %s\n" "libxml2" "$info" >&2 - - -if [ "$expat" != "no" ] && [[ "$libxml2defs" =~ -DNO_LIBXML2 ]] +if [ "$xerces" != "no" ] then tmpout=`mktemp /tmp/a.out.XXXXX` tmpsrc=`mktemp /tmp/a$$XXXXX.c` tmpver=`mktemp /tmp/a$$XXXXX.txt` cat >$tmpsrc << eof #include -#include +#include int main(void) { - XML_Expat_Version version_info; - version_info = XML_ExpatVersionInfo(); - printf("%d.%d.%d\n", version_info.major, version_info.minor, version_info.micro); + const char *version = XERCES_FULLVERSIONDOT; + printf("%s\n", version); return 0; } eof - libs="" - cmd="${CXX} ${EXTRA_CFLAGS} ${EXTRA_LDFLAGS} -o $tmpout $tmpsrc -lexpat" - $cmd $libs >/dev/null 2>&1 + if [ "`which pkg-config 2>/dev/null`" != "" ]; then + xercesc_cflags="`pkg-config xerces-c --cflags 2>/dev/null`" + xercesc_libs="`pkg-config xerces-c --libs 2>/dev/null`" + else + xercesc_cflags="" + xercesc_libs="" + fi + cmd="${CXX} ${EXTRA_CFLAGS} ${EXTRA_LDFLAGS} -o $tmpout $tmpsrc $xercesc_cflags" + $cmd $xercesc_libs >/dev/null 2>&1 if [ $? -ne 0 ]; then - expatdefs="EXPATDEFS=-DNO_EXPAT EXPATLIBS=" - info="${red} no${normal} [expat not found]" + xercescdefs="XERCESCDEFS=-DNO_XERCES_C XERCESCINC= XERCESCLIBS=" + info="${red} no${normal} [xercesc not found]" ret=1 else $tmpout >$tmpver 2>&1 + xercescdefs="" + if [ -n "$xercesc_cflags" ]; then + xercescdefs="${xercescdefs} XERCESCINC=${xercesc_cflags}" + fi + if [ -n "$xercesc_libs" ]; then + xercescdefs="${xercescdefs} XERCESCLIBS=${xercesc_libs}" + fi + # Remove a trailing space + xercescdefs="${xercescdefs# }" info="${green}yes${normal} [version `cat $tmpver`]" fi else info="${yellow} no${normal} [disabled]" - expatdefs="EXPATDEFS=-DNO_EXPAT EXPATLIBS=" + xercescdefs="XERCESCDEFS=-DNO_XERCES_C XERCESCINC= XERCESCLIBS=" ret=1 fi -printf "%12s: %s\n" "expat" "$info" >&2 +printf "%12s: %s\n" "Xerces-C++" "$info" >&2 rm -f $tmpsrc $tmpout $tmpver echo "" >&2 -echo $target $wxdefs $qt4defs $qt5defs $qt6defs $gtkdefs $x11defs $xftdefs $gsdefs $glfwdefs $gldefs $zmqdefs $avdefs $cairodefs $tiffdefs $aggdefs $libxml2defs $expatdefs $extradefs +echo $target $wxdefs $qt4defs $qt5defs $qt6defs $gtkdefs $x11defs $xftdefs $gsdefs $glfwdefs $gldefs $zmqdefs $avdefs $cairodefs $tiffdefs $aggdefs $xercescdefs $extradefs diff --git a/lib/grm/Makefile b/lib/grm/Makefile index c837286aa..f7e881a6e 100644 --- a/lib/grm/Makefile +++ b/lib/grm/Makefile @@ -45,25 +45,22 @@ UNAME := $(shell uname) src/grm/dom_render/graphics_tree/Node.o \ src/grm/dom_render/graphics_tree/Value.o \ src/grm/dom_render/graphics_tree/util.o - EXPATDEFS = -ifdef USE_STATIC_EXPAT_LIBS - EXPATLIBS = $(THIRDPARTYDIR)/lib/libexpat.a + XERCESCDEFS = + XERCESCINC = +ifdef USE_STATIC_XERCESC_LIBS + XERCESCLIBS = $(THIRDPARTYDIR)/lib/libxerces-c.a $(THIRDPARTYDIR)/lib/libicuuc.a $(THIRDPARTYDIR)/lib/libicudata.a else - EXPATLIBS = -lexpat + XERCESCLIBS = -lxerces-c endif -LIBXML2_CONFIG = false - LIBXML2DEFS = - LIBXML2INC = `$(LIBXML2_CONFIG) --cflags 2>/dev/null` - LIBXML2LIBS = `$(LIBXML2_CONFIG) --libs 2>/dev/null` - DEFINES = -DBUILDING_GR -DGRDIR=\"$(GRDIR)\" $(EXPATDEFS) $(LIBXML2DEFS) + DEFINES = -DBUILDING_GR -DGRDIR=\"$(GRDIR)\" $(XERCESCDEFS) INCLUDES = -I./include \ -I./src \ -I../gks \ -I../gr \ -I../gr3 \ -I$(THIRDPARTYDIR)/include \ - $(LIBXML2INC) + $(XERCESCINC) CC = cc CFLAGS = $(DEFINES) -std=c90 -O3 -Wall -fPIC -fvisibility=hidden $(EXTRA_CFLAGS) CXXFLAGS = $(DEFINES) -std=c++17 -O3 -Wall -fPIC -fvisibility=hidden $(EXTRA_CXXFLAGS) @@ -84,7 +81,7 @@ INSTALL_NAME = endif GRLIBS = -L ../gr/ -lGR GR3LIBS = -L ../gr3/ -lGR3 - LIBS = $(GRLIBS) $(GR3LIBS) $(EXPATLIBS) $(LIBXML2LIBS) -lm + LIBS = $(GRLIBS) $(GR3LIBS) $(XERCESCLIBS) -lm grplot_support = ifneq ($(QT5_QMAKE),) diff --git a/lib/grm/grplot/grplot.pro b/lib/grm/grplot/grplot.pro index d0c4bdaaf..c3723aabd 100644 --- a/lib/grm/grplot/grplot.pro +++ b/lib/grm/grplot/grplot.pro @@ -8,7 +8,7 @@ DEFINES += GRDIR=\\\"$(GRDIR)\\\" # Qt versions < 5.12 ignore `CONFIG`, so repeat the language flag here. # Only using `QMAKE_CXXFLAGS` does not work since newer Qt installations set # conflicting language flags themselves without setting `CONFIG`. -QMAKE_CXXFLAGS += -std=c++17 $$(LIBXML2DEFS) $$(EXTRA_CXXFLAGS) +QMAKE_CXXFLAGS += -std=c++17 $$(XERCESCDEFS) $$(EXTRA_CXXFLAGS) QMAKE_LFLAGS += $$(EXTRA_LDFLAGS) QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15 HEADERS += grplot_widget.hxx grplot_mainwindow.hxx util.hxx gredit/Bounding_logic.h gredit/Bounding_object.h gredit/CustomTreeWidgetItem.h gredit/TreeWidget.h gredit/AddElementWidget.h qtterm/grm_args_t_wrapper.h qtterm/receiver_thread.h diff --git a/lib/grm/grplot/grplot_widget.cxx b/lib/grm/grplot/grplot_widget.cxx index a1e473085..0fdc19fdf 100644 --- a/lib/grm/grplot/grplot_widget.cxx +++ b/lib/grm/grplot/grplot_widget.cxx @@ -387,7 +387,7 @@ GRPlotWidget::GRPlotWidget(QMainWindow *parent, int argc, char **argv) if (getenv("GRDISPLAY") && strcmp(getenv("GRDISPLAY"), "edit") == 0) { -#if !defined(NO_LIBXML2) +#if !defined(NO_LIBXML2) && 0 schema_tree = grm_load_graphics_tree_schema(); #else schema_tree = nullptr; @@ -2566,7 +2566,9 @@ void GRPlotWidget::load_file_slot() QMessageBox::critical(this, "File open not possible", QString::fromStdString(text_stream.str())); return; } +#if 0 grm_load_graphics_tree(file); +#endif redraw(); grm_render(); #else diff --git a/lib/grm/grplot/makefile.mingw b/lib/grm/grplot/makefile.mingw index a99bb1246..83ac82951 100644 --- a/lib/grm/grplot/makefile.mingw +++ b/lib/grm/grplot/makefile.mingw @@ -2,18 +2,16 @@ ifeq ($(strip $(THIRDPARTYDIR)),) override THIRDPARTYDIR = $(abspath $(CURDIR)/../../../3rdparty/build) endif - LIBXML2INC = -I$(THIRDPARTYDIR)/include/libxml2 INCLUDES = -I../include \ -I../../gks \ -I../../gr \ -I../../gr3 \ - -I$(THIRDPARTYDIR)/include \ - $(LIBXML2INC) + -I$(THIRDPARTYDIR)/include GRMLIB = ../libGRM.lib GKSLIBS = -L ../../gks/ -lGKS GRLIBS = -L ../../gr/ -lGR GR3LIBS = -L ../../gr3/ -lGR3 -LIBXML2LIBS = $(THIRDPARTYDIR)/lib/libxml2.a $(THIRDPARTYDIR)/lib/libz.a +XERCESCLIBS = $(THIRDPARTYDIR)/lib/libxerces-c.a $(THIRDPARTYDIR)/lib/libicuuc.a $(THIRDPARTYDIR)/lib/libicudata.a JPEGLIBS = $(THIRDPARTYDIR)/lib/libjpeg.a PNGLIBS = $(THIRDPARTYDIR)/lib/libpng.a ZLIBS = $(THIRDPARTYDIR)/lib/libz.a @@ -42,8 +40,8 @@ grplot.exe: grplot.cxx grplot_mainwindow.cxx grplot_widget.cxx util.cxx gredit/A moc -DGRDIR=\"$(GRDIR)\" -Iinclude gredit/TreeWidget.h -o gredit/moc_TreeWidget.cpp moc -DGRDIR=\"$(GRDIR)\" -Iinclude qtterm/grm_args_t_wrapper.h -o qtterm/moc_grm_args_t_wrapper.cpp moc -DGRDIR=\"$(GRDIR)\" -Iinclude qtterm/receiver_thread.h -o qtterm/moc_receiver_thread.cpp - $(CXX) -Wl,--subsystem,console -mconsole -std=c++17 -DGRDIR=\"$(GRDIR)\" -DGR_STATIC_LIB -DLIBXML_STATIC $(INCLUDES) -Iinclude -Iinclude/QtGui -Iinclude/QtWidgets -Iinclude/QtCore -I../ -o $@ $^ moc_grplot_widget.cxx moc_grplot_mainwindow.cxx gredit/moc_AddElementWidget.cpp gredit/moc_Bounding_logic.cpp gredit/moc_Bounding_object.cpp gredit/moc_CustomTreeWidgetItem.cpp gredit/moc_TreeWidget.cpp qtterm/moc_grm_args_t_wrapper.cpp qtterm/moc_receiver_thread.cpp Qt5Gui.dll Qt5Widgets.dll Qt5Core.dll \ - $(LIBXML2LIBS) $(JPEGLIBS) $(FTLIBS) $(PNGLIBS) $(ZLIBS) $(QHLIBS) $(LIBS) + $(CXX) -Wl,--subsystem,console -mconsole -std=c++17 -DGRDIR=\"$(GRDIR)\" -DGR_STATIC_LIB $(INCLUDES) -Iinclude -Iinclude/QtGui -Iinclude/QtWidgets -Iinclude/QtCore -I../ -o $@ $^ moc_grplot_widget.cxx moc_grplot_mainwindow.cxx gredit/moc_AddElementWidget.cpp gredit/moc_Bounding_logic.cpp gredit/moc_Bounding_object.cpp gredit/moc_CustomTreeWidgetItem.cpp gredit/moc_TreeWidget.cpp qtterm/moc_grm_args_t_wrapper.cpp qtterm/moc_receiver_thread.cpp Qt5Gui.dll Qt5Widgets.dll Qt5Core.dll \ + $(XERCESCLIBS) $(JPEGLIBS) $(FTLIBS) $(PNGLIBS) $(ZLIBS) $(QHLIBS) $(LIBS) endif clean: diff --git a/lib/grm/include/grm/plot.h b/lib/grm/include/grm/plot.h index 13719c9a3..3d40ad469 100644 --- a/lib/grm/include/grm/plot.h +++ b/lib/grm/include/grm/plot.h @@ -44,9 +44,11 @@ EXPORT int grm_process_tree(void); EXPORT int grm_export(const char *file_path); EXPORT int grm_switch(unsigned int id); +#if 0 #if !defined(NO_EXPAT) || !defined(NO_LIBXML2) EXPORT int grm_load_graphics_tree(FILE *file); #endif +#endif #ifdef __cplusplus } diff --git a/lib/grm/makefile.mingw b/lib/grm/makefile.mingw index 03dfe32de..956431778 100644 --- a/lib/grm/makefile.mingw +++ b/lib/grm/makefile.mingw @@ -2,23 +2,21 @@ ifeq ($(strip $(THIRDPARTYDIR)),) override THIRDPARTYDIR = $(abspath $(CURDIR)/../../3rdparty/build) endif - DEFINES = -DGRDIR=\"$(GRDIR)\" -DNO_GS -DNO_X11 -D_WIN32_WINNT=0x0600 -DBUILDING_DLL -DLIBXML_STATIC + DEFINES = -DGRDIR=\"$(GRDIR)\" -DNO_GS -DNO_X11 -D_WIN32_WINNT=0x0600 -DBUILDING_DLL CFLAGS = $(DEFINES) -std=c90 CXXFLAGS = $(DEFINES) -std=c++17 - LIBXML2INC = -I$(THIRDPARTYDIR)/include/libxml2 INCLUDES = -I./include \ -I./src \ -I../gks \ -I../gr \ -I../gr3 \ - -I$(THIRDPARTYDIR)/include \ - $(LIBXML2INC) -LIBXML2LIBS = $(THIRDPARTYDIR)/lib/libxml2.a $(THIRDPARTYDIR)/lib/libz.a + -I$(THIRDPARTYDIR)/include +XERCESCLIBS = $(THIRDPARTYDIR)/lib/libxerces-c.a $(THIRDPARTYDIR)/lib/libicuuc.a $(THIRDPARTYDIR)/lib/libicudata.a GKSLIBS = -L ../gks/ -lGKS GRLIBS = -L ../gr/ -lGR GR3LIBS = -L ../gr3/ -lGR3 LDFLAGS = -Wl,--out-implib,$(@:.dll=.a) - LIBS = $(GR3LIBS) $(GRLIBS) $(GKSLIBS) $(LIBXML2LIBS) -lm -lws2_32 -lmsimg32 -lgdi32 + LIBS = $(GR3LIBS) $(GRLIBS) $(GKSLIBS) $(XERCESCLIBS) -lm -lws2_32 -lmsimg32 -lgdi32 -lpthread OBJS = src/grm/args.o \ src/grm/backtrace.o \ diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 81027b68a..77342b0c0 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -27,12 +27,14 @@ extern "C" { #include "logging_int.h" } +#if 0 #ifndef NO_LIBXML2 #include #include #elif !defined(NO_EXPAT) #include #endif +#endif #include "plot_int.h" #include "grm/layout.hxx" @@ -4545,6 +4547,7 @@ err_t classes_polar_histogram(grm_args_t *subplot_args) /* ------------------------- xml ------------------------------------------------------------------------------------ */ +#if 0 #ifndef NO_LIBXML2 #if LIBXML_VERSION >= 21200 static void schema_parse_error_handler(void *has_schema_errors, const xmlError *error) @@ -4609,10 +4612,12 @@ err_t validate_graphics_tree(void) return error; } +#endif #endif int validate_graphics_tree_with_error_messages(void) { +#if 0 #ifndef NO_LIBXML2 err_t validation_error = validate_graphics_tree(); if (validation_error == ERROR_NONE) @@ -4636,6 +4641,7 @@ int validate_graphics_tree_with_error_messages(void) } #else fprintf(stderr, "No libxml2 support compiled in, no validation possible!\n"); +#endif #endif return 1; } @@ -4776,6 +4782,7 @@ char *grm_dump_graphics_tree_str(void) return graphics_tree_cstr; } +#if 0 #ifndef NO_LIBXML2 int grm_load_graphics_tree(FILE *file) { @@ -4938,6 +4945,7 @@ static void xml_parse_end_handler(void *data, const char *tagName) int grm_load_graphics_tree(FILE *file) { +#if 0 std::string xmlstring; XML_Parser parser = XML_ParserCreate(nullptr); std::shared_ptr parentNode; @@ -4961,8 +4969,12 @@ int grm_load_graphics_tree(FILE *file) XML_ParserFree(parser); return 1; +#else + return 0; +#endif } #endif +#endif int grm_merge(const grm_args_t *args) { @@ -5423,6 +5435,7 @@ int grm_switch(unsigned int id) /* ~~~~~~~~~~~~~~~~~~~~~~~~~ c++ libxml util ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +#if 0 #ifndef NO_LIBXML2 std::shared_ptr grm_load_graphics_tree_schema(void) { @@ -5510,6 +5523,7 @@ std::shared_ptr grm_load_graphics_tree_schema(void) return (error == ERROR_NONE) ? document : nullptr; } #endif +#endif /* ~~~~~~~~~~~~~~~~~~~~~~~~~ c++ util ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -5788,9 +5802,11 @@ int get_focus_and_factor_from_dom(const int x1, const int y1, const int x2, cons bool grm_validate(void) { +#if 0 #ifndef NO_LIBXML2 err_t validation_error = validate_graphics_tree(); return (validation_error == ERROR_NONE); +#endif #endif return false; } diff --git a/lib/grm/src/grm/util.c b/lib/grm/src/grm/util.c index cacd695bc..8b7d02b20 100644 --- a/lib/grm/src/grm/util.c +++ b/lib/grm/src/grm/util.c @@ -11,6 +11,7 @@ #include #include #if defined __unix__ || defined __APPLE__ +#include #include #elif defined _WIN32 #define WIN32_LEAN_AND_MEAN diff --git a/packaging/debian/debian.rules b/packaging/debian/debian.rules index ea55c33e8..3d8a48028 100644 --- a/packaging/debian/debian.rules +++ b/packaging/debian/debian.rules @@ -32,7 +32,7 @@ override_dh_auto_configure: cp ../SOURCES/pixman-0.42.2.tar.gz ${THIRDPARTY_SRC} cp ../SOURCES/tiff-4.5.1.tar.gz ${THIRDPARTY_SRC} cp ../SOURCES/libopenh264-2.0.0.tar.gz ${THIRDPARTY_SRC} - cp ../SOURCES/libxml2-2.10.4.tar.xz ${THIRDPARTY_SRC} + cp ../SOURCES/xerces-c-3.2.4.tar.gz ${THIRDPARTY_SRC} override_dh_auto_build: make -C 3rdparty GRDIR=${GRDIR} DIR=`pwd`/${THIRDPARTY} diff --git a/packaging/redhat/gr.spec b/packaging/redhat/gr.spec index 47fc7475e..076e8f3f6 100644 --- a/packaging/redhat/gr.spec +++ b/packaging/redhat/gr.spec @@ -32,7 +32,7 @@ Source8: https://gr-framework.org/downloads/3rdparty/cairo-1.16.0.tar.xz Source9: https://gr-framework.org/downloads/3rdparty/pixman-0.42.2.tar.gz Source10: https://gr-framework.org/downloads/3rdparty/tiff-4.5.1.tar.gz Source11: https://gr-framework.org/downloads/3rdparty/libopenh264-2.0.0.tar.gz -Source12: https://gr-framework.org/downloads/3rdparty/libxml2-2.10.4.tar.xz +Source12: https://gr-framework.org/downloads/3rdparty/xerces-c-3.2.4.tar.gz BuildRequires: git BuildRequires: gcc-c++ BuildRequires: libX11-devel From 5b84a1e517650477fc7eff3ce202129027817c2f Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Tue, 23 Jan 2024 17:05:08 +0100 Subject: [PATCH 19/48] [GRM] Integrate Xerces into `plot.cxx` / source code --- lib/grm/grplot/gredit/AddElementWidget.cpp | 2 +- lib/grm/grplot/grplot_widget.cxx | 11 +- lib/grm/include/grm/plot.h | 6 +- lib/grm/src/grm/plot.cxx | 1002 +++++++++++++------- lib/grm/src/grm/plot_int.h | 2 +- 5 files changed, 677 insertions(+), 346 deletions(-) diff --git a/lib/grm/grplot/gredit/AddElementWidget.cpp b/lib/grm/grplot/gredit/AddElementWidget.cpp index c9383ed64..e9c47f70d 100644 --- a/lib/grm/grplot/gredit/AddElementWidget.cpp +++ b/lib/grm/grplot/gredit/AddElementWidget.cpp @@ -12,7 +12,7 @@ AddElementWidget::AddElementWidget(GRPlotWidget *widget, QWidget *parent) : QWidget(parent) { grplot_widget = widget; -#if !defined(NO_LIBXML2) +#if !defined(NO_XERCES_C) schema_tree = grplot_widget->get_schema_tree(); #else schema_tree = nullptr; diff --git a/lib/grm/grplot/grplot_widget.cxx b/lib/grm/grplot/grplot_widget.cxx index 0fdc19fdf..b579fe2ba 100644 --- a/lib/grm/grplot/grplot_widget.cxx +++ b/lib/grm/grplot/grplot_widget.cxx @@ -387,7 +387,7 @@ GRPlotWidget::GRPlotWidget(QMainWindow *parent, int argc, char **argv) if (getenv("GRDISPLAY") && strcmp(getenv("GRDISPLAY"), "edit") == 0) { -#if !defined(NO_LIBXML2) && 0 +#if !defined(NO_XERCES_C) schema_tree = grm_load_graphics_tree_schema(); #else schema_tree = nullptr; @@ -2550,7 +2550,7 @@ void GRPlotWidget::load_file_slot() { if (getenv("GRDISPLAY") && strcmp(getenv("GRDISPLAY"), "edit") == 0) { -#ifndef NO_LIBXML2 +#ifndef NO_XERCES_C std::string path = QFileDialog::getOpenFileName(this, "Open XML", QDir::homePath(), "XML files (*.xml)").toStdString(); if (path.empty()) @@ -2566,11 +2566,9 @@ void GRPlotWidget::load_file_slot() QMessageBox::critical(this, "File open not possible", QString::fromStdString(text_stream.str())); return; } -#if 0 grm_load_graphics_tree(file); -#endif + global_root = grm_get_document_root(); redraw(); - grm_render(); #else std::stringstream text_stream; text_stream << "XML support not compiled in. Please recompile GRPlot with libxml2 support."; @@ -2603,7 +2601,8 @@ void GRPlotWidget::save_file_slot() QMessageBox::critical(this, "File save not possible", QString::fromStdString(text_stream.str())); return; } - save_file_stream << GRM::toXML(grm_get_render()) << std::endl; + auto graphics_tree_str = std::unique_ptr(grm_dump_graphics_tree_str(), std::free); + save_file_stream << graphics_tree_str.get() << std::endl; save_file_stream.close(); } } diff --git a/lib/grm/include/grm/plot.h b/lib/grm/include/grm/plot.h index 3d40ad469..085272e78 100644 --- a/lib/grm/include/grm/plot.h +++ b/lib/grm/include/grm/plot.h @@ -44,11 +44,9 @@ EXPORT int grm_process_tree(void); EXPORT int grm_export(const char *file_path); EXPORT int grm_switch(unsigned int id); -#if 0 -#if !defined(NO_EXPAT) || !defined(NO_LIBXML2) +#if !defined(NO_XERCES_C) EXPORT int grm_load_graphics_tree(FILE *file); #endif -#endif #ifdef __cplusplus } @@ -68,7 +66,7 @@ EXPORT int get_focus_and_factor_from_dom(const int x1, const int y1, const int x std::shared_ptr &subplot_element); EXPORT bool grm_validate(void); -#if !defined(NO_LIBXML2) +#if !defined(NO_XERCES_C) EXPORT std::shared_ptr grm_load_graphics_tree_schema(void); #endif #endif diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 77342b0c0..4561987a6 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -6,8 +6,18 @@ #include #include -#include +#include +#include +#include +#include #include +#include +#include +#include + +#ifdef __unix__ +#include +#endif extern "C" { #include @@ -27,13 +37,21 @@ extern "C" { #include "logging_int.h" } -#if 0 -#ifndef NO_LIBXML2 -#include -#include -#elif !defined(NO_EXPAT) -#include -#endif +#ifndef NO_XERCES_C +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include #endif #include "plot_int.h" @@ -4547,78 +4565,627 @@ err_t classes_polar_histogram(grm_args_t *subplot_args) /* ------------------------- xml ------------------------------------------------------------------------------------ */ -#if 0 -#ifndef NO_LIBXML2 -#if LIBXML_VERSION >= 21200 -static void schema_parse_error_handler(void *has_schema_errors, const xmlError *error) -#else -static void schema_parse_error_handler(void *has_schema_errors, xmlError *error) -#endif +#ifndef NO_XERCES_C +namespace XERCES_CPP_NAMESPACE { - fprintf(stderr, "XML validation error at line %d, column %d: %s", error->line, error->int2, error->message); - *((bool *)has_schema_errors) = true; + +/*! + * \brief A helper class to encode Xerces strings in UTF-8. + * + * This class takes a Xerces string and encodes it in UTF-8. By overloading the `<<` operator, the encoding result can + * be used with C++ streams directly. + */ +class TranscodeToUtf8Str : public TranscodeToStr +{ +public: + TranscodeToUtf8Str(const XMLCh *in) : TranscodeToStr(in, "UTF-8") {} +}; + +inline std::ostream &operator<<(std::ostream &target, const TranscodeToStr &to_dump) +{ + target << to_dump.str(); + return target; } -err_t validate_graphics_tree(void) +/*! + * \brief Another helper class to encode Xerces strings in another encoding. + * + * `TranscodeToUtf8Str` is meant to be used with temporary objects in a stream expression. In contrast, objects of + * `XMLStringBuffer` are meant to be used with permanent objects to avoid the overhead of constructing new objects for + * every string encoding operation. + */ +class XMLStringBuffer : public XMLFormatter, private XMLFormatTarget { - char *gr_dir = get_gr_dir(); - std::string schema_filepath{std::string(gr_dir) + "/" + SCHEMA_REL_FILEPATH}; - free(reinterpret_cast(gr_dir)); - xmlSchemaParserCtxtPtr schema_parser_ctxt = nullptr; - xmlSchemaPtr schema = nullptr; - bool has_schema_errors = false; - xmlSchemaValidCtxtPtr valid_ctxt = nullptr; - xmlDocPtr doc = nullptr; - err_t error = ERROR_NONE; +public: + /*! + * \brief Construct a new XMLStringBuffer object. + * + * \param[in] encoding The encoding to apply to Xerces strings, e.g. "UTF-8". + */ + XMLStringBuffer(const char *encoding) : XMLFormatter(encoding, this) {} + + void writeChars(const XMLByte *const toWrite, const XMLSize_t count, XMLFormatter *const formatter) override + { + out_buffer_.write((char *)toWrite, (int)count); + } + + /*! + * \brief Encode a given Xerces string and return the result. + * + * \param[in] chars Xerces string to encode. + * \return The encoded string. + */ + std::string encode(std::optional chars = std::nullopt) + { + if (chars) + { + *this << *chars; + } + std::string out = out_buffer_.str(); + out_buffer_.str(""); + return out; + } + +private: + std::stringstream out_buffer_; +}; - xmlInitParser(); - if (!file_exists(schema_filepath.c_str())) +/*! + * \brief A helper class for `FileInputSource` which manages the file reading. + */ +class FileBinInputStream : public BinInputStream +{ +public: + FileBinInputStream(FILE *file) : file_(file) {} + + XMLFilePos curPos() const override { return ftell(file_); } + + XMLSize_t readBytes(XMLByte *const toFill, const XMLSize_t maxToRead) override + { + return fread(toFill, sizeof(XMLByte), maxToRead, file_); + } + + const XMLCh *getContentType() const override { return nullptr; } + +private: + FILE *file_; +}; + +/*! + * \brief An adapter class to read XML data from a file. + */ +class FileInputSource : public InputSource +{ +public: + FileInputSource(FILE *file) + : file_(file), recovered_filename_(recover_filename()), + system_id_transcoder_(reinterpret_cast(recovered_filename_.c_str()), + recovered_filename_.length(), "UTF-8") + { + } + + BinInputStream *makeStream() const override { return new FileBinInputStream(file_); } + + const XMLCh *getSystemId() const override { return system_id_transcoder_.str(); } + +private: + /*! + * \brief Recover the filename from a file descriptor. + * + * Xerces assigns a system id to input sources to make them distinguishable from each other. Thus, this function tries + * to recover the filename from the given FILE object to have a suitable system id. Currently, this is only possible + * on Unix systems with mounted `/proc` filesystem. + * + * \return The recovered filename if found, otherwise ``. + */ + std::string recover_filename() const + { +#ifdef __unix__ + std::stringstream proc_link_stream; + const unsigned int MAXSIZE = 4096; + std::array filename; + ssize_t readlink_bytes; + + proc_link_stream << "/proc/self/fd/" << fileno(file_); + auto proc_link{proc_link_stream.str()}; + readlink_bytes = readlink(proc_link.c_str(), filename.data(), MAXSIZE); + filename[readlink_bytes] = '\0'; + if (readlink_bytes >= 0) + { + filename[readlink_bytes] = '\0'; + return filename.data(); + } +#endif + return ""; + } + + FILE *file_; + std::string recovered_filename_; + TranscodeFromStr system_id_transcoder_; +}; + +/*! + * \brief A helper class for `StringInputSource` which reads data from a string. + */ +class StringInputStream : public BinInputStream +{ +public: + StringInputStream(const std::string &str) : str_(str), cur_pos_(0) {} + + XMLFilePos curPos() const override { return cur_pos_; } + + XMLSize_t readBytes(XMLByte *const toFill, const XMLSize_t maxToRead) override + { + auto str_view = std::string_view(str_).substr(cur_pos_, maxToRead); + memcpy(toFill, str_view.data(), str_view.length()); + cur_pos_ += str_view.length(); + return str_view.length(); + } + + const XMLCh *getContentType() const override { return nullptr; } + +private: + const std::string &str_; + long cur_pos_; +}; + +/*! + * \brief An adapter class to read XML data from a string. + */ +class StringInputSource : public InputSource +{ +public: + StringInputSource(const std::string &str) + : str_(str), system_id_transcoder_(reinterpret_cast(""), + strlen(""), "UTF-8") + { + } + + StringInputSource(std::string &&str) + : str_(std::move(str)), system_id_transcoder_(reinterpret_cast(""), + strlen(""), "UTF-8") + { + } + + BinInputStream *makeStream() const override { return new StringInputStream(str_); } + + const XMLCh *getSystemId() const override { return system_id_transcoder_.str(); } + +private: + std::string str_; + TranscodeFromStr system_id_transcoder_; +}; + +/*! + * \brief A class which manages error reporting for a SAX parser. + */ +class SaxErrorHandler : public ErrorHandler +{ +public: + SaxErrorHandler() = default; + + SaxErrorHandler(const std::string &schema_filepath) : schema_filepath_(schema_filepath), schema_invalid_(false) {} + + void warning(const SAXParseException &e) override + { + std::cerr << "\nWarning at file " << TranscodeToUtf8Str(e.getSystemId()) << ", line " << e.getLineNumber() + << ", char " << e.getColumnNumber() << "\n Message: " << TranscodeToUtf8Str(e.getMessage()) << std::endl; + } + + void error(const SAXParseException &e) override + { + std::cerr << "\nError at file " << TranscodeToUtf8Str(e.getSystemId()) << ", line " << e.getLineNumber() + << ", char " << e.getColumnNumber() << "\n Message: " << TranscodeToUtf8Str(e.getMessage()) << std::endl; + } + + void fatalError(const SAXParseException &e) override + { + auto system_id{TranscodeToUtf8Str(e.getSystemId())}; + std::cerr << "\nFatal Error at file " << system_id << ", line " << e.getLineNumber() << ", char " + << e.getColumnNumber() << "\n Message: " << TranscodeToUtf8Str(e.getMessage()) << std::endl; + if (std::string(reinterpret_cast(system_id.str())) == schema_filepath_) + { + schema_invalid_ = true; + } + } + + void resetErrors() override + { + if (schema_filepath_) + { + schema_invalid_ = false; + } + } + + std::optional schema_invalid() const { return schema_invalid_; } + +private: + std::optional schema_filepath_; + std::optional schema_invalid_; +}; + +/*! + * \brief The core class to handle SAX parsing of XML encoded graphics trees. + * + * This class inherits from all parser types that are needed to handle the whole parsing process. + */ +class GraphicsTreeParseHandler : public DefaultHandler, public SaxErrorHandler, public PSVIHandler +{ +public: + GraphicsTreeParseHandler() {} + ~GraphicsTreeParseHandler() {} + + void startDocument() override {} + + void endDocument() override {} + + /*! + * \brief Handle the start of an XML tag in the document. + * + * This function creates a new GRM tree element and records all attributes which need to be inserted into the element. + * All further processing is delayed until the PSVI handler is called since it has access to the XML schema and the + * types of all attributes. + */ + void startElement(const XMLCh *const uri, const XMLCh *const localname, const XMLCh *const qname, + const Attributes &attributes) override + { + const std::string node_name = encode(qname); + + if (node_name == "root") + { + global_root = global_render->createElement("root"); + global_render->replaceChildren(global_root); + current_element_ = global_root; + insertion_parent_ = nullptr; + } + else + { + current_element_ = global_render->createElement(node_name); + } + + XMLSize_t attribute_count = attributes.getLength(); + current_attributes_.clear(); + current_attributes_.reserve(attribute_count); + for (XMLSize_t i = 0; i < attribute_count; i++) + { + current_attributes_.push_back({encode(attributes.getQName(i)), encode(attributes.getValue(i))}); + } + } + + /*! + * \brief Handle a closing XML tag. + * + * This function navigates one level up in the graphics tree hierarchy and sets this value as the new insertion + * parent. + */ + void endElement(const XMLCh *const uri, const XMLCh *const localname, const XMLCh *const qname) override + { + insertion_parent_ = insertion_parent_->parentElement(); + } + + /*! + * \brief Process the types of all attributes of a new XML tag. + * + * This handler is called after `startElement`. The type information in this method together with the previously + * recorded element data can be used to create a new element in the graphics tree. + */ + void handleAttributesPSVI(const XMLCh *const localName, const XMLCh *const uri, + PSVIAttributeList *psviAttributes) override + { + XMLSize_t attribute_count = psviAttributes->getLength(); + for (XMLSize_t i = 0; i < attribute_count; i++) + { + auto attribute_declaration = psviAttributes->getAttributePSVIAtIndex(i)->getAttributeDeclaration(); + if (attribute_declaration == nullptr) + { + continue; + } + + const std::string &attribute_name = current_attributes_[i].first; + const std::string &attribute_value = current_attributes_[i].second; + assert(attribute_name == encode(attribute_declaration->getName())); + const std::string attribute_type = encode(attribute_declaration->getTypeDefinition()->getName()); + + std::vector attribute_types; + if (attribute_type == "strint") + { + attribute_types = {"integer", "string"}; + } + else + { + attribute_types = {attribute_type}; + } + + for (const auto &attribute_type : attribute_types) + { + try + { + if (attribute_type == "integer") + { + current_element_->setAttribute(attribute_name, std::stoi(attribute_value)); + } + else if (attribute_type == "double") + { + current_element_->setAttribute(attribute_name, std::stod(attribute_value)); + } + else + { + current_element_->setAttribute(attribute_name, attribute_value); + } + break; + } + catch (const std::invalid_argument &) + { + } + } + if (attribute_name == "active" && attribute_value == "1") + { + global_render->setActiveFigure(current_element_); + } + } + + if (insertion_parent_ != nullptr) + { + insertion_parent_->appendChild(current_element_); + } + insertion_parent_ = current_element_; + } + + void handleElementPSVI(const XMLCh *const localName, const XMLCh *const uri, PSVIElement *elementInfo) override {} + + void handlePartialElementPSVI(const XMLCh *const localName, const XMLCh *const uri, PSVIElement *elementInfo) override + { + } + + void warning(const SAXParseException &e) override { SaxErrorHandler::warning(e); } + + void error(const SAXParseException &e) override { SaxErrorHandler::error(e); } + + void fatalError(const SAXParseException &e) override { SaxErrorHandler::fatalError(e); } + + void resetErrors() override { SaxErrorHandler::resetErrors(); } + +private: + std::string encode(std::optional chars = std::nullopt) { return xml_buffer_.encode(chars); } + + XMLStringBuffer xml_buffer_{"UTF-8"}; + std::shared_ptr insertion_parent_, current_element_; + std::vector> current_attributes_; +}; + +/*! + * \brief The core class to handle SAX parsing of XML schemas. + * + * This class inherits from all parser types that are needed to handle the whole parsing process. + */ +class SchemaParseHandler : public DefaultHandler, public SaxErrorHandler +{ +public: + SchemaParseHandler(GRM::Document &document) : document_(document) {} + ~SchemaParseHandler() {} + + void startDocument() override {} + + void endDocument() override {} + + /*! + * \brief Handle the start of an XML tag in the document. + * + * This function creates a new GRM tree element and adds all read attributes to the element. + */ + void startElement(const XMLCh *const uri, const XMLCh *const localname, const XMLCh *const qname, + const Attributes &attributes) override + { + const std::string node_name = encode(qname); + + if (node_name == "xs:schema") + { + current_gr_element_ = document_.createElement("xs:schema"); + document_.replaceChildren(current_gr_element_); + insertion_parent_ = nullptr; + } + else + { + current_gr_element_ = document_.createElement(node_name); + } + + XMLSize_t attribute_count = attributes.getLength(); + for (XMLSize_t i = 0; i < attribute_count; i++) + { + current_gr_element_->setAttribute(encode(attributes.getQName(i)), encode(attributes.getValue(i))); + } + if (insertion_parent_ != nullptr) + { + insertion_parent_->appendChild(current_gr_element_); + } + insertion_parent_ = current_gr_element_; + } + + /*! + * \brief Handle a closing XML tag. + * + * This function navigates one level up in the graphics tree hierarchy and sets this value as the new insertion + * parent. + */ + void endElement(const XMLCh *const uri, const XMLCh *const localname, const XMLCh *const qname) override + { + insertion_parent_ = insertion_parent_->parentElement(); + } + + void warning(const SAXParseException &e) override { SaxErrorHandler::warning(e); } + + void error(const SAXParseException &e) override { SaxErrorHandler::error(e); } + + void fatalError(const SAXParseException &e) override { SaxErrorHandler::fatalError(e); } + + void resetErrors() override { SaxErrorHandler::resetErrors(); } + +private: + std::string encode(std::optional chars = std::nullopt) { return xml_buffer_.encode(chars); } + + XMLStringBuffer xml_buffer_{"UTF-8"}; + GRM::Document &document_; + std::shared_ptr insertion_parent_, current_gr_element_; +}; +} // namespace XERCES_CPP_NAMESPACE + + +/*! + * \brief Load a graphics tree from an XML file. + * + * \param[in] file The file object to parse from. + * \return 1 on success, 0 on failure. + */ +int grm_load_graphics_tree(FILE *file) +{ + using namespace XERCES_CPP_NAMESPACE; + + std::string schema_filepath{std::string(get_gr_dir()) + "/" + SCHEMA_REL_FILEPATH}; + + if (plot_init_static_variables() != ERROR_NONE) { - return ERROR_PARSE_XML_NO_SCHEMA_FILE; + return 0; } - schema_parser_ctxt = xmlSchemaNewParserCtxt(schema_filepath.c_str()); - cleanup_and_set_error_if(schema_parser_ctxt == nullptr, ERROR_PARSE_XML_INVALID_SCHEMA); - schema = xmlSchemaParse(schema_parser_ctxt); - cleanup_and_set_error_if(schema == nullptr, ERROR_PARSE_XML_INVALID_SCHEMA); - xmlSchemaFreeParserCtxt(schema_parser_ctxt); - schema_parser_ctxt = nullptr; - valid_ctxt = xmlSchemaNewValidCtxt(schema); - doc = xmlReadDoc(BAD_CAST toXML(global_root).c_str(), nullptr, nullptr, XML_PARSE_NOBLANKS); - cleanup_and_set_error_if(doc == nullptr, ERROR_PARSE_XML_PARSING); - xmlSchemaSetValidStructuredErrors(valid_ctxt, schema_parse_error_handler, &has_schema_errors); - xmlSchemaValidateDoc(valid_ctxt, doc); - cleanup_and_set_error_if(has_schema_errors, ERROR_PARSE_XML_FAILED_SCHEMA_VALIDATION); -cleanup: - if (doc != nullptr) + try { - xmlFreeDoc(doc); + XMLPlatformUtils::Initialize(); } - if (valid_ctxt != nullptr) + catch (const XMLException &e) { - xmlSchemaFreeValidCtxt(valid_ctxt); + std::cerr << "Error during initialization! :\n" << TranscodeToUtf8Str(e.getMessage()) << std::endl; + return 0; } - if (schema != nullptr) + + bool auto_update; + global_render->getAutoUpdate(&auto_update); + global_render->setAutoUpdate(false); + + XMLSize_t errorCount = 0; + { + auto parser = + std::unique_ptr(static_cast(XMLReaderFactory::createXMLReader())); + + // Activate validation + parser->setFeature(XMLUni::fgSAX2CoreValidation, true); + parser->setFeature(XMLUni::fgXercesDynamic, false); + parser->setFeature(XMLUni::fgXercesSchema, true); + parser->setFeature(XMLUni::fgXercesSchemaFullChecking, true); + auto schema_filepath_transcoder = + TranscodeFromStr(reinterpret_cast(schema_filepath.c_str()), schema_filepath.length(), "UTF-8"); + parser->setProperty(XMLUni::fgXercesSchemaExternalNoNameSpaceSchemaLocation, + (void *)schema_filepath_transcoder.str()); + + try + { + GraphicsTreeParseHandler handler(*global_render->getContext()); + parser->setPSVIHandler(&handler); + parser->setContentHandler(&handler); + parser->setErrorHandler(static_cast(&handler)); + parser->parse(FileInputSource(file)); + errorCount = parser->getErrorCount(); + } + catch (const OutOfMemoryException &) + { + std::cerr << "OutOfMemoryException" << std::endl; + } + catch (const XMLException &e) + { + std::cerr << "\nAn error occurred\n Error: " << TranscodeToUtf8Str(e.getMessage()) << "\n" << std::endl; + } + + } // `parser` must be freed before `XMLPlatformUtils::Terminate()` is called + + XMLPlatformUtils::Terminate(); + + edit_figure = global_render->getActiveFigure(); + global_render->setAutoUpdate(auto_update); + + return errorCount == 0; +} + +/*! + * \brief Validate the currently loaded grapics tree against the internal XML schema definition. + * + * \return ERROR_NONE on success, an error code on failure. + */ +err_t validate_graphics_tree(void) +{ + using namespace XERCES_CPP_NAMESPACE; + + std::string schema_filepath{std::string(get_gr_dir()) + "/" + SCHEMA_REL_FILEPATH}; + if (!file_exists(schema_filepath.c_str())) + { + return ERROR_PARSE_XML_NO_SCHEMA_FILE; + } + + try { - xmlSchemaFree(schema); + XMLPlatformUtils::Initialize(); } - if (schema_parser_ctxt != nullptr) + catch (const XMLException &e) { - xmlSchemaFreeParserCtxt(schema_parser_ctxt); + std::cerr << "Error during initialization! :\n" << TranscodeToUtf8Str(e.getMessage()) << std::endl; + return ERROR_PARSE_XML_PARSING; } - xmlCleanupParser(); - return error; + XMLSize_t errorCount = 0; + bool schema_invalid = false; + { + auto parser = + std::unique_ptr(static_cast(XMLReaderFactory::createXMLReader())); + + // Activate validation + parser->setFeature(XMLUni::fgSAX2CoreValidation, true); + parser->setFeature(XMLUni::fgXercesDynamic, false); + parser->setFeature(XMLUni::fgXercesSchema, true); + parser->setFeature(XMLUni::fgXercesSchemaFullChecking, true); + auto schema_filepath_transcoder = + TranscodeFromStr(reinterpret_cast(schema_filepath.c_str()), schema_filepath.length(), "UTF-8"); + parser->setProperty(XMLUni::fgXercesSchemaExternalNoNameSpaceSchemaLocation, + (void *)schema_filepath_transcoder.str()); + + try + { + SaxErrorHandler error_handler(schema_filepath); + parser->setErrorHandler(&error_handler); + parser->parse(StringInputSource(toXML(global_root))); + errorCount = parser->getErrorCount(); + schema_invalid = error_handler.schema_invalid().value(); + } + catch (const OutOfMemoryException &) + { + std::cerr << "OutOfMemoryException" << std::endl; + } + catch (const XMLException &e) + { + std::cerr << "\nAn error occurred\n Error: " << TranscodeToUtf8Str(e.getMessage()) << "\n" << std::endl; + } + + } // `parser` must be freed before `XMLPlatformUtils::Terminate()` is called + + XMLPlatformUtils::Terminate(); + + return schema_invalid ? ERROR_PARSE_XML_INVALID_SCHEMA + : ((errorCount == 0) ? ERROR_NONE : ERROR_PARSE_XML_FAILED_SCHEMA_VALIDATION); +} } -#endif #endif +extern "C" { + +/*! + * \brief Validate the currently loaded graphics tree and print error messages to stderr. + * + * This is a helper function to make `validate_graphics_tree` more convenient to use. + * + * \return 1 on success, 0 on failure. + */ int validate_graphics_tree_with_error_messages(void) { -#if 0 -#ifndef NO_LIBXML2 +#ifndef NO_XERCES_C err_t validation_error = validate_graphics_tree(); if (validation_error == ERROR_NONE) { @@ -4640,8 +5207,7 @@ int validate_graphics_tree_with_error_messages(void) return 0; } #else - fprintf(stderr, "No libxml2 support compiled in, no validation possible!\n"); -#endif + fprintf(stderr, "No Xerces-C++ support compiled in, no validation possible!\n"); #endif return 1; } @@ -4782,200 +5348,6 @@ char *grm_dump_graphics_tree_str(void) return graphics_tree_cstr; } -#if 0 -#ifndef NO_LIBXML2 -int grm_load_graphics_tree(FILE *file) -{ - char *gr_dir = get_gr_dir(); - std::string schema_filepath{std::string(gr_dir) + "/" + SCHEMA_REL_FILEPATH}; - free(reinterpret_cast(gr_dir)); - bool xml_validation_enabled = false, use_xml_schema = false; - xmlSchemaParserCtxtPtr schema_parser_ctxt = nullptr; - xmlSchemaPtr schema = nullptr; - bool has_schema_errors = false; - int ret = -1; - xmlSchemaValidCtxtPtr valid_ctxt = nullptr; - xmlTextReaderPtr reader = nullptr; - std::shared_ptr insertion_parent, current_gr_element; - err_t error = ERROR_NONE; - - error = plot_init_static_variables(); - cleanup_if_error; - - xmlInitParser(); - xml_validation_enabled = is_env_variable_enabled(ENABLE_XML_VALIDATION_ENV_KEY.c_str()); - use_xml_schema = xml_validation_enabled && file_exists(schema_filepath.c_str()); - if (use_xml_schema) - { - schema_parser_ctxt = xmlSchemaNewParserCtxt(schema_filepath.c_str()); - cleanup_and_set_error_if(schema_parser_ctxt == nullptr, ERROR_PARSE_XML_INVALID_SCHEMA); - schema = xmlSchemaParse(schema_parser_ctxt); - cleanup_and_set_error_if(schema == nullptr, ERROR_PARSE_XML_INVALID_SCHEMA); - xmlSchemaFreeParserCtxt(schema_parser_ctxt); - schema_parser_ctxt = nullptr; - valid_ctxt = xmlSchemaNewValidCtxt(schema); - } - reader = xmlReaderForFd(fileno(file), nullptr, nullptr, XML_PARSE_NOBLANKS); - cleanup_and_set_error_if(reader == nullptr, ERROR_PARSE_XML_PARSING); - - if (use_xml_schema) - { - xmlTextReaderSchemaValidateCtxt(reader, valid_ctxt, 0); - xmlSchemaSetValidStructuredErrors(valid_ctxt, schema_parse_error_handler, &has_schema_errors); - } - - ret = xmlTextReaderRead(reader); - cleanup_and_set_error_if(has_schema_errors, ERROR_PARSE_XML_FAILED_SCHEMA_VALIDATION); - global_render->setAutoUpdate(false); - while (ret == 1) - { - xmlNodePtr node = xmlTextReaderCurrentNode(reader); - int node_type = xmlTextReaderNodeType(reader); - const xmlChar *node_name = xmlTextReaderConstName(reader); - if (node_type == XML_READER_TYPE_ELEMENT) - { - if (xmlStrEqual(node_name, BAD_CAST "root")) - { - global_root = global_render->createElement("root"); - global_render->replaceChildren(global_root); - insertion_parent = nullptr; - current_gr_element = global_root; - } - else - { - current_gr_element = global_render->createElement(reinterpret_cast(node_name)); - } - for (xmlAttrPtr attr = node->properties; attr != nullptr; attr = attr->next) - { - const xmlChar *attr_name = attr->name; - xmlChar *attr_value = xmlNodeListGetString(node->doc, attr->children, 1); - - current_gr_element->setAttribute(reinterpret_cast(attr_name), - reinterpret_cast(attr_value)); - if (reinterpret_cast(attr_name) == "active" && - reinterpret_cast(attr_value) == "1") - global_render->setActiveFigure(current_gr_element); - xmlFree(reinterpret_cast(attr_value)); - } - if (insertion_parent != nullptr) - { - insertion_parent->appendChild(current_gr_element); - } - if (!xmlTextReaderIsEmptyElement(reader)) - { - insertion_parent = current_gr_element; - } - } - else if (node_type == XML_READER_TYPE_END_ELEMENT) - { - insertion_parent = insertion_parent->parentElement(); - } - ret = xmlTextReaderRead(reader); - cleanup_and_set_error_if(has_schema_errors, ERROR_PARSE_XML_FAILED_SCHEMA_VALIDATION); - } - edit_figure = global_render->getActiveFigure(); - global_render->setAutoUpdate(true); - - if (ret != 0) - { - const xmlError *xml_error = xmlGetLastError(); - logger((stderr, "%s: failed to parse in line %d, col %d. Error %d: %s\n", xml_error->file, xml_error->line, - xml_error->int2, xml_error->code, xml_error->message)); - cleanup_and_set_error(ERROR_PARSE_XML_PARSING); - } - -cleanup: - if (reader != nullptr) - { - xmlFreeTextReader(reader); - } - if (valid_ctxt != nullptr) - { - xmlSchemaFreeValidCtxt(valid_ctxt); - } - if (schema != nullptr) - { - xmlSchemaFree(schema); - } - if (schema_parser_ctxt) - { - xmlSchemaFreeParserCtxt(schema_parser_ctxt); - } - xmlCleanupParser(); - - return error == ERROR_NONE; -} -#elif !defined(NO_EXPAT) -static void xml_parse_start_handler(void *data, const XML_Char *tagName, const XML_Char **attr) -{ - auto *insertionParent = (std::shared_ptr *)data; - if (strcmp(tagName, "root") == 0) - { - global_root = global_render->createElement("root"); - global_render->replaceChildren(global_root); - if (attr[0]) - { - global_root->setAttribute(attr[0], attr[1]); - } - (*insertionParent) = global_root; - } - else if (strcmp(tagName, "figure") == 0) - { - edit_figure = global_render->createElement("figure"); - global_root->append(edit_figure); - } - else - { - std::shared_ptr child = global_render->createElement(tagName); - for (int i = 0; attr[i]; i += 2) - { - child->setAttribute(attr[i], attr[i + 1]); - } - - (*insertionParent)->appendChild(child); - *insertionParent = child; - } -} - -static void xml_parse_end_handler(void *data, const char *tagName) -{ - auto currentNode = (std::shared_ptr *)data; - *((std::shared_ptr *)data) = (*currentNode)->parentElement(); -} - -int grm_load_graphics_tree(FILE *file) -{ -#if 0 - std::string xmlstring; - XML_Parser parser = XML_ParserCreate(nullptr); - std::shared_ptr parentNode; - - std::fseek(file, 0, SEEK_END); - xmlstring.resize(std::ftell(file)); - std::rewind(file); - std::fread(&xmlstring[0], 1, xmlstring.size(), file); - - plot_init_static_variables(); - - XML_SetUserData(parser, &parentNode); - XML_SetElementHandler(parser, xml_parse_start_handler, xml_parse_end_handler); - - if (XML_Parse(parser, xmlstring.c_str(), xmlstring.length(), XML_TRUE) == XML_STATUS_ERROR) - { - logger((stderr, "Cannot parse XML-String\n")); - return 0; - } - - XML_ParserFree(parser); - - return 1; -#else - return 0; -#endif -} -#endif -#endif - int grm_merge(const grm_args_t *args) { return grm_merge_extended(args, 0, nullptr); @@ -5433,97 +5805,61 @@ int grm_switch(unsigned int id) /* ========================= c++ ==================================================================================== */ -/* ~~~~~~~~~~~~~~~~~~~~~~~~~ c++ libxml util ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~ c++ xerces util ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -#if 0 -#ifndef NO_LIBXML2 +#ifndef NO_XERCES_C std::shared_ptr grm_load_graphics_tree_schema(void) { - char *gr_dir = get_gr_dir(); - std::string schema_filepath{std::string(gr_dir) + "/" + SCHEMA_REL_FILEPATH}; - free(reinterpret_cast(gr_dir)); - int ret = -1; - xmlSchemaValidCtxtPtr valid_ctxt = nullptr; - xmlTextReaderPtr reader = nullptr; - std::shared_ptr document; - std::shared_ptr insertion_parent, current_element; - err_t error = ERROR_NONE; - FILE *schema_file; - - error = plot_init_static_variables(); - cleanup_if_error; - - schema_file = fopen(schema_filepath.c_str(), "r"); - cleanup_and_set_error_if(schema_file == nullptr, ERROR_PARSE_XML_NO_SCHEMA_FILE); - xmlInitParser(); - reader = xmlReaderForFd(fileno(schema_file), nullptr, nullptr, XML_PARSE_NOBLANKS); - cleanup_and_set_error_if(reader == nullptr, ERROR_PARSE_XML_PARSING); - ret = xmlTextReaderRead(reader); - document = GRM::createDocument(); - while (ret == 1) - { - xmlNodePtr node = xmlTextReaderCurrentNode(reader); - int node_type = xmlTextReaderNodeType(reader); - const xmlChar *node_name = xmlTextReaderConstName(reader); - if (node_type == XML_READER_TYPE_ELEMENT) - { - current_element = document->createElement(reinterpret_cast(node_name)); - for (xmlAttrPtr attr = node->properties; attr != nullptr; attr = attr->next) - { - const xmlChar *attr_name = attr->name; - xmlChar *attr_value = xmlNodeListGetString(node->doc, attr->children, 1); - - current_element->setAttribute(reinterpret_cast(attr_name), - reinterpret_cast(attr_value)); - xmlFree(reinterpret_cast(attr_value)); - } - if (insertion_parent != nullptr) - { - insertion_parent->append(current_element); - } - else - { - document->append(current_element); - } - if (!xmlTextReaderIsEmptyElement(reader)) - { - insertion_parent = current_element; - } - } - else if (node_type == XML_READER_TYPE_END_ELEMENT) - { - insertion_parent = insertion_parent->parentElement(); - } - ret = xmlTextReaderRead(reader); - } + using namespace XERCES_CPP_NAMESPACE; - if (ret != 0) - { - const xmlError *xml_error = xmlGetLastError(); - logger((stderr, "%s: failed to parse in line %d, col %d. Error %d: %s\n", xml_error->file, xml_error->line, - xml_error->int2, xml_error->code, xml_error->message)); - cleanup_and_set_error(ERROR_PARSE_XML_PARSING); - } + std::string schema_filepath{std::string(get_gr_dir()) + "/" + SCHEMA_REL_FILEPATH}; -cleanup: - if (reader != nullptr) - { - xmlFreeTextReader(reader); - } - if (valid_ctxt != nullptr) + try { - xmlSchemaFreeValidCtxt(valid_ctxt); + XMLPlatformUtils::Initialize(); } - xmlCleanupParser(); - if (schema_file != nullptr) + catch (const XMLException &e) { - fclose(schema_file); + std::cerr << "Error during initialization! :\n" << TranscodeToUtf8Str(e.getMessage()) << std::endl; + return 0; } - return (error == ERROR_NONE) ? document : nullptr; + auto document = GRM::createDocument(); + XMLSize_t errorCount = 0; + { + auto parser = + std::unique_ptr(static_cast(XMLReaderFactory::createXMLReader())); + + // Activate validation + parser->setFeature(XMLUni::fgSAX2CoreValidation, false); + parser->setFeature(XMLUni::fgXercesDynamic, false); + parser->setFeature(XMLUni::fgXercesSchema, false); + parser->setFeature(XMLUni::fgXercesSchemaFullChecking, false); + + try + { + SchemaParseHandler handler(*document); + parser->setContentHandler(&handler); + parser->setErrorHandler(static_cast(&handler)); + parser->parse(schema_filepath.c_str()); + errorCount = parser->getErrorCount(); + } + catch (const OutOfMemoryException &) + { + std::cerr << "OutOfMemoryException" << std::endl; + } + catch (const XMLException &e) + { + std::cerr << "\nAn error occurred\n Error: " << TranscodeToUtf8Str(e.getMessage()) << "\n" << std::endl; + } + + } // `parser` must be freed before `XMLPlatformUtils::Terminate()` is called + + XMLPlatformUtils::Terminate(); + + return (errorCount == 0) ? document : nullptr; } #endif -#endif /* ~~~~~~~~~~~~~~~~~~~~~~~~~ c++ util ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -5802,11 +6138,9 @@ int get_focus_and_factor_from_dom(const int x1, const int y1, const int x2, cons bool grm_validate(void) { -#if 0 -#ifndef NO_LIBXML2 +#ifndef NO_XERCES_C err_t validation_error = validate_graphics_tree(); return (validation_error == ERROR_NONE); -#endif #endif return false; } diff --git a/lib/grm/src/grm/plot_int.h b/lib/grm/src/grm/plot_int.h index c9f9c91f1..7bfb3644b 100644 --- a/lib/grm/src/grm/plot_int.h +++ b/lib/grm/src/grm/plot_int.h @@ -174,7 +174,7 @@ int get_free_id_from_figure_elements(); extern "C" { #endif -#ifndef NO_LIBXML2 +#ifndef NO_XERCES_C err_t validate_graphics_tree_xml(void); #endif int validate_graphics_tree_with_error_messages(void); From aa50361cb2c7413e762b829bb037dfa09cfcb8d8 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Tue, 23 Jan 2024 17:05:20 +0100 Subject: [PATCH 20/48] [GRM] Include the invalid `kind` in a thrown exception --- lib/grm/src/grm/dom_render/render.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/grm/src/grm/dom_render/render.cxx b/lib/grm/src/grm/dom_render/render.cxx index ac05015df..ce34e0138 100644 --- a/lib/grm/src/grm/dom_render/render.cxx +++ b/lib/grm/src/grm/dom_render/render.cxx @@ -13253,7 +13253,11 @@ static void plotCoordinateRanges(const std::shared_ptr &element, element->setAttribute("_c_lim_max", NAN); kind = static_cast(element->getAttribute("kind")); if (!string_map_at(fmt_map, static_cast(kind.c_str()), static_cast(&fmt))) - throw NotFoundError("Invalid kind was given.\n"); + { + std::stringstream ss; + ss << "Invalid kind \"" << kind << "\" was given."; + throw NotFoundError(ss.str()); + } if (!str_equals_any(kind, "pie", "polar_histogram")) { current_component_name = data_component_names.begin(); From cddbf236379c17c3c2e545a1cbafce5fd7a47544 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Thu, 8 Feb 2024 11:27:54 +0100 Subject: [PATCH 21/48] [GRM] Add a context export and import This commits adds: - a context iterator - context serialization as JSON (with escaped XML comment chars or as base64 string) within graphics tree XML exports - context deserialization from XML comments (with type auto-detection) --- lib/grm/include/grm/dom_render/context.hxx | 54 +++ .../grm/dom_render/graphics_tree/util.hxx | 20 +- lib/grm/include/grm/dom_render/render.hxx | 2 + lib/grm/src/grm/dom_render/context.cxx | 139 +++++++ .../src/grm/dom_render/graphics_tree/util.cxx | 89 ++-- lib/grm/src/grm/dom_render/render.cxx | 6 + lib/grm/src/grm/json.c | 12 + lib/grm/src/grm/json_int.h | 1 + lib/grm/src/grm/plot.cxx | 389 +++++++++++++++++- lib/grm/src/grm/plot_int.h | 33 ++ lib/grm/src/grm/utilcpp.cxx | 126 +++++- lib/grm/src/grm/utilcpp_int.hxx | 41 +- lib/grm/test/internal_api/grm/CMakeLists.txt | 14 +- .../test/internal_api/grm/escape_minus.cxx | 35 ++ lib/grm/test/internal_api/grm/test.h | 1 + 15 files changed, 899 insertions(+), 63 deletions(-) create mode 100644 lib/grm/test/internal_api/grm/escape_minus.cxx diff --git a/lib/grm/include/grm/dom_render/context.hxx b/lib/grm/include/grm/dom_render/context.hxx index 018292053..df51c1409 100644 --- a/lib/grm/include/grm/dom_render/context.hxx +++ b/lib/grm/include/grm/dom_render/context.hxx @@ -1,11 +1,13 @@ #ifndef CONTEXT_HXX #define CONTEXT_HXX +#include #include #include #include #include #include +#include #include #include @@ -85,6 +87,55 @@ public: Inner operator[](const std::string &str); const Inner operator[](const std::string &str) const; + /*! + * \brief A forward iterator for the Context class. + * + * This iterator can be used to iterate over all key/value pairs in the context in lexicographical order. + */ + class Iterator + { + public: + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = std::variant>>, + std::reference_wrapper>>, + std::reference_wrapper>>>; + using pointer = std::variant> *, + std::pair> *, + std::pair> *>; + using reference = value_type; + + Iterator(Context &context, bool is_end_iterator = false); + + reference operator*(); + + // Prefix increment + Iterator &operator++(); + // Postfix increment + Iterator operator++(int); + + friend bool operator==(const Iterator &a, const Iterator &b); + friend bool operator!=(const Iterator &a, const Iterator &b); + + private: + std::variant>::iterator>, + std::reference_wrapper>::iterator>, + std::reference_wrapper>::iterator>> + next_iterator(); + + Context &context_; + std::map>::iterator table_double_it_; + std::map>::iterator table_int_it_; + std::map>::iterator table_string_it_; + std::variant>::iterator>, + std::reference_wrapper>::iterator>, + std::reference_wrapper>::iterator>> + current_it_; + }; + + Iterator begin(); + Iterator end(); + private: friend class Inner; std::map> tableDouble; @@ -93,6 +144,9 @@ private: std::map referenceNumberOfKeys; }; +bool operator==(const Context::Iterator &a, const Context::Iterator &b); +bool operator!=(const Context::Iterator &a, const Context::Iterator &b); + template static T &get(Context::Inner &&data) { /*! diff --git a/lib/grm/include/grm/dom_render/graphics_tree/util.hxx b/lib/grm/include/grm/dom_render/graphics_tree/util.hxx index a1d2917c0..dbca8f6b5 100644 --- a/lib/grm/include/grm/dom_render/graphics_tree/util.hxx +++ b/lib/grm/include/grm/dom_render/graphics_tree/util.hxx @@ -1,9 +1,11 @@ #ifndef GRM_GRAPHICS_TREE_INTERFACE_UTIL_HXX #define GRM_GRAPHICS_TREE_INTERFACE_UTIL_HXX +#include #include #include #include +#include #include #include @@ -15,11 +17,14 @@ class Node; struct EXPORT SerializerOptions { - std::string indent; - bool show_hidden; + std::string indent = ""; + bool show_hidden = false; }; -EXPORT std::string toXML(const std::shared_ptr &node, const SerializerOptions &options = {"", false}); - +EXPORT std::string +toXML(const std::shared_ptr &node, const SerializerOptions &options = {"", false}, + std::optional &new_attribute_name)>> + attribute_filter = std::nullopt); EXPORT std::string tolower(std::string string); EXPORT std::string toupper(std::string string); EXPORT std::vector split(const std::string &string, const std::string &token); @@ -40,6 +45,13 @@ protected: std::map, bool> &match_map) const = 0; }; EXPORT std::shared_ptr parseSelectors(const std::string &selectors); + +// `overloaded` utility taken from +template struct overloaded : Ts... +{ + using Ts::operator()...; +}; +template overloaded(Ts...) -> overloaded; } // namespace GRM #endif diff --git a/lib/grm/include/grm/dom_render/render.hxx b/lib/grm/include/grm/dom_render/render.hxx index c2091f45c..6ff4cef09 100644 --- a/lib/grm/include/grm/dom_render/render.hxx +++ b/lib/grm/include/grm/dom_render/render.hxx @@ -521,6 +521,8 @@ public: int getAxisId(); + std::shared_ptr getRenderContext(); + void render(); // render doc and render context void render(const std::shared_ptr &extContext); // render doc and external context void render(const std::shared_ptr &document); // external doc and render context diff --git a/lib/grm/src/grm/dom_render/context.cxx b/lib/grm/src/grm/dom_render/context.cxx index 9fc793197..7d9a0ea1b 100644 --- a/lib/grm/src/grm/dom_render/context.cxx +++ b/lib/grm/src/grm/dom_render/context.cxx @@ -1,4 +1,6 @@ +#include #include +#include #include #include @@ -373,3 +375,140 @@ const GRM::Context::Inner GRM::Context::operator[](const std::string &str) const */ return Inner(*this, str); } + +/*! + * \brief Construct a new GRM::Context iterator. + * + * \param[in] context The context to iterate over. + * \param[in] is_end_iterator Set this to true if an end iterator shall be created. + */ +GRM::Context::Iterator::Iterator(Context &context, bool is_end_iterator) + : context_(context), table_double_it_(context.tableDouble.begin()), table_int_it_(context.tableInt.begin()), + table_string_it_(context.tableString.begin()), current_it_(table_double_it_) +{ + if (is_end_iterator) + { + table_double_it_ = context.tableDouble.end(); + table_int_it_ = context.tableInt.end(); + table_string_it_ = context.tableString.end(); + } + else + { + current_it_ = next_iterator(); + } +}; + +/*! + * \brief Overload of the dereference operator. + * + * Please note, that this iterator has no arrow operator defined. The arrow operator must return a pointer which is not + * possible with a variadic value type which is hold by this iterator (the pointer would be returned in a variant and + * this couldn't be used for direct dereferencing; expressions like `it->first` wouldn't work). + * + * \return A reference to the value the iterator is currently pointing to + */ +GRM::Context::Iterator::reference GRM::Context::Iterator::operator*() +{ + return std::visit([](auto &&it_ref_wrapper) -> reference { return *it_ref_wrapper.get(); }, current_it_); +} + +/*! + * \brief Overload of the prefix increment operator. + */ +GRM::Context::Iterator &GRM::Context::Iterator::operator++() +{ + std::visit([](auto &&it) { ++it.get(); }, current_it_); + current_it_ = next_iterator(); + return *this; +} + +/*! + * \brief Overload of the postfix increment operator. + */ +GRM::Context::Iterator GRM::Context::Iterator::operator++(int) +{ + Iterator tmp{*this}; + ++(*this); + return tmp; +} + +/*! + * \brief Overload of the equality operator. + * + * \param[in] a First GRM::Context iterator to compare. + * \param[in] b Second GRM::Context iterator to compare. + * \return The equality of the two iterators. + */ +bool GRM::operator==(const GRM::Context::Iterator &a, const GRM::Context::Iterator &b) +{ + return a.table_double_it_ == b.table_double_it_ && a.table_int_it_ == b.table_int_it_ && + a.table_string_it_ == b.table_string_it_; +} + +/*! + * \brief Overload of the inequality operator. + * + * \param[in] a First GRM::Context iterator to compare. + * \param[in] b Second GRM::Context iterator to compare. + * \return The inequality of the two iterators. + */ +bool GRM::operator!=(const GRM::Context::Iterator &a, const GRM::Context::Iterator &b) +{ + return !(a == b); +} + +/*! \brief Find the next iterator of the underlying data structures to process. + * + * A GRM::Context iterator internally stores iterators to all data structures within the GRM::Context object. This + * function is used to find the next iterator to process. Since all iterators process sorted pairs of string keys and + * data values, the next iterator will always be the one with the smallest string key. By repeatedly calling this + * function, all items of all iterators will be processed in lexicographic order. + * + * \return The next internal iterator to process + */ +std::variant>::iterator>, + std::reference_wrapper>::iterator>, + std::reference_wrapper>::iterator>> +GRM::Context::Iterator::next_iterator() +{ + auto is_it_lt = [](const auto &it_a, const auto &it_b, const auto &it_a_end, const auto &it_b_end) { + if (it_a != it_a_end && it_b != it_b_end) + { + return it_a->first < it_b->first; + } + else + { + return it_a != it_a_end && it_b == it_b_end; + } + }; + + if (is_it_lt(table_double_it_, table_int_it_, context_.tableDouble.end(), context_.tableInt.end()) && + is_it_lt(table_double_it_, table_string_it_, context_.tableDouble.end(), context_.tableString.end())) + { + return table_double_it_; + } + else if (is_it_lt(table_int_it_, table_string_it_, context_.tableInt.end(), context_.tableString.end())) + { + return table_int_it_; + } + else + { + return table_string_it_; + } +} + +/*! + * \brief Create a iterator pointing to the first element of this context. + */ +GRM::Context::Iterator GRM::Context::begin() +{ + return Iterator(*this); +} + +/*! + * \brief Create a iterator pointing past the last element of this context. + */ +GRM::Context::Iterator GRM::Context::end() +{ + return Iterator(*this, true); +} diff --git a/lib/grm/src/grm/dom_render/graphics_tree/util.cxx b/lib/grm/src/grm/dom_render/graphics_tree/util.cxx index b3c043055..2c6d49622 100644 --- a/lib/grm/src/grm/dom_render/graphics_tree/util.cxx +++ b/lib/grm/src/grm/dom_render/graphics_tree/util.cxx @@ -9,7 +9,10 @@ #include "grm/utilcpp_int.hxx" static void nodeToXML(std::stringstream &os, const std::shared_ptr &node, - const GRM::SerializerOptions &options, const std::string &indent); + const GRM::SerializerOptions &options, const std::string &indent, + std::optional &new_attribute_name)>> + attribute_filter); static void documentToXML(std::stringstream &os, const std::shared_ptr &document, const GRM::SerializerOptions &options, const std::string &indent) @@ -17,12 +20,16 @@ static void documentToXML(std::stringstream &os, const std::shared_ptr\n"; for (const auto &child_node : document->childNodes()) { - nodeToXML(os, child_node, options, indent); + nodeToXML(os, child_node, options, indent, std::nullopt); } } -static void elementToXML(std::stringstream &os, const std::shared_ptr &element, - const GRM::SerializerOptions &options, const std::string &indent) +static void +elementToXML(std::stringstream &os, const std::shared_ptr &element, + const GRM::SerializerOptions &options, const std::string &indent, + std::optional &new_attribute_name)>> + attribute_filter) { os << indent << "<" << element->localName(); auto attribute_names_set = element->getAttributeNames(); @@ -34,33 +41,41 @@ static void elementToXML(std::stringstream &os, const std::shared_ptrgetAttribute("name") << "\""; } - for (const auto &attribute_name : attribute_names) + for (const auto &original_attribute_name : attribute_names) { - if (attribute_name != "name" && (options.show_hidden || !starts_with(attribute_name, "_"))) + auto attribute_name = std::ref(original_attribute_name); + std::optional new_attribute_name; + if (original_attribute_name == "name" || + (attribute_filter && !(*attribute_filter)(original_attribute_name, *element, new_attribute_name))) + continue; + if (new_attribute_name) { - auto value = (std::string)element->getAttribute(attribute_name); - if (value == "nan") - { - os << " " << attribute_name << "=\"" - << "NaN" - << "\""; - } - else if (value == "inf") - { - os << " " << attribute_name << "=\"" - << "INF" - << "\""; - } - else if (value == "-inf") - { - os << " " << attribute_name << "=\"" - << "-INF" - << "\""; - } - else - { - os << " " << attribute_name << "=\"" << value << "\""; - } + attribute_name = *new_attribute_name; + } + if (!options.show_hidden && starts_with(attribute_name.get(), "_")) continue; + + auto value = (std::string)element->getAttribute(original_attribute_name); + if (value == "nan") + { + os << " " << attribute_name.get() << "=\"" + << "NaN" + << "\""; + } + else if (value == "inf") + { + os << " " << attribute_name.get() << "=\"" + << "INF" + << "\""; + } + else if (value == "-inf") + { + os << " " << attribute_name.get() << "=\"" + << "-INF" + << "\""; + } + else + { + os << " " << attribute_name.get() << "=\"" << value << "\""; } } if (element->hasChildNodes()) @@ -68,7 +83,7 @@ static void elementToXML(std::stringstream &os, const std::shared_ptr\n"; for (const auto &child_node : element->childNodes()) { - nodeToXML(os, child_node, options, indent + options.indent); + nodeToXML(os, child_node, options, indent + options.indent, attribute_filter); } os << indent << "localName() << ">\n"; } @@ -85,7 +100,10 @@ static void commentToXML(std::stringstream &os, const std::shared_ptr &node, - const GRM::SerializerOptions &options, const std::string &indent) + const GRM::SerializerOptions &options, const std::string &indent, + std::optional &new_attribute_name)>> + attribute_filter) { switch (node->nodeType()) { @@ -98,7 +116,7 @@ static void nodeToXML(std::stringstream &os, const std::shared_ptr(node); - elementToXML(os, element, options, indent); + elementToXML(os, element, options, indent, attribute_filter); break; } case GRM::Node::Type::COMMENT_NODE: @@ -110,14 +128,17 @@ static void nodeToXML(std::stringstream &os, const std::shared_ptr &node, const GRM::SerializerOptions &options) +std::string GRM::toXML(const std::shared_ptr &node, const GRM::SerializerOptions &options, + std::optional &new_attribute_name)>> + attribute_filter) { if (!node) { throw TypeError("node is null"); } std::stringstream os; - nodeToXML(os, node, options, ""); + nodeToXML(os, node, options, "", attribute_filter); return os.str(); } diff --git a/lib/grm/src/grm/dom_render/render.cxx b/lib/grm/src/grm/dom_render/render.cxx index ce34e0138..701e755b5 100644 --- a/lib/grm/src/grm/dom_render/render.cxx +++ b/lib/grm/src/grm/dom_render/render.cxx @@ -15116,6 +15116,12 @@ static void applyRootDefaults(const std::shared_ptr &root) } } + +std::shared_ptr GRM::Render::getRenderContext() +{ + return this->context; +} + void GRM::Render::render(const std::shared_ptr &document, const std::shared_ptr &ext_context) { diff --git a/lib/grm/src/grm/json.c b/lib/grm/src/grm/json.c index 67651417d..b00ff6458 100644 --- a/lib/grm/src/grm/json.c +++ b/lib/grm/src/grm/json.c @@ -1608,6 +1608,18 @@ err_t tojson_init_variables(int *add_data, int *add_data_without_separator, char return ERROR_NONE; } +err_t tojson_write(memwriter_t *memwriter, const char *data_desc, ...) +{ + va_list vl; + err_t error; + + va_start(vl, data_desc); + error = tojson_write_vl(memwriter, data_desc, &vl); + va_end(vl); + + return error; +} + err_t tojson_write_vl(memwriter_t *memwriter, const char *data_desc, va_list *vl) { int add_data, add_data_without_separator; diff --git a/lib/grm/src/grm/json_int.h b/lib/grm/src/grm/json_int.h index a2a3216a3..702d45241 100644 --- a/lib/grm/src/grm/json_int.h +++ b/lib/grm/src/grm/json_int.h @@ -171,6 +171,7 @@ err_t tojson_serialize(memwriter_t *memwriter, char *data_desc, const void *data tojson_serialization_result_t *serial_result, tojson_shared_state_t *shared_state); void tojson_init_static_variables(void); err_t tojson_init_variables(int *add_data, int *add_data_without_separator, char **_data_desc, const char *data_desc); +err_t tojson_write(memwriter_t *memwriter, const char *data_desc, ...); err_t tojson_write_vl(memwriter_t *memwriter, const char *data_desc, va_list *vl); err_t tojson_write_buf(memwriter_t *memwriter, const char *data_desc, const void *buffer, int apply_padding); err_t tojson_write_arg(memwriter_t *memwriter, const arg_t *arg); diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 4561987a6..650a3f161 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -14,6 +14,7 @@ #include #include #include +#include #ifdef __unix__ #include @@ -34,8 +35,10 @@ extern "C" { #include "gks.h" #include "gr.h" #include "gr3.h" +#include "json_int.h" #include "logging_int.h" } +#include "utilcpp_int.hxx" #ifndef NO_XERCES_C #include @@ -149,6 +152,17 @@ DECLARE_MAP_METHODS(args_set) /* ========================= static variables ======================================================================= */ +/* ------------------------- dump ----------------------------------------------------------------------------------- */ + +/*! + * \brief List of attribute names in the graphics tree which will be exported as-is on XML dumps. Backup attributes + * (`_*_org) are ignored for these attribute names. + */ +const static std::unordered_set restore_backup_format_excludes = { + "space_3d_phi", + "space_3d_theta", +}; + /* ------------------------- plot ----------------------------------------------------------------------------------- */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~ general ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -4561,6 +4575,260 @@ err_t classes_polar_histogram(grm_args_t *subplot_args) return error; } +} /* end of extern "C" */ + +/* ------------------------- dump ----------------------------------------------------------------------------------- */ + +/*! + * \brief Dump the current GRM context object into a file object. + * + * \param[in] f The file object, the serialized context will be written to. + * \param[in] dump_encoding The encoding of the serialized context. The context is exported as JSON, but an additional + * encoding step can be necessary to embed the JSON document into a file (like an XML + * document). Possible values are: + * - `DUMP_JSON_PLAIN`: Do not apply any encoding. + * - `DUMP_JSON_ESCAPE_DOUBLE_MINUS`: Escape double minus signs (`--`) in the JSON document by + * replacing them with `-\-`. This can be used to embed the + * output into an XML comment. + * - `DUMP_JSON_BASE64`: Base64 encode the JSON document. + * \param[in] context_keys_to_discard The context keys which will be excluded in the dump. + */ +void dump_context(FILE *f, dump_encoding_t dump_encoding, + const std::unordered_set *context_keys_to_discard) +{ + char *base64_str = dump_context_str(dump_encoding, context_keys_to_discard); + fprintf(f, "%s", base64_str); + free(base64_str); +} + +/*! + * \brief Dump the current GRM context object into a string. + * + * \param[in] dump_encoding The encoding of the serialized context. See `dump_context` for details. + * \param[in] context_keys_to_discard The context keys which will be excluded in the dump. + * \return A C-string containing the serialized context. The caller is responsible for freeing the returned string. + */ +char *dump_context_str(dump_encoding_t dump_encoding, const std::unordered_set *context_keys_to_discard) +{ + auto memwriter = memwriter_new(); + if (memwriter == nullptr) + { + debug_print_malloc_error(); + return nullptr; + } + auto context = global_render->getContext(); + + tojson_write(memwriter, "o("); + for (auto item : *context) + { + std::visit( + GRM::overloaded{[&memwriter, &context_keys_to_discard]( + std::reference_wrapper>> pair_ref) { + if (context_keys_to_discard->find(pair_ref.get().first) != context_keys_to_discard->end()) + return; + std::stringstream format_stream; + format_stream << pair_ref.get().first << ":nD"; + tojson_write(memwriter, format_stream.str().c_str(), pair_ref.get().second.size(), + pair_ref.get().second.data()); + }, + [&memwriter, &context_keys_to_discard]( + std::reference_wrapper>> pair_ref) { + if (context_keys_to_discard->find(pair_ref.get().first) != context_keys_to_discard->end()) + return; + std::stringstream format_stream; + format_stream << pair_ref.get().first << ":nI"; + tojson_write(memwriter, format_stream.str().c_str(), pair_ref.get().second.size(), + pair_ref.get().second.data()); + }, + [&memwriter, &context_keys_to_discard]( + std::reference_wrapper>> pair_ref) { + if (context_keys_to_discard->find(pair_ref.get().first) != context_keys_to_discard->end()) + return; + std::stringstream format_stream; + format_stream << pair_ref.get().first << ":nS"; + std::vector c_strings; + c_strings.reserve(pair_ref.get().second.size()); + for (const auto &str : pair_ref.get().second) + { + c_strings.push_back(str.c_str()); + } + tojson_write(memwriter, format_stream.str().c_str(), pair_ref.get().second.size(), + c_strings.data()); + }}, + item); + } + tojson_write(memwriter, ")"); + char *encoded_string = nullptr; + switch (dump_encoding) + { + case DUMP_JSON_ESCAPE_DOUBLE_MINUS: + encoded_string = strdup(escape_double_minus(memwriter_buf(memwriter)).c_str()); + break; + case DUMP_JSON_BASE64: + err_t error; + encoded_string = base64_encode(nullptr, memwriter_buf(memwriter), memwriter_size(memwriter), &error); + if (error != ERROR_NONE) + { + logger((stderr, "Got error \"%d\" (\"%s\")!\n", error, error_names[error])); + } + break; + default: + // Plain JSON + encoded_string = strdup(memwriter_buf(memwriter)); + } + if (encoded_string == nullptr) + { + debug_print_malloc_error(); + } + + memwriter_delete(memwriter); + return encoded_string; +} + +/*! + * \brief Dump the current GRM context object into an XML comment. + * + * This function generates a special XML comment and embeds the context object as a JSON document. In order to avoid a + * broken XML comment, double minus signs (`--`) in the JSON document are escaped by replacing them with `-\-`. + * + * \param[in] f The file object, the comment will be written into. + * \param[in] context_keys_to_discard The context keys which will be excluded in the dump. + */ +void dump_context_as_xml_comment(FILE *f, const std::unordered_set *context_keys_to_discard) +{ + fprintf(f, "\n"); +} + +/*! + * \brief Dump the current GRM context object into an XML comment. See `dump_context_as_xml_comment` for details. + * + * \param[in] context_keys_to_discard The context keys which will be excluded in the dump. + * \return A C-string containing the XML comment. The caller is responsible for freeing the returned string. + */ +char *dump_context_as_xml_comment_str(const std::unordered_set *context_keys_to_discard) +{ + char *escaped_json_str = nullptr; + size_t escaped_json_strlen; + char *xml_comment = nullptr; + + escaped_json_str = dump_context_str(DUMP_JSON_ESCAPE_DOUBLE_MINUS, context_keys_to_discard); + cleanup_if(escaped_json_str == nullptr); + escaped_json_strlen = strlen(escaped_json_str); + /* 27 = strlen("") + 1 (`\0`) */ + xml_comment = static_cast(malloc(escaped_json_strlen + 27)); + cleanup_if(xml_comment == nullptr); + strcpy(xml_comment, ""); + xml_comment[escaped_json_strlen + 26] = '\0'; + +cleanup: + free(escaped_json_str); + + return xml_comment; +} + + +/* ------------------------- load ----------------------------------------------------------------------------------- */ + +namespace internal +{ + +/*! + * \brief Helper template function to avoid code duplication in `load_context_str`. + * + * All arguments of this function are simply passed through from `load_context_str`. + * + * \tparam T The type of the value which is read from the context argument container. + * \tparam U The type of the value which is stored in the GRM context. + */ +template +static void put_value_into_context(arg_t *context_arg, grm_args_value_iterator_t *context_arg_value_it, + GRM::Context &context) +{ + if (context_arg_value_it->is_array) + { + T *value_array = *static_cast(context_arg_value_it->value_ptr); + context[context_arg->key] = std::vector(value_array, value_array + context_arg_value_it->array_length); + } + else + { + T value = *static_cast(context_arg_value_it->value_ptr); + context[context_arg->key] = std::vector{value}; + } +}; +} // namespace internal + +/*! + * \brief Load a GRM context object from a JSON string + * + * \param[in] context The context object to load into + * \param[in] context_str The serialized context string to deserialize + * \param[in] dump_encoding The encoding of the context string. Set to `DUMP_AUTO_DETECT` to detect the encoding. + * WARNING: Auto-detection may not be reliable. Currently, only Base64 and double minus escape + * formats can be detected. Plain JSON is always detected as double minus escape format. + * \throw std::runtime_error + */ +void load_context_str(GRM::Context &context, const std::string &context_str, dump_encoding_t dump_encoding) +{ + const char *serialized_context; + std::string serialized_context_tmp_; + if (dump_encoding == DUMP_AUTO_DETECT) + { + dump_encoding = context_str[0] == '{' ? DUMP_JSON_ESCAPE_DOUBLE_MINUS : DUMP_JSON_BASE64; + } + switch (dump_encoding) + { + case DUMP_JSON_ESCAPE_DOUBLE_MINUS: + serialized_context_tmp_ = unescape_double_minus(context_str); + serialized_context = serialized_context_tmp_.c_str(); + break; + case DUMP_JSON_BASE64: + err_t error; + serialized_context = base64_decode(nullptr, context_str.c_str(), nullptr, &error); + if (error != ERROR_NONE) + { + std::stringstream error_description; + error_description << "error \"" << error << "\" (\"" << error_names[error] << "\")"; + logger((stderr, "Got %s!\n", error_description.str().c_str())); + // TODO: Throw a custom exception type when `plot.cxx` has a better C++ interface + throw std::runtime_error("Failed to decode base64 context string (" + error_description.str() + ")"); + } + break; + default: + // Plain JSON + serialized_context = context_str.c_str(); + } + auto context_args = grm_args_new(); + if (context_args == nullptr) + { + throw std::runtime_error("Failed to create context args object"); + } + fromjson_read(context_args, serialized_context); + auto context_args_it = grm_args_iter(context_args); + arg_t *context_arg; + while ((context_arg = context_args_it->next(context_args_it))) + { + auto context_arg_value_it = grm_arg_value_iter(context_arg); + while (context_arg_value_it->next(context_arg_value_it) != NULL) + { + switch (context_arg_value_it->format) + { + case 'i': + internal::put_value_into_context(context_arg, context_arg_value_it, context); + break; + case 'd': + internal::put_value_into_context(context_arg, context_arg_value_it, context); + break; + case 's': + internal::put_value_into_context(context_arg, context_arg_value_it, context); + break; + } + } + } +} /* ------------------------- xml ------------------------------------------------------------------------------------ */ @@ -4813,7 +5081,7 @@ class SaxErrorHandler : public ErrorHandler class GraphicsTreeParseHandler : public DefaultHandler, public SaxErrorHandler, public PSVIHandler { public: - GraphicsTreeParseHandler() {} + GraphicsTreeParseHandler(GRM::Context &context) : context_(context) {} ~GraphicsTreeParseHandler() {} void startDocument() override {} @@ -4864,6 +5132,24 @@ class GraphicsTreeParseHandler : public DefaultHandler, public SaxErrorHandler, insertion_parent_ = insertion_parent_->parentElement(); } + /*! + * \brief Process comments in the XML document. + * + * This function parses special GRM comments to load a serialized context. + */ + void comment(const XMLCh *const chars, const XMLSize_t length) override + { + std::string comment{encode(chars)}; + std::string_view comment_view{comment}; + comment_view = trim(comment_view); + if (starts_with(comment_view, "__grm_context__:")) + { + comment_view.remove_prefix(16); + comment_view = ltrim(comment_view); + load_context_str(context_, std::string(comment_view), DUMP_AUTO_DETECT); + } + } + /*! * \brief Process the types of all attributes of a new XML tag. * @@ -4950,6 +5236,7 @@ class GraphicsTreeParseHandler : public DefaultHandler, public SaxErrorHandler, std::string encode(std::optional chars = std::nullopt) { return xml_buffer_.encode(chars); } XMLStringBuffer xml_buffer_{"UTF-8"}; + GRM::Context &context_; std::shared_ptr insertion_parent_, current_element_; std::vector> current_attributes_; }; @@ -5030,6 +5317,7 @@ class SchemaParseHandler : public DefaultHandler, public SaxErrorHandler }; } // namespace XERCES_CPP_NAMESPACE +extern "C" { /*! * \brief Load a graphics tree from an XML file. @@ -5082,6 +5370,7 @@ int grm_load_graphics_tree(FILE *file) GraphicsTreeParseHandler handler(*global_render->getContext()); parser->setPSVIHandler(&handler); parser->setContentHandler(&handler); + parser->setLexicalHandler(&handler); parser->setErrorHandler(static_cast(&handler)); parser->parse(FileInputSource(file)); errorCount = parser->getErrorCount(); @@ -5322,10 +5611,84 @@ int grm_clear(void) return 1; } +namespace internal +{ +/*! + * \brief Restore backup attributes on the graphics tree when dumped with the `toXML` function. + * + * Use objects of this class as functor to filter attributes on the graphics tree which have a backup attribute. Rename + * backup attributes to match the names of the deleted attributes. This filter is needed to discard tranformed context + * data and to export the original data when the graphics tree is saved to an XML file. + */ +class RestoreBackupAttributeFilter +{ +public: + /*! + * \brief The overloaded ()-operator to provide functor functionality. + * + * \param[in] attribute_name The name of attribute to filter. + * \param[in] element The element the currently processed attribute belongs to. + * \param[out] new_attribute_name Can be used to set a modified name for the current attribute. Leave unset or set to + * `std::nullopt` to keep the current attribute name. + * \return `true` if the attribute should be kept, `false` otherwise. + */ + bool operator()(const std::string &attribute_name, const GRM::Element &element, + std::optional &new_attribute_name) + { + if (attribute_name.empty()) return false; + + if (attribute_name[0] == '_') + { + std::optional original_attribute_name = is_backup_attribute_for(attribute_name); + if (original_attribute_name && + restore_backup_format_excludes.find(*original_attribute_name) == restore_backup_format_excludes.end()) + { + new_attribute_name = *original_attribute_name; + } + return true; + } + + if (restore_backup_format_excludes.find(attribute_name) == restore_backup_format_excludes.end()) + { + std::stringstream potential_backup_attribute_name_stream; + potential_backup_attribute_name_stream << "_" << attribute_name << "_org"; + auto potential_backup_attribute_name = potential_backup_attribute_name_stream.str(); + if (element.hasAttribute(potential_backup_attribute_name)) + { + if (element.getAttribute(attribute_name) != element.getAttribute(potential_backup_attribute_name) && + str_equals_any(attribute_name, "x", "y", "z")) + { + context_keys_to_discard_.insert(static_cast(element.getAttribute(attribute_name))); + } + return false; + } + } + return true; + } + + /*! + * \brief Get the set of context keys which should be discarded when saving the graphics tree to an XML file. + */ + const std::unordered_set &context_keys_to_discard() const { return context_keys_to_discard_; } + +private: + std::unordered_set context_keys_to_discard_; +}; +} // namespace internal + void grm_dump_graphics_tree(FILE *f) { + internal::RestoreBackupAttributeFilter restore_backup_attribute_filter; const unsigned int indent = 2; - fprintf(f, "%s\n", toXML(global_root, GRM::SerializerOptions{std::string(indent, ' ')}).c_str()); + // Use a lambda around `restore_backup_attribute_filter` to make sure it is used by reference. + fprintf(f, "%s", + toXML(global_root, GRM::SerializerOptions{std::string(indent, ' ')}, + [&restore_backup_attribute_filter](const std::string &attribute_name, const GRM::Element &element, + std::optional &new_attribute_name) -> bool { + return restore_backup_attribute_filter(attribute_name, element, new_attribute_name); + }) + .c_str()); + dump_context_as_xml_comment(f, &restore_backup_attribute_filter.context_keys_to_discard()); } unsigned int grm_max_plotid(void) @@ -5342,10 +5705,21 @@ unsigned int grm_max_plotid(void) char *grm_dump_graphics_tree_str(void) { - std::string graphics_tree_str = toXML(global_root); - char *graphics_tree_cstr = new char[graphics_tree_str.length() + 1]; - strcpy(graphics_tree_cstr, graphics_tree_str.c_str()); - return graphics_tree_cstr; + internal::RestoreBackupAttributeFilter restore_backup_attribute_filter; + // Use a lambda around `restore_backup_attribute_filter` to make sure it is used by reference. + std::string graphics_tree_str = + toXML(global_root, GRM::SerializerOptions{}, + [&restore_backup_attribute_filter](const std::string &attribute_name, const GRM::Element &element, + std::optional &new_attribute_name) -> bool { + return restore_backup_attribute_filter(attribute_name, element, new_attribute_name); + }); + char *context_cstr = dump_context_as_xml_comment_str(&restore_backup_attribute_filter.context_keys_to_discard()); + char *graphics_tree_with_context_cstr = + static_cast(malloc(graphics_tree_str.length() + strlen(context_cstr) + 1)); + strcpy(graphics_tree_with_context_cstr, graphics_tree_str.c_str()); + strcpy(graphics_tree_with_context_cstr + graphics_tree_str.length(), context_cstr); + free(context_cstr); + return graphics_tree_with_context_cstr; } int grm_merge(const grm_args_t *args) @@ -5800,8 +6174,7 @@ int grm_switch(unsigned int id) return 1; } - -} /* end of extern "C" block */ +} /* ========================= c++ ==================================================================================== */ diff --git a/lib/grm/src/grm/plot_int.h b/lib/grm/src/grm/plot_int.h index 7bfb3644b..469f31b31 100644 --- a/lib/grm/src/grm/plot_int.h +++ b/lib/grm/src/grm/plot_int.h @@ -15,6 +15,9 @@ extern "C" { #ifdef __cplusplus } +#include + +#include #include #include @@ -67,6 +70,17 @@ extern const char *plot_clear_exclude_keys[]; /* ========================= datatypes ============================================================================== */ +/* ------------------------- dump ----------------------------------------------------------------------------------- */ + +typedef enum +{ + DUMP_AUTO_DETECT = 0, + DUMP_JSON_PLAIN = 1, + DUMP_JSON_ESCAPE_DOUBLE_MINUS = 2, + DUMP_JSON_BASE64 = 3, +} dump_encoding_t; + + /* ------------------------- plot ----------------------------------------------------------------------------------- */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~ kind to func ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -165,9 +179,28 @@ int get_free_id_from_figure_elements(); #ifdef __cplusplus } +#endif + + +/* ------------------------- dump ----------------------------------------------------------------------------------- */ +#ifdef __cplusplus +void dump_context(FILE *f, dump_encoding_t dump_encoding, + const std::unordered_set *context_keys_to_discard = nullptr); +char *dump_context_str(dump_encoding_t dump_encoding, + const std::unordered_set *context_keys_to_discard = nullptr); + +void dump_context_as_xml_comment(FILE *f, const std::unordered_set *context_keys_to_discard = nullptr); +char *dump_context_as_xml_comment_str(const std::unordered_set *context_keys_to_discard = nullptr); #endif +/* ------------------------- load ----------------------------------------------------------------------------------- */ + +#ifdef __cplusplus +void load_context_str(GRM::Context &context, const std::string &context_str, dump_encoding_t dump_encoding); +#endif + + /* ------------------------- xml ------------------------------------------------------------------------------------ */ #ifdef __cplusplus diff --git a/lib/grm/src/grm/utilcpp.cxx b/lib/grm/src/grm/utilcpp.cxx index 410d324b2..2b237d261 100644 --- a/lib/grm/src/grm/utilcpp.cxx +++ b/lib/grm/src/grm/utilcpp.cxx @@ -17,38 +17,146 @@ #include #endif -std::string ltrim(const std::string &s) +std::string_view ltrim(std::string_view s) { size_t start = s.find_first_not_of(WHITESPACE); return (start == std::string::npos) ? "" : s.substr(start); } -std::string rtrim(const std::string &s) +std::string_view rtrim(std::string_view s) { size_t end = s.find_last_not_of(WHITESPACE); return (end == std::string::npos) ? "" : s.substr(0, end + 1); } -std::string trim(const std::string &s) +std::string_view trim(std::string_view s) { return rtrim(ltrim(s)); } -bool file_exists(const std::string &name) -{ - return (access(name.c_str(), F_OK) != -1); -} - bool starts_with(std::string_view str, std::string_view prefix) { return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix); } -bool ends_with(const std::string &str, const std::string &suffix) +bool ends_with(std::string_view str, std::string_view suffix) { return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); } +/*! + * \brief Check if a substring of a given input string consists of a certain character, ending with an another end + * character. + * + * \param[in] input The string to check. + * \param[in] c The character to check for. + * \param[in] ends_with The end character to check for. + * \param[in] pos The starting position of the substring. All characters starting from `&input[pos]` will be checked. + * \return The index of the ending character. If the substring does not consist of `c` and `ends_with`, + * `std::string_view::npos` is returned instead. + */ +size_t string_consists_of(std::string_view input, char c, char ends_with, size_t pos) +{ + auto it_not_c = + std::find_if_not(std::begin(input) + pos, std::end(input), [&](char current_char) { return current_char == c; }); + if (it_not_c == std::end(input) || *it_not_c != ends_with) + { + return std::string_view::npos; + } + return it_not_c - std::begin(input); +} + +namespace internal +{ +/*! + * \brief Escape or unescape two consecutive characters (like `--`) in a string. + * + * This function can be used to escape or unescape two consecutive characters in a string by putting a given escape + * character in between them. To simplfy the explanation, assume the character which should be escaped is `-` and the + * escape character is `\`. Occurences of `--` in the string will be replaced with `-\-` and occurrences of `-\-` will + * be replaced with `-\\-` and so forth. Strings with even more consecutive characters (`---` or more) will also be + * handled correctly (`---` will be replaced with `-\-\-`, `-\--` will be replaced by `-\\-\-` and so forth). + * + * \param[in] input The string to escape or unescape. + * \param[in] escape_char The character which will be used as an escape character (`\` in the above example) + * \param[in] to_escape_char The character which will be escaped if two consecutive characters are found (`-` in the + * above example). + * \param[in] unescape Can be set to `true` to unescape instead. + */ +std::string escape_or_unescape(std::string_view input, char escape_char, char to_escape_char, bool unescape) +{ + std::vector output_parts; + + auto start_pos = input.find(to_escape_char); + while (start_pos != std::string_view::npos) + { + auto end_pos = string_consists_of(input, escape_char, to_escape_char, start_pos + 1); + if (end_pos != std::string_view::npos) + { + /* Explanation of `(unescape && end_pos - start_pos > 1 ? 1 : 0)`: + * In unescape mode, one escape character needs to be removed, so substract one from the found end position. + * However, if the routine found an already unescaped occurence (`--` instead of `-\-`), skip the + * substraction to not lose a non-escape character. */ + output_parts.push_back(input.substr(0, end_pos - (unescape && end_pos - start_pos > 1 ? 1 : 0))); + // Leave the second minus of `--` in the input, so strings like `---` can be handled in the next correctly. + input = input.substr(end_pos); + start_pos = 0; + } + else + { + start_pos = input.find(to_escape_char, start_pos + 1); + } + } + output_parts.push_back(input); + + return string_join(std::begin(output_parts), std::end(output_parts), unescape ? "" : std::string{escape_char}); +} +} // namespace internal + +/*! + * \brief Escape double minus signs (`--`) in a string, by replacing them with `-\-`. See `escape_or_unescape` for more + * details. + * + * \param[in] input String to escape. + * \return The escaped string. + */ +std::string escape_double_minus(std::string_view input) +{ + return internal::escape_or_unescape(input, '\\', '-', false); +} + +/*! + * \brief Unescape escaped double minus signs (`-\-`) in a string, by replacing them with `--`. See `escape_or_unescape` + * for more details. + * + * \param[in] input String to unescape. + * \return The unescaped string. + */ +std::string unescape_double_minus(std::string_view input) +{ + return internal::escape_or_unescape(input, '\\', '-', true); +} + +/*! + * \brief Check if an attribute is a backup copy of another attribute of a graphics tree element. + * + * This function makes a decision based only on the attribute name itself, there is no graphcs tree check involved! + * + * \param[in] name The attribute name to check. + * \return The attribute name of the original attribute if the input is a backup attribute, `std::nullopt` otherwise. + */ +std::optional is_backup_attribute_for(std::string_view name) +{ + if (name.empty() || !(name[0] == '_' && ends_with(name, "_org") && name.size() > 5)) return std::nullopt; + + return name.substr(1, name.size() - 5); +} + +bool file_exists(const std::string &name) +{ + return (access(name.c_str(), F_OK) != -1); +} + void linspace(double start, double end, int n, std::vector &x) { int i; diff --git a/lib/grm/src/grm/utilcpp_int.hxx b/lib/grm/src/grm/utilcpp_int.hxx index 131012efa..13673852d 100644 --- a/lib/grm/src/grm/utilcpp_int.hxx +++ b/lib/grm/src/grm/utilcpp_int.hxx @@ -4,7 +4,11 @@ /* ######################### includes ############################################################################### */ #include +#include +#include +#include #include +#include #include #include @@ -19,12 +23,39 @@ /* ------------------------- util ----------------------------------------------------------------------------------- */ -std::string ltrim(const std::string &s); -std::string rtrim(const std::string &s); -std::string trim(const std::string &s); -bool file_exists(const std::string &name); +std::string_view ltrim(std::string_view s); +std::string_view rtrim(std::string_view s); +std::string_view trim(std::string_view s); bool starts_with(std::string_view str, std::string_view prefix); -bool ends_with(const std::string &str, const std::string &suffix); +bool ends_with(std::string_view str, std::string_view suffix); +size_t string_consists_of(std::string_view input, char c, char ends_with, size_t pos = 0); + +template std::string string_join(Iterator first, Iterator last, std::string_view delimiter) +{ + if (first == last) + { + return std::string{}; + } + auto output_length = std::accumulate(first, last, 0, [](size_t sum, const auto &s) { return sum + s.size(); }) + + (last - first - 1) * delimiter.size(); + std::string output; + output.reserve(output_length); + for (; first != last - 1; ++first) + { + output += *first; + output += delimiter; + } + output += *first; + assert(output.size() == output_length); + return output; +} + +std::string escape_double_minus(std::string_view input); +std::string unescape_double_minus(std::string_view input); + +std::optional is_backup_attribute_for(std::string_view name); + +bool file_exists(const std::string &name); void linspace(double start, double end, int n, std::vector &x); diff --git a/lib/grm/test/internal_api/grm/CMakeLists.txt b/lib/grm/test/internal_api/grm/CMakeLists.txt index 1df106c9b..ca17a846a 100644 --- a/lib/grm/test/internal_api/grm/CMakeLists.txt +++ b/lib/grm/test/internal_api/grm/CMakeLists.txt @@ -3,10 +3,12 @@ cmake_minimum_required(VERSION 3.1...3.16) project( grm_test_internal_api DESCRIPTION "Test the internal api of GRM" - LANGUAGES C + LANGUAGES C CXX ) -set(EXECUTABLE_SOURCES args_automatic_array_conversion.c get_compatible_format.c datatype/string_array_map.c) +set(EXECUTABLE_SOURCES args_automatic_array_conversion.c get_compatible_format.c datatype/string_array_map.c + escape_minus.cxx +) foreach(executable_source ${EXECUTABLE_SOURCES}) get_filename_component(executable "${executable_source}" NAME_WE) @@ -17,6 +19,12 @@ foreach(executable_source ${EXECUTABLE_SOURCES}) target_compile_definitions("${PROJECT_NAME}_${executable}" PRIVATE BUILDING_GR) target_compile_options("${PROJECT_NAME}_${executable}" PRIVATE ${COMPILER_OPTION_ERROR_IMPLICIT}) set_target_properties( - "${PROJECT_NAME}_${executable}" PROPERTIES C_STANDARD 90 C_STANDARD_REQUIRED ON C_EXTENSIONS OFF + "${PROJECT_NAME}_${executable}" + PROPERTIES C_STANDARD 90 + C_STANDARD_REQUIRED ON + C_EXTENSIONS OFF + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF ) endforeach() diff --git a/lib/grm/test/internal_api/grm/escape_minus.cxx b/lib/grm/test/internal_api/grm/escape_minus.cxx new file mode 100644 index 000000000..a032ad06e --- /dev/null +++ b/lib/grm/test/internal_api/grm/escape_minus.cxx @@ -0,0 +1,35 @@ +#include +#include + +#include +#include "test.h" + + +const std::vector test_strings = { + "--", "---", "----", "-----", "-\\-", "-\\--", "-\\--\\-", "-\\-\\\\-\\-", "a -\\\\\\-\\\\--\\-- b", +}; + +void test() +{ + for (const auto &test_string : test_strings) + { + std::cout << "original input: " << test_string << std::endl; + auto escaped_minuses = escape_double_minus(test_string); + std::cout << "escaped: " << escaped_minuses << std::endl; + auto unescaped_double_minuses = unescape_double_minus(escaped_minuses); + std::cout << "unescaped: " << unescaped_double_minuses << std::endl << std::endl; + assert(test_string == unescaped_double_minuses); + } + + std::cout << std::endl; + + for (auto it = std::begin(test_strings); it != std::begin(test_strings) + 4; ++it) + { + std::cout << "original input: " << *it << std::endl; + auto unescaped_double_minuses = unescape_double_minus(*it); + std::cout << "unescaped: " << unescaped_double_minuses << std::endl << std::endl; + assert(unescaped_double_minuses == *it); + } +} + +DEFINE_TEST_MAIN diff --git a/lib/grm/test/internal_api/grm/test.h b/lib/grm/test/internal_api/grm/test.h index 1872d7cf6..f5a84da89 100644 --- a/lib/grm/test/internal_api/grm/test.h +++ b/lib/grm/test/internal_api/grm/test.h @@ -3,6 +3,7 @@ #include #include +#include #ifndef NDEBUG #define DEFINE_TEST_MAIN \ From aa47f2b1eb16d57d6a3b8661c8d80668bbb239d1 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 9 Feb 2024 17:25:46 +0100 Subject: [PATCH 22/48] [GRM] Make `grm_validate` C-compatible --- lib/grm/include/grm/plot.h | 2 +- lib/grm/src/grm/plot.cxx | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/grm/include/grm/plot.h b/lib/grm/include/grm/plot.h index 085272e78..c41032709 100644 --- a/lib/grm/include/grm/plot.h +++ b/lib/grm/include/grm/plot.h @@ -47,6 +47,7 @@ EXPORT int grm_switch(unsigned int id); #if !defined(NO_XERCES_C) EXPORT int grm_load_graphics_tree(FILE *file); #endif +EXPORT int grm_validate(void); #ifdef __cplusplus } @@ -64,7 +65,6 @@ EXPORT int get_focus_and_factor_from_dom(const int x1, const int y1, const int x const int keep_aspect_ratio, double *factor_x, double *factor_y, double *focus_x, double *focus_y, std::shared_ptr &subplot_element); -EXPORT bool grm_validate(void); #if !defined(NO_XERCES_C) EXPORT std::shared_ptr grm_load_graphics_tree_schema(void); diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 650a3f161..f9406c3c8 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -6174,7 +6174,17 @@ int grm_switch(unsigned int id) return 1; } + + +int grm_validate(void) +{ +#ifndef NO_XERCES_C + err_t validation_error = validate_graphics_tree(); + return (validation_error == ERROR_NONE); +#endif + return 0; } +} /* end of extern "C" */ /* ========================= c++ ==================================================================================== */ @@ -6508,12 +6518,3 @@ int get_focus_and_factor_from_dom(const int x1, const int y1, const int x2, cons *focus_y = (ndc_top - *factor_y * viewport[3]) / (1 - *factor_y) - (viewport[2] + viewport[3]) / 2.0; return 1; } - -bool grm_validate(void) -{ -#ifndef NO_XERCES_C - err_t validation_error = validate_graphics_tree(); - return (validation_error == ERROR_NONE); -#endif - return false; -} From d911aa14578738aaa95f07a720ea9442622a3e25 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 16 Feb 2024 10:44:27 +0100 Subject: [PATCH 23/48] [GRM] Fix contour plots by storing `z_min` and `z_max` in the tree --- .../grm/dom_render/graphics_tree/schema.xsd | 2 ++ lib/grm/src/grm/dom_render/render.cxx | 28 ++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd b/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd index 950418fea..5b675790b 100644 --- a/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd +++ b/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd @@ -1400,6 +1400,8 @@ + + diff --git a/lib/grm/src/grm/dom_render/render.cxx b/lib/grm/src/grm/dom_render/render.cxx index 701e755b5..9481d2393 100644 --- a/lib/grm/src/grm/dom_render/render.cxx +++ b/lib/grm/src/grm/dom_render/render.cxx @@ -6118,9 +6118,10 @@ static void processContour(const std::shared_ptr &element, const s int major_h = PLOT_DEFAULT_CONTOUR_MAJOR_H; auto plot_parent = element->parentElement(); getPlotParent(plot_parent); - - z_min = static_cast(plot_parent->getAttribute("_z_lim_min")); - z_max = static_cast(plot_parent->getAttribute("_z_lim_max")); + z_min = element->hasAttribute("z_min") ? static_cast(element->getAttribute("z_min")) + : static_cast(plot_parent->getAttribute("_z_lim_min")); + z_max = element->hasAttribute("z_max") ? static_cast(element->getAttribute("z_max")) + : static_cast(plot_parent->getAttribute("_z_lim_max")); if (element->hasAttribute("levels")) { num_levels = static_cast(element->getAttribute("levels")); @@ -6179,6 +6180,8 @@ static void processContour(const std::shared_ptr &element, const s z_min = grm_min(gridit_z[i], z_min); z_max = grm_max(gridit_z[i], z_max); } + element->setAttribute("z_min", z_min); + element->setAttribute("z_max", z_max); global_render->setSpace(element->parentElement(), z_min, z_max, 0, 90); // not plot_parent because it should be now on central_region @@ -6250,9 +6253,10 @@ static void processContourf(const std::shared_ptr &element, const int major_h = PLOT_DEFAULT_CONTOURF_MAJOR_H; auto plot_parent = element->parentElement(); getPlotParent(plot_parent); - - z_min = static_cast(plot_parent->getAttribute("_z_lim_min")); - z_max = static_cast(plot_parent->getAttribute("_z_lim_max")); + z_min = element->hasAttribute("z_min") ? static_cast(element->getAttribute("z_min")) + : static_cast(plot_parent->getAttribute("_z_lim_min")); + z_max = element->hasAttribute("z_max") ? static_cast(element->getAttribute("z_max")) + : static_cast(plot_parent->getAttribute("_z_lim_max")); if (element->hasAttribute("levels")) { num_levels = static_cast(element->getAttribute("levels")); @@ -6314,6 +6318,8 @@ static void processContourf(const std::shared_ptr &element, const z_min = grm_min(gridit_z[i], z_min); z_max = grm_max(gridit_z[i], z_max); } + element->setAttribute("z_min", z_min); + element->setAttribute("z_max", z_max); global_render->setLineColorInd(element, 989); global_render->setSpace(element->parentElement(), z_min, z_max, 0, 90); // central_region @@ -15659,6 +15665,14 @@ std::vector GRM::Render::getDefaultAndTooltip(const std::shared_ptr std::vector{"5", "Unitless integer values specifying the number of minor tick intervals " "between major tick marks. Values of 0 or 1 imply no minor ticks. Negative " "values specify no labels will be drawn for the z-axis"}}, + {std::string("z_max"), + std::vector{ + "None", + "The maximum z-coordinate of a contour(f) plot (after transforming the input data to a rectangular grid)"}}, + {std::string("z_min"), + std::vector{ + "None", + "The minimum z-coordinate of a contour(f) plot (after transforming the input data to a rectangular grid)"}}, {std::string("z_org"), std::vector{"0", "The world coordinates of the origin (point of intersection) of the z-axis"}}, {std::string("z_org_pos"), @@ -17542,7 +17556,7 @@ void updateFilter(const std::shared_ptr &element, const std::strin "y_labels", }; std::vector series_contour{ - "levels", "px", "py", "pz", "x", "y", "z", + "levels", "px", "py", "pz", "x", "y", "z", "z_max", "z_min", }; std::vector series_contourf = series_contour; std::vector series_heatmap{ From 78ea8ce9874621efa8f599c66facddc93d618731 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Tue, 20 Feb 2024 14:28:07 +0100 Subject: [PATCH 24/48] [GRM] Replace special XML characters in attributes with escaped variants --- .../src/grm/dom_render/graphics_tree/util.cxx | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/grm/src/grm/dom_render/graphics_tree/util.cxx b/lib/grm/src/grm/dom_render/graphics_tree/util.cxx index 2c6d49622..5d0797326 100644 --- a/lib/grm/src/grm/dom_render/graphics_tree/util.cxx +++ b/lib/grm/src/grm/dom_render/graphics_tree/util.cxx @@ -8,6 +8,36 @@ #include #include "grm/utilcpp_int.hxx" +static std::string escapeXMLAttribute(std::string_view attribute) +{ + std::stringstream os; + for (auto c : attribute) + { + switch (c) + { + case '&': + os << "&"; + break; + case '<': + os << "<"; + break; + case '>': + os << ">"; + break; + case '"': + os << """; + break; + case '\'': + os << "'"; + break; + default: + os << c; + break; + } + } + return os.str(); +} + static void nodeToXML(std::stringstream &os, const std::shared_ptr &node, const GRM::SerializerOptions &options, const std::string &indent, std::optional &e } else { - os << " " << attribute_name.get() << "=\"" << value << "\""; + os << " " << attribute_name.get() << "=\"" << escapeXMLAttribute(value) << "\""; } } if (element->hasChildNodes()) From fec38a4e6f9ae68e753593ff6edd9b032ae15cd6 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Tue, 20 Feb 2024 16:06:32 +0100 Subject: [PATCH 25/48] [grplot] Add `openXML` and `saveXML` test commands to `grplot` --- lib/grm/grplot/grplot_widget.cxx | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/grm/grplot/grplot_widget.cxx b/lib/grm/grplot/grplot_widget.cxx index b579fe2ba..9fe5fa832 100644 --- a/lib/grm/grplot/grplot_widget.cxx +++ b/lib/grm/grplot/grplot_widget.cxx @@ -2977,6 +2977,32 @@ void GRPlotWidget::processTestCommandsFile() break; } } + else if (words[0] == "openXML" && words.size() == 2) + { +#ifndef NO_XERCES_C + auto file = fopen(words[1].toStdString().c_str(), "r"); + if (file) + { + grm_load_graphics_tree(file); + global_root = grm_get_document_root(); + redraw(); + QTimer::singleShot(100, this, &GRPlotWidget::processTestCommandsFile); + return; + } +#else + std::cerr << "Xerces-C++ support not compiled in. XML files cannot be openend." << std::endl; +#endif + } + else if (words[0] == "saveXML" && words.size() == 2) + { + std::ofstream save_file_stream(words[1].toStdString()); + if (save_file_stream) + { + auto graphics_tree_str = + std::unique_ptr(grm_dump_graphics_tree_str(), std::free); + save_file_stream << graphics_tree_str.get() << std::endl; + } + } else { std::cerr << "Unknown test event: " << line.toStdString() << std::endl; From c251c43548b6d42d85d56b889bbda3e5891ee73c Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 19 Jul 2024 13:31:32 +0200 Subject: [PATCH 26/48] [grplot] Clear tooltips on XML file loading in testing mode --- lib/grm/grplot/grplot_widget.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/grm/grplot/grplot_widget.cxx b/lib/grm/grplot/grplot_widget.cxx index 9fe5fa832..cc25398fd 100644 --- a/lib/grm/grplot/grplot_widget.cxx +++ b/lib/grm/grplot/grplot_widget.cxx @@ -2985,6 +2985,7 @@ void GRPlotWidget::processTestCommandsFile() { grm_load_graphics_tree(file); global_root = grm_get_document_root(); + tooltips.clear(); redraw(); QTimer::singleShot(100, this, &GRPlotWidget::processTestCommandsFile); return; From b588391c0a3d1324c91f14333c5f8fd1077b5713 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Tue, 20 Feb 2024 17:30:24 +0100 Subject: [PATCH 27/48] [grplot] Handle lines starting with `#` in test files as comments --- lib/grm/grplot/grplot_widget.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/grm/grplot/grplot_widget.cxx b/lib/grm/grplot/grplot_widget.cxx index cc25398fd..d69b83efd 100644 --- a/lib/grm/grplot/grplot_widget.cxx +++ b/lib/grm/grplot/grplot_widget.cxx @@ -2763,6 +2763,7 @@ void GRPlotWidget::processTestCommandsFile() while (test_commands_stream && !test_commands_stream->atEnd()) { QString line = test_commands_stream->readLine(); + if (line.startsWith("#")) continue; QStringList words = line.split(","); if (!words.empty()) { From 7558d7c43fbe89f545fd9c1d2df98fc0a024d66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20K=C3=B6pke?= Date: Tue, 17 Nov 2020 15:29:55 +0100 Subject: [PATCH 28/48] [GRM] Add a BSON serializer and deserializer --- CMakeLists.txt | 1 + lib/grm/Makefile | 1 + lib/grm/include/grm/dump.h | 2 + lib/grm/makefile.mingw | 1 + lib/grm/src/grm/bson.c | 2412 +++++++++++++++++ lib/grm/src/grm/bson_int.h | 161 ++ lib/grm/src/grm/dump.c | 69 + lib/grm/src/grm/memwriter.c | 16 + lib/grm/src/grm/memwriter_int.h | 1 + lib/grm/test/public_api/grm/CMakeLists.txt | 1 + .../grm/bson_serialize_deserialize.c | 53 + 11 files changed, 2718 insertions(+) create mode 100644 lib/grm/src/grm/bson.c create mode 100644 lib/grm/src/grm/bson_int.h create mode 100644 lib/grm/test/public_api/grm/bson_serialize_deserialize.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 028aa207e..aae8ad832 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -517,6 +517,7 @@ set(GRM_SOURCES lib/grm/src/grm/args.c lib/grm/src/grm/backtrace.c lib/grm/src/grm/base64.c + lib/grm/src/grm/bson.c lib/grm/src/grm/dump.c lib/grm/src/grm/dynamic_args_array.c lib/grm/src/grm/error.c diff --git a/lib/grm/Makefile b/lib/grm/Makefile index f7e881a6e..990c2334b 100644 --- a/lib/grm/Makefile +++ b/lib/grm/Makefile @@ -12,6 +12,7 @@ UNAME := $(shell uname) GRMOBJS = src/grm/args.o \ src/grm/backtrace.o \ src/grm/base64.o \ + src/grm/bson.o \ src/grm/dump.o \ src/grm/dynamic_args_array.o \ src/grm/error.o \ diff --git a/lib/grm/include/grm/dump.h b/lib/grm/include/grm/dump.h index 28aa9a96e..bb7834cdc 100644 --- a/lib/grm/include/grm/dump.h +++ b/lib/grm/include/grm/dump.h @@ -22,6 +22,8 @@ extern "C" { #ifndef NDEBUG EXPORT void grm_dump(const grm_args_t *args, FILE *f); EXPORT void grm_dump_json(const grm_args_t *args, FILE *f); +EXPORT void grm_dump_bson(const grm_args_t *args, FILE *f); +EXPORT void grm_dump_bson_and_parse(const grm_args_t *args, FILE *f); EXPORT char *grm_dump_json_str(void); #else #define grm_dump diff --git a/lib/grm/makefile.mingw b/lib/grm/makefile.mingw index 956431778..c718a9b50 100644 --- a/lib/grm/makefile.mingw +++ b/lib/grm/makefile.mingw @@ -21,6 +21,7 @@ XERCESCLIBS = $(THIRDPARTYDIR)/lib/libxerces-c.a $(THIRDPARTYDIR)/lib/libicuuc.a OBJS = src/grm/args.o \ src/grm/backtrace.o \ src/grm/base64.o \ + src/grm/bson.o \ src/grm/dump.o \ src/grm/dynamic_args_array.o \ src/grm/error.o \ diff --git a/lib/grm/src/grm/bson.c b/lib/grm/src/grm/bson.c new file mode 100644 index 000000000..8f7567423 --- /dev/null +++ b/lib/grm/src/grm/bson.c @@ -0,0 +1,2412 @@ +#ifdef __unix__ +#define _POSIX_C_SOURCE 200112L +#endif + +/* ######################### includes ############################################################################### */ + +#include +#include +#include +#include +#include + +#include +#include "bson_int.h" +#include "plot_int.h" + + +/* ######################### internal implementation ################################################################ */ + +/* ========================= macros ================================================================================= */ + +const int i = 1; +#define is_bigendian() ((*(char *)&i) == 0) + +/* ------------------------- general -------------------------------------------------------------------------------- */ + +#ifndef DBL_DECIMAL_DIG +#define DBL_DECIMAL_DIG 17 +#endif + + +/* ========================= static variables ======================================================================= */ + +/* ------------------------- bson deserializer ------------------------------------------------------------------------ + */ + +static err_t (*frombson_datatype_to_func[128])(frombson_state_t *); +static int frombson_static_variables_initialized = 0; + +/* ------------------------- bson serializer ------------------------------------------------------------------------ */ + +static err_t (*tobson_datatype_to_func[128])(tobson_state_t *); +static int tobson_static_variables_initialized = 0; +static tobson_permanent_state_t tobson_permanent_state = {complete, 0}; +static char tobson_datatype_to_byte[128]; +static char null = 0x00; + + +/* ========================= methods ================================================================================ */ + +void revmemcpy(void *dest, const void *src, size_t len) +{ + char *d = (char *)dest + len - 1; + const char *s = src; + while (len--) + { + *d-- = *s++; + } +} + +char byte_to_type(const char *byte) +{ + switch (*byte) + { + case (char)0x01: + return 'd'; + case (char)0x02: + return 's'; + case (char)0x03: + return 'a'; + case (char)0x04: + return 'n'; + case (char)0x08: + return 'b'; + case (char)0x10: + return 'i'; + default: + return '\0'; + } +} + +void int_to_bytes(int i, char **bytes) +{ + *bytes = (char *)malloc(sizeof(int) * sizeof(char)); + if (is_bigendian()) + { + revmemcpy(*bytes, &i, sizeof(i)); + } + else + { + memcpy(*bytes, &i, sizeof(i)); + } +} + +void bytes_to_int(int *i, const char *bytes) +{ + if (is_bigendian()) + { + revmemcpy(i, bytes, sizeof(int)); + } + else + { + memcpy(i, bytes, sizeof(int)); + } +} + +void double_to_bytes(double d, char **bytes) +{ + *bytes = (char *)malloc(sizeof(double) * sizeof(char)); + if (is_bigendian()) + { + revmemcpy(*bytes, &d, sizeof(d)); + } + else + { + memcpy(*bytes, &d, sizeof(d)); + } +} + +void bytes_to_double(double *d, const char *bytes) +{ + if (is_bigendian()) + { + revmemcpy(d, bytes, sizeof(double)); + } + else + { + memcpy(d, bytes, sizeof(double)); + } +} + + +/* ------------------------- bson deserializer ---------------------------------------------------------------------- */ + +err_t frombson_read(grm_args_t *args, const char *bson_bytes) +{ + + int document_size; + frombson_state_t state; + frombson_object_infos_t object_infos; + err_t error = ERROR_NONE; + + frombson_init_static_variables(); + state.num_read_bytes = 0; + state.cur_byte = bson_bytes; + state.args = args; + state.cur_value_buf = NULL; + + object_infos.num_bytes_read_before = 0; + + if ((error = frombson_read_length(&state, &document_size)) != ERROR_NONE) + { + return error; + } + + object_infos.length = document_size; + state.object_infos = &object_infos; + + if ((error = frombson_read_object(&state)) != ERROR_NONE) + { + return error; + } + + return error; +} + +err_t frombson_read_value_format(frombson_state_t *state, char *value_format) +{ + + *value_format = byte_to_type(state->cur_byte); + state->num_read_bytes++; + state->cur_byte++; + + return ERROR_NONE; +} + +err_t frombson_read_key(frombson_state_t *state, const char **key) +{ + + *key = state->cur_byte; + + while (*(state->cur_byte) != '\0') + { + ++(state->num_read_bytes); + ++(state->cur_byte); + } + + ++(state->num_read_bytes); + ++(state->cur_byte); + + return ERROR_NONE; +} + +err_t frombson_skip_key(frombson_state_t *state) +{ + + while (*(state->cur_byte) != '\0') + { + ++(state->num_read_bytes); + ++(state->cur_byte); + } + + ++(state->num_read_bytes); + ++(state->cur_byte); + + return ERROR_NONE; +} + +err_t frombson_read_length(frombson_state_t *state, int *length) +{ + + bytes_to_int(length, state->cur_byte); + state->cur_byte += sizeof(int); + state->num_read_bytes += sizeof(int); + + return ERROR_NONE; +} + +err_t frombson_read_double_value(frombson_state_t *state, double *d) +{ + + bytes_to_double(d, state->cur_byte); + + state->num_read_bytes += sizeof(double); + state->cur_byte += sizeof(double); + + return ERROR_NONE; +} + +err_t frombson_read_int_value(frombson_state_t *state, int *i) +{ + + bytes_to_int(i, state->cur_byte); + + state->num_read_bytes += sizeof(int); + state->cur_byte += sizeof(int); + + return ERROR_NONE; +} + +err_t frombson_read_string_value(frombson_state_t *state, const char **s) +{ + + *s = state->cur_byte; + + while (*(state->cur_byte) != '\0') + { + ++(state->num_read_bytes); + ++(state->cur_byte); + } + + ++(state->num_read_bytes); + ++(state->cur_byte); + + return ERROR_NONE; +} + +err_t frombson_read_bool_value(frombson_state_t *state, int *b) +{ + + if (*(state->cur_byte) == (char)0x00) + { + *b = 0; + } + else + { + *b = 1; + } + + state->num_read_bytes++; + state->cur_byte++; + + return ERROR_NONE; +} + +err_t frombson_read_object(frombson_state_t *state) +{ + + err_t error = ERROR_NONE; + int object_closed = 0; + frombson_object_infos_t *object_infos = state->object_infos; + + while (object_infos->length - (state->num_read_bytes - object_infos->num_bytes_read_before) > 0) + { + + if ((error = frombson_read_value_format(state, &(state->cur_value_format))) != ERROR_NONE) + { + return error; + } + + if ((error = frombson_datatype_to_func[state->cur_value_format](state)) != ERROR_NONE) + { + return error; + } + + if (object_infos->length - (state->num_read_bytes - object_infos->num_bytes_read_before) == 1) + { + if (*(state->cur_byte) == '\0') + { + state->num_read_bytes++; + state->cur_byte++; + object_closed = 1; + } + } + } + + if (!object_closed) + { + error = ERROR_PARSE_OBJECT; + } + + return error; +} + +err_t frombson_parse_double(frombson_state_t *state) +{ + + double d; + int memory_allocated = 0; + err_t error; + + char cur_value_type[2] = "\0"; + cur_value_type[0] = state->cur_value_format; + + if ((error = frombson_read_key(state, &(state->cur_key))) != ERROR_NONE) + { + goto cleanup; + } + + state->cur_value_buf = malloc(sizeof(double)); + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + if ((error = frombson_read_double_value(state, &d)) != ERROR_NONE) + { + goto cleanup; + } + *((double *)state->cur_value_buf) = d; + + grm_args_push_buf(state->args, state->cur_key, cur_value_type, state->cur_value_buf, 0); + +cleanup: + if (memory_allocated) + { + free(state->cur_value_buf); + } + + return error; +} + +err_t frombson_parse_int(frombson_state_t *state) +{ + + int i; + err_t error; + int memory_allocated = 0; + + char cur_value_type[2] = "\0"; + cur_value_type[0] = state->cur_value_format; + + if ((error = frombson_read_key(state, &(state->cur_key))) != ERROR_NONE) + { + goto cleanup; + } + + state->cur_value_buf = malloc(sizeof(int)); + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + if ((error = frombson_read_int_value(state, &i)) != ERROR_NONE) + { + goto cleanup; + } + *((int *)state->cur_value_buf) = i; + + grm_args_push_buf(state->args, state->cur_key, cur_value_type, state->cur_value_buf, 0); + +cleanup: + if (memory_allocated) + { + free(state->cur_value_buf); + } + + return error; +} + +err_t frombson_parse_string(frombson_state_t *state) +{ + + int length; + const char *s; + err_t error; + int memory_allocated = 0; + + char cur_value_type[2] = "\0"; + cur_value_type[0] = state->cur_value_format; + + + if ((error = frombson_read_key(state, &(state->cur_key))) != ERROR_NONE) + { + goto cleanup; + } + + if ((error = frombson_read_length(state, &length)) != ERROR_NONE) + { + return error; + } + + state->cur_value_buf = malloc(length * sizeof(char)); + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + if ((error = frombson_read_string_value(state, &s)) != ERROR_NONE) + { + goto cleanup; + } + *((const char **)state->cur_value_buf) = s; + + grm_args_push_buf(state->args, state->cur_key, cur_value_type, state->cur_value_buf, 0); + +cleanup: + if (memory_allocated) + { + free(state->cur_value_buf); + } + + return error; +} + +err_t frombson_parse_bool(frombson_state_t *state) +{ + + int b; + err_t error; + int memory_allocated = 0; + + char cur_value_type[2] = "\0"; + cur_value_type[0] = state->cur_value_format; + + if ((error = frombson_read_key(state, &(state->cur_key))) != ERROR_NONE) + { + goto cleanup; + } + + state->cur_value_buf = malloc(sizeof(int)); + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + if ((error = frombson_read_bool_value(state, &b)) != ERROR_NONE) + { + goto cleanup; + } + *((int *)state->cur_value_buf) = b; + + grm_args_push_buf(state->args, state->cur_key, cur_value_type, state->cur_value_buf, 0); + +cleanup: + if (memory_allocated) + { + free(state->cur_value_buf); + } + + return error; +} + +err_t frombson_parse_array(frombson_state_t *state) +{ + + int length, num_bytes_read_before; + char first_value_type; + char final_value_type[3] = "\0"; + frombson_array_infos_t array_infos; + err_t error; + int memory_allocated = 0; + + final_value_type[0] = state->cur_value_format; + if ((error = frombson_read_key(state, &(state->cur_key))) != ERROR_NONE) + { + goto cleanup; + } + + num_bytes_read_before = state->num_read_bytes; + if ((error = frombson_read_length(state, &length)) != ERROR_NONE) + { + goto cleanup; + } + + /* read first value type without changing pointer */ + first_value_type = byte_to_type(state->cur_byte); + final_value_type[1] = toupper(first_value_type); + + state->cur_value_format = first_value_type; + array_infos.length = length; + array_infos.num_bytes_read_before = num_bytes_read_before; + state->array_infos = &array_infos; + + if ((error = frombson_datatype_to_func[toupper(first_value_type)](state)) != ERROR_NONE) + { + goto cleanup; + } + memory_allocated = 1; + + grm_args_push(state->args, state->cur_key, final_value_type, state->array_infos->num_elements, state->cur_value_buf); + +cleanup: + if (memory_allocated) + { + free(state->cur_value_buf); + } + + return error; +} + +err_t frombson_parse_object(frombson_state_t *state) +{ + + int length; + err_t error; + int num_bytes_read_before; + grm_args_t *new_args = grm_args_new(); + frombson_state_t inner_state; + frombson_object_infos_t object_infos; + + char cur_value_type[2] = "\0"; + cur_value_type[0] = state->cur_value_format; + + if ((error = frombson_read_key(state, &(state->cur_key))) != ERROR_NONE) + { + return error; + } + + num_bytes_read_before = state->num_read_bytes; + + if ((error = frombson_read_length(state, &length)) != ERROR_NONE) + { + return error; + } + + object_infos.length = length; + object_infos.num_bytes_read_before = num_bytes_read_before; + + inner_state.num_read_bytes = state->num_read_bytes; + inner_state.args = new_args; + inner_state.cur_byte = state->cur_byte; + inner_state.cur_value_buf = NULL; + inner_state.object_infos = &object_infos; + + frombson_read_object(&inner_state); + + state->num_read_bytes = inner_state.num_read_bytes; + state->cur_byte = inner_state.cur_byte; + grm_args_push(state->args, state->cur_key, cur_value_type, inner_state.args); + + + return error; +} + +err_t frombson_read_int_array(frombson_state_t *state) +{ + + int i = 0; + char next_value_type; + int array_closed = 0; + err_t error; + int memory_allocated = 0; + frombson_array_infos_t *array_infos = state->array_infos; + + array_infos->num_elements = (array_infos->length - 5) / (sizeof(int) + 3); + state->cur_value_buf = malloc(sizeof(int) * array_infos->num_elements); + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + int current_value; + + while (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) > 0) + { + if ((error = frombson_read_value_format(state, &next_value_type)) != ERROR_NONE) + { + goto cleanup; + } + if (state->cur_value_format != next_value_type) + { + error = ERROR_PARSE_ARRAY; + goto cleanup; + } + if ((error = frombson_skip_key(state)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = frombson_read_int_value(state, ¤t_value)) != ERROR_NONE) + { + goto cleanup; + } + ((int *)state->cur_value_buf)[i] = current_value; + ++i; + + if (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) == 1) + { + if (*(state->cur_byte) == '\0') + { + state->num_read_bytes++; + state->cur_byte++; + array_closed = 1; + } + } + } + + if (!array_closed) + { + error = ERROR_PARSE_ARRAY; + } + +cleanup: + if (memory_allocated && error != ERROR_NONE) + { + free(state->cur_value_buf); + } + + return error; +} + +err_t frombson_read_double_array(frombson_state_t *state) +{ + + int i = 0; + char next_value_type; + int array_closed = 0; + err_t error; + int memory_allocated = 0; + frombson_array_infos_t *array_infos = state->array_infos; + + array_infos->num_elements = (array_infos->length - 5) / (sizeof(double) + 3); + state->cur_value_buf = malloc(sizeof(double) * array_infos->num_elements); + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + double current_value; + + while (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) > 0) + { + if ((error = frombson_read_value_format(state, &next_value_type)) != ERROR_NONE) + { + goto cleanup; + } + if (state->cur_value_format != next_value_type) + { + error = ERROR_PARSE_ARRAY; + goto cleanup; + } + if ((error = frombson_skip_key(state)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = frombson_read_double_value(state, ¤t_value)) != ERROR_NONE) + { + goto cleanup; + } + ((double *)state->cur_value_buf)[i] = current_value; + ++i; + + if (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) == 1) + { + if (*(state->cur_byte) == '\0') + { + state->num_read_bytes++; + state->cur_byte++; + array_closed = 1; + } + } + } + + if (!array_closed) + { + error = ERROR_PARSE_ARRAY; + } + +cleanup: + if (memory_allocated && error != ERROR_NONE) + { + free(state->cur_value_buf); + } + + return error; +} + +err_t frombson_read_string_array(frombson_state_t *state) +{ + + int i = 0; + char next_value_type; + int array_closed = 0; + int string_length; + err_t error; + int memory_allocated = 0; + frombson_array_infos_t *array_infos = state->array_infos; + + state->cur_value_buf = malloc(array_infos->length - 4); /* array length minus length bytes */ + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + const char *current_value; + + while (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) > 0) + { + if ((error = frombson_read_value_format(state, &next_value_type)) != ERROR_NONE) + { + goto cleanup; + } + if (state->cur_value_format != next_value_type) + { + error = ERROR_PARSE_ARRAY; + goto cleanup; + } + if ((error = frombson_skip_key(state)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = frombson_read_length(state, &string_length)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = frombson_read_string_value(state, ¤t_value)) != ERROR_NONE) + { + goto cleanup; + } + ((const char **)state->cur_value_buf)[i] = current_value; + ++i; + + if (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) == 1) + { + if (*(state->cur_byte) == '\0') + { + state->num_read_bytes++; + state->cur_byte++; + array_closed = 1; + } + } + } + + array_infos->num_elements = i; + + if (!array_closed) + { + error = ERROR_PARSE_ARRAY; + } + +cleanup: + if (memory_allocated && error != ERROR_NONE) + { + free(state->cur_value_buf); + } + + return error; +} + +err_t frombson_read_bool_array(frombson_state_t *state) +{ + + int i = 0; + char next_value_type; + int array_closed = 0; + err_t error; + int memory_allocated = 0; + frombson_array_infos_t *array_infos = state->array_infos; + + array_infos->num_elements = (array_infos->length - 5) / (sizeof(char) + 3); + state->cur_value_buf = malloc(sizeof(int) * array_infos->num_elements); + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + int current_value; + + while (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) > 0) + { + if ((error = frombson_read_value_format(state, &next_value_type)) != ERROR_NONE) + { + goto cleanup; + } + if (state->cur_value_format != next_value_type) + { + error = ERROR_PARSE_ARRAY; + goto cleanup; + } + if ((error = frombson_skip_key(state)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = frombson_read_bool_value(state, ¤t_value)) != ERROR_NONE) + { + goto cleanup; + } + ((int *)state->cur_value_buf)[i] = current_value; + ++i; + + if (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) == 1) + { + if (*(state->cur_byte) == '\0') + { + state->num_read_bytes++; + state->cur_byte++; + array_closed = 1; + } + } + } + + if (!array_closed) + { + error = ERROR_PARSE_ARRAY; + } + +cleanup: + if (memory_allocated && error != ERROR_NONE) + { + free(state->cur_value_buf); + } + + return error; +} + +err_t frombson_read_object_array(frombson_state_t *state) +{ + + int i = 0; + char next_value_type; + int array_closed = 0; + int cur_object_length; + err_t error; + int memory_allocated = 0; + frombson_array_infos_t *array_infos = state->array_infos; + frombson_state_t inner_state; + frombson_object_infos_t object_infos; + int num_read_bytes_before; + + state->cur_value_buf = malloc(array_infos->length - 4); /* array length minus length bytes */ + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + + while (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) > 0) + { + if ((error = frombson_read_value_format(state, &next_value_type)) != ERROR_NONE) + { + goto cleanup; + } + if (state->cur_value_format != next_value_type) + { + error = ERROR_PARSE_ARRAY; + goto cleanup; + } + if ((error = frombson_skip_key(state)) != ERROR_NONE) + { + goto cleanup; + } + num_read_bytes_before = state->num_read_bytes; + if ((error = frombson_read_length(state, &cur_object_length)) != ERROR_NONE) + { + goto cleanup; + } + + inner_state.args = grm_args_new(); + inner_state.cur_byte = state->cur_byte; + inner_state.num_read_bytes = state->num_read_bytes; + inner_state.cur_value_buf = NULL; + + object_infos.length = cur_object_length; + object_infos.num_bytes_read_before = num_read_bytes_before; + inner_state.object_infos = &object_infos; + + if ((error = frombson_read_object(&inner_state)) != ERROR_NONE) + { + goto cleanup; + } + + state->num_read_bytes = inner_state.num_read_bytes; + state->cur_byte = inner_state.cur_byte; + + ((grm_args_t **)state->cur_value_buf)[i] = inner_state.args; + ++i; + + if (array_infos->length - (state->num_read_bytes - array_infos->num_bytes_read_before) == 1) + { + if (*(state->cur_byte) == '\0') + { + state->num_read_bytes++; + state->cur_byte++; + array_closed = 1; + } + } + } + + array_infos->num_elements = i; + + if (!array_closed) + { + error = ERROR_PARSE_ARRAY; + } + +cleanup: + if (memory_allocated && error != ERROR_NONE) + { + free(state->cur_value_buf); + } + + return error; +} + +void frombson_init_static_variables(void) +{ + if (!frombson_static_variables_initialized) + { + frombson_datatype_to_func['n'] = frombson_parse_array; + frombson_datatype_to_func['i'] = frombson_parse_int; + frombson_datatype_to_func['I'] = frombson_read_int_array; + frombson_datatype_to_func['d'] = frombson_parse_double; + frombson_datatype_to_func['D'] = frombson_read_double_array; + frombson_datatype_to_func['s'] = frombson_parse_string; + frombson_datatype_to_func['S'] = frombson_read_string_array; + frombson_datatype_to_func['b'] = frombson_parse_bool; + frombson_datatype_to_func['B'] = frombson_read_bool_array; + frombson_datatype_to_func['a'] = frombson_parse_object; + frombson_datatype_to_func['A'] = frombson_read_object_array; + + frombson_static_variables_initialized = 1; + } +} + + +/* ------------------------- bson serializer ------------------------------------------------------------------------ */ + +err_t tobson_stringify_int(tobson_state_t *state) +{ + int value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(int); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((int *)state->shared->data_ptr); + state->shared->data_ptr = ((int *)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(int); + } + else + { + value = va_arg(*state->shared->vl, int); + } + if ((error = tobson_stringify_int_value(state->memwriter, value)) != ERROR_NONE) + { + return error; + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_int_array(tobson_state_t *state) +{ + int *values; + int current_value; + unsigned int length; + int remaining_elements; + err_t error = ERROR_NONE; + char *length_as_bytes; + char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; + int size_before = state->memwriter->size; + char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); + char key = '0'; + if (state->shared->data_ptr != NULL) + { + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(int *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + values = *(int **)state->shared->data_ptr; + } + else + { + values = va_arg(*state->shared->vl, int *); + } + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &length)) + { + debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", + state->additional_type_info)); + length = 0; + } + } + else + { + length = state->shared->array_length; + } + remaining_elements = length; + /* write array start */ + if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) + { + return error; + } + /* write array content */ + while (remaining_elements) + { + current_value = *values++; + /* Datatype */ + if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte['i'])) != ERROR_NONE) + { + return error; + } + /* Key */ + if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + { + return error; + } + /* End Of Key */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Value */ + if ((error = tobson_stringify_int_value(state->memwriter, current_value)) != ERROR_NONE) + { + return error; + } + --remaining_elements; + } + /* write array end */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Set length of object*/ + int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + memcpy(placeholder_pos, length_as_bytes, 4); + free(length_as_bytes); + if (state->shared->data_ptr != NULL) + { + state->shared->data_ptr = ((int **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(int *); + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_int_value(memwriter_t *memwriter, int value) +{ + char *bytes; + err_t error; + + int_to_bytes(value, &bytes); + + error = memwriter_puts_with_len(memwriter, bytes, sizeof(int)); + free(bytes); + + return error; +} + + +err_t tobson_stringify_double(tobson_state_t *state) +{ + double value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(double); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((double *)state->shared->data_ptr); + state->shared->data_ptr = ((double *)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(double); + } + else + { + value = va_arg(*state->shared->vl, double); + } + if ((error = tobson_stringify_double_value(state->memwriter, value)) != ERROR_NONE) + { + return error; + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_double_array(tobson_state_t *state) +{ + double *values; + double current_value; + unsigned int length; + int remaining_elements; + err_t error = ERROR_NONE; + char *length_as_bytes; + char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; + int size_before = state->memwriter->size; + char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); + char key = '0'; + if (state->shared->data_ptr != NULL) + { + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(double *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + values = *(double **)state->shared->data_ptr; + } + else + { + values = va_arg(*state->shared->vl, double *); + } + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &length)) + { + debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", + state->additional_type_info)); + length = 0; + } + } + else + { + length = state->shared->array_length; + } + remaining_elements = length; + /* write array start */ + /* Placeholder for length of array*/ + if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) + { + return error; + } + /* write array content */ + while (remaining_elements) + { + current_value = *values++; + /* Datatype */ + if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte['d'])) != ERROR_NONE) + { + return error; + } + /* Key */ + if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + { + return error; + } + /* End Of Key */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Value */ + if ((error = tobson_stringify_double_value(state->memwriter, current_value)) != ERROR_NONE) + { + return error; + } + --remaining_elements; + } + /* write array end */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Set length of object*/ + int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + memcpy(placeholder_pos, length_as_bytes, 4); + free(length_as_bytes); + if (state->shared->data_ptr != NULL) + { + state->shared->data_ptr = ((double **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(double *); + } + state->shared->wrote_output = 1; + return error; +} + + +err_t tobson_stringify_char(tobson_state_t *state) +{ + char value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((char *)state->shared->data_ptr); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(char); + } + else + { + value = va_arg(*state->shared->vl, int); + } + if ((error = tobson_stringify_char_value(state->memwriter, value)) != ERROR_NONE) + { + return error; + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_char_value(memwriter_t *memwriter, char value) +{ + err_t error; + char length[4] = {(char)0x02, (char)0x00, (char)0x00, (char)0x00}; /*char is saved as string */ + if ((error = memwriter_puts_with_len(memwriter, length, 4)) != ERROR_NONE) + { + return error; + } + if ((error = memwriter_putc(memwriter, value)) != ERROR_NONE) + { + return error; + } + if ((error = memwriter_putc(memwriter, null)) != ERROR_NONE) + { + return error; + } + return ERROR_NONE; +} + + +err_t tobson_stringify_string(tobson_state_t *state) +{ + char *value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((char **)state->shared->data_ptr); + state->shared->data_ptr = ((char **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(char *); + } + else + { + value = va_arg(*state->shared->vl, char *); + } + if ((error = tobson_stringify_string_value(state->memwriter, value)) != ERROR_NONE) + { + return error; + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_string_array(tobson_state_t *state) +{ + char **values; + char *current_value; + unsigned int length; + int remaining_elements; + err_t error = ERROR_NONE; + char *length_as_bytes; + char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; + int size_before = state->memwriter->size; + char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); + char key = '0'; + if (state->shared->data_ptr != NULL) + { + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char **); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + values = *(char ***)state->shared->data_ptr; + } + else + { + values = va_arg(*state->shared->vl, char **); + } + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &length)) + { + debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", + state->additional_type_info)); + length = 0; + } + } + else + { + length = state->shared->array_length; + } + remaining_elements = length; + /* write array start */ + /* Placeholder for length of array*/ + if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) + { + return error; + } + /* write array content */ + while (remaining_elements) + { + current_value = *values++; + /* Datatype */ + if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte['s'])) != ERROR_NONE) + { + return error; + } + /* Key */ + if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + { + return error; + } + /* End Of Key */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Value */ + if ((error = tobson_stringify_string_value(state->memwriter, current_value)) != ERROR_NONE) + { + return error; + } + --remaining_elements; + } /* write array end */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Set length of object*/ + int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + memcpy(placeholder_pos, length_as_bytes, 4); + free(length_as_bytes); + if (state->shared->data_ptr != NULL) + { + state->shared->data_ptr = ((char ***)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(char **); + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_bool(tobson_state_t *state) +{ + int value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(int); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((int *)state->shared->data_ptr); + state->shared->data_ptr = ((int *)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(int); + } + else + { + value = va_arg(*state->shared->vl, int); + } + if ((error = tobson_stringify_bool_value(state->memwriter, value)) != ERROR_NONE) + { + return error; + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_bool_array(tobson_state_t *state) +{ + int *values; + int current_value; + unsigned int length; + int remaining_elements; + err_t error = ERROR_NONE; + char *length_as_bytes; + char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; + int size_before = state->memwriter->size; + char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); + char key = '0'; + if (state->shared->data_ptr != NULL) + { + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(int *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + values = *(int **)state->shared->data_ptr; + } + else + { + values = va_arg(*state->shared->vl, int *); + } + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &length)) + { + debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", + state->additional_type_info)); + length = 0; + } + } + else + { + length = state->shared->array_length; + } + remaining_elements = length; + /* write array start */ + /* Placeholder for length of array*/ + if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) + { + return error; + } + /* write array content */ + while (remaining_elements) + { + current_value = *values++; + /* Datatype */ + if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte['b'])) != ERROR_NONE) + { + return error; + } + /* Key */ + if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + { + return error; + } + /* End Of Key */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Value */ + if ((error = tobson_stringify_bool_value(state->memwriter, current_value)) != ERROR_NONE) + { + return error; + } + + --remaining_elements; + } + /* write array end */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Set length of object*/ + int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + memcpy(placeholder_pos, length_as_bytes, 4); + free(length_as_bytes); + if (state->shared->data_ptr != NULL) + { + state->shared->data_ptr = ((int **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(int *); + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_args(tobson_state_t *state) +{ + grm_args_t *value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(grm_args_t *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((grm_args_t **)state->shared->data_ptr); + state->shared->data_ptr = ((grm_args_t **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(grm_args_t *); + } + else + { + value = va_arg(*state->shared->vl, grm_args_t *); + } + if ((error = tobson_stringify_args_value(state->memwriter, value)) != ERROR_NONE) + { + return error; + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_args_array(tobson_state_t *state) +{ + grm_args_t **values; + grm_args_t *current_value; + unsigned int length; + int remaining_elements; + err_t error = ERROR_NONE; + char *length_as_bytes; + char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; + int size_before = state->memwriter->size; + char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); + char key = '0'; + if (state->shared->data_ptr != NULL) + { + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(grm_args_t **); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + values = *(grm_args_t ***)state->shared->data_ptr; + } + else + { + values = va_arg(*state->shared->vl, grm_args_t **); + } + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &length)) + { + debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", + state->additional_type_info)); + length = 0; + } + } + else + { + length = state->shared->array_length; + } + remaining_elements = length; + /* write array start */ + /* Placeholder for length of array*/ + if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) + { + return error; + } + /* write array content */ + while (remaining_elements) + { + current_value = *values++; + + /* Datatype */ + if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte['a'])) != ERROR_NONE) + { + return error; + } + /* Key */ + if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + { + return error; + } + /* End Of Key */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Value */ + if ((error = tobson_stringify_args_value(state->memwriter, current_value)) != ERROR_NONE) + { + return error; + } + --remaining_elements; + } + /* write array end */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + /* Set length of object*/ + int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + memcpy(placeholder_pos, length_as_bytes, 4); + free(length_as_bytes); + + if (state->shared->data_ptr != NULL) + { + state->shared->data_ptr = ((grm_args_t ***)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(grm_args_t **); + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_stringify_double_value(memwriter_t *memwriter, double value) +{ + err_t error; + char *bytes; + + double_to_bytes(value, &bytes); + error = memwriter_puts_with_len(memwriter, bytes, sizeof(double)); + free(bytes); + + return error; +} + +err_t tobson_stringify_char_array(tobson_state_t *state) +{ + char *chars; + char *escaped_chars = NULL; + unsigned int length; + err_t error = ERROR_NONE; + + if (state->shared->data_ptr != NULL) + { + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + chars = *(char **)state->shared->data_ptr; + } + else + { + chars = va_arg(*state->shared->vl, char *); + } + + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &length)) + { + debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", + state->additional_type_info)); + goto cleanup; + } + } + else + { + if (state->shared->read_length_from_string) + { + length = 0; + } + else + { + length = state->shared->array_length; + } + } + if ((error = tobson_escape_special_chars(&escaped_chars, chars, &length)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = memwriter_printf(state->memwriter, "\"%.*s\"", length, escaped_chars)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = tobson_stringify_string_value(state->memwriter, escaped_chars)) != ERROR_NONE) + { + goto cleanup; + } + state->shared->wrote_output = 1; + + if (state->shared->data_ptr != NULL) + { + state->shared->data_ptr = ((char **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(char *); + } + +cleanup: + free(escaped_chars); + return error; +} + +err_t tobson_stringify_string_value(memwriter_t *memwriter, char *value) +{ + char *escaped_chars = NULL; + unsigned int length = 0; + err_t error = ERROR_NONE; + char *length_as_bytes; + + if ((error = tobson_escape_special_chars(&escaped_chars, value, &length))) + { + goto cleanup; + } + int_to_bytes(length + 1, &length_as_bytes); /* plus one for the null byte */ + if ((error = memwriter_puts_with_len(memwriter, length_as_bytes, sizeof(int))) != ERROR_NONE) + { + goto cleanup; + } + if ((error = memwriter_printf(memwriter, "%s", escaped_chars)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = memwriter_putc(memwriter, null)) != ERROR_NONE) + { + goto cleanup; + } + +cleanup: + free(escaped_chars); + free(length_as_bytes); + return error; +} + +err_t tobson_stringify_bool_value(memwriter_t *memwriter, int value) +{ + return memwriter_putc(memwriter, value ? (char)0x01 : (char)0x00); +} + +err_t tobson_stringify_object(tobson_state_t *state) +{ + char **member_names = NULL; + char **data_types = NULL; + char **member_name_ptr; + char **data_type_ptr; + int has_members; + err_t error = ERROR_NONE; + + /* IMPORTANT: additional_type_info is altered after the unzip call! */ + if ((error = tobson_unzip_membernames_and_datatypes(state->additional_type_info, &member_names, &data_types)) != + ERROR_NONE) + { + goto cleanup; + } + member_name_ptr = member_names; + data_type_ptr = data_types; + + has_members = + (member_name_ptr != NULL && *member_name_ptr != NULL && data_type_ptr != NULL && *data_type_ptr != NULL); + /* write object start */ + if (!state->add_data_without_separator) + { + if (!state->shared->add_data) + { + ++(state->shared->struct_nested_level); + } + } + /* `add_data` is only relevant for the first object start; reset it to default afterwards since nested objects can + * follow*/ + state->shared->add_data = 0; + if (has_members) + { + /* write object content */ + int serialized_all_members = 0; + while (!serialized_all_members) + { + /* Datatype */ + if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte[**data_type_ptr])) != ERROR_NONE) + { + goto cleanup; + } + /* Key */ + if ((error = memwriter_printf(state->memwriter, "%s", *member_name_ptr)) != ERROR_NONE) + { + goto cleanup; + } + /* End Of Key */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + goto cleanup; + } + /* Values */ + if ((error = tobson_serialize(state->memwriter, *data_type_ptr, NULL, NULL, -1, -1, 0, NULL, NULL, + state->shared)) != ERROR_NONE) + { + goto cleanup; + } + ++member_name_ptr; + ++data_type_ptr; + if (*member_name_ptr == NULL || *data_type_ptr == NULL) + { + serialized_all_members = 1; + } + } + } + /* write object end if the type info is complete */ + if (!state->is_type_info_incomplete) + { + --(state->shared->struct_nested_level); + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + goto cleanup; + } + } + /* Only set serial result if not set before */ + if (state->shared->serial_result == 0 && state->is_type_info_incomplete) + { + state->shared->serial_result = has_members ? incomplete : incomplete_at_struct_beginning; + } + +cleanup: + free(member_names); + free(data_types); + if (error != ERROR_NONE) + { + return error; + } + + state->shared->wrote_output = 1; + + return ERROR_NONE; +} + +err_t tobson_stringify_args_value(memwriter_t *memwriter, grm_args_t *args) +{ + err_t error = ERROR_NONE; + + tobson_permanent_state.serial_result = incomplete_at_struct_beginning; + if ((error = tobson_write_args(memwriter, args)) != ERROR_NONE) + { + return error; + } + + return ERROR_NONE; +} + +int tobson_get_member_count(const char *data_desc) +{ + int nested_level = 0; + int member_count = 0; + if (data_desc == NULL || *data_desc == '\0') + { + return 0; + } + while (*data_desc != 0) + { + switch (*data_desc) + { + case '(': + ++nested_level; + break; + case ')': + --nested_level; + break; + case ',': + ++member_count; + break; + default: + break; + } + ++data_desc; + } + ++member_count; /* add last member (because it is not terminated by a ',') */ + return member_count; +} + +int tobson_is_bson_array_needed(const char *data_desc) +{ + const char *relevant_data_types = "iIdDcCs"; + int nested_level = 0; + int count_relevant_data_types = 0; + + while (*data_desc != 0 && count_relevant_data_types < 2) + { + if (*data_desc == '(') + { + ++nested_level; + } + else if (*data_desc == ')') + { + --nested_level; + } + else if (nested_level == 0 && strchr(relevant_data_types, *data_desc)) + { + ++count_relevant_data_types; + } + ++data_desc; + } + return count_relevant_data_types >= 2; +} + +void tobson_read_datatype(tobson_state_t *state) +{ + char *additional_type_info = NULL; + state->current_data_type = *state->data_type_ptr; + ++(state->data_type_ptr); + if (*state->data_type_ptr == '(') + { + int nested_level = 1; + additional_type_info = ++(state->data_type_ptr); + while (*state->data_type_ptr != 0 && nested_level > 0) + { + if (*state->data_type_ptr == '(') + { + ++nested_level; + } + else if (*state->data_type_ptr == ')') + { + --nested_level; + } + if (nested_level > 0) + { + ++(state->data_type_ptr); + } + } + if (*state->data_type_ptr != 0) + { + *(state->data_type_ptr)++ = 0; /* termination character for additional_type_info */ + state->is_type_info_incomplete = 0; + } + else + { + state->is_type_info_incomplete = 1; /* character search hit '\0' and not ')' */ + } + } + state->additional_type_info = additional_type_info; +} + +err_t tobson_skip_bytes(tobson_state_t *state) +{ + unsigned int count; + + if (state->shared->data_ptr == NULL) + { + debug_print_error(("Skipping bytes is not supported when using the variable argument list and is ignored.\n")); + return ERROR_NONE; + } + + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &count)) + { + debug_print_error(("Byte skipping with an invalid number -> ignoring.\n")); + return ERROR_NONE; + } + } + else + { + count = 1; + } + state->shared->data_ptr = ((char *)state->shared->data_ptr) + count; + state->shared->data_offset += count; + + return ERROR_NONE; +} + +err_t tobson_close_object(tobson_state_t *state) +{ + err_t error; + --(state->shared->struct_nested_level); + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) + { + return error; + } + return ERROR_NONE; +} + +err_t tobson_read_array_length(tobson_state_t *state) +{ + int value; + + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(size_t); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((size_t *)state->shared->data_ptr); + state->shared->data_ptr = ((size_t *)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(size_t); + } + else + { + value = va_arg(*state->shared->vl, size_t); + } + state->shared->array_length = value; + + return ERROR_NONE; +} + +err_t tobson_unzip_membernames_and_datatypes(char *mixed_ptr, char ***member_name_ptr, char ***data_type_ptr) +{ + int member_count; + char **arrays[2]; + + member_count = tobson_get_member_count(mixed_ptr); + /* add 1 to member count for a terminatory NULL pointer */ + *member_name_ptr = malloc((member_count + 1) * sizeof(char *)); + *data_type_ptr = malloc((member_count + 1) * sizeof(char *)); + if (*member_name_ptr == NULL || *data_type_ptr == NULL) + { + free(*member_name_ptr); + free(*data_type_ptr); + *member_name_ptr = *data_type_ptr = NULL; + debug_print_malloc_error(); + return ERROR_MALLOC; + } + arrays[member_name] = *member_name_ptr; + arrays[data_type] = *data_type_ptr; + if (member_count > 0) + { + char separators[2] = {':', ','}; + int current_array_index = member_name; + int nested_type_level = 0; + *arrays[current_array_index]++ = mixed_ptr; + + /* iterate over the whole type list */ + assert(mixed_ptr != NULL); /* otherwise there is an internal logical error since member_count > 0 */ + while (nested_type_level >= 0 && *mixed_ptr != 0) + { + /* extract one name or one type */ + while (*mixed_ptr != 0 && (nested_type_level > 0 || *mixed_ptr != separators[current_array_index])) + { + if (current_array_index == data_type) + { + if (*mixed_ptr == '(') + { + ++nested_type_level; + } + else if (*mixed_ptr == ')') + { + --nested_type_level; + } + } + if (nested_type_level >= 0) + { + ++mixed_ptr; + } + } + if (*mixed_ptr != 0) + { + *mixed_ptr++ = 0; /* terminate string in buffer */ + current_array_index = 1 - current_array_index; /* alternate between member_name (0) and data_type (1) */ + *arrays[current_array_index]++ = mixed_ptr; + } + } + } + *arrays[member_name] = NULL; + *arrays[data_type] = NULL; + return ERROR_NONE; +} + +err_t tobson_escape_special_chars(char **escaped_string, const char *unescaped_string, unsigned int *length) +{ + /* characters '\' and '"' must be escaped before written to a bson string value */ + /* length can be `0` -> use `strlen(unescaped_string)` instead */ + const char *src_ptr; + char *dest_ptr; + size_t needed_memory; + unsigned int remaining_chars; + unsigned int len; + const char *chars_to_escape = "\\\""; + + len = (length != NULL && *length != 0) ? *length : strlen(unescaped_string); + needed_memory = len + 1; /* reserve memory for the terminating `\0` character */ + src_ptr = unescaped_string; + remaining_chars = len; + while (remaining_chars) + { + if (strchr(chars_to_escape, *src_ptr) != NULL) + { + ++needed_memory; + } + ++src_ptr; + --remaining_chars; + } + dest_ptr = malloc(needed_memory); + if (dest_ptr == NULL) + { + return ERROR_MALLOC; + } + *escaped_string = dest_ptr; + src_ptr = unescaped_string; + remaining_chars = len; + while (remaining_chars) + { + if (strchr(chars_to_escape, *src_ptr) != NULL) + { + *dest_ptr++ = '\\'; + } + *dest_ptr++ = *src_ptr++; + --remaining_chars; + } + *dest_ptr = '\0'; + if (length != NULL) + { + *length = needed_memory - 1; + } + + return ERROR_NONE; +} + +err_t tobson_serialize(memwriter_t *memwriter, char *data_desc, const void *data, va_list *vl, int apply_padding, + int add_data, int add_data_without_separator, unsigned int *struct_nested_level, + tojson_serialization_result_t *serial_result, tobson_shared_state_t *shared_state) +{ + /** + * memwriter: memwriter handle + * data_desc: data description + * i: int + * I: int array -> I(count) or nI for variable length (see 'n' below) + * d: double + * D: double array + * c: char + * C: char array (fixed-width string) + * s: string ('\0'-terminated) + * n: array length (for all following arrays) + * o: object -> o(name:type, name:type, ...) + * e: empty byte (ignored memory) -> e(count) to specify multiple bytes + * data: pointer to the buffer that shall be serialized + * vl: if data is NULL the needed values are read from the va_list vl + */ + + tobson_state_t state; + int bson_array_needed = 0; + int allocated_shared_state_mem = 0; + err_t error = ERROR_NONE; + + state.memwriter = memwriter; + state.data_type_ptr = data_desc; + state.current_data_type = 0; + state.additional_type_info = NULL; + state.add_data_without_separator = add_data_without_separator; + state.is_type_info_incomplete = 0; + if (shared_state == NULL) + { + shared_state = malloc(sizeof(tobson_shared_state_t)); + if (shared_state == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + shared_state->apply_padding = apply_padding; + shared_state->array_length = 0; + shared_state->read_length_from_string = 0; + shared_state->data_ptr = data; + shared_state->vl = vl; + shared_state->data_offset = 0; + shared_state->wrote_output = 0; + shared_state->add_data = add_data; + shared_state->serial_result = 0; + shared_state->struct_nested_level = *struct_nested_level; + allocated_shared_state_mem = 1; + } + else + { + if (data != NULL) + { + shared_state->data_ptr = data; + } + if (vl != NULL) + { + shared_state->vl = vl; + } + if (apply_padding >= 0) + { + shared_state->apply_padding = apply_padding; + } + } + state.shared = shared_state; + + bson_array_needed = tobson_is_bson_array_needed(data_desc); + /* write list head if needed */ + while (*state.data_type_ptr != 0) + { + shared_state->wrote_output = 0; + tobson_read_datatype(&state); + if (tobson_datatype_to_func[(unsigned char)state.current_data_type]) + { + error = tobson_datatype_to_func[(unsigned char)state.current_data_type](&state); + } + else + { + debug_print_error(("WARNING: '%c' (ASCII code %d) is not a valid type identifier\n", state.current_data_type, + state.current_data_type)); + error = ERROR_UNSUPPORTED_DATATYPE; + } + if (error != ERROR_NONE) + { + goto cleanup; + } + } + + if (serial_result != NULL) + { + /* check if shared_state->serial_result was set before */ + if (shared_state->serial_result) + { + *serial_result = shared_state->serial_result; + } + else + { + *serial_result = (shared_state->struct_nested_level > 0) ? incomplete : complete; + } + } + if (struct_nested_level != NULL) + { + *struct_nested_level = shared_state->struct_nested_level; + } + +cleanup: + if (allocated_shared_state_mem) + { + free(shared_state); + } + + return error; +} + +void tobson_init_static_variables(void) +{ + if (!tobson_static_variables_initialized) + { + tobson_datatype_to_func['n'] = tobson_read_array_length; + tobson_datatype_to_func['e'] = tobson_skip_bytes; + tobson_datatype_to_func['i'] = tobson_stringify_int; + tobson_datatype_to_func['I'] = tobson_stringify_int_array; + tobson_datatype_to_func['d'] = tobson_stringify_double; + tobson_datatype_to_func['D'] = tobson_stringify_double_array; + tobson_datatype_to_func['c'] = tobson_stringify_char; + tobson_datatype_to_func['C'] = tobson_stringify_char_array; + tobson_datatype_to_func['s'] = tobson_stringify_string; + tobson_datatype_to_func['S'] = tobson_stringify_string_array; + tobson_datatype_to_func['b'] = tobson_stringify_bool; + tobson_datatype_to_func['B'] = tobson_stringify_bool_array; + tobson_datatype_to_func['o'] = tobson_stringify_object; + tobson_datatype_to_func['a'] = tobson_stringify_args; + tobson_datatype_to_func['A'] = tobson_stringify_args_array; + tobson_datatype_to_func[')'] = tobson_close_object; + + tobson_datatype_to_byte['d'] = 0x01; + tobson_datatype_to_byte['s'] = 0x02; + tobson_datatype_to_byte['c'] = 0x02; + tobson_datatype_to_byte['a'] = 0x03; + tobson_datatype_to_byte['n'] = 0x04; + tobson_datatype_to_byte['b'] = 0x08; + tobson_datatype_to_byte['i'] = 0x10; /* 32 bit integer */ + + tobson_static_variables_initialized = 1; + } +} + +err_t tobson_init_variables(int *add_data, int *add_data_without_separator, char **_data_desc, const char *data_desc) +{ + tobson_init_static_variables(); + *add_data = (tobson_permanent_state.serial_result != complete); + *add_data_without_separator = (tobson_permanent_state.serial_result == incomplete_at_struct_beginning); + if (*add_data) + { + char *data_desc_ptr; + int data_desc_len = strlen(data_desc); + *_data_desc = malloc(data_desc_len + 3); + if (*_data_desc == NULL) + { + debug_print_malloc_error(); + return ERROR_MALLOC; + } + data_desc_ptr = *_data_desc; + if (strncmp(data_desc, "o(", 2) != 0) + { + memcpy(data_desc_ptr, "o(", 2); + data_desc_ptr += 2; + } + memcpy(data_desc_ptr, data_desc, data_desc_len); + data_desc_ptr += data_desc_len; + *data_desc_ptr = '\0'; + } + else + { + *_data_desc = gks_strdup(data_desc); + if (*_data_desc == NULL) + { + debug_print_malloc_error(); + return ERROR_MALLOC; + } + } + + return ERROR_NONE; +} + +err_t tobson_write_vl(memwriter_t *memwriter, const char *data_desc, va_list *vl) +{ + int add_data, add_data_without_separator; + char *_data_desc; + err_t error; + + error = tobson_init_variables(&add_data, &add_data_without_separator, &_data_desc, data_desc); + if (!error) + { + error = + tobson_serialize(memwriter, _data_desc, NULL, vl, 0, add_data, add_data_without_separator, + &tobson_permanent_state.struct_nested_level, &tobson_permanent_state.serial_result, NULL); + } + free(_data_desc); + + return error; +} + +err_t tobson_write_buf(memwriter_t *memwriter, const char *data_desc, const void *buffer, int apply_padding) +{ + int add_data, add_data_without_separator; + char *_data_desc; + err_t error; + + error = tobson_init_variables(&add_data, &add_data_without_separator, &_data_desc, data_desc); + if (!error) + { + error = + tobson_serialize(memwriter, _data_desc, buffer, NULL, apply_padding, add_data, add_data_without_separator, + &tobson_permanent_state.struct_nested_level, &tobson_permanent_state.serial_result, NULL); + } + free(_data_desc); + + return error; +} + +err_t tobson_write_arg(memwriter_t *memwriter, const arg_t *arg) +{ + err_t error = ERROR_NONE; + + if (arg->key == NULL) + { + if ((error = tobson_write_buf(memwriter, arg->value_format, arg->value_ptr, 1)) != ERROR_NONE) + { + return error; + } + } + else + { + char *format, *format_ptr; + size_t key_length, value_format_length; + key_length = strlen(arg->key); + value_format_length = strlen(arg->value_format); + format = malloc(key_length + value_format_length + 2); /* 2 = 1 ':' + 1 '\0' */ + if (format == NULL) + { + debug_print_malloc_error(); + return ERROR_MALLOC; + } + format_ptr = format; + memcpy(format_ptr, arg->key, key_length); + format_ptr += key_length; + *format_ptr++ = ':'; + memcpy(format_ptr, arg->value_format, value_format_length); + format_ptr += value_format_length; + *format_ptr = '\0'; + if ((error = tobson_write_buf(memwriter, format, arg->value_ptr, 1)) != ERROR_NONE) + { + free(format); + return error; + } + free(format); + } + + return error; +} + +err_t tobson_write_args(memwriter_t *memwriter, const grm_args_t *args) +{ + const char *key_hierarchy_name; + grm_args_iterator_t *it; + arg_t *arg; + err_t error; + char *length_as_bytes; + char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; + int size_before = memwriter->size; + char *placeholder_pos = (memwriter->buf) + (memwriter->size); + + it = grm_args_iter(args); + if ((arg = it->next(it))) + { + /* Placeholder for length of object */ + if ((error = memwriter_puts_with_len(memwriter, length_placeholder, 4)) != ERROR_NONE) + { + return error; + } + + tobson_write_buf(memwriter, "o(", NULL, 1); + do + { + tobson_write_arg(memwriter, arg); + } + while ((arg = it->next(it))); + tobson_write_buf(memwriter, ")", NULL, 1); + + /* Set length of object */ + int_to_bytes(memwriter->size - size_before, &length_as_bytes); + memcpy(placeholder_pos, length_as_bytes, 4); + free(length_as_bytes); + } + args_iterator_delete(it); + + return 0; +} + +int tobson_is_complete(void) +{ + return tobson_permanent_state.serial_result == complete; +} + +int tobson_struct_nested_level(void) +{ + return tobson_permanent_state.struct_nested_level; +} diff --git a/lib/grm/src/grm/bson_int.h b/lib/grm/src/grm/bson_int.h new file mode 100644 index 000000000..b18ea8c24 --- /dev/null +++ b/lib/grm/src/grm/bson_int.h @@ -0,0 +1,161 @@ +#ifndef GRM_BSON_INT_H_INCLUDED +#define GRM_BSON_INT_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +/* ######################### includes ############################################################################### */ + +#include + +#include +#include +#include "memwriter_int.h" +#include "json_int.h" + + +/* ######################### internal interface ##################################################################### */ + +/* ========================= global varibales ======================================================================= */ + +/* ========================= macros ================================================================================= */ + +/* ========================= datatypes ============================================================================== */ + +/* ------------------------- bson deserializer ---------------------------------------------------------------------- */ + +typedef struct +{ + int length; + int num_bytes_read_before; + int num_elements; +} frombson_array_infos_t; + +typedef struct +{ + int length; + int num_bytes_read_before; +} frombson_object_infos_t; + +typedef struct +{ + grm_args_t *args; + const char *cur_byte; + int num_read_bytes; + char cur_value_format; + void *cur_value_buf; + const char *cur_key; + frombson_array_infos_t *array_infos; + frombson_object_infos_t *object_infos; +} frombson_state_t; + + +/* ------------------------- bson serializer ------------------------------------------------------------------------ */ + +typedef err_t (*tobson_post_processing_callback_t)(memwriter_t *, unsigned int, const char *); + +typedef struct +{ + int apply_padding; + size_t array_length; + int read_length_from_string; + const void *data_ptr; + va_list *vl; + int data_offset; + int wrote_output; + int add_data; + tojson_serialization_result_t serial_result; + unsigned int struct_nested_level; +} tobson_shared_state_t; + +typedef struct +{ + memwriter_t *memwriter; + char *data_type_ptr; + char current_data_type; + char *additional_type_info; + int is_type_info_incomplete; + int add_data_without_separator; + tobson_shared_state_t *shared; +} tobson_state_t; + +typedef struct +{ + tojson_serialization_result_t serial_result; + unsigned int struct_nested_level; +} tobson_permanent_state_t; + +/* ========================= methods ================================================================================ */ + +void revmemcpy(void *dest, const void *src, size_t len); + +char byte_to_type(const char *byte); + +void int_to_bytes(int i, char **bytes); + +void bytes_to_int(int *i, const char *bytes); + +void double_to_bytes(double d, char **bytes); + +void bytes_to_double(double *d, const char *bytes); + +/* ------------------------- bson deserializer ---------------------------------------------------------------------- */ + +err_t frombson_read(grm_args_t *args, const char *bson_bytes); +err_t frombson_read_value_format(frombson_state_t *state, char *value_format); +err_t frombson_read_key(frombson_state_t *state, const char **key); +err_t frombson_skip_key(frombson_state_t *state); +err_t frombson_read_length(frombson_state_t *state, int *length); +err_t frombson_read_double_value(frombson_state_t *state, double *d); +err_t frombson_read_int_value(frombson_state_t *state, int *i); +err_t frombson_read_string_value(frombson_state_t *state, const char **s); +err_t frombson_read_bool_value(frombson_state_t *state, int *b); +err_t frombson_read_object(frombson_state_t *state); +err_t frombson_parse_double(frombson_state_t *state); +err_t frombson_parse_int(frombson_state_t *state); +err_t frombson_parse_bool(frombson_state_t *state); +err_t frombson_parse_array(frombson_state_t *state); +err_t frombson_parse_object(frombson_state_t *state); +err_t frombson_read_int_array(frombson_state_t *state); +err_t frombson_read_double_array(frombson_state_t *state); +err_t frombson_read_string_array(frombson_state_t *state); +err_t frombson_read_bool_array(frombson_state_t *state); +void frombson_init_static_variables(void); + +/* ------------------------- bson serializer ------------------------------------------------------------------------ */ + +err_t tobson_stringify_int_value(memwriter_t *memwriter, int value); +err_t tobson_stringify_char_value(memwriter_t *memwriter, char value); +err_t tobson_stringify_double_value(memwriter_t *memwriter, double value); +err_t tobson_stringify_string_value(memwriter_t *memwriter, char *value); +err_t tobson_stringify_bool_value(memwriter_t *memwriter, int value); +err_t tobson_stringify_args_value(memwriter_t *memwriter, grm_args_t *args); + +err_t tobson_read_array_length(tobson_state_t *state); +err_t tobson_skip_bytes(tobson_state_t *state); +err_t tobson_stringify_object(tobson_state_t *state); +err_t tobson_close_object(tobson_state_t *state); + +int tobson_get_member_count(const char *data_desc); +int tobson_is_bson_array_needed(const char *data_desc); +void tobson_read_datatype(tobson_state_t *state); +err_t tobson_unzip_membernames_and_datatypes(char *mixed_ptr, char ***member_name_ptr, char ***data_type_ptr); +err_t tobson_escape_special_chars(char **escaped_string, const char *unescaped_string, unsigned int *length); +err_t tobson_serialize(memwriter_t *memwriter, char *data_desc, const void *data, va_list *vl, int apply_padding, + int add_data, int add_data_without_separator, unsigned int *struct_nested_level, + tojson_serialization_result_t *serial_result, tobson_shared_state_t *shared_state); +void tobson_init_static_variables(void); +err_t tobson_init_variables(int *add_data, int *add_data_without_separator, char **_data_desc, const char *data_desc); +err_t tobson_write_vl(memwriter_t *memwriter, const char *data_desc, va_list *vl); +err_t tobson_write_buf(memwriter_t *memwriter, const char *data_desc, const void *buffer, int apply_padding); +err_t tobson_write_arg(memwriter_t *memwriter, const arg_t *arg); +err_t tobson_write_args(memwriter_t *memwriter, const grm_args_t *args); +int tobson_is_complete(void); +int tobson_struct_nested_level(void); + + +#ifdef __cplusplus +} +#endif +#endif /* ifndef GRM_BSON_INT_H_INCLUDED */ diff --git a/lib/grm/src/grm/dump.c b/lib/grm/src/grm/dump.c index d37eb95bd..46e794f7f 100644 --- a/lib/grm/src/grm/dump.c +++ b/lib/grm/src/grm/dump.c @@ -12,6 +12,7 @@ #include "dump_int.h" #include "json_int.h" +#include "bson_int.h" #include "memwriter_int.h" #include "plot_int.h" @@ -299,6 +300,74 @@ void grm_dump_json(const grm_args_t *args, FILE *f) } } +void grm_dump_bson(const grm_args_t *args, FILE *f) +{ + static memwriter_t *memwriter = NULL; + char *buf; + int length; + + if (memwriter == NULL) + { + memwriter = memwriter_new(); + } + tobson_write_args(memwriter, args); + if (tobson_is_complete()) + { + memwriter_putc(memwriter, '\0'); + + buf = memwriter_buf(memwriter); + + bytes_to_int(&length, buf); + + while (length > 0) + { + fprintf(f, "\\x%x ", *(buf)); + length--; + ++buf; + } + memwriter_delete(memwriter); + memwriter = NULL; + } +} + +void grm_dump_bson_and_parse(const grm_args_t *args, FILE *f) +{ + static memwriter_t *memwriter = NULL; + char *buf; + int length; + grm_args_t *new_args = grm_args_new(); + + if (memwriter == NULL) + { + memwriter = memwriter_new(); + } + tobson_write_args(memwriter, args); + if (tojson_is_complete()) + { + memwriter_putc(memwriter, '\0'); + + buf = memwriter_buf(memwriter); + + bytes_to_int(&length, buf); + + while (length > 0) + { + fprintf(f, "\\x%x ", (unsigned char)*(buf)); + length--; + ++buf; + } + fprintf(f, "\n"); + + buf = memwriter_buf(memwriter); + frombson_read(new_args, buf); + + grm_dump(new_args, f); + grm_args_delete(new_args); + memwriter_delete(memwriter); + memwriter = NULL; + } +} + char *grm_dump_json_str(void) { static memwriter_t *memwriter = NULL; diff --git a/lib/grm/src/grm/memwriter.c b/lib/grm/src/grm/memwriter.c index 67f4233fd..c13ef2d99 100644 --- a/lib/grm/src/grm/memwriter.c +++ b/lib/grm/src/grm/memwriter.c @@ -173,6 +173,22 @@ err_t memwriter_puts(memwriter_t *memwriter, const char *s) return memwriter_printf(memwriter, "%s", s); } +err_t memwriter_puts_with_len(memwriter_t *memwriter, char *s, size_t length) +{ + err_t error = ERROR_NONE; + + while (length > 0) + { + if ((error = memwriter_putc(memwriter, *(s++))) != ERROR_NONE) + { + return error; + } + --length; + } + + return error; +} + err_t memwriter_putc(memwriter_t *memwriter, char c) { return memwriter_printf(memwriter, "%c", c); diff --git a/lib/grm/src/grm/memwriter_int.h b/lib/grm/src/grm/memwriter_int.h index e40aef1a2..0da22f6ae 100644 --- a/lib/grm/src/grm/memwriter_int.h +++ b/lib/grm/src/grm/memwriter_int.h @@ -51,6 +51,7 @@ err_t memwriter_enlarge_buf(memwriter_t *memwriter, size_t size_increment); err_t memwriter_ensure_buf(memwriter_t *memwriter, size_t needed_additional_size); err_t memwriter_printf(memwriter_t *memwriter, const char *format, ...); err_t memwriter_puts(memwriter_t *memwriter, const char *s); +err_t memwriter_puts_with_len(memwriter_t *memwriter, char *s, size_t length); err_t memwriter_putc(memwriter_t *memwriter, char c); char *memwriter_buf(const memwriter_t *memwriter); size_t memwriter_size(const memwriter_t *memwriter); diff --git a/lib/grm/test/public_api/grm/CMakeLists.txt b/lib/grm/test/public_api/grm/CMakeLists.txt index 1e8cfa978..7a0f3676c 100644 --- a/lib/grm/test/public_api/grm/CMakeLists.txt +++ b/lib/grm/test/public_api/grm/CMakeLists.txt @@ -9,6 +9,7 @@ project( set(EXECUTABLE_SOURCES bar_errorbar.c barplot.cxx + bson_serialize_deserialize.c custom_receiver.c custom_sender.c dom_render.cxx diff --git a/lib/grm/test/public_api/grm/bson_serialize_deserialize.c b/lib/grm/test/public_api/grm/bson_serialize_deserialize.c new file mode 100644 index 000000000..c0a0cd7b6 --- /dev/null +++ b/lib/grm/test/public_api/grm/bson_serialize_deserialize.c @@ -0,0 +1,53 @@ +#include + +int main(void) +{ + int i = 1986; + double d = 5.05; + char c = 'y'; + int y_i[] = {1986, 1986, 1986}; + double y_d[] = {5.05, 5.05, 5.05}; + char *y_s[] = {"eins", "zwei", "drei"}; + char y_c[] = {'a', 'b', 'c', '\0'}; + grm_args_t *args, *subarg, *subargs[2]; + + args = grm_args_new(); + subarg = grm_args_new(); + subargs[0] = grm_args_new(); + subargs[1] = grm_args_new(); + + /* + * grm_args_push(args, "hello", "s", "world"); + * grm_args_push(args, "y", "i", i); + * grm_args_push(args, "y", "d", d); + * grm_args_push(args, "y", "c", c); + * + * grm_args_push(args, "y", "nI", sizeof(y_i) / sizeof(y_i[0]), y_i); + * grm_args_push(args, "y", "nD", sizeof(y_d) / sizeof(y_d[0]), y_d); + * grm_args_push(args, "y", "nS", sizeof(y_s) / sizeof(y_s[0]), y_s); + * grm_args_push(args, "y", "nC", 4, y_c); + * + * grm_args_push(subarg, "hello", "s", "world"); + * grm_args_push(args, "sub", "a", subarg); + * + * grm_args_push(subargs[0], "hello", "s", "world"); + * grm_args_push(subargs[1], "y", "d", d); + * grm_args_push(args, "subs", "nA", 2, subargs); + */ + + grm_args_push(subargs[0], "eins", "s", "world"); + grm_args_push(subargs[0], "zwei", "s", "world"); + grm_args_push(subargs[1], "x", "d", d); + grm_args_push(subargs[1], "y", "d", d); + grm_args_push(args, "subs", "nA", 2, subargs); + + grm_dump(args, stdout); + /* + * grm_dump_json(args, stdout); + * grm_dump_bson(args, stdout); + */ + grm_dump_bson_and_parse(args, stdout); + /* grm_args_delete(args); */ + + return 0; +} From 717a6adabfdd1adc0050872c165abcf29e86d172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20K=C3=B6pke?= Date: Thu, 28 Jan 2021 15:06:14 +0100 Subject: [PATCH 29/48] [BSON] Optimize the BSON implementation This commit now uses a custom array type which is more efficient but breaks with the BSON spec (since GRM arrays are always homegenous). --- lib/grm/src/grm/bson.c | 1039 +++++++++++++++++++------------ lib/grm/src/grm/bson_int.h | 37 +- lib/grm/src/grm/dump.c | 2 +- lib/grm/src/grm/memwriter.c | 37 ++ lib/grm/src/grm/memwriter_int.h | 2 + 5 files changed, 720 insertions(+), 397 deletions(-) diff --git a/lib/grm/src/grm/bson.c b/lib/grm/src/grm/bson.c index 8f7567423..e8b93d876 100644 --- a/lib/grm/src/grm/bson.c +++ b/lib/grm/src/grm/bson.c @@ -19,8 +19,8 @@ /* ========================= macros ================================================================================= */ -const int i = 1; -#define is_bigendian() ((*(char *)&i) == 0) +#define is_little_endian() (*(char *)&(int){1}) +#define opt_arrays 1 /* ------------------------- general -------------------------------------------------------------------------------- */ @@ -46,7 +46,7 @@ static char tobson_datatype_to_byte[128]; static char null = 0x00; -/* ========================= methods ================================================================================ */ +/* ========================= small helper functions ================================================================= */ void revmemcpy(void *dest, const void *src, size_t len) { @@ -58,6 +58,23 @@ void revmemcpy(void *dest, const void *src, size_t len) } } +void memcpy_rev_chunks(void *dest, const void *src, size_t len, size_t chunk_size) +{ + + char *d = dest; + const char *s = src; + int i; + int j; + + for (i = 0; i < len; i += chunk_size) + { + for (j = 0; j < chunk_size; j++) + { + d[i + chunk_size - j - 1] = s[i + j]; + } + } +} + char byte_to_type(const char *byte) { switch (*byte) @@ -70,6 +87,8 @@ char byte_to_type(const char *byte) return 'a'; case (char)0x04: return 'n'; + case (char)0x05: + return 'x'; case (char)0x08: return 'b'; case (char)0x10: @@ -82,54 +101,53 @@ char byte_to_type(const char *byte) void int_to_bytes(int i, char **bytes) { *bytes = (char *)malloc(sizeof(int) * sizeof(char)); - if (is_bigendian()) + if (is_little_endian()) { - revmemcpy(*bytes, &i, sizeof(i)); + memcpy(*bytes, &i, sizeof(i)); } else { - memcpy(*bytes, &i, sizeof(i)); + revmemcpy(*bytes, &i, sizeof(i)); } } void bytes_to_int(int *i, const char *bytes) { - if (is_bigendian()) + if (is_little_endian()) { - revmemcpy(i, bytes, sizeof(int)); + memcpy(i, bytes, sizeof(int)); } else { - memcpy(i, bytes, sizeof(int)); + revmemcpy(i, bytes, sizeof(int)); } } void double_to_bytes(double d, char **bytes) { *bytes = (char *)malloc(sizeof(double) * sizeof(char)); - if (is_bigendian()) + if (is_little_endian()) { - revmemcpy(*bytes, &d, sizeof(d)); + memcpy(*bytes, &d, sizeof(d)); } else { - memcpy(*bytes, &d, sizeof(d)); + revmemcpy(*bytes, &d, sizeof(d)); } } void bytes_to_double(double *d, const char *bytes) { - if (is_bigendian()) + if (is_little_endian()) { - revmemcpy(d, bytes, sizeof(double)); + memcpy(d, bytes, sizeof(double)); } else { - memcpy(d, bytes, sizeof(double)); + revmemcpy(d, bytes, sizeof(double)); } } - /* ------------------------- bson deserializer ---------------------------------------------------------------------- */ err_t frombson_read(grm_args_t *args, const char *bson_bytes) @@ -216,6 +234,7 @@ err_t frombson_read_length(frombson_state_t *state, int *length) return ERROR_NONE; } + err_t frombson_read_double_value(frombson_state_t *state, double *d) { @@ -312,6 +331,7 @@ err_t frombson_read_object(frombson_state_t *state) return error; } + err_t frombson_parse_double(frombson_state_t *state) { @@ -523,6 +543,99 @@ err_t frombson_parse_array(frombson_state_t *state) return error; } +err_t frombson_parse_optimized_array(frombson_state_t *state) +{ + int length, num_elements, elem_size; + char value_type; + char final_value_type[3] = "\0"; + frombson_array_infos_t array_infos; + err_t error; + int memory_allocated = 0; + int array_closed = 0; + + final_value_type[0] = 'n'; + if ((error = frombson_read_key(state, &(state->cur_key))) != ERROR_NONE) + { + goto cleanup; + } + + if ((error = frombson_read_length(state, &length)) != ERROR_NONE) + { + goto cleanup; + } + + /* checking subtype */ + if (*state->cur_byte != (char)0x80) + { + error = ERROR_UNSUPPORTED_DATATYPE; + goto cleanup; + } + state->cur_byte++; + state->num_read_bytes++; + + /* read value type */ + if ((error = frombson_read_value_format(state, &value_type)) != ERROR_NONE) + { + goto cleanup; + } + final_value_type[1] = toupper(value_type); + switch (value_type) + { + case 'i': + elem_size = 4; + break; + case 'd': + elem_size = 8; + break; + } + + state->cur_value_buf = malloc(length - 7); /* array length minus length, subtype, valuetype, null */ + if (state->cur_value_buf == NULL) + { + debug_print_malloc_error(); + goto cleanup; + } + memory_allocated = 1; + num_elements = (length - 7) / elem_size; + + /* copy values*/ + if (is_little_endian()) + { + memcpy(state->cur_value_buf, state->cur_byte, num_elements * elem_size); + } + else + { + memcpy_rev_chunks(state->cur_value_buf, state->cur_byte, num_elements * elem_size, elem_size); + } + state->cur_byte += num_elements * elem_size; + state->num_read_bytes += num_elements * elem_size; + + /* check for the end */ + if (*(state->cur_byte) == '\0') + { + state->num_read_bytes++; + state->cur_byte++; + array_closed = 1; + } + + + if (!array_closed) + { + error = ERROR_PARSE_ARRAY; + goto cleanup; + } + + grm_args_push(state->args, state->cur_key, final_value_type, num_elements, state->cur_value_buf); + +cleanup: + if (memory_allocated) + { + free(state->cur_value_buf); + } + + return error; +} + err_t frombson_parse_object(frombson_state_t *state) { @@ -557,7 +670,10 @@ err_t frombson_parse_object(frombson_state_t *state) inner_state.cur_value_buf = NULL; inner_state.object_infos = &object_infos; - frombson_read_object(&inner_state); + if ((error = frombson_read_object(&inner_state)) != ERROR_NONE) + { + return error; + } state->num_read_bytes = inner_state.num_read_bytes; state->cur_byte = inner_state.cur_byte; @@ -567,6 +683,7 @@ err_t frombson_parse_object(frombson_state_t *state) return error; } + err_t frombson_read_int_array(frombson_state_t *state) { @@ -577,8 +694,7 @@ err_t frombson_read_int_array(frombson_state_t *state) int memory_allocated = 0; frombson_array_infos_t *array_infos = state->array_infos; - array_infos->num_elements = (array_infos->length - 5) / (sizeof(int) + 3); - state->cur_value_buf = malloc(sizeof(int) * array_infos->num_elements); + state->cur_value_buf = malloc(array_infos->length - 4); /* array length minus length bytes */ if (state->cur_value_buf == NULL) { debug_print_malloc_error(); @@ -620,6 +736,8 @@ err_t frombson_read_int_array(frombson_state_t *state) } } + array_infos->num_elements = i; + if (!array_closed) { error = ERROR_PARSE_ARRAY; @@ -644,8 +762,7 @@ err_t frombson_read_double_array(frombson_state_t *state) int memory_allocated = 0; frombson_array_infos_t *array_infos = state->array_infos; - array_infos->num_elements = (array_infos->length - 5) / (sizeof(double) + 3); - state->cur_value_buf = malloc(sizeof(double) * array_infos->num_elements); + state->cur_value_buf = malloc(array_infos->length - 4); /* array length minus length bytes */ if (state->cur_value_buf == NULL) { debug_print_malloc_error(); @@ -687,6 +804,8 @@ err_t frombson_read_double_array(frombson_state_t *state) } } + array_infos->num_elements = i; + if (!array_closed) { error = ERROR_PARSE_ARRAY; @@ -784,8 +903,7 @@ err_t frombson_read_bool_array(frombson_state_t *state) int memory_allocated = 0; frombson_array_infos_t *array_infos = state->array_infos; - array_infos->num_elements = (array_infos->length - 5) / (sizeof(char) + 3); - state->cur_value_buf = malloc(sizeof(int) * array_infos->num_elements); + state->cur_value_buf = malloc(array_infos->length - 4); /* array length minus length bytes */ if (state->cur_value_buf == NULL) { debug_print_malloc_error(); @@ -827,6 +945,8 @@ err_t frombson_read_bool_array(frombson_state_t *state) } } + array_infos->num_elements = i; + if (!array_closed) { error = ERROR_PARSE_ARRAY; @@ -931,6 +1051,7 @@ err_t frombson_read_object_array(frombson_state_t *state) return error; } + void frombson_init_static_variables(void) { if (!frombson_static_variables_initialized) @@ -946,15 +1067,109 @@ void frombson_init_static_variables(void) frombson_datatype_to_func['B'] = frombson_read_bool_array; frombson_datatype_to_func['a'] = frombson_parse_object; frombson_datatype_to_func['A'] = frombson_read_object_array; + frombson_datatype_to_func['x'] = frombson_parse_optimized_array; frombson_static_variables_initialized = 1; } } - /* ------------------------- bson serializer ------------------------------------------------------------------------ */ -err_t tobson_stringify_int(tobson_state_t *state) +err_t tobson_int_value(memwriter_t *memwriter, int value) +{ + char *bytes; + err_t error; + + int_to_bytes(value, &bytes); + + error = memwriter_puts_with_len(memwriter, bytes, sizeof(int)); + free(bytes); + + return error; +} + +err_t tobson_double_value(memwriter_t *memwriter, double value) +{ + err_t error; + char *bytes; + + double_to_bytes(value, &bytes); + error = memwriter_puts_with_len(memwriter, bytes, sizeof(double)); + free(bytes); + + return error; +} + +err_t tobson_string_value(memwriter_t *memwriter, char *value) +{ + char *escaped_chars = NULL; + unsigned int length = 0; + err_t error = ERROR_NONE; + char *length_as_bytes; + + if ((error = tobson_escape_special_chars(&escaped_chars, value, &length))) + { + goto cleanup; + } + int_to_bytes(length + 1, &length_as_bytes); /* plus one for the null byte */ + if ((error = memwriter_puts_with_len(memwriter, length_as_bytes, sizeof(int))) != ERROR_NONE) + { + goto cleanup; + } + if ((error = memwriter_printf(memwriter, "%s", escaped_chars)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = memwriter_putc(memwriter, null)) != ERROR_NONE) + { + goto cleanup; + } + +cleanup: + free(escaped_chars); + free(length_as_bytes); + return error; +} + +err_t tobson_bool_value(memwriter_t *memwriter, int value) +{ + return memwriter_putc(memwriter, value ? (char)0x01 : (char)0x00); +} + +err_t tobson_char_value(memwriter_t *memwriter, char value) +{ + err_t error; + char length[4] = {(char)0x02, (char)0x00, (char)0x00, (char)0x00}; /*char is saved as string */ + if ((error = memwriter_puts_with_len(memwriter, length, 4)) != ERROR_NONE) + { + return error; + } + if ((error = memwriter_putc(memwriter, value)) != ERROR_NONE) + { + return error; + } + if ((error = memwriter_putc(memwriter, null)) != ERROR_NONE) + { + return error; + } + return ERROR_NONE; +} + +err_t tobson_args_value(memwriter_t *memwriter, grm_args_t *args) +{ + err_t error = ERROR_NONE; + + tobson_permanent_state.serial_result = incomplete_at_struct_beginning; + if ((error = tobson_write_args(memwriter, args)) != ERROR_NONE) + { + return error; + } + + return ERROR_NONE; +} + + +err_t tobson_int(tobson_state_t *state) { int value; err_t error = ERROR_NONE; @@ -974,7 +1189,7 @@ err_t tobson_stringify_int(tobson_state_t *state) { value = va_arg(*state->shared->vl, int); } - if ((error = tobson_stringify_int_value(state->memwriter, value)) != ERROR_NONE) + if ((error = tobson_int_value(state->memwriter, value)) != ERROR_NONE) { return error; } @@ -982,54 +1197,199 @@ err_t tobson_stringify_int(tobson_state_t *state) return error; } -err_t tobson_stringify_int_array(tobson_state_t *state) +err_t tobson_double(tobson_state_t *state) { - int *values; - int current_value; - unsigned int length; - int remaining_elements; + double value; err_t error = ERROR_NONE; - char *length_as_bytes; - char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; - int size_before = state->memwriter->size; - char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); - char key = '0'; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(double); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } if (state->shared->data_ptr != NULL) { - if (state->shared->data_ptr != NULL && state->shared->apply_padding) - { - ptrdiff_t needed_padding = state->shared->data_offset % sizeof(int *); - state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; - state->shared->data_offset += needed_padding; - } - values = *(int **)state->shared->data_ptr; + value = *((double *)state->shared->data_ptr); + state->shared->data_ptr = ((double *)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(double); } else { - values = va_arg(*state->shared->vl, int *); + value = va_arg(*state->shared->vl, double); } - if (state->additional_type_info != NULL) + if ((error = tobson_double_value(state->memwriter, value)) != ERROR_NONE) { - if (!str_to_uint(state->additional_type_info, &length)) - { - debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", - state->additional_type_info)); - length = 0; - } + return error; + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_char(tobson_state_t *state) +{ + char value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((char *)state->shared->data_ptr); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(char); } else { - length = state->shared->array_length; + value = va_arg(*state->shared->vl, int); } - remaining_elements = length; - /* write array start */ - if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) + if ((error = tobson_char_value(state->memwriter, value)) != ERROR_NONE) { return error; } - /* write array content */ - while (remaining_elements) - { + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_string(tobson_state_t *state) +{ + char *value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((char **)state->shared->data_ptr); + state->shared->data_ptr = ((char **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(char *); + } + else + { + value = va_arg(*state->shared->vl, char *); + } + if ((error = tobson_string_value(state->memwriter, value)) != ERROR_NONE) + { + return error; + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_bool(tobson_state_t *state) +{ + int value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(int); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((int *)state->shared->data_ptr); + state->shared->data_ptr = ((int *)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(int); + } + else + { + value = va_arg(*state->shared->vl, int); + } + if ((error = tobson_bool_value(state->memwriter, value)) != ERROR_NONE) + { + return error; + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_args(tobson_state_t *state) +{ + grm_args_t *value; + err_t error = ERROR_NONE; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(grm_args_t *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + if (state->shared->data_ptr != NULL) + { + value = *((grm_args_t **)state->shared->data_ptr); + state->shared->data_ptr = ((grm_args_t **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(grm_args_t *); + } + else + { + value = va_arg(*state->shared->vl, grm_args_t *); + } + if ((error = tobson_args_value(state->memwriter, value)) != ERROR_NONE) + { + return error; + } + state->shared->wrote_output = 1; + return error; +} + + +err_t tobson_int_array(tobson_state_t *state) +{ + int *values; + int current_value; + unsigned int length; + int remaining_elements; + err_t error = ERROR_NONE; + char *length_as_bytes; + char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; + int size_before = state->memwriter->size; + char *placeholder_pos; + int key_num = 0; + char *key; + size_t num_digits; + if (state->shared->data_ptr != NULL) + { + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(int *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + values = *(int **)state->shared->data_ptr; + } + else + { + values = va_arg(*state->shared->vl, int *); + } + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &length)) + { + debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", + state->additional_type_info)); + length = 0; + } + } + else + { + length = state->shared->array_length; + } + remaining_elements = length; + num_digits = log10(length) + 2; /* Max key length plus null byte */ + key = (char *)malloc(num_digits); + /* write array start */ + if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) + { + return error; + } + /* write array content */ + while (remaining_elements) + { current_value = *values++; /* Datatype */ if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte['i'])) != ERROR_NONE) @@ -1037,7 +1397,8 @@ err_t tobson_stringify_int_array(tobson_state_t *state) return error; } /* Key */ - if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + sprintf(key, "%d", key_num++); + if ((error = memwriter_puts(state->memwriter, key)) != ERROR_NONE) { return error; } @@ -1047,7 +1408,7 @@ err_t tobson_stringify_int_array(tobson_state_t *state) return error; } /* Value */ - if ((error = tobson_stringify_int_value(state->memwriter, current_value)) != ERROR_NONE) + if ((error = tobson_int_value(state->memwriter, current_value)) != ERROR_NONE) { return error; } @@ -1060,8 +1421,10 @@ err_t tobson_stringify_int_array(tobson_state_t *state) } /* Set length of object*/ int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + placeholder_pos = state->memwriter->buf + size_before; memcpy(placeholder_pos, length_as_bytes, 4); free(length_as_bytes); + free(key); if (state->shared->data_ptr != NULL) { state->shared->data_ptr = ((int **)state->shared->data_ptr) + 1; @@ -1071,49 +1434,7 @@ err_t tobson_stringify_int_array(tobson_state_t *state) return error; } -err_t tobson_stringify_int_value(memwriter_t *memwriter, int value) -{ - char *bytes; - err_t error; - - int_to_bytes(value, &bytes); - - error = memwriter_puts_with_len(memwriter, bytes, sizeof(int)); - free(bytes); - - return error; -} - - -err_t tobson_stringify_double(tobson_state_t *state) -{ - double value; - err_t error = ERROR_NONE; - if (state->shared->data_ptr != NULL && state->shared->apply_padding) - { - ptrdiff_t needed_padding = state->shared->data_offset % sizeof(double); - state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; - state->shared->data_offset += needed_padding; - } - if (state->shared->data_ptr != NULL) - { - value = *((double *)state->shared->data_ptr); - state->shared->data_ptr = ((double *)state->shared->data_ptr) + 1; - state->shared->data_offset += sizeof(double); - } - else - { - value = va_arg(*state->shared->vl, double); - } - if ((error = tobson_stringify_double_value(state->memwriter, value)) != ERROR_NONE) - { - return error; - } - state->shared->wrote_output = 1; - return error; -} - -err_t tobson_stringify_double_array(tobson_state_t *state) +err_t tobson_double_array(tobson_state_t *state) { double *values; double current_value; @@ -1123,8 +1444,10 @@ err_t tobson_stringify_double_array(tobson_state_t *state) char *length_as_bytes; char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; int size_before = state->memwriter->size; - char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); - char key = '0'; + char *placeholder_pos; + int key_num = 0; + size_t num_digits; + char *key; if (state->shared->data_ptr != NULL) { if (state->shared->data_ptr != NULL && state->shared->apply_padding) @@ -1153,6 +1476,8 @@ err_t tobson_stringify_double_array(tobson_state_t *state) length = state->shared->array_length; } remaining_elements = length; + num_digits = log10(length) + 2; /* Max key length plus null byte */ + key = (char *)malloc(num_digits); /* write array start */ /* Placeholder for length of array*/ if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) @@ -1169,7 +1494,8 @@ err_t tobson_stringify_double_array(tobson_state_t *state) return error; } /* Key */ - if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + sprintf(key, "%d", key_num++); + if ((error = memwriter_putc(state->memwriter, *key)) != ERROR_NONE) { return error; } @@ -1179,7 +1505,7 @@ err_t tobson_stringify_double_array(tobson_state_t *state) return error; } /* Value */ - if ((error = tobson_stringify_double_value(state->memwriter, current_value)) != ERROR_NONE) + if ((error = tobson_double_value(state->memwriter, current_value)) != ERROR_NONE) { return error; } @@ -1192,8 +1518,10 @@ err_t tobson_stringify_double_array(tobson_state_t *state) } /* Set length of object*/ int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + placeholder_pos = state->memwriter->buf + size_before; memcpy(placeholder_pos, length_as_bytes, 4); free(length_as_bytes); + free(key); if (state->shared->data_ptr != NULL) { state->shared->data_ptr = ((double **)state->shared->data_ptr) + 1; @@ -1203,84 +1531,176 @@ err_t tobson_stringify_double_array(tobson_state_t *state) return error; } - -err_t tobson_stringify_char(tobson_state_t *state) +err_t tobson_optimized_array(tobson_state_t *state) { - char value; + double *values; + double current_value; + unsigned int length; + int total_length; + char *total_length_as_bytes; err_t error = ERROR_NONE; - if (state->shared->data_ptr != NULL && state->shared->apply_padding) + int elem_size; + if (state->shared->data_ptr != NULL) { - ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char); - state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; - state->shared->data_offset += needed_padding; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(double *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + values = *(double **)state->shared->data_ptr; } - if (state->shared->data_ptr != NULL) + else { - value = *((char *)state->shared->data_ptr); - state->shared->data_ptr = ((char *)state->shared->data_ptr) + 1; - state->shared->data_offset += sizeof(char); + values = va_arg(*state->shared->vl, double *); + } + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &length)) + { + debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", + state->additional_type_info)); + length = 0; + } } else { - value = va_arg(*state->shared->vl, int); + length = state->shared->array_length; + } + /* length(4 bytes) + datatype user defined binary(1 byte) + datatype of values(1 byte) + nullbyte(1 byte) */ + switch (tolower(state->current_data_type)) + { + case 'i': + elem_size = 4; + break; + case 'd': + elem_size = 8; + break; } - if ((error = tobson_stringify_char_value(state->memwriter, value)) != ERROR_NONE) + total_length = 7 + length * elem_size; + int_to_bytes(total_length, &total_length_as_bytes); + + /* total Length */ + if ((error = memwriter_puts_with_len(state->memwriter, total_length_as_bytes, 4)) != ERROR_NONE) { return error; } - state->shared->wrote_output = 1; - return error; -} -err_t tobson_stringify_char_value(memwriter_t *memwriter, char value) -{ - err_t error; - char length[4] = {(char)0x02, (char)0x00, (char)0x00, (char)0x00}; /*char is saved as string */ - if ((error = memwriter_puts_with_len(memwriter, length, 4)) != ERROR_NONE) + + /* datatype (user defined binary) */ + if ((error = memwriter_putc(state->memwriter, 0x80)) != ERROR_NONE) { return error; } - if ((error = memwriter_putc(memwriter, value)) != ERROR_NONE) + + + /* datatype of values */ + if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte[tolower(state->current_data_type)])) != + ERROR_NONE) { return error; } - if ((error = memwriter_putc(memwriter, null)) != ERROR_NONE) + + /* values */ + if (is_little_endian()) + { + if ((error = memwriter_memcpy(state->memwriter, values, length * elem_size)) != ERROR_NONE) + { + return error; + } + } + else + { + if ((error = memwriter_memcpy_rev_chunks(state->memwriter, values, length * elem_size, elem_size)) != ERROR_NONE) + { + return error; + } + } + /* write array end */ + if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) { return error; } - return ERROR_NONE; -} -err_t tobson_stringify_string(tobson_state_t *state) + free(total_length_as_bytes); + if (state->shared->data_ptr != NULL) + { + state->shared->data_ptr = ((double **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(double *); + } + state->shared->wrote_output = 1; + return error; +} + +err_t tobson_char_array(tobson_state_t *state) { - char *value; + char *chars; + char *escaped_chars = NULL; + unsigned int length; err_t error = ERROR_NONE; - if (state->shared->data_ptr != NULL && state->shared->apply_padding) + + if (state->shared->data_ptr != NULL) { - ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char *); - state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; - state->shared->data_offset += needed_padding; + if (state->shared->data_ptr != NULL && state->shared->apply_padding) + { + ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char *); + state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; + state->shared->data_offset += needed_padding; + } + chars = *(char **)state->shared->data_ptr; } - if (state->shared->data_ptr != NULL) + else { - value = *((char **)state->shared->data_ptr); - state->shared->data_ptr = ((char **)state->shared->data_ptr) + 1; - state->shared->data_offset += sizeof(char *); + chars = va_arg(*state->shared->vl, char *); + } + + if (state->additional_type_info != NULL) + { + if (!str_to_uint(state->additional_type_info, &length)) + { + debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", + state->additional_type_info)); + goto cleanup; + } } else { - value = va_arg(*state->shared->vl, char *); + if (state->shared->read_length_from_string) + { + length = 0; + } + else + { + length = state->shared->array_length; + } } - if ((error = tobson_stringify_string_value(state->memwriter, value)) != ERROR_NONE) + if ((error = tobson_escape_special_chars(&escaped_chars, chars, &length)) != ERROR_NONE) { - return error; + goto cleanup; + } + if ((error = memwriter_printf(state->memwriter, "\"%.*s\"", length, escaped_chars)) != ERROR_NONE) + { + goto cleanup; + } + if ((error = tobson_string_value(state->memwriter, escaped_chars)) != ERROR_NONE) + { + goto cleanup; } state->shared->wrote_output = 1; + + if (state->shared->data_ptr != NULL) + { + state->shared->data_ptr = ((char **)state->shared->data_ptr) + 1; + state->shared->data_offset += sizeof(char *); + } + +cleanup: + free(escaped_chars); return error; } -err_t tobson_stringify_string_array(tobson_state_t *state) +err_t tobson_string_array(tobson_state_t *state) { char **values; char *current_value; @@ -1290,8 +1710,10 @@ err_t tobson_stringify_string_array(tobson_state_t *state) char *length_as_bytes; char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; int size_before = state->memwriter->size; - char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); - char key = '0'; + char *placeholder_pos; + int key_num = 0; + size_t num_digits; + char *key; if (state->shared->data_ptr != NULL) { if (state->shared->data_ptr != NULL && state->shared->apply_padding) @@ -1320,6 +1742,8 @@ err_t tobson_stringify_string_array(tobson_state_t *state) length = state->shared->array_length; } remaining_elements = length; + num_digits = log10(length) + 2; /* Max key length plus null byte */ + key = (char *)malloc(num_digits); /* write array start */ /* Placeholder for length of array*/ if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) @@ -1336,7 +1760,8 @@ err_t tobson_stringify_string_array(tobson_state_t *state) return error; } /* Key */ - if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + sprintf(key, "%d", key_num++); + if ((error = memwriter_puts(state->memwriter, key)) != ERROR_NONE) { return error; } @@ -1346,7 +1771,7 @@ err_t tobson_stringify_string_array(tobson_state_t *state) return error; } /* Value */ - if ((error = tobson_stringify_string_value(state->memwriter, current_value)) != ERROR_NONE) + if ((error = tobson_string_value(state->memwriter, current_value)) != ERROR_NONE) { return error; } @@ -1358,8 +1783,10 @@ err_t tobson_stringify_string_array(tobson_state_t *state) } /* Set length of object*/ int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + placeholder_pos = state->memwriter->buf + size_before; memcpy(placeholder_pos, length_as_bytes, 4); free(length_as_bytes); + free(key); if (state->shared->data_ptr != NULL) { state->shared->data_ptr = ((char ***)state->shared->data_ptr) + 1; @@ -1369,35 +1796,7 @@ err_t tobson_stringify_string_array(tobson_state_t *state) return error; } -err_t tobson_stringify_bool(tobson_state_t *state) -{ - int value; - err_t error = ERROR_NONE; - if (state->shared->data_ptr != NULL && state->shared->apply_padding) - { - ptrdiff_t needed_padding = state->shared->data_offset % sizeof(int); - state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; - state->shared->data_offset += needed_padding; - } - if (state->shared->data_ptr != NULL) - { - value = *((int *)state->shared->data_ptr); - state->shared->data_ptr = ((int *)state->shared->data_ptr) + 1; - state->shared->data_offset += sizeof(int); - } - else - { - value = va_arg(*state->shared->vl, int); - } - if ((error = tobson_stringify_bool_value(state->memwriter, value)) != ERROR_NONE) - { - return error; - } - state->shared->wrote_output = 1; - return error; -} - -err_t tobson_stringify_bool_array(tobson_state_t *state) +err_t tobson_bool_array(tobson_state_t *state) { int *values; int current_value; @@ -1407,8 +1806,10 @@ err_t tobson_stringify_bool_array(tobson_state_t *state) char *length_as_bytes; char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; int size_before = state->memwriter->size; - char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); - char key = '0'; + char *placeholder_pos; + int key_num = 0; + size_t num_digits; + char *key; if (state->shared->data_ptr != NULL) { if (state->shared->data_ptr != NULL && state->shared->apply_padding) @@ -1437,6 +1838,8 @@ err_t tobson_stringify_bool_array(tobson_state_t *state) length = state->shared->array_length; } remaining_elements = length; + num_digits = log10(length) + 2; /* Max key length plus null byte */ + key = (char *)malloc(num_digits); /* write array start */ /* Placeholder for length of array*/ if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) @@ -1453,7 +1856,8 @@ err_t tobson_stringify_bool_array(tobson_state_t *state) return error; } /* Key */ - if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + sprintf(key, "%d", key_num++); + if ((error = memwriter_puts(state->memwriter, key)) != ERROR_NONE) { return error; } @@ -1463,7 +1867,7 @@ err_t tobson_stringify_bool_array(tobson_state_t *state) return error; } /* Value */ - if ((error = tobson_stringify_bool_value(state->memwriter, current_value)) != ERROR_NONE) + if ((error = tobson_bool_value(state->memwriter, current_value)) != ERROR_NONE) { return error; } @@ -1477,8 +1881,10 @@ err_t tobson_stringify_bool_array(tobson_state_t *state) } /* Set length of object*/ int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + placeholder_pos = state->memwriter->buf + size_before; memcpy(placeholder_pos, length_as_bytes, 4); free(length_as_bytes); + free(key); if (state->shared->data_ptr != NULL) { state->shared->data_ptr = ((int **)state->shared->data_ptr) + 1; @@ -1488,35 +1894,7 @@ err_t tobson_stringify_bool_array(tobson_state_t *state) return error; } -err_t tobson_stringify_args(tobson_state_t *state) -{ - grm_args_t *value; - err_t error = ERROR_NONE; - if (state->shared->data_ptr != NULL && state->shared->apply_padding) - { - ptrdiff_t needed_padding = state->shared->data_offset % sizeof(grm_args_t *); - state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; - state->shared->data_offset += needed_padding; - } - if (state->shared->data_ptr != NULL) - { - value = *((grm_args_t **)state->shared->data_ptr); - state->shared->data_ptr = ((grm_args_t **)state->shared->data_ptr) + 1; - state->shared->data_offset += sizeof(grm_args_t *); - } - else - { - value = va_arg(*state->shared->vl, grm_args_t *); - } - if ((error = tobson_stringify_args_value(state->memwriter, value)) != ERROR_NONE) - { - return error; - } - state->shared->wrote_output = 1; - return error; -} - -err_t tobson_stringify_args_array(tobson_state_t *state) +err_t tobson_args_array(tobson_state_t *state) { grm_args_t **values; grm_args_t *current_value; @@ -1526,8 +1904,10 @@ err_t tobson_stringify_args_array(tobson_state_t *state) char *length_as_bytes; char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; int size_before = state->memwriter->size; - char *placeholder_pos = (state->memwriter->buf) + (state->memwriter->size); - char key = '0'; + char *placeholder_pos; + int key_num = 0; + size_t num_digits; + char *key; if (state->shared->data_ptr != NULL) { if (state->shared->data_ptr != NULL && state->shared->apply_padding) @@ -1556,6 +1936,8 @@ err_t tobson_stringify_args_array(tobson_state_t *state) length = state->shared->array_length; } remaining_elements = length; + num_digits = log10(length) + 2; /* Max key length plus null byte */ + key = (char *)malloc(num_digits); /* write array start */ /* Placeholder for length of array*/ if ((error = memwriter_puts_with_len(state->memwriter, length_placeholder, 4)) != ERROR_NONE) @@ -1573,7 +1955,8 @@ err_t tobson_stringify_args_array(tobson_state_t *state) return error; } /* Key */ - if ((error = memwriter_putc(state->memwriter, key++)) != ERROR_NONE) + sprintf(key, "%d", key_num++); + if ((error = memwriter_puts(state->memwriter, key)) != ERROR_NONE) { return error; } @@ -1583,7 +1966,7 @@ err_t tobson_stringify_args_array(tobson_state_t *state) return error; } /* Value */ - if ((error = tobson_stringify_args_value(state->memwriter, current_value)) != ERROR_NONE) + if ((error = tobson_args_value(state->memwriter, current_value)) != ERROR_NONE) { return error; } @@ -1596,9 +1979,10 @@ err_t tobson_stringify_args_array(tobson_state_t *state) } /* Set length of object*/ int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + placeholder_pos = state->memwriter->buf + size_before; memcpy(placeholder_pos, length_as_bytes, 4); free(length_as_bytes); - + free(key); if (state->shared->data_ptr != NULL) { state->shared->data_ptr = ((grm_args_t ***)state->shared->data_ptr) + 1; @@ -1608,122 +1992,8 @@ err_t tobson_stringify_args_array(tobson_state_t *state) return error; } -err_t tobson_stringify_double_value(memwriter_t *memwriter, double value) -{ - err_t error; - char *bytes; - - double_to_bytes(value, &bytes); - error = memwriter_puts_with_len(memwriter, bytes, sizeof(double)); - free(bytes); - - return error; -} - -err_t tobson_stringify_char_array(tobson_state_t *state) -{ - char *chars; - char *escaped_chars = NULL; - unsigned int length; - err_t error = ERROR_NONE; - - if (state->shared->data_ptr != NULL) - { - if (state->shared->data_ptr != NULL && state->shared->apply_padding) - { - ptrdiff_t needed_padding = state->shared->data_offset % sizeof(char *); - state->shared->data_ptr = ((char *)state->shared->data_ptr) + needed_padding; - state->shared->data_offset += needed_padding; - } - chars = *(char **)state->shared->data_ptr; - } - else - { - chars = va_arg(*state->shared->vl, char *); - } - - if (state->additional_type_info != NULL) - { - if (!str_to_uint(state->additional_type_info, &length)) - { - debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", - state->additional_type_info)); - goto cleanup; - } - } - else - { - if (state->shared->read_length_from_string) - { - length = 0; - } - else - { - length = state->shared->array_length; - } - } - if ((error = tobson_escape_special_chars(&escaped_chars, chars, &length)) != ERROR_NONE) - { - goto cleanup; - } - if ((error = memwriter_printf(state->memwriter, "\"%.*s\"", length, escaped_chars)) != ERROR_NONE) - { - goto cleanup; - } - if ((error = tobson_stringify_string_value(state->memwriter, escaped_chars)) != ERROR_NONE) - { - goto cleanup; - } - state->shared->wrote_output = 1; - - if (state->shared->data_ptr != NULL) - { - state->shared->data_ptr = ((char **)state->shared->data_ptr) + 1; - state->shared->data_offset += sizeof(char *); - } - -cleanup: - free(escaped_chars); - return error; -} - -err_t tobson_stringify_string_value(memwriter_t *memwriter, char *value) -{ - char *escaped_chars = NULL; - unsigned int length = 0; - err_t error = ERROR_NONE; - char *length_as_bytes; - - if ((error = tobson_escape_special_chars(&escaped_chars, value, &length))) - { - goto cleanup; - } - int_to_bytes(length + 1, &length_as_bytes); /* plus one for the null byte */ - if ((error = memwriter_puts_with_len(memwriter, length_as_bytes, sizeof(int))) != ERROR_NONE) - { - goto cleanup; - } - if ((error = memwriter_printf(memwriter, "%s", escaped_chars)) != ERROR_NONE) - { - goto cleanup; - } - if ((error = memwriter_putc(memwriter, null)) != ERROR_NONE) - { - goto cleanup; - } - -cleanup: - free(escaped_chars); - free(length_as_bytes); - return error; -} - -err_t tobson_stringify_bool_value(memwriter_t *memwriter, int value) -{ - return memwriter_putc(memwriter, value ? (char)0x01 : (char)0x00); -} -err_t tobson_stringify_object(tobson_state_t *state) +err_t tobson_object(tobson_state_t *state) { char **member_names = NULL; char **data_types = NULL; @@ -1738,6 +2008,7 @@ err_t tobson_stringify_object(tobson_state_t *state) { goto cleanup; } + member_name_ptr = member_names; data_type_ptr = data_types; @@ -1760,6 +2031,18 @@ err_t tobson_stringify_object(tobson_state_t *state) int serialized_all_members = 0; while (!serialized_all_members) { + /* check for optimization of arrays */ + if (opt_arrays) + { + if (**data_type_ptr == 'n') + { + /* only optimize double and int */ + if (strchr("DI", *((*data_type_ptr) + 1))) + { + **data_type_ptr = 'x'; + } + } + } /* Datatype */ if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte[**data_type_ptr])) != ERROR_NONE) { @@ -1817,19 +2100,6 @@ err_t tobson_stringify_object(tobson_state_t *state) return ERROR_NONE; } -err_t tobson_stringify_args_value(memwriter_t *memwriter, grm_args_t *args) -{ - err_t error = ERROR_NONE; - - tobson_permanent_state.serial_result = incomplete_at_struct_beginning; - if ((error = tobson_write_args(memwriter, args)) != ERROR_NONE) - { - return error; - } - - return ERROR_NONE; -} - int tobson_get_member_count(const char *data_desc) { int nested_level = 0; @@ -1860,31 +2130,6 @@ int tobson_get_member_count(const char *data_desc) return member_count; } -int tobson_is_bson_array_needed(const char *data_desc) -{ - const char *relevant_data_types = "iIdDcCs"; - int nested_level = 0; - int count_relevant_data_types = 0; - - while (*data_desc != 0 && count_relevant_data_types < 2) - { - if (*data_desc == '(') - { - ++nested_level; - } - else if (*data_desc == ')') - { - --nested_level; - } - else if (nested_level == 0 && strchr(relevant_data_types, *data_desc)) - { - ++count_relevant_data_types; - } - ++data_desc; - } - return count_relevant_data_types >= 2; -} - void tobson_read_datatype(tobson_state_t *state) { char *additional_type_info = NULL; @@ -1986,6 +2231,7 @@ err_t tobson_read_array_length(tobson_state_t *state) return ERROR_NONE; } + err_t tobson_unzip_membernames_and_datatypes(char *mixed_ptr, char ***member_name_ptr, char ***data_type_ptr) { int member_count; @@ -2120,7 +2366,6 @@ err_t tobson_serialize(memwriter_t *memwriter, char *data_desc, const void *data */ tobson_state_t state; - int bson_array_needed = 0; int allocated_shared_state_mem = 0; err_t error = ERROR_NONE; @@ -2167,7 +2412,6 @@ err_t tobson_serialize(memwriter_t *memwriter, char *data_desc, const void *data } state.shared = shared_state; - bson_array_needed = tobson_is_bson_array_needed(data_desc); /* write list head if needed */ while (*state.data_type_ptr != 0) { @@ -2221,20 +2465,36 @@ void tobson_init_static_variables(void) { tobson_datatype_to_func['n'] = tobson_read_array_length; tobson_datatype_to_func['e'] = tobson_skip_bytes; - tobson_datatype_to_func['i'] = tobson_stringify_int; - tobson_datatype_to_func['I'] = tobson_stringify_int_array; - tobson_datatype_to_func['d'] = tobson_stringify_double; - tobson_datatype_to_func['D'] = tobson_stringify_double_array; - tobson_datatype_to_func['c'] = tobson_stringify_char; - tobson_datatype_to_func['C'] = tobson_stringify_char_array; - tobson_datatype_to_func['s'] = tobson_stringify_string; - tobson_datatype_to_func['S'] = tobson_stringify_string_array; - tobson_datatype_to_func['b'] = tobson_stringify_bool; - tobson_datatype_to_func['B'] = tobson_stringify_bool_array; - tobson_datatype_to_func['o'] = tobson_stringify_object; - tobson_datatype_to_func['a'] = tobson_stringify_args; - tobson_datatype_to_func['A'] = tobson_stringify_args_array; + tobson_datatype_to_func['i'] = tobson_int; + if (opt_arrays) + { + tobson_datatype_to_func['I'] = tobson_optimized_array; + } + else + { + tobson_datatype_to_func['I'] = tobson_int_array; + } + tobson_datatype_to_func['d'] = tobson_double; + if (opt_arrays) + { + tobson_datatype_to_func['D'] = tobson_optimized_array; + } + else + { + tobson_datatype_to_func['D'] = tobson_double_array; + } + tobson_datatype_to_func['c'] = tobson_char; + tobson_datatype_to_func['C'] = tobson_char_array; + tobson_datatype_to_func['s'] = tobson_string; + tobson_datatype_to_func['S'] = tobson_string_array; + tobson_datatype_to_func['b'] = tobson_bool; + tobson_datatype_to_func['B'] = tobson_bool_array; + tobson_datatype_to_func['o'] = tobson_object; + tobson_datatype_to_func['a'] = tobson_args; + tobson_datatype_to_func['A'] = tobson_args_array; tobson_datatype_to_func[')'] = tobson_close_object; + /* user defined datatype (optimized array) */ + tobson_datatype_to_func['x'] = tobson_read_array_length; tobson_datatype_to_byte['d'] = 0x01; tobson_datatype_to_byte['s'] = 0x02; @@ -2242,7 +2502,9 @@ void tobson_init_static_variables(void) tobson_datatype_to_byte['a'] = 0x03; tobson_datatype_to_byte['n'] = 0x04; tobson_datatype_to_byte['b'] = 0x08; - tobson_datatype_to_byte['i'] = 0x10; /* 32 bit integer */ + tobson_datatype_to_byte['i'] = 0x10; + /* binary (optimized array) */ + tobson_datatype_to_byte['x'] = 0x05; tobson_static_variables_initialized = 1; } @@ -2309,7 +2571,6 @@ err_t tobson_write_buf(memwriter_t *memwriter, const char *data_desc, const void int add_data, add_data_without_separator; char *_data_desc; err_t error; - error = tobson_init_variables(&add_data, &add_data_without_separator, &_data_desc, data_desc); if (!error) { @@ -2372,7 +2633,7 @@ err_t tobson_write_args(memwriter_t *memwriter, const grm_args_t *args) char *length_as_bytes; char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; int size_before = memwriter->size; - char *placeholder_pos = (memwriter->buf) + (memwriter->size); + char *placeholder_pos; it = grm_args_iter(args); if ((arg = it->next(it))) @@ -2384,6 +2645,7 @@ err_t tobson_write_args(memwriter_t *memwriter, const grm_args_t *args) } tobson_write_buf(memwriter, "o(", NULL, 1); + do { tobson_write_arg(memwriter, arg); @@ -2393,6 +2655,7 @@ err_t tobson_write_args(memwriter_t *memwriter, const grm_args_t *args) /* Set length of object */ int_to_bytes(memwriter->size - size_before, &length_as_bytes); + placeholder_pos = (memwriter->buf) + size_before; memcpy(placeholder_pos, length_as_bytes, 4); free(length_as_bytes); } diff --git a/lib/grm/src/grm/bson_int.h b/lib/grm/src/grm/bson_int.h index b18ea8c24..7c4377476 100644 --- a/lib/grm/src/grm/bson_int.h +++ b/lib/grm/src/grm/bson_int.h @@ -86,10 +86,12 @@ typedef struct unsigned int struct_nested_level; } tobson_permanent_state_t; -/* ========================= methods ================================================================================ */ +/* ========================= small helper functions ================================================================= */ void revmemcpy(void *dest, const void *src, size_t len); +void memcpy_rev_chunks(void *dest, const void *src, size_t len, size_t chunk_size); + char byte_to_type(const char *byte); void int_to_bytes(int i, char **bytes); @@ -107,34 +109,53 @@ err_t frombson_read_value_format(frombson_state_t *state, char *value_format); err_t frombson_read_key(frombson_state_t *state, const char **key); err_t frombson_skip_key(frombson_state_t *state); err_t frombson_read_length(frombson_state_t *state, int *length); + err_t frombson_read_double_value(frombson_state_t *state, double *d); err_t frombson_read_int_value(frombson_state_t *state, int *i); err_t frombson_read_string_value(frombson_state_t *state, const char **s); err_t frombson_read_bool_value(frombson_state_t *state, int *b); err_t frombson_read_object(frombson_state_t *state); + err_t frombson_parse_double(frombson_state_t *state); err_t frombson_parse_int(frombson_state_t *state); err_t frombson_parse_bool(frombson_state_t *state); err_t frombson_parse_array(frombson_state_t *state); err_t frombson_parse_object(frombson_state_t *state); + err_t frombson_read_int_array(frombson_state_t *state); err_t frombson_read_double_array(frombson_state_t *state); err_t frombson_read_string_array(frombson_state_t *state); err_t frombson_read_bool_array(frombson_state_t *state); + void frombson_init_static_variables(void); /* ------------------------- bson serializer ------------------------------------------------------------------------ */ -err_t tobson_stringify_int_value(memwriter_t *memwriter, int value); -err_t tobson_stringify_char_value(memwriter_t *memwriter, char value); -err_t tobson_stringify_double_value(memwriter_t *memwriter, double value); -err_t tobson_stringify_string_value(memwriter_t *memwriter, char *value); -err_t tobson_stringify_bool_value(memwriter_t *memwriter, int value); -err_t tobson_stringify_args_value(memwriter_t *memwriter, grm_args_t *args); +err_t tobson_int_value(memwriter_t *memwriter, int value); +err_t tobson_double_value(memwriter_t *memwriter, double value); +err_t tobson_char_value(memwriter_t *memwriter, char value); +err_t tobson_string_value(memwriter_t *memwriter, char *value); +err_t tobson_bool_value(memwriter_t *memwriter, int value); +err_t tobson_args_value(memwriter_t *memwriter, grm_args_t *args); + +err_t tobson_int(tobson_state_t *state); +err_t tobson_double(tobson_state_t *state); +err_t tobson_char(tobson_state_t *state); +err_t tobson_string(tobson_state_t *state); +err_t tobson_bool(tobson_state_t *state); +err_t tobson_args(tobson_state_t *state); + +err_t tobson_int_array(tobson_state_t *state); +err_t tobson_double_array(tobson_state_t *state); +err_t tobson_optimized_array(tobson_state_t *state); +err_t tobson_char_array(tobson_state_t *state); +err_t tobson_string_array(tobson_state_t *state); +err_t tobson_bool_array(tobson_state_t *state); +err_t tobson_args_array(tobson_state_t *state); err_t tobson_read_array_length(tobson_state_t *state); err_t tobson_skip_bytes(tobson_state_t *state); -err_t tobson_stringify_object(tobson_state_t *state); +err_t tobson_object(tobson_state_t *state); err_t tobson_close_object(tobson_state_t *state); int tobson_get_member_count(const char *data_desc); diff --git a/lib/grm/src/grm/dump.c b/lib/grm/src/grm/dump.c index 46e794f7f..30b7c131f 100644 --- a/lib/grm/src/grm/dump.c +++ b/lib/grm/src/grm/dump.c @@ -342,7 +342,7 @@ void grm_dump_bson_and_parse(const grm_args_t *args, FILE *f) memwriter = memwriter_new(); } tobson_write_args(memwriter, args); - if (tojson_is_complete()) + if (tobson_is_complete()) { memwriter_putc(memwriter, '\0'); diff --git a/lib/grm/src/grm/memwriter.c b/lib/grm/src/grm/memwriter.c index c13ef2d99..ba7b7eda8 100644 --- a/lib/grm/src/grm/memwriter.c +++ b/lib/grm/src/grm/memwriter.c @@ -194,6 +194,43 @@ err_t memwriter_putc(memwriter_t *memwriter, char c) return memwriter_printf(memwriter, "%c", c); } +err_t memwriter_memcpy(memwriter_t *memwriter, const void *source, size_t num) +{ + err_t error = ERROR_NONE; + + memwriter_ensure_buf(memwriter, num); + + memcpy(&memwriter->buf[memwriter->size], source, num); + + memwriter->size += num; + + return error; +} + +err_t memwriter_memcpy_rev_chunks(memwriter_t *memwriter, const void *source, size_t num, int chunk_size) +{ + err_t error = ERROR_NONE; + + memwriter_ensure_buf(memwriter, num); + + char *d = &memwriter->buf[memwriter->size]; + const char *s = source; + int i; + int j; + + for (i = 0; i < num; i += chunk_size) + { + for (j = 0; j < chunk_size; j++) + { + d[i + chunk_size - j - 1] = s[i + j]; + } + } + + memwriter->size += num; + + return error; +} + char *memwriter_buf(const memwriter_t *memwriter) { return memwriter->buf; diff --git a/lib/grm/src/grm/memwriter_int.h b/lib/grm/src/grm/memwriter_int.h index 0da22f6ae..6c9538db2 100644 --- a/lib/grm/src/grm/memwriter_int.h +++ b/lib/grm/src/grm/memwriter_int.h @@ -52,6 +52,8 @@ err_t memwriter_ensure_buf(memwriter_t *memwriter, size_t needed_additional_size err_t memwriter_printf(memwriter_t *memwriter, const char *format, ...); err_t memwriter_puts(memwriter_t *memwriter, const char *s); err_t memwriter_puts_with_len(memwriter_t *memwriter, char *s, size_t length); +err_t memwriter_memcpy(memwriter_t *memwriter, const void *source, size_t num); +err_t memwriter_memcpy_rev_chunks(memwriter_t *memwriter, const void *source, size_t num, int chunk_size); err_t memwriter_putc(memwriter_t *memwriter, char c); char *memwriter_buf(const memwriter_t *memwriter); size_t memwriter_size(const memwriter_t *memwriter); From b222987502854200c24ec978feea04e533883e2c Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Thu, 2 May 2024 17:03:50 +0200 Subject: [PATCH 30/48] [BSON] Always export objects with a length header --- CMakeLists.txt | 1 + js/Makefile | 1 + lib/grm/Makefile | 1 + lib/grm/makefile.mingw | 1 + lib/grm/src/grm/bson.c | 100 +++++++++++---------- lib/grm/src/grm/bson_int.h | 4 + lib/grm/src/grm/datatype/size_t_list.c | 31 +++++++ lib/grm/src/grm/datatype/size_t_list_int.h | 28 ++++++ 8 files changed, 121 insertions(+), 46 deletions(-) create mode 100644 lib/grm/src/grm/datatype/size_t_list.c create mode 100644 lib/grm/src/grm/datatype/size_t_list_int.h diff --git a/CMakeLists.txt b/CMakeLists.txt index aae8ad832..3e8ba8f6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -535,6 +535,7 @@ set(GRM_SOURCES lib/grm/src/grm/import.cxx lib/grm/src/grm/utilcpp.cxx lib/grm/src/grm/datatype/double_map.c + lib/grm/src/grm/datatype/size_t_list.c lib/grm/src/grm/datatype/string_array_map.c lib/grm/src/grm/datatype/string_list.c lib/grm/src/grm/datatype/string_map.c diff --git a/js/Makefile b/js/Makefile index 3ed83a28a..dcdbdad52 100755 --- a/js/Makefile +++ b/js/Makefile @@ -107,6 +107,7 @@ XERCESCDIR = ../3rdparty/xerces-c $(GRMDIR)/src/grm/utilcpp.o \ $(GRMDIR)/src/grm/import.o \ $(GRMDIR)/src/grm/datatype/double_map.o \ + $(GRMDIR)/src/grm/datatype/size_t_list.o \ $(GRMDIR)/src/grm/datatype/string_array_map.o \ $(GRMDIR)/src/grm/datatype/string_list.o \ $(GRMDIR)/src/grm/datatype/string_map.o \ diff --git a/lib/grm/Makefile b/lib/grm/Makefile index 990c2334b..a0fdb98de 100644 --- a/lib/grm/Makefile +++ b/lib/grm/Makefile @@ -30,6 +30,7 @@ UNAME := $(shell uname) src/grm/utilcpp.o \ src/grm/import.o \ src/grm/datatype/double_map.o \ + src/grm/datatype/size_t_list.o \ src/grm/datatype/string_array_map.o \ src/grm/datatype/string_list.o \ src/grm/datatype/string_map.o \ diff --git a/lib/grm/makefile.mingw b/lib/grm/makefile.mingw index c718a9b50..dc54f74d9 100644 --- a/lib/grm/makefile.mingw +++ b/lib/grm/makefile.mingw @@ -39,6 +39,7 @@ XERCESCLIBS = $(THIRDPARTYDIR)/lib/libxerces-c.a $(THIRDPARTYDIR)/lib/libicuuc.a src/grm/utilcpp.o \ src/grm/import.o \ src/grm/datatype/double_map.o \ + src/grm/datatype/size_t_list.o \ src/grm/datatype/string_array_map.o \ src/grm/datatype/string_list.o \ src/grm/datatype/string_map.o \ diff --git a/lib/grm/src/grm/bson.c b/lib/grm/src/grm/bson.c index e8b93d876..4f68b78fc 100644 --- a/lib/grm/src/grm/bson.c +++ b/lib/grm/src/grm/bson.c @@ -41,7 +41,7 @@ static int frombson_static_variables_initialized = 0; static err_t (*tobson_datatype_to_func[128])(tobson_state_t *); static int tobson_static_variables_initialized = 0; -static tobson_permanent_state_t tobson_permanent_state = {complete, 0}; +static tobson_permanent_state_t tobson_permanent_state = {complete, 0, NULL}; static char tobson_datatype_to_byte[128]; static char null = 0x00; @@ -1159,6 +1159,9 @@ err_t tobson_args_value(memwriter_t *memwriter, grm_args_t *args) { err_t error = ERROR_NONE; + /* write object start */ + tobson_open_object(memwriter); + tobson_permanent_state.serial_result = incomplete_at_struct_beginning; if ((error = tobson_write_args(memwriter, args)) != ERROR_NONE) { @@ -2003,11 +2006,8 @@ err_t tobson_object(tobson_state_t *state) err_t error = ERROR_NONE; /* IMPORTANT: additional_type_info is altered after the unzip call! */ - if ((error = tobson_unzip_membernames_and_datatypes(state->additional_type_info, &member_names, &data_types)) != - ERROR_NONE) - { - goto cleanup; - } + error = tobson_unzip_membernames_and_datatypes(state->additional_type_info, &member_names, &data_types); + cleanup_if_error; member_name_ptr = member_names; data_type_ptr = data_types; @@ -2019,7 +2019,9 @@ err_t tobson_object(tobson_state_t *state) { if (!state->shared->add_data) { + tobson_open_object(state->memwriter); ++(state->shared->struct_nested_level); + cleanup_if_error; } } /* `add_data` is only relevant for the first object start; reset it to default afterwards since nested objects can @@ -2044,26 +2046,17 @@ err_t tobson_object(tobson_state_t *state) } } /* Datatype */ - if ((error = memwriter_putc(state->memwriter, tobson_datatype_to_byte[**data_type_ptr])) != ERROR_NONE) - { - goto cleanup; - } + error = memwriter_putc(state->memwriter, tobson_datatype_to_byte[**data_type_ptr]); + cleanup_if_error; /* Key */ - if ((error = memwriter_printf(state->memwriter, "%s", *member_name_ptr)) != ERROR_NONE) - { - goto cleanup; - } + error = memwriter_printf(state->memwriter, "%s", *member_name_ptr); + cleanup_if_error; /* End Of Key */ - if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) - { - goto cleanup; - } + error = memwriter_putc(state->memwriter, null); + cleanup_if_error; /* Values */ - if ((error = tobson_serialize(state->memwriter, *data_type_ptr, NULL, NULL, -1, -1, 0, NULL, NULL, - state->shared)) != ERROR_NONE) - { - goto cleanup; - } + error = tobson_serialize(state->memwriter, *data_type_ptr, NULL, NULL, -1, -1, 0, NULL, NULL, state->shared); + cleanup_if_error; ++member_name_ptr; ++data_type_ptr; if (*member_name_ptr == NULL || *data_type_ptr == NULL) @@ -2075,11 +2068,8 @@ err_t tobson_object(tobson_state_t *state) /* write object end if the type info is complete */ if (!state->is_type_info_incomplete) { - --(state->shared->struct_nested_level); - if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) - { - goto cleanup; - } + error = tobson_close_object(state); + cleanup_if_error; } /* Only set serial result if not set before */ if (state->shared->serial_result == 0 && state->is_type_info_incomplete) @@ -2195,14 +2185,49 @@ err_t tobson_skip_bytes(tobson_state_t *state) return ERROR_NONE; } +err_t tobson_open_object(memwriter_t *memwriter) +{ + err_t error = ERROR_NONE; + + const char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; + if (tobson_permanent_state.memwriter_object_start_offset_stack == NULL) + { + tobson_permanent_state.memwriter_object_start_offset_stack = size_t_list_new(); + return_error_if(tobson_permanent_state.memwriter_object_start_offset_stack == NULL, ERROR_MALLOC); + } + size_t_list_push(tobson_permanent_state.memwriter_object_start_offset_stack, memwriter_size(memwriter)); + error = memwriter_puts_with_len(memwriter, (char *)length_placeholder, 4); + + return error; +} + err_t tobson_close_object(tobson_state_t *state) { err_t error; - --(state->shared->struct_nested_level); + + size_t size_before = size_t_list_pop(tobson_permanent_state.memwriter_object_start_offset_stack); + char *length_as_bytes; + char *placeholder_pos; + + /* Close object */ if ((error = memwriter_putc(state->memwriter, null)) != ERROR_NONE) { return error; } + + /* Set length of object*/ + int_to_bytes(state->memwriter->size - size_before, &length_as_bytes); + placeholder_pos = state->memwriter->buf + size_before; + memcpy(placeholder_pos, length_as_bytes, 4); + free(length_as_bytes); + if (size_t_list_empty(tobson_permanent_state.memwriter_object_start_offset_stack)) + { + size_t_list_delete(tobson_permanent_state.memwriter_object_start_offset_stack); + tobson_permanent_state.memwriter_object_start_offset_stack = NULL; + } + + --(state->shared->struct_nested_level); + return ERROR_NONE; } @@ -2630,34 +2655,17 @@ err_t tobson_write_args(memwriter_t *memwriter, const grm_args_t *args) grm_args_iterator_t *it; arg_t *arg; err_t error; - char *length_as_bytes; - char length_placeholder[4] = {0x01, 0x01, 0x01, 0x01}; - int size_before = memwriter->size; - char *placeholder_pos; it = grm_args_iter(args); if ((arg = it->next(it))) { - /* Placeholder for length of object */ - if ((error = memwriter_puts_with_len(memwriter, length_placeholder, 4)) != ERROR_NONE) - { - return error; - } - tobson_write_buf(memwriter, "o(", NULL, 1); - do { tobson_write_arg(memwriter, arg); } while ((arg = it->next(it))); tobson_write_buf(memwriter, ")", NULL, 1); - - /* Set length of object */ - int_to_bytes(memwriter->size - size_before, &length_as_bytes); - placeholder_pos = (memwriter->buf) + size_before; - memcpy(placeholder_pos, length_as_bytes, 4); - free(length_as_bytes); } args_iterator_delete(it); diff --git a/lib/grm/src/grm/bson_int.h b/lib/grm/src/grm/bson_int.h index 7c4377476..a50fadb4a 100644 --- a/lib/grm/src/grm/bson_int.h +++ b/lib/grm/src/grm/bson_int.h @@ -14,6 +14,8 @@ extern "C" { #include "memwriter_int.h" #include "json_int.h" +#include "datatype/size_t_list_int.h" + /* ######################### internal interface ##################################################################### */ @@ -84,6 +86,7 @@ typedef struct { tojson_serialization_result_t serial_result; unsigned int struct_nested_level; + size_t_list_t *memwriter_object_start_offset_stack; } tobson_permanent_state_t; /* ========================= small helper functions ================================================================= */ @@ -156,6 +159,7 @@ err_t tobson_args_array(tobson_state_t *state); err_t tobson_read_array_length(tobson_state_t *state); err_t tobson_skip_bytes(tobson_state_t *state); err_t tobson_object(tobson_state_t *state); +err_t tobson_open_object(memwriter_t *memwriter); err_t tobson_close_object(tobson_state_t *state); int tobson_get_member_count(const char *data_desc); diff --git a/lib/grm/src/grm/datatype/size_t_list.c b/lib/grm/src/grm/datatype/size_t_list.c new file mode 100644 index 000000000..23e441dc1 --- /dev/null +++ b/lib/grm/src/grm/datatype/size_t_list.c @@ -0,0 +1,31 @@ +#ifdef __unix__ +#define _POSIX_C_SOURCE 200112L +#endif + +/* ######################### includes ############################################################################### */ + +#include "size_t_list_int.h" + + +/* ######################### implementation ######################################################################### */ + +/* ========================= methods ================================================================================ */ + +/* ------------------------ size_t list ----------------------------------------------------------------------------- */ + +DEFINE_LIST_METHODS(size_t) + +err_t size_t_list_entry_copy(size_t_list_entry_t *copy, size_t_list_const_entry_t entry) +{ + *copy = entry; + + return ERROR_NONE; +} + +err_t size_t_list_entry_delete(size_t_list_entry_t entry) +{ + return ERROR_NONE; +} + + +#undef DEFINE_MAP_METHODS diff --git a/lib/grm/src/grm/datatype/size_t_list_int.h b/lib/grm/src/grm/datatype/size_t_list_int.h new file mode 100644 index 000000000..d0498f8dd --- /dev/null +++ b/lib/grm/src/grm/datatype/size_t_list_int.h @@ -0,0 +1,28 @@ +#ifndef GRM_SIZE_T_LIST_INT_H_INCLUDED +#define GRM_SIZE_T_LIST_INT_H_INCLUDED + +/* ######################### includes ############################################################################### */ + +#include "template/list_int.h" + + +/* ######################### interface ############################################################################## */ + +/* ========================= datatypes ============================================================================== */ + +/* ------------------------- size_t list ---------------------------------------------------------------------------- */ + +DECLARE_LIST_TYPE(size_t, size_t) + + +/* ========================= methods ================================================================================ */ + +/* ------------------------- size_t list ---------------------------------------------------------------------------- */ + +DECLARE_LIST_METHODS(size_t) + + +#undef DECLARE_LIST_TYPE +#undef DECLARE_LIST_METHODS + +#endif /* ifndef GRM_SIZE_T_LIST_INT_H_INCLUDED */ From aea08ee5752bad91331f3fed42071d19eb1935a8 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 15 Mar 2024 15:13:52 +0100 Subject: [PATCH 31/48] [BSON] Rework the BSON test --- lib/grm/test/internal_api/grm/CMakeLists.txt | 8 +- .../grm/bson_serialize_deserialize.c | 177 ++++++++++++++++++ lib/grm/test/public_api/grm/CMakeLists.txt | 1 - .../grm/bson_serialize_deserialize.c | 53 ------ 4 files changed, 183 insertions(+), 56 deletions(-) create mode 100644 lib/grm/test/internal_api/grm/bson_serialize_deserialize.c delete mode 100644 lib/grm/test/public_api/grm/bson_serialize_deserialize.c diff --git a/lib/grm/test/internal_api/grm/CMakeLists.txt b/lib/grm/test/internal_api/grm/CMakeLists.txt index ca17a846a..08cbe498c 100644 --- a/lib/grm/test/internal_api/grm/CMakeLists.txt +++ b/lib/grm/test/internal_api/grm/CMakeLists.txt @@ -6,8 +6,12 @@ project( LANGUAGES C CXX ) -set(EXECUTABLE_SOURCES args_automatic_array_conversion.c get_compatible_format.c datatype/string_array_map.c - escape_minus.cxx +set(EXECUTABLE_SOURCES + args_automatic_array_conversion.c + bson_serialize_deserialize.c + get_compatible_format.c + datatype/string_array_map.c + escape_minus.cxx ) foreach(executable_source ${EXECUTABLE_SOURCES}) diff --git a/lib/grm/test/internal_api/grm/bson_serialize_deserialize.c b/lib/grm/test/internal_api/grm/bson_serialize_deserialize.c new file mode 100644 index 000000000..65bacfe4f --- /dev/null +++ b/lib/grm/test/internal_api/grm/bson_serialize_deserialize.c @@ -0,0 +1,177 @@ +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 500 + +#include +#include +#include +#include +#include +#include "grm/memwriter_int.h" + + +#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5))) || defined(__clang__) +#define UNUSED \ + __attribute__((unused, deprecated("Marked as \"UNUSED\" but used. Please remove the \"UNUSED\" marker."))) +#else +#define UNUSED +#endif + +static const char *tmp_dir = NULL; + +static char *create_tmp_dir(void) +{ + char *tmp_dir_; + const char *dirname_template, *system_tmp_dir; + + dirname_template = "grm.XXXXXX"; + system_tmp_dir = getenv("TMPDIR"); + if (system_tmp_dir == NULL) + { + system_tmp_dir = "/tmp"; + } + tmp_dir_ = malloc(strlen(system_tmp_dir) + strlen(dirname_template) + 2); + if (tmp_dir_ == NULL) + { + return NULL; + } + sprintf(tmp_dir_, "%s/%s", system_tmp_dir, dirname_template); + if (mkdtemp(tmp_dir_) == NULL) + { + free(tmp_dir_); + return NULL; + } + + tmp_dir = tmp_dir_; + return tmp_dir_; +} + +static int remove_callback(const char *fpath, const struct stat *sb UNUSED, int typeflag UNUSED, + struct FTW *ftwbuf UNUSED) +{ + int rv = remove(fpath); + fprintf(stderr, "Removed \"%s\"\n", fpath); + + if (rv) perror(fpath); + + return rv; +} + +static void cleanup(void) +{ + if (tmp_dir == NULL) return; + nftw(tmp_dir, remove_callback, 64, FTW_DEPTH | FTW_PHYS); + free((void *)tmp_dir); + tmp_dir = NULL; +} + +static void signal_handler(int sig UNUSED) +{ + cleanup(); +} + +static void test_bson() +{ + int i = 1986; + double d = 5.05; + char c = 'y'; + int y_i[] = {1986, 1986, 1986}; + double y_d[] = {5.05, 5.05, 5.05}; + char *y_s[] = {"eins", "zwei", "drei"}; + char y_c[] = {'a', 'b', 'c', '\0'}; + grm_args_t *args, *subarg, *subargs[2], *read_args; + char *filepath, *bson_buffer; + memwriter_t *memwriter; + FILE *bson_file; + int bson_buffer_size; + size_t bson_file_size, bytes_read; + err_t error; + + args = grm_args_new(); + subarg = grm_args_new(); + subargs[0] = grm_args_new(); + subargs[1] = grm_args_new(); + + grm_args_push(args, "y_i", "i", i); + grm_args_push(args, "y_d", "d", d); + grm_args_push(args, "y_s", "s", y_s[0]); + grm_args_push(args, "y_c", "c", c); + + grm_args_push(args, "y_I", "nI", sizeof(y_i) / sizeof(y_i[0]), y_i); + grm_args_push(args, "y_D", "nD", sizeof(y_d) / sizeof(y_d[0]), y_d); + grm_args_push(args, "y_S", "nS", sizeof(y_s) / sizeof(y_s[0]), y_s); + grm_args_push(args, "y_C", "nC", 4, y_c); + + grm_args_push(subarg, "hello", "s", "world"); + grm_args_push(args, "sub", "a", subarg); + + grm_args_push(subargs[0], "hello", "s", "world"); + grm_args_push(subargs[1], "y", "d", d); + grm_args_push(args, "subs", "nA", 2, subargs); + + printf("Argument container to be serialized as BSON:\n"); + grm_dump(args, stdout); + + printf("\nArgument container as BSON:\n"); + grm_dump_bson(args, stdout); + + filepath = malloc(strlen(tmp_dir) + strlen("bson.bytes") + 2); + assert(filepath != NULL); + + sprintf(filepath, "%s/%s", tmp_dir, "bson.bytes"); + bson_file = fopen(filepath, "wb"); + assert(bson_file != NULL); + + memwriter = memwriter_new(); + assert(memwriter != NULL); + tobson_write_args(memwriter, args); + assert(tobson_is_complete()); + bson_buffer = memwriter_buf(memwriter); + bytes_to_int(&bson_buffer_size, bson_buffer); + fwrite(bson_buffer, 1, bson_buffer_size, bson_file); + memwriter_delete(memwriter); + + fclose(bson_file); + + bson_file = fopen(filepath, "rb"); + assert(bson_file != NULL); + + fseek(bson_file, 0L, SEEK_END); + bson_file_size = ftell(bson_file); + rewind(bson_file); + + bson_buffer = malloc(bson_file_size); + assert(bson_buffer != NULL); + bytes_read = fread(bson_buffer, 1, bson_file_size, bson_file); + assert(bytes_read == bson_file_size); + + fclose(bson_file); + + read_args = grm_args_new(); + assert(read_args != NULL); + + error = frombson_read(read_args, bson_buffer); + assert(error == ERROR_NONE); + + printf("\nParsed Argument container:\n"); + grm_dump(read_args, stdout); + + grm_args_delete(args); +} + +int main(void) +{ + signal(SIGINT, signal_handler); + signal(SIGQUIT, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGABRT, signal_handler); + signal(SIGSEGV, signal_handler); + atexit(cleanup); + create_tmp_dir(); + assert(tmp_dir != NULL); + + test_bson(); + + grm_finalize(); + + return 0; +} diff --git a/lib/grm/test/public_api/grm/CMakeLists.txt b/lib/grm/test/public_api/grm/CMakeLists.txt index 7a0f3676c..1e8cfa978 100644 --- a/lib/grm/test/public_api/grm/CMakeLists.txt +++ b/lib/grm/test/public_api/grm/CMakeLists.txt @@ -9,7 +9,6 @@ project( set(EXECUTABLE_SOURCES bar_errorbar.c barplot.cxx - bson_serialize_deserialize.c custom_receiver.c custom_sender.c dom_render.cxx diff --git a/lib/grm/test/public_api/grm/bson_serialize_deserialize.c b/lib/grm/test/public_api/grm/bson_serialize_deserialize.c deleted file mode 100644 index c0a0cd7b6..000000000 --- a/lib/grm/test/public_api/grm/bson_serialize_deserialize.c +++ /dev/null @@ -1,53 +0,0 @@ -#include - -int main(void) -{ - int i = 1986; - double d = 5.05; - char c = 'y'; - int y_i[] = {1986, 1986, 1986}; - double y_d[] = {5.05, 5.05, 5.05}; - char *y_s[] = {"eins", "zwei", "drei"}; - char y_c[] = {'a', 'b', 'c', '\0'}; - grm_args_t *args, *subarg, *subargs[2]; - - args = grm_args_new(); - subarg = grm_args_new(); - subargs[0] = grm_args_new(); - subargs[1] = grm_args_new(); - - /* - * grm_args_push(args, "hello", "s", "world"); - * grm_args_push(args, "y", "i", i); - * grm_args_push(args, "y", "d", d); - * grm_args_push(args, "y", "c", c); - * - * grm_args_push(args, "y", "nI", sizeof(y_i) / sizeof(y_i[0]), y_i); - * grm_args_push(args, "y", "nD", sizeof(y_d) / sizeof(y_d[0]), y_d); - * grm_args_push(args, "y", "nS", sizeof(y_s) / sizeof(y_s[0]), y_s); - * grm_args_push(args, "y", "nC", 4, y_c); - * - * grm_args_push(subarg, "hello", "s", "world"); - * grm_args_push(args, "sub", "a", subarg); - * - * grm_args_push(subargs[0], "hello", "s", "world"); - * grm_args_push(subargs[1], "y", "d", d); - * grm_args_push(args, "subs", "nA", 2, subargs); - */ - - grm_args_push(subargs[0], "eins", "s", "world"); - grm_args_push(subargs[0], "zwei", "s", "world"); - grm_args_push(subargs[1], "x", "d", d); - grm_args_push(subargs[1], "y", "d", d); - grm_args_push(args, "subs", "nA", 2, subargs); - - grm_dump(args, stdout); - /* - * grm_dump_json(args, stdout); - * grm_dump_bson(args, stdout); - */ - grm_dump_bson_and_parse(args, stdout); - /* grm_args_delete(args); */ - - return 0; -} From 908431b81c9620208e0f90563f0f0bf32aca7153 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 15 Mar 2024 15:14:45 +0100 Subject: [PATCH 32/48] Prettify `grm_dump_bson` and remove unneeded `grm_dump_bson_and_parse` --- lib/grm/include/grm/dump.h | 26 ++++++++++--- lib/grm/src/grm/dump.c | 75 +++++++++++--------------------------- 2 files changed, 42 insertions(+), 59 deletions(-) diff --git a/lib/grm/include/grm/dump.h b/lib/grm/include/grm/dump.h index bb7834cdc..85f70087a 100644 --- a/lib/grm/include/grm/dump.h +++ b/lib/grm/include/grm/dump.h @@ -22,13 +22,29 @@ extern "C" { #ifndef NDEBUG EXPORT void grm_dump(const grm_args_t *args, FILE *f); EXPORT void grm_dump_json(const grm_args_t *args, FILE *f); -EXPORT void grm_dump_bson(const grm_args_t *args, FILE *f); -EXPORT void grm_dump_bson_and_parse(const grm_args_t *args, FILE *f); EXPORT char *grm_dump_json_str(void); +EXPORT void grm_dump_bson(const grm_args_t *args, FILE *f); #else -#define grm_dump -#define grm_dump_json -#define grm_dump_json_str +#define grm_dump(args, f) \ + do \ + { \ + } \ + while (0) +#define grm_dump_json(args, f) \ + do \ + { \ + } \ + while (0) +#define grm_dump_json_str \ + do \ + { \ + } \ + while (0) +#define grm_dump_bson(args, f) \ + do \ + { \ + } \ + while (0) #endif #ifdef __cplusplus diff --git a/lib/grm/src/grm/dump.c b/lib/grm/src/grm/dump.c index 30b7c131f..398706c5d 100644 --- a/lib/grm/src/grm/dump.c +++ b/lib/grm/src/grm/dump.c @@ -300,42 +300,35 @@ void grm_dump_json(const grm_args_t *args, FILE *f) } } -void grm_dump_bson(const grm_args_t *args, FILE *f) +char *grm_dump_json_str(void) { static memwriter_t *memwriter = NULL; - char *buf; - int length; + char *result; if (memwriter == NULL) { memwriter = memwriter_new(); } - tobson_write_args(memwriter, args); - if (tobson_is_complete()) + /* tojson_write_args(memwriter, global_root_args); */ + tojson_write_args(memwriter, active_plot_args); + if (tojson_is_complete()) { memwriter_putc(memwriter, '\0'); - - buf = memwriter_buf(memwriter); - - bytes_to_int(&length, buf); - - while (length > 0) - { - fprintf(f, "\\x%x ", *(buf)); - length--; - ++buf; - } + result = malloc(memwriter_size(memwriter) + 1); + strcpy(result, memwriter_buf(memwriter)); memwriter_delete(memwriter); memwriter = NULL; + return result; } + return ""; } -void grm_dump_bson_and_parse(const grm_args_t *args, FILE *f) +void grm_dump_bson(const grm_args_t *args, FILE *f) { static memwriter_t *memwriter = NULL; char *buf; int length; - grm_args_t *new_args = grm_args_new(); + int i; if (memwriter == NULL) { @@ -344,51 +337,25 @@ void grm_dump_bson_and_parse(const grm_args_t *args, FILE *f) tobson_write_args(memwriter, args); if (tobson_is_complete()) { - memwriter_putc(memwriter, '\0'); - buf = memwriter_buf(memwriter); - bytes_to_int(&length, buf); - while (length > 0) + for (i = 0; i < length; i++, buf++) { - fprintf(f, "\\x%x ", (unsigned char)*(buf)); - length--; - ++buf; + fprintf(f, "%.2X", (unsigned char)*(buf)); + if (i % 16 == 15) + { + putc('\n', f); + } + else if (i % 2 == 1) + { + putc(' ', f); + } } fprintf(f, "\n"); - buf = memwriter_buf(memwriter); - frombson_read(new_args, buf); - - grm_dump(new_args, f); - grm_args_delete(new_args); memwriter_delete(memwriter); memwriter = NULL; } } - -char *grm_dump_json_str(void) -{ - static memwriter_t *memwriter = NULL; - char *result; - - if (memwriter == NULL) - { - memwriter = memwriter_new(); - } - /* tojson_write_args(memwriter, global_root_args); */ - tojson_write_args(memwriter, active_plot_args); - if (tojson_is_complete()) - { - memwriter_putc(memwriter, '\0'); - result = malloc(memwriter_size(memwriter) + 1); - strcpy(result, memwriter_buf(memwriter)); - memwriter_delete(memwriter); - memwriter = NULL; - return result; - } - return ""; -} - #endif From 5026c1e41c56fe2c8643d3d4a402e27dc46ba10b Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Thu, 2 May 2024 17:31:23 +0200 Subject: [PATCH 33/48] [BSON] Add a `tobson_write` function --- lib/grm/src/grm/bson.c | 12 ++++++++++++ lib/grm/src/grm/bson_int.h | 1 + 2 files changed, 13 insertions(+) diff --git a/lib/grm/src/grm/bson.c b/lib/grm/src/grm/bson.c index 4f68b78fc..da05ec145 100644 --- a/lib/grm/src/grm/bson.c +++ b/lib/grm/src/grm/bson.c @@ -2573,6 +2573,18 @@ err_t tobson_init_variables(int *add_data, int *add_data_without_separator, char return ERROR_NONE; } +err_t tobson_write(memwriter_t *memwriter, const char *data_desc, ...) +{ + va_list vl; + err_t error; + + va_start(vl, data_desc); + error = tobson_write_vl(memwriter, data_desc, &vl); + va_end(vl); + + return error; +} + err_t tobson_write_vl(memwriter_t *memwriter, const char *data_desc, va_list *vl) { int add_data, add_data_without_separator; diff --git a/lib/grm/src/grm/bson_int.h b/lib/grm/src/grm/bson_int.h index a50fadb4a..1ffa100e8 100644 --- a/lib/grm/src/grm/bson_int.h +++ b/lib/grm/src/grm/bson_int.h @@ -172,6 +172,7 @@ err_t tobson_serialize(memwriter_t *memwriter, char *data_desc, const void *data tojson_serialization_result_t *serial_result, tobson_shared_state_t *shared_state); void tobson_init_static_variables(void); err_t tobson_init_variables(int *add_data, int *add_data_without_separator, char **_data_desc, const char *data_desc); +err_t tobson_write(memwriter_t *memwriter, const char *data_desc, ...); err_t tobson_write_vl(memwriter_t *memwriter, const char *data_desc, va_list *vl); err_t tobson_write_buf(memwriter_t *memwriter, const char *data_desc, const void *buffer, int apply_padding); err_t tobson_write_arg(memwriter_t *memwriter, const arg_t *arg); From d11302bd38938b7de0b5070a242c5c649eba72b6 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 3 May 2024 08:42:56 +0200 Subject: [PATCH 34/48] [GRM] Use BSON to serialize contexts in XML documents in Release mode --- lib/grm/src/grm/plot.cxx | 93 ++++++++++++++++++++++++++------------ lib/grm/src/grm/plot_int.h | 1 + 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index f9406c3c8..723fe357d 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -36,6 +36,7 @@ extern "C" { #include "gr.h" #include "gr3.h" #include "json_int.h" +#include "bson_int.h" #include "logging_int.h" } #include "utilcpp_int.hxx" @@ -4583,14 +4584,15 @@ err_t classes_polar_histogram(grm_args_t *subplot_args) * \brief Dump the current GRM context object into a file object. * * \param[in] f The file object, the serialized context will be written to. - * \param[in] dump_encoding The encoding of the serialized context. The context is exported as JSON, but an additional - * encoding step can be necessary to embed the JSON document into a file (like an XML - * document). Possible values are: - * - `DUMP_JSON_PLAIN`: Do not apply any encoding. - * - `DUMP_JSON_ESCAPE_DOUBLE_MINUS`: Escape double minus signs (`--`) in the JSON document by - * replacing them with `-\-`. This can be used to embed the - * output into an XML comment. - * - `DUMP_JSON_BASE64`: Base64 encode the JSON document. + * \param[in] dump_encoding The encoding of the serialized context. The context is exported as JSON or BSON, but an + * additional encoding step can be necessary to embed the JSON/BSON document into a file (like + * an XML document). Possible values are: + * - `DUMP_JSON_PLAIN`: Export as JSON and do not apply any encoding. + * - `DUMP_JSON_ESCAPE_DOUBLE_MINUS`: Export as JSON and escape double minus signs (`--`) in + * the JSON document by replacing them with `-\-`. This can + * be used to embed the output into an XML comment. + * - `DUMP_JSON_BASE64`: Export a Base64 encoded JSON document. + * - `DUMP_BSON_BASE64`: Export a Base64 encoded BSON document. * \param[in] context_keys_to_discard The context keys which will be excluded in the dump. */ void dump_context(FILE *f, dump_encoding_t dump_encoding, @@ -4618,29 +4620,30 @@ char *dump_context_str(dump_encoding_t dump_encoding, const std::unordered_setgetContext(); - tojson_write(memwriter, "o("); + auto write_callback = (dump_encoding != DUMP_BSON_BASE64) ? tojson_write : tobson_write; + write_callback(memwriter, "o("); for (auto item : *context) { std::visit( - GRM::overloaded{[&memwriter, &context_keys_to_discard]( + GRM::overloaded{[&memwriter, &context_keys_to_discard, &write_callback]( std::reference_wrapper>> pair_ref) { if (context_keys_to_discard->find(pair_ref.get().first) != context_keys_to_discard->end()) return; std::stringstream format_stream; format_stream << pair_ref.get().first << ":nD"; - tojson_write(memwriter, format_stream.str().c_str(), pair_ref.get().second.size(), - pair_ref.get().second.data()); + write_callback(memwriter, format_stream.str().c_str(), pair_ref.get().second.size(), + pair_ref.get().second.data()); }, - [&memwriter, &context_keys_to_discard]( + [&memwriter, &context_keys_to_discard, &write_callback]( std::reference_wrapper>> pair_ref) { if (context_keys_to_discard->find(pair_ref.get().first) != context_keys_to_discard->end()) return; std::stringstream format_stream; format_stream << pair_ref.get().first << ":nI"; - tojson_write(memwriter, format_stream.str().c_str(), pair_ref.get().second.size(), - pair_ref.get().second.data()); + write_callback(memwriter, format_stream.str().c_str(), pair_ref.get().second.size(), + pair_ref.get().second.data()); }, - [&memwriter, &context_keys_to_discard]( + [&memwriter, &context_keys_to_discard, &write_callback]( std::reference_wrapper>> pair_ref) { if (context_keys_to_discard->find(pair_ref.get().first) != context_keys_to_discard->end()) return; @@ -4652,20 +4655,22 @@ char *dump_context_str(dump_encoding_t dump_encoding, const std::unordered_set *context_keys_to_discard) { +#ifndef NDEBUG + auto dump_encoding = DUMP_JSON_ESCAPE_DOUBLE_MINUS; +#else + auto dump_encoding = DUMP_BSON_BASE64; +#endif fprintf(f, "\n"); } @@ -4709,23 +4719,28 @@ void dump_context_as_xml_comment(FILE *f, const std::unordered_set */ char *dump_context_as_xml_comment_str(const std::unordered_set *context_keys_to_discard) { - char *escaped_json_str = nullptr; + char *encoded_json_str = nullptr; size_t escaped_json_strlen; char *xml_comment = nullptr; - escaped_json_str = dump_context_str(DUMP_JSON_ESCAPE_DOUBLE_MINUS, context_keys_to_discard); - cleanup_if(escaped_json_str == nullptr); - escaped_json_strlen = strlen(escaped_json_str); +#ifndef NDEBUG + auto dump_encoding = DUMP_JSON_ESCAPE_DOUBLE_MINUS; +#else + auto dump_encoding = DUMP_BSON_BASE64; +#endif + encoded_json_str = dump_context_str(dump_encoding, context_keys_to_discard); + cleanup_if(encoded_json_str == nullptr); + escaped_json_strlen = strlen(encoded_json_str); /* 27 = strlen("") + 1 (`\0`) */ xml_comment = static_cast(malloc(escaped_json_strlen + 27)); cleanup_if(xml_comment == nullptr); strcpy(xml_comment, ""); xml_comment[escaped_json_strlen + 26] = '\0'; cleanup: - free(escaped_json_str); + free(encoded_json_str); return xml_comment; } @@ -4777,7 +4792,19 @@ void load_context_str(GRM::Context &context, const std::string &context_str, dum std::string serialized_context_tmp_; if (dump_encoding == DUMP_AUTO_DETECT) { - dump_encoding = context_str[0] == '{' ? DUMP_JSON_ESCAPE_DOUBLE_MINUS : DUMP_JSON_BASE64; + if (context_str[0] == '{') + { + dump_encoding = DUMP_JSON_ESCAPE_DOUBLE_MINUS; + } + else if (context_str[0] == 'e' && context_str[1] == 'y') + { + // Base64 encoded `{"` is `ey` + dump_encoding = DUMP_JSON_BASE64; + } + else + { + dump_encoding = DUMP_BSON_BASE64; + } } switch (dump_encoding) { @@ -4786,6 +4813,7 @@ void load_context_str(GRM::Context &context, const std::string &context_str, dum serialized_context = serialized_context_tmp_.c_str(); break; case DUMP_JSON_BASE64: + case DUMP_BSON_BASE64: err_t error; serialized_context = base64_decode(nullptr, context_str.c_str(), nullptr, &error); if (error != ERROR_NONE) @@ -4806,7 +4834,14 @@ void load_context_str(GRM::Context &context, const std::string &context_str, dum { throw std::runtime_error("Failed to create context args object"); } - fromjson_read(context_args, serialized_context); + if (dump_encoding != DUMP_BSON_BASE64) + { + fromjson_read(context_args, serialized_context); + } + else + { + frombson_read(context_args, serialized_context); + } auto context_args_it = grm_args_iter(context_args); arg_t *context_arg; while ((context_arg = context_args_it->next(context_args_it))) diff --git a/lib/grm/src/grm/plot_int.h b/lib/grm/src/grm/plot_int.h index 469f31b31..956f43b53 100644 --- a/lib/grm/src/grm/plot_int.h +++ b/lib/grm/src/grm/plot_int.h @@ -78,6 +78,7 @@ typedef enum DUMP_JSON_PLAIN = 1, DUMP_JSON_ESCAPE_DOUBLE_MINUS = 2, DUMP_JSON_BASE64 = 3, + DUMP_BSON_BASE64 = 4, } dump_encoding_t; From 120e69931a8308f669df60f710a60e832267ba9c Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 3 May 2024 17:38:56 +0200 Subject: [PATCH 35/48] [GRM] Export an `internal` attribute in XML containing internals --- lib/grm/grplot/grplot_widget.cxx | 5 +- .../grm/dom_render/graphics_tree/util.hxx | 12 +++- .../src/grm/dom_render/graphics_tree/util.cxx | 61 ++++++++++++++++++- lib/grm/src/grm/dom_render/render.cxx | 8 ++- lib/grm/src/grm/plot.cxx | 14 +++-- 5 files changed, 90 insertions(+), 10 deletions(-) diff --git a/lib/grm/grplot/grplot_widget.cxx b/lib/grm/grplot/grplot_widget.cxx index d69b83efd..6afa34dfd 100644 --- a/lib/grm/grplot/grplot_widget.cxx +++ b/lib/grm/grplot/grplot_widget.cxx @@ -1239,7 +1239,10 @@ void GRPlotWidget::AttributeEditEvent() clicked.clear(); if (getenv("GRM_DEBUG")) { - std::cerr << toXML(grm_get_document_root(), GRM::SerializerOptions{std::string(2, ' '), true}) << "\n"; + std::cerr << toXML(grm_get_document_root(), + GRM::SerializerOptions{std::string(2, ' '), + GRM::SerializerOptions::InternalAttributesFormat::Plain}) + << "\n"; } reset_pixmap(); } diff --git a/lib/grm/include/grm/dom_render/graphics_tree/util.hxx b/lib/grm/include/grm/dom_render/graphics_tree/util.hxx index dbca8f6b5..47a4ce9bf 100644 --- a/lib/grm/include/grm/dom_render/graphics_tree/util.hxx +++ b/lib/grm/include/grm/dom_render/graphics_tree/util.hxx @@ -17,11 +17,19 @@ class Node; struct EXPORT SerializerOptions { + enum class InternalAttributesFormat + { + None, // Hide internal attributes + Plain, // Export internal attributes like public attributes + Obfuscated, // Collect internal attributes and store them into a special `internal` attribute, BSON+Base64 encoded + }; + std::string indent = ""; - bool show_hidden = false; + InternalAttributesFormat internal_attribute_format = InternalAttributesFormat::None; }; EXPORT std::string -toXML(const std::shared_ptr &node, const SerializerOptions &options = {"", false}, +toXML(const std::shared_ptr &node, + const SerializerOptions &options = {"", SerializerOptions::InternalAttributesFormat::None}, std::optional &new_attribute_name)>> attribute_filter = std::nullopt); diff --git a/lib/grm/src/grm/dom_render/graphics_tree/util.cxx b/lib/grm/src/grm/dom_render/graphics_tree/util.cxx index 5d0797326..ee9821ed1 100644 --- a/lib/grm/src/grm/dom_render/graphics_tree/util.cxx +++ b/lib/grm/src/grm/dom_render/graphics_tree/util.cxx @@ -6,6 +6,9 @@ #include #include #include +#include "grm/base64_int.h" +#include "grm/bson_int.h" +#include "grm/memwriter_int.h" #include "grm/utilcpp_int.hxx" static std::string escapeXMLAttribute(std::string_view attribute) @@ -61,6 +64,7 @@ elementToXML(std::stringstream &os, const std::shared_ptr &e std::optional &new_attribute_name)>> attribute_filter) { + std::map internal_attributes; os << indent << "<" << element->localName(); auto attribute_names_set = element->getAttributeNames(); std::vector attribute_names{attribute_names_set.begin(), attribute_names_set.end()}; @@ -82,7 +86,19 @@ elementToXML(std::stringstream &os, const std::shared_ptr &e { attribute_name = *new_attribute_name; } - if (!options.show_hidden && starts_with(attribute_name.get(), "_")) continue; + if (starts_with(attribute_name.get(), "_")) + { + switch (options.internal_attribute_format) + { + case GRM::SerializerOptions::InternalAttributesFormat::None: + continue; + case GRM::SerializerOptions::InternalAttributesFormat::Obfuscated: + internal_attributes[attribute_name.get()] = element->getAttribute(original_attribute_name); + continue; + default: + break; + } + } auto value = (std::string)element->getAttribute(original_attribute_name); if (value == "nan") @@ -108,6 +124,49 @@ elementToXML(std::stringstream &os, const std::shared_ptr &e os << " " << attribute_name.get() << "=\"" << escapeXMLAttribute(value) << "\""; } } + if (options.internal_attribute_format == GRM::SerializerOptions::InternalAttributesFormat::Obfuscated && + !internal_attributes.empty()) + { + auto memwriter = std::unique_ptr(memwriter_new(), memwriter_delete); + if (!memwriter) + { + throw std::bad_alloc(); + } + tobson_write(memwriter.get(), "o("); + for (const auto &attribute : internal_attributes) + { + std::stringstream format_stream; + format_stream << attribute.first; + switch (attribute.second.type()) + { + case GRM::Value::Type::DOUBLE: + format_stream << ":d"; + tobson_write(memwriter.get(), format_stream.str().c_str(), static_cast(attribute.second)); + break; + case GRM::Value::Type::INT: + format_stream << ":i"; + tobson_write(memwriter.get(), format_stream.str().c_str(), static_cast(attribute.second)); + break; + case GRM::Value::Type::STRING: + format_stream << ":s"; + tobson_write(memwriter.get(), format_stream.str().c_str(), + static_cast(attribute.second).c_str()); + break; + default: + break; + } + } + tobson_write(memwriter.get(), ")"); + err_t error = ERROR_NONE; + auto base64_encoded_cstr = std::unique_ptr( + base64_encode(nullptr, memwriter_buf(memwriter.get()), memwriter_size(memwriter.get()), &error), std::free); + if (error != ERROR_NONE) + { + logger((stderr, "Got error \"%d\" (\"%s\")!\n", error, error_names[error])); + throw std::runtime_error("Got error \"" + std::to_string(error) + "\" (\"" + error_names[error] + "\")!"); + } + os << " internal=\"" << base64_encoded_cstr.get() << "\""; + } if (element->hasChildNodes()) { os << ">\n"; diff --git a/lib/grm/src/grm/dom_render/render.cxx b/lib/grm/src/grm/dom_render/render.cxx index 9481d2393..88e0a42a4 100644 --- a/lib/grm/src/grm/dom_render/render.cxx +++ b/lib/grm/src/grm/dom_render/render.cxx @@ -15211,7 +15211,9 @@ void GRM::Render::render() applyRootDefaults(root); if (logger_enabled()) { - std::cerr << toXML(root, GRM::SerializerOptions{std::string(indent, ' '), true}) << "\n"; + std::cerr << toXML(root, GRM::SerializerOptions{std::string(indent, ' '), + GRM::SerializerOptions::InternalAttributesFormat::Plain}) + << "\n"; } if (static_cast(root->getAttribute("clear_ws"))) gr_clearws(); root->setAttribute("_modified", true); @@ -15232,7 +15234,9 @@ void GRM::Render::render() } if (logger_enabled()) { - std::cerr << toXML(root, GRM::SerializerOptions{std::string(indent, ' '), true}) << "\n"; + std::cerr << toXML(root, GRM::SerializerOptions{std::string(indent, ' '), + GRM::SerializerOptions::InternalAttributesFormat::Plain}) + << "\n"; } redraw_ws = false; // reset marker types diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 723fe357d..28329444c 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -4702,7 +4702,9 @@ char *dump_context_str(dump_encoding_t dump_encoding, const std::unordered_set *context_keys_to_discard) { #ifndef NDEBUG - auto dump_encoding = DUMP_JSON_ESCAPE_DOUBLE_MINUS; + // TODO: Undo + // auto dump_encoding = DUMP_JSON_ESCAPE_DOUBLE_MINUS; + auto dump_encoding = DUMP_BSON_BASE64; #else auto dump_encoding = DUMP_BSON_BASE64; #endif @@ -4724,7 +4726,9 @@ char *dump_context_as_xml_comment_str(const std::unordered_set *con char *xml_comment = nullptr; #ifndef NDEBUG - auto dump_encoding = DUMP_JSON_ESCAPE_DOUBLE_MINUS; + // TODO: Undo + // auto dump_encoding = DUMP_JSON_ESCAPE_DOUBLE_MINUS; + auto dump_encoding = DUMP_BSON_BASE64; #else auto dump_encoding = DUMP_BSON_BASE64; #endif @@ -5717,7 +5721,9 @@ void grm_dump_graphics_tree(FILE *f) const unsigned int indent = 2; // Use a lambda around `restore_backup_attribute_filter` to make sure it is used by reference. fprintf(f, "%s", - toXML(global_root, GRM::SerializerOptions{std::string(indent, ' ')}, + toXML(global_root, + GRM::SerializerOptions{std::string(indent, ' '), + GRM::SerializerOptions::InternalAttributesFormat::Obfuscated}, [&restore_backup_attribute_filter](const std::string &attribute_name, const GRM::Element &element, std::optional &new_attribute_name) -> bool { return restore_backup_attribute_filter(attribute_name, element, new_attribute_name); @@ -5743,7 +5749,7 @@ char *grm_dump_graphics_tree_str(void) internal::RestoreBackupAttributeFilter restore_backup_attribute_filter; // Use a lambda around `restore_backup_attribute_filter` to make sure it is used by reference. std::string graphics_tree_str = - toXML(global_root, GRM::SerializerOptions{}, + toXML(global_root, GRM::SerializerOptions{"", GRM::SerializerOptions::InternalAttributesFormat::Obfuscated}, [&restore_backup_attribute_filter](const std::string &attribute_name, const GRM::Element &element, std::optional &new_attribute_name) -> bool { return restore_backup_attribute_filter(attribute_name, element, new_attribute_name); From 338e98765187bfbe3d6dc4286f9bfcf9f5560d71 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Wed, 15 May 2024 07:42:23 +0200 Subject: [PATCH 36/48] [GRM] Add byte stream transformation code to decode internal attributes --- lib/grm/src/grm/plot.cxx | 214 +++++++++++++++++++++++++++++++- lib/grm/src/grm/utilcpp.cxx | 22 ++++ lib/grm/src/grm/utilcpp_int.hxx | 3 +- 3 files changed, 233 insertions(+), 6 deletions(-) diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 28329444c..e7d18b879 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -4940,22 +4940,226 @@ class XMLStringBuffer : public XMLFormatter, private XMLFormatTarget /*! * \brief A helper class for `FileInputSource` which manages the file reading. + * + * This class reads XML data from a FILE handle and decodes obfuscated internal attributes on the fly. */ class FileBinInputStream : public BinInputStream { public: - FileBinInputStream(FILE *file) : file_(file) {} + explicit FileBinInputStream(FILE *file) : file_(file) {} - XMLFilePos curPos() const override { return ftell(file_); } + [[nodiscard]] XMLFilePos curPos() const override { return cur_pos_; } - XMLSize_t readBytes(XMLByte *const toFill, const XMLSize_t maxToRead) override + /*! + * \brief Read the requested amoutn of bytes from the wrapped XML file handle, or less if EOF is reached. + * + * \param[out] to_fill The buffer which is filled with the read bytes. Must be allocated by the user. + * \param[in] max_to_read The maximum amount of bytes to read from the file. The buffer must be large enough. + * \return The real amount of read bytes. + */ + [[nodiscard]] XMLSize_t readBytes(XMLByte *const to_fill, const XMLSize_t max_to_read) override { - return fread(toFill, sizeof(XMLByte), maxToRead, file_); + size_t max_to_read_from_file = grm_max(0L, static_cast(max_to_read) - static_cast(buffer_.size())); + buffer_.resize(buffer_.size() + max_to_read_from_file); + auto read_from_file = + fread(buffer_.data() + buffer_.size() - max_to_read_from_file, sizeof(char), max_to_read_from_file, file_); + buffer_.resize(buffer_.size() - max_to_read_from_file + read_from_file); + size_t look_ahead_pos = 0; + while (true) + { + auto buffer_view = std::string_view(buffer_.data(), buffer_.size()); + look_ahead_pos = buffer_view.find(look_ahead_prefix_, look_ahead_pos); + if (look_ahead_pos == std::string_view::npos) + { + look_ahead_pos = ends_with_any_subprefix(buffer_view, look_ahead_prefix_); + } + + if (look_ahead_pos != std::string_view::npos && look_ahead(buffer_, look_ahead_pos)) + { + buffer_ = transform_look_ahead_buffer(buffer_, look_ahead_pos); + } + else + { + break; + } + } + size_t bytes_to_copy = grm_min(max_to_read, buffer_.size()); + std::copy(std::begin(buffer_), std::begin(buffer_) + static_cast(bytes_to_copy), to_fill); + buffer_.erase(std::begin(buffer_), std::begin(buffer_) + static_cast(bytes_to_copy)); + + cur_pos_ += static_cast(bytes_to_copy); + return bytes_to_copy; } - const XMLCh *getContentType() const override { return nullptr; } + [[nodiscard]] const XMLCh *getContentType() const override { return nullptr; } + +protected: + /*! + * \brief Look ahead in the opened file to find the end of the next obfuscated internal attribute. + * + * \param[inout] buffer The buffer to check for an internal attribute. If it only contains a part of an internal + * attribute, read more bytes from the file and append them to the buffer. + * \param[in] look_ahead_pos The position to start the search for the next internal attribute from. + * \return If a complete internal attribute was found. + */ + [[nodiscard]] bool look_ahead(std::vector &buffer, size_t look_ahead_pos) + { + const size_t chunk_size = 100; + size_t buffer_view_start = look_ahead_pos; + auto buffer_view = std::string_view(buffer.data(), buffer.size()).substr(buffer_view_start); + int delimiter_count = 0; + bool successful = false; + + auto read_from_file_at_least_once = false; + while (true) + { + auto delimiter_found = false; + if (delimiter_count == 0) + { + if ((delimiter_found = starts_with(buffer_view, look_ahead_prefix_))) + { + buffer_view_start += look_ahead_prefix_.size(); + buffer_view = buffer_view.substr(look_ahead_prefix_.size()); + ++delimiter_count; + } + else if (read_from_file_at_least_once) + { + // In this case the first look ahead operation could not complete the searched prefix -> abort now + break; + } + } + if (delimiter_count == 1) + { + if ((delimiter_found = buffer_view.find(attribute_delimiter) != std::string_view::npos)) + { + ++delimiter_count; + } + } + if (delimiter_count == 2) + { + successful = true; + break; + } + buffer.resize(buffer.size() + chunk_size); + auto read_bytes = fread(buffer.data() + buffer.size() - chunk_size, sizeof(char), chunk_size, file_); + read_from_file_at_least_once = true; + buffer.resize(buffer.size() - chunk_size + read_bytes); + if (read_bytes == 0) break; + buffer_view = std::string_view(buffer.data(), buffer.size()).substr(buffer_view_start); + } + + return successful; + } + + /*! + * \brief Decode the next internal attribute in the given look ahead buffer and return the result as new buffer. + * + * \param[in] look_ahead_buffer The buffer to operate on. + * \param[in] start_index An optional start_index to start the search for the next internal attribute from. + * \return A buffer containing the decoded internal attribute and the rest of the passed look ahead buffer. + */ + [[nodiscard]] std::vector transform_look_ahead_buffer(const std::vector &look_ahead_buffer, + size_t start_index = 0) const + { + auto look_ahead_buffer_view = + std::string_view(look_ahead_buffer.data(), look_ahead_buffer.size()).substr(start_index); + auto value_start_pos = look_ahead_buffer_view.find(attribute_delimiter) + 1; + auto value_end_pos = look_ahead_buffer_view.find(attribute_delimiter, value_start_pos); + auto attribute_value = std::string(look_ahead_buffer_view.substr(value_start_pos, value_end_pos - value_start_pos)); + err_t error = ERROR_NONE; + auto bson_data = std::unique_ptr( + base64_decode(nullptr, attribute_value.c_str(), nullptr, &error), std::free); + if (error != ERROR_NONE) + { + logger((stderr, "Got error \"%d\" (\"%s\")!\n", error, error_names[error])); + throw std::runtime_error("Got error \"" + std::to_string(error) + "\" (\"" + error_names[error] + "\")!"); + } + + auto internal_args = std::unique_ptr(grm_args_new(), grm_args_delete); + error = frombson_read(internal_args.get(), bson_data.get()); + if (error != ERROR_NONE) + { + logger((stderr, "Got error \"%d\" (\"%s\")!\n", error, error_names[error])); + throw std::runtime_error("Got error \"" + std::to_string(error) + "\" (\"" + error_names[error] + "\")!"); + } + auto args_it = std::unique_ptr( + grm_args_iter(internal_args.get()), args_iterator_delete); + arg_t *arg; + auto transformed_attribute_value = std::stringstream(); + while ((arg = args_it->next(args_it.get())) != nullptr) + { + /* Check if there is already content in the string stream (after first iteration) */ + if (transformed_attribute_value.rdbuf()->in_avail() > 0) + { + transformed_attribute_value << " "; + } + transformed_attribute_value << arg->key << "=" << attribute_delimiter; + if (*arg->value_format) + { + auto value_it = std::unique_ptr( + grm_arg_value_iter(arg), args_value_iterator_delete); + while (value_it->next(value_it.get()) != nullptr) + { + /* + * Decoded attributes must not + * - be arrays + * - be nested + * - be of single char type + * In these cases there is some internal error. + */ + assert(!value_it->is_array && value_it->format != 'a' && value_it->format != 'c'); + switch (value_it->format) + { + case 'i': + transformed_attribute_value << *static_cast(value_it->value_ptr); + break; + case 'd': + { + auto double_as_string = std::to_string(*static_cast(value_it->value_ptr)); + if (double_as_string == "inf" || double_as_string == "-inf") + { + std::transform(std::begin(double_as_string), std::end(double_as_string), + std::begin(double_as_string), [](auto c) { return std::toupper(c); }); + } + else if (double_as_string == "nan") + { + double_as_string = "NaN"; + } + transformed_attribute_value << double_as_string; + } + break; + case 's': + transformed_attribute_value << *static_cast(value_it->value_ptr); + break; + default: + /* This branch should never be reached */ + logger((stderr, "Internal error!\n")); + assert(false); + } + } + } + transformed_attribute_value << attribute_delimiter; + } + + auto transformed_look_ahead_buffer = std::vector(); + transformed_look_ahead_buffer.reserve(start_index + transformed_attribute_value.tellp() + + look_ahead_buffer_view.size() - value_end_pos - 1); + transformed_look_ahead_buffer.insert(std::end(transformed_look_ahead_buffer), std::begin(look_ahead_buffer), + std::begin(look_ahead_buffer) + static_cast(start_index)); + transformed_look_ahead_buffer.insert(std::end(transformed_look_ahead_buffer), + std::istreambuf_iterator(transformed_attribute_value), + std::istreambuf_iterator()); + transformed_look_ahead_buffer.insert(std::end(transformed_look_ahead_buffer), + std::begin(look_ahead_buffer_view) + value_end_pos + 1, + std::end(look_ahead_buffer_view)); + return transformed_look_ahead_buffer; + } private: + const char attribute_delimiter = '"'; + const std::string look_ahead_prefix_ = "internal=" + std::string(1, attribute_delimiter); + std::vector buffer_; + XMLFilePos cur_pos_ = 0; FILE *file_; }; diff --git a/lib/grm/src/grm/utilcpp.cxx b/lib/grm/src/grm/utilcpp.cxx index 2b237d261..bf799156f 100644 --- a/lib/grm/src/grm/utilcpp.cxx +++ b/lib/grm/src/grm/utilcpp.cxx @@ -44,6 +44,28 @@ bool ends_with(std::string_view str, std::string_view suffix) return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); } +/*! + * \brief Check if a given string ends with any subprefix of a given prefix (excluding the prefix itself). For example, + * the string "abcdefg" ends with any subprefix of "fghij" (subprefix "fg")." + * + * \param[in] str The input string which is checked at the end. + * \param[in] prefix The prefix which will be shortened to any subprefix (removing one character at the end in each + * step). + * \return The position of a found subprefix. If no subprefix is found, `std::string_view::npos` is returned. + */ +size_t ends_with_any_subprefix(std::string_view str, std::string_view prefix) +{ + for (auto i = prefix.size() - 1; i > 0; --i) + { + if (ends_with(str, prefix.substr(0, i))) + { + return str.size() - i; + } + } + + return std::string_view::npos; +} + /*! * \brief Check if a substring of a given input string consists of a certain character, ending with an another end * character. diff --git a/lib/grm/src/grm/utilcpp_int.hxx b/lib/grm/src/grm/utilcpp_int.hxx index 13673852d..bcb396fb5 100644 --- a/lib/grm/src/grm/utilcpp_int.hxx +++ b/lib/grm/src/grm/utilcpp_int.hxx @@ -28,9 +28,10 @@ std::string_view rtrim(std::string_view s); std::string_view trim(std::string_view s); bool starts_with(std::string_view str, std::string_view prefix); bool ends_with(std::string_view str, std::string_view suffix); +size_t ends_with_any_subprefix(std::string_view str, std::string_view prefix); size_t string_consists_of(std::string_view input, char c, char ends_with, size_t pos = 0); -template std::string string_join(Iterator first, Iterator last, std::string_view delimiter) +template std::string string_join(Iterator first, Iterator last, std::string_view delimiter = "") { if (first == last) { From fce8ef643edceeb45d1be83a8aba88f9a564d94e Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 17 May 2024 09:55:26 +0200 Subject: [PATCH 37/48] [GRM] Do not convert element names to lower case Converting element names to lowercase can lead to failing schema validation. Especially schema trees contain elements with capital letters (like `complexType` or `attributeGroup`). --- lib/grm/src/grm/dom_render/graphics_tree/Element.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/grm/src/grm/dom_render/graphics_tree/Element.cxx b/lib/grm/src/grm/dom_render/graphics_tree/Element.cxx index 6b9b2950a..4744cdd11 100644 --- a/lib/grm/src/grm/dom_render/graphics_tree/Element.cxx +++ b/lib/grm/src/grm/dom_render/graphics_tree/Element.cxx @@ -7,7 +7,7 @@ #include GRM::Element::Element(std::string local_name, const std::shared_ptr &owner_document) - : GRM::Node(GRM::Node::Type::ELEMENT_NODE, owner_document), m_local_name(tolower(std::move(local_name))) + : GRM::Node(GRM::Node::Type::ELEMENT_NODE, owner_document), m_local_name(std::move(local_name)) { } @@ -149,7 +149,7 @@ bool GRM::Element::hasAttribute(const std::string &qualifiedName) const template static std::vector> getElementsByTagName_impl(T &element, const std::string &qualifiedName) { - std::string local_name = GRM::tolower(qualifiedName); + std::string local_name = qualifiedName; std::vector> found_elements; for (const auto &child_element : element.children()) { From 38e9638a561dd5c978755d9956ce315ef948d326 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 17 May 2024 10:15:23 +0200 Subject: [PATCH 38/48] [GRM] Check if function pointer are set before calling them --- lib/grm/src/grm/dom_render/graphics_tree/Element.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/grm/src/grm/dom_render/graphics_tree/Element.cxx b/lib/grm/src/grm/dom_render/graphics_tree/Element.cxx index 4744cdd11..27959f2e5 100644 --- a/lib/grm/src/grm/dom_render/graphics_tree/Element.cxx +++ b/lib/grm/src/grm/dom_render/graphics_tree/Element.cxx @@ -69,7 +69,7 @@ void GRM::Element::setAttribute(const std::string &qualifiedName, const GRM::Val if (value != old_value) { auto elem_p = std::static_pointer_cast(shared_from_this()); - contextUpdate(elem_p, qualifiedName, old_value); + if (contextUpdate) contextUpdate(elem_p, qualifiedName, old_value); if (qualifiedName == "kind") { ; @@ -77,13 +77,13 @@ void GRM::Element::setAttribute(const std::string &qualifiedName, const GRM::Val if (qualifiedName == "viewport_x_min" || qualifiedName == "viewport_x_max" || qualifiedName == "viewport_y_min" || qualifiedName == "viewport_y_max") { - update(elem_p, qualifiedName, std::to_string(static_cast(old_value))); + if (update) update(elem_p, qualifiedName, std::to_string(static_cast(old_value))); } else { - update(elem_p, qualifiedName, static_cast(old_value)); + if (update) update(elem_p, qualifiedName, static_cast(old_value)); } - render(); + if (render) render(); } } From 1c5f2f2432c50c4dba15227c430ce81619ad462c Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 17 May 2024 09:59:15 +0200 Subject: [PATCH 39/48] [GRM] Add missing `xmlns` attributes to schema trees --- lib/grm/src/grm/plot.cxx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index e7d18b879..f32502b0e 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -5511,7 +5511,7 @@ class SchemaParseHandler : public DefaultHandler, public SaxErrorHandler if (node_name == "xs:schema") { - current_gr_element_ = document_.createElement("xs:schema"); + current_gr_element_ = document_.createElement(node_name); document_.replaceChildren(current_gr_element_); insertion_parent_ = nullptr; } @@ -5525,6 +5525,12 @@ class SchemaParseHandler : public DefaultHandler, public SaxErrorHandler { current_gr_element_->setAttribute(encode(attributes.getQName(i)), encode(attributes.getValue(i))); } + if (node_name == "xs:schema") + { + // xmlns namespace attributes are not handled as attributes in this method, so add the namespace explicitly. + current_gr_element_->setAttribute("xmlns:xs", "http://www.w3.org/2001/XMLSchema"); + current_gr_element_->setAttribute("xmlns:vc", "http://www.w3.org/2007/XMLSchema-versioning"); + } if (insertion_parent_ != nullptr) { insertion_parent_->appendChild(current_gr_element_); From 2e1c2e35f749c3622de0e61b707e6a9a710f2b45 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 17 May 2024 10:01:43 +0200 Subject: [PATCH 40/48] [GRM] Add a `PATH_SEPARATOR` macro for path separation --- lib/grm/src/grm/plot.cxx | 2 +- lib/grm/src/grm/util_int.h | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index f32502b0e..52468623d 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -5652,7 +5652,7 @@ err_t validate_graphics_tree(void) { using namespace XERCES_CPP_NAMESPACE; - std::string schema_filepath{std::string(get_gr_dir()) + "/" + SCHEMA_REL_FILEPATH}; + std::string schema_filepath{std::string(get_gr_dir()) + PATH_SEPARATOR + SCHEMA_REL_FILEPATH}; if (!file_exists(schema_filepath.c_str())) { return ERROR_PARSE_XML_NO_SCHEMA_FILE; diff --git a/lib/grm/src/grm/util_int.h b/lib/grm/src/grm/util_int.h index 9e1f16a6c..2384f4996 100644 --- a/lib/grm/src/grm/util_int.h +++ b/lib/grm/src/grm/util_int.h @@ -20,6 +20,14 @@ extern "C" { /* ------------------------- util ----------------------------------------------------------------------------------- */ +#ifndef PATH_SEPARATOR +#ifdef _WIN32 +#define PATH_SEPARATOR '\\' +#else +#define PATH_SEPARATOR '/' +#endif +#endif + #define is_string_delimiter(char_ptr, str) ((*(char_ptr) == '"') && (((char_ptr) == (str)) || *((char_ptr)-1) != '\\')) #ifndef array_size From b07f4c98a776fd8bd504d7e9d6f9b9273e5112f3 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 17 May 2024 10:07:55 +0200 Subject: [PATCH 41/48] [GRM] Create a GRM tmp directory on initialization --- lib/grm/include/grm/error.h | 3 +- lib/grm/src/grm/backtrace.c | 24 +- lib/grm/src/grm/plot.cxx | 8 + lib/grm/src/grm/util.c | 229 +++++++++++++++--- lib/grm/src/grm/util_int.h | 5 +- .../grm/bson_serialize_deserialize.c | 6 +- 6 files changed, 232 insertions(+), 43 deletions(-) diff --git a/lib/grm/include/grm/error.h b/lib/grm/include/grm/error.h index 581ee6b55..e2c99bddf 100644 --- a/lib/grm/include/grm/error.h +++ b/lib/grm/include/grm/error.h @@ -141,7 +141,8 @@ extern "C" { X(ERROR_LAYOUT_CONTRADICTING_ATTRIBUTES, 58) \ X(ERROR_LAYOUT_INVALID_ARGUMENT_RANGE, 59) \ X(ERROR_LAYOUT_COMPONENT_LENGTH_MISMATCH, 60) \ - Y(ERROR_NOT_IMPLEMENTED, 61) + X(ERROR_TMP_DIR_CREATION, 61) \ + Y(ERROR_NOT_IMPLEMENTED, 62) #define ENUM_VALUE(name, value) name = value, #define ENUM_LAST_VALUE(name, value) name = value diff --git a/lib/grm/src/grm/backtrace.c b/lib/grm/src/grm/backtrace.c index c6887f056..3afe4d4f9 100644 --- a/lib/grm/src/grm/backtrace.c +++ b/lib/grm/src/grm/backtrace.c @@ -51,6 +51,28 @@ static int signals[] = {SIGABRT, SIGSEGV}; /* ========================= functions ============================================================================== */ #ifdef BACKTRACE_AVAILABLE +static const char *get_tmp_directory_no_malloc(void) +{ + const char *tmp_dir; + const char *env_vars[] = { + "TMPDIR", + "TMP", + "TEMP", + "TEMPDIR", + }; + int i; + + for (i = 0; i < array_size(env_vars); ++i) + { + if ((tmp_dir = getenv(env_vars[i])) != NULL) + { + return tmp_dir; + } + } + + return "/tmp"; +} + void backtrace_init(void) { if (is_backtrace_enabled < 0) @@ -66,7 +88,7 @@ void backtrace_handler(int sig) char backtrace_filepath[MAX_FILEPATH_LENGTH]; int backtrace_fd; - snprintf(backtrace_filepath, MAX_FILEPATH_LENGTH, "%s/grm_backtrace", get_tmp_directory()); + snprintf(backtrace_filepath, MAX_FILEPATH_LENGTH, "%s/grm_backtrace", get_tmp_directory_no_malloc()); frames = backtrace(callstack, MAX_CALLSTACK_DEPTH); backtrace_fd = open(backtrace_filepath, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IRGRP | S_IROTH); backtrace_symbols_fd(callstack, frames, backtrace_fd); diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 52468623d..ad32fbae0 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -505,6 +505,11 @@ static string_map_entry_t key_to_formats[] = {{"a", "A"}, {"z_range", "D"}, {"z_log", "i"}}; +/* ------------------------- util ----------------------------------------------------------------------------------- */ + +static const char *grm_tmp_dir = NULL; + + /* ========================= functions ============================================================================== */ /* ------------------------- plot ----------------------------------------------------------------------------------- */ @@ -564,6 +569,8 @@ err_t plot_init_static_variables(void) } type_map = string_array_map_new_from_string_split(array_size(key_to_formats), key_to_formats, '|'); error_cleanup_and_set_error_if(type_map == nullptr, ERROR_MALLOC); + grm_tmp_dir = create_tmp_dir(); + error_cleanup_and_set_error_if(grm_tmp_dir == nullptr, ERROR_TMP_DIR_CREATION); install_backtrace_handler_if_enabled(); plot_static_variables_initialized = 1; } @@ -5839,6 +5846,7 @@ void grm_finalize(void) type_map = nullptr; grid_delete(global_grid); global_grid = nullptr; + delete_tmp_dir(); uninstall_backtrace_handler_if_enabled(); plot_static_variables_initialized = 0; } diff --git a/lib/grm/src/grm/util.c b/lib/grm/src/grm/util.c index 8b7d02b20..71c3a36cc 100644 --- a/lib/grm/src/grm/util.c +++ b/lib/grm/src/grm/util.c @@ -1,5 +1,6 @@ #ifdef __unix__ #define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 500 #endif /* ######################### includes ############################################################################### */ @@ -38,6 +39,11 @@ #define PRIVATE_NAME_BUFFER_LEN 80 +/* ========================= static variables ======================================================================= */ + +static const char *tmp_dir_ = NULL; + + /* ========================= functions ============================================================================== */ /* ------------------------- util ----------------------------------------------------------------------------------- */ @@ -63,6 +69,145 @@ void bin_data(unsigned int n, double *x, unsigned int num_bins, double *bins, do } } +/*! + * \brief Create an exclusive temporary directory. Consecutive calls return the same directory, unless deleted with + * delete_tmp_dir(). + * + * \return The path to the directory or `NULL` if an error occurred. + */ +const char *create_tmp_dir(void) +{ + char *system_tmp_dir = NULL, *tmp_dir = NULL; +#ifdef _WIN32 + LPWSTR tmp_dir_wide = NULL; +#endif + + if (tmp_dir_ == NULL) + { + const char *dirname_template = "grm.XXXXXX"; + size_t tmp_dir_len; + + system_tmp_dir = get_tmp_directory(); + tmp_dir_len = strlen(system_tmp_dir) + strlen(dirname_template) + 1; + tmp_dir = malloc(tmp_dir_len + 1); + error_cleanup_if(tmp_dir == NULL); + sprintf(tmp_dir, "%s%c%s", system_tmp_dir, PATH_SEPARATOR, dirname_template); +#ifdef _WIN32 + { + char *tmp_dir_utf8; + + tmp_dir_wide = convert_utf8_to_wstring(tmp_dir); + error_cleanup_if(_wmktemp_s(tmp_dir_wide, tmp_dir_len + 1) != ERROR_SUCCESS || + !CreateDirectoryW(tmp_dir_wide, NULL)); + tmp_dir_utf8 = convert_wstring_to_utf8(tmp_dir_wide); + error_cleanup_if(tmp_dir_utf8 == NULL); + free(tmp_dir); + tmp_dir = tmp_dir_utf8; + } +#else + error_cleanup_if(mkdtemp(tmp_dir) == NULL); +#endif + + tmp_dir_ = tmp_dir; + } + + goto cleanup; + +error_cleanup: + free(tmp_dir); + tmp_dir = NULL; + +cleanup: + free(system_tmp_dir); +#ifdef _WIN32 + free(tmp_dir_wide); +#endif + + return tmp_dir_; +} + +#ifdef _WIN32 +BOOL remove_directory_recursively(LPCWSTR dir_path) +{ + WIN32_FIND_DATAW find_file_data; + wchar_t search_pattern[MAX_PATH]; + HANDLE find_handle = INVALID_HANDLE_VALUE; + BOOL successful = FALSE; + + cleanup_if(_snwprintf_s(search_pattern, MAX_PATH, MAX_PATH, L"%s\\*", dir_path) < 0); + find_handle = FindFirstFileW(search_pattern, &find_file_data); + cleanup_if(find_handle == INVALID_HANDLE_VALUE); + + do + { + wchar_t current_path[MAX_PATH]; + if (wcscmp(find_file_data.cFileName, L".") == 0 || wcscmp(find_file_data.cFileName, L"..") == 0) + { + continue; + } + cleanup_if(_snwprintf_s(current_path, MAX_PATH, MAX_PATH, L"%s\\%s", dir_path, find_file_data.cFileName) < 0); + if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + cleanup_if(!remove_directory_recursively(current_path)); + } + else + { + cleanup_if(!DeleteFileW(current_path)); + } + } + while (FindNextFileW(find_handle, &find_file_data)); + + cleanup_if(!RemoveDirectoryW(dir_path)); + + successful = TRUE; + +cleanup: + if (find_handle != INVALID_HANDLE_VALUE) + { + FindClose(find_handle); + } + + return successful; +} +#else +static int remove_callback(const char *fpath, const struct stat *sb UNUSED, int typeflag UNUSED, + struct FTW *ftwbuf UNUSED) +{ + int rv = remove(fpath); + if (rv) perror(fpath); + return rv; +} +#endif + +/*! + * \brief Delete the temporary directory and all its contents created by create_tmp_dir(). + */ +/*! + * \brief Delete the temporary directory and all its contents created by create_tmp_dir(). + */ +int delete_tmp_dir(void) +{ + int successful = 0; + if (tmp_dir_ == NULL) return 0; +#ifdef _WIN32 + { + LPWSTR tmp_dir_wide = convert_utf8_to_wstring(tmp_dir_); + if (tmp_dir_wide == NULL) return 0; + successful = remove_directory_recursively(tmp_dir_wide); + free(tmp_dir_wide); + } +#else + successful = (nftw(tmp_dir_, remove_callback, 64, FTW_DEPTH | FTW_PHYS) == 0); +#endif + if (successful) + { + free((void *)tmp_dir_); + tmp_dir_ = NULL; + } + + return successful; +} + void linspace(double start, double end, unsigned int n, double *x) { unsigned int i; @@ -326,75 +471,85 @@ int file_exists(const char *file_path) #endif } -char *get_gr_dir(void) +char *get_env_variable(const char *name) { #ifdef _WIN32 - DWORD env_variable_char_count; - LPWSTR env_variable_value_wide = NULL; - LPSTR env_variable_value_utf8 = NULL; - DWORD error; - - env_variable_char_count = GetEnvironmentVariableW(L"GRDIR", NULL, 0) + 1; - error = GetLastError(); - if (error == ERROR_ENVVAR_NOT_FOUND) - { - return _strdup(GRDIR); - } - else if (error != ERROR_SUCCESS) - { - goto error_cleanup; - } - env_variable_value_wide = malloc(sizeof(wchar_t) * env_variable_char_count); - error_cleanup_if(env_variable_value_wide == NULL); - GetEnvironmentVariableW(L"GRDIR", env_variable_value_wide, env_variable_char_count); - error_cleanup_if(GetLastError() != ERROR_SUCCESS); + DWORD char_count; + LPWSTR name_wide = NULL, value_wide = NULL; + LPSTR value_utf8 = NULL; - env_variable_value_utf8 = convert_wstring_to_utf8(env_variable_value_wide); - error_cleanup_if(env_variable_value_utf8 == NULL); + name_wide = convert_utf8_to_wstring(name); + error_cleanup_if(name_wide == NULL); + char_count = GetEnvironmentVariableW(name_wide, NULL, 0) + 1; + error_cleanup_if(char_count == 0); + value_wide = malloc(sizeof(wchar_t) * char_count); + error_cleanup_if(value_wide == NULL); + char_count = GetEnvironmentVariableW(name_wide, value_wide, char_count); + error_cleanup_if(char_count == 0); - free(env_variable_value_wide); + value_utf8 = convert_wstring_to_utf8(value_wide); + error_cleanup_if(value_utf8 == NULL); - return env_variable_value_utf8; + goto cleanup; error_cleanup: - free(env_variable_value_wide); - free(env_variable_value_utf8); + free(value_utf8); + value_utf8 = NULL; - return NULL; +cleanup: + free(name_wide); + free(value_wide); + + return value_utf8; #else - const char *env_variable_value; - if ((env_variable_value = getenv("GRDIR")) != NULL) + const char *value; + if ((value = getenv(name)) != NULL) { - return strdup(env_variable_value); + return strdup(value); + } + + return NULL; +#endif +} + +char *get_gr_dir(void) +{ + char *env_variable_value; + if ((env_variable_value = get_env_variable("GRDIR")) != NULL) + { + return env_variable_value; } else { return strdup(GRDIR); } -#endif } -const char *get_tmp_directory(void) +char *get_tmp_directory(void) { - const char *tmpdir; + char *tmp_dir; const char *env_vars[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR", }; - int i; + unsigned int i; for (i = 0; i < array_size(env_vars); ++i) { - if ((tmpdir = getenv(env_vars[i])) != NULL) + if ((tmp_dir = get_env_variable(env_vars[i])) != NULL) { - return tmpdir; + break; } } + if (tmp_dir == NULL) + { + tmp_dir = strdup("/tmp"); + } - return "/tmp"; + return tmp_dir; } #ifdef _WIN32 diff --git a/lib/grm/src/grm/util_int.h b/lib/grm/src/grm/util_int.h index 2384f4996..b9dbec129 100644 --- a/lib/grm/src/grm/util_int.h +++ b/lib/grm/src/grm/util_int.h @@ -126,6 +126,8 @@ extern "C" { /* ------------------------- util ----------------------------------------------------------------------------------- */ void bin_data(unsigned int num_points, double *points, unsigned int num_bins, double *bins, double *weights); +const char *create_tmp_dir(void); +int delete_tmp_dir(void); void linspace(double start, double end, unsigned int n, double *x); size_t djb2_hash(const char *str); int is_equidistant_array(unsigned int length, const double *x); @@ -144,8 +146,9 @@ const char *private_name(const char *public_name); unsigned long next_or_equal_power2(unsigned long num); int is_env_variable_enabled(const char *env_variable_name); int file_exists(const char *file_path); +char *get_env_variable(const char *name); char *get_gr_dir(void); -const char *get_tmp_directory(void); +char *get_tmp_directory(void); #ifdef _WIN32 char *convert_wstring_to_utf8(const wchar_t *wstring); wchar_t *convert_utf8_to_wstring(const char *utf8_bytes); diff --git a/lib/grm/test/internal_api/grm/bson_serialize_deserialize.c b/lib/grm/test/internal_api/grm/bson_serialize_deserialize.c index 65bacfe4f..3a8d96c4a 100644 --- a/lib/grm/test/internal_api/grm/bson_serialize_deserialize.c +++ b/lib/grm/test/internal_api/grm/bson_serialize_deserialize.c @@ -18,12 +18,12 @@ static const char *tmp_dir = NULL; -static char *create_tmp_dir(void) +static char *create_bson_tmp_dir(void) { char *tmp_dir_; const char *dirname_template, *system_tmp_dir; - dirname_template = "grm.XXXXXX"; + dirname_template = "grm.bson.XXXXXX"; system_tmp_dir = getenv("TMPDIR"); if (system_tmp_dir == NULL) { @@ -166,7 +166,7 @@ int main(void) signal(SIGABRT, signal_handler); signal(SIGSEGV, signal_handler); atexit(cleanup); - create_tmp_dir(); + create_bson_tmp_dir(); assert(tmp_dir != NULL); test_bson(); From 55c098cc168e9134b7af3e3b0c4becfcd810e2be Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 17 May 2024 20:03:50 +0200 Subject: [PATCH 42/48] [GRM] Merge public and private schemas to get a full one --- CMakeLists.txt | 5 + lib/grm/Makefile | 1 + lib/grm/include/grm/plot.h | 2 +- .../graphics_tree/private_schema.xsd | 731 ++++++++++++++++++ .../grm/dom_render/graphics_tree/schema.xsd | 6 +- lib/grm/src/grm/plot.cxx | 199 ++++- lib/grm/src/grm/plot_int.h | 4 + 7 files changed, 914 insertions(+), 34 deletions(-) create mode 100644 lib/grm/src/grm/dom_render/graphics_tree/private_schema.xsd diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e8ba8f6b..522c23cab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1425,6 +1425,11 @@ if(GR_INSTALL) DESTINATION ${CMAKE_INSTALL_DATADIR}/xml/GRM RENAME grm_graphics_tree_schema.xsd ) + install( + FILES lib/grm/src/grm/dom_render/graphics_tree/private_schema.xsd + DESTINATION ${CMAKE_INSTALL_DATADIR}/xml/GRM + RENAME grm_graphics_tree_private_schema.xsd + ) include(CMakePackageConfigHelpers) configure_package_config_file( "cmake/Config.cmake.in" "GRConfig.cmake" INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/GR" diff --git a/lib/grm/Makefile b/lib/grm/Makefile index a0fdb98de..c952f3662 100644 --- a/lib/grm/Makefile +++ b/lib/grm/Makefile @@ -143,6 +143,7 @@ install: cp -rp include/grm $(INCDIR)/ @if [ ! -d $(SHAREDIR)/xml/GRM ]; then mkdir -m 755 -p $(SHAREDIR)/xml/GRM; fi cp -p src/grm/dom_render/graphics_tree/schema.xsd $(SHAREDIR)/xml/GRM/grm_graphics_tree_schema.xsd + cp -p src/grm/dom_render/graphics_tree/private_schema.xsd $(SHAREDIR)/xml/GRM/grm_graphics_tree_private_schema.xsd ifeq ($(UNAME), Darwin) @if [ -d grplot/grplot.app ]; then \ $(MAKE) -C grplot -f makefile.mak install; \ diff --git a/lib/grm/include/grm/plot.h b/lib/grm/include/grm/plot.h index c41032709..ca7b3158e 100644 --- a/lib/grm/include/grm/plot.h +++ b/lib/grm/include/grm/plot.h @@ -67,7 +67,7 @@ EXPORT int get_focus_and_factor_from_dom(const int x1, const int y1, const int x std::shared_ptr &subplot_element); #if !defined(NO_XERCES_C) -EXPORT std::shared_ptr grm_load_graphics_tree_schema(void); +EXPORT std::shared_ptr grm_load_graphics_tree_schema(bool with_private_attributes = false); #endif #endif #endif /* ifndef GRM_PLOT_H_INCLUDED */ diff --git a/lib/grm/src/grm/dom_render/graphics_tree/private_schema.xsd b/lib/grm/src/grm/dom_render/graphics_tree/private_schema.xsd new file mode 100644 index 000000000..10ba424bd --- /dev/null +++ b/lib/grm/src/grm/dom_render/graphics_tree/private_schema.xsd @@ -0,0 +1,731 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd b/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd index 5b675790b..42117d84c 100644 --- a/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd +++ b/lib/grm/src/grm/dom_render/graphics_tree/schema.xsd @@ -12,6 +12,10 @@ + + + + @@ -25,7 +29,7 @@ - + diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index ad32fbae0..31cc8f256 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -76,6 +77,8 @@ extern "C" { /* ========================= constants ============================================================================== */ static const std::string SCHEMA_REL_FILEPATH = "share/xml/GRM/grm_graphics_tree_schema.xsd"; +static const std::string PRIVATE_SCHEMA_REL_FILEPATH = "share/xml/GRM/grm_graphics_tree_private_schema.xsd"; +static const std::string FULL_SCHEMA_FILENAME = "grm_graphics_tree_full_schema.xsd"; static const std::string ENABLE_XML_VALIDATION_ENV_KEY = "GRM_VALIDATE"; /* ========================= datatypes ============================================================================== */ @@ -5499,7 +5502,10 @@ class GraphicsTreeParseHandler : public DefaultHandler, public SaxErrorHandler, class SchemaParseHandler : public DefaultHandler, public SaxErrorHandler { public: - SchemaParseHandler(GRM::Document &document) : document_(document) {} + SchemaParseHandler(GRM::Document &document, GRM::Document *document_to_be_merged = nullptr) + : document_(document), document_to_be_merged_(document_to_be_merged) + { + } ~SchemaParseHandler() {} void startDocument() override {} @@ -5553,6 +5559,49 @@ class SchemaParseHandler : public DefaultHandler, public SaxErrorHandler */ void endElement(const XMLCh *const uri, const XMLCh *const localname, const XMLCh *const qname) override { + current_gr_element_ = insertion_parent_; + if (document_to_be_merged_ != nullptr) + { + if (current_gr_element_->localName() == "xs:element") + { + auto element_name_attribute = current_gr_element_->getAttribute("name"); + if (element_name_attribute.isString()) + { + std::stringstream selector; + /* + * It would be better to use `xs:element[name=""]` as selector, but `:element` is detected as + * pseudo class in the selector although it part of the name. Thus, use only `[name=""]` and check + * if any result is an `xs:element`. + */ + selector << "[name=\"" << static_cast(element_name_attribute) << "\"]"; + std::shared_ptr element_to_be_merged; + for (const auto &matched_element : document_to_be_merged_->querySelectorsAll(selector.str())) + { + if (matched_element->localName() == "xs:element") + { + element_to_be_merged = matched_element; + break; + } + } + if (element_to_be_merged) + { + merge_elements_(*current_gr_element_, *element_to_be_merged); + } + } + } + else if (current_gr_element_->localName() == "xs:schema") + { + /* + * At this point, the end of the schema document is reached. Append all attribute groups of the other + * document now. + */ + for (const auto &element : document_to_be_merged_->documentElement()->children()) + { + if (element->localName() != "xs:attributeGroup") continue; + current_gr_element_->appendChild(element); + } + } + } insertion_parent_ = insertion_parent_->parentElement(); } @@ -5564,11 +5613,35 @@ class SchemaParseHandler : public DefaultHandler, public SaxErrorHandler void resetErrors() override { SaxErrorHandler::resetErrors(); } +protected: + static void merge_elements_(GRM::Element &element, GRM::Element &element_to_be_merged) + { + for (auto &merge_child : element_to_be_merged.children()) + { + auto found_child = false; + for (const auto &child : element.children()) + { + if (child->localName() == merge_child->localName() && child->hasChildNodes() && + merge_child->hasChildNodes()) + { + merge_elements_(*child, *merge_child); + found_child = true; + break; + } + } + if (!found_child) + { + element.appendChild(merge_child); + } + } + } + private: std::string encode(std::optional chars = std::nullopt) { return xml_buffer_.encode(chars); } XMLStringBuffer xml_buffer_{"UTF-8"}; GRM::Document &document_; + GRM::Document *document_to_be_merged_; std::shared_ptr insertion_parent_, current_gr_element_; }; } // namespace XERCES_CPP_NAMESPACE @@ -5585,13 +5658,13 @@ int grm_load_graphics_tree(FILE *file) { using namespace XERCES_CPP_NAMESPACE; - std::string schema_filepath{std::string(get_gr_dir()) + "/" + SCHEMA_REL_FILEPATH}; - if (plot_init_static_variables() != ERROR_NONE) { return 0; } + std::string schema_filepath{get_merged_schema_filepath()}; + try { XMLPlatformUtils::Initialize(); @@ -5756,12 +5829,35 @@ int validate_graphics_tree_with_error_messages(void) #endif return 1; } +} + +#ifndef NO_XERCES_C +std::string get_merged_schema_filepath() +{ + if (plot_init_static_variables() != ERROR_NONE) + { + throw std::runtime_error("Initialization of static plot variables failed."); + } + + std::string schema_filepath{std::string(grm_tmp_dir) + PATH_SEPARATOR + FULL_SCHEMA_FILENAME}; + if (!file_exists(schema_filepath.c_str())) + { + const unsigned int indent = 2; + auto merged_schema = grm_load_graphics_tree_schema(true); + std::ofstream schema_file{schema_filepath}; + schema_file << toXML(merged_schema, GRM::SerializerOptions{std::string(indent, ' ')}); + } + return schema_filepath; +} +#endif /* ========================= methods ================================================================================ */ /* ------------------------- args set ------------------------------------------------------------------------------- */ +extern "C" { + DEFINE_SET_METHODS(args) int args_set_entry_copy(args_set_entry_t *copy, args_set_const_entry_t entry) @@ -6450,11 +6546,13 @@ int grm_validate(void) /* ~~~~~~~~~~~~~~~~~~~~~~~~~ c++ xerces util ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #ifndef NO_XERCES_C -std::shared_ptr grm_load_graphics_tree_schema(void) +std::shared_ptr grm_load_graphics_tree_schema(bool with_private_attributes) { using namespace XERCES_CPP_NAMESPACE; - std::string schema_filepath{std::string(get_gr_dir()) + "/" + SCHEMA_REL_FILEPATH}; + const std::string gr_dir{get_gr_dir()}; + const std::string schema_filepath{gr_dir + PATH_SEPARATOR + SCHEMA_REL_FILEPATH}; + const std::string private_schema_filepath{gr_dir + PATH_SEPARATOR + PRIVATE_SCHEMA_REL_FILEPATH}; try { @@ -6463,43 +6561,80 @@ std::shared_ptr grm_load_graphics_tree_schema(void) catch (const XMLException &e) { std::cerr << "Error during initialization! :\n" << TranscodeToUtf8Str(e.getMessage()) << std::endl; - return 0; + return nullptr; } - auto document = GRM::createDocument(); + std::shared_ptr private_schema_document; XMLSize_t errorCount = 0; - { - auto parser = - std::unique_ptr(static_cast(XMLReaderFactory::createXMLReader())); + if (with_private_attributes) + { + private_schema_document = GRM::createDocument(); + auto parser = + std::unique_ptr(static_cast(XMLReaderFactory::createXMLReader())); - // Activate validation - parser->setFeature(XMLUni::fgSAX2CoreValidation, false); - parser->setFeature(XMLUni::fgXercesDynamic, false); - parser->setFeature(XMLUni::fgXercesSchema, false); - parser->setFeature(XMLUni::fgXercesSchemaFullChecking, false); + // Deactivate validation since there is no schema to validate the schema + parser->setFeature(XMLUni::fgSAX2CoreValidation, false); + parser->setFeature(XMLUni::fgXercesDynamic, false); + parser->setFeature(XMLUni::fgXercesSchema, false); + parser->setFeature(XMLUni::fgXercesSchemaFullChecking, false); - try - { - SchemaParseHandler handler(*document); - parser->setContentHandler(&handler); - parser->setErrorHandler(static_cast(&handler)); - parser->parse(schema_filepath.c_str()); - errorCount = parser->getErrorCount(); - } - catch (const OutOfMemoryException &) - { - std::cerr << "OutOfMemoryException" << std::endl; - } - catch (const XMLException &e) + try + { + SchemaParseHandler handler(*private_schema_document); + parser->setContentHandler(&handler); + parser->setErrorHandler(static_cast(&handler)); + parser->parse(private_schema_filepath.c_str()); + errorCount = parser->getErrorCount(); + } + catch (const OutOfMemoryException &) + { + std::cerr << "OutOfMemoryException" << std::endl; + } + catch (const XMLException &e) + { + std::cerr << "\nAn error occurred\n Error: " << TranscodeToUtf8Str(e.getMessage()) << "\n" << std::endl; + } + } + + std::shared_ptr schema_document; + if (errorCount == 0) + { + errorCount = 0; + schema_document = GRM::createDocument(); { - std::cerr << "\nAn error occurred\n Error: " << TranscodeToUtf8Str(e.getMessage()) << "\n" << std::endl; - } + auto parser = + std::unique_ptr(static_cast(XMLReaderFactory::createXMLReader())); - } // `parser` must be freed before `XMLPlatformUtils::Terminate()` is called + // Deactivate validation since there is no schema to validate the schema + parser->setFeature(XMLUni::fgSAX2CoreValidation, false); + parser->setFeature(XMLUni::fgXercesDynamic, false); + parser->setFeature(XMLUni::fgXercesSchema, false); + parser->setFeature(XMLUni::fgXercesSchemaFullChecking, false); + + try + { + SchemaParseHandler handler(*schema_document, + with_private_attributes ? private_schema_document.get() : nullptr); + parser->setContentHandler(&handler); + parser->setErrorHandler(static_cast(&handler)); + parser->parse(schema_filepath.c_str()); + errorCount = parser->getErrorCount(); + } + catch (const OutOfMemoryException &) + { + std::cerr << "OutOfMemoryException" << std::endl; + } + catch (const XMLException &e) + { + std::cerr << "\nAn error occurred\n Error: " << TranscodeToUtf8Str(e.getMessage()) << "\n" << std::endl; + } + + } // `parser` must be freed before `XMLPlatformUtils::Terminate()` is called + } XMLPlatformUtils::Terminate(); - return (errorCount == 0) ? document : nullptr; + return (errorCount == 0) ? schema_document : nullptr; } #endif diff --git a/lib/grm/src/grm/plot_int.h b/lib/grm/src/grm/plot_int.h index 956f43b53..819686242 100644 --- a/lib/grm/src/grm/plot_int.h +++ b/lib/grm/src/grm/plot_int.h @@ -217,4 +217,8 @@ int validate_graphics_tree_with_error_messages(void); } #endif +#if defined(__cplusplus) && !defined(NO_XERCES_C) +std::string get_merged_schema_filepath(); +#endif + #endif /* ifndef GRM_PLOT_INT_H_INCLUDED */ From ac172bafa579309faadf88ff88eb4abf2bf12258 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Tue, 16 Jul 2024 16:47:14 +0200 Subject: [PATCH 43/48] [GRM] Validate against the merged schema in debug mode --- lib/grm/src/grm/plot.cxx | 20 +++++++++++--------- lib/grm/src/grm/plot_int.h | 11 +++-------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 31cc8f256..a17a6baf2 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -5722,17 +5722,19 @@ int grm_load_graphics_tree(FILE *file) return errorCount == 0; } +} /*! * \brief Validate the currently loaded grapics tree against the internal XML schema definition. * * \return ERROR_NONE on success, an error code on failure. */ -err_t validate_graphics_tree(void) +err_t validate_graphics_tree(bool include_private_attributes) { using namespace XERCES_CPP_NAMESPACE; - std::string schema_filepath{std::string(get_gr_dir()) + PATH_SEPARATOR + SCHEMA_REL_FILEPATH}; + auto schema_filepath{include_private_attributes ? get_merged_schema_filepath() + : (std::string(get_gr_dir()) + PATH_SEPARATOR + SCHEMA_REL_FILEPATH)}; if (!file_exists(schema_filepath.c_str())) { return ERROR_PARSE_XML_NO_SCHEMA_FILE; @@ -5768,7 +5770,10 @@ err_t validate_graphics_tree(void) { SaxErrorHandler error_handler(schema_filepath); parser->setErrorHandler(&error_handler); - parser->parse(StringInputSource(toXML(global_root))); + parser->parse(StringInputSource(toXML( + global_root, GRM::SerializerOptions{"", include_private_attributes + ? GRM::SerializerOptions::InternalAttributesFormat::Plain + : GRM::SerializerOptions::InternalAttributesFormat::None}))); errorCount = parser->getErrorCount(); schema_invalid = error_handler.schema_invalid().value(); } @@ -5788,12 +5793,9 @@ err_t validate_graphics_tree(void) return schema_invalid ? ERROR_PARSE_XML_INVALID_SCHEMA : ((errorCount == 0) ? ERROR_NONE : ERROR_PARSE_XML_FAILED_SCHEMA_VALIDATION); } -} #endif -extern "C" { - /*! * \brief Validate the currently loaded graphics tree and print error messages to stderr. * @@ -5801,10 +5803,10 @@ extern "C" { * * \return 1 on success, 0 on failure. */ -int validate_graphics_tree_with_error_messages(void) +bool validate_graphics_tree_with_error_messages() { #ifndef NO_XERCES_C - err_t validation_error = validate_graphics_tree(); + err_t validation_error = validate_graphics_tree(true); if (validation_error == ERROR_NONE) { fprintf(stderr, "The internal graphics tree passed the validity check.\n"); @@ -5829,7 +5831,7 @@ int validate_graphics_tree_with_error_messages(void) #endif return 1; } -} + #ifndef NO_XERCES_C std::string get_merged_schema_filepath() diff --git a/lib/grm/src/grm/plot_int.h b/lib/grm/src/grm/plot_int.h index 819686242..10fa20629 100644 --- a/lib/grm/src/grm/plot_int.h +++ b/lib/grm/src/grm/plot_int.h @@ -205,18 +205,13 @@ void load_context_str(GRM::Context &context, const std::string &context_str, dum /* ------------------------- xml ------------------------------------------------------------------------------------ */ #ifdef __cplusplus -extern "C" { -#endif - #ifndef NO_XERCES_C -err_t validate_graphics_tree_xml(void); +err_t validate_graphics_tree(bool include_private_attributes = false); #endif -int validate_graphics_tree_with_error_messages(void); - -#ifdef __cplusplus -} +bool validate_graphics_tree_with_error_messages(); #endif + #if defined(__cplusplus) && !defined(NO_XERCES_C) std::string get_merged_schema_filepath(); #endif From 5307cd3d4de8e8e7de9431e5a4959682fe2d7392 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Tue, 16 Jul 2024 17:08:59 +0200 Subject: [PATCH 44/48] [GRM] Deactivate (global) auto update XML schema loading --- lib/grm/src/grm/plot.cxx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index a17a6baf2..03db54fdb 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -6566,6 +6566,10 @@ std::shared_ptr grm_load_graphics_tree_schema(bool with_private_a return nullptr; } + bool auto_update; + global_render->getAutoUpdate(&auto_update); + global_render->setAutoUpdate(false); + std::shared_ptr private_schema_document; XMLSize_t errorCount = 0; if (with_private_attributes) @@ -6636,6 +6640,8 @@ std::shared_ptr grm_load_graphics_tree_schema(bool with_private_a XMLPlatformUtils::Terminate(); + global_render->setAutoUpdate(auto_update); + return (errorCount == 0) ? schema_document : nullptr; } #endif From 8328f78e26d368476d03e40918c1336590206213 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Thu, 18 Jul 2024 15:01:37 +0200 Subject: [PATCH 45/48] [GRM] Check individually if x and y labels are defined in the tree --- lib/grm/src/grm/interaction.cxx | 73 ++++++++++++++------------------- 1 file changed, 30 insertions(+), 43 deletions(-) diff --git a/lib/grm/src/grm/interaction.cxx b/lib/grm/src/grm/interaction.cxx index 3f3396073..503b0d626 100644 --- a/lib/grm/src/grm/interaction.cxx +++ b/lib/grm/src/grm/interaction.cxx @@ -1300,36 +1300,31 @@ err_t get_tooltips(int mouse_x, int mouse_y, err_t (*tooltip_callback)(int, int, auto coordinate_system = subplot_element->querySelectors("coordinate_system"); auto left_side_region = subplot_element->querySelectors("side_region[location=\"left\"]"); auto bottom_side_region = subplot_element->querySelectors("side_region[location=\"bottom\"]"); - std::vector> label_vec; + struct + { + std::shared_ptr x, y; + } label_elements{}; - if (bottom_side_region && bottom_side_region->hasAttribute("text_content")) label_vec.push_back(bottom_side_region); - if (left_side_region && left_side_region->hasAttribute("text_content")) label_vec.push_back(left_side_region); + if (bottom_side_region && bottom_side_region->hasAttribute("text_content")) label_elements.x = bottom_side_region; + if (left_side_region && left_side_region->hasAttribute("text_content")) label_elements.y = left_side_region; - if (label_vec.empty()) + if (label_elements.x && label_elements.x->hasAttribute("text_content")) + { + static auto xlabel = static_cast(label_elements.x->getAttribute("text_content")); + info->xlabel = (char *)xlabel.c_str(); + } + else { info->xlabel = (char *)"x"; - info->ylabel = (char *)"y"; + } + if (label_elements.y && label_elements.y->hasAttribute("text_content")) + { + static auto ylabel = static_cast(label_elements.y->getAttribute("text_content")); + info->ylabel = (char *)ylabel.c_str(); } else { - if (!label_vec[0]->hasAttribute("text_content")) - { - info->xlabel = (char *)"x"; - } - else - { - static auto xlabel = static_cast(label_vec[0]->getAttribute("text_content")); - info->xlabel = (char *)xlabel.c_str(); - } - if (!label_vec[1]->hasAttribute("text_content")) - { - info->ylabel = (char *)"y"; - } - else - { - static auto ylabel = static_cast(label_vec[1]->getAttribute("text_content")); - info->ylabel = (char *)ylabel.c_str(); - } + info->ylabel = (char *)"y"; } x_range_min = (double)(mouse_x - 50) / max_width_height; @@ -1690,31 +1685,23 @@ err_t get_tooltips(int mouse_x, int mouse_y, err_t (*tooltip_callback)(int, int, info->y_px = -1; info->x = 0; info->y = 0; - if (label_vec.empty()) + if (label_elements.x && label_elements.x->hasAttribute("text_content")) + { + static auto xlabel = static_cast(label_elements.x->getAttribute("text_content")); + info->xlabel = (char *)xlabel.c_str(); + } + else { info->xlabel = (char *)"x"; - info->ylabel = (char *)"y"; + } + if (label_elements.y && label_elements.y->hasAttribute("text_content")) + { + static auto ylabel = static_cast(label_elements.y->getAttribute("text_content")); + info->ylabel = (char *)ylabel.c_str(); } else { - if (!label_vec[0]->hasAttribute("text_content")) - { - info->xlabel = (char *)"x"; - } - else - { - static auto xlabel = static_cast(label_vec[0]->getAttribute("text_content")); - info->xlabel = (char *)xlabel.c_str(); - } - if (!label_vec[1]->hasAttribute("text_content")) - { - info->ylabel = (char *)"y"; - } - else - { - static auto ylabel = static_cast(label_vec[1]->getAttribute("text_content")); - info->ylabel = (char *)ylabel.c_str(); - } + info->ylabel = (char *)"y"; } info->label = (char *)""; return_error_if(info == nullptr, ERROR_MALLOC); From e8410e3d3d967076e2706862a4a94cc32ce8c650 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 19 Jul 2024 16:24:22 +0200 Subject: [PATCH 46/48] [GRM] Fix the BSON encoder to not escape `\` and `"` `\` and `"` must be escaped in JSON strings but not in BSON, so remove the escaping code. --- lib/grm/src/grm/bson.c | 81 +++++--------------------------------- lib/grm/src/grm/bson_int.h | 1 - 2 files changed, 9 insertions(+), 73 deletions(-) diff --git a/lib/grm/src/grm/bson.c b/lib/grm/src/grm/bson.c index da05ec145..7d04af893 100644 --- a/lib/grm/src/grm/bson.c +++ b/lib/grm/src/grm/bson.c @@ -1102,21 +1102,15 @@ err_t tobson_double_value(memwriter_t *memwriter, double value) err_t tobson_string_value(memwriter_t *memwriter, char *value) { - char *escaped_chars = NULL; - unsigned int length = 0; err_t error = ERROR_NONE; char *length_as_bytes; - if ((error = tobson_escape_special_chars(&escaped_chars, value, &length))) - { - goto cleanup; - } - int_to_bytes(length + 1, &length_as_bytes); /* plus one for the null byte */ + int_to_bytes(strlen(value) + 1, &length_as_bytes); /* plus one for the null byte */ if ((error = memwriter_puts_with_len(memwriter, length_as_bytes, sizeof(int))) != ERROR_NONE) { goto cleanup; } - if ((error = memwriter_printf(memwriter, "%s", escaped_chars)) != ERROR_NONE) + if ((error = memwriter_printf(memwriter, "%s", value)) != ERROR_NONE) { goto cleanup; } @@ -1126,7 +1120,6 @@ err_t tobson_string_value(memwriter_t *memwriter, char *value) } cleanup: - free(escaped_chars); free(length_as_bytes); return error; } @@ -1639,7 +1632,6 @@ err_t tobson_optimized_array(tobson_state_t *state) err_t tobson_char_array(tobson_state_t *state) { char *chars; - char *escaped_chars = NULL; unsigned int length; err_t error = ERROR_NONE; @@ -1664,31 +1656,28 @@ err_t tobson_char_array(tobson_state_t *state) { debug_print_error(("The given array length \"%s\" is no valid number; the array contents will be ignored.", state->additional_type_info)); - goto cleanup; + return error; } } else { if (state->shared->read_length_from_string) { - length = 0; + length = strlen(chars); } else { length = state->shared->array_length; } } - if ((error = tobson_escape_special_chars(&escaped_chars, chars, &length)) != ERROR_NONE) - { - goto cleanup; - } - if ((error = memwriter_printf(state->memwriter, "\"%.*s\"", length, escaped_chars)) != ERROR_NONE) + /* TODO: Is it correct that `chars` is written twice here? */ + if ((error = memwriter_printf(state->memwriter, "\"%.*s\"", length, chars)) != ERROR_NONE) { - goto cleanup; + return error; } - if ((error = tobson_string_value(state->memwriter, escaped_chars)) != ERROR_NONE) + if ((error = tobson_string_value(state->memwriter, chars)) != ERROR_NONE) { - goto cleanup; + return error; } state->shared->wrote_output = 1; @@ -1698,8 +1687,6 @@ err_t tobson_char_array(tobson_state_t *state) state->shared->data_offset += sizeof(char *); } -cleanup: - free(escaped_chars); return error; } @@ -2319,56 +2306,6 @@ err_t tobson_unzip_membernames_and_datatypes(char *mixed_ptr, char ***member_nam return ERROR_NONE; } -err_t tobson_escape_special_chars(char **escaped_string, const char *unescaped_string, unsigned int *length) -{ - /* characters '\' and '"' must be escaped before written to a bson string value */ - /* length can be `0` -> use `strlen(unescaped_string)` instead */ - const char *src_ptr; - char *dest_ptr; - size_t needed_memory; - unsigned int remaining_chars; - unsigned int len; - const char *chars_to_escape = "\\\""; - - len = (length != NULL && *length != 0) ? *length : strlen(unescaped_string); - needed_memory = len + 1; /* reserve memory for the terminating `\0` character */ - src_ptr = unescaped_string; - remaining_chars = len; - while (remaining_chars) - { - if (strchr(chars_to_escape, *src_ptr) != NULL) - { - ++needed_memory; - } - ++src_ptr; - --remaining_chars; - } - dest_ptr = malloc(needed_memory); - if (dest_ptr == NULL) - { - return ERROR_MALLOC; - } - *escaped_string = dest_ptr; - src_ptr = unescaped_string; - remaining_chars = len; - while (remaining_chars) - { - if (strchr(chars_to_escape, *src_ptr) != NULL) - { - *dest_ptr++ = '\\'; - } - *dest_ptr++ = *src_ptr++; - --remaining_chars; - } - *dest_ptr = '\0'; - if (length != NULL) - { - *length = needed_memory - 1; - } - - return ERROR_NONE; -} - err_t tobson_serialize(memwriter_t *memwriter, char *data_desc, const void *data, va_list *vl, int apply_padding, int add_data, int add_data_without_separator, unsigned int *struct_nested_level, tojson_serialization_result_t *serial_result, tobson_shared_state_t *shared_state) diff --git a/lib/grm/src/grm/bson_int.h b/lib/grm/src/grm/bson_int.h index 1ffa100e8..a90387cbc 100644 --- a/lib/grm/src/grm/bson_int.h +++ b/lib/grm/src/grm/bson_int.h @@ -166,7 +166,6 @@ int tobson_get_member_count(const char *data_desc); int tobson_is_bson_array_needed(const char *data_desc); void tobson_read_datatype(tobson_state_t *state); err_t tobson_unzip_membernames_and_datatypes(char *mixed_ptr, char ***member_name_ptr, char ***data_type_ptr); -err_t tobson_escape_special_chars(char **escaped_string, const char *unescaped_string, unsigned int *length); err_t tobson_serialize(memwriter_t *memwriter, char *data_desc, const void *data, va_list *vl, int apply_padding, int add_data, int add_data_without_separator, unsigned int *struct_nested_level, tojson_serialization_result_t *serial_result, tobson_shared_state_t *shared_state); From b25bd4358cc82c91510dd1bcfd04ea3922a2f024 Mon Sep 17 00:00:00 2001 From: Ingo Meyer Date: Fri, 19 Jul 2024 16:32:12 +0200 Subject: [PATCH 47/48] [GRM] Reset `scale` (log) on axes when loading a graphics tree This is only a temporary fix because all attributes should be set to defaults after rendering and should not be preserved for a new plot. --- lib/grm/src/grm/plot.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/grm/src/grm/plot.cxx b/lib/grm/src/grm/plot.cxx index 03db54fdb..4df58247c 100644 --- a/lib/grm/src/grm/plot.cxx +++ b/lib/grm/src/grm/plot.cxx @@ -5663,6 +5663,8 @@ int grm_load_graphics_tree(FILE *file) return 0; } + gr_setscale(0); // TODO: Check why scale is not restored after a render call in `render.cxx` automatically + std::string schema_filepath{get_merged_schema_filepath()}; try From ab55a1b6fd2308980c683bec161325f2040d33d2 Mon Sep 17 00:00:00 2001 From: David Gustavsson Date: Sat, 20 Jul 2024 14:24:51 +0200 Subject: [PATCH 48/48] Adaptive superscript height --- lib/gr/mathtex2.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/gr/mathtex2.c b/lib/gr/mathtex2.c index 87111fe56..c36f0b294 100644 --- a/lib/gr/mathtex2.c +++ b/lib/gr/mathtex2.c @@ -2733,7 +2733,6 @@ static size_t convert_subsuper_to_box_model(ParserNode *node, size_t previous_bo sub_and_super_filled = 1; } } - /* TODO: better default height? */ previous_node_height = state->fontsize; if (previous_node) { @@ -2754,26 +2753,27 @@ static size_t convert_subsuper_to_box_model(ParserNode *node, size_t previous_bo { previous_node_width = tmp_hlist->u.hlist.width; } - if (is_overunder) - { - padding = -previous_node_width / 2 - body_width / 2; - previous_node_kern_width = body_width / 2 - default_thickness * 2; - } } else if (!isnan(previous_node->u.hlist.subsuper_height)) { previous_node_height = previous_node->u.hlist.subsuper_height; previous_node_depth = previous_node->u.hlist.subsuper_depth; previous_node_width = previous_node->u.hlist.subsuper_width; - if (previous_node_width < 0) - { - previous_node_width = -previous_node_width; - } - if (is_overunder) - { - padding = -previous_node_width / 2 - body_width / 2; - previous_node_kern_width = body_width / 2 - default_thickness * 2; - } + } + else + { + previous_node_depth = previous_node->u.hlist.depth; + previous_node_width = previous_node->u.hlist.width; + previous_node_height = previous_node->u.hlist.height; + } + if (previous_node_width < 0) + { + previous_node_width = -previous_node_width; + } + if (is_overunder) + { + padding = -previous_node_width / 2 - body_width / 2; + previous_node_kern_width = body_width / 2 - default_thickness * 2; } } if (previous_node_kern_index)