diff --git a/ChangeLog b/ChangeLog index d7b158e6..73e0afe6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,37 @@ +2019-06-22 (0.12.15) + FLTK: reactivated the FLTK port using FLTK v1.4 + +2019-03-02 (0.12.15) + COMMON: Implemented - FOR character IN string + +2019-03-02 (0.12.15) + ANDROID: In editor, hide status when scrolled + +2019-02-26 (0.12.15) + ANDROID: Fix POINT returning transposed red + blue + +2019-02-23 (0.12.15) + ANDROID: Fix crash entering c,v in edit control mode + +2019-02-14 (0.12.15) + ANDROID: fix crash passing negative duration to SOUND command + +2019-02-11 (0.12.15) + UI: ALT+F4 from edit/run now returns to edit + UI: avoid flicker from with ALT+F4 -e edit startup + +2019-02-02 (0.12.15) + UI: Edit enter now continues prior line comment + +2019-01-21 (0.12.15.1) + ANDROID: fix setup screen colour display + ANDROID: show link to android page in about + +2019-01-19 (0.12.15) + UI: added kill-word editor command (alt+d) + UI: added select-word editor command (alt+w) + UI: find command primed from editor selection + 2018-12-28 (0.12.15) Fix crash when using GOTO with a non-existent label Fix crash in editor when double tapping empty document diff --git a/Makefile.am b/Makefile.am index dc6d0f6c..3dcbe27d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,8 +16,11 @@ test: leak-test: (cd src/platform/console && make leak-test) +fuzz-test: + (cd src/platform/console && make fuzz-test) + cppcheck: - (cppcheck --quiet --enable=all src/common src/ui src/platform/android/jni src/platform/sdl) + (cppcheck --quiet --enable=all src/common src/ui src/platform/android/jni src/platform/sdl src/platform/fltk) covcheck: (make clean -s && \ diff --git a/README.md b/README.md index 5391bb3f..2fd9e1a7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ SmallBASIC is a fast and easy to learn BASIC language interpreter ideal for ever Initial setup on linux ``` - $ sudo apt-get install git autotools-dev automake gcc g++ libsdl2-dev libfreetype6-dev libfontconfig1-dev + $ sudo apt-get install git autotools-dev automake gcc g++ libsdl2-dev libfreetype6-dev libfontconfig1-dev xxd $ git clone https://github.com/smallbasic/SmallBASIC.git $ cd SmallBASIC $ sh autogen.sh @@ -117,6 +117,25 @@ Useful adb commands for debugging: adb shell dumpsys cpuinfo adb shell top -m 10 +### Building the FLTK version + +1. Install and build FLTK 1.4 + +``` +$ cd ~/github +$ git clone https://github.com/fltk/fltk.git +$ sudo make install + +``` + +2. Build + +``` +$ cd ~/github/SmallBASIC +$ ./configure --enable-fltk +$ make -s +``` + ### .indent.pro settings ``` -brf -nbap -br -brs -cdb -cdw -ce -cli0 -fca -i2 -l110 -lc110 -lp diff --git a/configure.ac b/configure.ac index af504e20..5d295b8c 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl dnl Configure script for SmallBASIC dnl -dnl Copyright(C) 2001-2018 Chris Warren-Smith. +dnl Copyright(C) 2001-2019 Chris Warren-Smith. dnl dnl This program is distributed under the terms of the GPL v2.0 dnl Download the GNU Public License (GPL) from www.gnu.org @@ -39,6 +39,11 @@ AC_ARG_ENABLE(web, [ac_build_web="yes"], [ac_build_web="no"]) +AC_ARG_ENABLE(fltk, + AS_HELP_STRING([--enable-fltk],[build fltk version(default=no)]), + [ac_build_fltk="yes"], + [ac_build_fltk="no"]) + AC_ARG_ENABLE(dist, AS_HELP_STRING([--enable-dist],[prepare to run make dist(default=no)]), [ac_build_dist="yes"], @@ -156,6 +161,11 @@ function buildSDL() { AC_MSG_ERROR([libfreetype6-dev not installed: configure failed.]) fi + AC_CHECK_PROG(have_xxd, xxd, [yes], [no]) + if test "${have_xxd}" = "no" ; then + AC_MSG_ERROR([xxd command not installed: configure failed.]) + fi + case "${host_os}" in *mingw* | cygwin*) dnl avoid using MSCRT versions of printf for long double @@ -206,15 +216,13 @@ function buildSDL() { dnl preconfigured values for SDL build AC_DEFINE(_SDL, 1, [Defined when building SDL version]) AC_DEFINE(_UnixOS, 1, [Building under Unix like systems.]) - AC_DEFINE(IMPL_DEV_DELAY, 1, [Driver implements dev_delay()]) - AC_DEFINE(IMPL_LOG_WRITE, 1, [Driver implements lwrite()]) AC_DEFINE(IMPL_DEV_READ, 1, [Implement dev_read()]) + AC_DEFINE(IMPL_DEV_DELAY, 1, [Driver implements dev_delay()]) AC_DEFINE(IMPL_LOG_WRITE, 1, [Driver implements lwrite()]) BUILD_SUBDIRS="src/common src/platform/sdl" AC_SUBST(BUILD_SUBDIRS) (cd src/platform/android/app/src/main/assets && xxd -i main.bas > ../../../../../../../src/platform/sdl/main_bas.h) - (cd documentation && g++ -o build_kwp build_kwp.cpp && ./build_kwp > ../src/ui/kwp.h) } function buildAndroid() { @@ -233,8 +241,6 @@ function buildAndroid() { TEST_DIR="src/platform/android" AC_SUBST(TEST_DIR) - - (cd documentation && g++ -o build_kwp build_kwp.cpp && ./build_kwp > ../src/ui/kwp.h) } function buildConsole() { @@ -294,8 +300,6 @@ function buildConsole() { fi AC_SUBST(BUILD_SUBDIRS) - - (cd documentation && g++ -o build_kwp build_kwp.cpp && ./build_kwp > ../src/ui/kwp.h) } function buildWeb() { @@ -321,16 +325,74 @@ function buildWeb() { AC_SUBST(BUILD_SUBDIRS) } +function buildFLTK() { + TARGET="Building FLTK version." + + dnl Checks for FLTK 1.x + AC_CHECK_PROG(have_fltk, fltk-config, [yes], [no]) + + AC_CHECK_PROG(have_xxd, xxd, [yes], [no]) + if test "${have_xxd}" = "no" ; then + AC_MSG_ERROR([xxd command not installed: configure failed.]) + fi + + dnl avoid using MSCRT versions of printf for long double + case "${host_os}" in + *mingw* | cygwin*) + PACKAGE_CFLAGS="${PACKAGE_CFLAGS} -D__USE_MINGW_ANSI_STDIO" + esac + + FLTK_CXXFLAGS="${PACKAGE_CFLAGS} `fltk-config --cxxflags`" + FLTK_CXXFLAGS="${FLTK_CXXFLAGS} -fno-exceptions -fno-rtti -std=c++11 -Wno-unknown-pragmas" + PACKAGE_LIBS="${PACKAGE_LIBS} `fltk-config --ldstaticflags --use-images`" + + dnl do not depend on cygwin.dll under cygwin build + case "${host_os}" in + *mingw* | cygwin*) + FLTK_CXXFLAGS="${FLTK_CXXFLAGS} -mms-bitfields" + PACKAGE_LIBS="-Wl,-Bstatic ${PACKAGE_LIBS} -lwsock32 -lws2_32 -static-libgcc -static-libstdc++" + AC_DEFINE(_Win32, 1, [Windows build]) + ;; + + *) + (cd images && xxd -i sb-desktop-128x128.png > ../src/platform/fltk/icon.h) + xxd + esac + + defaultConditionals + + dnl preconfigured values for FLTK build + AC_DEFINE(_UnixOS, 1, [Building under Unix like systems.]) + AC_DEFINE(_FLTK, 1, [Defined for FLTK build.]) + AC_DEFINE(IMPL_DEV_READ, 1, [Implement dev_read()]) + AC_DEFINE(IMPL_DEV_DELAY, 1, [Driver implements dev_delay()]) + AC_DEFINE(IMPL_LOG_WRITE, 1, [Driver implements lwrite()]) + + BUILD_SUBDIRS="src/common src/platform/fltk" + AC_SUBST(BUILD_SUBDIRS) + AC_SUBST(FLTK_CXXFLAGS) + + desktopentrydir='$(datarootdir)'/applications + AC_SUBST(desktopentrydir) + + dnl generate kwp.h + (cd src/platform/fltk && g++ -o build_kwp build_kwp.cxx && ./build_kwp) +} + if test x$ac_build_sdl = xyes; then buildSDL elif test x$ac_build_android = xyes; then buildAndroid elif test x$ac_build_web = xyes; then buildWeb +elif test x$ac_build_fltk = xyes; then + buildFLTK else buildConsole fi +(cd documentation && g++ -o build_kwp build_kwp.cpp && ./build_kwp > ../src/ui/kwp.h) + checkPCRE checkTermios checkDebugMode @@ -363,6 +425,7 @@ src/platform/android/Makefile src/platform/console/Makefile src/platform/sdl/Makefile src/platform/web/Makefile +src/platform/fltk/Makefile ]) AC_OUTPUT diff --git a/debian/changelog b/debian/changelog index 259597e6..4a24ecc9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -6,7 +6,7 @@ smallbasic (0.12.15) unstable; urgency=low smallbasic (0.12.14) unstable; urgency=low * Various see web site - -- Chris Warren-Smith Sat, 15 Sept 2018 09:45:25 +1000 + -- Chris Warren-Smith Sat, 15 Sep 2018 09:45:25 +1000 smallbasic (0.12.13) unstable; urgency=low * Various see web site diff --git a/images/mainmenu.png b/images/mainmenu.png index 4d90fd41..756d90dc 100644 Binary files a/images/mainmenu.png and b/images/mainmenu.png differ diff --git a/images/sb-desktop-128x128.png b/images/sb-desktop-128x128.png index 5ff6e8b1..1ba86d12 100644 Binary files a/images/sb-desktop-128x128.png and b/images/sb-desktop-128x128.png differ diff --git a/images/sb-desktop-32x32.png b/images/sb-desktop-32x32.png index 39e71d1f..8f908c30 100644 Binary files a/images/sb-desktop-32x32.png and b/images/sb-desktop-32x32.png differ diff --git a/images/sb-desktop-64x64.png b/images/sb-desktop-64x64.png index 652da120..0be60c36 100644 Binary files a/images/sb-desktop-64x64.png and b/images/sb-desktop-64x64.png differ diff --git a/images/sb16x16.png b/images/sb16x16.png index 0c1ed2b2..7c359da1 100644 Binary files a/images/sb16x16.png and b/images/sb16x16.png differ diff --git a/images/sb32x32.png b/images/sb32x32.png index cc2f2531..3d1679d5 100644 Binary files a/images/sb32x32.png and b/images/sb32x32.png differ diff --git a/samples/distro-examples/tests/output/ref.out b/samples/distro-examples/tests/output/ref.out index 560f580a..7b33b3e5 100644 --- a/samples/distro-examples/tests/output/ref.out +++ b/samples/distro-examples/tests/output/ref.out @@ -1,13 +1,4 @@ cat= cat dog= dog -3= 3 -1= 1 -a=b a=b -a<>b a<>b -{"name":"kitchen"} -{"name":"hall"} -{"fridge":"empty","name":"Kitchen"} -{"name":"toilet","occupied":1} -{"fridge":"empty","name":"Kitchen"} -{"name":"hall"} -toilet +10 +20 diff --git a/samples/distro-examples/tests/ref.bas b/samples/distro-examples/tests/ref.bas index 5f4bd82b..58ee14c7 100644 --- a/samples/distro-examples/tests/ref.bas +++ b/samples/distro-examples/tests/ref.bas @@ -1,44 +1,26 @@ -' reference variable tests - -a = "cat" -b = @a - -print "cat=", b +rem reference variables not supported a = "dog" +b = @a +a = "cat" +print "cat=", a print "dog=", b -print "3=", len(b) -print "1=", not empty(b) -print "a=b", iFF(a==b, "a=b", "a<>b") -b = "goodbye a" -print "a<>b", iFF(a==b, "a=b", "a<>b") - -dim rooms -sub addRoom(byref room) - rooms << byref room +rem complex pseudo class method references +func C + func f(j) + return j + end + local r = {} + r.f = @f + return r end - -dim kitchen,hall,toilet -kitchen.name= "kitchen" -hall.name = "hall" -toilet.name ="toilet" - -addRoom(kitchen) -addRoom(hall) - -print rooms(0) -print rooms(1) - -kitchen.name = "Kitchen" -kitchen.fridge = "empty" -print rooms(0) - -insert rooms, 0, @toilet -toilet.occupied = true - -print rooms(0) -print rooms(1) -print rooms(2) - -roomref = byref rooms(0) -print roomref.name +func Q + local r = {} + r.c = C() + return r +end +c.m = Q() +j = [10,20] +for n in c.m.c.f(j) + print n +next n diff --git a/samples/distro-examples/tests/strings.bas b/samples/distro-examples/tests/strings.bas index 73dd682e..e7e65c58 100644 --- a/samples/distro-examples/tests/strings.bas +++ b/samples/distro-examples/tests/strings.bas @@ -181,3 +181,11 @@ s= "a\c\e" if mid(s, 2, 1) != "\\" then throw s if mid(s, 4, 1) != "\\" then throw s +REM FOR character IN string +s1 = "cats" +s2 = "" +for c in s1 + s2 += c +next c +if (s1 <> s2) then throw s2 + diff --git a/src/common/blib.c b/src/common/blib.c index b724886e..f0299b61 100644 --- a/src/common/blib.c +++ b/src/common/blib.c @@ -1559,6 +1559,7 @@ void cmd_for_in(bcip_t true_ip, bcip_t false_ip, var_p_t var_p) { node.x.vfor.var_ptr = var_p; node.x.vfor.to_expr_ip = prog_ip; node.x.vfor.flags = 0; + node.x.vfor.str_ptr = NULL; if (code_isvar()) { // array variable @@ -1571,19 +1572,28 @@ void cmd_for_in(bcip_t true_ip, bcip_t false_ip, var_p_t var_p) { v_detach(new_var); return; } - if (new_var->type != V_ARRAY) { + + switch (new_var->type) { + case V_MAP: + case V_ARRAY: + case V_STR: + break; + + default: v_free(new_var); v_detach(new_var); err_typemismatch(); return; } - node.x.vfor.flags = 1; // allocated here + // allocated here + node.x.vfor.flags = 1; node.x.vfor.arr_ptr = array_p = new_var; } if (!prog_error) { - node.x.vfor.step_expr_ip = 0; // element-index + // element-index + node.x.vfor.step_expr_ip = 0; var_p_t var_elem_ptr = 0; switch (array_p->type) { @@ -1597,6 +1607,13 @@ void cmd_for_in(bcip_t true_ip, bcip_t false_ip, var_p_t var_p) { } break; + case V_STR: + var_elem_ptr = node.x.vfor.str_ptr = v_new(); + v_init_str(var_elem_ptr, 1); + var_elem_ptr->v.p.ptr[0] = array_p->v.p.ptr[0]; + var_elem_ptr->v.p.ptr[1] = '\0'; + break; + default: break; } @@ -1789,6 +1806,130 @@ void cmd_until() { v_free(&var); } +// +// FOR chr in str +// +var_t *cmd_next_for_in_str(stknode_t *node) { + var_t *result = NULL; + var_t *array_p = node->x.vfor.arr_ptr; + int index = ++node->x.vfor.step_expr_ip; + if (index < v_strlen(array_p)) { + result = node->x.vfor.str_ptr; + result->v.p.ptr[0] = array_p->v.p.ptr[index]; + } + return result; +} + +// +// FOR [EACH] v1 IN v2 +// +void cmd_next_for_in(stknode_t *node, bcip_t next_ip) { + var_t *array_p = node->x.vfor.arr_ptr; + var_t *var_elem_ptr = NULL; + + bcip_t jump_ip = node->x.vfor.jump_ip; + var_t *var_p = node->x.vfor.var_ptr; + + switch (array_p->type) { + case V_STR: + var_elem_ptr = cmd_next_for_in_str(node); + break; + + case V_MAP: + var_elem_ptr = map_elem_key(array_p, ++node->x.vfor.step_expr_ip); + break; + + case V_ARRAY: + if (v_asize(array_p) > (int) ++node->x.vfor.step_expr_ip) { + var_elem_ptr = v_elem(array_p, node->x.vfor.step_expr_ip); + } + break; + + default: + break; + } + + if (var_elem_ptr) { + v_set(var_p, var_elem_ptr); + stknode_t *stknode = code_push(kwFOR); + stknode->x.vfor = node->x.vfor; + code_jump(jump_ip); + } else { + // end of iteration + if (node->x.vfor.flags & 1) { + // allocated in for + v_free(node->x.vfor.arr_ptr); + v_detach(node->x.vfor.arr_ptr); + } + if (node->x.vfor.str_ptr) { + v_free(node->x.vfor.str_ptr); + v_detach(node->x.vfor.str_ptr); + node->x.vfor.str_ptr = NULL; + } + code_jump(next_ip); + } +} + +// +// FOR v=exp1 TO exp2 [STEP exp3] +// +void cmd_next_for_to(stknode_t *node, bcip_t next_ip) { + int check = 0; + var_t var_to; + + bcip_t jump_ip = node->x.vfor.jump_ip; + var_t *var_p = node->x.vfor.var_ptr; + + prog_ip = node->x.vfor.to_expr_ip; + v_init(&var_to); + eval(&var_to); + + if (!prog_error && (var_to.type == V_INT || var_to.type == V_NUM)) { + // get step val + var_t var_step; + var_step.const_flag = 0; + var_step.type = V_INT; + var_step.v.i = 1; + + if (node->x.vfor.step_expr_ip != INVALID_ADDR) { + prog_ip = node->x.vfor.step_expr_ip; + eval(&var_step); + } + + if (!prog_error && (var_step.type == V_INT || var_step.type == V_NUM)) { + v_inc(var_p, &var_step); + if (v_sign(&var_step) < 0) { + check = (v_compare(var_p, &var_to) >= 0); + } else { + check = (v_compare(var_p, &var_to) <= 0); + } + } else { + if (!prog_error) { + err_typemismatch(); + } + } + v_free(&var_step); + } else { + if (!prog_error) { + rt_raise("FOR-TO: TO v IS NOT A NUMBER"); + } + } + + // + // run + // + if (!prog_error) { + if (check) { + stknode_t *stknode = code_push(kwFOR); + stknode->x.vfor = node->x.vfor; + code_jump(jump_ip); + } else { + code_jump(next_ip); + } + } + v_free(&var_to); +} + /** * NEXT */ @@ -1813,108 +1954,11 @@ void cmd_next() { return; } - bcip_t jump_ip = node.x.vfor.jump_ip; - var_t *var_p = node.x.vfor.var_ptr; if (node.x.vfor.subtype == kwTO) { - // - // FOR v=exp1 TO exp2 [STEP exp3] - // - int check = 0; - var_t var_to; - - prog_ip = node.x.vfor.to_expr_ip; - v_init(&var_to); - eval(&var_to); - - if (!prog_error && (var_to.type == V_INT || var_to.type == V_NUM)) { - // get step val - var_t var_step; - var_step.const_flag = 0; - var_step.type = V_INT; - var_step.v.i = 1; - - if (node.x.vfor.step_expr_ip != INVALID_ADDR) { - prog_ip = node.x.vfor.step_expr_ip; - eval(&var_step); - } - - if (!prog_error && (var_step.type == V_INT || var_step.type == V_NUM)) { - v_inc(var_p, &var_step); - if (v_sign(&var_step) < 0) { - check = (v_compare(var_p, &var_to) >= 0); - } else { - check = (v_compare(var_p, &var_to) <= 0); - } - } else { - if (!prog_error) { - err_typemismatch(); - } - } - v_free(&var_step); - } else { - if (!prog_error) { - rt_raise("FOR-TO: TO v IS NOT A NUMBER"); - } - } - - // - // RUN - // - if (!prog_error) { - if (check) { - stknode_t *stknode = code_push(kwFOR); - stknode->x.vfor = node.x.vfor; - code_jump(jump_ip); - } else { - code_jump(next_ip); - } - } - v_free(&var_to); + cmd_next_for_to(&node, next_ip); } else { - // - // FOR [EACH] v1 IN v2 - // - var_t *array_p = node.x.vfor.arr_ptr; - var_t *var_elem_ptr = 0; - - switch (array_p->type) { - case V_MAP: - node.x.vfor.step_expr_ip++; // element-index - var_elem_ptr = map_elem_key(array_p, node.x.vfor.step_expr_ip); - break; - - case V_ARRAY: - node.x.vfor.step_expr_ip++; // element-index - - if (v_asize(array_p) > (int) node.x.vfor.step_expr_ip) { - var_elem_ptr = v_elem(array_p, node.x.vfor.step_expr_ip); - } else { - if (node.x.vfor.flags & 1) { - // allocated in for - v_free(node.x.vfor.arr_ptr); - v_detach(node.x.vfor.arr_ptr); - } - } - break; - - default: - if (node.x.vfor.flags & 1) { - // allocated in for - v_free(node.x.vfor.arr_ptr); - v_detach(node.x.vfor.arr_ptr); - } - break; - } - - if (var_elem_ptr) { - v_set(var_p, var_elem_ptr); - stknode_t *stknode = code_push(kwFOR); - stknode->x.vfor = node.x.vfor; - code_jump(jump_ip); - } else { - code_jump(next_ip); - } + cmd_next_for_in(&node, next_ip); } } diff --git a/src/common/hashmap.h b/src/common/hashmap.h index 70ae73f2..fad7377a 100644 --- a/src/common/hashmap.h +++ b/src/common/hashmap.h @@ -5,7 +5,7 @@ // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org // -// Copyright(C) 2007-2016 Chris Warren-Smith. +// Copyright(C) 2007-2019 Chris Warren-Smith. #ifndef _HASHMAP_H_ #define _HASHMAP_H_ @@ -16,12 +16,12 @@ * Callback structure for hashmap_foreach */ typedef struct hashmap_cb { + var_p_t var; + var_p_t parent; + char *buffer; int count; int index; int start; - char *buffer; - var_p_t var; - var_p_t parent; } hashmap_cb; typedef int (*hashmap_foreach_func)(hashmap_cb *cb, var_p_t k, var_p_t v); diff --git a/src/common/sberr.c b/src/common/sberr.c index cda951e0..ce660d9d 100644 --- a/src/common/sberr.c +++ b/src/common/sberr.c @@ -94,7 +94,7 @@ void sc_raise(const char *format, ...) { * run-time error */ void rt_raise(const char *format, ...) { - if (!gsb_last_error && !prog_error && prog_source) { + if (!gsb_last_error && ctask && !prog_error && prog_source) { prog_error = errRuntime; va_list args; diff --git a/src/common/scan.h b/src/common/scan.h index 5fc45af2..9a8f1969 100644 --- a/src/common/scan.h +++ b/src/common/scan.h @@ -16,6 +16,8 @@ #include "common/sys.h" +#define KEYWORD_PADDING 4 + #if defined(__cplusplus) extern "C" { #endif @@ -83,7 +85,7 @@ struct proc_keyword_s { * External procedure (Modules) */ typedef struct { - char name[SB_KEYWORD_SIZE + 1]; /**< keyword name */ + char name[SB_KEYWORD_SIZE + KEYWORD_PADDING]; /**< keyword name */ int lib_id; /**< library id */ bid_t pcode; /**< keyword code */ int symbol_index; /**< symbol index on symbol-table */ @@ -96,7 +98,7 @@ typedef struct { * External functions (Modules) */ typedef struct { - char name[SB_KEYWORD_SIZE + 1]; /**< keyword name */ + char name[SB_KEYWORD_SIZE + KEYWORD_PADDING]; /**< keyword name */ int lib_id; /**< library id */ bid_t fcode; /**< keyword code */ int symbol_index; /**< symbol index on symbol-table */ @@ -126,7 +128,7 @@ typedef struct comp_var_s comp_var_t; * compiler's label node */ struct comp_label_s { - char name[SB_KEYWORD_SIZE + 1]; /**< label name @ingroup scan */ + char name[SB_KEYWORD_SIZE + KEYWORD_PADDING]; /**< label name @ingroup scan */ bcip_t ip; /**< address in BC @ingroup scan */ bid_t block_id; /**< block_id (FOR-NEXT,IF-FI,etc) used for GOTOs @ingroup scan */ bcip_t dp; /**< data pointer @ingroup scan */ @@ -165,7 +167,7 @@ typedef struct comp_proc_s comp_udp_t; * compiler's pass-2 stack node */ struct comp_pass_node_s { - char sec[SB_KEYWORD_SIZE + 1]; /**< section-name (PalmOS) @ingroup scan */ + char sec[SB_KEYWORD_SIZE + KEYWORD_PADDING]; /**< section-name (PalmOS) @ingroup scan */ bcip_t pos; /**< address in BC @ingroup scan */ int line; /**< source code line number @ingroup scan */ bid_t block_id; /**< block ID @ingroup scan */ diff --git a/src/common/sys.h b/src/common/sys.h index b1d45a50..e9048f1e 100644 --- a/src/common/sys.h +++ b/src/common/sys.h @@ -69,6 +69,8 @@ extern "C" { #define SB_STR_VER VERSION " SDL " SB_VERSYS SB_BIT_SZ BUILD_DATE #elif defined (_ANDROID) #define SB_STR_VER VERSION " Android " BUILD_DATE +#elif defined (_FLTK) + #define SB_STR_VER VERSION " FLTK " BUILD_DATE #else #define SB_STR_VER VERSION " Console " SB_VERSYS SB_BIT_SZ BUILD_DATE #endif diff --git a/src/common/var.c b/src/common/var.c index fa347809..2dfad5ac 100644 --- a/src/common/var.c +++ b/src/common/var.c @@ -790,3 +790,8 @@ void v_input2var(const char *str, var_t *var) { } } +void v_create_func(var_p_t map, const char *name, method cb) { + var_p_t v_func = map_add_var(map, name, 0); + v_func->type = V_FUNC; + v_func->v.fn.cb = cb; +} diff --git a/src/common/var.h b/src/common/var.h index 2041d6e9..425f86b1 100644 --- a/src/common/var.h +++ b/src/common/var.h @@ -107,6 +107,7 @@ typedef struct stknode_s { struct { var_t *var_ptr; /**< 'FOR' variable */ var_t *arr_ptr; /**< FOR-IN array-variable */ + var_t *str_ptr; /**< FOR-IN string variable */ bcip_t to_expr_ip; /**< IP of 'TO' expression */ bcip_t step_expr_ip; /**< IP of 'STEP' expression (FOR-IN = current element) */ bcip_t jump_ip; /**< code block IP */ diff --git a/src/common/var_eval.c b/src/common/var_eval.c index e5e824be..4509fef9 100644 --- a/src/common/var_eval.c +++ b/src/common/var_eval.c @@ -21,10 +21,9 @@ bcip_t get_array_idx(var_t *array) { bcip_t idx = 0; bcip_t lev = 0; - bcip_t m = 0; - var_t var; do { + var_t var; v_init(&var); eval(&var); @@ -34,13 +33,13 @@ bcip_t get_array_idx(var_t *array) { err_varnotnum(); break; } else { - bcip_t i; bcip_t idim = v_getint(&var); + v_free(&var); idim = idim - v_lbound(array, lev); - m = idim; - for (i = lev + 1; i < v_maxdim(array); i++) { + bcip_t m = idim; + for (bcip_t i = lev + 1; i < v_maxdim(array); i++) { m = m * (ABS(v_ubound(array, i) - v_lbound(array, i)) + 1); } idx += m; @@ -228,13 +227,17 @@ var_t *code_resolve_map(var_t *var_p, int until_parens) { return var_p; } -var_t *resolve_var_ref(var_t *var_p) { +var_t *resolve_var_ref(var_t *var_p, int *is_ptr) { switch (code_peek()) { case kwTYPE_LEVEL_BEGIN: - var_p = resolve_var_ref(code_getvarptr_arridx(var_p)); + if (var_p->type == V_PTR) { + *is_ptr = 1; + } else { + var_p = resolve_var_ref(code_getvarptr_arridx(var_p), is_ptr); + } break; case kwTYPE_UDS_EL: - var_p = resolve_var_ref(map_resolve_fields(var_p, NULL)); + var_p = resolve_var_ref(map_resolve_fields(var_p, NULL), is_ptr); break; } return var_p; @@ -293,7 +296,12 @@ int code_isvar() { var_p = code_resolve_varptr(var_p, 0); break; case V_REF: - var_p = resolve_var_ref(var_p); + is_ptr = 0; + var_p = resolve_var_ref(var_p, &is_ptr); + if (is_ptr) { + prog_ip = cur_ip; + return 1; + } break; default: if (code_peek() == kwTYPE_LEVEL_BEGIN) { diff --git a/src/include/var.h b/src/include/var.h index 7e3ea686..f5a2eadd 100644 --- a/src/include/var.h +++ b/src/include/var.h @@ -467,6 +467,13 @@ int v_strlen(const var_t *v); */ #define v_is_type(v, t) (v != NULL && v->type == t) +/** + * @ingroup var + * + * setup a method on the map using the given name + */ +void v_create_func(var_p_t map, const char *name, method cb); + #if defined(__cplusplus) } #endif diff --git a/src/lib/lodepng.c b/src/lib/lodepng.c index 05287bbf..72db2635 100644 --- a/src/lib/lodepng.c +++ b/src/lib/lodepng.c @@ -1,7 +1,7 @@ /* -LodePNG version 20180910 +LodePNG version 20190210 -Copyright (c) 2005-2018 Lode Vandevenne +Copyright (c) 2005-2019 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -30,16 +30,16 @@ Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for #include "lodepng.h" -#include -#include -#include +#include /* LONG_MAX */ +#include /* file handling */ +#include /* allocations */ #if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ #pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ #pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ #endif /*_MSC_VER */ -const char* LODEPNG_VERSION_STRING = "20180910"; +const char* LODEPNG_VERSION_STRING = "20190210"; /* This source file is built up in the following large parts. The code sections @@ -60,24 +60,21 @@ lodepng source code. Don't forget to remove "static" if you copypaste them from here.*/ #ifdef LODEPNG_COMPILE_ALLOCATORS -static void* lodepng_malloc(size_t size) -{ +static void* lodepng_malloc(size_t size) { #ifdef LODEPNG_MAX_ALLOC if(size > LODEPNG_MAX_ALLOC) return 0; #endif return malloc(size); } -static void* lodepng_realloc(void* ptr, size_t new_size) -{ +static void* lodepng_realloc(void* ptr, size_t new_size) { #ifdef LODEPNG_MAX_ALLOC if(new_size > LODEPNG_MAX_ALLOC) return 0; #endif return realloc(ptr, new_size); } -static void lodepng_free(void* ptr) -{ +static void lodepng_free(void* ptr) { free(ptr); } #else /*LODEPNG_COMPILE_ALLOCATORS*/ @@ -102,8 +99,7 @@ It makes the error handling code shorter and more readable. Example: if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83); */ -#define CERROR_BREAK(errorvar, code)\ -{\ +#define CERROR_BREAK(errorvar, code){\ errorvar = code;\ break;\ } @@ -112,22 +108,19 @@ Example: if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83); #define ERROR_BREAK(code) CERROR_BREAK(error, code) /*Set error var to the error code, and return it.*/ -#define CERROR_RETURN_ERROR(errorvar, code)\ -{\ +#define CERROR_RETURN_ERROR(errorvar, code){\ errorvar = code;\ return code;\ } /*Try the code, if it returns error, also return the error.*/ -#define CERROR_TRY_RETURN(call)\ -{\ +#define CERROR_TRY_RETURN(call){\ unsigned error = call;\ if(error) return error;\ } /*Set error var to the error code, and return from the void function.*/ -#define CERROR_RETURN(errorvar, code)\ -{\ +#define CERROR_RETURN(errorvar, code){\ errorvar = code;\ return;\ } @@ -143,29 +136,24 @@ About uivector, ucvector and string: #ifdef LODEPNG_COMPILE_ZLIB /*dynamic vector of unsigned ints*/ -typedef struct uivector -{ +typedef struct uivector { unsigned* data; size_t size; /*size in number of unsigned longs*/ size_t allocsize; /*allocated size in bytes*/ } uivector; -static void uivector_cleanup(void* p) -{ +static void uivector_cleanup(void* p) { ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; lodepng_free(((uivector*)p)->data); ((uivector*)p)->data = NULL; } /*returns 1 if success, 0 if failure ==> nothing done*/ -static unsigned uivector_reserve(uivector* p, size_t allocsize) -{ - if(allocsize > p->allocsize) - { +static unsigned uivector_reserve(uivector* p, size_t allocsize) { + if(allocsize > p->allocsize) { size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2); void* data = lodepng_realloc(p->data, newsize); - if(data) - { + if(data) { p->allocsize = newsize; p->data = (unsigned*)data; } @@ -175,32 +163,28 @@ static unsigned uivector_reserve(uivector* p, size_t allocsize) } /*returns 1 if success, 0 if failure ==> nothing done*/ -static unsigned uivector_resize(uivector* p, size_t size) -{ +static unsigned uivector_resize(uivector* p, size_t size) { if(!uivector_reserve(p, size * sizeof(unsigned))) return 0; p->size = size; return 1; /*success*/ } /*resize and give all new elements the value*/ -static unsigned uivector_resizev(uivector* p, size_t size, unsigned value) -{ +static unsigned uivector_resizev(uivector* p, size_t size, unsigned value) { size_t oldsize = p->size, i; if(!uivector_resize(p, size)) return 0; for(i = oldsize; i < size; ++i) p->data[i] = value; return 1; } -static void uivector_init(uivector* p) -{ +static void uivector_init(uivector* p) { p->data = NULL; p->size = p->allocsize = 0; } #ifdef LODEPNG_COMPILE_ENCODER /*returns 1 if success, 0 if failure ==> nothing done*/ -static unsigned uivector_push_back(uivector* p, unsigned c) -{ +static unsigned uivector_push_back(uivector* p, unsigned c) { if(!uivector_resize(p, p->size + 1)) return 0; p->data[p->size - 1] = c; return 1; @@ -211,22 +195,18 @@ static unsigned uivector_push_back(uivector* p, unsigned c) /* /////////////////////////////////////////////////////////////////////////// */ /*dynamic vector of unsigned chars*/ -typedef struct ucvector -{ +typedef struct ucvector { unsigned char* data; size_t size; /*used size*/ size_t allocsize; /*allocated size*/ } ucvector; /*returns 1 if success, 0 if failure ==> nothing done*/ -static unsigned ucvector_reserve(ucvector* p, size_t allocsize) -{ - if(allocsize > p->allocsize) - { +static unsigned ucvector_reserve(ucvector* p, size_t allocsize) { + if(allocsize > p->allocsize) { size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2); void* data = lodepng_realloc(p->data, newsize); - if(data) - { + if(data) { p->allocsize = newsize; p->data = (unsigned char*)data; } @@ -236,8 +216,7 @@ static unsigned ucvector_reserve(ucvector* p, size_t allocsize) } /*returns 1 if success, 0 if failure ==> nothing done*/ -static unsigned ucvector_resize(ucvector* p, size_t size) -{ +static unsigned ucvector_resize(ucvector* p, size_t size) { if(!ucvector_reserve(p, size * sizeof(unsigned char))) return 0; p->size = size; return 1; /*success*/ @@ -245,15 +224,13 @@ static unsigned ucvector_resize(ucvector* p, size_t size) #ifdef LODEPNG_COMPILE_PNG -static void ucvector_cleanup(void* p) -{ +static void ucvector_cleanup(void* p) { ((ucvector*)p)->size = ((ucvector*)p)->allocsize = 0; lodepng_free(((ucvector*)p)->data); ((ucvector*)p)->data = NULL; } -static void ucvector_init(ucvector* p) -{ +static void ucvector_init(ucvector* p) { p->data = NULL; p->size = p->allocsize = 0; } @@ -262,8 +239,7 @@ static void ucvector_init(ucvector* p) #ifdef LODEPNG_COMPILE_ZLIB /*you can both convert from vector to buffer&size and vica versa. If you use init_buffer to take over a buffer and size, it is not needed to use cleanup*/ -static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size) -{ +static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size) { p->data = buffer; p->allocsize = p->size = size; } @@ -271,8 +247,7 @@ static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size #if (defined(LODEPNG_COMPILE_PNG) && defined(LODEPNG_COMPILE_ANCILLARY_CHUNKS)) || defined(LODEPNG_COMPILE_ENCODER) /*returns 1 if success, 0 if failure ==> nothing done*/ -static unsigned ucvector_push_back(ucvector* p, unsigned char c) -{ +static unsigned ucvector_push_back(ucvector* p, unsigned char c) { if(!ucvector_resize(p, p->size + 1)) return 0; p->data[p->size - 1] = c; return 1; @@ -286,22 +261,18 @@ static unsigned ucvector_push_back(ucvector* p, unsigned char c) #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*free string pointer and set it to NULL*/ -static void string_cleanup(char** out) -{ +static void string_cleanup(char** out) { lodepng_free(*out); *out = NULL; } /* dynamically allocates a new string with a copy of the null terminated input text */ -static char* alloc_string(const char* in) -{ +static char* alloc_string(const char* in) { size_t insize = strlen(in); char* out = (char*)lodepng_malloc(insize + 1); - if(out) - { + if(out) { size_t i; - for(i = 0; i != insize; ++i) - { + for(i = 0; i != insize; ++i) { out[i] = in[i]; } out[i] = 0; @@ -313,15 +284,13 @@ static char* alloc_string(const char* in) /* ////////////////////////////////////////////////////////////////////////// */ -unsigned lodepng_read32bitInt(const unsigned char* buffer) -{ +unsigned lodepng_read32bitInt(const unsigned char* buffer) { return (unsigned)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]); } #if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) /*buffer must have at least 4 allocated bytes available*/ -static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) -{ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { buffer[0] = (unsigned char)((value >> 24) & 0xff); buffer[1] = (unsigned char)((value >> 16) & 0xff); buffer[2] = (unsigned char)((value >> 8) & 0xff); @@ -330,8 +299,7 @@ static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) #endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ #ifdef LODEPNG_COMPILE_ENCODER -static void lodepng_add32bitInt(ucvector* buffer, unsigned value) -{ +static void lodepng_add32bitInt(ucvector* buffer, unsigned value) { ucvector_resize(buffer, buffer->size + 4); /*todo: give error if resize failed*/ lodepng_set32bitInt(&buffer->data[buffer->size - 4], value); } @@ -344,15 +312,13 @@ static void lodepng_add32bitInt(ucvector* buffer, unsigned value) #ifdef LODEPNG_COMPILE_DISK /* returns negative value on error. This should be pure C compatible, so no fstat. */ -static long lodepng_filesize(const char* filename) -{ +static long lodepng_filesize(const char* filename) { FILE* file; long size; file = fopen(filename, "rb"); if(!file) return -1; - if(fseek(file, 0, SEEK_END) != 0) - { + if(fseek(file, 0, SEEK_END) != 0) { fclose(file); return -1; } @@ -366,8 +332,7 @@ static long lodepng_filesize(const char* filename) } /* load file into buffer that already has the correct allocated size. Returns error code.*/ -static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) -{ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { FILE* file; size_t readsize; file = fopen(filename, "rb"); @@ -380,8 +345,7 @@ static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* return 0; } -unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) -{ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { long size = lodepng_filesize(filename); if (size < 0) return 78; *outsize = (size_t)size; @@ -393,8 +357,7 @@ unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* fil } /*write given buffer to the file, overwriting the file, it doesn't append to it.*/ -unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) -{ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { FILE* file; file = fopen(filename, "wb" ); if(!file) return 79; @@ -414,8 +377,7 @@ unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_ENCODER /*TODO: this ignores potential out of memory errors*/ -#define addBitToStream(/*size_t**/ bitpointer, /*ucvector**/ bitstream, /*unsigned char*/ bit)\ -{\ +#define addBitToStream(/*size_t**/ bitpointer, /*ucvector**/ bitstream, /*unsigned char*/ bit){\ /*add a new byte at the end*/\ if(((*bitpointer) & 7) == 0) ucvector_push_back(bitstream, (unsigned char)0);\ /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/\ @@ -423,14 +385,12 @@ unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const ++(*bitpointer);\ } -static void addBitsToStream(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) -{ +static void addBitsToStream(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) { size_t i; for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> i) & 1)); } -static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) -{ +static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits) { size_t i; for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> (nbits - 1 - i)) & 1)); } @@ -440,18 +400,15 @@ static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, uns #define READBIT(bitpointer, bitstream) ((bitstream[bitpointer >> 3] >> (bitpointer & 0x7)) & (unsigned char)1) -static unsigned char readBitFromStream(size_t* bitpointer, const unsigned char* bitstream) -{ +static unsigned char readBitFromStream(size_t* bitpointer, const unsigned char* bitstream) { unsigned char result = (unsigned char)(READBIT(*bitpointer, bitstream)); ++(*bitpointer); return result; } -static unsigned readBitsFromStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) -{ +static unsigned readBitsFromStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { unsigned result = 0, i; - for(i = 0; i != nbits; ++i) - { + for(i = 0; i != nbits; ++i) { result += ((unsigned)READBIT(*bitpointer, bitstream)) << i; ++(*bitpointer); } @@ -502,8 +459,7 @@ static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] /* Huffman tree struct, containing multiple representations of the tree */ -typedef struct HuffmanTree -{ +typedef struct HuffmanTree { unsigned* tree2d; unsigned* tree1d; unsigned* lengths; /*the lengths of the codes of the 1d-tree*/ @@ -513,34 +469,29 @@ typedef struct HuffmanTree /*function used for debug purposes to draw the tree in ascii art with C++*/ /* -static void HuffmanTree_draw(HuffmanTree* tree) -{ +static void HuffmanTree_draw(HuffmanTree* tree) { std::cout << "tree. length: " << tree->numcodes << " maxbitlen: " << tree->maxbitlen << std::endl; - for(size_t i = 0; i != tree->tree1d.size; ++i) - { + for(size_t i = 0; i != tree->tree1d.size; ++i) { if(tree->lengths.data[i]) std::cout << i << " " << tree->tree1d.data[i] << " " << tree->lengths.data[i] << std::endl; } std::cout << std::endl; }*/ -static void HuffmanTree_init(HuffmanTree* tree) -{ +static void HuffmanTree_init(HuffmanTree* tree) { tree->tree2d = 0; tree->tree1d = 0; tree->lengths = 0; } -static void HuffmanTree_cleanup(HuffmanTree* tree) -{ +static void HuffmanTree_cleanup(HuffmanTree* tree) { lodepng_free(tree->tree2d); lodepng_free(tree->tree1d); lodepng_free(tree->lengths); } /*the tree representation used by the decoder. return value is error*/ -static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) -{ +static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) { unsigned nodefilled = 0; /*up to which node it is filled*/ unsigned treepos = 0; /*position in the tree (1 of the numcodes columns)*/ unsigned n, i; @@ -558,27 +509,20 @@ static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) There is only memory for such good tree currently, if there are more nodes (due to too long length codes), error 55 will happen */ - for(n = 0; n < tree->numcodes * 2; ++n) - { + for(n = 0; n < tree->numcodes * 2; ++n) { tree->tree2d[n] = 32767; /*32767 here means the tree2d isn't filled there yet*/ } - for(n = 0; n < tree->numcodes; ++n) /*the codes*/ - { - for(i = 0; i != tree->lengths[n]; ++i) /*the bits for this code*/ - { + for(n = 0; n < tree->numcodes; ++n) /*the codes*/ { + for(i = 0; i != tree->lengths[n]; ++i) /*the bits for this code*/ { unsigned char bit = (unsigned char)((tree->tree1d[n] >> (tree->lengths[n] - i - 1)) & 1); /*oversubscribed, see comment in lodepng_error_text*/ if(treepos > 2147483647 || treepos + 2 > tree->numcodes) return 55; - if(tree->tree2d[2 * treepos + bit] == 32767) /*not yet filled in*/ - { - if(i + 1 == tree->lengths[n]) /*last bit*/ - { + if(tree->tree2d[2 * treepos + bit] == 32767) /*not yet filled in*/ { + if(i + 1 == tree->lengths[n]) /*last bit*/ { tree->tree2d[2 * treepos + bit] = n; /*put the current code in it*/ treepos = 0; - } - else - { + } else { /*put address of the next step in here, first that address has to be found of course (it's just nodefilled + 1)...*/ ++nodefilled; @@ -591,8 +535,7 @@ static unsigned HuffmanTree_make2DTree(HuffmanTree* tree) } } - for(n = 0; n < tree->numcodes * 2; ++n) - { + for(n = 0; n < tree->numcodes * 2; ++n) { if(tree->tree2d[n] == 32767) tree->tree2d[n] = 0; /*remove possible remaining 32767's*/ } @@ -604,8 +547,7 @@ Second step for the ...makeFromLengths and ...makeFromFrequencies functions. numcodes, lengths and maxbitlen must already be filled in correctly. return value is error. */ -static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) -{ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { uivector blcount; uivector nextcode; unsigned error = 0; @@ -621,18 +563,15 @@ static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) || !uivector_resizev(&nextcode, tree->maxbitlen + 1, 0)) error = 83; /*alloc fail*/ - if(!error) - { + if(!error) { /*step 1: count number of instances of each code length*/ for(bits = 0; bits != tree->numcodes; ++bits) ++blcount.data[tree->lengths[bits]]; /*step 2: generate the nextcode values*/ - for(bits = 1; bits <= tree->maxbitlen; ++bits) - { + for(bits = 1; bits <= tree->maxbitlen; ++bits) { nextcode.data[bits] = (nextcode.data[bits - 1] + blcount.data[bits - 1]) << 1; } /*step 3: generate all the codes*/ - for(n = 0; n != tree->numcodes; ++n) - { + for(n = 0; n != tree->numcodes; ++n) { if(tree->lengths[n] != 0) tree->tree1d[n] = nextcode.data[tree->lengths[n]]++; } } @@ -650,8 +589,7 @@ by Deflate. maxbitlen is the maximum bits that a code in the tree can have. return value is error. */ static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, - size_t numcodes, unsigned maxbitlen) -{ + size_t numcodes, unsigned maxbitlen) { unsigned i; tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); if(!tree->lengths) return 83; /*alloc fail*/ @@ -667,8 +605,7 @@ static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* b Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ /*chain node for boundary package merge*/ -typedef struct BPMNode -{ +typedef struct BPMNode { int weight; /*the sum of all weights in this chain*/ unsigned index; /*index of this leaf node (called "count" in the paper)*/ struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ @@ -676,8 +613,7 @@ typedef struct BPMNode } BPMNode; /*lists of chains*/ -typedef struct BPMLists -{ +typedef struct BPMLists { /*memory pool*/ unsigned memsize; BPMNode* memory; @@ -691,26 +627,22 @@ typedef struct BPMLists } BPMLists; /*creates a new chain node with the given parameters, from the memory in the lists */ -static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) -{ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { unsigned i; BPMNode* result; /*memory full, so garbage collect*/ - if(lists->nextfree >= lists->numfree) - { + if(lists->nextfree >= lists->numfree) { /*mark only those that are in use*/ for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; - for(i = 0; i != lists->listsize; ++i) - { + for(i = 0; i != lists->listsize; ++i) { BPMNode* node; for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; } /*collect those that are free*/ lists->numfree = 0; - for(i = 0; i != lists->memsize; ++i) - { + for(i = 0; i != lists->memsize; ++i) { if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; } lists->nextfree = 0; @@ -724,22 +656,18 @@ static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMN } /*sort the leaves with stable mergesort*/ -static void bpmnode_sort(BPMNode* leaves, size_t num) -{ +static void bpmnode_sort(BPMNode* leaves, size_t num) { BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); size_t width, counter = 0; - for(width = 1; width < num; width *= 2) - { + for(width = 1; width < num; width *= 2) { BPMNode* a = (counter & 1) ? mem : leaves; BPMNode* b = (counter & 1) ? leaves : mem; size_t p; - for(p = 0; p < num; p += 2 * width) - { + for(p = 0; p < num; p += 2 * width) { size_t q = (p + width > num) ? num : (p + width); size_t r = (p + 2 * width > num) ? num : (p + 2 * width); size_t i = p, j = q, k; - for(k = p; k < r; k++) - { + for(k = p; k < r; k++) { if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; else b[k] = a[j++]; } @@ -751,31 +679,25 @@ static void bpmnode_sort(BPMNode* leaves, size_t num) } /*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ -static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) -{ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { unsigned lastindex = lists->chains1[c]->index; - if(c == 0) - { + if(c == 0) { if(lastindex >= numpresent) return; lists->chains0[c] = lists->chains1[c]; lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); - } - else - { + } else { /*sum of the weights of the head nodes of the previous lookahead chains.*/ int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; lists->chains0[c] = lists->chains1[c]; - if(lastindex < numpresent && sum > leaves[lastindex].weight) - { + if(lastindex < numpresent && sum > leaves[lastindex].weight) { lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); return; } lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); /*in the end we are only interested in the chain of the last list, so no need to recurse if we're at the last one (this gives measurable speedup)*/ - if(num + 1 < (int)(2 * numpresent - 2)) - { + if(num + 1 < (int)(2 * numpresent - 2)) { boundaryPM(lists, leaves, numpresent, c - 1, num); boundaryPM(lists, leaves, numpresent, c - 1, num); } @@ -783,8 +705,7 @@ static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int } unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, - size_t numcodes, unsigned maxbitlen) -{ + size_t numcodes, unsigned maxbitlen) { unsigned error = 0; unsigned i; size_t numpresent = 0; /*number of symbols with non-zero frequency*/ @@ -796,10 +717,8 @@ unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequen leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); if(!leaves) return 83; /*alloc fail*/ - for(i = 0; i != numcodes; ++i) - { - if(frequencies[i] > 0) - { + for(i = 0; i != numcodes; ++i) { + if(frequencies[i] > 0) { leaves[numpresent].weight = (int)frequencies[i]; leaves[numpresent].index = i; ++numpresent; @@ -813,17 +732,12 @@ unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequen make these work as well ensure there are at least two symbols. The Package-Merge code below also doesn't work correctly if there's only one symbol, it'd give it the theoritical 0 bits but in practice zlib wants 1 bit*/ - if(numpresent == 0) - { + if(numpresent == 0) { lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ - } - else if(numpresent == 1) - { + } else if(numpresent == 1) { lengths[leaves[0].index] = 1; lengths[leaves[0].index == 0 ? 1 : 0] = 1; - } - else - { + } else { BPMLists lists; BPMNode* node; @@ -839,15 +753,13 @@ unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequen lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ - if(!error) - { + if(!error) { for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; bpmnode_create(&lists, leaves[0].weight, 1, 0); bpmnode_create(&lists, leaves[1].weight, 2, 0); - for(i = 0; i != lists.listsize; ++i) - { + for(i = 0; i != lists.listsize; ++i) { lists.chains0[i] = &lists.memory[0]; lists.chains1[i] = &lists.memory[1]; } @@ -855,8 +767,7 @@ unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequen /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); - for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) - { + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; } } @@ -873,8 +784,7 @@ unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequen /*Create the Huffman tree given the symbol frequencies*/ static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, - size_t mincodes, size_t numcodes, unsigned maxbitlen) -{ + size_t mincodes, size_t numcodes, unsigned maxbitlen) { unsigned error = 0; while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ tree->maxbitlen = maxbitlen; @@ -889,20 +799,17 @@ static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigne return error; } -static unsigned HuffmanTree_getCode(const HuffmanTree* tree, unsigned index) -{ +static unsigned HuffmanTree_getCode(const HuffmanTree* tree, unsigned index) { return tree->tree1d[index]; } -static unsigned HuffmanTree_getLength(const HuffmanTree* tree, unsigned index) -{ +static unsigned HuffmanTree_getLength(const HuffmanTree* tree, unsigned index) { return tree->lengths[index]; } #endif /*LODEPNG_COMPILE_ENCODER*/ /*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ -static unsigned generateFixedLitLenTree(HuffmanTree* tree) -{ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) { unsigned i, error = 0; unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); if(!bitlen) return 83; /*alloc fail*/ @@ -920,8 +827,7 @@ static unsigned generateFixedLitLenTree(HuffmanTree* tree) } /*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ -static unsigned generateFixedDistanceTree(HuffmanTree* tree) -{ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) { unsigned i, error = 0; unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); if(!bitlen) return 83; /*alloc fail*/ @@ -941,11 +847,9 @@ returns the code, or (unsigned)(-1) if error happened inbitlength is the length of the complete buffer, in bits (so its byte length times 8) */ static unsigned huffmanDecodeSymbol(const unsigned char* in, size_t* bp, - const HuffmanTree* codetree, size_t inbitlength) -{ + const HuffmanTree* codetree, size_t inbitlength) { unsigned treepos = 0, ct; - for(;;) - { + for(;;) { if(*bp >= inbitlength) return (unsigned)(-1); /*error: end of input memory reached without endcode*/ /* decode the symbol from the tree. The "readBitFromStream" code is inlined in @@ -968,8 +872,7 @@ static unsigned huffmanDecodeSymbol(const unsigned char* in, size_t* bp, /* ////////////////////////////////////////////////////////////////////////// */ /*get the tree of a deflated block with fixed tree, as specified in the deflate specification*/ -static void getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) -{ +static void getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { /*TODO: check for out of memory errors*/ generateFixedLitLenTree(tree_ll); generateFixedDistanceTree(tree_d); @@ -977,8 +880,7 @@ static void getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) /*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, - const unsigned char* in, size_t* bp, size_t inlength) -{ + const unsigned char* in, size_t* bp, size_t inlength) { /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ unsigned error = 0; unsigned n, HLIT, HDIST, HCLEN, i; @@ -1004,15 +906,13 @@ static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, HuffmanTree_init(&tree_cl); - while(!error) - { + while(!error) { /*read the code length codes out of 3 * (amount of code length codes) bits*/ bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); if(!bitlen_cl) ERROR_BREAK(83 /*alloc fail*/); - for(i = 0; i != NUM_CODE_LENGTH_CODES; ++i) - { + for(i = 0; i != NUM_CODE_LENGTH_CODES; ++i) { if(i < HCLEN) bitlen_cl[CLCL_ORDER[i]] = readBitsFromStream(bp, in, 3); else bitlen_cl[CLCL_ORDER[i]] = 0; /*if not, it must stay 0*/ } @@ -1029,17 +929,13 @@ static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ i = 0; - while(i < HLIT + HDIST) - { + while(i < HLIT + HDIST) { unsigned code = huffmanDecodeSymbol(in, bp, &tree_cl, inbitlength); - if(code <= 15) /*a length code*/ - { + if(code <= 15) /*a length code*/ { if(i < HLIT) bitlen_ll[i] = code; else bitlen_d[i - HLIT] = code; ++i; - } - else if(code == 16) /*repeat previous*/ - { + } else if(code == 16) /*repeat previous*/ { unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ unsigned value; /*set value to the previous code*/ @@ -1051,50 +947,40 @@ static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, if(i < HLIT + 1) value = bitlen_ll[i - 1]; else value = bitlen_d[i - HLIT - 1]; /*repeat this value in the next lengths*/ - for(n = 0; n < replength; ++n) - { + for(n = 0; n < replength; ++n) { if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ if(i < HLIT) bitlen_ll[i] = value; else bitlen_d[i - HLIT] = value; ++i; } - } - else if(code == 17) /*repeat "0" 3-10 times*/ - { + } else if(code == 17) /*repeat "0" 3-10 times*/ { unsigned replength = 3; /*read in the bits that indicate repeat length*/ if((*bp + 3) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ replength += readBitsFromStream(bp, in, 3); /*repeat this value in the next lengths*/ - for(n = 0; n < replength; ++n) - { + for(n = 0; n < replength; ++n) { if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ if(i < HLIT) bitlen_ll[i] = 0; else bitlen_d[i - HLIT] = 0; ++i; } - } - else if(code == 18) /*repeat "0" 11-138 times*/ - { + } else if(code == 18) /*repeat "0" 11-138 times*/ { unsigned replength = 11; /*read in the bits that indicate repeat length*/ if((*bp + 7) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ replength += readBitsFromStream(bp, in, 7); /*repeat this value in the next lengths*/ - for(n = 0; n < replength; ++n) - { + for(n = 0; n < replength; ++n) { if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ if(i < HLIT) bitlen_ll[i] = 0; else bitlen_d[i - HLIT] = 0; ++i; } - } - else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ - { - if(code == (unsigned)(-1)) - { + } else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ { + if(code == (unsigned)(-1)) { /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol (10=no endcode, 11=wrong jump outside of tree)*/ error = (*bp) > inbitlength ? 10 : 11; @@ -1125,8 +1011,7 @@ static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, /*inflate a block with dynamic of fixed Huffman tree*/ static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size_t* bp, - size_t* pos, size_t inlength, unsigned btype) -{ + size_t* pos, size_t inlength, unsigned btype) { unsigned error = 0; HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ HuffmanTree tree_d; /*the huffman tree for distance codes*/ @@ -1138,19 +1023,15 @@ static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size if(btype == 1) getTreeInflateFixed(&tree_ll, &tree_d); else if(btype == 2) error = getTreeInflateDynamic(&tree_ll, &tree_d, in, bp, inlength); - while(!error) /*decode all symbols until end reached, breaks at end code*/ - { + while(!error) /*decode all symbols until end reached, breaks at end code*/ { /*code_ll is literal, length or end code*/ unsigned code_ll = huffmanDecodeSymbol(in, bp, &tree_ll, inbitlength); - if(code_ll <= 255) /*literal symbol*/ - { + if(code_ll <= 255) /*literal symbol*/ { /*ucvector_push_back would do the same, but for some reason the two lines below run 10% faster*/ if(!ucvector_resize(out, (*pos) + 1)) ERROR_BREAK(83 /*alloc fail*/); out->data[*pos] = (unsigned char)code_ll; ++(*pos); - } - else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ - { + } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { unsigned code_d, distance; unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ size_t start, forward, backward, length; @@ -1165,10 +1046,8 @@ static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size /*part 3: get distance code*/ code_d = huffmanDecodeSymbol(in, bp, &tree_d, inbitlength); - if(code_d > 29) - { - if(code_d == (unsigned)(-1)) /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ - { + if(code_d > 29) { + if(code_d == (unsigned)(-1)) /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ { /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol (10=no endcode, 11=wrong jump outside of tree)*/ error = (*bp) > inlength * 8 ? 10 : 11; @@ -1190,21 +1069,16 @@ static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size if(!ucvector_resize(out, (*pos) + length)) ERROR_BREAK(83 /*alloc fail*/); if (distance < length) { - for(forward = 0; forward < length; ++forward) - { + for(forward = 0; forward < length; ++forward) { out->data[(*pos)++] = out->data[backward++]; } } else { memcpy(out->data + *pos, out->data + backward, length); *pos += length; } - } - else if(code_ll == 256) - { + } else if(code_ll == 256) { break; /*end code, break the loop*/ - } - else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ - { + } else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/ { /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol (10=no endcode, 11=wrong jump outside of tree)*/ error = ((*bp) > inlength * 8) ? 10 : 11; @@ -1218,8 +1092,7 @@ static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size return error; } -static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength) -{ +static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength) { size_t p; unsigned LEN, NLEN, n, error = 0; @@ -1248,8 +1121,7 @@ static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, siz static unsigned lodepng_inflatev(ucvector* out, const unsigned char* in, size_t insize, - const LodePNGDecompressSettings* settings) -{ + const LodePNGDecompressSettings* settings) { /*bit pointer in the "in" data, current byte is bp >> 3, current bit is bp & 0x7 (from lsb to msb of the byte)*/ size_t bp = 0; unsigned BFINAL = 0; @@ -1258,8 +1130,7 @@ static unsigned lodepng_inflatev(ucvector* out, (void)settings; - while(!BFINAL) - { + while(!BFINAL) { unsigned BTYPE; if(bp + 2 >= insize * 8) return 52; /*error, bit pointer will jump past memory*/ BFINAL = readBitFromStream(&bp, in); @@ -1278,8 +1149,7 @@ static unsigned lodepng_inflatev(ucvector* out, unsigned lodepng_inflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, - const LodePNGDecompressSettings* settings) -{ + const LodePNGDecompressSettings* settings) { unsigned error; ucvector v; ucvector_init_buffer(&v, *out, *outsize); @@ -1291,14 +1161,10 @@ unsigned lodepng_inflate(unsigned char** out, size_t* outsize, static unsigned inflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, - const LodePNGDecompressSettings* settings) -{ - if(settings->custom_inflate) - { + const LodePNGDecompressSettings* settings) { + if(settings->custom_inflate) { return settings->custom_inflate(out, outsize, in, insize, settings); - } - else - { + } else { return lodepng_inflate(out, outsize, in, insize, settings); } } @@ -1314,15 +1180,13 @@ static unsigned inflate(unsigned char** out, size_t* outsize, static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; /*bitlen is the size in bits of the code*/ -static void addHuffmanSymbol(size_t* bp, ucvector* compressed, unsigned code, unsigned bitlen) -{ +static void addHuffmanSymbol(size_t* bp, ucvector* compressed, unsigned code, unsigned bitlen) { addBitsToStreamReversed(bp, compressed, code, bitlen); } /*search the index in the array, that has the largest value smaller than or equal to the given value, given array must be sorted (if no value is smaller, it returns the size of the given array)*/ -static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) -{ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ size_t left = 1; size_t right = array_size - 1; @@ -1336,8 +1200,7 @@ static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t v return left; } -static void addLengthDistance(uivector* values, size_t length, size_t distance) -{ +static void addLengthDistance(uivector* values, size_t length, size_t distance) { /*values in encoded vector are those used by deflate: 0-255: literal bytes 256: end @@ -1360,8 +1223,7 @@ bytes as input because 3 is the minimum match length for deflate*/ static const unsigned HASH_NUM_VALUES = 65536; static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ -typedef struct Hash -{ +typedef struct Hash { int* head; /*hash value to head circular pos - can be outdated if went around window*/ /*circular pos to prev circular pos*/ unsigned short* chain; @@ -1374,8 +1236,7 @@ typedef struct Hash unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ } Hash; -static unsigned hash_init(Hash* hash, unsigned windowsize) -{ +static unsigned hash_init(Hash* hash, unsigned windowsize) { unsigned i; hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); @@ -1385,8 +1246,7 @@ static unsigned hash_init(Hash* hash, unsigned windowsize) hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); - if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) - { + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { return 83; /*alloc fail*/ } @@ -1401,8 +1261,7 @@ static unsigned hash_init(Hash* hash, unsigned windowsize) return 0; } -static void hash_cleanup(Hash* hash) -{ +static void hash_cleanup(Hash* hash) { lodepng_free(hash->head); lodepng_free(hash->val); lodepng_free(hash->chain); @@ -1414,11 +1273,9 @@ static void hash_cleanup(Hash* hash) -static unsigned getHash(const unsigned char* data, size_t size, size_t pos) -{ +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { unsigned result = 0; - if(pos + 2 < size) - { + if(pos + 2 < size) { /*A simple shift and xor hash is used. Since the data of PNGs is dominated by zeroes due to the filters, a better hash does not have a significant effect on speed in traversing the chain, and causes more time spend on @@ -1435,8 +1292,7 @@ static unsigned getHash(const unsigned char* data, size_t size, size_t pos) return result & HASH_BIT_MASK; } -static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) -{ +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { const unsigned char* start = data + pos; const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; if(end > data + size) end = data + size; @@ -1447,8 +1303,7 @@ static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) } /*wpos = pos & (windowsize - 1)*/ -static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) -{ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { hash->val[wpos] = (int)hashval; if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; hash->head[hashval] = (int)wpos; @@ -1469,8 +1324,7 @@ this hash technique is one out of several ways to speed this up. */ static unsigned encodeLZ77(uivector* out, Hash* hash, const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, - unsigned minmatch, unsigned nicematch, unsigned lazymatching) -{ + unsigned minmatch, unsigned nicematch, unsigned lazymatching) { size_t pos; unsigned i, error = 0; /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ @@ -1495,20 +1349,16 @@ static unsigned encodeLZ77(uivector* out, Hash* hash, if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; - for(pos = inpos; pos < insize; ++pos) - { + for(pos = inpos; pos < insize; ++pos) { size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ unsigned chainlength = 0; hashval = getHash(in, insize, pos); - if(usezeros && hashval == 0) - { + if(usezeros && hashval == 0) { if(numzeros == 0) numzeros = countZeros(in, insize, pos); else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; - } - else - { + } else { numzeros = 0; } @@ -1524,37 +1374,32 @@ static unsigned encodeLZ77(uivector* out, Hash* hash, /*search for the longest string*/ prev_offset = 0; - for(;;) - { + for(;;) { if(chainlength++ >= maxchainlength) break; current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ prev_offset = current_offset; - if(current_offset > 0) - { + if(current_offset > 0) { /*test the next characters*/ foreptr = &in[pos]; backptr = &in[pos - current_offset]; /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ - if(numzeros >= 3) - { + if(numzeros >= 3) { unsigned skip = hash->zeros[hashpos]; if(skip > numzeros) skip = numzeros; backptr += skip; foreptr += skip; } - while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ - { + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { ++backptr; ++foreptr; } current_length = (unsigned)(foreptr - &in[pos]); - if(current_length > length) - { + if(current_length > length) { length = current_length; /*the longest length*/ offset = current_offset; /*the offset that is related to this longest length*/ /*jump out once a length of max length is found (speed gain). This also jumps @@ -1565,39 +1410,30 @@ static unsigned encodeLZ77(uivector* out, Hash* hash, if(hashpos == hash->chain[hashpos]) break; - if(numzeros >= 3 && length > numzeros) - { + if(numzeros >= 3 && length > numzeros) { hashpos = hash->chainz[hashpos]; if(hash->zeros[hashpos] != numzeros) break; - } - else - { + } else { hashpos = hash->chain[hashpos]; /*outdated hash value, happens if particular value was not encountered in whole last window*/ if(hash->val[hashpos] != (int)hashval) break; } } - if(lazymatching) - { - if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) - { + if(lazymatching) { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { lazy = 1; lazylength = length; lazyoffset = offset; continue; /*try the next byte*/ } - if(lazy) - { + if(lazy) { lazy = 0; if(pos == 0) ERROR_BREAK(81); - if(length > lazylength + 1) - { + if(length > lazylength + 1) { /*push the previous character as literal*/ if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); - } - else - { + } else { length = lazylength; offset = lazyoffset; hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ @@ -1609,31 +1445,22 @@ static unsigned encodeLZ77(uivector* out, Hash* hash, if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); /*encode it as length/distance pair or literal value*/ - if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ - { + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); - } - else if(length < minmatch || (length == 3 && offset > 4096)) - { + } else if(length < minmatch || (length == 3 && offset > 4096)) { /*compensate for the fact that longer offsets have more extra bits, a length of only 3 may be not worth it then*/ if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); - } - else - { + } else { addLengthDistance(out, length, offset); - for(i = 1; i < length; ++i) - { + for(i = 1; i < length; ++i) { ++pos; wpos = pos & (windowsize - 1); hashval = getHash(in, insize, pos); - if(usezeros && hashval == 0) - { + if(usezeros && hashval == 0) { if(numzeros == 0) numzeros = countZeros(in, insize, pos); else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; - } - else - { + } else { numzeros = 0; } updateHashChain(hash, wpos, hashval, numzeros); @@ -1646,15 +1473,13 @@ static unsigned encodeLZ77(uivector* out, Hash* hash, /* /////////////////////////////////////////////////////////////////////////// */ -static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) -{ +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ size_t i, j, numdeflateblocks = (datasize + 65534) / 65535; unsigned datapos = 0; - for(i = 0; i != numdeflateblocks; ++i) - { + for(i = 0; i != numdeflateblocks; ++i) { unsigned BFINAL, BTYPE, LEN, NLEN; unsigned char firstbyte; @@ -1674,8 +1499,7 @@ static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, s ucvector_push_back(out, (unsigned char)(NLEN >> 8)); /*Decompressed data*/ - for(j = 0; j < 65535 && datapos < datasize; ++j) - { + for(j = 0; j < 65535 && datapos < datasize; ++j) { ucvector_push_back(out, data[datapos++]); } } @@ -1689,15 +1513,12 @@ tree_ll: the tree for lit and len codes. tree_d: the tree for distance codes. */ static void writeLZ77data(size_t* bp, ucvector* out, const uivector* lz77_encoded, - const HuffmanTree* tree_ll, const HuffmanTree* tree_d) -{ + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { size_t i = 0; - for(i = 0; i != lz77_encoded->size; ++i) - { + for(i = 0; i != lz77_encoded->size; ++i) { unsigned val = lz77_encoded->data[i]; addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_ll, val), HuffmanTree_getLength(tree_ll, val)); - if(val > 256) /*for a length code, 3 more things have to be added*/ - { + if(val > 256) /*for a length code, 3 more things have to be added*/ { unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; unsigned length_extra_bits = lz77_encoded->data[++i]; @@ -1719,8 +1540,7 @@ static void writeLZ77data(size_t* bp, ucvector* out, const uivector* lz77_encode /*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, const unsigned char* data, size_t datapos, size_t dataend, - const LodePNGCompressSettings* settings, unsigned final) -{ + const LodePNGCompressSettings* settings, unsigned final) { unsigned error = 0; /* @@ -1774,16 +1594,12 @@ static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, /*This while loop never loops due to a break at the end, it is here to allow breaking out of it to the cleanup phase on error conditions.*/ - while(!error) - { - if(settings->use_lz77) - { + while(!error) { + if(settings->use_lz77) { error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, settings->minmatch, settings->nicematch, settings->lazymatching); if(error) break; - } - else - { + } else { if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ } @@ -1792,12 +1608,10 @@ static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, if(!uivector_resizev(&frequencies_d, 30, 0)) ERROR_BREAK(83 /*alloc fail*/); /*Count the frequencies of lit, len and dist codes*/ - for(i = 0; i != lz77_encoded.size; ++i) - { + for(i = 0; i != lz77_encoded.size; ++i) { unsigned symbol = lz77_encoded.data[i]; ++frequencies_ll.data[symbol]; - if(symbol > 256) - { + if(symbol > 256) { unsigned dist = lz77_encoded.data[i + 2]; ++frequencies_d.data[dist]; i += 3; @@ -1820,47 +1634,36 @@ static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), 17 (3-10 zeroes), 18 (11-138 zeroes)*/ - for(i = 0; i != (unsigned)bitlen_lld.size; ++i) - { + for(i = 0; i != (unsigned)bitlen_lld.size; ++i) { unsigned j = 0; /*amount of repititions*/ while(i + j + 1 < (unsigned)bitlen_lld.size && bitlen_lld.data[i + j + 1] == bitlen_lld.data[i]) ++j; - if(bitlen_lld.data[i] == 0 && j >= 2) /*repeat code for zeroes*/ - { + if(bitlen_lld.data[i] == 0 && j >= 2) /*repeat code for zeroes*/ { ++j; /*include the first zero*/ - if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ - { + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { uivector_push_back(&bitlen_lld_e, 17); uivector_push_back(&bitlen_lld_e, j - 3); - } - else /*repeat code 18 supports max 138 zeroes*/ - { + } else /*repeat code 18 supports max 138 zeroes*/ { if(j > 138) j = 138; uivector_push_back(&bitlen_lld_e, 18); uivector_push_back(&bitlen_lld_e, j - 11); } i += (j - 1); - } - else if(j >= 3) /*repeat code for value other than zero*/ - { + } else if(j >= 3) /*repeat code for value other than zero*/ { size_t k; unsigned num = j / 6, rest = j % 6; uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); - for(k = 0; k < num; ++k) - { + for(k = 0; k < num; ++k) { uivector_push_back(&bitlen_lld_e, 16); uivector_push_back(&bitlen_lld_e, 6 - 3); } - if(rest >= 3) - { + if(rest >= 3) { uivector_push_back(&bitlen_lld_e, 16); uivector_push_back(&bitlen_lld_e, rest - 3); } else j -= rest; i += j; - } - else /*too short to benefit from repeat code*/ - { + } else /*too short to benefit from repeat code*/ { uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]); } } @@ -1868,8 +1671,7 @@ static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, /*generate tree_cl, the huffmantree of huffmantrees*/ if(!uivector_resizev(&frequencies_cl, NUM_CODE_LENGTH_CODES, 0)) ERROR_BREAK(83 /*alloc fail*/); - for(i = 0; i != bitlen_lld_e.size; ++i) - { + for(i = 0; i != bitlen_lld_e.size; ++i) { ++frequencies_cl.data[bitlen_lld_e.data[i]]; /*after a repeat code come the bits that specify the number of repetitions, those don't need to be in the frequencies_cl calculation*/ @@ -1881,13 +1683,11 @@ static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, if(error) break; if(!uivector_resize(&bitlen_cl, tree_cl.numcodes)) ERROR_BREAK(83 /*alloc fail*/); - for(i = 0; i != tree_cl.numcodes; ++i) - { + for(i = 0; i != tree_cl.numcodes; ++i) { /*lenghts of code length tree is in the order as specified by deflate*/ bitlen_cl.data[i] = HuffmanTree_getLength(&tree_cl, CLCL_ORDER[i]); } - while(bitlen_cl.data[bitlen_cl.size - 1] == 0 && bitlen_cl.size > 4) - { + while(bitlen_cl.data[bitlen_cl.size - 1] == 0 && bitlen_cl.size > 4) { /*remove zeros at the end, but minimum size must be 4*/ if(!uivector_resize(&bitlen_cl, bitlen_cl.size - 1)) ERROR_BREAK(83 /*alloc fail*/); } @@ -1926,8 +1726,7 @@ static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, for(i = 0; i != HCLEN + 4; ++i) addBitsToStream(bp, out, bitlen_cl.data[i], 3); /*write the lenghts of the lit/len AND the dist alphabet*/ - for(i = 0; i != bitlen_lld_e.size; ++i) - { + for(i = 0; i != bitlen_lld_e.size; ++i) { addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_cl, bitlen_lld_e.data[i]), HuffmanTree_getLength(&tree_cl, bitlen_lld_e.data[i])); /*extra bits of repeat codes*/ @@ -1965,8 +1764,7 @@ static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash, static unsigned deflateFixed(ucvector* out, size_t* bp, Hash* hash, const unsigned char* data, size_t datapos, size_t dataend, - const LodePNGCompressSettings* settings, unsigned final) -{ + const LodePNGCompressSettings* settings, unsigned final) { HuffmanTree tree_ll; /*tree for literal values and length codes*/ HuffmanTree tree_d; /*tree for distance codes*/ @@ -1984,19 +1782,15 @@ static unsigned deflateFixed(ucvector* out, size_t* bp, Hash* hash, addBitToStream(bp, out, 1); /*first bit of BTYPE*/ addBitToStream(bp, out, 0); /*second bit of BTYPE*/ - if(settings->use_lz77) /*LZ77 encoded*/ - { + if(settings->use_lz77) /*LZ77 encoded*/ { uivector lz77_encoded; uivector_init(&lz77_encoded); error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, settings->minmatch, settings->nicematch, settings->lazymatching); if(!error) writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d); uivector_cleanup(&lz77_encoded); - } - else /*no LZ77, but still will be Huffman compressed*/ - { - for(i = datapos; i < dataend; ++i) - { + } else /*no LZ77, but still will be Huffman compressed*/ { + for(i = datapos; i < dataend; ++i) { addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, data[i]), HuffmanTree_getLength(&tree_ll, data[i])); } } @@ -2011,8 +1805,7 @@ static unsigned deflateFixed(ucvector* out, size_t* bp, Hash* hash, } static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, - const LodePNGCompressSettings* settings) -{ + const LodePNGCompressSettings* settings) { unsigned error = 0; size_t i, blocksize, numdeflateblocks; size_t bp = 0; /*the bit pointer*/ @@ -2021,8 +1814,7 @@ static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t if(settings->btype > 2) return 61; else if(settings->btype == 0) return deflateNoCompression(out, in, insize); else if(settings->btype == 1) blocksize = insize; - else /*if(settings->btype == 2)*/ - { + else /*if(settings->btype == 2)*/ { /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ blocksize = insize / 8 + 8; if(blocksize < 65536) blocksize = 65536; @@ -2035,8 +1827,7 @@ static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t error = hash_init(&hash, settings->windowsize); if(error) return error; - for(i = 0; i != numdeflateblocks && !error; ++i) - { + for(i = 0; i != numdeflateblocks && !error; ++i) { unsigned final = (i == numdeflateblocks - 1); size_t start = i * blocksize; size_t end = start + blocksize; @@ -2053,8 +1844,7 @@ static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t unsigned lodepng_deflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, - const LodePNGCompressSettings* settings) -{ + const LodePNGCompressSettings* settings) { unsigned error; ucvector v; ucvector_init_buffer(&v, *out, *outsize); @@ -2066,14 +1856,10 @@ unsigned lodepng_deflate(unsigned char** out, size_t* outsize, static unsigned deflate(unsigned char** out, size_t* outsize, const unsigned char* in, size_t insize, - const LodePNGCompressSettings* settings) -{ - if(settings->custom_deflate) - { + const LodePNGCompressSettings* settings) { + if(settings->custom_deflate) { return settings->custom_deflate(out, outsize, in, insize, settings); - } - else - { + } else { return lodepng_deflate(out, outsize, in, insize, settings); } } @@ -2084,18 +1870,15 @@ static unsigned deflate(unsigned char** out, size_t* outsize, /* / Adler32 */ /* ////////////////////////////////////////////////////////////////////////// */ -static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) -{ +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { unsigned s1 = adler & 0xffff; unsigned s2 = (adler >> 16) & 0xffff; - while(len > 0) - { + while(len > 0) { /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ unsigned amount = len > 5552 ? 5552 : len; len -= amount; - while(amount > 0) - { + while(amount > 0) { s1 += (*data++); s2 += s1; --amount; @@ -2108,8 +1891,7 @@ static unsigned update_adler32(unsigned adler, const unsigned char* data, unsign } /*Return the adler32 of the bytes data[0..len-1]*/ -static unsigned adler32(const unsigned char* data, unsigned len) -{ +static unsigned adler32(const unsigned char* data, unsigned len) { return update_adler32(1L, data, len); } @@ -2120,15 +1902,13 @@ static unsigned adler32(const unsigned char* data, unsigned len) #ifdef LODEPNG_COMPILE_DECODER unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, - size_t insize, const LodePNGDecompressSettings* settings) -{ + size_t insize, const LodePNGDecompressSettings* settings) { unsigned error = 0; unsigned CM, CINFO, FDICT; if(insize < 2) return 53; /*error, size of zlib data too small*/ /*read information from zlib header*/ - if((in[0] * 256 + in[1]) % 31 != 0) - { + if((in[0] * 256 + in[1]) % 31 != 0) { /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ return 24; } @@ -2139,13 +1919,11 @@ unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const uns FDICT = (in[1] >> 5) & 1; /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ - if(CM != 8 || CINFO > 7) - { + if(CM != 8 || CINFO > 7) { /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ return 25; } - if(FDICT != 0) - { + if(FDICT != 0) { /*error: the specification of PNG says about the zlib stream: "The additional flags shall not specify a preset dictionary."*/ return 26; @@ -2154,8 +1932,7 @@ unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const uns error = inflate(out, outsize, in + 2, insize - 2, settings); if(error) return error; - if(!settings->ignore_adler32) - { + if(!settings->ignore_adler32) { unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); unsigned checksum = adler32(*out, (unsigned)(*outsize)); if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ @@ -2165,14 +1942,10 @@ unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const uns } static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, - size_t insize, const LodePNGDecompressSettings* settings) -{ - if(settings->custom_zlib) - { + size_t insize, const LodePNGDecompressSettings* settings) { + if(settings->custom_zlib) { return settings->custom_zlib(out, outsize, in, insize, settings); - } - else - { + } else { return lodepng_zlib_decompress(out, outsize, in, insize, settings); } } @@ -2182,8 +1955,7 @@ static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsi #ifdef LODEPNG_COMPILE_ENCODER unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, - size_t insize, const LodePNGCompressSettings* settings) -{ + size_t insize, const LodePNGCompressSettings* settings) { /*initially, *out must be NULL and outsize 0, if you just give some random *out that's pointing to a non allocated buffer, this'll crash*/ ucvector outv; @@ -2208,8 +1980,7 @@ unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsig error = deflate(&deflatedata, &deflatesize, in, insize, settings); - if(!error) - { + if(!error) { unsigned ADLER32 = adler32(in, (unsigned)insize); for(i = 0; i != deflatesize; ++i) ucvector_push_back(&outv, deflatedata[i]); lodepng_free(deflatedata); @@ -2224,14 +1995,10 @@ unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsig /* compress using the default or custom zlib function */ static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, - size_t insize, const LodePNGCompressSettings* settings) -{ - if(settings->custom_zlib) - { + size_t insize, const LodePNGCompressSettings* settings) { + if(settings->custom_zlib) { return settings->custom_zlib(out, outsize, in, insize, settings); - } - else - { + } else { return lodepng_zlib_compress(out, outsize, in, insize, settings); } } @@ -2242,16 +2009,14 @@ static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsign #ifdef LODEPNG_COMPILE_DECODER static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, - size_t insize, const LodePNGDecompressSettings* settings) -{ + size_t insize, const LodePNGDecompressSettings* settings) { if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ return settings->custom_zlib(out, outsize, in, insize, settings); } #endif /*LODEPNG_COMPILE_DECODER*/ #ifdef LODEPNG_COMPILE_ENCODER static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, - size_t insize, const LodePNGCompressSettings* settings) -{ + size_t insize, const LodePNGCompressSettings* settings) { if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ return settings->custom_zlib(out, outsize, in, insize, settings); } @@ -2266,8 +2031,7 @@ static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsign /*this is a good tradeoff between speed and compression ratio*/ #define DEFAULT_WINDOWSIZE 2048 -void lodepng_compress_settings_init(LodePNGCompressSettings* settings) -{ +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ settings->btype = 2; settings->use_lz77 = 1; @@ -2288,8 +2052,7 @@ const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT #ifdef LODEPNG_COMPILE_DECODER -void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) -{ +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { settings->ignore_adler32 = 0; settings->custom_zlib = 0; @@ -2352,12 +2115,10 @@ static unsigned lodepng_crc32_table[256] = { }; /*Return the CRC of the bytes buf[0..len-1].*/ -unsigned lodepng_crc32(const unsigned char* data, size_t length) -{ +unsigned lodepng_crc32(const unsigned char* data, size_t length) { unsigned r = 0xffffffffu; size_t i; - for(i = 0; i < length; ++i) - { + for(i = 0; i < length; ++i) { r = lodepng_crc32_table[(r ^ data[i]) & 0xff] ^ (r >> 8); } return r ^ 0xffffffffu; @@ -2370,19 +2131,16 @@ unsigned lodepng_crc32(const unsigned char* data, size_t length); /* / Reading and writing single bits and bytes from/to stream for LodePNG / */ /* ////////////////////////////////////////////////////////////////////////// */ -static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) -{ +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); ++(*bitpointer); return result; } -static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) -{ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { unsigned result = 0; size_t i; - for(i = 0 ; i < nbits; ++i) - { + for(i = 0 ; i < nbits; ++i) { result <<= 1; result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); } @@ -2390,11 +2148,9 @@ static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned ch } #ifdef LODEPNG_COMPILE_DECODER -static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) -{ +static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { /*the current bit in bitstream must be 0 for this to work*/ - if(bit) - { + if(bit) { /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/ bitstream[(*bitpointer) >> 3] |= (bit << (7 - ((*bitpointer) & 0x7))); } @@ -2402,8 +2158,7 @@ static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream } #endif /*LODEPNG_COMPILE_DECODER*/ -static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) -{ +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { /*the current bit in bitstream may be 0 or 1 for this to work*/ if(bit == 0) bitstream[(*bitpointer) >> 3] &= (unsigned char)(~(1 << (7 - ((*bitpointer) & 0x7)))); else bitstream[(*bitpointer) >> 3] |= (1 << (7 - ((*bitpointer) & 0x7))); @@ -2414,51 +2169,42 @@ static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, /* / PNG chunks / */ /* ////////////////////////////////////////////////////////////////////////// */ -unsigned lodepng_chunk_length(const unsigned char* chunk) -{ +unsigned lodepng_chunk_length(const unsigned char* chunk) { return lodepng_read32bitInt(&chunk[0]); } -void lodepng_chunk_type(char type[5], const unsigned char* chunk) -{ +void lodepng_chunk_type(char type[5], const unsigned char* chunk) { unsigned i; for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; type[4] = 0; /*null termination char*/ } -unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) -{ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { if(strlen(type) != 4) return 0; return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); } -unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) -{ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { return((chunk[4] & 32) != 0); } -unsigned char lodepng_chunk_private(const unsigned char* chunk) -{ +unsigned char lodepng_chunk_private(const unsigned char* chunk) { return((chunk[6] & 32) != 0); } -unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) -{ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { return((chunk[7] & 32) != 0); } -unsigned char* lodepng_chunk_data(unsigned char* chunk) -{ +unsigned char* lodepng_chunk_data(unsigned char* chunk) { return &chunk[8]; } -const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) -{ +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { return &chunk[8]; } -unsigned lodepng_chunk_check_crc(const unsigned char* chunk) -{ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { unsigned length = lodepng_chunk_length(chunk); unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ @@ -2467,15 +2213,13 @@ unsigned lodepng_chunk_check_crc(const unsigned char* chunk) else return 0; } -void lodepng_chunk_generate_crc(unsigned char* chunk) -{ +void lodepng_chunk_generate_crc(unsigned char* chunk) { unsigned length = lodepng_chunk_length(chunk); unsigned CRC = lodepng_crc32(&chunk[4], length + 4); lodepng_set32bitInt(chunk + 8 + length, CRC); } -unsigned char* lodepng_chunk_next(unsigned char* chunk) -{ +unsigned char* lodepng_chunk_next(unsigned char* chunk) { if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ @@ -2486,8 +2230,7 @@ unsigned char* lodepng_chunk_next(unsigned char* chunk) } } -const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk) -{ +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk) { if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ @@ -2498,28 +2241,23 @@ const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk) } } -unsigned char* lodepng_chunk_find(unsigned char* chunk, const unsigned char* end, const char type[5]) -{ - for(;;) - { +unsigned char* lodepng_chunk_find(unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { if(chunk + 12 >= end) return 0; if(lodepng_chunk_type_equals(chunk, type)) return chunk; chunk = lodepng_chunk_next(chunk); } } -const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) -{ - for(;;) - { +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { if(chunk + 12 >= end) return 0; if(lodepng_chunk_type_equals(chunk, type)) return chunk; chunk = lodepng_chunk_next_const(chunk); } } -unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk) -{ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk) { unsigned i; unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12; unsigned char *chunk_start, *new_buffer; @@ -2538,8 +2276,7 @@ unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsi } unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length, - const char* type, const unsigned char* data) -{ + const char* type, const unsigned char* data) { unsigned i; unsigned char *chunk, *new_buffer; size_t new_length = (*outlength) + length + 12; @@ -2573,10 +2310,8 @@ unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned l /* ////////////////////////////////////////////////////////////////////////// */ /*return type is a LodePNG error code*/ -static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) /*bd = bitdepth*/ -{ - switch(colortype) - { +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) /*bd = bitdepth*/ { + switch(colortype) { case 0: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; /*grey*/ case 2: if(!( bd == 8 || bd == 16)) return 37; break; /*RGB*/ case 3: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; /*palette*/ @@ -2587,10 +2322,8 @@ static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) /*bd return 0; /*allowed color type / bits combination*/ } -static unsigned getNumColorChannels(LodePNGColorType colortype) -{ - switch(colortype) - { +static unsigned getNumColorChannels(LodePNGColorType colortype) { + switch(colortype) { case 0: return 1; /*grey*/ case 2: return 3; /*RGB*/ case 3: return 1; /*palette*/ @@ -2600,16 +2333,14 @@ static unsigned getNumColorChannels(LodePNGColorType colortype) return 0; /*unexisting color type*/ } -static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) -{ +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { /*bits per pixel is amount of channels * bits per channel*/ return getNumColorChannels(colortype) * bitdepth; } /* ////////////////////////////////////////////////////////////////////////// */ -void lodepng_color_mode_init(LodePNGColorMode* info) -{ +void lodepng_color_mode_init(LodePNGColorMode* info) { info->key_defined = 0; info->key_r = info->key_g = info->key_b = 0; info->colortype = LCT_RGBA; @@ -2618,18 +2349,15 @@ void lodepng_color_mode_init(LodePNGColorMode* info) info->palettesize = 0; } -void lodepng_color_mode_cleanup(LodePNGColorMode* info) -{ +void lodepng_color_mode_cleanup(LodePNGColorMode* info) { lodepng_palette_clear(info); } -unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) -{ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { size_t i; lodepng_color_mode_cleanup(dest); *dest = *source; - if(source->palette) - { + if(source->palette) { dest->palette = (unsigned char*)lodepng_malloc(1024); if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ for(i = 0; i != source->palettesize * 4; ++i) dest->palette[i] = source->palette[i]; @@ -2637,8 +2365,7 @@ unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* return 0; } -LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) -{ +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { LodePNGColorMode result; lodepng_color_mode_init(&result); result.colortype = colortype; @@ -2646,41 +2373,35 @@ LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bi return result; } -static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) -{ +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { size_t i; if(a->colortype != b->colortype) return 0; if(a->bitdepth != b->bitdepth) return 0; if(a->key_defined != b->key_defined) return 0; - if(a->key_defined) - { + if(a->key_defined) { if(a->key_r != b->key_r) return 0; if(a->key_g != b->key_g) return 0; if(a->key_b != b->key_b) return 0; } if(a->palettesize != b->palettesize) return 0; - for(i = 0; i != a->palettesize * 4; ++i) - { + for(i = 0; i != a->palettesize * 4; ++i) { if(a->palette[i] != b->palette[i]) return 0; } return 1; } -void lodepng_palette_clear(LodePNGColorMode* info) -{ +void lodepng_palette_clear(LodePNGColorMode* info) { if(info->palette) lodepng_free(info->palette); info->palette = 0; info->palettesize = 0; } unsigned lodepng_palette_add(LodePNGColorMode* info, - unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { unsigned char* data; /*the same resize technique as C++ std::vectors is used, and here it's made so that for a palette with the max of 256 colors, it'll have the exact alloc size*/ - if(!info->palette) /*allocate palette if empty*/ - { + if(!info->palette) /*allocate palette if empty*/ { /*room for 256 colors with 4 bytes each*/ data = (unsigned char*)lodepng_realloc(info->palette, 1024); if(!data) return 83; /*alloc fail*/ @@ -2695,57 +2416,47 @@ unsigned lodepng_palette_add(LodePNGColorMode* info, } /*calculate bits per pixel out of colortype and bitdepth*/ -unsigned lodepng_get_bpp(const LodePNGColorMode* info) -{ +unsigned lodepng_get_bpp(const LodePNGColorMode* info) { return lodepng_get_bpp_lct(info->colortype, info->bitdepth); } -unsigned lodepng_get_channels(const LodePNGColorMode* info) -{ +unsigned lodepng_get_channels(const LodePNGColorMode* info) { return getNumColorChannels(info->colortype); } -unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) -{ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; } -unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) -{ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { return (info->colortype & 4) != 0; /*4 or 6*/ } -unsigned lodepng_is_palette_type(const LodePNGColorMode* info) -{ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { return info->colortype == LCT_PALETTE; } -unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) -{ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { size_t i; - for(i = 0; i != info->palettesize; ++i) - { + for(i = 0; i != info->palettesize; ++i) { if(info->palette[i * 4 + 3] < 255) return 1; } return 0; } -unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) -{ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { return info->key_defined || lodepng_is_alpha_type(info) || lodepng_has_palette_alpha(info); } -size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) -{ +size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); size_t n = (size_t)w * (size_t)h; return ((n / 8) * bpp) + ((n & 7) * bpp + 7) / 8; } -size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) -{ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); } @@ -2756,8 +2467,7 @@ size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* colo /*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, and in addition has one extra byte per line: the filter byte. So this gives a larger result than lodepng_get_raw_size. */ -static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, const LodePNGColorMode* color) -{ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, const LodePNGColorMode* color) { size_t bpp = lodepng_get_bpp(color); /* + 1 for the filter byte, and possibly plus padding bits per line */ size_t line = ((size_t)(w / 8) * bpp) + 1 + ((w & 7) * bpp + 7) / 8; @@ -2766,16 +2476,14 @@ static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, const LodePNGCol /* Safely check if multiplying two integers will overflow (no undefined behavior, compiler removing the code, etc...) and output result. */ -static int lodepng_mulofl(size_t a, size_t b, size_t* result) -{ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) { *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ return (a != 0 && *result / a != b); } /* Safely check if adding two integers will overflow (no undefined behavior, compiler removing the code, etc...) and output result. */ -static int lodepng_addofl(size_t a, size_t b, size_t* result) -{ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { *result = a + b; /* Unsigned addition is well defined and safe in C90 */ return *result < a; } @@ -2789,8 +2497,7 @@ you can safely compute in a size_t (but not an unsigned): Returns 1 if overflow possible, 0 if not. */ static int lodepng_pixel_overflow(unsigned w, unsigned h, - const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) -{ + const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); size_t numpixels, total; size_t line; /* bytes per line in worst case */ @@ -2812,33 +2519,28 @@ static int lodepng_pixel_overflow(unsigned w, unsigned h, #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS -static void LodePNGUnknownChunks_init(LodePNGInfo* info) -{ +static void LodePNGUnknownChunks_init(LodePNGInfo* info) { unsigned i; for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; } -static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) -{ +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { unsigned i; for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); } -static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) -{ +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { unsigned i; LodePNGUnknownChunks_cleanup(dest); - for(i = 0; i != 3; ++i) - { + for(i = 0; i != 3; ++i) { size_t j; dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ - for(j = 0; j < src->unknown_chunks_size[i]; ++j) - { + for(j = 0; j < src->unknown_chunks_size[i]; ++j) { dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; } } @@ -2848,18 +2550,15 @@ static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* /******************************************************************************/ -static void LodePNGText_init(LodePNGInfo* info) -{ +static void LodePNGText_init(LodePNGInfo* info) { info->text_num = 0; info->text_keys = NULL; info->text_strings = NULL; } -static void LodePNGText_cleanup(LodePNGInfo* info) -{ +static void LodePNGText_cleanup(LodePNGInfo* info) { size_t i; - for(i = 0; i != info->text_num; ++i) - { + for(i = 0; i != info->text_num; ++i) { string_cleanup(&info->text_keys[i]); string_cleanup(&info->text_strings[i]); } @@ -2867,30 +2566,25 @@ static void LodePNGText_cleanup(LodePNGInfo* info) lodepng_free(info->text_strings); } -static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) -{ +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { size_t i = 0; dest->text_keys = 0; dest->text_strings = 0; dest->text_num = 0; - for(i = 0; i != source->text_num; ++i) - { + for(i = 0; i != source->text_num; ++i) { CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); } return 0; } -void lodepng_clear_text(LodePNGInfo* info) -{ +void lodepng_clear_text(LodePNGInfo* info) { LodePNGText_cleanup(info); } -unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) -{ +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); - if(!new_keys || !new_strings) - { + if(!new_keys || !new_strings) { lodepng_free(new_keys); lodepng_free(new_strings); return 83; /*alloc fail*/ @@ -2908,8 +2602,7 @@ unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) /******************************************************************************/ -static void LodePNGIText_init(LodePNGInfo* info) -{ +static void LodePNGIText_init(LodePNGInfo* info) { info->itext_num = 0; info->itext_keys = NULL; info->itext_langtags = NULL; @@ -2917,11 +2610,9 @@ static void LodePNGIText_init(LodePNGInfo* info) info->itext_strings = NULL; } -static void LodePNGIText_cleanup(LodePNGInfo* info) -{ +static void LodePNGIText_cleanup(LodePNGInfo* info) { size_t i; - for(i = 0; i != info->itext_num; ++i) - { + for(i = 0; i != info->itext_num; ++i) { string_cleanup(&info->itext_keys[i]); string_cleanup(&info->itext_langtags[i]); string_cleanup(&info->itext_transkeys[i]); @@ -2933,36 +2624,31 @@ static void LodePNGIText_cleanup(LodePNGInfo* info) lodepng_free(info->itext_strings); } -static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) -{ +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { size_t i = 0; dest->itext_keys = 0; dest->itext_langtags = 0; dest->itext_transkeys = 0; dest->itext_strings = 0; dest->itext_num = 0; - for(i = 0; i != source->itext_num; ++i) - { + for(i = 0; i != source->itext_num; ++i) { CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], source->itext_transkeys[i], source->itext_strings[i])); } return 0; } -void lodepng_clear_itext(LodePNGInfo* info) -{ +void lodepng_clear_itext(LodePNGInfo* info) { LodePNGIText_cleanup(info); } unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, - const char* transkey, const char* str) -{ + const char* transkey, const char* str) { char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); - if(!new_keys || !new_langtags || !new_transkeys || !new_strings) - { + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) { lodepng_free(new_keys); lodepng_free(new_langtags); lodepng_free(new_transkeys); @@ -2985,8 +2671,7 @@ unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langt } /* same as set but does not delete */ -static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) -{ +static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { info->iccp_name = alloc_string(name); info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); @@ -2998,24 +2683,23 @@ static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const un return 0; /*ok*/ } -unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) -{ +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { if(info->iccp_name) lodepng_clear_icc(info); + info->iccp_defined = 1; return lodepng_assign_icc(info, name, profile, profile_size); } -void lodepng_clear_icc(LodePNGInfo* info) -{ +void lodepng_clear_icc(LodePNGInfo* info) { string_cleanup(&info->iccp_name); lodepng_free(info->iccp_profile); info->iccp_profile = NULL; info->iccp_profile_size = 0; + info->iccp_defined = 0; } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ -void lodepng_info_init(LodePNGInfo* info) -{ +void lodepng_info_init(LodePNGInfo* info) { lodepng_color_mode_init(&info->color); info->interlace_method = 0; info->compression_method = 0; @@ -3041,8 +2725,7 @@ void lodepng_info_init(LodePNGInfo* info) #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } -void lodepng_info_cleanup(LodePNGInfo* info) -{ +void lodepng_info_cleanup(LodePNGInfo* info) { lodepng_color_mode_cleanup(&info->color); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS LodePNGText_cleanup(info); @@ -3054,8 +2737,7 @@ void lodepng_info_cleanup(LodePNGInfo* info) #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } -unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) -{ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { lodepng_info_cleanup(dest); *dest = *source; lodepng_color_mode_init(&dest->color); @@ -3064,8 +2746,7 @@ unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); - if(source->iccp_defined) - { + if(source->iccp_defined) { CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); } @@ -3078,8 +2759,7 @@ unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) /* ////////////////////////////////////////////////////////////////////////// */ /*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ -static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) -{ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ unsigned p = index & m; @@ -3097,26 +2777,21 @@ This is the data structure used to count the number of unique colors and to get index for a color. It's like an octree, but because the alpha channel is used too, each node has 16 instead of 8 children. */ -struct ColorTree -{ +struct ColorTree { ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ int index; /*the payload. Only has a meaningful value if this is in the last level*/ }; -static void color_tree_init(ColorTree* tree) -{ +static void color_tree_init(ColorTree* tree) { int i; for(i = 0; i != 16; ++i) tree->children[i] = 0; tree->index = -1; } -static void color_tree_cleanup(ColorTree* tree) -{ +static void color_tree_cleanup(ColorTree* tree) { int i; - for(i = 0; i != 16; ++i) - { - if(tree->children[i]) - { + for(i = 0; i != 16; ++i) { + if(tree->children[i]) { color_tree_cleanup(tree->children[i]); lodepng_free(tree->children[i]); } @@ -3124,11 +2799,9 @@ static void color_tree_cleanup(ColorTree* tree) } /*returns -1 if color not present, its index otherwise*/ -static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { int bit = 0; - for(bit = 0; bit < 8; ++bit) - { + for(bit = 0; bit < 8; ++bit) { int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); if(!tree->children[i]) return -1; else tree = tree->children[i]; @@ -3137,8 +2810,7 @@ static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, uns } #ifdef LODEPNG_COMPILE_ENCODER -static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { return color_tree_get(tree, r, g, b, a) >= 0; } #endif /*LODEPNG_COMPILE_ENCODER*/ @@ -3146,14 +2818,11 @@ static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, uns /*color is not allowed to already exist. Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist")*/ static void color_tree_add(ColorTree* tree, - unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) -{ + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { int bit; - for(bit = 0; bit < 8; ++bit) - { + for(bit = 0; bit < 8; ++bit) { int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); - if(!tree->children[i]) - { + if(!tree->children[i]) { tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); color_tree_init(tree->children[i]); } @@ -3165,67 +2834,47 @@ static void color_tree_add(ColorTree* tree, /*put a pixel, given its RGBA color, into image of any color type*/ static unsigned rgba8ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, - unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - if(mode->colortype == LCT_GREY) - { - unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(mode->colortype == LCT_GREY) { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3;*/ if(mode->bitdepth == 8) out[i] = grey; else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = grey; - else - { + else { /*take the most significant bits of grey*/ grey = (grey >> (8 - mode->bitdepth)) & ((1 << mode->bitdepth) - 1); addColorBits(out, i, mode->bitdepth, grey); } - } - else if(mode->colortype == LCT_RGB) - { - if(mode->bitdepth == 8) - { + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { out[i * 3 + 0] = r; out[i * 3 + 1] = g; out[i * 3 + 2] = b; - } - else - { + } else { out[i * 6 + 0] = out[i * 6 + 1] = r; out[i * 6 + 2] = out[i * 6 + 3] = g; out[i * 6 + 4] = out[i * 6 + 5] = b; } - } - else if(mode->colortype == LCT_PALETTE) - { + } else if(mode->colortype == LCT_PALETTE) { int index = color_tree_get(tree, r, g, b, a); if(index < 0) return 82; /*color not in palette*/ if(mode->bitdepth == 8) out[i] = index; else addColorBits(out, i, mode->bitdepth, (unsigned)index); - } - else if(mode->colortype == LCT_GREY_ALPHA) - { - unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/; - if(mode->bitdepth == 8) - { + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned char grey = r; /*((unsigned short)r + g + b) / 3;*/ + if(mode->bitdepth == 8) { out[i * 2 + 0] = grey; out[i * 2 + 1] = a; - } - else if(mode->bitdepth == 16) - { + } else if(mode->bitdepth == 16) { out[i * 4 + 0] = out[i * 4 + 1] = grey; out[i * 4 + 2] = out[i * 4 + 3] = a; } - } - else if(mode->colortype == LCT_RGBA) - { - if(mode->bitdepth == 8) - { + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { out[i * 4 + 0] = r; out[i * 4 + 1] = g; out[i * 4 + 2] = b; out[i * 4 + 3] = a; - } - else - { + } else { out[i * 8 + 0] = out[i * 8 + 1] = r; out[i * 8 + 2] = out[i * 8 + 3] = g; out[i * 8 + 4] = out[i * 8 + 5] = b; @@ -3239,33 +2888,25 @@ static unsigned rgba8ToPixel(unsigned char* out, size_t i, /*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ static void rgba16ToPixel(unsigned char* out, size_t i, const LodePNGColorMode* mode, - unsigned short r, unsigned short g, unsigned short b, unsigned short a) -{ - if(mode->colortype == LCT_GREY) - { - unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + unsigned short r, unsigned short g, unsigned short b, unsigned short a) { + if(mode->colortype == LCT_GREY) { + unsigned short grey = r; /*((unsigned)r + g + b) / 3;*/ out[i * 2 + 0] = (grey >> 8) & 255; out[i * 2 + 1] = grey & 255; - } - else if(mode->colortype == LCT_RGB) - { + } else if(mode->colortype == LCT_RGB) { out[i * 6 + 0] = (r >> 8) & 255; out[i * 6 + 1] = r & 255; out[i * 6 + 2] = (g >> 8) & 255; out[i * 6 + 3] = g & 255; out[i * 6 + 4] = (b >> 8) & 255; out[i * 6 + 5] = b & 255; - } - else if(mode->colortype == LCT_GREY_ALPHA) - { - unsigned short grey = r; /*((unsigned)r + g + b) / 3*/; + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned short grey = r; /*((unsigned)r + g + b) / 3;*/ out[i * 4 + 0] = (grey >> 8) & 255; out[i * 4 + 1] = grey & 255; out[i * 4 + 2] = (a >> 8) & 255; out[i * 4 + 3] = a & 255; - } - else if(mode->colortype == LCT_RGBA) - { + } else if(mode->colortype == LCT_RGBA) { out[i * 8 + 0] = (r >> 8) & 255; out[i * 8 + 1] = r & 255; out[i * 8 + 2] = (g >> 8) & 255; @@ -3281,24 +2922,17 @@ static void rgba16ToPixel(unsigned char* out, size_t i, static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a, const unsigned char* in, size_t i, - const LodePNGColorMode* mode) -{ - if(mode->colortype == LCT_GREY) - { - if(mode->bitdepth == 8) - { + const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { *r = *g = *b = in[i]; if(mode->key_defined && *r == mode->key_r) *a = 0; else *a = 255; - } - else if(mode->bitdepth == 16) - { + } else if(mode->bitdepth == 16) { *r = *g = *b = in[i * 2 + 0]; if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; else *a = 255; - } - else - { + } else { unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ size_t j = i * mode->bitdepth; unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); @@ -3306,17 +2940,12 @@ static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, if(mode->key_defined && value == mode->key_r) *a = 0; else *a = 255; } - } - else if(mode->colortype == LCT_RGB) - { - if(mode->bitdepth == 8) - { + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; else *a = 255; - } - else - { + } else { *r = in[i * 6 + 0]; *g = in[i * 6 + 2]; *b = in[i * 6 + 4]; @@ -3325,56 +2954,40 @@ static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; else *a = 255; } - } - else if(mode->colortype == LCT_PALETTE) - { + } else if(mode->colortype == LCT_PALETTE) { unsigned index; if(mode->bitdepth == 8) index = in[i]; - else - { + else { size_t j = i * mode->bitdepth; index = readBitsFromReversedStream(&j, in, mode->bitdepth); } - if(index >= mode->palettesize) - { + if(index >= mode->palettesize) { /*This is an error according to the PNG spec, but common PNG decoders make it black instead. Done here too, slightly faster due to no error handling needed.*/ *r = *g = *b = 0; *a = 255; - } - else - { + } else { *r = mode->palette[index * 4 + 0]; *g = mode->palette[index * 4 + 1]; *b = mode->palette[index * 4 + 2]; *a = mode->palette[index * 4 + 3]; } - } - else if(mode->colortype == LCT_GREY_ALPHA) - { - if(mode->bitdepth == 8) - { + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { *r = *g = *b = in[i * 2 + 0]; *a = in[i * 2 + 1]; - } - else - { + } else { *r = *g = *b = in[i * 4 + 0]; *a = in[i * 4 + 2]; } - } - else if(mode->colortype == LCT_RGBA) - { - if(mode->bitdepth == 8) - { + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { *r = in[i * 4 + 0]; *g = in[i * 4 + 1]; *b = in[i * 4 + 2]; *a = in[i * 4 + 3]; - } - else - { + } else { *r = in[i * 8 + 0]; *g = in[i * 8 + 2]; *b = in[i * 8 + 4]; @@ -3390,57 +3003,40 @@ enough memory, if has_alpha is true the output is RGBA. mode has the color mode of the input buffer.*/ static void getPixelColorsRGBA8(unsigned char* buffer, size_t numpixels, unsigned has_alpha, const unsigned char* in, - const LodePNGColorMode* mode) -{ + const LodePNGColorMode* mode) { unsigned num_channels = has_alpha ? 4 : 3; size_t i; - if(mode->colortype == LCT_GREY) - { - if(mode->bitdepth == 8) - { - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i]; if(has_alpha) buffer[3] = mode->key_defined && in[i] == mode->key_r ? 0 : 255; } - } - else if(mode->bitdepth == 16) - { - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2]; if(has_alpha) buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; } - } - else - { + } else { unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ size_t j = 0; - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; if(has_alpha) buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; } } - } - else if(mode->colortype == LCT_RGB) - { - if(mode->bitdepth == 8) - { - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 3 + 0]; buffer[1] = in[i * 3 + 1]; buffer[2] = in[i * 3 + 2]; if(has_alpha) buffer[3] = mode->key_defined && buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b ? 0 : 255; } - } - else - { - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 6 + 0]; buffer[1] = in[i * 6 + 2]; buffer[2] = in[i * 6 + 4]; @@ -3450,67 +3046,47 @@ static void getPixelColorsRGBA8(unsigned char* buffer, size_t numpixels, && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; } } - } - else if(mode->colortype == LCT_PALETTE) - { + } else if(mode->colortype == LCT_PALETTE) { unsigned index; size_t j = 0; - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { if(mode->bitdepth == 8) index = in[i]; else index = readBitsFromReversedStream(&j, in, mode->bitdepth); - if(index >= mode->palettesize) - { + if(index >= mode->palettesize) { /*This is an error according to the PNG spec, but most PNG decoders make it black instead. Done here too, slightly faster due to no error handling needed.*/ buffer[0] = buffer[1] = buffer[2] = 0; if(has_alpha) buffer[3] = 255; - } - else - { + } else { buffer[0] = mode->palette[index * 4 + 0]; buffer[1] = mode->palette[index * 4 + 1]; buffer[2] = mode->palette[index * 4 + 2]; if(has_alpha) buffer[3] = mode->palette[index * 4 + 3]; } } - } - else if(mode->colortype == LCT_GREY_ALPHA) - { - if(mode->bitdepth == 8) - { - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; if(has_alpha) buffer[3] = in[i * 2 + 1]; } - } - else - { - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; if(has_alpha) buffer[3] = in[i * 4 + 2]; } } - } - else if(mode->colortype == LCT_RGBA) - { - if(mode->bitdepth == 8) - { - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 4 + 0]; buffer[1] = in[i * 4 + 1]; buffer[2] = in[i * 4 + 2]; if(has_alpha) buffer[3] = in[i * 4 + 3]; } - } - else - { - for(i = 0; i != numpixels; ++i, buffer += num_channels) - { + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { buffer[0] = in[i * 8 + 0]; buffer[1] = in[i * 8 + 2]; buffer[2] = in[i * 8 + 4]; @@ -3523,16 +3099,12 @@ static void getPixelColorsRGBA8(unsigned char* buffer, size_t numpixels, /*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with given color type, but the given color type must be 16-bit itself.*/ static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, - const unsigned char* in, size_t i, const LodePNGColorMode* mode) -{ - if(mode->colortype == LCT_GREY) - { + const unsigned char* in, size_t i, const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; else *a = 65535; - } - else if(mode->colortype == LCT_RGB) - { + } else if(mode->colortype == LCT_RGB) { *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; @@ -3541,14 +3113,10 @@ static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned s && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; else *a = 65535; - } - else if(mode->colortype == LCT_GREY_ALPHA) - { + } else if(mode->colortype == LCT_GREY_ALPHA) { *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; - } - else if(mode->colortype == LCT_RGBA) - { + } else if(mode->colortype == LCT_RGBA) { *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; @@ -3558,37 +3126,32 @@ static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned s unsigned lodepng_convert(unsigned char* out, const unsigned char* in, const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, - unsigned w, unsigned h) -{ + unsigned w, unsigned h) { size_t i; ColorTree tree; size_t numpixels = (size_t)w * (size_t)h; unsigned error = 0; - if(lodepng_color_mode_equal(mode_out, mode_in)) - { + if(lodepng_color_mode_equal(mode_out, mode_in)) { size_t numbytes = lodepng_get_raw_size(w, h, mode_in); for(i = 0; i != numbytes; ++i) out[i] = in[i]; return 0; } - if(mode_out->colortype == LCT_PALETTE) - { + if(mode_out->colortype == LCT_PALETTE) { size_t palettesize = mode_out->palettesize; const unsigned char* palette = mode_out->palette; size_t palsize = (size_t)1u << mode_out->bitdepth; /*if the user specified output palette but did not give the values, assume they want the values of the input color type (assuming that one is palette). Note that we never create a new palette ourselves.*/ - if(palettesize == 0) - { + if(palettesize == 0) { palettesize = mode_in->palettesize; palette = mode_in->palette; /*if the input was also palette with same bitdepth, then the color types are also equal, so copy literally. This to preserve the exact indices that were in the PNG even in case there are duplicate colors in the palette.*/ - if (mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) - { + if (mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { size_t numbytes = lodepng_get_raw_size(w, h, mode_in); for(i = 0; i != numbytes; ++i) out[i] = in[i]; return 0; @@ -3596,43 +3159,32 @@ unsigned lodepng_convert(unsigned char* out, const unsigned char* in, } if(palettesize < palsize) palsize = palettesize; color_tree_init(&tree); - for(i = 0; i != palsize; ++i) - { + for(i = 0; i != palsize; ++i) { const unsigned char* p = &palette[i * 4]; color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); } } - if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) - { - for(i = 0; i != numpixels; ++i) - { + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for(i = 0; i != numpixels; ++i) { unsigned short r = 0, g = 0, b = 0, a = 0; getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); rgba16ToPixel(out, i, mode_out, r, g, b, a); } - } - else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) - { + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { getPixelColorsRGBA8(out, numpixels, 1, in, mode_in); - } - else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) - { + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { getPixelColorsRGBA8(out, numpixels, 0, in, mode_in); - } - else - { + } else { unsigned char r = 0, g = 0, b = 0, a = 0; - for(i = 0; i != numpixels; ++i) - { + for(i = 0; i != numpixels; ++i) { getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); if (error) break; } } - if(mode_out->colortype == LCT_PALETTE) - { + if(mode_out->colortype == LCT_PALETTE) { color_tree_cleanup(&tree); } @@ -3649,63 +3201,47 @@ any palette index but doesn't have an alpha channel. Idem with ignoring color ke unsigned lodepng_convert_rgb( unsigned* r_out, unsigned* g_out, unsigned* b_out, unsigned r_in, unsigned g_in, unsigned b_in, - const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) -{ + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { unsigned r = 0, g = 0, b = 0; unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ unsigned shift = 16 - mode_out->bitdepth; - if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) - { + if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { r = g = b = r_in * mul; - } - else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) - { + } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { r = r_in * mul; g = g_in * mul; b = b_in * mul; - } - else if(mode_in->colortype == LCT_PALETTE) - { + } else if(mode_in->colortype == LCT_PALETTE) { if(r_in >= mode_in->palettesize) return 82; r = mode_in->palette[r_in * 4 + 0] * 257u; g = mode_in->palette[r_in * 4 + 1] * 257u; b = mode_in->palette[r_in * 4 + 2] * 257u; - } - else - { + } else { return 31; } /* now convert to output format */ - if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) - { + if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { *r_out = r >> shift ; - } - else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) - { + } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { *r_out = r >> shift ; *g_out = g >> shift ; *b_out = b >> shift ; - } - else if(mode_out->colortype == LCT_PALETTE) - { + } else if(mode_out->colortype == LCT_PALETTE) { unsigned i; /* a 16-bit color cannot be in the palette */ if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; for(i = 0; i < mode_out->palettesize; i++) { unsigned j = i * 4; if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && - (b >> 8) == mode_out->palette[j + 2]) - { + (b >> 8) == mode_out->palette[j + 2]) { *r_out = i; return 0; } } return 82; - } - else - { + } else { return 31; } @@ -3714,8 +3250,7 @@ unsigned lodepng_convert_rgb( #ifdef LODEPNG_COMPILE_ENCODER -void lodepng_color_profile_init(LodePNGColorProfile* profile) -{ +void lodepng_color_profile_init(LodePNGColorProfile* profile) { profile->colored = 0; profile->key = 0; profile->key_r = profile->key_g = profile->key_b = 0; @@ -3726,8 +3261,7 @@ void lodepng_color_profile_init(LodePNGColorProfile* profile) } /*function used for debug purposes with C++*/ -/*void printColorProfile(LodePNGColorProfile* p) -{ +/*void printColorProfile(LodePNGColorProfile* p) { std::cout << "colored: " << (int)p->colored << ", "; std::cout << "key: " << (int)p->key << ", "; std::cout << "key_r: " << (int)p->key_r << ", "; @@ -3739,8 +3273,7 @@ void lodepng_color_profile_init(LodePNGColorProfile* profile) }*/ /*Returns how many bits needed to represent given value (max 8 bit)*/ -static unsigned getValueRequiredBits(unsigned char value) -{ +static unsigned getValueRequiredBits(unsigned char value) { if(value == 0 || value == 255) return 1; /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; @@ -3751,8 +3284,7 @@ static unsigned getValueRequiredBits(unsigned char value) It's ok to set some parameters of profile to done already.*/ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, const unsigned char* in, unsigned w, unsigned h, - const LodePNGColorMode* mode_in) -{ + const LodePNGColorMode* mode_in) { unsigned error = 0; size_t i; ColorTree tree; @@ -3780,25 +3312,20 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, if(profile->bits >= bpp) bits_done = 1; if(profile->numcolors >= maxnumcolors) numcolors_done = 1; - if(!numcolors_done) - { - for(i = 0; i < profile->numcolors; i++) - { + if(!numcolors_done) { + for(i = 0; i < profile->numcolors; i++) { const unsigned char* color = &profile->palette[i * 4]; color_tree_add(&tree, color[0], color[1], color[2], color[3], i); } } /*Check if the 16-bit input is truly 16-bit*/ - if(mode_in->bitdepth == 16 && !sixteen) - { + if(mode_in->bitdepth == 16 && !sixteen) { unsigned short r, g, b, a; - for(i = 0; i != numpixels; ++i) - { + for(i = 0; i != numpixels; ++i) { getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || - (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ - { + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { profile->bits = 16; sixteen = 1; bits_done = 1; @@ -3808,38 +3335,29 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, } } - if(sixteen) - { + if(sixteen) { unsigned short r = 0, g = 0, b = 0, a = 0; - for(i = 0; i != numpixels; ++i) - { + for(i = 0; i != numpixels; ++i) { getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); - if(!colored_done && (r != g || r != b)) - { + if(!colored_done && (r != g || r != b)) { profile->colored = 1; colored_done = 1; } - if(!alpha_done) - { + if(!alpha_done) { unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b); - if(a != 65535 && (a != 0 || (profile->key && !matchkey))) - { + if(a != 65535 && (a != 0 || (profile->key && !matchkey))) { profile->alpha = 1; profile->key = 0; alpha_done = 1; - } - else if(a == 0 && !profile->alpha && !profile->key) - { + } else if(a == 0 && !profile->alpha && !profile->key) { profile->key = 1; profile->key_r = r; profile->key_g = g; profile->key_b = b; - } - else if(a == 65535 && profile->key && matchkey) - { + } else if(a == 65535 && profile->key && matchkey) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ profile->alpha = 1; profile->key = 0; @@ -3849,13 +3367,10 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, if(alpha_done && numcolors_done && colored_done && bits_done) break; } - if(profile->key && !profile->alpha) - { - for(i = 0; i != numpixels; ++i) - { + if(profile->key && !profile->alpha) { + for(i = 0; i != numpixels; ++i) { getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); - if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b) - { + if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ profile->alpha = 1; profile->key = 0; @@ -3863,48 +3378,37 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, } } } - } - else /* < 16-bit */ - { + } else /* < 16-bit */ { unsigned char r = 0, g = 0, b = 0, a = 0; - for(i = 0; i != numpixels; ++i) - { + for(i = 0; i != numpixels; ++i) { getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); - if(!bits_done && profile->bits < 8) - { + if(!bits_done && profile->bits < 8) { /*only r is checked, < 8 bits is only relevant for greyscale*/ unsigned bits = getValueRequiredBits(r); if(bits > profile->bits) profile->bits = bits; } bits_done = (profile->bits >= bpp); - if(!colored_done && (r != g || r != b)) - { + if(!colored_done && (r != g || r != b)) { profile->colored = 1; colored_done = 1; if(profile->bits < 8) profile->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ } - if(!alpha_done) - { + if(!alpha_done) { unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b); - if(a != 255 && (a != 0 || (profile->key && !matchkey))) - { + if(a != 255 && (a != 0 || (profile->key && !matchkey))) { profile->alpha = 1; profile->key = 0; alpha_done = 1; if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ - } - else if(a == 0 && !profile->alpha && !profile->key) - { + } else if(a == 0 && !profile->alpha && !profile->key) { profile->key = 1; profile->key_r = r; profile->key_g = g; profile->key_b = b; - } - else if(a == 255 && profile->key && matchkey) - { + } else if(a == 255 && profile->key && matchkey) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ profile->alpha = 1; profile->key = 0; @@ -3913,13 +3417,10 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, } } - if(!numcolors_done) - { - if(!color_tree_has(&tree, r, g, b, a)) - { + if(!numcolors_done) { + if(!color_tree_has(&tree, r, g, b, a)) { color_tree_add(&tree, r, g, b, a, profile->numcolors); - if(profile->numcolors < 256) - { + if(profile->numcolors < 256) { unsigned char* p = profile->palette; unsigned n = profile->numcolors; p[n * 4 + 0] = r; @@ -3935,13 +3436,10 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, if(alpha_done && numcolors_done && colored_done && bits_done) break; } - if(profile->key && !profile->alpha) - { - for(i = 0; i != numpixels; ++i) - { + if(profile->key && !profile->alpha) { + for(i = 0; i != numpixels; ++i) { getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); - if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b) - { + if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b) { /* Color key cannot be used if an opaque pixel also has that RGB color. */ profile->alpha = 1; profile->key = 0; @@ -3966,8 +3464,7 @@ unsigned lodepng_get_color_profile(LodePNGColorProfile* profile, (with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for all pixels of an image but only for a few additional values. */ static unsigned lodepng_color_profile_add(LodePNGColorProfile* profile, - unsigned r, unsigned g, unsigned b, unsigned a) -{ + unsigned r, unsigned g, unsigned b, unsigned a) { unsigned error = 0; unsigned char image[8]; LodePNGColorMode mode; @@ -3986,8 +3483,7 @@ static unsigned lodepng_color_profile_add(LodePNGColorProfile* profile, when relevant.*/ static unsigned auto_choose_color_from_profile(LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, - const LodePNGColorProfile* prof) -{ + const LodePNGColorProfile* prof) { unsigned error = 0; unsigned palettebits, palette_ok; size_t i, n; @@ -3999,8 +3495,7 @@ static unsigned auto_choose_color_from_profile(LodePNGColorMode* mode_out, mode_out->key_defined = 0; - if(key && numpixels <= 16) - { + if(key && numpixels <= 16) { alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ key = 0; if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ @@ -4011,12 +3506,10 @@ static unsigned auto_choose_color_from_profile(LodePNGColorMode* mode_out, if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ if(!prof->colored && bits <= palettebits) palette_ok = 0; /*grey is less overhead*/ - if(palette_ok) - { + if(palette_ok) { const unsigned char* p = prof->palette; lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ - for(i = 0; i != prof->numcolors; ++i) - { + for(i = 0; i != prof->numcolors; ++i) { error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); if(error) break; } @@ -4025,21 +3518,17 @@ static unsigned auto_choose_color_from_profile(LodePNGColorMode* mode_out, mode_out->bitdepth = palettebits; if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize - && mode_in->bitdepth == mode_out->bitdepth) - { + && mode_in->bitdepth == mode_out->bitdepth) { /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ lodepng_color_mode_cleanup(mode_out); lodepng_color_mode_copy(mode_out, mode_in); } - } - else /*8-bit or 16-bit per channel*/ - { + } else /*8-bit or 16-bit per channel*/ { mode_out->bitdepth = bits; mode_out->colortype = alpha ? (prof->colored ? LCT_RGBA : LCT_GREY_ALPHA) : (prof->colored ? LCT_RGB : LCT_GREY); - if(key) - { + if(key) { unsigned mask = (1u << mode_out->bitdepth) - 1u; /*profile always uses 16-bit, mask converts it*/ mode_out->key_r = prof->key_r & mask; mode_out->key_g = prof->key_g & mask; @@ -4058,8 +3547,7 @@ Updates values of mode with a potentially smaller color model. mode_out should contain the user chosen color model, but will be overwritten with the new chosen one.*/ unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, const unsigned char* image, unsigned w, unsigned h, - const LodePNGColorMode* mode_in) -{ + const LodePNGColorMode* mode_in) { unsigned error = 0; LodePNGColorProfile prof; lodepng_color_profile_init(&prof); @@ -4075,8 +3563,7 @@ Paeth predicter, used by PNG filter type 4 The parameters are of type short, but should come from unsigned chars, the shorts are only needed to make the paeth calculation correct. */ -static unsigned char paethPredictor(short a, short b, short c) -{ +static unsigned char paethPredictor(short a, short b, short c) { short pa = abs(b - c); short pb = abs(a - c); short pc = abs(a + b - c - c); @@ -4109,14 +3596,12 @@ bpp: bits per pixel end at a full byte */ static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], - size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) -{ + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ unsigned i; /*calculate width and height in pixels of each pass*/ - for(i = 0; i != 7; ++i) - { + for(i = 0; i != 7; ++i) { passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; if(passw[i] == 0) passh[i] = 0; @@ -4124,8 +3609,7 @@ static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t fil } filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; - for(i = 0; i != 7; ++i) - { + for(i = 0; i != 7; ++i) { /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ filter_passstart[i + 1] = filter_passstart[i] + ((passw[i] && passh[i]) ? passh[i] * (1 + (passw[i] * bpp + 7) / 8) : 0); @@ -4144,16 +3628,13 @@ static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t fil /*read the information from the header and store it in the LodePNGInfo. return value is error*/ unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, - const unsigned char* in, size_t insize) -{ + const unsigned char* in, size_t insize) { unsigned width, height; LodePNGInfo* info = &state->info_png; - if(insize == 0 || in == 0) - { + if(insize == 0 || in == 0) { CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ } - if(insize < 33) - { + if(insize < 33) { CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ } @@ -4163,16 +3644,13 @@ unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, lodepng_info_init(info); if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 - || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) - { + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ } - if(lodepng_chunk_length(in + 8) != 13) - { + if(lodepng_chunk_length(in + 8) != 13) { CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ } - if(!lodepng_chunk_type_equals(in + 8, "IHDR")) - { + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ } @@ -4185,20 +3663,17 @@ unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, info->filter_method = in[27]; info->interlace_method = in[28]; - if(width == 0 || height == 0) - { + if(width == 0 || height == 0) { CERROR_RETURN_ERROR(state->error, 93); } if(w) *w = width; if(h) *h = height; - if(!state->decoder.ignore_crc) - { + if(!state->decoder.ignore_crc) { unsigned CRC = lodepng_read32bitInt(&in[29]); unsigned checksum = lodepng_crc32(&in[12], 17); - if(CRC != checksum) - { + if(CRC != checksum) { CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ } } @@ -4215,8 +3690,7 @@ unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, } static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, - size_t bytewidth, unsigned char filterType, size_t length) -{ + size_t bytewidth, unsigned char filterType, size_t length) { /* For PNG filter method 0 unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, @@ -4227,8 +3701,7 @@ static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scan */ size_t i; - switch(filterType) - { + switch(filterType) { case 0: for(i = 0; i != length; ++i) recon[i] = scanline[i]; break; @@ -4237,47 +3710,34 @@ static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scan for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth]; break; case 2: - if(precon) - { + if(precon) { for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; - } - else - { + } else { for(i = 0; i != length; ++i) recon[i] = scanline[i]; } break; case 3: - if(precon) - { + if(precon) { for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1); for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) >> 1); - } - else - { + } else { for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + (recon[i - bytewidth] >> 1); } break; case 4: - if(precon) - { - for(i = 0; i != bytewidth; ++i) - { + if(precon) { + for(i = 0; i != bytewidth; ++i) { recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ } - for(i = bytewidth; i < length; ++i) - { + for(i = bytewidth; i < length; ++i) { recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); } - } - else - { - for(i = 0; i != bytewidth; ++i) - { + } else { + for(i = 0; i != bytewidth; ++i) { recon[i] = scanline[i]; } - for(i = bytewidth; i < length; ++i) - { + for(i = bytewidth; i < length; ++i) { /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ recon[i] = (scanline[i] + recon[i - bytewidth]); } @@ -4288,8 +3748,7 @@ static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scan return 0; } -static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) -{ +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { /* For PNG filter method 0 this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) @@ -4305,8 +3764,7 @@ static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w size_t bytewidth = (bpp + 7) / 8; size_t linebytes = (w * bpp + 7) / 8; - for(y = 0; y < h; ++y) - { + for(y = 0; y < h; ++y) { size_t outindex = linebytes * y; size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ unsigned char filterType = in[inindex]; @@ -4330,47 +3788,37 @@ out must be big enough AND must be 0 everywhere if bpp < 8 in the current implem (because that's likely a little bit faster) NOTE: comments about padding bits are only relevant if bpp < 8 */ -static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) -{ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned i; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); - if(bpp >= 8) - { - for(i = 0; i != 7; ++i) - { + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { unsigned x, y, b; size_t bytewidth = bpp / 8; for(y = 0; y < passh[i]; ++y) - for(x = 0; x < passw[i]; ++x) - { + for(x = 0; x < passw[i]; ++x) { size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; - for(b = 0; b < bytewidth; ++b) - { + for(b = 0; b < bytewidth; ++b) { out[pixeloutstart + b] = in[pixelinstart + b]; } } } - } - else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ - { - for(i = 0; i != 7; ++i) - { + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { unsigned x, y, b; unsigned ilinebits = bpp * passw[i]; unsigned olinebits = bpp * w; size_t obp, ibp; /*bit pointers (for out and in buffer)*/ for(y = 0; y < passh[i]; ++y) - for(x = 0; x < passw[i]; ++x) - { + for(x = 0; x < passw[i]; ++x) { ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; - for(b = 0; b < bpp; ++b) - { + for(b = 0; b < bpp; ++b) { unsigned char bit = readBitFromReversedStream(&ibp, in); /*note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise*/ setBitOfReversedStream0(&obp, out, bit); @@ -4381,8 +3829,7 @@ static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsig } static void removePaddingBits(unsigned char* out, const unsigned char* in, - size_t olinebits, size_t ilinebits, unsigned h) -{ + size_t olinebits, size_t ilinebits, unsigned h) { /* After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers @@ -4395,11 +3842,9 @@ static void removePaddingBits(unsigned char* out, const unsigned char* in, unsigned y; size_t diff = ilinebits - olinebits; size_t ibp = 0, obp = 0; /*input and output bit pointers*/ - for(y = 0; y < h; ++y) - { + for(y = 0; y < h; ++y) { size_t x; - for(x = 0; x < olinebits; ++x) - { + for(x = 0; x < olinebits; ++x) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } @@ -4411,8 +3856,7 @@ static void removePaddingBits(unsigned char* out, const unsigned char* in, the IDAT chunks (with filter index bytes and possible padding bits) return value is error*/ static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, - unsigned w, unsigned h, const LodePNGInfo* info_png) -{ + unsigned w, unsigned h, const LodePNGInfo* info_png) { /* This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. Steps: @@ -4423,30 +3867,24 @@ static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, unsigned bpp = lodepng_get_bpp(&info_png->color); if(bpp == 0) return 31; /*error: invalid colortype*/ - if(info_png->interlace_method == 0) - { - if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) - { + if(info_png->interlace_method == 0) { + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) { CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h); } /*we can immediately filter into the out buffer, no other steps needed*/ else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); - } - else /*interlace_method is 1 (Adam7)*/ - { + } else /*interlace_method is 1 (Adam7)*/ { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned i; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); - for(i = 0; i != 7; ++i) - { + for(i = 0; i != 7; ++i) { CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, move bytes instead of bits or move not at all*/ - if(bpp < 8) - { + if(bpp < 8) { /*remove padding bits in scanlines; after this there still may be padding bits between the different reduced images: each reduced image still starts nicely at a byte*/ removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, @@ -4460,21 +3898,18 @@ static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, return 0; } -static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) -{ +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { unsigned pos = 0, i; if(color->palette) lodepng_free(color->palette); color->palettesize = chunkLength / 3; color->palette = (unsigned char*)lodepng_malloc(4 * color->palettesize); - if(!color->palette && color->palettesize) - { + if(!color->palette && color->palettesize) { color->palettesize = 0; return 83; /*alloc fail*/ } if(color->palettesize > 256) return 38; /*error: palette too big*/ - for(i = 0; i != color->palettesize; ++i) - { + for(i = 0; i != color->palettesize; ++i) { color->palette[4 * i + 0] = data[pos++]; /*R*/ color->palette[4 * i + 1] = data[pos++]; /*G*/ color->palette[4 * i + 2] = data[pos++]; /*B*/ @@ -4484,26 +3919,20 @@ static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* dat return 0; /* OK */ } -static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) -{ +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { unsigned i; - if(color->colortype == LCT_PALETTE) - { + if(color->colortype == LCT_PALETTE) { /*error: more alpha values given than there are palette entries*/ if(chunkLength > color->palettesize) return 39; for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; - } - else if(color->colortype == LCT_GREY) - { + } else if(color->colortype == LCT_GREY) { /*error: this chunk must be 2 bytes for greyscale image*/ if(chunkLength != 2) return 30; color->key_defined = 1; color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; - } - else if(color->colortype == LCT_RGB) - { + } else if(color->colortype == LCT_RGB) { /*error: this chunk must be 6 bytes for RGB image*/ if(chunkLength != 6) return 41; @@ -4520,10 +3949,8 @@ static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* dat #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*background color chunk (bKGD)*/ -static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) -{ - if(info->color.colortype == LCT_PALETTE) - { +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(info->color.colortype == LCT_PALETTE) { /*error: this chunk must be 1 byte for indexed color image*/ if(chunkLength != 1) return 43; @@ -4532,18 +3959,14 @@ static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, siz info->background_defined = 1; info->background_r = info->background_g = info->background_b = data[0]; - } - else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) - { + } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { /*error: this chunk must be 2 bytes for greyscale image*/ if(chunkLength != 2) return 44; /*the values are truncated to bitdepth in the PNG file*/ info->background_defined = 1; info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; - } - else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) - { + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { /*error: this chunk must be 6 bytes for greyscale image*/ if(chunkLength != 6) return 45; @@ -4558,14 +3981,12 @@ static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, siz } /*text chunk (tEXt)*/ -static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) -{ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { unsigned error = 0; char *key = 0, *str = 0; unsigned i; - while(!error) /*not really a while loop, only used to break on error*/ - { + while(!error) /*not really a while loop, only used to break on error*/ { unsigned length, string2_begin; length = 0; @@ -4602,8 +4023,7 @@ static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, siz /*compressed text chunk (zTXt)*/ static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, - const unsigned char* data, size_t chunkLength) -{ + const unsigned char* data, size_t chunkLength) { unsigned error = 0; unsigned i; @@ -4613,8 +4033,7 @@ static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSetting ucvector_init(&decoded); - while(!error) /*not really a while loop, only used to break on error*/ - { + while(!error) /*not really a while loop, only used to break on error*/ { for(length = 0; length < chunkLength && data[length] != 0; ++length) ; if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ @@ -4651,8 +4070,7 @@ static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSetting /*international text chunk (iTXt)*/ static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, - const unsigned char* data, size_t chunkLength) -{ + const unsigned char* data, size_t chunkLength) { unsigned error = 0; unsigned i; @@ -4661,8 +4079,7 @@ static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSetting ucvector decoded; ucvector_init(&decoded); /* TODO: only use in case of compressed text */ - while(!error) /*not really a while loop, only used to break on error*/ - { + while(!error) /*not really a while loop, only used to break on error*/ { /*Quick check if the chunk length isn't too small. Even without check it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ @@ -4712,8 +4129,7 @@ static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSetting length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; - if(compressed) - { + if(compressed) { /*will fail if zlib error, e.g. if length is too small*/ error = zlib_decompress(&decoded.data, &decoded.size, (unsigned char*)(&data[begin]), @@ -4721,9 +4137,7 @@ static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSetting if(error) break; if(decoded.allocsize < decoded.size) decoded.allocsize = decoded.size; ucvector_push_back(&decoded, 0); - } - else - { + } else { if(!ucvector_resize(&decoded, length + 1)) CERROR_BREAK(error, 83 /*alloc fail*/); decoded.data[length] = 0; @@ -4743,8 +4157,7 @@ static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSetting return error; } -static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) -{ +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ info->time_defined = 1; @@ -4758,8 +4171,7 @@ static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, siz return 0; /* OK */ } -static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) -{ +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ info->phys_defined = 1; @@ -4770,8 +4182,7 @@ static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, siz return 0; /* OK */ } -static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) -{ +static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ info->gama_defined = 1; @@ -4780,8 +4191,7 @@ static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, siz return 0; /* OK */ } -static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) -{ +static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ info->chrm_defined = 1; @@ -4797,8 +4207,7 @@ static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, siz return 0; /* OK */ } -static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) -{ +static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ info->srgb_defined = 1; @@ -4808,8 +4217,7 @@ static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, siz } static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings, - const unsigned char* data, size_t chunkLength) -{ + const unsigned char* data, size_t chunkLength) { unsigned error = 0; unsigned i; @@ -4854,8 +4262,7 @@ static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecompressSetting #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, - const unsigned char* in, size_t insize) -{ + const unsigned char* in, size_t insize) { const unsigned char* chunk = in + pos; unsigned chunkLength; const unsigned char* data; @@ -4868,64 +4275,38 @@ unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, data = lodepng_chunk_data_const(chunk); if(data + chunkLength + 4 > in + insize) return 30; - if(lodepng_chunk_type_equals(chunk, "PLTE")) - { + if(lodepng_chunk_type_equals(chunk, "PLTE")) { error = readChunk_PLTE(&state->info_png.color, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "tRNS")) - { + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { error = readChunk_tRNS(&state->info_png.color, data, chunkLength); - } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS - else if(lodepng_chunk_type_equals(chunk, "bKGD")) - { + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { error = readChunk_bKGD(&state->info_png, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "tEXt")) - { + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { error = readChunk_tEXt(&state->info_png, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "zTXt")) - { + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "iTXt")) - { + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "tIME")) - { + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { error = readChunk_tIME(&state->info_png, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "pHYs")) - { + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { error = readChunk_pHYs(&state->info_png, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "gAMA")) - { + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { error = readChunk_gAMA(&state->info_png, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "cHRM")) - { + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { error = readChunk_cHRM(&state->info_png, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "sRGB")) - { + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { error = readChunk_sRGB(&state->info_png, data, chunkLength); - } - else if(lodepng_chunk_type_equals(chunk, "iCCP")) - { + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { error = readChunk_iCCP(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); - } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ - else - { + } else { /* unhandled chunk is ok (is not an error) */ unhandled = 1; } - if(!error && !unhandled && !state->decoder.ignore_crc) - { + if(!error && !unhandled && !state->decoder.ignore_crc) { if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ } @@ -4935,8 +4316,7 @@ unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, /*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, - const unsigned char* in, size_t insize) -{ + const unsigned char* in, size_t insize) { unsigned char IEND = 0; const unsigned char* chunk; size_t i; @@ -4951,14 +4331,15 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ - /*provide some proper output values if error will happen*/ + + /* safe output values in case error happens */ *out = 0; + *w = *h = 0; state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ if(state->error) return; - if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) - { + if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ } @@ -4967,14 +4348,12 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer*/ - while(!IEND && !state->error) - { + while(!IEND && !state->error) { unsigned chunkLength; const unsigned char* data; /*the data in the chunk*/ /*error: size of the in buffer too small to contain next chunk*/ - if((size_t)((chunk - in) + 12) > insize || chunk < in) - { + if((size_t)((chunk - in) + 12) > insize || chunk < in) { if(state->decoder.ignore_end) break; /*other errors may still happen though*/ CERROR_BREAK(state->error, 30); } @@ -4982,14 +4361,12 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ chunkLength = lodepng_chunk_length(chunk); /*error: chunk length larger than the max PNG chunk size*/ - if(chunkLength > 2147483647) - { + if(chunkLength > 2147483647) { if(state->decoder.ignore_end) break; /*other errors may still happen though*/ CERROR_BREAK(state->error, 63); } - if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) - { + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) { CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ } @@ -4998,8 +4375,7 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, unknown = 0; /*IDAT chunk, containing compressed image data*/ - if(lodepng_chunk_type_equals(chunk, "IDAT")) - { + if(lodepng_chunk_type_equals(chunk, "IDAT")) { size_t oldsize = idat.size; size_t newsize; if(lodepng_addofl(oldsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); @@ -5008,106 +4384,73 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS critical_pos = 3; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ - } - /*IEND chunk*/ - else if(lodepng_chunk_type_equals(chunk, "IEND")) - { + } else if(lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ IEND = 1; - } - /*palette chunk (PLTE)*/ - else if(lodepng_chunk_type_equals(chunk, "PLTE")) - { + } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); if(state->error) break; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS critical_pos = 2; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ - } - /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled - in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that - affects the alpha channel of pixels. */ - else if(lodepng_chunk_type_equals(chunk, "tRNS")) - { + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); if(state->error) break; - } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS - /*background color chunk (bKGD)*/ - else if(lodepng_chunk_type_equals(chunk, "bKGD")) - { + /*background color chunk (bKGD)*/ + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { state->error = readChunk_bKGD(&state->info_png, data, chunkLength); if(state->error) break; - } - /*text chunk (tEXt)*/ - else if(lodepng_chunk_type_equals(chunk, "tEXt")) - { - if(state->decoder.read_text_chunks) - { + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + /*text chunk (tEXt)*/ + if(state->decoder.read_text_chunks) { state->error = readChunk_tEXt(&state->info_png, data, chunkLength); if(state->error) break; } - } - /*compressed text chunk (zTXt)*/ - else if(lodepng_chunk_type_equals(chunk, "zTXt")) - { - if(state->decoder.read_text_chunks) - { + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + /*compressed text chunk (zTXt)*/ + if(state->decoder.read_text_chunks) { state->error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); if(state->error) break; } - } - /*international text chunk (iTXt)*/ - else if(lodepng_chunk_type_equals(chunk, "iTXt")) - { - if(state->decoder.read_text_chunks) - { + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + /*international text chunk (iTXt)*/ + if(state->decoder.read_text_chunks) { state->error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); if(state->error) break; } - } - else if(lodepng_chunk_type_equals(chunk, "tIME")) - { + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { state->error = readChunk_tIME(&state->info_png, data, chunkLength); if(state->error) break; - } - else if(lodepng_chunk_type_equals(chunk, "pHYs")) - { + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { state->error = readChunk_pHYs(&state->info_png, data, chunkLength); if(state->error) break; - } - else if(lodepng_chunk_type_equals(chunk, "gAMA")) - { + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { state->error = readChunk_gAMA(&state->info_png, data, chunkLength); if(state->error) break; - } - else if(lodepng_chunk_type_equals(chunk, "cHRM")) - { + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { state->error = readChunk_cHRM(&state->info_png, data, chunkLength); if(state->error) break; - } - else if(lodepng_chunk_type_equals(chunk, "sRGB")) - { + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { state->error = readChunk_sRGB(&state->info_png, data, chunkLength); if(state->error) break; - } - else if(lodepng_chunk_type_equals(chunk, "iCCP")) - { + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { state->error = readChunk_iCCP(&state->info_png, &state->decoder.zlibsettings, data, chunkLength); if(state->error) break; - } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ - else /*it's not an implemented chunk type, so ignore it: skip over the data*/ - { + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ - if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) - { + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { CERROR_BREAK(state->error, 69); } unknown = 1; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS - if(state->decoder.remember_unknown_chunks) - { + if(state->decoder.remember_unknown_chunks) { state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); if(state->error) break; @@ -5115,8 +4458,7 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ } - if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ - { + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ } @@ -5126,12 +4468,9 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, ucvector_init(&scanlines); /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. If the decompressed size does not match the prediction, the image must be corrupt.*/ - if(state->info_png.interlace_method == 0) - { + if(state->info_png.interlace_method == 0) { predict = lodepng_get_raw_size_idat(*w, *h, &state->info_png.color); - } - else - { + } else { /*Adam-7 interlaced: predicted size is the sum of the 7 sub-images sizes*/ const LodePNGColorMode* color = &state->info_png.color; predict = 0; @@ -5144,22 +4483,19 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, predict += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, color); } if(!state->error && !ucvector_reserve(&scanlines, predict)) state->error = 83; /*alloc fail*/ - if(!state->error) - { + if(!state->error) { state->error = zlib_decompress(&scanlines.data, &scanlines.size, idat.data, idat.size, &state->decoder.zlibsettings); if(!state->error && scanlines.size != predict) state->error = 91; /*decompressed size doesn't match prediction*/ } ucvector_cleanup(&idat); - if(!state->error) - { + if(!state->error) { outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); *out = (unsigned char*)lodepng_malloc(outsize); if(!*out) state->error = 83; /*alloc fail*/ } - if(!state->error) - { + if(!state->error) { for(i = 0; i < outsize; i++) (*out)[i] = 0; state->error = postProcessScanlines(*out, scanlines.data, *w, *h, &state->info_png); } @@ -5168,24 +4504,19 @@ static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, LodePNGState* state, - const unsigned char* in, size_t insize) -{ + const unsigned char* in, size_t insize) { *out = 0; decodeGeneric(out, w, h, state, in, insize); if(state->error) return state->error; - if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) - { + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { /*same color type, no copying or converting of data needed*/ /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype the raw image has to the end user*/ - if(!state->decoder.color_convert) - { + if(!state->decoder.color_convert) { state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); if(state->error) return state->error; } - } - else - { + } else { /*color conversion needed; sort of copy of the data*/ unsigned char* data = *out; size_t outsize; @@ -5193,15 +4524,13 @@ unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, /*TODO: check if this works according to the statement in the documentation: "The converter can convert from greyscale input color type, to 8-bit greyscale or greyscale with alpha"*/ if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) - && !(state->info_raw.bitdepth == 8)) - { + && !(state->info_raw.bitdepth == 8)) { return 56; /*unsupported color mode conversion*/ } outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); *out = (unsigned char*)lodepng_malloc(outsize); - if(!(*out)) - { + if(!(*out)) { state->error = 83; /*alloc fail*/ } else state->error = lodepng_convert(*out, data, &state->info_raw, @@ -5212,8 +4541,7 @@ unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, } unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, - size_t insize, LodePNGColorType colortype, unsigned bitdepth) -{ + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { unsigned error; LodePNGState state; lodepng_state_init(&state); @@ -5224,42 +4552,39 @@ unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, co return error; } -unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) -{ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); } -unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) -{ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); } #ifdef LODEPNG_COMPILE_DISK unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, - LodePNGColorType colortype, unsigned bitdepth) -{ + LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer = 0; size_t buffersize; unsigned error; + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; error = lodepng_load_file(&buffer, &buffersize, filename); if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); lodepng_free(buffer); return error; } -unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) -{ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); } -unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) -{ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); } #endif /*LODEPNG_COMPILE_DISK*/ -void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) -{ +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { settings->color_convert = 1; #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS settings->read_text_chunks = 1; @@ -5275,8 +4600,7 @@ void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) #if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) -void lodepng_state_init(LodePNGState* state) -{ +void lodepng_state_init(LodePNGState* state) { #ifdef LODEPNG_COMPILE_DECODER lodepng_decoder_settings_init(&state->decoder); #endif /*LODEPNG_COMPILE_DECODER*/ @@ -5288,14 +4612,12 @@ void lodepng_state_init(LodePNGState* state) state->error = 1; } -void lodepng_state_cleanup(LodePNGState* state) -{ +void lodepng_state_cleanup(LodePNGState* state) { lodepng_color_mode_cleanup(&state->info_raw); lodepng_info_cleanup(&state->info_png); } -void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) -{ +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { lodepng_state_cleanup(dest); *dest = *source; lodepng_color_mode_init(&dest->info_raw); @@ -5313,15 +4635,13 @@ void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) /* ////////////////////////////////////////////////////////////////////////// */ /*chunkName must be string of 4 characters*/ -static unsigned addChunk(ucvector* out, const char* chunkName, const unsigned char* data, size_t length) -{ +static unsigned addChunk(ucvector* out, const char* chunkName, const unsigned char* data, size_t length) { CERROR_TRY_RETURN(lodepng_chunk_create(&out->data, &out->size, (unsigned)length, chunkName, data)); out->allocsize = out->size; /*fix the allocsize again*/ return 0; } -static void writeSignature(ucvector* out) -{ +static void writeSignature(ucvector* out) { /*8 bytes PNG signature, aka the magic bytes*/ ucvector_push_back(out, 137); ucvector_push_back(out, 80); @@ -5334,8 +4654,7 @@ static void writeSignature(ucvector* out) } static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, - LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) -{ + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { unsigned error = 0; ucvector header; ucvector_init(&header); @@ -5354,14 +4673,12 @@ static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, return error; } -static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) -{ +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { unsigned error = 0; size_t i; ucvector PLTE; ucvector_init(&PLTE); - for(i = 0; i != info->palettesize * 4; ++i) - { + for(i = 0; i != info->palettesize * 4; ++i) { /*add all channels except alpha channel*/ if(i % 4 != 3) ucvector_push_back(&PLTE, info->palette[i]); } @@ -5371,36 +4688,27 @@ static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) return error; } -static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) -{ +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { unsigned error = 0; size_t i; ucvector tRNS; ucvector_init(&tRNS); - if(info->colortype == LCT_PALETTE) - { + if(info->colortype == LCT_PALETTE) { size_t amount = info->palettesize; /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ - for(i = info->palettesize; i != 0; --i) - { + for(i = info->palettesize; i != 0; --i) { if(info->palette[4 * (i - 1) + 3] == 255) --amount; else break; } /*add only alpha channel*/ for(i = 0; i != amount; ++i) ucvector_push_back(&tRNS, info->palette[4 * i + 3]); - } - else if(info->colortype == LCT_GREY) - { - if(info->key_defined) - { + } else if(info->colortype == LCT_GREY) { + if(info->key_defined) { ucvector_push_back(&tRNS, (unsigned char)(info->key_r >> 8)); ucvector_push_back(&tRNS, (unsigned char)(info->key_r & 255)); } - } - else if(info->colortype == LCT_RGB) - { - if(info->key_defined) - { + } else if(info->colortype == LCT_RGB) { + if(info->key_defined) { ucvector_push_back(&tRNS, (unsigned char)(info->key_r >> 8)); ucvector_push_back(&tRNS, (unsigned char)(info->key_r & 255)); ucvector_push_back(&tRNS, (unsigned char)(info->key_g >> 8)); @@ -5417,8 +4725,7 @@ static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) } static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, - LodePNGCompressSettings* zlibsettings) -{ + LodePNGCompressSettings* zlibsettings) { ucvector zlibdata; unsigned error = 0; @@ -5431,8 +4738,7 @@ static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t d return error; } -static unsigned addChunk_IEND(ucvector* out) -{ +static unsigned addChunk_IEND(ucvector* out) { unsigned error = 0; error = addChunk(out, "IEND", 0, 0); return error; @@ -5440,8 +4746,7 @@ static unsigned addChunk_IEND(ucvector* out) #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS -static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) -{ +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { unsigned error = 0; size_t i; ucvector text; @@ -5457,8 +4762,7 @@ static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* te } static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, - LodePNGCompressSettings* zlibsettings) -{ + LodePNGCompressSettings* zlibsettings) { unsigned error = 0; ucvector data, compressed; size_t i, textsize = strlen(textstring); @@ -5472,8 +4776,7 @@ static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* te error = zlib_compress(&compressed.data, &compressed.size, (unsigned char*)textstring, textsize, zlibsettings); - if(!error) - { + if(!error) { for(i = 0; i != compressed.size; ++i) ucvector_push_back(&data, compressed.data[i]); error = addChunk(out, "zTXt", data.data, data.size); } @@ -5484,8 +4787,7 @@ static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* te } static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* keyword, const char* langtag, - const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) -{ + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { unsigned error = 0; ucvector data; size_t i, textsize = strlen(textstring); @@ -5502,20 +4804,16 @@ static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* ke for(i = 0; transkey[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)transkey[i]); ucvector_push_back(&data, 0); /*null termination char*/ - if(compressed) - { + if(compressed) { ucvector compressed_data; ucvector_init(&compressed_data); error = zlib_compress(&compressed_data.data, &compressed_data.size, (unsigned char*)textstring, textsize, zlibsettings); - if(!error) - { + if(!error) { for(i = 0; i != compressed_data.size; ++i) ucvector_push_back(&data, compressed_data.data[i]); } ucvector_cleanup(&compressed_data); - } - else /*not compressed*/ - { + } else /*not compressed*/ { for(i = 0; textstring[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)textstring[i]); } @@ -5524,27 +4822,21 @@ static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* ke return error; } -static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) -{ +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { unsigned error = 0; ucvector bKGD; ucvector_init(&bKGD); - if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) - { + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { ucvector_push_back(&bKGD, (unsigned char)(info->background_r >> 8)); ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); - } - else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) - { + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { ucvector_push_back(&bKGD, (unsigned char)(info->background_r >> 8)); ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); ucvector_push_back(&bKGD, (unsigned char)(info->background_g >> 8)); ucvector_push_back(&bKGD, (unsigned char)(info->background_g & 255)); ucvector_push_back(&bKGD, (unsigned char)(info->background_b >> 8)); ucvector_push_back(&bKGD, (unsigned char)(info->background_b & 255)); - } - else if(info->color.colortype == LCT_PALETTE) - { + } else if(info->color.colortype == LCT_PALETTE) { ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); /*palette index*/ } @@ -5554,8 +4846,7 @@ static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) return error; } -static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) -{ +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { unsigned error = 0; unsigned char* data = (unsigned char*)lodepng_malloc(7); if(!data) return 83; /*alloc fail*/ @@ -5571,8 +4862,7 @@ static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) return error; } -static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) -{ +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { unsigned error = 0; ucvector data; ucvector_init(&data); @@ -5587,8 +4877,7 @@ static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) return error; } -static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) -{ +static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { unsigned error = 0; ucvector data; ucvector_init(&data); @@ -5601,8 +4890,7 @@ static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) return error; } -static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) -{ +static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { unsigned error = 0; ucvector data; ucvector_init(&data); @@ -5622,14 +4910,12 @@ static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) return error; } -static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) -{ +static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { unsigned char data = info->srgb_intent; return addChunk(out, "sRGB", &data, 1); } -static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) -{ +static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { unsigned error = 0; ucvector data, compressed; size_t i; @@ -5643,8 +4929,7 @@ static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCom error = zlib_compress(&compressed.data, &compressed.size, info->iccp_profile, info->iccp_profile_size, zlibsettings); - if(!error) - { + if(!error) { for(i = 0; i != compressed.size; ++i) ucvector_push_back(&data, compressed.data[i]); error = addChunk(out, "iCCP", data.data, data.size); } @@ -5657,11 +4942,9 @@ static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCom #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, - size_t length, size_t bytewidth, unsigned char filterType) -{ + size_t length, size_t bytewidth, unsigned char filterType) { size_t i; - switch(filterType) - { + switch(filterType) { case 0: /*None*/ for(i = 0; i != length; ++i) out[i] = scanline[i]; break; @@ -5670,39 +4953,29 @@ static void filterScanline(unsigned char* out, const unsigned char* scanline, co for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; break; case 2: /*Up*/ - if(prevline) - { + if(prevline) { for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; - } - else - { + } else { for(i = 0; i != length; ++i) out[i] = scanline[i]; } break; case 3: /*Average*/ - if(prevline) - { + if(prevline) { for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); - } - else - { + } else { for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); } break; case 4: /*Paeth*/ - if(prevline) - { + if(prevline) { /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); - for(i = bytewidth; i < length; ++i) - { + for(i = bytewidth; i < length; ++i) { out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); } - } - else - { + } else { for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); @@ -5713,8 +4986,7 @@ static void filterScanline(unsigned char* out, const unsigned char* scanline, co } /* log2 approximation. A slight bit faster than std::log. */ -static float flog2(float f) -{ +static float flog2(float f) { float result = 0; while(f > 32) { result += 4; f /= 16; } while(f > 2) { ++result; f /= 2; } @@ -5722,8 +4994,7 @@ static float flog2(float f) } static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, - const LodePNGColorMode* info, const LodePNGEncoderSettings* settings) -{ + const LodePNGColorMode* info, const LodePNGEncoderSettings* settings) { /* For PNG filter method 0 out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are @@ -5758,50 +5029,38 @@ static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, if(bpp == 0) return 31; /*error: invalid color type*/ - if(strategy == LFS_ZERO) - { - for(y = 0; y != h; ++y) - { + if(strategy == LFS_ZERO) { + for(y = 0; y != h; ++y) { size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ size_t inindex = linebytes * y; out[outindex] = 0; /*filter type byte*/ filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, 0); prevline = &in[inindex]; } - } - else if(strategy == LFS_MINSUM) - { + } else if(strategy == LFS_MINSUM) { /*adaptive filtering*/ size_t sum[5]; unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ size_t smallest = 0; unsigned char type, bestType = 0; - for(type = 0; type != 5; ++type) - { + for(type = 0; type != 5; ++type) { attempt[type] = (unsigned char*)lodepng_malloc(linebytes); if(!attempt[type]) return 83; /*alloc fail*/ } - if(!error) - { - for(y = 0; y != h; ++y) - { + if(!error) { + for(y = 0; y != h; ++y) { /*try the 5 filter types*/ - for(type = 0; type != 5; ++type) - { + for(type = 0; type != 5; ++type) { filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); /*calculate the sum of the result*/ sum[type] = 0; - if(type == 0) - { + if(type == 0) { for(x = 0; x != linebytes; ++x) sum[type] += (unsigned char)(attempt[type][x]); - } - else - { - for(x = 0; x != linebytes; ++x) - { + } else { + for(x = 0; x != linebytes; ++x) { /*For differences, each byte should be treated as signed, values above 127 are negative (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. This means filtertype 0 is almost never chosen, but that is justified.*/ @@ -5811,8 +5070,7 @@ static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, } /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ - if(type == 0 || sum[type] < smallest) - { + if(type == 0 || sum[type] < smallest) { bestType = type; smallest = sum[type]; } @@ -5827,39 +5085,32 @@ static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, } for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); - } - else if(strategy == LFS_ENTROPY) - { + } else if(strategy == LFS_ENTROPY) { float sum[5]; unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ float smallest = 0; unsigned type, bestType = 0; unsigned count[256]; - for(type = 0; type != 5; ++type) - { + for(type = 0; type != 5; ++type) { attempt[type] = (unsigned char*)lodepng_malloc(linebytes); if(!attempt[type]) return 83; /*alloc fail*/ } - for(y = 0; y != h; ++y) - { + for(y = 0; y != h; ++y) { /*try the 5 filter types*/ - for(type = 0; type != 5; ++type) - { + for(type = 0; type != 5; ++type) { filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); for(x = 0; x != 256; ++x) count[x] = 0; for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; ++count[type]; /*the filter type itself is part of the scanline*/ sum[type] = 0; - for(x = 0; x != 256; ++x) - { + for(x = 0; x != 256; ++x) { float p = count[x] / (float)(linebytes + 1); sum[type] += count[x] == 0 ? 0 : flog2(1 / p) * p; } /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ - if(type == 0 || sum[type] < smallest) - { + if(type == 0 || sum[type] < smallest) { bestType = type; smallest = sum[type]; } @@ -5873,11 +5124,8 @@ static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, } for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); - } - else if(strategy == LFS_PREDEFINED) - { - for(y = 0; y != h; ++y) - { + } else if(strategy == LFS_PREDEFINED) { + for(y = 0; y != h; ++y) { size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ size_t inindex = linebytes * y; unsigned char type = settings->predefined_filters[y]; @@ -5885,9 +5133,7 @@ static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); prevline = &in[inindex]; } - } - else if(strategy == LFS_BRUTE_FORCE) - { + } else if(strategy == LFS_BRUTE_FORCE) { /*brute force filter chooser. deflate the scanline after every filter attempt to see which one deflates best. This is very slow and gives only slightly smaller, sometimes even larger, result*/ @@ -5906,15 +5152,12 @@ static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, images only, so disable it*/ zlibsettings.custom_zlib = 0; zlibsettings.custom_deflate = 0; - for(type = 0; type != 5; ++type) - { + for(type = 0; type != 5; ++type) { attempt[type] = (unsigned char*)lodepng_malloc(linebytes); if(!attempt[type]) return 83; /*alloc fail*/ } - for(y = 0; y != h; ++y) /*try the 5 filter types*/ - { - for(type = 0; type != 5; ++type) - { + for(y = 0; y != h; ++y) /*try the 5 filter types*/ { + for(type = 0; type != 5; ++type) { unsigned testsize = (unsigned)linebytes; /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ @@ -5924,8 +5167,7 @@ static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); lodepng_free(dummy); /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ - if(type == 0 || size[type] < smallest) - { + if(type == 0 || size[type] < smallest) { bestType = type; smallest = size[type]; } @@ -5942,18 +5184,15 @@ static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, } static void addPaddingBits(unsigned char* out, const unsigned char* in, - size_t olinebits, size_t ilinebits, unsigned h) -{ + size_t olinebits, size_t ilinebits, unsigned h) { /*The opposite of the removePaddingBits function olinebits must be >= ilinebits*/ unsigned y; size_t diff = olinebits - ilinebits; size_t obp = 0, ibp = 0; /*bit pointers*/ - for(y = 0; y != h; ++y) - { + for(y = 0; y != h; ++y) { size_t x; - for(x = 0; x < ilinebits; ++x) - { + for(x = 0; x < ilinebits; ++x) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } @@ -5974,47 +5213,37 @@ in has the following size in bits: w * h * bpp. out is possibly bigger due to padding bits between reduced images NOTE: comments about padding bits are only relevant if bpp < 8 */ -static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) -{ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned i; Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); - if(bpp >= 8) - { - for(i = 0; i != 7; ++i) - { + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { unsigned x, y, b; size_t bytewidth = bpp / 8; for(y = 0; y < passh[i]; ++y) - for(x = 0; x < passw[i]; ++x) - { + for(x = 0; x < passw[i]; ++x) { size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; - for(b = 0; b < bytewidth; ++b) - { + for(b = 0; b < bytewidth; ++b) { out[pixeloutstart + b] = in[pixelinstart + b]; } } } - } - else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ - { - for(i = 0; i != 7; ++i) - { + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { unsigned x, y, b; unsigned ilinebits = bpp * passw[i]; unsigned olinebits = bpp * w; size_t obp, ibp; /*bit pointers (for out and in buffer)*/ for(y = 0; y < passh[i]; ++y) - for(x = 0; x < passw[i]; ++x) - { + for(x = 0; x < passw[i]; ++x) { ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); - for(b = 0; b < bpp; ++b) - { + for(b = 0; b < bpp; ++b) { unsigned char bit = readBitFromReversedStream(&ibp, in); setBitOfReversedStream(&obp, out, bit); } @@ -6027,8 +5256,7 @@ static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigne return value is error**/ static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, unsigned w, unsigned h, - const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) -{ + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { /* This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: *) if no Adam7: 1) add padding bits (= posible extra bits per scanline if bpp < 8) 2) filter @@ -6037,35 +5265,27 @@ static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned bpp = lodepng_get_bpp(&info_png->color); unsigned error = 0; - if(info_png->interlace_method == 0) - { + if(info_png->interlace_method == 0) { *outsize = h + (h * ((w * bpp + 7) / 8)); /*image size plus an extra byte per scanline + possible padding bits*/ *out = (unsigned char*)lodepng_malloc(*outsize); if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ - if(!error) - { + if(!error) { /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ - if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) - { + if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8) { unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7) / 8)); if(!padded) error = 83; /*alloc fail*/ - if(!error) - { + if(!error) { addPaddingBits(padded, in, ((w * bpp + 7) / 8) * 8, w * bpp, h); error = filter(*out, padded, w, h, &info_png->color, settings); } lodepng_free(padded); - } - else - { + } else { /*we can immediately filter into the out buffer, no other steps needed*/ error = filter(*out, in, w, h, &info_png->color, settings); } } - } - else /*interlace_method is 1 (Adam7)*/ - { + } else /*interlace_method is 1 (Adam7)*/ { unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; unsigned char* adam7; @@ -6079,15 +5299,12 @@ static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const adam7 = (unsigned char*)lodepng_malloc(passstart[7]); if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ - if(!error) - { + if(!error) { unsigned i; Adam7_interlace(adam7, in, w, h, bpp); - for(i = 0; i != 7; ++i) - { - if(bpp < 8) - { + for(i = 0; i != 7; ++i) { + if(bpp < 8) { unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); if(!padded) ERROR_BREAK(83); /*alloc fail*/ addPaddingBits(padded, &adam7[passstart[i]], @@ -6095,9 +5312,7 @@ static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const error = filter(&(*out)[filter_passstart[i]], padded, passw[i], passh[i], &info_png->color, settings); lodepng_free(padded); - } - else - { + } else { error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], passw[i], passh[i], &info_png->color, settings); } @@ -6118,15 +5333,12 @@ returns 0 if the palette is opaque, returns 1 if the palette has a single color with alpha 0 ==> color key returns 2 if the palette is semi-translucent. */ -static unsigned getPaletteTranslucency(const unsigned char* palette, size_t palettesize) -{ +static unsigned getPaletteTranslucency(const unsigned char* palette, size_t palettesize) { size_t i; unsigned key = 0; unsigned r = 0, g = 0, b = 0; /*the value of the color with alpha 0, so long as color keying is possible*/ - for(i = 0; i != palettesize; ++i) - { - if(!key && palette[4 * i + 3] == 0) - { + for(i = 0; i != palettesize; ++i) { + if(!key && palette[4 * i + 3] == 0) { r = palette[4 * i + 0]; g = palette[4 * i + 1]; b = palette[4 * i + 2]; key = 1; i = (size_t)(-1); /*restart from beginning, to detect earlier opaque colors with key's value*/ @@ -6139,11 +5351,9 @@ static unsigned getPaletteTranslucency(const unsigned char* palette, size_t pale } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS -static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) -{ +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { unsigned char* inchunk = data; - while((size_t)(inchunk - data) < datasize) - { + while((size_t)(inchunk - data) < datasize) { CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); out->allocsize = out->size; /*fix the allocsize again*/ inchunk = lodepng_chunk_next(inchunk); @@ -6151,8 +5361,7 @@ static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t data return 0; } -static unsigned isGreyICCProfile(const unsigned char* profile, unsigned size) -{ +static unsigned isGreyICCProfile(const unsigned char* profile, unsigned size) { /* It is a grey profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 are "RGB ". We do not perform any full parsing of the ICC profile here, other @@ -6166,8 +5375,7 @@ static unsigned isGreyICCProfile(const unsigned char* profile, unsigned size) return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; } -static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) -{ +static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { /* See comment in isGreyICCProfile*/ if(size < 20) return 0; return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; @@ -6176,8 +5384,7 @@ static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) unsigned lodepng_encode(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h, - LodePNGState* state) -{ + LodePNGState* state) { unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ size_t datasize = 0; ucvector outv; @@ -6193,18 +5400,15 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, /*check input values validity*/ if((state->info_png.color.colortype == LCT_PALETTE || state->encoder.force_palette) - && (state->info_png.color.palettesize == 0 || state->info_png.color.palettesize > 256)) - { + && (state->info_png.color.palettesize == 0 || state->info_png.color.palettesize > 256)) { state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ goto cleanup; } - if(state->encoder.zlibsettings.btype > 2) - { + if(state->encoder.zlibsettings.btype > 2) { state->error = 61; /*error: unexisting btype*/ goto cleanup; } - if(state->info_png.interlace_method > 1) - { + if(state->info_png.interlace_method > 1) { state->error = 71; /*error: unexisting interlace mode*/ goto cleanup; } @@ -6215,11 +5419,9 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, /* color convert and compute scanline filter types */ lodepng_info_copy(&info, &state->info_png); - if(state->encoder.auto_convert) - { + if(state->encoder.auto_convert) { #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS - if(state->info_png.background_defined) - { + if(state->info_png.background_defined) { unsigned bg_r = state->info_png.background_r; unsigned bg_g = state->info_png.background_g; unsigned bg_b = state->info_png.background_b; @@ -6234,8 +5436,7 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, state->error = auto_choose_color_from_profile(&info.color, &state->info_raw, &prof); if(state->error) goto cleanup; if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, - bg_r, bg_g, bg_b, &info.color, &state->info_png.color)) - { + bg_r, bg_g, bg_b, &info.color, &state->info_png.color)) { state->error = 104; goto cleanup; } @@ -6248,34 +5449,29 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, } } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS - if(state->info_png.iccp_defined) - { + if(state->info_png.iccp_defined) { unsigned grey_icc = isGreyICCProfile(state->info_png.iccp_profile, state->info_png.iccp_profile_size); unsigned grey_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; /* TODO: perhaps instead of giving errors or less optimal compression, we can automatically modify the ICC profile here to say "GRAY" or "RGB " to match the PNG color type, unless this will require non trivial changes to the rest of the ICC profile */ - if(!grey_icc && !isRGBICCProfile(state->info_png.iccp_profile, state->info_png.iccp_profile_size)) - { + if(!grey_icc && !isRGBICCProfile(state->info_png.iccp_profile, state->info_png.iccp_profile_size)) { state->error = 100; /* Disallowed profile color type for PNG */ goto cleanup; } - if(!state->encoder.auto_convert && grey_icc != grey_png) - { + if(!state->encoder.auto_convert && grey_icc != grey_png) { /* Non recoverable: encoder not allowed to convert color type, and requested color type not compatible with ICC color type */ state->error = 101; goto cleanup; } - if(grey_icc && !grey_png) - { + if(grey_icc && !grey_png) { /* Non recoverable: trying to set greyscale ICC profile while colored pixels were given */ state->error = 102; goto cleanup; /* NOTE: this relies on the fact that lodepng_auto_choose_color never returns palette for greyscale pixels */ } - if(!grey_icc && grey_png) - { + if(!grey_icc && grey_png) { /* Recoverable but an unfortunate loss in compression density: We have greyscale pixels but are forced to store them in more expensive RGB format that will repeat each value 3 times because the PNG spec does not allow an RGB ICC profile with internal greyscale color data */ @@ -6285,15 +5481,13 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, } } #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ - if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) - { + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { unsigned char* converted; size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7) / 8; converted = (unsigned char*)lodepng_malloc(size); if(!converted && size) state->error = 83; /*alloc fail*/ - if(!state->error) - { + if(!state->error) { state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); } if(!state->error) preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); @@ -6302,8 +5496,7 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, } else preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); - /* output all PNG chunks */ - { + /* output all PNG chunks */ { #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS size_t i; #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ @@ -6313,8 +5506,7 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*unknown chunks between IHDR and PLTE*/ - if(info.unknown_chunks_data[0]) - { + if(info.unknown_chunks_data[0]) { state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); if(state->error) goto cleanup; } @@ -6325,27 +5517,22 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, if(info.chrm_defined) addChunk_cHRM(&outv, &info); #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*PLTE*/ - if(info.color.colortype == LCT_PALETTE) - { + if(info.color.colortype == LCT_PALETTE) { addChunk_PLTE(&outv, &info.color); } - if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) - { + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { addChunk_PLTE(&outv, &info.color); } /*tRNS*/ - if(info.color.colortype == LCT_PALETTE && getPaletteTranslucency(info.color.palette, info.color.palettesize) != 0) - { + if(info.color.colortype == LCT_PALETTE && getPaletteTranslucency(info.color.palette, info.color.palettesize) != 0) { addChunk_tRNS(&outv, &info.color); } - if((info.color.colortype == LCT_GREY || info.color.colortype == LCT_RGB) && info.color.key_defined) - { + if((info.color.colortype == LCT_GREY || info.color.colortype == LCT_RGB) && info.color.key_defined) { addChunk_tRNS(&outv, &info.color); } #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*bKGD (must come between PLTE and the IDAt chunks*/ - if(info.background_defined) - { + if(info.background_defined) { state->error = addChunk_bKGD(&outv, &info); if(state->error) goto cleanup; } @@ -6353,8 +5540,7 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, if(info.phys_defined) addChunk_pHYs(&outv, &info); /*unknown chunks between PLTE and IDAT*/ - if(info.unknown_chunks_data[1]) - { + if(info.unknown_chunks_data[1]) { state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); if(state->error) goto cleanup; } @@ -6366,54 +5552,41 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, /*tIME*/ if(info.time_defined) addChunk_tIME(&outv, &info.time); /*tEXt and/or zTXt*/ - for(i = 0; i != info.text_num; ++i) - { - if(strlen(info.text_keys[i]) > 79) - { + for(i = 0; i != info.text_num; ++i) { + if(strlen(info.text_keys[i]) > 79) { state->error = 66; /*text chunk too large*/ goto cleanup; } - if(strlen(info.text_keys[i]) < 1) - { + if(strlen(info.text_keys[i]) < 1) { state->error = 67; /*text chunk too small*/ goto cleanup; } - if(state->encoder.text_compression) - { + if(state->encoder.text_compression) { addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); - } - else - { + } else { addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); } } /*LodePNG version id in text chunk*/ - if(state->encoder.add_id) - { + if(state->encoder.add_id) { unsigned already_added_id_text = 0; - for(i = 0; i != info.text_num; ++i) - { - if(!strcmp(info.text_keys[i], "LodePNG")) - { + for(i = 0; i != info.text_num; ++i) { + if(!strcmp(info.text_keys[i], "LodePNG")) { already_added_id_text = 1; break; } } - if(already_added_id_text == 0) - { + if(already_added_id_text == 0) { addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ } } /*iTXt*/ - for(i = 0; i != info.itext_num; ++i) - { - if(strlen(info.itext_keys[i]) > 79) - { + for(i = 0; i != info.itext_num; ++i) { + if(strlen(info.itext_keys[i]) > 79) { state->error = 66; /*text chunk too large*/ goto cleanup; } - if(strlen(info.itext_keys[i]) < 1) - { + if(strlen(info.itext_keys[i]) < 1) { state->error = 67; /*text chunk too small*/ goto cleanup; } @@ -6423,8 +5596,7 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, } /*unknown chunks between IDAT and IEND*/ - if(info.unknown_chunks_data[2]) - { + if(info.unknown_chunks_data[2]) { state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); if(state->error) goto cleanup; } @@ -6444,8 +5616,7 @@ unsigned lodepng_encode(unsigned char** out, size_t* outsize, } unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, - unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) -{ + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { unsigned error; LodePNGState state; lodepng_state_init(&state); @@ -6459,20 +5630,17 @@ unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsig return error; } -unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) -{ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); } -unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) -{ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); } #ifdef LODEPNG_COMPILE_DISK unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, - LodePNGColorType colortype, unsigned bitdepth) -{ + LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer; size_t buffersize; unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); @@ -6481,19 +5649,16 @@ unsigned lodepng_encode_file(const char* filename, const unsigned char* image, u return error; } -unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) -{ +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); } -unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) -{ +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); } #endif /*LODEPNG_COMPILE_DISK*/ -void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) -{ +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { lodepng_compress_settings_init(&settings->zlibsettings); settings->filter_palette_zero = 1; settings->filter_strategy = LFS_MINSUM; @@ -6514,10 +5679,8 @@ void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) This returns the description of a numerical error code in English. This is also the documentation of all the error codes. */ -const char* lodepng_error_text(unsigned code) -{ - switch(code) - { +const char* lodepng_error_text(unsigned code) { + switch(code) { case 0: return "no error, everything went ok"; case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ @@ -6531,10 +5694,11 @@ const char* lodepng_error_text(unsigned code) case 19: return "end of out buffer memory reached while inflating"; case 20: return "invalid deflate block BTYPE encountered while decoding"; case 21: return "NLEN is not ones complement of LEN in a deflate block"; - /*end of out buffer memory reached while inflating: - This can happen if the inflated deflate data is longer than the amount of bytes required to fill up - all the pixels of the image, given the color depth and image dimensions. Something that doesn't - happen in a normal, well encoded, PNG image.*/ + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ case 22: return "end of out buffer memory reached while inflating"; case 23: return "end of in buffer memory reached while inflating"; case 24: return "invalid FCHECK in zlib header"; @@ -6579,7 +5743,8 @@ const char* lodepng_error_text(unsigned code) case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; /*LodePNG leaves the choice of RGB to greyscale conversion formula to the user.*/ case 62: return "conversion from color to greyscale not supported"; - case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*(2^31-1)*/ + /*(2^31-1)*/ + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; @@ -6633,12 +5798,10 @@ const char* lodepng_error_text(unsigned code) /* ////////////////////////////////////////////////////////////////////////// */ #ifdef LODEPNG_COMPILE_CPP -namespace lodepng -{ +namespace lodepng { #ifdef LODEPNG_COMPILE_DISK -unsigned load_file(std::vector& buffer, const std::string& filename) -{ +unsigned load_file(std::vector& buffer, const std::string& filename) { long size = lodepng_filesize(filename.c_str()); if(size < 0) return 78; buffer.resize((size_t)size); @@ -6646,8 +5809,7 @@ unsigned load_file(std::vector& buffer, const std::string& filena } /*write given buffer to the file, overwriting the file, it doesn't append to it.*/ -unsigned save_file(const std::vector& buffer, const std::string& filename) -{ +unsigned save_file(const std::vector& buffer, const std::string& filename) { return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); } #endif /* LODEPNG_COMPILE_DISK */ @@ -6655,13 +5817,11 @@ unsigned save_file(const std::vector& buffer, const std::string& #ifdef LODEPNG_COMPILE_ZLIB #ifdef LODEPNG_COMPILE_DECODER unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, - const LodePNGDecompressSettings& settings) -{ + const LodePNGDecompressSettings& settings) { unsigned char* buffer = 0; size_t buffersize = 0; unsigned error = zlib_decompress(&buffer, &buffersize, in, insize, &settings); - if(buffer) - { + if(buffer) { out.insert(out.end(), &buffer[0], &buffer[buffersize]); lodepng_free(buffer); } @@ -6669,21 +5829,18 @@ unsigned decompress(std::vector& out, const unsigned char* in, si } unsigned decompress(std::vector& out, const std::vector& in, - const LodePNGDecompressSettings& settings) -{ + const LodePNGDecompressSettings& settings) { return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); } #endif /* LODEPNG_COMPILE_DECODER */ #ifdef LODEPNG_COMPILE_ENCODER unsigned compress(std::vector& out, const unsigned char* in, size_t insize, - const LodePNGCompressSettings& settings) -{ + const LodePNGCompressSettings& settings) { unsigned char* buffer = 0; size_t buffersize = 0; unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); - if(buffer) - { + if(buffer) { out.insert(out.end(), &buffer[0], &buffer[buffersize]); lodepng_free(buffer); } @@ -6691,8 +5848,7 @@ unsigned compress(std::vector& out, const unsigned char* in, size } unsigned compress(std::vector& out, const std::vector& in, - const LodePNGCompressSettings& settings) -{ + const LodePNGCompressSettings& settings) { return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); } #endif /* LODEPNG_COMPILE_ENCODER */ @@ -6701,24 +5857,20 @@ unsigned compress(std::vector& out, const std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, - size_t insize, LodePNGColorType colortype, unsigned bitdepth) -{ + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer; unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); - if(buffer && !error) - { + if(buffer && !error) { State state; state.info_raw.colortype = colortype; state.info_raw.bitdepth = bitdepth; @@ -6743,19 +5893,16 @@ unsigned decode(std::vector& out, unsigned& w, unsigned& h, const } unsigned decode(std::vector& out, unsigned& w, unsigned& h, - const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) -{ + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) { return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); } unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, - const unsigned char* in, size_t insize) -{ + const unsigned char* in, size_t insize) { unsigned char* buffer = NULL; unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); - if(buffer && !error) - { + if(buffer && !error) { size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); out.insert(out.end(), &buffer[0], &buffer[buffersize]); } @@ -6765,16 +5912,16 @@ unsigned decode(std::vector& out, unsigned& w, unsigned& h, unsigned decode(std::vector& out, unsigned& w, unsigned& h, State& state, - const std::vector& in) -{ + const std::vector& in) { return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); } #ifdef LODEPNG_COMPILE_DISK unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, - LodePNGColorType colortype, unsigned bitdepth) -{ + LodePNGColorType colortype, unsigned bitdepth) { std::vector buffer; + /* safe output values in case error happens */ + w = h = 0; unsigned error = load_file(buffer, filename); if(error) return error; return decode(out, w, h, buffer, colortype, bitdepth); @@ -6784,13 +5931,11 @@ unsigned decode(std::vector& out, unsigned& w, unsigned& h, const #ifdef LODEPNG_COMPILE_ENCODER unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, - LodePNGColorType colortype, unsigned bitdepth) -{ + LodePNGColorType colortype, unsigned bitdepth) { unsigned char* buffer; size_t buffersize; unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); - if(buffer) - { + if(buffer) { out.insert(out.end(), &buffer[0], &buffer[buffersize]); lodepng_free(buffer); } @@ -6799,21 +5944,18 @@ unsigned encode(std::vector& out, const unsigned char* in, unsign unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, - LodePNGColorType colortype, unsigned bitdepth) -{ + LodePNGColorType colortype, unsigned bitdepth) { if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); } unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, - State& state) -{ + State& state) { unsigned char* buffer; size_t buffersize; unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); - if(buffer) - { + if(buffer) { out.insert(out.end(), &buffer[0], &buffer[buffersize]); lodepng_free(buffer); } @@ -6822,8 +5964,7 @@ unsigned encode(std::vector& out, unsigned encode(std::vector& out, const std::vector& in, unsigned w, unsigned h, - State& state) -{ + State& state) { if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; return encode(out, in.empty() ? 0 : &in[0], w, h, state); } @@ -6831,8 +5972,7 @@ unsigned encode(std::vector& out, #ifdef LODEPNG_COMPILE_DISK unsigned encode(const std::string& filename, const unsigned char* in, unsigned w, unsigned h, - LodePNGColorType colortype, unsigned bitdepth) -{ + LodePNGColorType colortype, unsigned bitdepth) { std::vector buffer; unsigned error = encode(buffer, in, w, h, colortype, bitdepth); if(!error) error = save_file(buffer, filename); @@ -6841,8 +5981,7 @@ unsigned encode(const std::string& filename, unsigned encode(const std::string& filename, const std::vector& in, unsigned w, unsigned h, - LodePNGColorType colortype, unsigned bitdepth) -{ + LodePNGColorType colortype, unsigned bitdepth) { if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); } diff --git a/src/lib/lodepng.h b/src/lib/lodepng.h index 2780e095..88866191 100644 --- a/src/lib/lodepng.h +++ b/src/lib/lodepng.h @@ -1,7 +1,7 @@ /* -LodePNG version 20180910 +LodePNG version 20190210 -Copyright (c) 2005-2018 Lode Vandevenne +Copyright (c) 2005-2019 Lode Vandevenne This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -88,8 +88,7 @@ source files with custom allocators.*/ #ifdef LODEPNG_COMPILE_PNG /*The PNG color types (also used for raw).*/ -typedef enum LodePNGColorType -{ +typedef enum LodePNGColorType { LCT_GREY = 0, /*greyscale: 1,2,4,8,16 bit*/ LCT_RGB = 2, /*RGB: 8,16 bit*/ LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ @@ -196,8 +195,7 @@ unsigned lodepng_encode24_file(const char* filename, #ifdef LODEPNG_COMPILE_CPP -namespace lodepng -{ +namespace lodepng { #ifdef LODEPNG_COMPILE_DECODER /*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ @@ -253,8 +251,7 @@ const char* lodepng_error_text(unsigned code); #ifdef LODEPNG_COMPILE_DECODER /*Settings for zlib decompression*/ typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; -struct LodePNGDecompressSettings -{ +struct LodePNGDecompressSettings { /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ @@ -282,8 +279,7 @@ Settings for zlib compression. Tweaking these settings tweaks the balance between speed and compression ratio. */ typedef struct LodePNGCompressSettings LodePNGCompressSettings; -struct LodePNGCompressSettings /*deflate = compress*/ -{ +struct LodePNGCompressSettings /*deflate = compress*/ { /*LZ77 related settings*/ unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ @@ -316,8 +312,7 @@ Color mode of an image. Contains all information required to decode the pixel bits to RGBA colors. This information is the same as used in the PNG file format, and is used both for PNG and raw image data in LodePNG. */ -typedef struct LodePNGColorMode -{ +typedef struct LodePNGColorMode { /*header (IHDR)*/ LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ @@ -395,8 +390,7 @@ size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* colo #ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS /*The information of a Time chunk in PNG.*/ -typedef struct LodePNGTime -{ +typedef struct LodePNGTime { unsigned year; /*2 bytes used (0-65535)*/ unsigned month; /*1-12*/ unsigned day; /*1-31*/ @@ -407,8 +401,7 @@ typedef struct LodePNGTime #endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ /*Information about the PNG image, except pixels, width and height.*/ -typedef struct LodePNGInfo -{ +typedef struct LodePNGInfo { /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ unsigned compression_method;/*compression method of the original file. Always 0.*/ unsigned filter_method; /*filter method of the original file*/ @@ -614,8 +607,7 @@ unsigned lodepng_convert(unsigned char* out, const unsigned char* in, Settings for the decoder. This contains settings for the PNG and the Zlib decoder, but not the Info settings from the Info structs. */ -typedef struct LodePNGDecoderSettings -{ +typedef struct LodePNGDecoderSettings { LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ @@ -641,8 +633,7 @@ void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); #ifdef LODEPNG_COMPILE_ENCODER /*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ -typedef enum LodePNGFilterStrategy -{ +typedef enum LodePNGFilterStrategy { /*every filter at zero*/ LFS_ZERO, /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ @@ -664,8 +655,7 @@ which helps decide which color model to use for encoding. Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms. NOTE: This is not related to the ICC color profile, search "iccp_profile" instead to find the ICC/chromacity/... fields in this header file.*/ -typedef struct LodePNGColorProfile -{ +typedef struct LodePNGColorProfile { unsigned colored; /*not greyscale*/ unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ @@ -693,8 +683,7 @@ unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in); /*Settings for the encoder.*/ -typedef struct LodePNGEncoderSettings -{ +typedef struct LodePNGEncoderSettings { LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ @@ -730,8 +719,7 @@ void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); #if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) /*The settings, state and information for extended encoding and decoding.*/ -typedef struct LodePNGState -{ +typedef struct LodePNGState { #ifdef LODEPNG_COMPILE_DECODER LodePNGDecoderSettings decoder; /*the decoding settings*/ #endif /*LODEPNG_COMPILE_DECODER*/ @@ -965,11 +953,9 @@ unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const #ifdef LODEPNG_COMPILE_CPP /* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ -namespace lodepng -{ +namespace lodepng { #ifdef LODEPNG_COMPILE_PNG -class State : public LodePNGState -{ +class State : public LodePNGState { public: State(); State(const State& other); @@ -1042,17 +1028,21 @@ unsigned compress(std::vector& out, const std::vector (2^31)-1 [ ] partial decoding (stream processing) [X] let the "isFullyOpaque" function check color keys and transparent palettes too [X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" -[ ] don't stop decoding on errors like 69, 57, 58 (make warnings) +[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) [ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... -[ ] errors with line numbers (and version) +[ ] error messages with line numbers (and version) +[ ] errors in state instead of as return code? +[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk [ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes [ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... [ ] allow user to give data (void*) to custom allocator +[ ] provide alternatives for C library functions not present on some platforms (memcpy, ...) +[ ] rename "grey" to "gray" everywhere since "color" also uses US spelling (keep "grey" copies for backwards compatibility) */ #endif /*LODEPNG_H inclusion guard*/ @@ -1147,8 +1137,10 @@ The following features are supported by the decoder: *) zlib decompression (inflate) *) zlib compression (deflate) *) CRC32 and ADLER32 checksums +*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, + plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. *) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. -*) the following chunks are supported (generated/interpreted) by both encoder and decoder: +*) the following chunks are supported by both encoder and decoder: IHDR: header information PLTE: color palette IDAT: pixel data @@ -1160,6 +1152,10 @@ The following features are supported by the decoder: bKGD: suggested background color pHYs: physical dimensions tIME: modification time + cHRM: RGB chromaticities + gAMA: RGB gamma correction + iCCP: ICC color profile + sRGB: rendering intent 1.2. features not supported --------------------------- @@ -1168,10 +1164,10 @@ The following features are _not_ supported: *) some features needed to make a conformant PNG-Editor might be still missing. *) partial loading/stream processing. All data must be available and is processed in one call. -*) The following public chunks are not supported but treated as unknown chunks by LodePNG - cHRM, gAMA, iCCP, sRGB, sBIT, hIST, sPLT - Some of these are not supported on purpose: LodePNG wants to provide the RGB values - stored in the pixels, not values modified by system dependent gamma or color models. +*) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG: + sBIT + hIST + sPLT 2. C and C++ version @@ -1673,8 +1669,7 @@ examples can be found on the LodePNG website. #include "lodepng.h" #include -int main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { const char* filename = argc > 1 ? argv[1] : "test.png"; //load and decode @@ -1693,8 +1688,7 @@ int main(int argc, char *argv[]) #include "lodepng.h" -int main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { unsigned error; unsigned char* image; size_t width, height; @@ -1763,6 +1757,7 @@ yyyymmdd. Some changes aren't backwards compatible. Those are indicated with a (!) symbol. +*) 30 dec 2018: code style changes only: removed newlines before opening braces. *) 10 sep 2018: added way to inspect metadata chunks without full decoding. *) 19 aug 2018 (!): fixed color mode bKGD is encoded with and made it use palette index in case of palette. @@ -1920,5 +1915,5 @@ Domain: gmail dot com. Account: lode dot vandevenne. -Copyright (c) 2005-2018 Lode Vandevenne +Copyright (c) 2005-2019 Lode Vandevenne */ diff --git a/src/lib/stb_textedit.h b/src/lib/stb_textedit.h index 5c8ca118..7306edf7 100644 --- a/src/lib/stb_textedit.h +++ b/src/lib/stb_textedit.h @@ -1,4 +1,4 @@ -// stb_textedit.h - v1.11 - public domain - Sean Barrett +// stb_textedit.h - v1.13 - public domain - Sean Barrett // Development of this library was sponsored by RAD Game Tools // // This C header file implements the guts of a multi-line text-editing @@ -13,7 +13,7 @@ // texts, as its performance does not scale and it has limited undo). // // Non-trivial behaviors are modelled after Windows text controls. -// +// // // LICENSE // @@ -29,6 +29,8 @@ // // VERSION HISTORY // +// 1.13 (2019-02-07) fix bug in undo size management +// 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash // 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield // 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual // 1.9 (2016-08-27) customizable move-by-word @@ -85,8 +87,8 @@ // moderate sizes. The undo system does no memory allocations, so // it grows STB_TexteditState by the worst-case storage which is (in bytes): // -// [4 + sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT -// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT +// [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT // // // Implementation mode: @@ -109,7 +111,7 @@ // Symbols that must be the same in header-file and implementation mode: // // STB_TEXTEDIT_CHARTYPE the character type -// STB_TEXTEDIT_POSITIONTYPE small type that a valid cursor position +// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer // @@ -198,7 +200,7 @@ // void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, float x, float y) // int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) // int stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len) -// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +// void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key) // // Each of these functions potentially updates the string and updates the // state. @@ -211,20 +213,20 @@ // call this with the mouse x,y on a mouse down; it will update the cursor // and reset the selection start/end to the cursor point. the x,y must // be relative to the text widget, with (0,0) being the top left. -// +// // drag: // call this with the mouse x,y on a mouse drag/up; it will update the // cursor and the selection end point -// +// // cut: // call this to delete the current selection; returns true if there was // one. you should FIRST copy the current selection to the system paste buffer. // (To copy, just copy the current selection out of the string yourself.) -// +// // paste: // call this to paste text at the current cursor point or over the current // selection if there is one. -// +// // key: // call this for keyboard inputs sent to the textfield. you can use it // for "key down" events or for "translated" key events. if you need to @@ -232,8 +234,10 @@ // inputs, set a high bit to distinguish the two; then you can define the // various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit // set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is -// clear. +// clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to +// anything other type you wante before including. // +// // When rendering, you can read the cursor position and selection state from // the STB_TexteditState. // @@ -292,9 +296,9 @@ typedef struct { // private data STB_TEXTEDIT_POSITIONTYPE where; - short insert_length; - short delete_length; - short char_storage; + STB_TEXTEDIT_POSITIONTYPE insert_length; + STB_TEXTEDIT_POSITIONTYPE delete_length; + int char_storage; } StbUndoRecord; typedef struct @@ -303,7 +307,7 @@ typedef struct StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; short undo_point, redo_point; - short undo_char_point, redo_char_point; + int undo_char_point, redo_char_point; } StbUndoState; typedef struct @@ -555,7 +559,6 @@ static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *s // now scan to find xpos find->x = r.x0; - i = 0; for (i=0; first+i < n; ++i) find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); } @@ -634,11 +637,9 @@ static void stb_textedit_move_to_last(STB_TEXTEDIT_STRING *str, STB_TexteditStat } #ifdef STB_TEXTEDIT_IS_SPACE -static int is_word_boundary( STB_TEXTEDIT_STRING *_str, int _idx ) +static int is_word_boundary( STB_TEXTEDIT_STRING *str, int idx ) { - return _idx > 0 ? ((STB_TEXTEDIT_IS_SPACE(STB_TEXTEDIT_GETCHAR(_str,_idx-1)) || - STB_TEXTEDIT_IS_PUNCT(STB_TEXTEDIT_GETCHAR(_str,_idx-1))) && - !STB_TEXTEDIT_IS_SPACE(STB_TEXTEDIT_GETCHAR(_str, _idx))) : 1; + return idx > 0 ? (STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str,idx-1) ) && !STB_TEXTEDIT_IS_SPACE( STB_TEXTEDIT_GETCHAR(str, idx) ) ) : 1; } #ifndef STB_TEXTEDIT_MOVEWORDLEFT @@ -687,7 +688,7 @@ static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) { if (STB_TEXT_HAS_SELECTION(state)) { - stb_textedit_delete_selection(str,state); // implicity clamps + stb_textedit_delete_selection(str,state); // implicitly clamps state->has_preferred_x = 0; return 1; } @@ -713,8 +714,12 @@ static int stb_textedit_paste_internal(STB_TEXTEDIT_STRING *str, STB_TexteditSta return 0; } +#ifndef STB_TEXTEDIT_KEYTYPE +#define STB_TEXTEDIT_KEYTYPE int +#endif + // API key: process a keyboard input -static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, int key) +static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) { retry: switch (key) { @@ -735,7 +740,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, state->has_preferred_x = 0; } } else { - stb_textedit_delete_selection(str,state); // implicity clamps + stb_textedit_delete_selection(str,state); // implicitly clamps if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { stb_text_makeundo_insert(state, state->cursor, 1); ++state->cursor; @@ -751,7 +756,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, state->insert_mode = !state->insert_mode; break; #endif - + case STB_TEXTEDIT_K_UNDO: stb_text_undo(str, state); state->has_preferred_x = 0; @@ -766,7 +771,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, // if currently there's a selection, move cursor to start of selection if (STB_TEXT_HAS_SELECTION(state)) stb_textedit_move_to_first(state); - else + else if (state->cursor > 0) --state->cursor; state->has_preferred_x = 0; @@ -815,7 +820,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, #ifdef STB_TEXTEDIT_MOVEWORDRIGHT case STB_TEXTEDIT_K_WORDRIGHT: - if (STB_TEXT_HAS_SELECTION(state)) + if (STB_TEXT_HAS_SELECTION(state)) stb_textedit_move_to_last(str, state); else { state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); @@ -893,7 +898,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, } break; } - + case STB_TEXTEDIT_K_UP: case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { StbFindState find; @@ -970,7 +975,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, } state->has_preferred_x = 0; break; - + #ifdef STB_TEXTEDIT_K_TEXTSTART2 case STB_TEXTEDIT_K_TEXTSTART2: #endif @@ -987,7 +992,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, state->select_start = state->select_end = 0; state->has_preferred_x = 0; break; - + #ifdef STB_TEXTEDIT_K_TEXTSTART2 case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: #endif @@ -1091,11 +1096,11 @@ static void stb_textedit_discard_undo(StbUndoState *state) if (state->undo_rec[0].char_storage >= 0) { int n = state->undo_rec[0].insert_length, i; // delete n characters from all other records - state->undo_char_point = state->undo_char_point - (short) n; // vsnet05 + state->undo_char_point -= n; STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); for (i=0; i < state->undo_point; ++i) if (state->undo_rec[i].char_storage >= 0) - state->undo_rec[i].char_storage = state->undo_rec[i].char_storage - (short) n; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it + state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it } --state->undo_point; STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); @@ -1109,18 +1114,22 @@ static void stb_textedit_discard_undo(StbUndoState *state) static void stb_textedit_discard_redo(StbUndoState *state) { int k = STB_TEXTEDIT_UNDOSTATECOUNT-1; + if (state->redo_point <= k) { // if the k'th undo state has characters, clean those up if (state->undo_rec[k].char_storage >= 0) { int n = state->undo_rec[k].insert_length, i; - // delete n characters from all other records - state->redo_char_point = state->redo_char_point + (short) n; // vsnet05 + // move the remaining redo character data to the end of the buffer + state->redo_char_point += n; STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); + // adjust the position of all the other records to account for above memmove for (i=state->redo_point; i < k; ++i) if (state->undo_rec[i].char_storage >= 0) - state->undo_rec[i].char_storage = state->undo_rec[i].char_storage + (short) n; // vsnet05 + state->undo_rec[i].char_storage += n; } - STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point, state->undo_rec + state->redo_point-1, (size_t) ((size_t)(STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); + // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' + STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, (size_t) ((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); + // now move redo_point to point to the new one ++state->redo_point; } } @@ -1156,15 +1165,15 @@ static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, return NULL; r->where = pos; - r->insert_length = (short) insert_len; - r->delete_length = (short) delete_len; + r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len; + r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len; if (insert_len == 0) { r->char_storage = -1; return NULL; } else { r->char_storage = state->undo_char_point; - state->undo_char_point = state->undo_char_point + (short) insert_len; + state->undo_char_point += insert_len; return &state->undo_char[r->char_storage]; } } @@ -1204,16 +1213,16 @@ static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) // there's definitely room to store the characters eventually while (s->undo_char_point + u.delete_length > s->redo_char_point) { - // there's currently not enough room, so discard a redo record - stb_textedit_discard_redo(s); // should never happen: if (s->redo_point == STB_TEXTEDIT_UNDOSTATECOUNT) return; + // there's currently not enough room, so discard a redo record + stb_textedit_discard_redo(s); } r = &s->undo_rec[s->redo_point-1]; r->char_storage = s->redo_char_point - u.delete_length; - s->redo_char_point = s->redo_char_point - (short) u.delete_length; + s->redo_char_point = s->redo_char_point - u.delete_length; // now save the characters for (i=0; i < u.delete_length; ++i) @@ -1358,38 +1367,38 @@ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ */ diff --git a/src/platform/android/app/build.gradle b/src/platform/android/app/build.gradle index f8e48a08..03a01dcc 100644 --- a/src/platform/android/app/build.gradle +++ b/src/platform/android/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId 'net.sourceforge.smallbasic' minSdkVersion 16 targetSdkVersion 27 - versionCode 32 - versionName "0.12.15" + versionCode 36 + versionName "0.12.15.3" resConfigs "en" } diff --git a/src/platform/android/app/src/main/assets/main.bas b/src/platform/android/app/src/main/assets/main.bas index 262a0ef2..8cf064bd 100644 --- a/src/platform/android/app/src/main/assets/main.bas +++ b/src/platform/android/app/src/main/assets/main.bas @@ -119,7 +119,7 @@ sub do_about() color 7 print "Version "; sbver print - print "Copyright (c) 2002-2018 Chris Warren-Smith" + print "Copyright (c) 2002-2019 Chris Warren-Smith" print "Copyright (c) 1999-2006 Nicholas Christopoulos" + chr(10) local bn_home @@ -127,7 +127,11 @@ sub do_about() bn_home.y = ypos * char_h bn_home.type = "link" bn_home.isExternal = true - bn_home.label = "https://smallbasic.github.io" + if (is_sdl) then + bn_home.label = "https://smallbasic.github.io" + else + bn_home.label = "https://smallbasic.github.io/pages/android.html" + endif bn_home.color = colLink print:print @@ -147,7 +151,7 @@ end sub do_setup() local frm - color 3 + color 3, colBkGnd cls print boldOn + "Setup web service port number." print boldOff @@ -169,7 +173,7 @@ sub do_setup() env("serverToken=" + token) endif - color 3 + color 3, colBkGnd cls print "Web service port number: " + env("serverSocket") print diff --git a/src/platform/android/app/src/main/java/net/sourceforge/smallbasic/MainActivity.java b/src/platform/android/app/src/main/java/net/sourceforge/smallbasic/MainActivity.java index a60c6412..f72990ec 100644 --- a/src/platform/android/app/src/main/java/net/sourceforge/smallbasic/MainActivity.java +++ b/src/platform/android/app/src/main/java/net/sourceforge/smallbasic/MainActivity.java @@ -417,17 +417,21 @@ public void run() { }).start(); } - public void playTone(int frq, int dur, int vol) { + public void playTone(int frq, int dur, int vol, boolean bgplay) { float volume = (vol / 100f); final Sound sound = new Sound(frq, dur, volume); - _sounds.add(sound); - _audioExecutor.execute(new Runnable() { - @Override - public void run() { - sound.play(); - _sounds.remove(sound); - } - }); + if (bgplay) { + _sounds.add(sound); + _audioExecutor.execute(new Runnable() { + @Override + public void run() { + sound.play(); + _sounds.remove(sound); + } + }); + } else { + sound.play(); + } } public boolean removeLocationUpdates() { diff --git a/src/platform/android/app/src/main/java/net/sourceforge/smallbasic/Sound.java b/src/platform/android/app/src/main/java/net/sourceforge/smallbasic/Sound.java index d8861296..c60dbe8e 100644 --- a/src/platform/android/app/src/main/java/net/sourceforge/smallbasic/Sound.java +++ b/src/platform/android/app/src/main/java/net/sourceforge/smallbasic/Sound.java @@ -19,18 +19,14 @@ class Sound { private final int _dur; private boolean _silent; - public Sound(int frq, int dur, float vol) { + Sound(int frq, int dur, float vol) { this._sound = generateTone(frq, dur); this._volume = vol; this._dur = dur; this._silent = false; } - public boolean isSilent() { - return _silent; - } - - public void play() { + void play() { if (!_silent) { try { AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, @@ -48,7 +44,7 @@ public void play() { } } - public void setSilent(boolean silent) { + void setSilent(boolean silent) { this._silent = silent; } @@ -56,7 +52,7 @@ public void setSilent(boolean silent) { * http://stackoverflow.com/questions/2413426/playing-an-arbitrary-tone-with-android */ private byte[] generateTone(int freqOfTone, int durationMillis) { - int numSamples = durationMillis * AUDIO_SAMPLE_RATE / 1000; + int numSamples = Math.max(1, durationMillis * AUDIO_SAMPLE_RATE / 1000); double sample[] = new double[numSamples]; byte result[] = new byte[2 * numSamples]; diff --git a/src/platform/android/app/src/main/res/drawable-hdpi/ic_launcher.png b/src/platform/android/app/src/main/res/drawable-hdpi/ic_launcher.png index c2694d38..00e4f41b 100644 Binary files a/src/platform/android/app/src/main/res/drawable-hdpi/ic_launcher.png and b/src/platform/android/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/src/platform/android/app/src/main/res/drawable-xhdpi/ic_launcher.png b/src/platform/android/app/src/main/res/drawable-xhdpi/ic_launcher.png index c9189d3d..ba6f8e24 100644 Binary files a/src/platform/android/app/src/main/res/drawable-xhdpi/ic_launcher.png and b/src/platform/android/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/src/platform/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/src/platform/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png index 7c9f5db8..62f9c7a6 100644 Binary files a/src/platform/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png and b/src/platform/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/src/platform/android/build.gradle b/src/platform/android/build.gradle index ef83891a..49991e4f 100644 --- a/src/platform/android/build.gradle +++ b/src/platform/android/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.3.2' classpath "com.github.ben-manes:gradle-versions-plugin:0.20.0" } } @@ -23,3 +23,13 @@ apply plugin: "com.github.ben-manes.versions" task clean(type: Delete) { delete rootProject.buildDir } + +task test { + doLast { + exec { + commandLine 'sh', '-c', 'adb -a logcat -c && \ + adb -a shell am start net.sourceforge.smallbasic/net.sourceforge.smallbasic.MainActivity && \ + adb -a logcat DEBUG:I smallbasic:I AndroidRuntime:E *:S' + } + } +} diff --git a/src/platform/android/jni/Android.mk b/src/platform/android/jni/Android.mk index b3cf1d77..31cbe948 100644 --- a/src/platform/android/jni/Android.mk +++ b/src/platform/android/jni/Android.mk @@ -15,7 +15,7 @@ LOCAL_PATH := $(JNI_PATH) include $(CLEAR_VARS) LOCAL_MODULE := smallbasic LOCAL_CFLAGS := -DHAVE_CONFIG_H=1 -DLODEPNG_NO_COMPILE_CPP \ - -DPIXELFORMAT_RGBA8888 + -DPIXELFORMAT_RGBA8888 -Wno-unknown-pragmas LOCAL_C_INCLUDES := $(SB_HOME) $(SB_HOME)/src \ $(FREETYPE_HOME)/freetype/include/freetype2 \ $(FREETYPE_HOME)/freetype/include/freetype2/freetype diff --git a/src/platform/android/jni/runtime.cpp b/src/platform/android/jni/runtime.cpp index b12b080e..f36489ed 100644 --- a/src/platform/android/jni/runtime.cpp +++ b/src/platform/android/jni/runtime.cpp @@ -738,8 +738,8 @@ void Runtime::playTone(int frq, int dur, int vol, bool bgplay) { JNIEnv *env; _app->activity->vm->AttachCurrentThread(&env, NULL); jclass clazz = env->GetObjectClass(_app->activity->clazz); - jmethodID methodId = env->GetMethodID(clazz, "playTone", "(III)V"); - env->CallVoidMethod(_app->activity->clazz, methodId, frq, dur, vol); + jmethodID methodId = env->GetMethodID(clazz, "playTone", "(IIIZ)V"); + env->CallVoidMethod(_app->activity->clazz, methodId, frq, dur, vol, bgplay); env->DeleteLocalRef(clazz); _app->activity->vm->DetachCurrentThread(); } @@ -934,7 +934,7 @@ void System::completeKeyword(int index) { } } -void System::editSource(strlib::String loadPath) { +void System::editSource(strlib::String loadPath, bool restoreOnExit) { logEntered(); strlib::String fileName; @@ -985,8 +985,10 @@ void System::editSource(strlib::String loadPath) { editWidget->setCursorRow(gsb_last_line + editWidget->getSelectionRow() - 1); runtime->alert(gsb_last_errmsg); } + + bool showStatus = !editWidget->getScroll(); _srcRendered = false; - _output->setStatus(cleanFile); + _output->setStatus(showStatus ? cleanFile : ""); _output->redraw(); _state = kEditState; runtime->showKeypad(true); @@ -994,8 +996,22 @@ void System::editSource(strlib::String loadPath) { while (_state == kEditState) { MAEvent event = getNextEvent(); switch (event.type) { + case EVENT_TYPE_POINTER_PRESSED: + if (!showStatus && event.point.x < editWidget->getMarginWidth()) { + _output->setStatus(editWidget->isDirty() ? dirtyFile : cleanFile); + _output->redraw(); + showStatus = true; + } + break; + case EVENT_TYPE_POINTER_RELEASED: + if (showStatus && event.point.x < editWidget->getMarginWidth() && editWidget->getScroll()) { + _output->setStatus(""); + _output->redraw(); + showStatus = false; + } + break; case EVENT_TYPE_OPTIONS_BOX_BUTTON_CLICKED: - if (editWidget->isDirty()) { + if (editWidget->isDirty() && !editWidget->getScroll()) { _output->setStatus(dirtyFile); _output->redraw(); } @@ -1068,10 +1084,8 @@ void System::editSource(strlib::String loadPath) { redraw = widget->edit(event.key, sw, charWidth); break; } - if (widget->isDirty() && !dirty) { - _output->setStatus(dirtyFile); - } else if (!widget->isDirty() && dirty) { - _output->setStatus(cleanFile); + if (editWidget->isDirty() != dirty && !editWidget->getScroll()) { + _output->setStatus(editWidget->isDirty() ? dirtyFile : cleanFile); } if (redraw) { _output->redraw(); @@ -1119,7 +1133,7 @@ void System::editSource(strlib::String loadPath) { // deletes editWidget unless it has been removed _output->removeInputs(); - if (!isClosing()) { + if (!isClosing() && restoreOnExit) { _output->selectScreen(prevScreenId); } logLeaving(); @@ -1169,7 +1183,9 @@ void osd_audio(const char *path) { } void osd_sound(int frq, int dur, int vol, int bgplay) { - runtime->playTone(frq, dur, vol, bgplay); + if (dur > 0 && frq > 0) { + runtime->playTone(frq, dur, vol, bgplay); + } } void osd_clear_sound_queue() { diff --git a/src/platform/android/jni/runtime.h b/src/platform/android/jni/runtime.h index 7536eab6..e9bb23ac 100644 --- a/src/platform/android/jni/runtime.h +++ b/src/platform/android/jni/runtime.h @@ -74,7 +74,7 @@ struct Runtime : public System { bool loadSettings(Properties &settings); void saveConfig(); void runPath(const char *path); - void setClipboardText(const char *s) { setString("setClipboardText", s); } + void setClipboardText(const char *s) { if (s) setString("setClipboardText", s); } char *getClipboardText(); void setFocus(bool focus) { _hasFocus = focus; } int getFontId(); diff --git a/src/platform/console/Makefile.am b/src/platform/console/Makefile.am index f4a191e6..acb83ff3 100644 --- a/src/platform/console/Makefile.am +++ b/src/platform/console/Makefile.am @@ -26,7 +26,7 @@ sbasic_DEPENDENCIES = $(top_srcdir)/src/common/libsb_common.a TEST_DIR=../../../samples/distro-examples/tests UNIT_TESTS=array break byref eval-test iifs matrices metaa ongoto \ uds hash pass1 call_tau short-circuit strings stack-test \ - replace-test read-data proc optchk letbug ptr \ + replace-test read-data proc optchk letbug ptr ref \ trycatch chain stream-files split-join sprint all scope goto test: ${bin_PROGRAMS} @@ -44,3 +44,9 @@ leak-test: ${bin_PROGRAMS} @for utest in $(UNIT_TESTS); do \ valgrind --leak-check=full ./${bin_PROGRAMS} ${TEST_DIR}/$${utest}.bas 1>/dev/null; \ done; + +fuzz-test: ${bin_PROGRAMS} + @for utest in $(UNIT_TESTS); do \ + zzuf valgrind --leak-check=full ./${bin_PROGRAMS} ${TEST_DIR}/$${utest}.bas 1>/dev/null; \ + done; + diff --git a/src/platform/console/image.cpp b/src/platform/console/image.cpp index 22a4eb21..a328b8ea 100644 --- a/src/platform/console/image.cpp +++ b/src/platform/console/image.cpp @@ -70,12 +70,6 @@ ImageBuffer::~ImageBuffer() { _image = NULL; } -void create_function(var_p_t map, const char *name, method cb) { - var_p_t v_func = map_add_var(map, name, 0); - v_func->type = V_FUNC; - v_func->v.fn.cb = cb; -} - dev_file_t *get_file() { dev_file_t *result = NULL; code_skipnext(); @@ -434,10 +428,10 @@ void create_image(var_p_t var, ImageBuffer *image) { var_p_t value = map_add_var(var, IMG_NAME, 0); v_setstr(value, image->_filename); } - create_function(var, "clip", cmd_image_clip); - create_function(var, "filter", cmd_image_filter); - create_function(var, "paste", cmd_image_paste); - create_function(var, "save", cmd_image_save); + v_create_func(var, "clip", cmd_image_clip); + v_create_func(var, "filter", cmd_image_filter); + v_create_func(var, "paste", cmd_image_paste); + v_create_func(var, "save", cmd_image_save); } // diff --git a/src/platform/console/main.cpp b/src/platform/console/main.cpp index 558c6e2d..b58b567b 100644 --- a/src/platform/console/main.cpp +++ b/src/platform/console/main.cpp @@ -50,7 +50,7 @@ void show_help() { OPTIONS[i].val, OPTIONS[i].name); i++; } - fprintf(stdout, "\nhttps://smallbasic.sourceforge.io\n\n"); + fprintf(stdout, "\nhttps://smallbasic.github.io\n\n"); } void show_brief_help() { diff --git a/src/platform/fltk/BasicEditor.cxx b/src/platform/fltk/BasicEditor.cxx new file mode 100644 index 00000000..7d6e775f --- /dev/null +++ b/src/platform/fltk/BasicEditor.cxx @@ -0,0 +1,782 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include +#include +#include "platform/fltk/BasicEditor.h" +#include "platform/fltk/kwp.h" +#include "platform/fltk/Profile.h" + +using namespace strlib; + +Fl_Color defaultColor[] = { + FL_BLACK, // A - Plain + fl_rgb_color(0, 128, 0), // B - Comments + fl_rgb_color(0, 0, 192), // C - Strings + fl_rgb_color(192, 0, 0), // D - code_keywords + fl_rgb_color(128, 128, 0), // E - code_functions + fl_rgb_color(0, 128, 128), // F - code_procedures + fl_rgb_color(128, 0, 128), // G - Find matches + fl_rgb_color(0, 128, 0), // H - Italic Comments ' + fl_rgb_color(0, 128, 128), // I - Numbers + fl_rgb_color(128, 128, 64), // J - Operators +}; + +Fl_Text_Display::Style_Table_Entry styletable[] = { + { defaultColor[0], FL_COURIER, 12}, // A - Plain + { defaultColor[1], FL_COURIER, 12}, // B - Comments + { defaultColor[2], FL_COURIER, 12}, // C - Strings + { defaultColor[3], FL_COURIER, 12}, // D - code_keywords + { defaultColor[4], FL_COURIER, 12}, // E - code_functions + { defaultColor[5], FL_COURIER, 12}, // F - code_procedures + { defaultColor[6], FL_COURIER, 12}, // G - Find matches + { defaultColor[7], FL_COURIER_ITALIC, 12}, // H - Italic Comments + { defaultColor[8], FL_COURIER, 12}, // I - Numbers + { defaultColor[9], FL_COURIER, 12}, // J - Operators + { FL_WHITE, FL_COURIER, 12}, // K - Background +}; + +#define PLAIN 'A' +#define COMMENTS 'B' +#define STRINGS 'C' +#define KEYWORDS 'D' +#define FUNCTIONS 'E' +#define PROCEDURES 'F' +#define FINDMATCH 'G' +#define ITCOMMENTS 'H' +#define DIGITS 'I' +#define OPERATORS 'J' + +/** + * return whether the character is a valid variable symbol + */ +bool isvar(int c) { + return (isalnum(c) || c == '_'); +} + +/** + * Compare two keywords + */ +int compare_keywords(const void *a, const void *b) { + return (strcasecmp(*((const char **)a), *((const char **)b))); +} + +/** + * Update unfinished styles. + */ +void style_unfinished_cb(int, void *) { +} + +/** + * Update the style buffer + */ +void style_update_cb(int pos, // I - Position of update + int nInserted, // I - Number of inserted chars + int nDeleted, // I - Number of deleted chars + int /* nRestyled */ , // I - Number of restyled chars + const char * /* deletedText */ , // I - Text that was deleted + void *cbArg) { // I - Callback data + BasicEditor *editor = (BasicEditor *) cbArg; + Fl_Text_Buffer *stylebuf = editor->_stylebuf; + Fl_Text_Buffer *textbuf = editor->_textbuf; + + // if this is just a selection change, just unselect the style buffer + if (nInserted == 0 && nDeleted == 0) { + stylebuf->unselect(); + return; + } + // track changes in the text buffer + if (nInserted > 0) { + // insert characters into the style buffer + char *stylex = new char[nInserted + 1]; + memset(stylex, PLAIN, nInserted); + stylex[nInserted] = '\0'; + stylebuf->replace(pos, pos + nDeleted, stylex); + delete[]stylex; + } else { + // just delete characters in the style buffer + stylebuf->remove(pos, pos + nDeleted); + } + + // Select the area that was just updated to avoid unnecessary callbacks + stylebuf->select(pos, pos + nInserted - nDeleted); + + // re-parse the changed region; we do this by parsing from the + // beginning of the line of the changed region to the end of + // the line of the changed region Then we check the last + // style character and keep updating if we have a multi-line + // comment character + if (nInserted > 0) { + int start = textbuf->line_start(pos); + int end = textbuf->line_end(pos + nInserted); + char *text_range = textbuf->text_range(start, end); + char *style_range = stylebuf->text_range(start, end); + int last = style_range[end - start - 1]; + + editor->styleParse(text_range, style_range, end - start); + stylebuf->replace(start, end, style_range); + editor->redisplay_range(start, end); + + if (last != style_range[end - start - 1]) { + // the last character on the line changed styles, + // so reparse the remainder of the buffer + free(text_range); + free(style_range); + end = textbuf->length(); + text_range = textbuf->text_range(start, end); + style_range = stylebuf->text_range(start, end); + editor->styleParse(text_range, style_range, end - start); + stylebuf->replace(start, end, style_range); + editor->redisplay_range(start, end); + } + free(text_range); + free(style_range); + } +} + +//--BasicEditor------------------------------------------------------------------ + +BasicEditor::BasicEditor(int x, int y, int w, int h, StatusBar *status) : + Fl_Text_Editor(x, y, w, h), + _readonly(false), + _status(status) { + + const char *s = getenv("INDENT_LEVEL"); + _indentLevel = (s && s[0] ? atoi(s) : 2); + _matchingBrace = -1; + _textbuf = new Fl_Text_Buffer(); + _stylebuf = new Fl_Text_Buffer(); + _search[0] = 0; + highlight_data(_stylebuf, styletable, + sizeof(styletable) / sizeof(styletable[0]), + PLAIN, style_unfinished_cb, 0); + _textbuf->add_modify_callback(style_update_cb, this); + buffer(_textbuf); +} + +BasicEditor::~BasicEditor() { + delete _stylebuf; +} + +/** + * Parse text and produce style data. + */ +void BasicEditor::styleParse(const char *text, char *style, int length) { + char current = PLAIN; + int last = 0; // prev char was alpha-num + char buf[1024]; + char *bufptr; + const char *temp; + int searchLen = strlen(_search); + + for (; length > 0; length--, text++) { + if (current == PLAIN) { + // check for directives, comments, strings, and keywords + if ((*text == '#' && (*(text - 1) == 0 || *(text - 1) == 10)) || + (strncasecmp(text, "rem", 3) == 0 && text[3] == ' ') || *text == '\'') { + // basic comment + current = COMMENTS; + for (; length > 0 && *text != '\n'; length--, text++) { + if (*text == ';') { + current = ITCOMMENTS; + } + *style++ = current; + } + if (length == 0) { + break; + } + } else if (strncmp(text, "\\\"", 2) == 0) { + // quoted quote + *style++ = current; + *style++ = current; + text++; + length--; + continue; + } else if (*text == '\"') { + current = STRINGS; + } else if (!last) { + // begin keyword/number search at non-alnum boundary + + // test for digit sequence + if (isdigit(*text)) { + *style++ = DIGITS; + if (*text == '0' && *(text + 1) == 'x') { + // hex number + *style++ = DIGITS; + text++; + length--; + } + while (*text && (*(text + 1) == '.' || isdigit(*(text + 1)))) { + *style++ = DIGITS; + text++; + length--; + } + continue; + } + // test for a keyword + temp = text; + bufptr = buf; + while (*temp != 0 && *temp != ' ' && + *temp != '\n' && *temp != '\r' && *temp != '"' && + *temp != '(' && *temp != ')' && *temp != '=' && bufptr < (buf + sizeof(buf) - 1)) { + *bufptr++ = tolower(*temp++); + } + + *bufptr = '\0'; + bufptr = buf; + + if (searchLen > 0) { + const char *sfind = strstr(bufptr, _search); + // find text match + if (sfind != 0) { + int offset = sfind - bufptr; + style += offset; + text += offset; + length -= offset; + for (int i = 0; i < searchLen && text < temp; i++) { + *style++ = FINDMATCH; + text++; + length--; + } + text--; + length++; + last = 1; + continue; + } + } + + if (bsearch(&bufptr, code_keywords, code_keywords_len, sizeof(code_keywords[0]), compare_keywords)) { + while (text < temp) { + *style++ = KEYWORDS; + text++; + length--; + } + text--; + length++; + last = 1; + continue; + } else if (bsearch(&bufptr, code_functions, code_functions_len, + sizeof(code_functions[0]), compare_keywords)) { + while (text < temp) { + *style++ = FUNCTIONS; + text++; + length--; + } + text--; + length++; + last = 1; + continue; + } else if (bsearch(&bufptr, code_procedures, code_procedures_len, + sizeof(code_procedures[0]), compare_keywords)) { + while (text < temp) { + *style++ = PROCEDURES; + text++; + length--; + } + text--; + length++; + last = 1; + continue; + } + } + } else if (current == STRINGS) { + // continuing in string + if (strncmp(text, "\\\"", 2) == 0) { + // quoted end quote + *style++ = current; + *style++ = current; + text++; + length--; + continue; + } else if (*text == '\"') { + // End quote + *style++ = current; + current = PLAIN; + continue; + } + } + // copy style info + *style++ = current; + last = isvar(*text) || *text == '.'; + + if (*text == '\n') { + current = PLAIN; // basic lines do not continue + } + } +} + +/** + * handler for the style change event + */ +void BasicEditor::styleChanged() { + _textbuf->select(0, _textbuf->length()); + _textbuf->select(0, 0); + damage(FL_DAMAGE_ALL); +} + +/** + * display the editor buffer + */ +void BasicEditor::draw() { + Fl_Text_Editor::draw(); + if (_matchingBrace != -1) { + // highlight the matching brace + int X, Y; + int cursor = cursor_style(); + cursor_style(BLOCK_CURSOR); + if (position_to_xy(_matchingBrace, &X, &Y)) { + draw_cursor(X, Y); + } + cursor_style(cursor); + } +} + +/** + * returns the indent position level + */ +unsigned BasicEditor::getIndent(char *spaces, int len, int pos) { + // count the indent level and find the start of text + char *buf = buffer()->line_text(pos); + int i = 0; + while (buf && buf[i] == ' ' && i < len) { + spaces[i] = buf[i]; + i++; + } + + if (strncasecmp(buf + i, "while ", 6) == 0 || + strncasecmp(buf + i, "if ", 3) == 0 || + strncasecmp(buf + i, "elseif ", 7) == 0 || + strncasecmp(buf + i, "elif ", 5) == 0 || + strncasecmp(buf + i, "else", 4) == 0 || + strncasecmp(buf + i, "repeat", 6) == 0 || + strncasecmp(buf + i, "for ", 4) == 0 || + strncasecmp(buf + i, "select ", 7) == 0 || + strncasecmp(buf + i, "case ", 5) == 0 || + strncasecmp(buf + i, "sub ", 4) == 0 || + strncasecmp(buf + i, "func ", 5) == 0) { + + // handle if-then-blah on same line + if (strncasecmp(buf + i, "if ", 3) == 0) { + // find the end of line index + int j = i + 4; + while (buf[j] != 0 && buf[j] != '\n') { + // line also 'ends' at start of comments + if (strncasecmp(buf + j, "rem", 3) == 0 || buf[j] == '\'') { + break; + } + j++; + } + // right trim trailing spaces + while (buf[j - 1] == ' ' && j > i) { + j--; + } + if (strncasecmp(buf + j - 4, "then", 4) != 0) { + // 'then' is not final text on line + spaces[i] = 0; + return i; + } + } + // indent new line + for (int j = 0; j < _indentLevel; j++, i++) { + spaces[i] = ' '; + } + } + spaces[i] = 0; + free((void *)buf); + return i; +} + +/** + * handler for the TAB character + */ +void BasicEditor::handleTab() { + char spaces[250]; + int indent; + + // get the desired indent based on the previous line + int lineStart = buffer()->line_start(insert_position()); + int prevLineStart = buffer()->line_start(lineStart - 1); + + if (prevLineStart && prevLineStart + 1 == lineStart) { + // allows for a single blank line between statments + prevLineStart = buffer()->line_start(prevLineStart - 1); + } + // note - spaces not used in this context + indent = prevLineStart == 0 ? 0 : getIndent(spaces, sizeof(spaces), prevLineStart); + + // get the current lines indent + char *buf = buffer()->line_text(lineStart); + int curIndent = 0; + while (buf && buf[curIndent] == ' ') { + curIndent++; + } + + // adjust indent for closure statements + if (strncasecmp(buf + curIndent, "wend", 4) == 0 || + strncasecmp(buf + curIndent, "fi", 2) == 0 || + strncasecmp(buf + curIndent, "endif", 5) == 0 || + strncasecmp(buf + curIndent, "elseif ", 7) == 0 || + strncasecmp(buf + curIndent, "elif ", 5) == 0 || + strncasecmp(buf + curIndent, "else", 4) == 0 || + strncasecmp(buf + curIndent, "next", 4) == 0 || + strncasecmp(buf + curIndent, "case", 4) == 0 || + strncasecmp(buf + curIndent, "end", 3) == 0 || + strncasecmp(buf + curIndent, "until ", 6) == 0) { + if (indent >= _indentLevel) { + indent -= _indentLevel; + } + } + if (curIndent < indent) { + // insert additional spaces + int len = indent - curIndent; + if (len > (int)sizeof(spaces) - 1) { + len = (int)sizeof(spaces) - 1; + } + memset(spaces, ' ', len); + spaces[len] = 0; + buffer()->insert(lineStart, spaces); + if (insert_position() - lineStart < indent) { + // jump cursor to start of text + insert_position(lineStart + indent); + } else { + // move cursor along with text movement, staying on same line + int maxpos = buffer()->line_end(lineStart); + if (insert_position() + len <= maxpos) { + insert_position(insert_position() + len); + } + } + } else if (curIndent > indent) { + // remove excess spaces + buffer()->remove(lineStart, lineStart + (curIndent - indent)); + } else { + // already have ideal indent - soft-tab to indent + insert_position(lineStart + indent); + } + free((void *)buf); +} + +/** + * sets the current display font + */ +void BasicEditor::setFont(Fl_Font font) { + if (font) { + int len = sizeof(styletable) / sizeof(styletable[0]); + for (int i = 0; i < len; i++) { + styletable[i].font = font; + } + styleChanged(); + } +} + +/** + * sets the current font size + */ +void BasicEditor::setFontSize(int size) { + int len = sizeof(styletable) / sizeof(styletable[0]); + for (int i = 0; i < len; i++) { + styletable[i].size = size; + } + styleChanged(); +} + +/** + * display the matching brace + */ +void BasicEditor::showMatchingBrace() { + char cursorChar = buffer()->char_at(insert_position() - 1); + char cursorMatch = 0; + int pair = -1; + int iter = -1; + int pos = insert_position() - 2; + + switch (cursorChar) { + case ']': + cursorMatch = '['; + break; + case ')': + cursorMatch = '('; + break; + case '(': + cursorMatch = ')'; + pos = insert_position(); + iter = 1; + break; + case '[': + cursorMatch = ']'; + iter = 1; + pos = insert_position(); + break; + } + if (cursorMatch != -0) { + // scan for matching opening on the same line + int level = 1; + int len = buffer()->length(); + int gap = 0; + while (pos > 0 && pos < len) { + char nextChar = buffer()->char_at(pos); + if (nextChar == 0 || nextChar == '\n') { + break; + } + if (nextChar == cursorChar) { + level++; // nested char + } else if (nextChar == cursorMatch) { + level--; + if (level == 0) { + // found matching char at pos + if (gap > 1) { + pair = pos; + } + break; + } + } + pos += iter; + gap++; + } + } + + if (_matchingBrace != -1) { + int lineStart = buffer()->line_start(_matchingBrace); + int lineEnd = buffer()->line_end(_matchingBrace); + redisplay_range(lineStart, lineEnd); + _matchingBrace = -1; + } + if (pair != -1) { + redisplay_range(pair, pair); + _matchingBrace = pair; + } +} + +/** + * highlight the given search text + */ +void BasicEditor::showFindText(const char *find) { + // copy lowercase search string for high-lighting + strcpy(_search, find); + int findLen = strlen(_search); + + for (int i = 0; i < findLen; i++) { + _search[i] = tolower(_search[i]); + } + + style_update_cb(0, _textbuf->length(), _textbuf->length(), 0, 0, this); +} + +/** + * FLTK event handler + */ +int BasicEditor::handle(int e) { + int cursor_pos = insert_position(); + bool navigateKey = false; + + switch (Fl::event_key()) { + case FL_Home: + case FL_Left: + case FL_Up: + case FL_Right: + case FL_Down: + case FL_Page_Up: + case FL_Page_Down: + case FL_End: + navigateKey = true; + } + + if (_readonly && ((e == FL_KEYBOARD && !navigateKey) || e == FL_PASTE)) { + // prevent buffer modification when in readonly state + return 0; + } + + if (e == FL_KEYBOARD && Fl::event_key() == FL_Tab) { + if (Fl::event_state(FL_CTRL)) { + // pass ctrl+key to parent + return 0; + } + handleTab(); + return 1; // skip default handler + } + // call default handler then process keys + int rtn = Fl_Text_Editor::handle(e); + switch (e) { + case FL_KEYBOARD: + if (Fl::event_key() == FL_Enter) { + char spaces[250]; + int indent = getIndent(spaces, sizeof(spaces), cursor_pos); + if (indent) { + buffer()->insert(insert_position(), spaces); + insert_position(insert_position() + indent); + damage(FL_DAMAGE_ALL); + } + } + // fallthru to show row-col + case FL_RELEASE: + showMatchingBrace(); + showRowCol(); + break; + } + + return rtn; +} + +/** + * displays the current row and col position + */ +void BasicEditor::showRowCol() { + int row = -1; + int col = 0; + + if (!position_to_linecol(insert_position(), &row, &col)) { + // This is a workaround for a bug in the FLTK TextDisplay widget + // where linewrapping causes a mis-calculation of line offsets which + // sometimes prevents the display of the last few lines of text. + insert_position(0); + scroll(0, 0); + insert_position(buffer()->length()); + scroll(count_lines(0, buffer()->length(), 1), 0); + position_to_linecol(insert_position(), &row, &col); + } + + _status->setRowCol(row, col + 1); +} + +/** + * sets the cursor to the given line number + */ +void BasicEditor::gotoLine(int line) { + int numLines = buffer()->count_lines(0, buffer()->length()); + if (line < 1) { + line = 1; + } else if (line > numLines) { + line = numLines; + } + int pos = buffer()->skip_lines(0, line - 1); // find pos at line-1 + insert_position(buffer()->line_start(pos)); // insert at column 0 + show_insert_position(); + _status->setRowCol(line, 1); + scroll(line, hor_offset()); +} + +/** + * returns where text selection starts + */ +void BasicEditor::getSelStartRowCol(int *row, int *col) { + int start = buffer()->primary_selection()->start(); + int end = buffer()->primary_selection()->end(); + if (start == end) { + *row = -1; + *col = -1; + } else { + position_to_linecol(start, row, col); + } +} + +/** + * returns where text selection ends + */ +void BasicEditor::getSelEndRowCol(int *row, int *col) { + int start = buffer()->primary_selection()->start(); + int end = buffer()->primary_selection()->end(); + if (start == end) { + *row = -1; + *col = -1; + } else { + position_to_linecol(end, row, col); + } +} + +/** + * return the selected text and its coordinate rectangle + */ +char *BasicEditor::getSelection(Fl_Rect *rc) { + char *result = 0; + if (!_readonly) { + int x1, y1, x2, y2, start, end; + + if (_textbuf->selected()) { + _textbuf->selection_position(&start, &end); + } else { + int pos = insert_position(); + if (isvar(_textbuf->char_at(pos))) { + start = _textbuf->word_start(pos); + end = _textbuf->word_end(pos); + } else { + start = end = 0; + } + } + + if (start != end) { + position_to_xy(start, &x1, &y1); + position_to_xy(end, &x2, &y2); + + rc->x(x1); + rc->y(y1); + rc->w(x2 - x1); + rc->h(maxSize()); + result = _textbuf->text_range(start, end); + } + } + return result; +} + +/** + * returns the current font size + */ +int BasicEditor::getFontSize() { + return (int)styletable[0].size; +} + +/** + * returns the current font face name + */ +Fl_Font BasicEditor::getFont() { + return styletable[0].font; +} + +/** + * returns the BASIC keyword list + */ +void BasicEditor::getKeywords(strlib::List &keywords) { + for (int i = 0; i < code_keywords_len; i++) { + keywords.add(new String(code_keywords[i])); + } + + for (int i = 0; i < code_functions_len; i++) { + keywords.add(new String(code_functions[i])); + } + + for (int i = 0; i < code_procedures_len; i++) { + keywords.add(new String(code_procedures[i])); + } +} + +/** + * returns the row and col position for the current cursor position + */ +void BasicEditor::getRowCol(int *row, int *col) { + position_to_linecol(insert_position(), row, col); +} + +/** + * find text within the editor buffer + */ +bool BasicEditor::findText(const char *find, bool forward, bool updatePos) { + showFindText(find); + + bool found = false; + if (find != 0 && find[0] != 0) { + int pos = insert_position(); + found = forward ? _textbuf->search_forward(pos, _search, &pos) : + _textbuf->search_backward(pos - strlen(find), _search, &pos); + if (found && updatePos) { + _textbuf->select(pos, pos + strlen(_search)); + insert_position(pos + strlen(_search)); + show_insert_position(); + } + } + return found; +} + diff --git a/src/platform/fltk/BasicEditor.h b/src/platform/fltk/BasicEditor.h new file mode 100644 index 00000000..8814e7ae --- /dev/null +++ b/src/platform/fltk/BasicEditor.h @@ -0,0 +1,60 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef BASIC_EDITOR_H +#define BASIC_EDITOR_H + +#include +#include "ui/strlib.h" +#include + +bool isvar(int c); + +struct StatusBar { + virtual ~StatusBar() {} + virtual void setRowCol(int row, int col) = 0; +}; + +struct BasicEditor : public Fl_Text_Editor { + BasicEditor(int x, int y, int w, int h, StatusBar *status); + ~BasicEditor(); + + bool findText(const char *find, bool forward, bool updatePos); + int handle(int e); + unsigned getIndent(char *indent, int len, int pos); + void draw(); + int getFontSize(); + Fl_Font getFont(); + void getKeywords(strlib::List &keywords); + void getRowCol(int *row, int *col); + void getSelEndRowCol(int *row, int *col); + void getSelStartRowCol(int *row, int *col); + char *getSelection(Fl_Rect *rc); + void gotoLine(int line); + void handleTab(); + void setFont(Fl_Font font); + void setFontSize(int size); + void showFindText(const char *text); + void showMatchingBrace(); + void showRowCol(); + void styleChanged(); + void styleParse(const char *text, char *style, int length); + int hor_offset() { return mHorizOffset; } + int maxSize() { return mMaxsize; } + int top_line() { return mTopLineNum; } + + bool _readonly; + int _indentLevel; + int _matchingBrace; + Fl_Text_Buffer *_stylebuf; + Fl_Text_Buffer *_textbuf; + char _search[PATH_MAX]; + StatusBar *_status; +}; + +#endif diff --git a/src/platform/fltk/EditorWidget.cxx b/src/platform/fltk/EditorWidget.cxx new file mode 100644 index 00000000..27049d3b --- /dev/null +++ b/src/platform/fltk/EditorWidget.cxx @@ -0,0 +1,1527 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include "platform/fltk/MainWindow.h" +#include "platform/fltk/EditorWidget.h" +#include "platform/fltk/FileWidget.h" +#include "common/smbas.h" + +// in MainWindow.cxx +extern String recentPath[]; +extern String recentLabel[]; +extern int recentMenu[]; +extern const char *historyFile; +extern const char *untitledFile; + +// in BasicEditor.cxx +extern Fl_Text_Display::Style_Table_Entry styletable[]; +extern Fl_Color defaultColor[]; + +int completionIndex = 0; + +static bool rename_active = false; +const char scanLabel[] = "(Refresh)"; + +EditorWidget *get_editor() { + EditorWidget *result = wnd->getEditor(); + if (!result) { + result = wnd->getEditor(true); + } + return result; +} + +struct LineInput : public Fl_Input { + LineInput(int x, int y, int w, int h); + void resize(int x, int y, int w, int h); + int handle(int event); + +private: + int orig_x, orig_y, orig_w, orig_h; +}; + +//--EditorWidget---------------------------------------------------------------- + +EditorWidget::EditorWidget(Fl_Widget *rect, Fl_Menu_Bar *menuBar) : + Fl_Group(rect->x(), rect->y(), rect->w(), rect->h()), + _editor(NULL), + _tty(NULL), + _dirty(false), + _loading(false), + _modifiedTime(0), + _commandText(NULL), + _rowStatus(NULL), + _colStatus(NULL), + _runStatus(NULL), + _modStatus(NULL), + _funcList(NULL), + _funcListEvent(false), + _logPrintBn(NULL), + _lockBn(NULL), + _hideIdeBn(NULL), + _gotoLineBn(NULL), + _commandOpt(cmd_find), + _commandChoice(NULL), + _menuBar(menuBar) { + _filename[0] = 0; + box(FL_NO_BOX); + begin(); + + const int st_w = 40; + const int bn_w = 28; + const int st_h = STATUS_HEIGHT; + const int choice_w = 80; + const int tileHeight = rect->h() - st_h; + const int ttyHeight = tileHeight / 8; + const int browserWidth = rect->w() / 5; + const int editHeight = tileHeight - ttyHeight; + const int editWidth = rect->w() - browserWidth; + const int st_y = rect->y() + editHeight + ttyHeight; + + Fl_Group *tile = new Fl_Tile(rect->x(), rect->y(), rect->w(), tileHeight); + _editor = new BasicEditor(rect->x(), rect->y(), editWidth, editHeight, this); + _editor->linenumber_width(LINE_NUMBER_WIDTH); + _editor->wrap_mode(true, 0); + _editor->selection_color(fl_rgb_color(190, 189, 188)); + _editor->_textbuf->add_modify_callback(changed_cb, this); + _editor->box(FL_FLAT_BOX); + _editor->take_focus(); + + // sub-func jump droplist + _funcList = new Fl_Tree(_editor->w(), rect->y(), browserWidth, editHeight); + _funcList->labelfont(FL_HELVETICA); + _funcList->when(FL_WHEN_RELEASE); + _funcList->box(FL_FLAT_BOX); + + Fl_Tree_Item *scan = new Fl_Tree_Item(_funcList); + scan->label(scanLabel); + _funcList->showroot(0); + _funcList->add(scanLabel, scan); + + _tty = new TtyWidget(rect->x(), rect->y() + editHeight, rect->w(), ttyHeight, TTY_ROWS); + tile->end(); + + // editor status bar + _statusBar = new Fl_Group(rect->x(), st_y, rect->w(), st_h); + _logPrintBn = new Fl_Toggle_Button(rect->w() - bn_w, st_y + 1, bn_w, st_h - 2); + _lockBn = new Fl_Toggle_Button(_logPrintBn->x() - (bn_w + 2), st_y + 1, bn_w, st_h - 2); + _hideIdeBn = new Fl_Toggle_Button(_lockBn->x() - (bn_w + 2), st_y + 1, bn_w, st_h - 2); + _gotoLineBn = new Fl_Toggle_Button(_hideIdeBn->x() - (bn_w + 2), st_y + 1, bn_w, st_h - 2); + _colStatus = new Fl_Button(_gotoLineBn->x() - (st_w + 2), st_y, st_w, st_h); + _rowStatus = new Fl_Button(_colStatus->x() - (st_w + 2), st_y, st_w, st_h); + _runStatus = new Fl_Button(_rowStatus->x() - (st_w + 2), st_y, st_w, st_h); + _modStatus = new Fl_Button(_runStatus->x() - (st_w + 2), st_y, st_w, st_h); + _commandChoice = new Fl_Button(rect->x(), st_y, choice_w, st_h); + _commandText = new Fl_Input(rect->x() + choice_w + 2, st_y + 1, _modStatus->x() - choice_w - 4, st_h - 2); + _commandText->align(FL_ALIGN_LEFT | FL_ALIGN_CLIP); + _commandText->when(FL_WHEN_ENTER_KEY_ALWAYS); + _commandText->labelfont(FL_HELVETICA); + + for (int n = 0; n < _statusBar->children(); n++) { + Fl_Widget *w = _statusBar->child(n); + w->labelfont(FL_HELVETICA); + w->box(FL_NO_BOX); + } + _commandText->box(FL_THIN_DOWN_BOX); + _logPrintBn->box(FL_THIN_UP_BOX); + _lockBn->box(FL_THIN_UP_BOX); + _hideIdeBn->box(FL_THIN_UP_BOX); + _gotoLineBn->box(FL_THIN_UP_BOX); + + _statusBar->resizable(_commandText); + _statusBar->end(); + resizable(tile); + end(); + + // command selection + setCommand(cmd_find); + runState(rs_ready); + setModified(false); + + // button callbacks + _lockBn->callback(scroll_lock_cb); + _modStatus->callback(save_file_cb); + _runStatus->callback(MainWindow::run_cb); + _commandChoice->callback(command_cb, (void *)1); + _commandText->callback(command_cb, (void *)1); + _funcList->callback(func_list_cb, 0); + _logPrintBn->callback(un_select_cb, (void *)_hideIdeBn); + _hideIdeBn->callback(un_select_cb, (void *)_logPrintBn); + _colStatus->callback(goto_line_cb, 0); + _rowStatus->callback(goto_line_cb, 0); + + // setup icons + _gotoLineBn->label("B"); // right arrow (goto) + _hideIdeBn->label("W"); // large dot + _lockBn->label("J"); // vertical bars + _logPrintBn->label("T"); // italic bold T + + // setup tooltips + _commandText->tooltip("Press Ctrl+f or Ctrl+Shift+f to find again"); + _rowStatus->tooltip("Cursor row position"); + _colStatus->tooltip("Cursor column position"); + _runStatus->tooltip("Run or BREAK"); + _modStatus->tooltip("Save file"); + _logPrintBn->tooltip("Display PRINT statements in the log window"); + _lockBn->tooltip("Prevent log window auto-scrolling"); + _hideIdeBn->tooltip("Hide the editor while program is running"); + _gotoLineBn->tooltip("Position the cursor to the last program line after BREAK"); + statusMsg(SB_STR_VER); + + // setup defaults or restore settings + if (wnd && wnd->_profile) { + wnd->_profile->loadConfig(this); + } + take_focus(); +} + +EditorWidget::~EditorWidget() { + delete _editor; +} + +//--Event handler methods------------------------------------------------------- + +/** + * change the selected text to upper/lower/camel case + */ +void EditorWidget::change_case(Fl_Widget *w, void *eventData) { + Fl_Text_Buffer *tb = _editor->_textbuf; + int start, end; + char *selection = getSelection(&start, &end); + int len = strlen(selection); + enum { up, down, mixed } curcase = isupper(selection[0]) ? up : down; + + for (int i = 1; i < len; i++) { + if (isalpha(selection[i])) { + bool isup = isupper(selection[i]); + if ((curcase == up && isup == false) || (curcase == down && isup)) { + curcase = mixed; + break; + } + } + } + + // transform pattern: Foo -> FOO, FOO -> foo, foo -> Foo + for (int i = 0; i < len; i++) { + selection[i] = curcase == mixed ? toupper(selection[i]) : tolower(selection[i]); + } + if (curcase == down) { + selection[0] = toupper(selection[0]); + // upcase chars following non-alpha chars + for (int i = 1; i < len; i++) { + if (isalpha(selection[i]) == false && i + 1 < len) { + selection[i + 1] = toupper(selection[i + 1]); + } + } + } + + if (selection[0]) { + tb->replace_selection(selection); + tb->select(start, end); + } + free((void *)selection); +} + +/** + * command handler + */ +void EditorWidget::command_opt(Fl_Widget *w, void *eventData) { + setCommand((CommandOpt) (intptr_t) eventData); +} + +/** + * cut selected text to the clipboard + */ +void EditorWidget::cut_text(Fl_Widget *w, void *eventData) { + Fl_Text_Editor::kf_cut(0, _editor); +} + +/** + * delete selected text + */ +void EditorWidget::do_delete(Fl_Widget *w, void *eventData) { + _editor->_textbuf->remove_selection(); +} + +/** + * perform keyword completion + */ +void EditorWidget::expand_word(Fl_Widget *w, void *eventData) { + int start, end; + const char *fullWord = 0; + unsigned fullWordLen = 0; + + Fl_Text_Buffer *textbuf = _editor->_textbuf; + char *text = textbuf->text(); + + if (textbuf->selected()) { + // get word before selection + int pos1, pos2; + textbuf->selection_position(&pos1, &pos2); + start = textbuf->word_start(pos1 - 1); + end = pos1; + // get word from before selection to end of selection + fullWord = text + start; + fullWordLen = pos2 - start - 1; + } else { + // nothing selected - get word to left of cursor position + int pos = _editor->insert_position(); + end = textbuf->word_end(pos); + start = textbuf->word_start(end - 1); + completionIndex = 0; + } + + if (start >= end) { + free(text); + return; + } + + const char *expandWord = text + start; + unsigned expandWordLen = end - start; + int wordPos = 0; + + // scan for expandWord from within the current text buffer + if (completionIndex != -1 && searchBackward(text, start - 1, + expandWord, expandWordLen, + &wordPos)) { + int matchPos = -1; + if (textbuf->selected() == 0) { + matchPos = wordPos; + completionIndex = 1; // find next word on next call + } else { + // find the next word prior to the currently selected word + int index = 1; + while (wordPos > 0) { + if (strncasecmp(text + wordPos, fullWord, fullWordLen) != 0 || + isalpha(text[wordPos + fullWordLen + 1])) { + // isalpha - matches fullWord but word has more chars + matchPos = wordPos; + if (completionIndex == index) { + completionIndex++; + break; + } + // count index for non-matching fullWords only + index++; + } + + if (searchBackward(text, wordPos - 1, expandWord, + expandWordLen, &wordPos) == 0) { + matchPos = -1; + break; // no more partial matches + } + } + if (index == completionIndex) { + // end of expansion sequence + matchPos = -1; + } + } + if (matchPos != -1) { + char *word = textbuf->text_range(matchPos, textbuf->word_end(matchPos)); + if (textbuf->selected()) { + textbuf->replace_selection(word + expandWordLen); + } else { + textbuf->insert(end, word + expandWordLen); + } + textbuf->select(end, end + strlen(word + expandWordLen)); + _editor->insert_position(end + strlen(word + expandWordLen)); + free((void *)word); + free(text); + return; + } + } + + completionIndex = -1; // no more buffer expansions + + strlib::List keywords; + _editor->getKeywords(keywords); + + // find the next replacement + int firstIndex = -1; + int lastIndex = -1; + int curIndex = -1; + int numWords = keywords.size(); + for (int i = 0; i < numWords; i++) { + const char *keyword = ((String *)keywords.get(i))->c_str(); + if (strncasecmp(expandWord, keyword, expandWordLen) == 0) { + if (firstIndex == -1) { + firstIndex = i; + } + if (fullWordLen == 0) { + if (expandWordLen == strlen(keyword)) { + // nothing selected and word to left of cursor matches + curIndex = i; + } + } else if (strncasecmp(fullWord, keyword, fullWordLen) == 0) { + // selection+word to left of selection matches + curIndex = i; + } + lastIndex = i; + } else if (lastIndex != -1) { + // moved beyond matching words + break; + } + } + + if (lastIndex != -1) { + if (lastIndex == curIndex || curIndex == -1) { + lastIndex = firstIndex; // wrap to first in subset + } else { + lastIndex = curIndex + 1; + } + + const char *keyword = ((String *)keywords.get(lastIndex))->c_str(); + // updated the segment of the replacement text + // that completes the current selection + if (textbuf->selected()) { + textbuf->replace_selection(keyword + expandWordLen); + } else { + textbuf->insert(end, keyword + expandWordLen); + } + textbuf->select(end, end + strlen(keyword + expandWordLen)); + } + free(text); +} + +/** + * handler for find text command + */ +void EditorWidget::find(Fl_Widget *w, void *eventData) { + setCommand(cmd_find); +} + +/** + * performs the current command + */ +void EditorWidget::command(Fl_Widget *w, void *eventData) { + bool found = false; + bool forward = (intptr_t) eventData; + bool updatePos = (_commandOpt != cmd_find_inc); + + if (Fl::event_button() == FL_RIGHT_MOUSE) { + // right click + forward = 0; + } + + switch (_commandOpt) { + case cmd_find_inc: + case cmd_find: + found = _editor->findText(_commandText->value(), forward, updatePos); + _commandText->textcolor(found ? _commandChoice->color() : FL_RED); + _commandText->redraw(); + break; + + case cmd_replace: + _commandBuffer.clear(); + _commandBuffer.append(_commandText->value()); + setCommand(cmd_replace_with); + break; + + case cmd_replace_with: + replace_next(); + break; + + case cmd_goto: + gotoLine(atoi(_commandText->value())); + take_focus(); + break; + + case cmd_input_text: + wnd->setModal(false); + break; + } +} + +/** + * sub/func selection list handler + */ +void EditorWidget::func_list(Fl_Widget *w, void *eventData) { + if (_funcList && _funcList->callback_item()) { + Fl_Tree_Item *item = (Fl_Tree_Item*)_funcList->callback_item(); + const char *label = item->label(); + if (label) { + _funcListEvent = true; + if (strcmp(label, scanLabel) == 0) { + resetList(); + } else { + gotoLine((int)(intptr_t)item->user_data()); + take_focus(); + } + } + } +} + +/** + * goto-line command handler + */ +void EditorWidget::goto_line(Fl_Widget *w, void *eventData) { + setCommand(cmd_goto); +} + +/** + * paste clipboard text onto the buffer + */ +void EditorWidget::paste_text(Fl_Widget *w, void *eventData) { + Fl_Text_Editor::kf_paste(0, _editor); +} + +/** + * rename the currently selected variable + */ +void EditorWidget::rename_word(Fl_Widget *w, void *eventData) { + if (rename_active) { + rename_active = false; + } else { + Fl_Rect rc; + char *selection = getSelection(&rc); + if (selection) { + showFindText(selection); + begin(); + LineInput *in = new LineInput(rc.x(), rc.y(), rc.w() + 10, rc.h()); + end(); + + in->value(selection); + in->callback(rename_word_cb); + in->textfont(FL_COURIER); + in->textsize(getFontSize()); + + rename_active = true; + while (rename_active && in == Fl::focus()) { + Fl::wait(); + } + + showFindText(""); + replaceAll(selection, in->value(), true, true); + remove(in); + take_focus(); + delete in; + free((void *)selection); + } + } +} + +/** + * replace the next find occurance + */ +void EditorWidget::replace_next(Fl_Widget *w, void *eventData) { + if (readonly()) { + return; + } + + const char *find = _commandBuffer; + const char *replace = _commandText->value(); + + Fl_Text_Buffer *textbuf = _editor->_textbuf; + int pos = _editor->insert_position(); + int found = textbuf->search_forward(pos, find, &pos); + + if (found) { + // found a match; update the position and replace text + textbuf->select(pos, pos + strlen(find)); + textbuf->remove_selection(); + textbuf->insert(pos, replace); + textbuf->select(pos, pos + strlen(replace)); + _editor->insert_position(pos + strlen(replace)); + _editor->show_insert_position(); + } else { + setCommand(cmd_find); + _editor->take_focus(); + } +} + +/** + * save file menu command handler + */ +void EditorWidget::save_file(Fl_Widget *w, void *eventData) { + if (_filename[0] == '\0') { + // no filename - get one! + wnd->save_file_as(); + return; + } else { + doSaveFile(_filename); + } +} + +/** + * prevent the tty window from scrolling with new data + */ +void EditorWidget::scroll_lock(Fl_Widget *w, void *eventData) { + _tty->setScrollLock(_lockBn->value()); +} + +/** + * select all text + */ +void EditorWidget::select_all(Fl_Widget *w, void *eventData) { + Fl_Text_Editor::kf_select_all(0, _editor); +} + +/** + * set colour menu command handler + */ +void EditorWidget::set_color(Fl_Widget *w, void *eventData) { + StyleField field = (StyleField)(intptr_t)eventData; + uint8_t r, g, b; + Fl::get_color(styletable[field].color, r, g, b); + if (fl_color_chooser(w->label(), r, g, b)) { + styletable[field].color = fl_rgb_color(r, g, b); + _editor->styleChanged(); + wnd->_profile->updateTheme(); + wnd->_profile->setEditTheme(this); + wnd->updateConfig(this); + wnd->show(); + } +} + +/** + * replace text menu command handler + */ +void EditorWidget::show_replace(Fl_Widget *w, void *eventData) { + const char *prime = _editor->_search; + if (!prime || !prime[0]) { + // use selected text when search not available + prime = _editor->_textbuf->selection_text(); + } + _commandText->value(prime); + setCommand(cmd_replace); +} + +/** + * undo any edit changes + */ +void EditorWidget::undo(Fl_Widget *w, void *eventData) { + Fl_Text_Editor::kf_undo(0, _editor); +} + +/** + * de-select the button specified in the eventData + */ +void EditorWidget::un_select(Fl_Widget *w, void *eventData) { + ((Fl_Button *)eventData)->value(false); +} + +//--Public methods-------------------------------------------------------------- + +/** + * handles saving the current buffer + */ +bool EditorWidget::checkSave(bool discard) { + if (!_dirty) { + // continue next operation + return true; + } + + const char *msg = "The current file has not been saved\n\nWould you like to save it now?"; + int r; + if (discard) { + r = fl_choice(msg, "Save", "Discard", "Cancel", NULL); + } else { + r = fl_choice(msg, "Save", "Cancel", NULL, NULL); + } + fprintf(stderr, "selected %d\n", r); + if (r == 0) { + // save selected + save_file(); + return !_dirty; + } + + // continue operation when discard selected + return (discard && r == 1); +} + +/** + * copy selection text to the clipboard + */ +void EditorWidget::copyText() { + if (!_tty->copySelection()) { + Fl_Text_Editor::kf_copy(0, _editor); + } +} + +/** + * saves the editor buffer to the given file name + */ +void EditorWidget::doSaveFile(const char *newfile) { + if (!_dirty && strcmp(newfile, _filename) == 0) { + // neither buffer or filename have changed + return; + } + + char basfile[PATH_MAX]; + Fl_Text_Buffer *textbuf = _editor->_textbuf; + + if (wnd->_profile->createBackups() && access(newfile, 0) == 0) { + // rename any existing file as a backup + strcpy(basfile, newfile); + strcat(basfile, "~"); + rename(newfile, basfile); + } + + strcpy(basfile, newfile); + if (strchr(basfile, '.') == 0) { + strcat(basfile, ".bas"); + } + + if (textbuf->savefile(basfile)) { + fl_alert("Error writing to file \'%s\':\n%s.", basfile, strerror(errno)); + return; + } + + _dirty = 0; + strcpy(_filename, basfile); + _modifiedTime = getModifiedTime(); + + wnd->updateEditTabName(this); + wnd->showEditTab(this); + + // store a copy in lastedit.bas + if (wnd->_profile->createBackups()) { + getHomeDir(basfile, sizeof(basfile)); + strlcat(basfile, "lastedit.bas", sizeof(basfile)); + textbuf->savefile(basfile); + } + + textbuf->call_modify_callbacks(); + showPath(); + fileChanged(true); + addHistory(_filename); + _editor->take_focus(); +} + +/** + * called when the buffer has changed + */ +void EditorWidget::fileChanged(bool loadfile) { + _funcList->clear(); + if (loadfile) { + // update the func/sub navigator + createFuncList(); + _funcList->redraw(); + + const char *filename = getFilename(); + if (filename && filename[0]) { + // update the last used file menu + bool found = false; + + for (int i = 0; i < NUM_RECENT_ITEMS; i++) { + if (recentPath[i].c_str() != NULL && + strcmp(filename, recentPath[i].c_str()) == 0) { + found = true; + break; + } + } + + if (found == false) { + const char *label = FileWidget::splitPath(filename, NULL); + + // shift items downwards + for (int i = NUM_RECENT_ITEMS - 1; i > 0; i--) { + _menuBar->replace(recentMenu[i], recentLabel[i - 1]); + recentLabel[i].clear(); + recentLabel[i].append(recentLabel[i - 1]); + recentPath[i].clear(); + recentPath[i].append(recentPath[i - 1]); + } + // create new item in first position + recentLabel[0].clear(); + recentLabel[0].append(label); + recentPath[0].clear(); + recentPath[0].append(filename); + _menuBar->replace(recentMenu[0], recentLabel[0]); + } + } + } + + _funcList->add(scanLabel); +} + +/** + * keyboard shortcut handler + */ +bool EditorWidget::focusWidget() { + switch (Fl::event_key()) { + case 'b': + setBreakToLine(!isBreakToLine()); + return true; + + case 'e': + setCommand(cmd_find); + take_focus(); + return true; + + case 'f': + if (strlen(_commandText->value()) > 0 && _commandOpt == cmd_find) { + // continue search - shift -> backward else forward + command(0, (void *)(intptr_t)(Fl::event_state(FL_SHIFT) ? 0 : 1)); + } + setCommand(cmd_find); + return true; + + case 'i': + setCommand(cmd_find_inc); + return true; + + case 't': + setLogPrint(!isLogPrint()); + return true; + + case 'w': + setHideIde(!isHideIDE()); + return true; + + case 'j': + setScrollLock(!isScrollLock()); + return true; + } + return false; +} + +/** + * returns the current font size + */ +int EditorWidget::getFontSize() { + return _editor->getFontSize(); +} + +/** + * use the input control as the INPUT basic command handler + */ +void EditorWidget::getInput(char *result, int size) { + if (result && result[0]) { + _commandText->value(result); + } + setCommand(cmd_input_text); + wnd->setModal(true); + while (wnd->isModal()) { + Fl::wait(); + } + if (wnd->isBreakExec()) { + brun_break(); + } else { + const char *value = _commandText->value(); + int valueLen = strlen(value); + int len = (valueLen < size) ? valueLen : size; + strncpy(result, value, len); + result[len] = 0; + } + setCommand(cmd_find); +} + +/** + * returns the row and col position for the current cursor position + */ +void EditorWidget::getRowCol(int *row, int *col) { + return ((BasicEditor *)_editor)->getRowCol(row, col); +} + +/** + * returns the selected text or the word around the cursor if there + * is no current selection. caller must free the returned value + */ +char *EditorWidget::getSelection(int *start, int *end) { + char *result = 0; + + Fl_Text_Buffer *tb = _editor->_textbuf; + if (tb->selected()) { + result = tb->selection_text(); + tb->selection_position(start, end); + } else { + int pos = _editor->insert_position(); + *start = tb->word_start(pos); + *end = tb->word_end(pos); + result = tb->text_range(*start, *end); + } + + return result; +} + +/** + * returns where text selection ends + */ +void EditorWidget::getSelEndRowCol(int *row, int *col) { + return ((BasicEditor *)_editor)->getSelEndRowCol(row, col); +} + +/** + * returns where text selection starts + */ +void EditorWidget::getSelStartRowCol(int *row, int *col) { + return ((BasicEditor *)_editor)->getSelStartRowCol(row, col); +} + +/** + * sets the cursor to the given line number + */ +void EditorWidget::gotoLine(int line) { + ((BasicEditor *)_editor)->gotoLine(line); +} + +/** + * FLTK event handler + */ +int EditorWidget::handle(int e) { + switch (e) { + case FL_SHOW: + case FL_FOCUS: + Fl::focus(_editor); + handleFileChange(); + return 1; + case FL_KEYBOARD: + if (Fl::event_key() == FL_Escape) { + take_focus(); + return 1; + } + break; + case FL_ENTER: + if (rename_active) { + // prevent drawing over the inplace editor child control + return 0; + } + } + + return Fl_Group::handle(e); +} + +/** + * load the given filename into the buffer + */ +void EditorWidget::loadFile(const char *newfile) { + // save the current filename + char oldpath[PATH_MAX]; + strcpy(oldpath, _filename); + + // convert relative path to full path + getcwd(_filename, sizeof(_filename)); + strcat(_filename, "/"); + strcat(_filename, newfile); + + if (access(_filename, R_OK) != 0) { + // filename unreadable, try newfile + strcpy(_filename, newfile); + } + + FileWidget::forwardSlash(_filename); + + _loading = true; + if (_editor->_textbuf->loadfile(_filename)) { + // read failed + fl_alert("Error reading from file \'%s\':\n%s.", _filename, strerror(errno)); + + // restore previous file + strcpy(_filename, oldpath); + _editor->_textbuf->loadfile(_filename); + } + + _dirty = false; + _loading = false; + + _editor->_textbuf->call_modify_callbacks(); + _editor->show_insert_position(); + _modifiedTime = getModifiedTime(); + readonly(false); + + wnd->updateEditTabName(this); + wnd->showEditTab(this); + + showPath(); + fileChanged(true); + setRowCol(1, 1); +} + +/** + * returns the buffer readonly flag + */ +bool EditorWidget::readonly() { + return ((BasicEditor *)_editor)->_readonly; +} + +/** + * sets the buffer readonly flag + */ +void EditorWidget::readonly(bool is_readonly) { + if (!is_readonly && access(_filename, W_OK) != 0) { + // cannot set writable since file is readonly + is_readonly = true; + } + _modStatus->label(is_readonly ? "RO" : "@line"); + _modStatus->redraw(); + _editor->cursor_style(is_readonly ? Fl_Text_Display::DIM_CURSOR : Fl_Text_Display::NORMAL_CURSOR); + ((BasicEditor *)_editor)->_readonly = is_readonly; +} + +/** + * displays the current run-mode flag + */ +void EditorWidget::runState(RunMessage runMessage) { + _runStatus->callback(MainWindow::run_cb); + const char *msg = 0; + switch (runMessage) { + case rs_err: + msg = "ERR"; + break; + case rs_run: + msg = "BRK"; + _runStatus->callback(MainWindow::run_break_cb); + break; + default: + msg = "RUN"; + } + _runStatus->copy_label(msg); + _runStatus->redraw(); +} + +/** + * Saves the selected text to the given file path + */ +void EditorWidget::saveSelection(const char *path) { + FILE *fp = fopen(path, "w"); + if (fp) { + Fl_Rect rc; + char *selection = getSelection(&rc); + if (selection) { + fwrite(selection, strlen(selection), 1, fp); + free((void *)selection); + } else { + // save as an empty file + fputc(0, fp); + } + fclose(fp); + } +} + +/** + * Sets the editor and editor toolbar color from the selected theme + */ +void EditorWidget::setTheme(EditTheme *theme) { + _editor->color(get_color(theme->_background)); + _editor->linenumber_bgcolor(get_color(theme->_background)); + _editor->linenumber_fgcolor(get_color(theme->_number_color)); + _editor->cursor_color(get_color(theme->_cursor_color)); + _editor->selection_color(get_color(theme->_selection_color)); + _funcList->color(fl_color_average(get_color(theme->_background), get_color(theme->_color), .92f)); + _funcList->item_labelfgcolor(get_color(theme->_color)); + _tty->color(_editor->color()); + _tty->labelcolor(fl_contrast(_tty->color(), get_color(theme->_background))); + _tty->selection_color(_editor->selection_color()); + resetList(); +} + +/** + * sets the current display font + */ +void EditorWidget::setFont(Fl_Font font) { + if (font) { + _editor->setFont(font); + _tty->setFont(font); + wnd->_profile->setFont(font); + } +} + +/** + * sets the current font size + */ +void EditorWidget::setFontSize(int size) { + _editor->setFontSize(size); + _tty->setFontSize(size); + wnd->_profile->setFontSize(size); +} + +/** + * sets the indent level to the given amount + */ +void EditorWidget::setIndentLevel(int level) { + ((BasicEditor *)_editor)->_indentLevel = level; +} + +/** + * displays the row/col in the editor toolbar + */ +void EditorWidget::setRowCol(int row, int col) { + char rowcol[20]; + sprintf(rowcol, "%d", row); + _rowStatus->copy_label(rowcol); + _rowStatus->redraw(); + sprintf(rowcol, "%d", col); + _colStatus->copy_label(rowcol); + _colStatus->redraw(); + if (!_funcListEvent) { + selectRowInBrowser(row); + } else { + _funcListEvent = false; + } +} + +/** + * display the full pathname + */ +void EditorWidget::showPath() { + _commandChoice->tooltip(_filename); +} + +/** + * prints a status message on the tty-widget + */ +void EditorWidget::statusMsg(const char *msg) { + if (msg) { + _tty->print(msg); + _tty->print("\n"); + } +} + +/** + * sets the font face, size and colour + */ +void EditorWidget::updateConfig(EditorWidget *current) { + setFont(current->_editor->getFont()); + setFontSize(current->_editor->getFontSize()); +} + +//--Protected methods----------------------------------------------------------- + +/** + * add filename to the hiistory file + */ +void EditorWidget::addHistory(const char *filename) { + FILE *fp; + char buffer[PATH_MAX]; + char updatedfile[PATH_MAX]; + char path[PATH_MAX]; + + int len = strlen(filename); + if (strcasecmp(filename + len - 4, ".sbx") == 0 || access(filename, R_OK) != 0) { + // don't remember bas exe or invalid files + return; + } + + len -= strlen(untitledFile); + if (len > 0 && strcmp(filename + len, untitledFile) == 0) { + // don't remember the untitled file + return; + } + // save paths with unix path separators + strcpy(updatedfile, filename); + FileWidget::forwardSlash(updatedfile); + filename = updatedfile; + + // save into the history file + getHomeDir(path, sizeof(path)); + strlcat(path, historyFile, sizeof(path)); + + fp = fopen(path, "r"); + if (fp) { + // don't add the item if it already exists + while (feof(fp) == 0) { + if (fgets(buffer, sizeof(buffer), fp) && strncmp(filename, buffer, strlen(filename) - 1) == 0) { + fclose(fp); + return; + } + } + fclose(fp); + } + + fp = fopen(path, "a"); + if (fp) { + fwrite(filename, strlen(filename), 1, fp); + fwrite("\n", 1, 1, fp); + fclose(fp); + } +} + +/** + * creates the sub/func selection list + */ +void EditorWidget::createFuncList() { + Fl_Text_Buffer *textbuf = _editor->_textbuf; + char *text = textbuf->text(); + int len = textbuf->length(); + int curLine = 1; + const char *keywords[] = { + "sub ", "func ", "def ", "label ", "const ", "local ", "dim " + }; + int keywords_length = sizeof(keywords) / sizeof(keywords[0]); + int keywords_len[keywords_length]; + for (int j = 0; j < keywords_length; j++) { + keywords_len[j] = strlen(keywords[j]); + } + Fl_Tree_Item *menuGroup = NULL; + + for (int i = 0; i < len; i++) { + // skip to the newline start + while (i < len && i != 0 && text[i] != '\n') { + i++; + } + + // skip any successive newlines + while (i < len && text[i] == '\n') { + curLine++; + i++; + } + + // skip any leading whitespace + while (i < len && (text[i] == ' ' || text[i] == '\t')) { + i++; + } + + for (int j = 0; j < keywords_length; j++) { + if (!strncasecmp(text + i, keywords[j], keywords_len[j])) { + i += keywords_len[j]; + int i_begin = i; + while (i < len && text[i] != '=' && text[i] != '\r' && text[i] != '\n') { + i++; + } + if (i > i_begin) { + String s(text + i_begin, i - i_begin); + if (j < 2) { + menuGroup = new Fl_Tree_Item(_funcList); + menuGroup->label(s.c_str()); + menuGroup->user_data((void *)(intptr_t)curLine); + _funcList->add(s.c_str(), menuGroup); + } else if (menuGroup != NULL) { + Fl_Tree_Item *leaf = new Fl_Tree_Item(_funcList); + leaf->label(s.c_str()); + leaf->user_data((void *)(intptr_t)curLine); + menuGroup->add(_funcList->prefs(), s.c_str(), leaf); + } + } + break; + } + } + if (text[i] == '\n') { + i--; // avoid eating the entire next line + } + } + free(text); +} + +/** + * called when the buffer has change - sets the modified flag + */ +void EditorWidget::doChange(int inserted, int deleted) { + if (!_loading) { + // do nothing while file load in progress + if (inserted || deleted) { + _dirty = 1; + } + + if (!readonly()) { + setModified(_dirty); + } + } +} + +/** + * handler for the sub/func list selection event + */ +void EditorWidget::findFunc(const char *find) { + char *text = _editor->_textbuf->text(); + int findLen = strlen(find); + int len = _editor->_textbuf->length(); + int lineNo = 1; + for (int i = 0; i < len; i++) { + if (strncasecmp(text + i, find, findLen) == 0) { + gotoLine(lineNo); + break; + } else if (text[i] == '\n') { + lineNo++; + } + } + free(text); +} + +/** + * returns the current selection text + */ +char *EditorWidget::getSelection(Fl_Rect *rc) { + return ((BasicEditor *)_editor)->getSelection(rc); +} + +/** + * returns the current file modified time + */ +uint32_t EditorWidget::getModifiedTime() { + struct stat st_file; + uint32_t modified = 0; + if (_filename[0] && !stat(_filename, &st_file)) { + modified = st_file.st_mtime; + } + return modified; +} + +/** + * handler for the external file change event + */ +void EditorWidget::handleFileChange() { + // handle outside changes to the file + if (_filename[0] && _modifiedTime != 0 && _modifiedTime != getModifiedTime()) { + String st; + st.append("File") + .append(_filename) + .append("has changed on disk.\n\n") + .append("Do you want to reload the file?"); + if (fl_choice(st.c_str(), "Yes", "No", NULL, NULL) == 0) { + reloadFile(); + } else { + _modifiedTime = 0; + } + } +} + +/** + * reset the function list + */ +void EditorWidget::resetList() { + _funcList->clear(); + createFuncList(); + _funcList->add(scanLabel); +} + +/** + * resize the heights + */ +void EditorWidget::resize(int x, int y, int w, int h) { + Fl_Group *tile = _editor->parent(); + int edit_scale_w = 1 + (_editor->w() * 100 / tile->w()); + int edit_scale_h = 1 + (_editor->h() * 100 / tile->h()); + edit_scale_w = MIN(80, edit_scale_w); + edit_scale_h = MIN(80, edit_scale_h); + + tile->resizable(_editor); + Fl_Group::resize(x, y, w, h); + tile->resize(tile->x(), y, w, h - STATUS_HEIGHT); + tile->resizable(NULL); + + int status_y = y + h - STATUS_HEIGHT; + int edit_w = tile->w() * edit_scale_w / 100; + int edit_h = tile->h() * edit_scale_h / 100; + int tty_y = tile->y() + edit_h; + int tty_h = status_y - tty_y; + int func_x = tile->x() + edit_w; + int func_w = tile->w() - edit_w; + + _editor->resize(_editor->x(), _editor->y(), edit_w, edit_h); + _funcList->resize(func_x, _funcList->y(), func_w, _editor->h()); + _tty->resize(_tty->x(), tty_y, _tty->w(), tty_h); + _statusBar->resize(_statusBar->x(), status_y, _statusBar->w(), STATUS_HEIGHT); +} + +/** + * create a new editor buffer + */ +void EditorWidget::newFile() { + if (!readonly() && checkSave(true)) { + Fl_Text_Buffer *textbuf = _editor->_textbuf; + _filename[0] = '\0'; + textbuf->select(0, textbuf->length()); + textbuf->remove_selection(); + _dirty = 0; + textbuf->call_modify_callbacks(); + fileChanged(false); + _modifiedTime = 0; + } +} + +/** + * reload the editor buffer + */ +void EditorWidget::reloadFile() { + char buffer[PATH_MAX]; + strcpy(buffer, _filename); + loadFile(buffer); +} + +/** + * replace all occurances of the given text + */ +int EditorWidget::replaceAll(const char *find, const char *replace, bool restorePos, bool matchWord) { + int times = 0; + + if (strcmp(find, replace) != 0) { + Fl_Text_Buffer *textbuf = _editor->_textbuf; + int prevPos = _editor->insert_position(); + + // loop through the whole string + int pos = 0; + _editor->insert_position(pos); + + while (textbuf->search_forward(pos, find, &pos)) { + // found a match; update the position and replace text + if (!matchWord || + ((pos == 0 || !isvar(textbuf->char_at(pos - 1))) && + !isvar(textbuf->char_at(pos + strlen(find))))) { + textbuf->select(pos, pos + strlen(find)); + textbuf->remove_selection(); + textbuf->insert(pos, replace); + } + // advance beyond replace string + pos += strlen(replace); + _editor->insert_position(pos); + times++; + } + + if (restorePos) { + _editor->insert_position(prevPos); + } + _editor->show_insert_position(); + } + + return times; +} + +/** + * handler for searching backwards + */ +bool EditorWidget::searchBackward(const char *text, int startPos, + const char *find, int findLen, int *foundPos) { + int matchIndex = findLen - 1; + for (int i = startPos; i >= 0; i--) { + bool equals = toupper(text[i]) == toupper(find[matchIndex]); + if (equals == false && matchIndex < findLen - 1) { + // partial match now fails - reset search at current index + matchIndex = findLen - 1; + equals = toupper(text[i]) == toupper(find[matchIndex]); + } + matchIndex = (equals ? matchIndex - 1 : findLen - 1); + if (matchIndex == -1 && (i == 0 || isalpha(text[i - 1]) == 0)) { + // char prior to word is non-alpha + *foundPos = i; + return true; + } + } + return false; +} + +/** + * sync the browser widget selection + */ +void EditorWidget::selectRowInBrowser(int row) { + Fl_Tree_Item *root = _funcList->root(); + int len = root->children() - 1; + bool found = false; + for (int i = 0; i < len; i++) { + int line = (int)(intptr_t)root->child(i)->user_data(); + int nextLine = (int)(intptr_t)root->child(i + 1)->user_data(); + if (row >= line && (i == len - 1 || row < nextLine)) { + int y = _funcList->vposition() + root->child(i)->y(); + int bottom = _funcList->y() + _funcList->h(); + int pos = bottom - y - (_funcList->h() / 2); + _funcList->select_only(root->child(i), 0); + _funcList->vposition(-pos); + found = true; + break; + } + } + if (!found) { + _funcList->select_only(root->child(0), 0); + _funcList->vposition(0); + } +} + +/** + * sets the current command + */ +void EditorWidget::setCommand(CommandOpt command) { + if (_commandOpt == cmd_input_text) { + wnd->setModal(false); + } + + _commandOpt = command; + switch (command) { + case cmd_find: + _commandChoice->label("@search Find:"); + break; + case cmd_find_inc: + _commandChoice->label("Inc Find:"); + break; + case cmd_replace: + _commandChoice->label("Replace:"); + break; + case cmd_replace_with: + _commandChoice->label("With:"); + break; + case cmd_goto: + _commandChoice->label("Goto:"); + break; + case cmd_input_text: + _commandChoice->label("INPUT:"); + break; + } + _commandChoice->redraw(); + + _commandText->color(_commandChoice->color()); + _commandText->redraw(); + _commandText->take_focus(); + _commandText->when(_commandOpt == cmd_find_inc ? FL_WHEN_CHANGED : FL_WHEN_ENTER_KEY_ALWAYS); +} + +/** + * display the toolbar modified flag + */ +void EditorWidget::setModified(bool dirty) { + _dirty = dirty; + _modStatus->when(dirty ? FL_WHEN_CHANGED : FL_WHEN_NEVER); + _modStatus->label(dirty ? "MOD" : "@line"); + _modStatus->redraw(); +} + +/** + * highlight the given search text + */ +void EditorWidget::showFindText(const char *text) { + _editor->showFindText(text); +} + +LineInput::LineInput(int x, int y, int w, int h) : + Fl_Input(x, y, w, h), + orig_x(x), + orig_y(y), + orig_w(w), + orig_h(h) { + when(FL_WHEN_ENTER_KEY); + box(FL_BORDER_BOX); + fl_color(fl_rgb_color(220, 220, 220)); + take_focus(); +} + +/** + * veto the layout changes + */ +void LineInput::resize(int x, int y, int w, int h) { + Fl_Input::resize(orig_x, orig_y, orig_w, orig_h); +} + +int LineInput::handle(int event) { + int result; + if (event == FL_KEYBOARD) { + if (Fl::event_state(FL_CTRL) && Fl::event_key() == 'b') { + if (!wnd->isEdit()) { + wnd->setBreak(); + } + } else if (Fl::event_key(FL_Escape)) { + do_callback(); + } else { + // grow the input box width as text is entered + const char *text = value(); + int strw = fl_width(text) + fl_width(value()) + 4; + if (strw > w()) { + w(strw); + orig_w = strw; + redraw(); + } + } + result = Fl_Input::handle(event); + } else { + result = Fl_Input::handle(event); + } + return result; +} diff --git a/src/platform/fltk/EditorWidget.h b/src/platform/fltk/EditorWidget.h new file mode 100644 index 00000000..6dff63e9 --- /dev/null +++ b/src/platform/fltk/EditorWidget.h @@ -0,0 +1,195 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef EDITOR_WIDGET_H +#define EDITOR_WIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ui/strlib.h" +#include "platform/fltk/TtyWidget.h" +#include "platform/fltk/BasicEditor.h" +#include "platform/fltk/utils.h" + +#ifdef CALLBACK_METHOD +#undef CALLBACK_METHOD +#endif + +struct EditorWidget; +EditorWidget *get_editor(); + +#define CALLBACK_METHOD(FN) \ + void FN(Fl_Widget*w=0, void *v=0); \ + static void FN ## _cb(Fl_Widget *w, void *v) { \ + EditorWidget *e = get_editor(); \ + if (e) e->FN(w, v); \ + } + +enum RunMessage { + rs_err, + rs_run, + rs_ready +}; + +enum StyleField { + st_text = 0, + st_comments, + st_strings, + st_keywords, + st_funcs, + st_subs, + st_findMatches, + st_icomments, + st_numbers, + st_operators, + st_background +}; + +enum CommandOpt { + cmd_find = 0, + cmd_find_inc, + cmd_replace, + cmd_replace_with, + cmd_goto, + cmd_input_text, +}; + +struct EditorWidget : public Fl_Group, StatusBar { + EditorWidget(Fl_Widget *rect, Fl_Menu_Bar *menu); + virtual ~EditorWidget(); + + CALLBACK_METHOD(change_case); + CALLBACK_METHOD(command); + CALLBACK_METHOD(command_opt); + CALLBACK_METHOD(cut_text); + CALLBACK_METHOD(do_delete); + CALLBACK_METHOD(expand_word); + CALLBACK_METHOD(find); + CALLBACK_METHOD(func_list); + CALLBACK_METHOD(goto_line); + CALLBACK_METHOD(paste_text); + CALLBACK_METHOD(rename_word); + CALLBACK_METHOD(replace_next); + CALLBACK_METHOD(save_file); + CALLBACK_METHOD(scroll_lock); + CALLBACK_METHOD(select_all); + CALLBACK_METHOD(set_color); + CALLBACK_METHOD(show_replace); + CALLBACK_METHOD(undo); + CALLBACK_METHOD(un_select); + + static void changed_cb(int, int inserted, int deleted, int, const char *, void *v) { + ((EditorWidget *) v)->doChange(inserted, deleted); + } + + bool checkSave(bool discard); + void copyText(); + void doSaveFile(const char *newfile); + void fileChanged(bool loadfile); + bool focusWidget(); + const char *getFilename() { return _filename; } + int getFontSize(); + void getInput(char *result, int size); + void getRowCol(int *row, int *col); + char *getSelection(int *start, int *end); + void getSelEndRowCol(int *row, int *col); + void getSelStartRowCol(int *row, int *col); + void gotoLine(int line); + int handle(int e); + bool isDirty() { return _dirty; } + void loadFile(const char *newfile); + bool readonly(); + void readonly(bool is_readonly); + void runState(RunMessage runMessage); + void saveSelection(const char *path); + void setBreakToLine(bool b) { _gotoLineBn->value(b); } + void setTheme(EditTheme *theme); + void setFont(Fl_Font font); + void setFontSize(int i); + void setHideIde(bool b) { _hideIdeBn->value(b); if (b) setLogPrint(!b); } + void setIndentLevel(int level); + void setLogPrint(bool b) { _logPrintBn->value(b); if (b) setHideIde(!b); } + void setRowCol(int row, int col); + void setScrollLock(bool b) { _lockBn->value(b); _tty->setScrollLock(b); } + void showPath(); + void statusMsg(const char *msg); + void updateConfig(EditorWidget *current); + bool isBreakToLine() { return _gotoLineBn->value(); } + bool isHideIDE() { return _hideIdeBn->value(); } + bool isLoading() { return _loading; } + bool isLogPrint() { return _logPrintBn->value(); } + bool isScrollLock() { return _lockBn->value(); } + void selectAll() { _editor->_textbuf->select(0, _editor->_textbuf->length()); } + const char *data() { return _editor->_textbuf->text(); } + int dataLength() { return _editor->_textbuf->length(); } + int top_line() { return _editor->top_line(); } + Fl_Text_Editor *getEditor() { return _editor; } + TtyWidget *getTty() { return _tty; } + +protected: + void addHistory(const char *filename); + void createFuncList(); + void doChange(int inserted, int deleted); + void findFunc(const char *find); + char *getSelection(Fl_Rect *rc); + void getKeywords(strlib::List &keywords); + uint32_t getModifiedTime(); + void handleFileChange(); + void resetList(); + void resize(int x, int y, int w, int h); + void newFile(); + void reloadFile(); + int replaceAll(const char *find, const char *replace, bool restorePos, bool matchWord); + bool searchBackward(const char *text, int startPos, const char *find, int findLen, int *foundPos); + void selectRowInBrowser(int row); + void setCommand(CommandOpt command); + void setModified(bool dirty); + void showFindText(const char *text); + +private: + BasicEditor *_editor; + TtyWidget *_tty; + + char _filename[PATH_MAX]; + bool _dirty; + bool _loading; + uint32_t _modifiedTime; + + // tool-bar + Fl_Group *_statusBar; + Fl_Input *_commandText; + Fl_Widget *_rowStatus; + Fl_Widget *_colStatus; + Fl_Button *_runStatus; + Fl_Button *_modStatus; + Fl_Tree *_funcList; + bool _funcListEvent; + + Fl_Toggle_Button *_logPrintBn; + Fl_Toggle_Button *_lockBn; + Fl_Toggle_Button *_hideIdeBn; + Fl_Toggle_Button *_gotoLineBn; + + // same order as display items + CommandOpt _commandOpt; + Fl_Button *_commandChoice; + + strlib::String _commandBuffer; + + Fl_Menu_Bar *_menuBar; +}; + +#endif diff --git a/src/platform/fltk/FileWidget.cxx b/src/platform/fltk/FileWidget.cxx new file mode 100755 index 00000000..867a3876 --- /dev/null +++ b/src/platform/fltk/FileWidget.cxx @@ -0,0 +1,520 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include +#include +#include +#include +#include "platform/fltk/MainWindow.h" +#include "platform/fltk/HelpWidget.h" +#include "platform/fltk/FileWidget.h" +#include "ui/strlib.h" +#include "common/device.h" + +FileWidget *fileWidget; +String click; +enum SORT_BY { e_name, e_size, e_time } sortBy; +bool sortDesc; + +const char CMD_SET_DIR = '+'; +const char CMD_CHG_DIR = '!'; +const char CMD_ENTER_PATH = '@'; +const char CMD_SAVE_AS = '~'; +const char CMD_SORT_DATE = '#'; +const char CMD_SORT_SIZE = '^'; +const char CMD_SORT_NAME = '$'; + +struct FileNode { + FileNode(const char *label, const char *name, time_t m_time, off_t size, bool isdir) : + _label(label, strlen(label)), + _name(name, strlen(name)), + _m_time(m_time), + _size(size), + _isdir(isdir) { + } + + String _label; + String _name; + time_t _m_time; + off_t _size; + bool _isdir; +}; + +int stringCompare(const void *a, const void *b) { + String *s1 = ((String **) a)[0]; + String *s2 = ((String **) b)[0]; + return strcasecmp(s1->c_str(), s2->c_str()); +} + +int fileNodeCompare(const void *a, const void *b) { + FileNode *n1 = ((FileNode **) a)[0]; + FileNode *n2 = ((FileNode **) b)[0]; + int result = 0; + switch (sortBy) { + case e_name: + if (n1->_isdir && !n2->_isdir) { + result = -1; + } else if (!n1->_isdir && n2->_isdir) { + result = 1; + } else { + result = strcasecmp(n1->_name.c_str(), n2->_name.c_str()); + } + break; + case e_size: + result = n1->_size < n2->_size ? -1 : n1->_size > n2->_size ? 1 : 0; + break; + case e_time: + result = n1->_m_time < n2->_m_time ? -1 : n1->_m_time > n2->_m_time ? 1 : 0; + break; + } + if (sortDesc) { + result = -result; + } + return result; +} + +void updateSortBy(SORT_BY newSort) { + if (sortBy == newSort) { + sortDesc = !sortDesc; + } else { + sortBy = newSort; + sortDesc = false; + } +} + +static void anchorClick_event(void *) { + Fl::remove_check(anchorClick_event); + fileWidget->anchorClick(); +} + +static void anchorClick_cb(Fl_Widget *w, void *v) { + if (fileWidget) { + click.clear(); + click.append((char *)v); + Fl::add_check(anchorClick_event); // post message + } +} + +FileWidget::FileWidget(Fl_Widget *rect) : + HelpWidget(rect), + _saveEditorAs(0), + _recentPaths(NULL) { + callback(anchorClick_cb); + fileWidget = this; + sortDesc = false; + sortBy = e_name; +} + +FileWidget::~FileWidget() { + fileWidget = NULL; + delete _recentPaths; +} + +// +// convert slash chars in filename to forward slashes +// +const char *FileWidget::forwardSlash(char *filename) { + const char *result = 0; + int len = filename ? strlen(filename) : 0; + for (int i = 0; i < len; i++) { + if (filename[i] == '\\') { + filename[i] = '/'; + result = &filename[i]; + } + } + return result; +} + +/** + * return the name component of the full file path + */ +const char *FileWidget::splitPath(const char *filename, char *path) { + const char *result = strrchr(filename, '/'); + if (!result) { + result = strrchr(filename, '\\'); + } + + if (!result) { + result = filename; + } else { + // skip slash + result++; + } + + if (path) { + // return the path component + int len = result - filename - 1; + if (len > 0) { + strncpy(path, filename, len); + // snip any double slashes + while (len > 0 && path[len - 1] == '/') { + len--; + } + path[len] = 0; + } else { + path[0] = 0; + } + } + + return result; +} + +// +// removes CRLF line endings +// +const char *FileWidget::trimEOL(char *buffer) { + int index = strlen(buffer) - 1; + const char *result = buffer; + while (index > 0 && (buffer[index] == '\r' || buffer[index] == '\n')) { + buffer[index] = 0; + index--; + } + return result; +} + +// +// anchor link clicked +// +void FileWidget::anchorClick() { + const char *target = click.c_str(); + + switch (target[0]) { + case CMD_CHG_DIR: + changeDir(target + 1); + return; + + case CMD_SET_DIR: + setDir(target + 1); + return; + + case CMD_SAVE_AS: + saveAs(); + return; + + case CMD_ENTER_PATH: + enterPath(); + return; + + case CMD_SORT_NAME: + updateSortBy(e_name); + displayPath(); + return; + + case CMD_SORT_SIZE: + updateSortBy(e_size); + displayPath(); + return; + + case CMD_SORT_DATE: + updateSortBy(e_time); + displayPath(); + return; + } + + String docHome; + if (target[0] == '/') { + const char *base = getDocHome(); + if (base && base[0]) { + // remove any overlapping string segments between the docHome + // of the index page, eg c:/home/cache/smh/handheld/ and the + // anchored sub-page, eg, "/handheld/articles/2006/... " + // ie, remove the URL component from the file name + int len = strlen(base); + const char *p = strchr(base + 1, '/'); + while (p && *p && *(p + 1)) { + if (strncmp(p, target, len - (p - base)) == 0) { + len = p - base; + break; + } + p = strchr(p + 1, '/'); + } + docHome.append(base, len); + } + } else { + docHome.append(_path); + } + + if (_saveEditorAs) { + Fl_Input *input = (Fl_Input *)getInput("saveas"); + input->value(target); + } else { + setDocHome(docHome); + String fullPath; + fullPath.append(docHome.c_str()); + fullPath.append("/"); + fullPath.append(target[0] == '/' ? target + 1 : target); + wnd->editFile(fullPath.c_str()); + } +} + +// +// open file +// +void FileWidget::fileOpen(EditorWidget *_saveEditorAs) { + this->_saveEditorAs = _saveEditorAs; + displayPath(); +} + +// +// display the given path +// +void FileWidget::openPath(const char *newPath, StringList *recentPaths) { + if (newPath && access(newPath, R_OK) == 0) { + strcpy(_path, newPath); + } else { + getcwd(_path, sizeof(_path)); + } + + delete _recentPaths; + _recentPaths = recentPaths; + + forwardSlash(_path); + displayPath(); + redraw(); +} + +// +// change to the given dir +// +void FileWidget::changeDir(const char *target) { + char newPath[PATH_MAX + 1]; + + strcpy(newPath, _path); + + // file browser window + if (strcmp(target, "..") == 0) { + // go up a level c:/src/foo or /src/foo + char *p = strrchr(newPath, '/'); + if (strchr(newPath, '/') != p) { + *p = 0; // last item not first + } else { + *(p + 1) = 0; // found root + } + } else { + // go down a level + if (newPath[strlen(newPath) - 1] != '/') { + strcat(newPath, "/"); + } + strcat(newPath, target); + } + setDir(newPath); +} + +// +// set to the given dir +// +void FileWidget::setDir(const char *target) { + if (chdir(target) == 0) { + strcpy(_path, target); + displayPath(); + } else { + fl_message("Invalid path '%s'", target); + } +} + +// +// display the path +// +void FileWidget::displayPath() { + dirent *entry; + struct stat stbuf; + strlib::List files; + char modifedTime[100]; + String html; + + if (chdir(_path) != 0) { + return; + } + + DIR *dp = opendir(_path); + if (dp == 0) { + return; + } + + while ((entry = readdir(dp)) != 0) { + char *name = entry->d_name; + int len = strlen(name); + if (strcmp(name, ".") == 0) { + continue; + } + + if (strcmp(name, "..") == 0) { + if (strcmp(_path, "/") != 0 && strcmp(_path + 1, ":/") != 0) { + // not "/" or "C:/" + files.add(new FileNode("Go up", "..", stbuf.st_mtime, stbuf.st_size, true)); + } + } else if (stat(name, &stbuf) != -1 && stbuf.st_mode & S_IFDIR) { + files.add(new FileNode(name, name, stbuf.st_mtime, stbuf.st_size, true)); + } else if (strncasecmp(name + len - 4, ".htm", 4) == 0 || + strncasecmp(name + len - 5, ".html", 5) == 0 || + strncasecmp(name + len - 4, ".bas", 4) == 0 || + strncasecmp(name + len - 4, ".txt", 4) == 0) { + files.add(new FileNode(name, name, stbuf.st_mtime, stbuf.st_size, false)); + } + } + closedir(dp); + + files.sort(fileNodeCompare); + + if (_saveEditorAs) { + const char *path = _saveEditorAs->getFilename(); + const char *slash = strrchr(path, '/'); + html.append("Save ").append(slash ? slash + 1 : path).append(" as:
") + .append(" 

"); + } + + _recentPaths->sort(stringCompare); + html.append("Recent places:
"); + List_each(String*, it, *_recentPaths) { + String *nextPath = (*it); + if (!nextPath->equals(_path)) { + html.append(" [ ") + .append(nextPath) + .append(" ]
"); + } + } + + html.append("
Files in: ").append(_path) + .append(""); + + html.append("") + .append("") + .append("") + .append(""); + + List_each(FileNode*, it, files) { + FileNode *fileNode = (*it); + html.append("").append(""); + html.append(""); + strftime(modifedTime, sizeof(modifedTime), "%Y-%m-%d %I:%M %p", + localtime(&fileNode->_m_time)); + html.append(""); + } + + html.append("
NameSizeDate
"); + if (fileNode->_isdir) { + html.append("[ "); + } + html.append(fileNode->_label); + if (fileNode->_isdir) { + html.append(" ]"); + } + html.append(""); + if (fileNode->_isdir) { + html.append(0); + } else { + html.append(fileNode->_isdir ? 0 : (int)fileNode->_size); + } + html.append("").append(modifedTime).append("
"); + loadBuffer(html); + take_focus(); +} + +// +// open the path +// +void FileWidget::enterPath() { + const char *newPath = fl_input("Enter path:", _path); + if (newPath != 0) { + if (chdir(newPath) == 0) { + strcpy(_path, newPath); + displayPath(); + } else { + fl_message("Invalid path '%s'", newPath); + } + } +} + +// +// event handler +// +int FileWidget::handle(int e) { + static char buffer[PATH_MAX]; + static int dnd_active = 0; + + switch (e) { + case FL_SHOW: + if (_saveEditorAs) { + _saveEditorAs = 0; + displayPath(); + } + break; + + case FL_DND_LEAVE: + dnd_active = 0; + return 1; + + case FL_DND_DRAG: + case FL_DND_RELEASE: + case FL_DND_ENTER: + dnd_active = 1; + return 1; + + case FL_MOVE: + if (dnd_active) { + return 1; // return 1 to become drop-target + } + break; + + case FL_PASTE: + strncpy(buffer, Fl::event_text(), Fl::event_length()); + buffer[Fl::event_length()] = 0; + forwardSlash(buffer); + wnd->editFile(buffer); + dnd_active = 0; + return 1; + } + + return HelpWidget::handle(e); +} + +// +// save the buffer with a new name +// +void FileWidget::saveAs() { + if (_saveEditorAs) { + const char *enteredPath = getInputValue(getInput("saveas")); + if (enteredPath && enteredPath[0]) { + // a path has been entered + char savepath[PATH_MAX + 1]; + if (enteredPath[0] == '~') { + // substitute ~ for $HOME contents + const char *home = dev_getenv("HOME"); + if (home) { + strcpy(savepath, home); + } else { + savepath[0] = 0; + } + strcat(savepath, enteredPath + 1); + } else if (enteredPath[0] == '/' || enteredPath[1] == ':') { + // absolute path given + strcpy(savepath, enteredPath); + } else { + strcpy(savepath, _path); + strcat(savepath, "/"); + strcat(savepath, enteredPath); + } + const char *msg = "%s\n\nFile already exists.\nDo you want to replace it?"; + if (access(savepath, 0) != 0 || fl_choice(msg, "Yes", "No", 0, savepath) == 0) { + _saveEditorAs->doSaveFile(savepath); + } + } + } +} + diff --git a/src/platform/fltk/FileWidget.h b/src/platform/fltk/FileWidget.h new file mode 100755 index 00000000..0089b215 --- /dev/null +++ b/src/platform/fltk/FileWidget.h @@ -0,0 +1,41 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef FILE_WIDGET_H +#define FILE_WIDGET_H + +#include +#include "platform/fltk/HelpWidget.h" +#include "platform/fltk/EditorWidget.h" + +struct FileWidget : public HelpWidget { + FileWidget(Fl_Widget *rect); + ~FileWidget(); + + static const char *forwardSlash(char *filename); + static const char *splitPath(const char *filename, char *path); + static const char *trimEOL(char *buffer); + + void anchorClick(); + void fileOpen(EditorWidget *saveEditorAs); + void openPath(const char *newPath, StringList *recentPaths); + +private: + void changeDir(const char *target); + void setDir(const char *target); + void displayPath(); + void enterPath(); + int handle(int e); + void saveAs(); + + char _path[PATH_MAX + 1]; + EditorWidget *_saveEditorAs; + StringList *_recentPaths; +}; + +#endif diff --git a/src/platform/fltk/HelpView.cxx b/src/platform/fltk/HelpView.cxx new file mode 100644 index 00000000..558cce03 --- /dev/null +++ b/src/platform/fltk/HelpView.cxx @@ -0,0 +1,216 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include +#include +#include +#include "platform/fltk/HelpView.h" +#include "ui/kwp.h" +#include "ui/strlib.h" + +const char *aboutText = + "About SmallBASIC...

" + "Version " SB_STR_VER "
" + "Copyright (c) 2002-2019 Chris Warren-Smith.

" + "Copyright (c) 2000-2006 Nicholas Christopoulos.

" + "" + "https://smallbasic.github.io

" + "SmallBASIC comes with ABSOLUTELY NO WARRANTY. " + "This program is free software; you can use it " + "redistribute it and/or modify it under the terms of the " + "GNU General Public License version 2 as published by " + "the Free Software Foundation.

" "Press F1 for help"; + +const char CMD_LEVEL1_OPEN = '+'; +const char CMD_LEVEL2_OPEN = '!'; +const char CMD_MORE = '^'; +const char LEVEL1_OPEN[] = "+ "; +const char LEVEL2_OPEN[] = " + "; +const char LEVEL1_CLOSE[] = " - "; +const char LEVEL2_CLOSE[] = " - "; + +HelpView *helpView; + +const char *getBriefHelp(const char *selection) { + const char *result = NULL; + int len = selection != NULL ? strlen(selection) : 0; + if (len > 0) { + for (int i = 0; i < keyword_help_len && !result; i++) { + if (strcasecmp(selection, keyword_help[i].keyword) == 0) { + result = keyword_help[i].signature; + break; + } + } + } + return result; +} + +static void helpViewClick_event(void *) { + Fl::remove_check(helpViewClick_event); + helpView->anchorClick(); + helpView = NULL; +} + +// post message and return +static void helpViewClick_cb(Fl_Widget *w, void *v) { + helpView = (HelpView *)w; + Fl::add_check(helpViewClick_event); +} + +HelpView::HelpView(Fl_Widget *rect) : + HelpWidget(rect), + _openKeyword(-1), + _openPackage(0) { +} + +HelpView::~HelpView() { +} + +void HelpView::about() { + loadBuffer(aboutText); +} + +// +// anchor link clicked +// +void HelpView::anchorClick() { + const char *target = (const char *)user_data(); + + switch (target[0]) { + case CMD_LEVEL1_OPEN: + for (int i = 0; i < keyword_help_len; i++) { + if (strcasecmp(target + 1, keyword_help[i].package) == 0) { + _openPackage = i; + _openKeyword = i; + break; + } + } + helpIndex(); + break; + + case CMD_LEVEL2_OPEN: + for (int i = 0; i < keyword_help_len; i++) { + if (strcasecmp(target + 1, keyword_help[i].keyword) == 0) { + _openKeyword = i; + break; + } + } + helpIndex(); + break; + + case CMD_MORE: + if (_openKeyword != -1) { + showHelp(keyword_help[_openKeyword].nodeId); + } + break; + } +} + +// +// display help index +// +void HelpView::helpIndex() { + callback(helpViewClick_cb); + + String html; + const char *package = NULL; + + for (int i = 0; i < keyword_help_len; i++) { + if (package == NULL || strcasecmp(package, keyword_help[i].package) != 0) { + package = keyword_help[i].package; + bool bold = (_openPackage != -1 && strcasecmp(keyword_help[_openPackage].package, + keyword_help[i].package) == 0); + if (bold) { + html.append(""); + } + html.append("") + .append(package) + .append(""); + if (strcasecmp("System", package) != 0) { + html.append(" | "); + } + if (bold) { + html.append(""); + } + } + } + + if (_openKeyword != -1) { + // display opened keyword + html.append("

") + .append(keyword_help[_openKeyword].signature) + .append("
") + .append(keyword_help[_openKeyword].help) + .append("
More"); + } + + html.append("

"); + + for (int i = 0; i < keyword_help_len; i++) { + if (_openPackage != -1 && strcasecmp(keyword_help[_openPackage].package, + keyword_help[i].package) == 0) { + if (_openKeyword == i) { + html.append(""); + } + // display opened package + html.append("") + .append(keyword_help[i].keyword) + .append(" | "); + if (_openKeyword == i) { + html.append(""); + } + } + } + + loadBuffer(html); + take_focus(); +} + +bool HelpView::loadHelp(const char *path) { + char localFile[PATH_MAX]; + dev_file_t df; + bool result; + + memset(&df, 0, sizeof(dev_file_t)); + strcpy(df.name, path); + if (http_open(&df) && cacheLink(&df, localFile, sizeof(localFile))) { + loadFile(localFile); + result = true; + } else { + result = false; + } + return result; +} + +void HelpView::showContextHelp(const char *selection) { + int len = selection != NULL ? strlen(selection) : 0; + if (len > 0) { + for (int i = 0; i < keyword_help_len; i++) { + if (strcasecmp(selection, keyword_help[i].keyword) == 0) { + _openPackage = _openKeyword = i; + break; + } + } + } + helpIndex(); +} + +void HelpView::showHelp(const char *nodeId) { + char path[PATH_MAX]; + sprintf(path, "http://smallbasic.github.io/reference/ide/%s.html", nodeId); + loadHelp(path); +} diff --git a/src/platform/fltk/HelpView.h b/src/platform/fltk/HelpView.h new file mode 100644 index 00000000..1b4b7fb6 --- /dev/null +++ b/src/platform/fltk/HelpView.h @@ -0,0 +1,33 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef Fl_HELP_VIEW +#define Fl_HELP_VIEW + +#include "platform/fltk/HelpWidget.h" + +const char *getBriefHelp(const char *selection); + +struct HelpView : public HelpWidget { + HelpView(Fl_Widget *rect); + ~HelpView(); + + void about(); + void anchorClick(); + void helpIndex(); + bool loadHelp(const char *path); + void showContextHelp(const char *selection); + +private: + void showHelp(const char *node); + + int _openKeyword; + int _openPackage; +}; + +#endif diff --git a/src/platform/fltk/HelpWidget.cxx b/src/platform/fltk/HelpWidget.cxx new file mode 100644 index 00000000..9f612832 --- /dev/null +++ b/src/platform/fltk/HelpWidget.cxx @@ -0,0 +1,3059 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define Fl_HELP_WIDGET_RESOURCES +#include "platform/fltk/HelpWidget.h" + +#define FOREGROUND_COLOR fl_rgb_color(0x12, 0x12, 0x12) +#define BACKGROUND_COLOR fl_rgb_color(192, 192, 192) +#define ANCHOR_COLOR fl_rgb_color(0,0,255) +#define BUTTON_COLOR fl_rgb_color(0,0,255) + +#define DEFAULT_INDENT 8 +#define TABLE_PADDING 4 +#define CODE_PADDING 4 +#define LI_INDENT 18 +#define FONT_SIZE_H1 23 +#define CELL_SPACING 4 +#define INPUT_WIDTH 90 +#define BUTTON_WIDTH 20 +#define SCROLL_SIZE 10000 +#define HSCROLL_STEP 20 +#define ELIPSE_LEN 10 +#define IMG_TEXT_BORDER 25 +#define NO_COLOR 0 + +Fl_Color getColor(strlib::String *s, Fl_Color def); +void lineBreak(const char *s, int slen, int width, int &stlen, int &pxlen); +const char *skipWhite(const char *s); +bool unquoteTag(const char *tagBegin, const char *&tagEnd); +Fl_Image *loadImage(const char *imgSrc); +struct AnchorNode; +struct InputNode; +static char truestr[] = "true"; +static char falsestr[] = "false"; +static char spacestr[] = " "; +static char anglestr[] = "<"; +char rangeValue[20]; + +//--Display--------------------------------------------------------------------- + +struct Display { + strlib::String *selection; + Fl_Group *wnd; + AnchorNode *anchor; + Fl_Font font; + Fl_Color color; + Fl_Color background; + Fl_Color selectionColor; + Fl_Color anchorColor; + int16_t x1, x2, y1, y2; + int16_t tabW, tabH; + int16_t lineHeight; + int16_t indent; + int16_t baseIndent; + uint16_t fontSize; + uint16_t tableLevel; + uint16_t nodeId; + int16_t imgY; + uint16_t imgIndent; + int16_t markX, markY, pointX, pointY; + uint8_t uline; + uint8_t center; + uint8_t content; + uint8_t exposed; + uint8_t measure; + uint8_t selected; + uint8_t invertedSel; + uint8_t insideCode; + uint8_t insideUl; + + void drawBackground(Fl_Rect &rc) { + if (background != NO_COLOR) { + Fl_Color oldColor = fl_color(); + fl_color(background); + fl_rectf(rc.x(), rc.y(), rc.w(), rc.h()); + fl_color(oldColor); + } + } + void endImageFlow() { + // end text flow around images + if (imgY != -1) { + indent = imgIndent; + x1 = indent; + y1 = imgY + 1; + imgY = -1; + } + } + + void newRow(uint16_t nrows = 1, bool doBackground = true) { + int bgY = y1; + + x1 = indent; + y1 += nrows *lineHeight; + // flow around images + if (imgY != -1 && y1 > imgY) { + imgY = -1; + x1 = indent = imgIndent; + } + + if (!measure && background != NO_COLOR && doBackground) { + Fl_Rect rc(x1, bgY, x2 - x1 + CELL_SPACING, lineHeight); + drawBackground(rc); + } + } + + // restore previous colors + void restoreColors() { + fl_color(color); + background = oldBackground; + } + + void setColors(Fl_Color nodeForeground, Fl_Color nodeBackground) { + fl_color(nodeForeground != NO_COLOR ? nodeForeground : color); + + oldBackground = background; + if (nodeBackground != NO_COLOR) { + background = nodeBackground; + } + } + +private: + Fl_Color oldBackground; +}; + +//--Attributes------------------------------------------------------------------ + +struct Value { + int value; + bool relative; +}; + +struct Attributes : public Properties { + Attributes(int growSize) : Properties(growSize) { + } + strlib::String *getValue() { + return get("value"); + } + strlib::String *getName() { + return get("name"); + } + strlib::String *getHref() { + return get("href"); + } + strlib::String *getType() { + return get("type"); + } + strlib::String *getSrc() { + return get("src"); + } + strlib::String *getOnclick() { + return get("onclick"); + } + strlib::String *getBgColor() { + return get("bgcolor"); + } + strlib::String *getFgColor() { + return get("fgcolor"); + } + strlib::String *getBackground() { + return get("background"); + } + strlib::String *getAlign() { + return get("align"); + } + bool isReadonly() { + return get("readonly") != 0; + } + void getValue(strlib::String &s) { + s.append(getValue()); + } + void getName(strlib::String &s) { + s.append(getName()); + } + void getHref(strlib::String &s) { + s.append(getHref()); + } + void getType(strlib::String &s) { + s.append(getType()); + } + Value getWidth(int def = -1) { + return getValue("width", def); + } + Value getHeight(int def = -1) { + return getValue("height", def); + } + int getSize(int def = -1) { + return getIntValue("size", def); + } + int getStart(int def = 0) { + return getIntValue("start", def); + } + int getBorder(int def = -1) { + return getIntValue("border", def); + } + int getRows(int def = 1) { + return getIntValue("rows", def); + } + int getCols(int def = 20) { + return getIntValue("cols", def); + } + int getMax(int def = 1) { + return getIntValue("min", def); + } + int getMin(int def = 1) { + return getIntValue("max", def); + } + int getColSpan(int def = 1) { + return getIntValue("colspan", def); + } + int getIntValue(const char *attr, int def); + Value getValue(const char *attr, int def); +}; + +int Attributes::getIntValue(const char *attr, int def) { + strlib::String *s = get(attr); + return (s != NULL ? s->toInteger() : def); +} + +Value Attributes::getValue(const char *attr, int def) { + Value val; + val.relative = false; + strlib::String *s = get(attr); + if (s) { + int ipc = s->indexOf('%', 0); + if (ipc != -1) { + val.relative = true; + } + val.value = s->toInteger(); + } else { + val.value = def; + } + return val; +} + +//--BaseNode-------------------------------------------------------------------- + +struct BaseNode { + virtual ~BaseNode() {} + virtual void display(Display *out) {} + virtual int indexOf(const char *sFind, uint8_t matchCase) { return -1; } + virtual void getText(strlib::String *s) {} + virtual int getY() { return -1; } +}; + +//-- MeasureNode---------------------------------------------------------------- + +struct MeasureNode : public BaseNode { + MeasureNode() : initX(0), initY(0) {} + int16_t initX, initY; +}; + +//--CodeNode-------------------------------------------------------------------- + +struct CodeNode : public MeasureNode { + CodeNode(int fontSize) : + MeasureNode(), + _font(FL_COURIER), + _fontSize(fontSize), + _nodeId(0), + _xEnd(0), + _yEnd(0), + _sameLine(false) { + } + void display(Display *out); + void doEndBlock(Display *out); + Fl_Font _font; + uint16_t _fontSize; + uint16_t _nodeId; + int16_t _xEnd; + int16_t _yEnd; + bool _sameLine; +}; + +void CodeNode::display(Display *out) { + _sameLine = false; + out->endImageFlow(); + fl_font(_font, _fontSize); + + if (out->exposed) { + // defer drawing in child nodes + out->measure = true; + initX = out->x1; + initY = out->y1; + _font = out->font; + _nodeId = out->nodeId; + } else if (!out->measure) { + int textHeight = fl_height() + fl_descent(); + int xpos = initX; + int ypos = (initY - textHeight) + fl_descent() * 2; + int width, height; + if (_yEnd == initY + (CODE_PADDING * 2)) { + _sameLine = true; + width = _xEnd - (CODE_PADDING * 2); + height = out->lineHeight; + } else { + width = out->x2 - (DEFAULT_INDENT * 2); + height = _yEnd - initY + textHeight - fl_descent(); + } + fl_color(fl_lighter(out->color)); + fl_rectf(xpos, ypos, width, height); + fl_color(out->background); + if (!_sameLine) { + fl_rect(xpos, ypos, width, height); + } + } + + out->insideCode = true; + + if (_sameLine) { + out->x1 += CODE_PADDING; + } else { + out->indent += CODE_PADDING; + out->x1 = out->indent; + out->y1 += CODE_PADDING; + } +} + +void CodeNode::doEndBlock(Display *out) { + out->insideCode = false; + + if (!_sameLine) { + out->indent -= CODE_PADDING; + if (out->indent < out->baseIndent) { + out->indent = out->baseIndent; + } + out->y1 += CODE_PADDING; + } + + if (out->exposed) { + out->measure = false; + out->nodeId = _nodeId; + _xEnd = out->x1; + _yEnd = out->y1; + } +} + +struct CodeEndNode : public BaseNode { + CodeEndNode(CodeNode *head) : _head(head) {} + void display(Display *out); + CodeNode *_head; +}; + +void CodeEndNode::display(Display *out) { + fl_color(out->color); + if (_head) { + _head->doEndBlock(out); + } +} + +//--FontNode-------------------------------------------------------------------- + +struct FontNode : public BaseNode { + FontNode(Fl_Font font, int fontSize, Fl_Color color, bool bold, bool italic); + void display(Display *out); + + Fl_Font _font; + Fl_Color _color; + uint16_t _fontSize; +}; + +FontNode::FontNode(Fl_Font font, int fontSize, Fl_Color color, bool bold, bool italic) : + BaseNode(), + _font(font), + _color(color), + _fontSize(fontSize) { + if (bold) { + _font += FL_BOLD; + } + if (italic) { + _font += FL_ITALIC; + } +} + +void FontNode::display(Display *out) { + fl_font(_font, _fontSize); + if (_color == (Fl_Color) - 1) { + fl_color(out->color); // restores color + } else if (_color != 0) { + fl_color(_color); + } + int oldLineHeight = out->lineHeight; + out->lineHeight = fl_height(); + if (out->lineHeight > oldLineHeight) { + out->y1 += (out->lineHeight - oldLineHeight); + } + out->font = _font; + out->fontSize = _fontSize; +} + +//--BrNode---------------------------------------------------------------------- + +struct BrNode : public BaseNode { + BrNode(uint8_t premode) : + BaseNode(), + premode(premode) { + } + + void display(Display *out) { + // when
 is active don't flow text around images
+    if (premode && out->imgY != -1) {
+      out->endImageFlow();
+    } else {
+      out->newRow(1);
+    }
+    out->lineHeight = fl_height() + fl_descent();
+  }
+  uint8_t premode;
+};
+
+//--AnchorNode------------------------------------------------------------------
+
+struct AnchorNode : public BaseNode {
+  AnchorNode(Attributes &p) :
+    BaseNode(),
+    wrapxy(0),
+    pushed(0) {
+    p.getName(name);
+    p.getHref(href);
+  }
+
+  void display(Display *out) {
+    if (pushed) {
+      out->uline = true;
+    }
+    out->anchor = this;
+    x1 = x2 = out->x1;
+    y1 = y2 = out->y1 - out->lineHeight;
+    wrapxy = 0;
+    if (href.length() > 0) {
+      fl_color(out->anchorColor);
+    }
+  }
+
+  bool ptInSegment(int x, int y) {
+    if (y > y1 && y < y2) {
+      // found row
+      if ((x < x1 && y < y1 + lineHeight) ||
+          (x > x2 && y > y2 - lineHeight)) {
+        // outside row start or end
+        return false;
+      }
+      return true;
+    }
+    return false;
+  }
+
+  int getY() {
+    return y1;
+  }
+
+  int16_t x1, x2, y1, y2;
+  uint16_t lineHeight;
+  uint8_t wrapxy;  // begin on page boundary
+  uint8_t pushed;
+  uint8_t __padding[4];
+  strlib::String name;
+  strlib::String href;
+};
+
+AnchorNode *pushedAnchor = 0;
+
+struct AnchorEndNode : public BaseNode {
+  AnchorEndNode() :
+    BaseNode() {
+  }
+
+  void display(Display *out) {
+    AnchorNode *beginNode = out->anchor;
+    if (beginNode) {
+      beginNode->x2 = out->x1;
+      beginNode->y2 = out->y1;
+      beginNode->lineHeight = out->lineHeight;
+    }
+    out->uline = false;
+    out->anchor = 0;
+    fl_color(out->color);
+  }
+};
+
+//--StyleNode-------------------------------------------------------------------
+
+struct StyleNode : public BaseNode {
+  StyleNode(uint8_t uline, uint8_t center) :
+    BaseNode(),
+    uline(uline), center(center) {
+  }
+
+  void display(Display *out) {
+    out->uline = uline;
+    out->center = center;
+  }
+  uint8_t uline;     // 2
+  uint8_t center;    // 2
+};
+
+//--LiNode----------------------------------------------------------------------
+
+struct UlNode : public BaseNode {
+  UlNode(Attributes &p, bool ordered) :
+    BaseNode(),
+    start(p.getStart(1)),
+    ordered(ordered) {
+  }
+  void display(Display *out) {
+    nextId = start;
+    out->insideUl = true;
+    out->newRow(1);
+    out->indent += LI_INDENT;
+  }
+  int nextId;
+  int start;
+  bool ordered;
+};
+
+struct UlEndNode : public BaseNode {
+  UlEndNode() :
+    BaseNode() {
+  }
+  void display(Display *out) {
+    out->insideUl = false;
+    out->indent -= LI_INDENT;
+    out->newRow(1);
+  }
+};
+
+struct LiNode : public BaseNode {
+  LiNode(UlNode *ulNode) :
+    ulNode(ulNode) {
+  }
+
+  void display(Display *out) {
+    out->content = true;
+    out->x1 = out->indent;
+    out->y1 += out->lineHeight;
+    int x = out->x1 - (LI_INDENT - DEFAULT_INDENT);
+    int y = out->y1 - fl_height() - fl_descent();
+    if (out->measure == false) {
+      if (ulNode && ulNode->ordered) {
+        char t[10];
+        sprintf(t, "%d.", ulNode->nextId++);
+        fl_draw(t, 2, x - 4, out->y1);
+      } else {
+        dotImage.draw(x - 4, y + fl_height() - fl_descent(), 8, 8);
+        // draw messes with the current font - restore
+        fl_font(out->font, out->fontSize);
+      }
+    }
+  }
+  UlNode *ulNode;
+};
+
+//--ImageNode-------------------------------------------------------------------
+
+struct ImageNode : public BaseNode {
+  ImageNode(strlib::String *docHome, Attributes *a);
+  ImageNode(strlib::String *docHome, strlib::String *src, bool fixed);
+  ImageNode(Fl_Image *image);
+  void makePath(strlib::String *src, strlib::String *docHome);
+  void reload();
+  void display(Display *out);
+  Fl_Image *image;
+  Value w, h;
+  uint8_t background, fixed;
+  uint8_t valign; // 0=top, 1=center, 2=bottom
+  uint8_t __padding[5];
+  strlib::String path, url;
+};
+
+ImageNode::ImageNode(strlib::String *docHome, Attributes *a) :
+  BaseNode(),
+  background(false),
+  fixed(false),
+  valign(0) {
+  makePath(a->getSrc(), docHome);
+  image = loadImage(path.c_str());
+  w = a->getWidth(image->w());
+  h = a->getHeight(image->h());
+}
+
+ImageNode::ImageNode(strlib::String *docHome, strlib::String *src, bool fixed) :
+  BaseNode(),
+  background(false),
+  fixed(false),
+  valign(0) {
+  makePath(src, docHome);
+  image = loadImage(path.c_str());
+  w.value = image->w();
+  h.value = image->h();
+  w.relative = 0;
+  h.relative = 0;
+}
+
+ImageNode::ImageNode(Fl_Image *image) :
+  BaseNode(),
+  image(image),
+  background(false),
+  fixed(false),
+  valign(2) {
+  w.value = image->w();
+  h.value = image->h();
+  w.relative = 0;
+  h.relative = 0;
+  valign = 2;
+}
+
+void ImageNode::makePath(strlib::String *src, strlib::String *docHome) {
+  // 
+  url.append(src);              // html path
+  path.append(docHome);         // local file system path
+  if (src) {
+    if ((*src)[0] == '/') {
+      path.append(src->substring(1));
+    } else {
+      path.append(src);
+    }
+  }
+}
+
+void ImageNode::reload() {
+  image = loadImage(path.c_str());
+  int iw = image->w();
+  int ih = image->h();
+  if (w.relative == 0) {
+    w.value = iw;
+  }
+  if (h.relative == 0) {
+    h.value = ih;
+  }
+}
+
+void ImageNode::display(Display *out) {
+  if (image == 0) {
+    return;
+  }
+  int iw = w.relative ? (w.value *(out->x2 - out->x1) / 100) : w.value < out->x2 ? w.value : out->x2;
+  int ih = h.relative ? (h.value *(out->wnd->h() - out->y1) / 100) : h.value;
+  if (out->measure == false) {
+    if (background) {
+      // tile image inside rect x,y,tabW,tabH
+      int x = out->x1 - 1;
+      int y = fixed ? 0 : out->y1 - fl_height();
+      int y1 = y;
+      int x1 = x;
+      int numHorz = out->tabW / w.value;
+      int numVert = out->tabH / h.value;
+      for (int iy = 0; iy <= numVert; iy++) {
+        x1 = x;
+        for (int ix = 0; ix <= numHorz; ix++) {
+          if (x1 + w.value > x + out->tabW) {
+            iw = out->tabW - (x1 - x);
+          } else {
+            iw = w.value;
+          }
+          if (y1 + h.value > y + out->tabH) {
+            ih = out->tabH - (y1 - y);
+          } else {
+            ih = h.value;
+          }
+          image->draw(x1, y1, iw, ih);
+          x1 += w.value;
+        }
+        y1 += h.value;
+      }
+    } else {
+      int x = out->x1 + DEFAULT_INDENT;
+      int y = out->y1;
+      switch (valign) {
+      case 0:                  // top
+        y -= fl_height();
+        break;
+      case 1:                  // center
+        break;
+      case 2:                  // bottom
+        break;
+      }
+      if (out->anchor && out->anchor->pushed) {
+        x += 1;
+        y += 1;
+      }
+      image->draw(x, y, iw, ih);
+    }
+  }
+  if (background == 0) {
+    out->content = true;
+    if (iw + IMG_TEXT_BORDER > out->x2) {
+      out->x1 = out->indent;
+      out->y1 += ih;
+      out->imgY = -1;
+    } else {
+      out->imgY = out->y1 + ih;
+      out->imgIndent = out->indent;
+      out->x1 += iw + DEFAULT_INDENT;
+      out->indent = out->x1;
+    }
+  }
+  fl_font(out->font, out->fontSize);    // restore font
+}
+
+//--TextNode--------------------------------------------------------------------
+
+struct TextNode : public BaseNode {
+  TextNode(const char *s, uint16_t textlen);
+  void display(Display *out);
+  void drawSelection(const char *s, uint16_t len, uint16_t width, Display *out);
+  int indexOf(const char *sFind, uint8_t matchCase);
+  void getText(strlib::String *s);
+  int getY();
+
+  const char *s;
+  uint16_t textlen;
+  uint16_t width;
+  int16_t ybegin;
+};
+
+TextNode::TextNode(const char *s, uint16_t textlen) :
+  BaseNode(),
+  s(s),
+  textlen(textlen),
+  width(0),
+  ybegin(0) {
+}
+
+void TextNode::getText(strlib::String *s) {
+  s->append(this->s, this->textlen);
+}
+
+void TextNode::drawSelection(const char *s, uint16_t len, uint16_t width, Display *out) {
+  int out_y = out->y1 - fl_height();
+  if (out->pointY < out_y) {
+    return;                     // selection above text
+  }
+  if (out->markY > out_y + out->lineHeight) {
+    return;                     // selection below text
+  }
+
+  Fl_Rect rc(out->x1, out_y, width, out->lineHeight + fl_descent());
+  int selBegin = 0;             // selection index into the draw string
+  int selEnd = len;
+
+  if (out->markY > out_y) {
+    if (out->pointY < out_y + out->lineHeight) {
+      // paint single row selection
+      int16_t leftX, rightX;
+      if (out->markX < out->pointX) {
+        leftX = out->markX;
+        rightX = out->pointX;
+      } else {                  // invert selection
+        leftX = out->pointX;
+        rightX = out->markX;
+      }
+
+      if (leftX > out->x1 + width || rightX < out->x1) {
+        return;                 // selection left or right of text
+      }
+
+      bool left = true;
+      int x = out->x1;
+      // find the left+right margins
+      if (leftX == rightX) {
+        // double click - select word
+        for (int i = 0; i < len; i++) {
+          int width = fl_width(s + i, 1);
+          x += width;
+          if (left) {
+            if (s[i] == ' ') {
+              rc.x(x);
+              selBegin = i + 1;
+            }
+            if (x > leftX) {
+              left = false;
+            }
+          } else if (s[i] == ' ' && x > rightX) {
+            rc.w(x - rc.x() - width);
+            selEnd = i;
+            break;
+          }
+        }
+      } else {
+        // drag row - draw around character boundary
+        for (int i = 0; i < len; i++) {
+          x += fl_width(s + i, 1);
+          if (left) {
+            if (x < leftX) {
+              rc.x(x);
+              selBegin = i + 1;
+            } else {
+              left = false;
+            }
+          } else if (x > rightX) {
+            rc.w(x - rc.x());
+            selEnd = i + 1;
+            break;
+          }
+        }
+      }
+    } else {
+      // top row multiline - find the left margin
+      int16_t leftX = out->invertedSel ? out->pointX : out->markX;
+      if (leftX > out->x1 + width) {
+        // selection left of text
+        return;
+      }
+      int x = out->x1;
+      int char_w = 0;
+      for (int i = 0; i < len; i++) {
+        char_w = fl_width(s + i, 1);
+        x += char_w;
+        if (x < leftX) {
+          rc.x(x);
+          selBegin = i;
+        } else {
+          break;
+        }
+      }
+      // subtract the left non-selected segement length from the right side
+      rc.w(rc.w() - (x - out->x1) + char_w);
+    }
+  } else {
+    if (out->pointY < out_y + out->lineHeight) {
+      // bottom row multiline - find the right margin
+      int16_t rightX = out->invertedSel ? out->markX : out->pointX;
+      if (rightX < out->x1) {
+        return;
+      }
+      int x = out->x1;
+      for (int i = 0; i < len; i++) {
+        x += fl_width(s + i, 1);
+        if (x > rightX) {
+          rc.w(x - rc.x());
+          selEnd = i + 1;
+          break;
+        }
+      }
+    }
+    // else middle row multiline - fill left+right
+  }
+
+  if (selEnd > selBegin && out->selection != 0) {
+    // capture the selected text
+    out->selection->append(s + selBegin, selEnd - selBegin);
+  }
+
+  fl_color(out->selectionColor);
+  fl_rectf(rc.x(), rc.y(), rc.w(), rc.h());
+  fl_color(out->color);
+}
+
+void TextNode::display(Display *out) {
+  ybegin = out->y1;
+  out->content = true;
+
+  if (width == 0) {
+    width = fl_width(s, textlen);
+  }
+  if (width < out->x2 - out->x1) {
+    // simple non-wrapping textout
+    if (out->center) {
+      int xctr = ((out->x2 - out->x1) - width) / 2;
+      if (xctr > out->x1) {
+        out->x1 = xctr;
+      }
+    }
+    if (out->measure == false) {
+      if (out->selected) {
+        drawSelection(s, textlen, width, out);
+      }
+      fl_draw(s, textlen, out->x1, out->y1);
+      if (out->uline) {
+        fl_line(out->x1, out->y1 + 1, out->x1 + width, out->y1 + 1);
+      }
+    }
+    out->x1 += width;
+  } else {
+    int linelen, linepx, cliplen;
+    int len = textlen;
+    const char *p = s;
+    while (len > 0) {
+      lineBreak(p, len, out->x2 - out->x1, linelen, linepx);
+      cliplen = linelen;
+      if (linepx > out->x2 - out->x1) {
+        // no break point - create new line if not already on one
+        if (out->x1 != out->indent) {
+          out->newRow();
+          // anchor now starts on a new line
+          if (out->anchor && out->anchor->wrapxy == false) {
+            out->anchor->x1 = out->x1;
+            out->anchor->y1 = out->y1 - out->lineHeight;
+            out->anchor->wrapxy = true;
+          }
+        }
+        // clip long text - leave room for elipses
+        int cellW = out->x2 - out->indent - ELIPSE_LEN;
+        if (linepx > cellW) {
+          linepx = 0;
+          cliplen = 0;
+          do {
+            linepx += fl_width(p + cliplen, 1);
+            cliplen++;
+          }
+          while (linepx < cellW);
+        }
+      }
+      if (out->measure == false) {
+        if (out->selected) {
+          drawSelection(p, cliplen, linepx, out);
+        }
+        fl_draw(p, cliplen, out->x1, out->y1);
+        if (out->uline) {
+          fl_line(out->x1, out->y1 + 1, out->x1 + linepx, out->y1 + 1);
+        }
+        if (cliplen != linelen) {
+          fl_point(out->x1 + linepx, out->y1);
+          fl_point(out->x1 + linepx + 2, out->y1);
+          fl_point(out->x1 + linepx + 4, out->y1);
+        }
+      }
+      p += linelen;
+      len -= linelen;
+
+      if (out->anchor) {
+        out->anchor->wrapxy = true;
+      }
+      if (out->x1 + linepx < out->x2) {
+        out->x1 += linepx;
+      } else {
+        out->newRow();
+      }
+    }
+  }
+}
+
+int TextNode::indexOf(const char *sFind, uint8_t matchCase) {
+  int numMatch = 0;
+  int findLen = strlen(sFind);
+  for (int i = 0; i < textlen; i++) {
+    uint8_t equals = matchCase ? s[i] == sFind[numMatch] : toupper(s[i]) == toupper(sFind[numMatch]);
+    numMatch = (equals ? numMatch + 1 : 0);
+    if (numMatch == findLen) {
+      return i + 1;
+    }
+  }
+  return -1;
+}
+
+int TextNode::getY() {
+  return ybegin;
+}
+
+//--HrNode----------------------------------------------------------------------
+
+struct HrNode : public BaseNode {
+  HrNode() :
+    BaseNode() {
+  }
+  void display(Display *out) {
+    if (out->imgY != -1) {
+      out->endImageFlow();
+      out->y1 -= out->lineHeight;
+    }
+    out->y1 += 4;
+    out->x1 = out->indent;
+    if (out->measure == false) {
+      fl_color(FL_DARK1);
+      fl_line(out->x1, out->y1 + 1, out->x2 - 6, out->y1 + 1);
+      fl_color(FL_DARK2);
+      fl_line(out->x1, out->y1 + 2, out->x2 - 6, out->y1 + 2);
+      fl_color(out->color);
+    }
+    out->y1 += out->lineHeight + 2;
+  }
+};
+
+//--ParagraphNode---------------------------------------------------------------
+
+struct ParagraphNode : public BaseNode {
+  ParagraphNode() : BaseNode() {}
+  void display(Display *out) {
+    if (out->imgY != -1) {
+      out->endImageFlow();
+    } else if (!out->insideCode && !out->insideUl) {
+      out->newRow(2);
+    }
+  }
+};
+
+//--Table Support---------------------------------------------------------------
+
+struct TableNode;
+
+struct TrNode : public BaseNode {
+  TrNode(TableNode *tableNode, Attributes *a);
+  void display(Display *out);
+
+  TableNode *table;
+  Fl_Color background, foreground;
+  uint16_t cols, __padding1;
+  int16_t y1, height;
+};
+
+struct TrEndNode : public BaseNode {
+  TrEndNode(TrNode *trNode);
+  void display(Display *out);
+
+  TrNode *tr;
+};
+
+struct TdNode : public BaseNode {
+  TdNode(TrNode *trNode, Attributes *a);
+  void display(Display *out);
+
+  TrNode *tr;
+  Value width;
+  Fl_Color background, foreground;
+  uint16_t colspan;
+};
+
+struct TdEndNode : public BaseNode {
+  TdEndNode(TdNode *tdNode);
+  void display(Display *out);
+
+  TdNode *td;
+};
+
+struct TableNode : public MeasureNode {
+  TableNode(Attributes *a);
+  ~TableNode();
+  void display(Display *out);
+  void doEndTD(Display *out, TrNode *tr, Value *tdWidth);
+  void doEndTable(Display *out);
+  void setColWidth(Value *width);
+  void cleanup();
+
+  uint16_t *columns;
+  int16_t *sizes;
+  uint16_t rows, cols;
+  uint16_t nextCol;
+  uint16_t nextRow;
+  uint16_t width;
+  uint16_t nodeId;
+  int16_t maxY;                     // end of table
+  int16_t border;
+};
+
+struct TableEndNode : public BaseNode {
+  TableEndNode(TableNode *tableNode);
+  void display(Display *out);
+
+  TableNode *table;
+};
+
+//--TableNode-------------------------------------------------------------------
+
+TableNode::TableNode(Attributes *a) :
+  MeasureNode(),
+  columns(0),
+  sizes(0),
+  rows(0),
+  cols(0) {
+  border = a->getBorder();
+}
+
+TableNode::~TableNode() {
+  cleanup();
+}
+
+void TableNode::display(Display *out) {
+  nextCol = 0;
+  nextRow = 0;
+  out->endImageFlow();
+  width = out->x2 - out->indent;
+  initX = out->indent;
+  initY = maxY = out->y1;
+  nodeId = out->nodeId;
+
+  if (out->content) {
+    // update table initial row position- we remember content
+    // state on re-visiting the table via the value of initY
+    initY += out->lineHeight + CELL_SPACING;
+    maxY = initY;
+  }
+  out->content = false;
+
+  if (cols && (out->exposed || columns == 0)) {
+    // prepare default column widths
+    if (out->tableLevel == 0) {
+      // traverse the table structure to determine widths
+      out->measure = true;
+    }
+    cleanup();
+    columns = (uint16_t *)malloc(sizeof(uint16_t) * cols);
+    sizes = (int16_t *)malloc(sizeof(int16_t) * cols);
+    int cellW = width / cols;
+    for (int i = 0; i < cols; i++) {
+      columns[i] = cellW * (i + 1);
+      sizes[i] = 0;
+    }
+  }
+  int lineHeight = fl_height() + fl_descent();
+  if (lineHeight > out->lineHeight) {
+    out->lineHeight = lineHeight;
+  }
+  out->tableLevel++;
+}
+
+// called from  to prepare for wrapping and resizing
+void TableNode::doEndTD(Display *out, TrNode *tr, Value *tdWidth) {
+  int index = nextCol - 1;
+  if (out->y1 > maxY || tdWidth->value != -1) {
+    // veto column changes - wrapped or fixed-width cell
+    sizes[index] = -1;
+  } else if (out->y1 == tr->y1 && out->x1 < columns[index] && out->x1 > sizes[index] && sizes[index] != -1) {
+    // largest  on same line, less than the default width
+    // add CELL_SPACING*2 since  reduces width by CELL_SPACING
+    sizes[index] = out->x1 + (CELL_SPACING * 3);
+  }
+
+  if (out->y1 > maxY) {
+    maxY = out->y1;             // new max table height
+  }
+  // close image flow to prevent bleeding into previous cell
+  out->imgY = -1;
+}
+
+void TableNode::doEndTable(Display *out) {
+  out->x2 = width;
+  out->indent = initX;
+  out->x1 = initX;
+  out->y1 = maxY;
+  if (out->content) {
+    out->newRow(1, false);
+  }
+  out->content = false;
+  out->tableLevel--;
+  out->tabH = out->y1 - initY;
+  out->tabW = width;
+
+  if (cols && columns && out->exposed) {
+    // adjust columns for best fit (left align)
+    int delta = 0;
+    for (int i = 0; i < cols - 1; i++) {
+      if (sizes[i] > 0) {
+        int spacing = columns[i] - sizes[i];
+        columns[i] = sizes[i] - delta;
+        delta += spacing;
+      } else {
+        // apply delta only to wrapped column
+        columns[i] -= delta;
+      }
+    }
+    // redraw outer tables
+    if (out->tableLevel == 0) {
+      out->measure = false;
+      out->nodeId = nodeId;
+    }
+  }
+}
+
+void TableNode::setColWidth(Value *colw) {
+  // set user specified column width
+  int tdw = colw->relative ? colw->value * width / 100 : colw->value < width ? colw->value : width;
+  int delta = columns[nextCol] - tdw;
+  columns[nextCol] = tdw;
+  for (int i = nextCol + 1; i < cols - 1; i++) {
+    columns[i] -= delta;
+  }
+}
+
+void TableNode::cleanup() {
+  if (columns) {
+    free(columns);
+  }
+  if (sizes) {
+    free(sizes);
+  }
+}
+
+TableEndNode::TableEndNode(TableNode *tableNode) :
+  BaseNode(),
+  table(tableNode) {
+}
+
+void TableEndNode::display(Display *out) {
+  if (table) {
+    table->doEndTable(out);
+  }
+}
+
+//--TrNode----------------------------------------------------------------------
+
+TrNode::TrNode(TableNode *tableNode, Attributes *a) :
+  BaseNode(),
+  table(tableNode),
+  cols(0),
+  y1(0),
+  height(0) {
+  if (table) {
+    table->rows++;
+  }
+  foreground = getColor(a->getFgColor(), NO_COLOR);
+  background = getColor(a->getBgColor(), NO_COLOR);
+}
+
+void TrNode::display(Display *out) {
+  out->setColors(foreground, background);
+
+  if (table == 0) {
+    return;
+  }
+
+  if (out->content) {
+    // move bottom of  to next line
+    table->maxY += out->lineHeight + TABLE_PADDING;
+  }
+  out->content = false;
+  y1 = table->maxY;
+  table->nextCol = 0;
+  table->nextRow++;
+
+  if (background && out->measure == false) {
+    Fl_Rect rc(table->initX, y1 - fl_height(), table->width, out->lineHeight);
+    out->drawBackground(rc);
+  }
+}
+
+TrEndNode::TrEndNode(TrNode *trNode) :
+  BaseNode(),
+  tr(trNode) {
+  if (tr && tr->table && tr->cols > tr->table->cols) {
+    tr->table->cols = tr->cols;
+  }
+}
+
+void TrEndNode::display(Display *out) {
+  out->restoreColors();
+
+  if (tr && tr->table) {
+    tr->height = tr->table->maxY - tr->y1 + out->lineHeight;
+  }
+}
+
+//--TdNode----------------------------------------------------------------------
+
+TdNode::TdNode(TrNode *trNode, Attributes *a) :
+  BaseNode(),
+  tr(trNode) {
+  if (tr) {
+    tr->cols++;
+  }
+  foreground = getColor(a->getFgColor(), NO_COLOR);
+  background = getColor(a->getBgColor(), NO_COLOR);
+  width = a->getWidth();
+  colspan = a->getColSpan(1) - 1;       // count 1 for each additional col
+}
+
+void TdNode::display(Display *out) {
+  out->setColors(foreground, background);
+
+  if (tr == 0 || tr->table == 0 || tr->table->cols == 0) {
+    return;                     // invalid table model
+  }
+
+  TableNode *table = tr->table;
+  if (out->measure && table->nextRow == 1 && width.value != -1) {
+    table->setColWidth(&width);
+  }
+
+  out->x1 = table->initX + TABLE_PADDING + (table->nextCol == 0 ? 0 : table->columns[table->nextCol - 1]);
+  out->y1 = tr->y1;             // top+left of next cell
+
+  // adjust for colspan attribute
+  if (colspan) {
+    table->nextCol += colspan;
+  }
+  if (table->nextCol > table->cols - 1) {
+    table->nextCol = table->cols - 1;
+  }
+
+  out->indent = out->x1;
+  out->x2 = out->x1 + table->columns[table->nextCol] - CELL_SPACING;
+  if (out->x2 > out->tabW) {
+    out->x2 = out->tabW - CELL_SPACING; // stay within table bounds
+  }
+  table->nextCol++;
+
+  if (out->measure == false) {
+    Fl_Rect rc(out->indent - CELL_SPACING,
+               tr->y1 - fl_height() + fl_descent(),
+               out->x2 - out->indent + (CELL_SPACING * 2),
+               out->lineHeight);
+    out->drawBackground(rc);
+    if (table->border > 0) {
+      Fl_Color oldColor = fl_color();
+      fl_color(FL_BLACK);
+      fl_overlay_rect(rc.x(), rc.y(), rc.w(), rc.h());
+      fl_color(oldColor);
+    }
+  }
+
+}
+
+TdEndNode::TdEndNode(TdNode *tdNode) :
+  BaseNode(),
+  td(tdNode) {
+}
+
+void TdEndNode::display(Display *out) {
+  out->restoreColors();
+
+  if (td && td->tr && td->tr->table) {
+    td->tr->table->doEndTD(out, td->tr, &td->width);
+  }
+}
+
+//--NamedInput------------------------------------------------------------------
+
+struct NamedInput {
+  NamedInput(InputNode *node, strlib::String *name) {
+    this->input = node;
+    this->name.append(name->c_str());
+  }
+  ~NamedInput() {
+  }
+  InputNode *input;
+  strlib::String name;
+};
+
+//--InputNode-------------------------------------------------------------------
+
+static void onclick_callback(Fl_Widget *button, void *buttonId) {
+  ((HelpWidget *)button->parent())->onclick(button);
+}
+
+static void def_button_callback(Fl_Widget *button, void *buttonId) {
+  // supply "onclick=fff" to make it do something useful
+  // check for parent of HelpWidget
+  if (Fl::modal() == (Fl_Window *)button->parent()->parent()) {
+    Fl::modal()->set_non_modal();
+  }
+}
+
+struct InputNode : public BaseNode {
+  InputNode(Fl_Group *parent);
+  InputNode(Fl_Group *parent, Attributes *a, const char *v, int len);
+  InputNode(Fl_Group *parent, Attributes *a);
+  void update(strlib::List *namedInputs, Properties *p, Attributes *a);
+  void display(Display *out);
+
+  Fl_Widget *button;
+  uint32_t rows, cols;
+  strlib::String onclick;
+};
+
+// creates either a text, checkbox, radio, hidden or button control
+InputNode::InputNode(Fl_Group *parent, Attributes *a) :
+  BaseNode() {
+  parent->begin();
+  strlib::String *type = a->getType();
+  if (type != NULL && type->equals("text")) {
+    button = new Fl_Input(0, 0, INPUT_WIDTH, 0);
+    button->argument(ID_TEXTBOX);
+  } else if (type != NULL && type->equals("readonly")) {
+    button = new Fl_Input(0, 0, INPUT_WIDTH, 0);
+    button->argument(ID_READONLY);
+  } else if (type != NULL && type->equals("checkbox")) {
+    button = new Fl_Check_Button(0, 0, BUTTON_WIDTH, 0);
+    button->argument(ID_CHKBOX);
+  } else if (type != NULL && type->equals("radio")) {
+    button = new Fl_Radio_Button(0, 0, BUTTON_WIDTH, 0);
+    button->argument(ID_RADIO);
+  } else if (type != NULL && type->equals("slider")) {
+    button = new Fl_Slider(0, 0, BUTTON_WIDTH, 0);
+    button->argument(ID_RANGEVAL);
+  } else if (type != NULL && type->equals("valueinput")) {
+    button = new Fl_Value_Input(0, 0, BUTTON_WIDTH, 0);
+    button->argument(ID_RANGEVAL);
+  } else if (type != NULL && type->equals("hidden")) {
+    button = new Fl_Input(0, 0, 0, 0);
+    button->argument(ID_HIDDEN);
+  } else {
+    button = new Fl_Button(0, 0, 0, 0);
+    button->argument(ID_BUTTON);
+    button->callback(def_button_callback);
+  }
+  parent->end();
+}
+
+InputNode::InputNode(Fl_Group *parent, Attributes *a, const char *s, int len) :
+  BaseNode() {
+  // creates a textarea control
+  parent->begin();
+  if (a->isReadonly()) {
+    strlib::String str;
+    str.append(s, len);
+    button = new Fl_Input(0, 0, INPUT_WIDTH, 0);
+    button->argument(ID_READONLY);
+    button->copy_label(str.c_str());
+  } else {
+    button = new Fl_Input(0, 0, INPUT_WIDTH, 0);
+    button->argument(ID_TEXTAREA);
+    ((Fl_Input *)button)->value(s, len);
+  }
+  parent->end();
+}
+
+InputNode::InputNode(Fl_Group *parent) :
+  BaseNode() {
+  // creates a select control
+  parent->begin();
+  button = new Fl_Choice(0, 0, INPUT_WIDTH, 0);
+  button->argument(ID_SELECT);
+  parent->end();
+}
+
+void createDropList(InputNode *node, strlib::List *options) {
+  Fl_Choice *menu = (Fl_Choice *)node->button;
+  List_each(String *, it, *options) {
+    String *s = (*it);
+    menu->add(s->c_str());
+  }
+}
+
+void InputNode::update(strlib::List *names, Properties *env, Attributes *a) {
+  Fl_Valuator *valuator;
+  Fl_Input *input;
+  Fl_Color color;
+  strlib::String *name = a->getName();
+  strlib::String *value = a->getValue();
+  strlib::String *align = a->getAlign();
+
+  if (name != NULL) {
+    names->add(new NamedInput(this, name));
+  }
+
+  if (button == 0) {
+    return;
+  }
+  // value uses environment/external attributes
+  if (value == 0 && name != 0 && env) {
+    value = env->get(name->c_str());
+  }
+
+  switch (button->argument()) {
+  case ID_READONLY:
+    button->align(FL_ALIGN_LEFT | FL_ALIGN_CLIP);
+    if (value && value->length()) {
+      button->copy_label(value->c_str());
+    }
+    // fallthru
+  case ID_TEXTAREA:
+    button->box(FL_NO_BOX);
+    rows = a->getRows();
+    cols = a->getCols();
+    if (rows > 1) {
+      button->type(FL_MULTILINE_INPUT);
+    }
+    break;
+  case ID_RANGEVAL:
+    valuator = (Fl_Valuator *)button;
+    valuator->minimum(a->getMin());
+    valuator->maximum(a->getMax());
+    valuator->step(1);
+    valuator->align(FL_ALIGN_LEFT);
+    if (value && value->length()) {
+      valuator->value(value->toInteger());
+    }
+    break;
+  case ID_TEXTBOX:
+    button->box(FL_NO_BOX);
+    input = (Fl_Input *)button;
+    if (value && value->length()) {
+      input->value(value->c_str());
+    }
+    break;
+  case ID_BUTTON:
+    if (value && value->length()) {
+      button->copy_label(value->c_str());
+    } else {
+      button->copy_label(" ");
+    }
+    break;
+  case ID_HIDDEN:
+    if (value && value->length()) {
+      button->copy_label(value->c_str());
+    }
+    break;
+  }
+
+  // size
+  int size = a->getSize();
+  if (size != -1) {
+    button->size(size, button->h());
+  }
+  // set callback
+  onclick.append(a->getOnclick());
+  if (onclick.length()) {
+    button->callback(onclick_callback);
+  }
+  // set colors
+  color = getColor(a->getBgColor(), 0);
+  if (color) {
+    button->color(color);       // background
+  } else {
+    button->color(BUTTON_COLOR);
+  }
+  color = getColor(a->getFgColor(), 0);
+  if (color) {
+    button->labelcolor(color);  // foreground
+    button->color(color);
+  } else {
+    button->labelcolor(ANCHOR_COLOR);
+    button->color(ANCHOR_COLOR);
+  }
+
+  // set alignment
+  if (align != 0) {
+    if (align->equals("right")) {
+      button->align(FL_ALIGN_RIGHT | FL_ALIGN_CLIP);
+    } else if (align->equals("center")) {
+      button->align(FL_ALIGN_CENTER | FL_ALIGN_CLIP);
+    } else if (align->equals("top")) {
+      button->align(FL_ALIGN_TOP | FL_ALIGN_CLIP);
+    } else if (align->equals("bottom")) {
+      button->align(FL_ALIGN_BOTTOM | FL_ALIGN_CLIP);
+    } else {
+      button->align(FL_ALIGN_LEFT | FL_ALIGN_CLIP);
+    }
+  }
+  // set border
+  switch (a->getBorder(0)) {
+  case 1:
+    button->box(FL_BORDER_BOX);
+    break;
+  case 2:
+    button->box(FL_SHADOW_BOX);
+    break;
+  case 3:
+    button->box(FL_ENGRAVED_BOX);
+    break;
+  case 4:
+    button->box(FL_THIN_DOWN_BOX);
+    break;
+  }
+}
+
+void InputNode::display(Display *out) {
+  if (button == 0 || ID_HIDDEN == button->argument()) {
+    return;
+  }
+
+  int height = 4 + fl_height() + fl_descent();
+  switch (button->argument()) {
+  case ID_SELECT:
+    height += 4;
+    break;
+  case ID_BUTTON:
+    if (button->w() == 0 && button->label()) {
+      button->size(12 + fl_width(button->label()), button->h());
+    }
+    break;
+  case ID_TEXTAREA:
+  case ID_READONLY:
+    button->size(4 + (fl_width("$") * cols), button->h());
+    height = 4 + (fl_height() + fl_descent() * rows);
+    break;
+  default:
+    break;
+  }
+  if (out->x1 != out->indent && button->w() > out->x2 - out->x1) {
+    out->newRow();
+  }
+  out->lineHeight = height;
+  button->resize(out->x1, out->y1 - fl_height(), button->w(), out->lineHeight - 2);
+  button->labelfont(out->font);
+  button->labelsize(out->fontSize);
+  if (button->y() + button->h() < out->y2 && button->y() >= 0) {
+    button->clear_visible();
+  } else {
+    // draw a fake control in case partially visible
+    fl_color(button->color());
+    fl_rectf(button->x(), button->y(), button->w(), button->h());
+    fl_color(out->color);
+  }
+  out->x1 += button->w();
+  out->content = true;
+}
+
+//--EnvNode---------------------------------------------------------------------
+
+struct EnvNode : public TextNode {
+  EnvNode(Properties *p, const char *s, uint16_t textlen) :
+    TextNode(0, 0) {
+    strlib::String var;
+    var.append(s, textlen);
+    var.trim();
+    if (p) {
+      strlib::String *s = p->get(var.c_str());
+      value.append(s);
+    }
+    if (value.length() == 0) {
+      value.append(getenv(var.c_str()));
+    }
+    this->s = value.c_str();
+    this->textlen = value.length();
+  }
+  // here to provide value cleanup
+  strlib::String value;
+};
+
+//--HelpWidget------------------------------------------------------------------
+
+static void scrollbar_callback(Fl_Widget *scrollBar, void *helpWidget) {
+  ((HelpWidget *)helpWidget)->redraw();
+}
+
+static void anchor_callback(Fl_Widget *helpWidget, void *target) {
+  ((HelpWidget *)helpWidget)->navigateTo((const char *)target);
+}
+
+HelpWidget::HelpWidget(Fl_Widget *rect, int defsize) :
+  Fl_Group(rect->x(), rect->y(), rect->w(), rect->h()),
+  background(BACKGROUND_COLOR),
+  foreground(FOREGROUND_COLOR),
+  scrollHeight(0),
+  scrollWindowHeight(0),
+  markX(0),
+  markY(0),
+  pointX(0),
+  pointY(0),
+  hscroll(0),
+  scrollY(0),
+  mouseMode(mm_select),
+  nodeList(100),
+  namedInputs(5),
+  inputs(5),
+  anchors(5),
+  images(5),
+  cookies(NULL) {
+  begin();
+  scrollbar = new Fl_Scrollbar(rect->w() - SCROLL_X, rect->y(), SCROLL_W, rect->h());
+  scrollbar->type(FL_VERTICAL);
+  scrollbar->value(0, 1, 0, SCROLL_SIZE);
+  scrollbar->user_data(this);
+  scrollbar->callback(scrollbar_callback);
+  scrollbar->deactivate();
+  scrollbar->show();
+  end();
+  callback(anchor_callback);    // default callback
+  init();
+  docHome.clear();
+  labelsize(defsize);
+}
+
+HelpWidget::~HelpWidget() {
+  cleanup();
+}
+
+void HelpWidget::init() {
+  scrollHeight = 0;
+  scrollWindowHeight = 0;
+  hscroll = 0;
+  endSelection();
+  scrollbar->value(0);
+}
+
+void HelpWidget::setTheme(EditTheme *theme) {
+  background = get_color(theme->_background);
+  foreground = get_color(theme->_color);
+  selection_color(get_color(theme->_selection_color));
+  labelcolor(get_color(theme->_cursor_color));
+}
+
+void HelpWidget::endSelection() {
+  markX = pointX = -1;
+  markY = pointY = -1;
+  selection.clear();
+}
+
+void HelpWidget::setFontSize(int i) {
+  labelsize(i);
+  reloadPage();
+}
+
+void HelpWidget::cleanup() {
+  List_each(InputNode *, it, inputs) {
+    InputNode *p = (*it);
+    if (p->button) {
+      remove(p->button);
+      delete p->button;
+      p->button = 0;
+    }
+  }
+
+  // button/anchor items destroyed in nodeList
+  inputs.clear();
+  anchors.clear();
+  images.clear();
+  nodeList.removeAll();
+  namedInputs.removeAll();
+  title.clear();
+}
+
+void HelpWidget::reloadPage() {
+  cleanup();
+  init();
+  compile();
+  redraw();
+  pushedAnchor = 0;
+}
+
+// returns the control with the given name
+Fl_Widget *HelpWidget::getInput(const char *name) {
+  List_each(NamedInput *, it, namedInputs) {
+    NamedInput *ni = (*it);
+    if (ni->name.equals(name)) {
+      return ni->input->button;
+    }
+  }
+  return NULL;
+}
+
+// return the value of the given control
+const char *HelpWidget::getInputValue(Fl_Widget *widget) {
+  if (widget == 0) {
+    return NULL;
+  }
+  switch (widget->argument()) {
+  case ID_TEXTBOX:
+  case ID_TEXTAREA:
+    return ((Fl_Input *)widget)->value();
+  case ID_RADIO:
+  case ID_CHKBOX:
+    return ((Fl_Radio_Button *)widget)->value() ? truestr : falsestr;
+  case ID_SELECT:
+    return NULL;
+  case ID_RANGEVAL:
+    sprintf(rangeValue, "%f", ((Fl_Valuator *)widget)->value());
+    return rangeValue;
+  case ID_HIDDEN:
+  case ID_READONLY:
+    return widget->label();
+  }
+  return NULL;
+}
+
+// return the nth form value
+const char *HelpWidget::getInputValue(int i) {
+  int len = namedInputs.size();
+  if (i < len) {
+    NamedInput *ni = namedInputs[i];
+    return getInputValue(ni->input->button);
+  }
+  return 0;
+}
+
+// return the name of the given button control
+const char *HelpWidget::getInputName(Fl_Widget *button) {
+  List_each(NamedInput *, it, namedInputs) {
+    NamedInput *ni = (*it);
+    if (ni->input->button == button) {
+      return ni->name.c_str();
+    }
+  }
+  return NULL;
+}
+
+// return all of the forms names and values - except hidden ones
+void HelpWidget::getInputProperties(Properties *p) {
+  if (p != 0) {
+    List_each(NamedInput *, it, namedInputs) {
+      NamedInput *ni = (*it);
+      const char *value = getInputValue(ni->input->button);
+      if (value) {
+        p->put(ni->name.c_str(), value);
+      }
+    }
+  }
+}
+
+// update a widget's display value using the given string based
+// assignment statement, eg val=1000
+bool HelpWidget::setInputValue(const char *assignment) {
+  strlib::String s = assignment;
+  strlib::String name = s.leftOf('=');
+  strlib::String value = s.rightOf('=');
+  if (value.length() == 0) {
+    return false;
+  }
+
+  List_each(NamedInput *, it, namedInputs) {
+    NamedInput *ni = (*it);
+    if (ni->name.equals(name)) {
+      Fl_Widget *button = ni->input->button;
+
+      switch (button->argument()) {
+      case ID_TEXTBOX:
+      case ID_TEXTAREA:
+        ((Fl_Input *)button)->value(value.c_str());
+        break;
+      case ID_RADIO:
+      case ID_CHKBOX:
+        ((Fl_Radio_Button *)button)->value(value.equals(truestr) || value.equals("1"));
+        break;
+      case ID_SELECT:
+        break;
+      case ID_RANGEVAL:
+        ((Fl_Valuator *)button)->value(value.toNumber());
+        break;
+      case ID_READONLY:
+        button->copy_label(value.c_str());
+        break;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+void HelpWidget::scrollTo(const char *anchorName) {
+  List_each(AnchorNode *, it, anchors) {
+    AnchorNode *p = (*it);
+    if (p->name.equals(anchorName)) {
+      if (p->getY() > scrollHeight) {
+        vscroll(-scrollHeight);
+      } else {
+        vscroll(-p->getY());
+      }
+      redraw();
+      return;
+    }
+  }
+}
+
+void HelpWidget::resize(int x, int y, int w, int h) {
+  Fl_Group::resize(x, y, w, h);
+  scrollbar->resize(w - SCROLL_X, y, SCROLL_W, h);
+  endSelection();
+}
+
+void HelpWidget::draw() {
+  int vscroll = -scrollbar->value();
+  Display out;
+  out.uline = false;
+  out.center = false;
+  out.wnd = this;
+  out.anchor = 0;
+  out.font = FL_HELVETICA;
+  out.fontSize = (int)labelsize();
+  out.color = foreground;
+  out.background = background;
+  out.selectionColor = selection_color();
+  out.anchorColor = labelcolor();
+  out.y2 = h();
+  out.indent = DEFAULT_INDENT + hscroll;
+  out.baseIndent = out.indent;
+  out.x1 = x() + out.indent;
+  out.x2 = w() - (DEFAULT_INDENT * 2) + hscroll;
+  out.content = false;
+  out.measure = false;
+  out.exposed = exposed();
+  out.tableLevel = 0;
+  out.imgY = -1;
+  out.imgIndent = out.indent;
+  out.tabW = out.x2;
+  out.tabH = out.y2;
+  out.selection = 0;
+  out.selected = (markX != pointX || markY != pointY);
+  out.insideCode = false;
+  out.insideUl = false;
+
+  if (Fl::event_clicks() == 1 && damage() == DAMAGE_HIGHLIGHT) {
+    // double click
+    out.selected = true;
+  }
+  if (out.selected) {
+    out.markX = markX;
+    out.pointX = pointX;
+    if (markY < pointY) {
+      out.markY = markY;
+      out.pointY = pointY;
+      out.invertedSel = false;
+    } else {
+      out.markY = pointY;
+      out.pointY = markY;
+      out.invertedSel = true;
+    }
+    out.markX += hscroll;
+    out.markY += vscroll;
+    out.pointX += hscroll;
+    out.pointY += vscroll;
+
+    if (damage() == DAMAGE_HIGHLIGHT) {
+      // capture new selection text
+      out.selection = &selection;
+      out.selection->clear();
+    }
+  }
+  // must call setfont() before getascent() etc
+  fl_font(out.font, out.fontSize);
+  out.y1 = y() + fl_height();
+  out.lineHeight = fl_height() + fl_descent();
+  out.y1 += vscroll;
+
+  fl_push_clip(x(), y(), w() - SCROLL_X, h());
+  bool havePushedAnchor = false;
+  if (pushedAnchor && (damage() == DAMAGE_PUSHED)) {
+    // just draw the anchor-push
+    int h = (pushedAnchor->y2 - pushedAnchor->y1) + pushedAnchor->lineHeight;
+    fl_push_clip(x(), y() + pushedAnchor->y1, out.x2, h);
+    havePushedAnchor = true;
+  }
+  // draw the background
+  fl_color(out.background);
+  fl_rectf(x(), y(), w() - SCROLL_X, h());
+  fl_color(out.color);
+
+  out.background = NO_COLOR;
+
+  // hide any inputs
+  List_each(InputNode *, it, inputs) {
+    InputNode *p = (*it);
+    if (p->button) {
+      p->button->clear_visible();
+    }
+  }
+
+  int id = 0;
+  List_each(BaseNode *, it, nodeList) {
+    BaseNode *p = (*it);
+    out.nodeId = id;
+    p->display(&out);
+    if (out.nodeId < id) {
+      // perform second pass on previous outer table
+      MeasureNode *node = (MeasureNode *)nodeList[out.nodeId];
+      out.x1 = node->initX;
+      out.y1 = node->initY;
+      out.exposed = false;
+      for (int j = out.nodeId; j <= id; j++) {
+        p = nodeList[j];
+        out.nodeId = j;
+        p->display(&out);
+      }
+      out.exposed = exposed();
+    }
+    if (out.exposed == false && out.tableLevel == 0 && out.y1 - out.lineHeight > out.y2) {
+      // clip remaining content
+      break;
+    }
+    id++;
+  }
+
+  if (out.exposed) {
+    // size has changed or need to recalculate scrollbar
+    int pageHeight = (out.y1 - vscroll) + out.lineHeight;
+    int windowHeight = h() - out.lineHeight;
+    int scrollH = pageHeight;
+    if (scrollH != scrollHeight || windowHeight != scrollWindowHeight) {
+      scrollWindowHeight = windowHeight;
+      scrollHeight = scrollH;
+      if (scrollHeight < scrollWindowHeight) {
+        // nothing to scroll
+        scrollHeight = 0;
+        scrollbar->deactivate();
+        scrollbar->value(0, 1, 0, 1);
+      } else {
+        scrollbar->activate();
+        scrollbar->value(-vscroll, scrollWindowHeight, 0, scrollHeight);
+        scrollbar->linesize(out.lineHeight);
+      }
+      scrollbar->redraw();
+    }
+  }
+  if (havePushedAnchor) {
+    fl_pop_clip();
+  }
+  fl_pop_clip();
+
+  // draw child controls
+  draw_child(*scrollbar);
+
+  // prevent other child controls from drawing over the scrollbar
+  fl_push_clip(x(), y(), w() - SCROLL_X, h());
+  int numchildren = children();
+  for (int n = 0; n < numchildren; n++) {
+    Fl_Widget &w = *child(n);
+    if (&w != scrollbar) {
+      update_child(w);
+    }
+  }
+  fl_pop_clip();
+}
+
+void HelpWidget::compile() {
+  uint8_t pre = !isHtmlFile();
+  uint8_t bold = false;
+  uint8_t italic = false;
+  uint8_t center = false;
+  uint8_t uline = false;
+  Fl_Color color = 0;
+  Fl_Font font = FL_HELVETICA;
+  int fontSize = (int)labelsize();
+  int taglen = 0;
+  int textlen = 0;
+  bool padlines = false;          // padding between line-breaks
+
+  strlib::Stack tableStack(5);
+  strlib::Stack trStack(5);
+  strlib::Stack tdStack(5);
+  strlib::Stack olStack(5);
+  strlib::Stack codeStack(5);
+  strlib::List options(5);
+  Attributes p(5);
+  strlib::String *prop;
+  BaseNode *node;
+  InputNode *inputNode;
+
+  const char *text = htmlStr.c_str();
+  const char *tagBegin = text;
+  const char *tagEnd = text;
+  const char *tag;
+  const char *tagPair = 0;
+
+#define ADD_PREV_SEGMENT                        \
+  prevlen = i-pindex;                           \
+  if (prevlen > 0) {                            \
+    nodeList.add(new TextNode(p, prevlen));     \
+    padlines = true;                            \
+  }
+
+  while (text && *text) {
+    // find the start of the next tag
+    while (*tagBegin != 0 && *tagBegin != '<') {
+      tagBegin++;
+    }
+    tagEnd = tagBegin;
+    while (*tagEnd != 0 && *tagEnd != '>') {
+      tagEnd++;
+    }
+    if (tagBegin == text) {
+      if (*tagEnd == 0) {
+        break;                  // no tag closure
+      }
+      text = tagEnd + 1;        // adjoining tags
+    }
+    // process open text leading to the found tag
+    if (tagBegin > text && tagPair == 0) {
+      textlen = tagBegin - text;
+      const char *p = text;
+      int pindex = 0;
+      int prevlen, ispace;
+      for (int i = 0; i < textlen; i++) {
+        switch (text[i]) {
+        case '&':
+          // handle entities
+          if (text[i + 1] == '#') {
+            ADD_PREV_SEGMENT;
+            int ch = 0;
+            i += 2;
+            while (isdigit(text[i])) {
+              ch = (ch * 10) + (text[i++] - '0');
+            }
+            if (text[i] == ';') {
+              i++;
+            }
+            if (ch == 133) {
+              node = new ImageNode(&ellipseImage);
+              nodeList.add(node);
+            } else if (ch > 129 && ch < 256) {
+              node = new TextNode(&entityMap[ch].xlat, 1);
+              nodeList.add(node);
+            }
+            pindex = i;
+            p = text + pindex;
+          } else {
+            for (int j = 0; j < entityMapLen; j++) {
+              if (0 == strncasecmp(text + i + 1, entityMap[j].ent, entityMap[j].elen - 1)) {
+                ADD_PREV_SEGMENT;
+                // save entity replacement
+                node = new TextNode(&entityMap[j].xlat, 1);
+                nodeList.add(node);
+                // skip past entity
+                i += entityMap[j].elen;
+                pindex = i;
+                p = text + pindex;
+                i--;
+                // stop searching
+                break;
+              }
+            }
+          }
+          break;
+
+        case '\r':
+        case '\n':
+          ADD_PREV_SEGMENT;
+          if ((prevlen && text[i - 1] == ' ')) {
+            padlines = false;
+          }
+          if (pre) {
+            nodeList.add(new BrNode(pre));
+          } else if (padlines == true) {
+            nodeList.add(new TextNode(spacestr, 1));
+            padlines = false;   // don't add consequtive spacestrs
+          }
+          // skip white space
+          while (i < textlen && (IS_WHITE(text[i + 1]))) {
+            i++;                // ends on final white-char
+          }
+
+          // skip white-char character
+          pindex = i + 1;
+          p = text + pindex;
+          break;
+
+        case '-':
+        case '~':
+        case ':':
+          // break into separate segments to cause line-breaks
+          prevlen = i - pindex + 1;
+          if (prevlen > 0) {
+            nodeList.add(new TextNode(p, prevlen));
+            padlines = true;
+          }
+          pindex = i + 1;
+          p = text + pindex;
+          break;
+
+        case ' ':
+        case '\t':
+          if (pre) {
+            continue;
+          }
+          // skip multiple whitespaces
+          ispace = i;
+          while (text[ispace + 1] == ' ' || text[ispace + 1] == '\t') {
+            ispace++;
+            if (ispace == textlen) {
+              break;
+            }
+          }
+          if (ispace > i) {
+            ADD_PREV_SEGMENT;
+            pindex = i = ispace;
+            p = text + pindex;
+          }
+          break;
+        }
+      }                         // end for (int i=0; i 0) {
+      tag = tagBegin + 1;
+      if (tag[0] == '/') {
+        // process the end of tag
+        tag++;
+        if (0 == strncasecmp(tag, "b>", 2)) {
+          bold = false;
+          node = new FontNode(font, fontSize, 0, bold, italic);
+          nodeList.add(node);
+        } else if (0 == strncasecmp(tag, "i", 1)) {
+          italic = false;
+          node = new FontNode(font, fontSize, 0, bold, italic);
+          nodeList.add(node);
+        } else if (0 == strncasecmp(tag, "center", 6)) {
+          center = false;
+          nodeList.add(new StyleNode(uline, center));
+        } else if (0 == strncasecmp(tag, "font", 4) ||
+                   0 == strncasecmp(tag, "blockquote", 10) ||
+                   0 == strncasecmp(tag, "h", 1)) {     // 
+          if (0 == strncasecmp(tag, "h", 1)) {
+            if (bold > 0) {
+              bold--;
+            }
+            padlines = false;
+          }
+          color = (0 == strncasecmp(tag, "f", 1) ? (Fl_Color) - 1 : 0);
+          font = FL_HELVETICA;
+          node = new FontNode(font, fontSize, color, bold, italic);
+          nodeList.add(node);
+        } else if (0 == strncasecmp(tag, "pre", 3)) {
+          pre = false;
+          node = new FontNode(font, fontSize, 0, bold, italic);
+          nodeList.add(node);
+          nodeList.add(new BrNode(pre));
+        } else if (0 == strncasecmp(tag, "a", 1)) {
+          nodeList.add(new AnchorEndNode());
+        } else if (0 == strncasecmp(tag, "ul", 2) || 0 == strncasecmp(tag, "ol", 2)) {
+          nodeList.add(new UlEndNode());
+          olStack.pop();
+          padlines = false;
+        } else if (0 == strncasecmp(tag, "u", 1)) {
+          uline = false;
+          nodeList.add(new StyleNode(uline, center));
+        } else if (0 == strncasecmp(tag, "td", 2)) {
+          nodeList.add(new TdEndNode((TdNode *)tdStack.pop()));
+          text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "tr", 2)) {
+          node = new TrEndNode((TrNode *)trStack.pop());
+          nodeList.add(node);
+          padlines = false;
+          text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "table", 5)) {
+          node = new TableEndNode((TableNode *)tableStack.pop());
+          nodeList.add(node);
+          padlines = false;
+          text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "textarea", 8) && tagPair) {
+          inputNode = new InputNode(this, &p, tagPair, tagBegin - tagPair);
+          nodeList.add(inputNode);
+          inputs.add(inputNode);
+          inputNode->update(&namedInputs, cookies, &p);
+          tagPair = 0;
+          p.removeAll();
+        } else if (0 == strncasecmp(tag, "select", 6) && tagPair) {
+          inputNode = new InputNode(this);
+          createDropList(inputNode, &options);
+          nodeList.add(inputNode);
+          inputs.add(inputNode);
+          inputNode->update(&namedInputs, cookies, &p);
+          tagPair = 0;
+          p.removeAll();
+        } else if (0 == strncasecmp(tag, "option", 6) && tagPair) {
+          strlib::String *option = new String();
+          option->append(tagPair, tagBegin - tagPair);
+          options.add(option);  // continue scan for more options
+        } else if (0 == strncasecmp(tag, "title", 5) && tagPair) {
+          title.clear();
+          title.append(tagPair, tagBegin - tagPair);
+          tagPair = 0;
+        } else if (0 == strncasecmp(tag, "code", 4)) {
+          node = new CodeEndNode((CodeNode *)codeStack.pop());
+          nodeList.add(node);
+        } else if (0 == strncasecmp(tag, "script", 6) ||
+                   0 == strncasecmp(tag, "style", 5)) {
+          tagPair = 0;
+        }
+      } else if (isalpha(tag[0]) || tag[0] == '!') {
+        // process the start of the tag
+        if (0 == strncasecmp(tag, "br", 2)) {
+          nodeList.add(new BrNode(pre));
+          padlines = false;
+          text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "p>", 2)) {
+          nodeList.add(new ParagraphNode());
+          padlines = false;
+          text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "b>", 2)) {
+          bold = true;
+          node = new FontNode(font, fontSize, 0, bold, italic);
+          nodeList.add(node);
+        } else if (0 == strncasecmp(tag, "i>", 2)) {
+          italic = true;
+          node = new FontNode(font, fontSize, 0, bold, italic);
+          nodeList.add(node);
+        } else if (0 == strncasecmp(tag, "center", 6)) {
+          center = true;
+          nodeList.add(new StyleNode(uline, center));
+        } else if (0 == strncasecmp(tag, "hr", 2)) {
+          nodeList.add(new HrNode());
+          padlines = false;
+        } else if (0 == strncasecmp(tag, "title", 5)) {
+          tagPair = text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "pre", 3)) {
+          pre = true;
+          node = new FontNode(FL_COURIER, fontSize, 0, bold, italic);
+          nodeList.add(node);
+          nodeList.add(new BrNode(pre));
+        } else if (0 == strncasecmp(tag, "code", 4)) {
+          node = new CodeNode(fontSize);
+          codeStack.push((CodeNode *)node);
+          nodeList.add(node);
+        } else if (0 == strncasecmp(tag, "td", 2)) {
+          p.removeAll();
+          p.load(tag + 2, taglen - 2);
+          node = new TdNode((TrNode *)trStack.peek(), &p);
+          nodeList.add(node);
+          tdStack.push((TdNode *)node);
+          text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "tr", 2)) {
+          p.removeAll();
+          p.load(tag + 2, taglen - 2);
+          node = new TrNode((TableNode *)tableStack.peek(), &p);
+          nodeList.add(node);
+          trStack.push((TrNode *)node);
+          text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "table", 5)) {
+          p.removeAll();
+          p.load(tag + 5, taglen - 5);
+          node = new TableNode(&p);
+          nodeList.add(node);
+          tableStack.push((TableNode *)node);
+          padlines = false;
+          text = skipWhite(tagEnd + 1);
+          // continue the font in case we resize
+          node = new FontNode(font, fontSize, 0, bold, italic);
+          nodeList.add(node);
+          prop = p.getBackground();
+          if (prop != NULL) {
+            node = new ImageNode(&docHome, prop, false);
+            nodeList.add(node);
+            images.add((ImageNode *)node);
+          }
+        } else if (0 == strncasecmp(tag, "ul>", 3) ||
+                   0 == strncasecmp(tag, "ol ", 3) ||
+                   0 == strncasecmp(tag, "ol>", 3)) {
+          p.removeAll();
+          p.load(tag + 2, taglen - 2);
+          node = new UlNode(p, tag[0] == 'o' || tag[0] == 'O');
+          olStack.push((UlNode *)node);
+          nodeList.add(node);
+          padlines = false;
+        } else if (0 == strncasecmp(tag, "u>", 2)) {
+          uline = true;
+          nodeList.add(new StyleNode(uline, center));
+        } else if (0 == strncasecmp(tag, "li>", 3)) {
+          node = new LiNode((UlNode *)olStack.peek());
+          nodeList.add(node);
+          padlines = false;
+          text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "a ", 2)) {
+          p.removeAll();
+          p.load(tag + 2, taglen - 2);
+          node = new AnchorNode(p);
+          nodeList.add(node);
+          anchors.add((AnchorNode *)node);
+        } else if (0 == strncasecmp(tag, "font ", 5)) {
+          p.removeAll();
+          p.load(tag + 5, taglen - 5);
+          color = getColor(p.get("color"), 0);
+          prop = p.get("font-size");
+          if (prop != NULL) {
+            // convert from points to pixels
+            float h, v;
+            Fl::screen_dpi(h, v);
+            fontSize = (int)(prop->toInteger() * v  / 72.0);
+          } else {
+            prop = p.get("size");
+            if (prop != NULL) {
+              fontSize = 7 + (prop->toInteger() * 2);
+            }
+          }
+          prop = p.get("face");
+          if (prop != NULL) {
+            font = get_font(prop->c_str());
+          }
+          node = new FontNode(font, fontSize, color, bold, italic);
+          nodeList.add(node);
+        } else if (taglen >= 2 && 0 == strncasecmp(tag, "h", 1)) {
+          // H1-H6 from large to small
+          int size = FONT_SIZE_H1 - ((tag[1] - '1') * 2);
+          nodeList.add(new FontNode(font, size, 0, ++bold, italic));
+          padlines = false;
+        } else if (0 == strncasecmp(tag, "blockquote", 10)) {
+          nodeList.add(new FontNode(font, fontSize, 0, true, true));
+          padlines = false;
+        } else if (0 == strncasecmp(tag, "input ", 6)) {
+          // check for quoted values including '>'
+          if (unquoteTag(tagBegin + 6, tagEnd)) {
+            taglen = tagEnd - tagBegin - 1;
+            text = *tagEnd == 0 ? 0 : tagEnd + 1;
+          }
+          p.removeAll();
+          p.load(tag + 6, taglen - 6);
+          inputNode = new InputNode(this, &p);
+          nodeList.add(inputNode);
+          inputs.add(inputNode);
+          inputNode->update(&namedInputs, cookies, &p);
+        } else if (0 == strncasecmp(tag, "textarea", 8)) {
+          p.load(tag + 8, taglen - 8);
+          tagPair = text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "select", 6)) {
+          p.load(tag + 6, taglen - 6);
+          tagPair = text = skipWhite(tagEnd + 1);
+          options.removeAll();
+        } else if (0 == strncasecmp(tag, "option", 6)) {
+          tagPair = text = skipWhite(tagEnd + 1);
+        } else if (0 == strncasecmp(tag, "img ", 4)) {
+          p.removeAll();
+          p.load(tag + 4, taglen - 4);
+          node = new ImageNode(&docHome, &p);
+          nodeList.add(node);
+          images.add((ImageNode *)node);
+        } else if (0 == strncasecmp(tag, "body ", 5)) {
+          p.removeAll();
+          p.load(tag + 5, taglen - 5);
+          text = skipWhite(tagEnd + 1);
+          foreground = getColor(p.getFgColor(), foreground);
+          background = getColor(p.getBgColor(), background);
+          prop = p.getBackground();
+          if (prop != NULL) {
+            node = new ImageNode(&docHome, prop, true);
+            nodeList.add(node);
+            images.add((ImageNode *)node);
+          }
+        } else if (0 == strncasecmp(tag, "script", 6) ||
+                   0 == strncasecmp(tag, "style", 5)) {
+          tagPair = text;
+        } else {
+          // unknown tag
+          text = skipWhite(tagEnd + 1);
+        }
+      } else if (tag[0] == '?') {
+        nodeList.add(new EnvNode(cookies, tag + 1, taglen - 1));
+      } else {
+        // '<' is a literal character
+        nodeList.add(new TextNode(anglestr, 1));
+        tagEnd = tagBegin;
+        text = tagBegin + 1;
+      }                         // end if-start, else-end tag
+    }                           // if found a tag
+    tagBegin = *tagEnd == 0 ? tagEnd : tagEnd + 1;
+  }
+
+  // prevent nodes from being auto-deleted
+  codeStack.clear();
+  olStack.clear();
+  tdStack.clear();
+  trStack.clear();
+  while (tableStack.peek()) {
+    node = new TableEndNode((TableNode *)tableStack.pop());
+    nodeList.add(node);
+  }
+}
+
+// handle click from form button
+void HelpWidget::onclick(Fl_Widget *button) {
+  List_each(InputNode *, it, inputs) {
+    InputNode *p = (*it);
+    if (p->button == button) {
+      this->event.clear();
+      this->event.append(p->onclick.c_str());
+      user_data((void *)this->event.c_str());
+      do_callback();
+      return;
+    }
+  }
+}
+
+int HelpWidget::onMove(int event) {
+  int ex = Fl::event_x();
+  int ey = Fl::event_y();
+
+  if (pushedAnchor && event == FL_DRAG) {
+    bool pushed = pushedAnchor->ptInSegment(ex, ey);
+    if (pushedAnchor->pushed != pushed) {
+      fl_cursor(FL_CURSOR_HAND);
+      pushedAnchor->pushed = pushed;
+      damage(DAMAGE_PUSHED);
+    }
+    return 1;
+  } else {
+    List_each(AnchorNode *, it, anchors) {
+      AnchorNode *p = (*it);
+      if (p->ptInSegment(ex, ey)) {
+        fl_cursor(FL_CURSOR_HAND);
+        return 1;
+      }
+    }
+    fl_cursor(FL_CURSOR_DEFAULT);
+  }
+
+  int vscroll = -scrollbar->value();
+  if (event == FL_DRAG) {
+    switch (mouseMode) {
+    case mm_select:
+      // drag text selection
+      pointX = ex - hscroll;
+      pointY = ey - vscroll;
+      damage(DAMAGE_HIGHLIGHT);
+      break;
+    case mm_scroll:
+      // follow the mouse navigation
+      if (scrollY != ey) {
+        // scroll up (less -ve) when draged down
+        int16_t scroll = vscroll + (ey - scrollY);
+        scrollY = ey;
+        if (scroll > 0) {
+          scroll = 0;           // too far up
+        } else if (-scroll > scrollHeight) {
+          scroll = -scrollHeight;       // too far down
+        }
+        if (scroll != vscroll) {
+          vscroll = scroll;
+          damage(FL_DAMAGE_EXPOSE);
+        }
+      }
+      break;
+    case mm_page:
+      break;
+    }
+    return 1;
+  }
+
+  return 0;
+}
+
+int HelpWidget::onPush(int event) {
+  pushedAnchor = 0;
+  int ex = Fl::event_x();
+  int ey = Fl::event_y();
+  int vscroll = -scrollbar->value();
+  int16_t scroll = vscroll;
+
+  List_each(AnchorNode *, it, anchors) {
+    AnchorNode *p = (*it);
+    if (p->ptInSegment(ex, ey)) {
+      pushedAnchor = p;
+      pushedAnchor->pushed = true;
+      fl_cursor(FL_CURSOR_HAND);
+      damage(DAMAGE_PUSHED);
+      return 1;
+    }
+  }
+
+  switch (mouseMode) {
+  case mm_select:
+    // begin/continue text selection
+    if (Fl::event_state(FL_SHIFT)) {
+      pointX = (ex - hscroll);
+      pointY = (ey - vscroll);
+    } else {
+      markX = pointX = (ex - hscroll);
+      markY = pointY = (ey - vscroll);
+    }
+    damage(DAMAGE_HIGHLIGHT);
+    break;
+
+  case mm_scroll:
+    scrollY = ey;
+    break;
+
+  case mm_page:
+    if (ey > h() / 2) {
+      // page down/up
+      scroll += ex > w() / 2 ? -h() : h();
+    } else {
+      // home/end
+      scroll = ex > w() / 2 ? -scrollHeight : 0;
+    }
+    if (scroll > 0) {
+      scroll = 0;               // too far up
+    } else if (-scroll > scrollHeight) {
+      scroll = -scrollHeight;   // too far down
+    }
+    if (scroll != scrollbar->value()) {
+      scrollbar->value(scroll);
+      damage(FL_DAMAGE_EXPOSE);
+    }
+    break;
+  }
+  return 1;                     // return 1 to become the belowmouse
+}
+
+int HelpWidget::handleKeys() {
+  int result = 0;
+  switch (Fl::event_key()) {
+  case FL_Right:
+    if (-hscroll < w() / 2) {
+      hscroll -= HSCROLL_STEP;
+      redraw();
+      result = 1;
+    }
+    break;
+  case FL_Left:
+    if (hscroll < 0) {
+      hscroll += HSCROLL_STEP;
+      redraw();
+      result = 1;
+    }
+    break;
+  default:
+    break;
+  }
+
+  if (!result && Fl::event_state(FL_CTRL)) {
+    result = 1;
+    switch (Fl::event_key()) {
+    case 'u':
+      vscroll(-scrollWindowHeight);
+      break;
+    case 'd':
+      vscroll(scrollWindowHeight);
+      break;
+    case 'r':
+      reloadPage();
+      break;
+    case 'f':
+      find(fl_input("Find:"), false);
+      break;
+    case 'a':
+      selectAll();
+      break;
+    case FL_Insert:
+    case 'c':
+      copySelection();
+      break;
+    case 'b':
+    case 'q':
+      if (Fl::modal() == parent()) {
+        Fl::modal()->set_non_modal();
+      }
+      break;
+    default:
+      result = 0;
+      break;
+    }
+  }
+  return result;
+}
+
+int HelpWidget::handle(int event) {
+  int handled = Fl_Group::handle(event);
+  if (handled && event != FL_MOVE) {
+    return handled;
+  }
+
+  switch (event) {
+  case EVENT_INCREASE_FONT:
+    if (getFontSize() < MAX_FONT_SIZE) {
+      setFontSize(getFontSize() + 1);
+    }
+    return 1;
+
+  case EVENT_DECREASE_FONT:
+    if (getFontSize() > MIN_FONT_SIZE) {
+      setFontSize(getFontSize() - 1);
+    }
+    return 1;
+
+  case EVENT_COPY_TEXT:
+    copySelection();
+    return 1;
+
+  case EVENT_SEL_ALL_TEXT:
+    selectAll();
+    return 1;
+
+  case EVENT_FIND:
+    find(fl_input("Find:"), false);
+    return 1;
+
+  case FL_SHOW:
+    take_focus();
+    break;
+
+  case FL_FOCUS:
+    return 1;                   // aquire focus
+
+  case FL_PUSH:
+    if (Fl::event_x() < w() - SCROLL_X) {
+      return onPush(event);
+    }
+    break;
+
+  case FL_ENTER:
+    return 1;
+
+  case FL_KEYDOWN:
+    if (handleKeys()) {
+      return 1;
+    }
+    break;
+
+  case FL_DRAG:
+  case FL_MOVE:
+    return onMove(event);
+
+  case FL_RELEASE:
+    if (pushedAnchor) {
+      fl_cursor(FL_CURSOR_DEFAULT);
+      bool pushed = pushedAnchor->pushed;
+      pushedAnchor->pushed = false;
+      damage(DAMAGE_PUSHED);
+      if (pushed) {
+        this->event.clear();
+        this->event.append(pushedAnchor->href.c_str());
+        if (this->event.length()) {
+          // href has been set
+          user_data((void *)this->event.c_str());
+          do_callback();
+        }
+      }
+      return 1;
+    }
+  }
+
+  return scrollbar->active() ? scrollbar->handle(event) : 0;
+}
+
+bool HelpWidget::find(const char *s, bool matchCase) {
+  if (s == 0 || s[0] == 0) {
+    return false;
+  }
+
+  int foundRow = 0;
+  int lineHeight = fl_height() + fl_descent();
+
+  List_each(BaseNode *, it, nodeList) {
+    BaseNode *p = (*it);
+    if (p->indexOf(s, matchCase) != -1) {
+      foundRow = p->getY() - scrollbar->value();
+      if (foundRow > -scrollbar->value() + lineHeight) {
+        break;
+      }
+    }
+  }
+
+  int scroll = scrollbar->value();
+  if (-scroll == foundRow) {
+    return false;
+  }
+
+  scroll = foundRow;
+
+  // check scroll bounds
+  if (foundRow) {
+    scroll += lineHeight;
+  }
+  if (-scroll > scrollHeight) {
+    scroll = -scrollHeight;
+  }
+
+  scrollbar->value(scroll);
+  redraw();
+  return true;
+}
+
+void HelpWidget::copySelection() {
+  Fl::copy(selection.c_str(), selection.length(), true);
+}
+
+void HelpWidget::selectAll() {
+  markX = markY = 0;
+  pointX = w();
+  pointY = scrollHeight + h();
+  selection.clear();
+  getText(&selection);
+  redraw();
+}
+
+void HelpWidget::navigateTo(const char *s) {
+  if (strncmp(s, "http://", 7) == 0) {
+    // launch in real browser
+    browseFile(s);
+    return;
+  }
+
+  strlib::String path;
+  path.append(docHome);
+
+  int len = path.length();
+  if (len && path[len - 1] == '/' && s[0] == '/') {
+    // avoid adding double slashes
+    path.append(s + 1);
+  } else {
+    path.append(s);
+  }
+  loadFile(path.c_str());
+}
+
+void HelpWidget::loadBuffer(const char *str) {
+  if (strncasecmp("file:", str, 5) == 0) {
+    loadFile(str + 5);
+  } else {
+    htmlStr = str;
+    reloadPage();
+  }
+}
+
+void HelpWidget::loadFile(const char *f, bool useDocHome) {
+  FILE *fp;
+
+  fileName.clear();
+  htmlStr.clear();
+
+  if (docHome.length() != 0 && useDocHome) {
+    fileName.append(docHome);
+  }
+
+  if (strncasecmp(f, "file:///", 8) == 0) {
+    // only supports file protocol
+    f += 8;
+  }
+
+  const char *target = strrchr(f, '#');
+  long len = target != NULL ? target - f : strlen(f);
+  fileName.append(f, len);
+  fileName.replaceAll('\\', '/');
+
+  // update docHome using the given file-name
+  if (docHome.length() == 0) {
+    int i = fileName.lastIndexOf('/', 0);
+    if (i != -1) {
+      docHome = fileName.substring(0, i + 1);
+    } else {
+      docHome.append("./");
+    }
+    if (docHome[docHome.length() - 1] != '/') {
+      docHome.append("/");
+    }
+  }
+  if ((fp = fopen(fileName.c_str(), "rb")) != NULL) {
+    fseek(fp, 0, SEEK_END);
+    len = ftell(fp);
+    rewind(fp);
+    htmlStr.append(fp, len);
+    fclose(fp);
+  } else {
+    htmlStr.append("File not found: \"");
+    htmlStr.append(fileName.c_str());
+    htmlStr.append("\" - ");
+    htmlStr.append(strerror(errno));
+  }
+
+  reloadPage();
+  if (target) {
+    // draw to obtain dimensions
+    Fl::flush();
+    scrollTo(target + 1);
+  }
+}
+
+// reload broken images
+void HelpWidget::reloadImages() {
+  List_each(ImageNode *, it, images) {
+    ImageNode *imageNode = (*it);
+    if (imageNode->image == &brokenImage) {
+      imageNode->reload();
+    }
+  }
+  redraw();
+}
+
+void HelpWidget::setDocHome(const char *s) {
+  docHome.clear();
+  docHome.append(s);
+  if (s && s[strlen(s) - 1] != '/') {
+    docHome.append("/");
+  }
+}
+
+const char *HelpWidget::getAnchor(int index) {
+  int len = anchors.size();
+  if (index < len && index > -1) {
+    return anchors[index]->href.c_str();
+  }
+  return NULL;
+}
+
+void HelpWidget::getText(strlib::String *s) {
+  List_each(BaseNode *, it, nodeList) {
+    BaseNode *p = (*it);
+    p->getText(s);
+  }
+}
+
+bool HelpWidget::isHtmlFile() {
+  const char *filename = fileName.c_str();
+  if (!fileName || !fileName[0]) {
+    return false;
+  }
+  int len = strlen(filename);
+  return (strcasecmp(filename + len - 4, ".htm") == 0 ||
+          strcasecmp(filename + len - 5, ".html") == 0);
+}
+
+void HelpWidget::vscroll(int offs) {
+  if (scrollbar->active()) {
+    int value = scrollbar->value() + offs;
+    scrollbar->value(value);
+  }
+}
+
+//--Helper functions------------------------------------------------------------
+
+/**
+ * Returns the number of characters that will fit within
+ * the gixen pixel width.
+ */
+void lineBreak(const char *s, int slen, int width, int &linelen, int &linepx) {
+  // find the end of the first word
+  int i = 0;
+  int txtWidth;
+  int ibreak = -1;
+  int breakWidth = -1;
+
+  while (i < slen) {
+    if (s[i++] == ' ') {
+      ibreak = i;
+      break;
+    }
+  }
+
+  // no break point found
+  if (ibreak == -1) {
+    linelen = slen;
+    linepx = fl_width(s, slen);
+    return;
+  }
+  // find the last break-point within the available width
+  txtWidth = fl_width(s, i);
+  ibreak = i;
+  breakWidth = txtWidth;
+
+  while (i < slen && txtWidth < width) {
+    ibreak = i;
+    breakWidth = txtWidth;
+    while (i < slen) {
+      if (s[i++] == ' ') {
+        break;
+      }
+    }
+    txtWidth += fl_width(s + ibreak, i - ibreak);
+  }
+
+  if (txtWidth < width) {
+    // entire segment fits
+    linelen = slen;
+    linepx = txtWidth;
+  } else {
+    // first break-point is after boundary
+    linelen = ibreak;
+    linepx = breakWidth;
+  }
+}
+
+// return a new tagEnd if the current '>' is embedded in quotes
+bool unquoteTag(const char *tagBegin, const char *&tagEnd) {
+  bool quote = false;
+  int len = tagEnd - tagBegin;
+  int i = 1;
+  while (i < len) {
+    switch (tagBegin[i++]) {
+    case '\'':
+    case '\"':
+      quote = !quote;
+      break;
+    }
+  }
+  //  - move end-tag
+  // ':
+        if (quote == false) {
+          tagEnd += i;
+          return true;
+        }
+        break;
+      }
+      i++;
+    }
+  }
+  return false;
+}
+
+/**
+ * skip white space between tags:
+ * -skip--skip--skip-skip-
+ */ +const char *skipWhite(const char *s) { + if (s == 0 || s[0] == 0) { + return 0; + } + while (IS_WHITE(*s)) { + s++; + } + return s; +} + +Fl_Color getColor(strlib::String *s, Fl_Color def) { + Fl_Color result; + if (s != NULL && s->length()) { + result = get_color(s->c_str(), def); + } else { + result = def; + } + return result; +} + +// image factory based on file extension +Fl_Shared_Image *loadImage(const char *name, uchar *buff) { + return Fl_Shared_Image::get(name); +} + +Fl_Image *loadImage(const char *imgSrc) { + if (imgSrc == 0 || access(imgSrc, 0) != 0) { + return &brokenImage; + } + Fl_Image *image = loadImage(imgSrc, 0); + return image != 0 ? image : &brokenImage; +} + +#if defined(WIN32) +#include +#include +#include +#endif + +void browseFile(const char *url) { +#if defined(WIN32) + ShellExecute(xid(Window::first()), "open", url, 0, 0, SW_SHOWNORMAL); +#else + if (fork() == 0) { + fclose(stderr); + fclose(stdin); + fclose(stdout); + execlp("htmlview", "htmlview", url, NULL); + execlp("firefox", "firefox", url, NULL); + execlp("mozilla", "mozilla", url, NULL); + ::exit(0); // in case exec failed + } +#endif +} diff --git a/src/platform/fltk/HelpWidget.h b/src/platform/fltk/HelpWidget.h new file mode 100644 index 00000000..623fbe88 --- /dev/null +++ b/src/platform/fltk/HelpWidget.h @@ -0,0 +1,333 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef Fl_HELP_WIDGET +#define Fl_HELP_WIDGET + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ui/strlib.h" +#include "ui/textedit.h" +#include "platform/fltk/utils.h" + +#define ID_BUTTON 1 +#define ID_TEXTBOX 2 +#define ID_TEXTAREA 3 +#define ID_CHKBOX 4 +#define ID_RADIO 5 +#define ID_SELECT 6 +#define ID_RANGEVAL 7 +#define ID_HIDDEN 8 +#define ID_READONLY 9 + +#define MIN_FONT_SIZE 11 +#define MAX_FONT_SIZE 22 +#define EVENT_INCREASE_FONT 100 +#define EVENT_DECREASE_FONT 101 +#define EVENT_COPY_TEXT 102 +#define EVENT_SEL_ALL_TEXT 103 +#define EVENT_FIND 104 + +using namespace strlib; + +Fl_Shared_Image *loadImage(const char *name, uchar *buff); +void browseFile(const char *s); + +struct BaseNode; +struct NamedInput; +struct InputNode; +struct AnchorNode; +struct ImageNode; + +class HelpWidget : public Fl_Group { +public: + HelpWidget(Fl_Widget *rect, int defsize = MIN_FONT_SIZE); + virtual ~HelpWidget(); + + void loadBuffer(const char *buffer); + void loadFile(const char *fileName, bool useDocHome = false); + void navigateTo(const char *fileName); + void scrollTo(const char *anchorName); + bool find(const char *s, bool matchCase); + Fl_Widget *getInput(const char *name); + const char *getInputValue(Fl_Widget *button); + const char *getInputValue(int i); + const char *getInputName(Fl_Widget *button); + const char *getTitle() { return title; } + const char *getFileName() { return fileName; } + const char *getDocHome() { return docHome; } + const char *getSelection() { return selection; } + const strlib::String getEventName() { return event; } + void getText(strlib::String *s); + void getInputProperties(Properties *p); + void setCookies(Properties *p) { cookies = p; } + void setTheme(EditTheme *theme); + bool setInputValue(const char *assignment); + void selectAll(); + void copySelection(); + void onclick(Fl_Widget *button); + void setDocHome(const char *home); + void reloadImages(); + void setFontSize(int i); + int getFontSize() { return (int)labelsize(); } + void setSelectMode() { mouseMode = mm_select; } + void setPageMode() { mouseMode = mm_page; } + void setScrollMode() { mouseMode = mm_scroll; } + int getNumAnchors() { return anchors.size(); } + const char *getAnchor(int index); + bool isHtmlFile(); + void setTitle(const char *s) { title.empty(); title.append(s); } + +protected: + void reloadPage(); + void compile(); + void init(); + void cleanup(); + void endSelection(); + + // fltk methods + void draw(); + void resize(int x, int y, int w, int h); + int onMove(int event); + int onPush(int event); + int handle(int event); + int handleKeys(); + +private: + bool exposed() { return (damage() & (FL_DAMAGE_EXPOSE | FL_DAMAGE_ALL)); } + void vscroll(int offs); + + Fl_Scrollbar *scrollbar; + Fl_Color background, foreground; + int32_t scrollHeight, scrollWindowHeight; + int16_t markX, markY, pointX, pointY; + int16_t hscroll, scrollY; + enum { mm_select, mm_page, mm_scroll } mouseMode; + strlib::List nodeList; + strlib::List namedInputs; + strlib::List inputs; + strlib::List anchors; + strlib::List images; + strlib::Properties *cookies; + strlib::String htmlStr; + strlib::String event; + strlib::String fileName; + strlib::String docHome; + strlib::String title; + strlib::String selection; +}; + +#ifdef Fl_HELP_WIDGET_RESOURCES +// somewhere to keep this clutter + +/* XPM */ +static const char *dot_xpm[] = { + "8 8 3 1", + " c None", + ". c #141414", + "+ c #000000", + " .. ", + " ++++++ ", + " ++++++ ", + ".++++++.", + ".++++++.", + " ++++++ ", + " ++++++ ", + " .. " +}; + +static Fl_Pixmap dotImage(dot_xpm); + +static const char *ellipse_xpm[] = { + "6 1 2 1", + " c #000000", + ". c #FFFFFF", + " . . ." +}; + +static Fl_Pixmap ellipseImage(ellipse_xpm); + +static const char *broken_xpm[] = { + "16 18 4 1", + "@ c #000000", + " c #ffffff", + "+ c none", + "x c #ff0000", + // pixels + "@@@@@@@+++++++++", + "@ @++++++++++", + "@ @+++++++++++", + "@ @++@++++++++", + "@ @@+++++++++", + "@ @+++@+++++", + "@ @++@@++++@", + "@ xxx @@ @++@@", + "@ xxx xx@@ @", + "@ xxx xxx @", + "@ xxxxxx @", + "@ xxxx @", + "@ xxxxxx @", + "@ xxx xxx @", + "@ xxx xxx @", + "@ xxx xxx @", + "@ @", + "@@@@@@@@@@@@@@@@", + NULL +}; + +static Fl_Pixmap brokenImage(broken_xpm); + +#pragma GCC diagnostic ignored "-Wnarrowing" +struct ENTITY_MAP { + const char *ent; + int elen; + char xlat; +} entityMap[] = { + { + "lsquor;", 8, 130}, { + "fnof;", 5, 131}, { + "ldquor;", 8, 132}, { + "hellip;", 8, 133}, { + "dagger;", 8, 134}, { + "Dagger;", 8, 135}, { + "xxx;", 5, 136}, { + "permil;", 8, 137}, { + "Scaron;", 8, 138}, { + "lsaquo;", 8, 139}, { + "OElig;", 7, 140}, { + "OElig;", 7, 141}, { + "OElig;", 7, 142}, { + "OElig;", 7, 143}, { + "OElig;", 7, 144}, { + "lsquo;", 7, 145}, { + "rsquo;", 7, 146}, { + "ldquo;", 7, 147}, { + "rdquo;", 7, 148}, { + "bull;", 6, 149}, { + "ndash;", 7, 150}, { + "mdash;", 7, 151}, { + "tilde;", 7, 152}, { + "trade;", 7, 153}, { + "scaron;", 8, 154}, { + "rsaquo;", 8, 155}, { + "oelig;", 7, 156}, { + "oelig;", 7, 157}, { + "oelig;", 7, 158}, { + "Yuml;", 6, 159}, { + "nbsp;", 6, ' '}, { + "iexcl;", 7, 161}, { + "cent;", 6, 162}, { + "pound;", 7, 163}, { + "curren;", 8, 164}, { + "yen;", 5, 165}, { + "brvbar;", 8, 166}, { + "sect;", 6, 167}, { + "uml;", 5, 168}, { + "copy;", 6, 169}, { + "ordf;", 6, 170}, { + "laquo;", 7, 171}, { + "not;", 5, 172}, { + "shy;", 5, 173}, { + "reg;", 5, 174}, { + "macr;", 6, 175}, { + "deg;", 5, 176}, { + "plusmn;", 8, 177}, { + "sup2;", 6, 178}, { + "sup3;", 6, 179}, { + "acute;", 7, 180}, { + "micro;", 7, 181}, { + "para;", 6, 182}, { + "middot;", 8, 183}, { + "cedil;", 7, 184}, { + "sup1;", 6, 185}, { + "ordm;", 6, 186}, { + "raquo;", 7, 187}, { + "frac14;", 8, 188}, { + "frac12;", 8, 189}, { + "frac34;", 8, 190}, { + "iquest;", 8, 191}, { + "Agrave;", 8, 192}, { + "Aacute;", 8, 193}, { + "Acirc;", 7, 194}, { + "Atilde;", 8, 195}, { + "Auml;", 6, 196}, { + "Aring;", 7, 197}, { + "AElig;", 7, 198}, { + "Ccedil;", 8, 199}, { + "Egrave;", 8, 200}, { + "Eacute;", 8, 201}, { + "Ecirc;", 7, 202}, { + "Euml;", 6, 203}, { + "Igrave;", 8, 204}, { + "Iacute;", 8, 205}, { + "Icirc;", 7, 206}, { + "Iuml;", 6, 207}, { + "ETH;", 5, 208}, { + "Ntilde;", 8, 209}, { + "Ograve;", 8, 210}, { + "Oacute;", 8, 211}, { + "Ocirc;", 7, 212}, { + "Otilde;", 8, 213}, { + "Ouml;", 6, 214}, { + "times;", 7, 215}, { + "Oslash;", 8, 216}, { + "Ugrave;", 8, 217}, { + "Uacute;", 8, 218}, { + "Ucirc;", 7, 219}, { + "Uuml;", 6, 220}, { + "Yacute;", 8, 221}, { + "THORN;", 7, 222}, { + "szlig;", 7, 223}, { + "agrave;", 8, 224}, { + "aacute;", 8, 225}, { + "acirc;", 7, 226}, { + "atilde;", 8, 227}, { + "auml;", 6, 228}, { + "aring;", 7, 229}, { + "aelig;", 7, 230}, { + "ccedil;", 8, 231}, { + "egrave;", 8, 232}, { + "eacute;", 8, 233}, { + "ecirc;", 7, 234}, { + "euml;", 6, 235}, { + "igrave;", 8, 236}, { + "iacute;", 8, 237}, { + "icirc;", 7, 238}, { + "iuml;", 6, 239}, { + "eth;", 5, 240}, { + "ntilde;", 8, 241}, { + "ograve;", 8, 242}, { + "oacute;", 8, 243}, { + "ocirc;", 7, 244}, { + "otilde;", 8, 245}, { + "ouml;", 6, 246}, { + "divide;", 8, 247}, { + "oslash;", 8, 248}, { + "ugrave;", 8, 249}, { + "uacute;", 8, 250}, { + "ucirc;", 7, 251}, { + "uuml;", 6, 252}, { + "yacute;", 8, 253}, { + "thorn;", 7, 254}, { + "yuml;", 6, 255}, { + "gt;", 4, '>'}, { + "lt;", 4, '<'}, { + "amp;", 5, '&'}, { + "quot;", 6, '\"'},}; + +int entityMapLen = (int)(sizeof(entityMap) / sizeof(entityMap[0])); + +#endif + +#endif diff --git a/src/platform/fltk/MainWindow.cxx b/src/platform/fltk/MainWindow.cxx new file mode 100644 index 00000000..2c608a12 --- /dev/null +++ b/src/platform/fltk/MainWindow.cxx @@ -0,0 +1,1631 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include +#include +#include "platform/fltk/MainWindow.h" +#include "platform/fltk/EditorWidget.h" +#include "platform/fltk/HelpView.h" +#include "platform/fltk/FileWidget.h" +#include "platform/fltk/utils.h" +#include "ui/strlib.h" +#include "common/sbapp.h" +#include "common/sys.h" +#include "common/fs_socket_client.h" +#include "common/keymap.h" + +char *packageHome; +char *runfile = 0; +int recentIndex = 0; +int restart = 0; +bool opt_interactive; +int recentMenu[NUM_RECENT_ITEMS]; +strlib::String recentPath[NUM_RECENT_ITEMS]; +strlib::String recentLabel[NUM_RECENT_ITEMS]; +int recentPosition[NUM_RECENT_ITEMS]; +MainWindow *wnd; +ExecState runMode = init_state; + +const char *fontCache = "fonts.txt"; +const char *endFont = "."; +const char *untitledFile = "untitled.bas"; +const char *fileTabName = "File"; +const char *helpTabName = "Help"; +const char *pluginHome = "plugins"; +const char *historyFile = "history.txt"; +const char *keywordsFile = "keywords.txt"; +const char *recentFile0 = "&File/_Open Recent File/%s %d"; +const char *recentFile = "&File/Open Recent File/%s %d"; + +//--EditWindow functions-------------------------------------------------------- + +void MainWindow::statusMsg(RunMessage runMessage, const char *statusMessage) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + editWidget->statusMsg(statusMessage); + editWidget->runState(runMessage); + } +} + +void MainWindow::busyMessage() { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + editWidget->statusMsg("Selection unavailable while program is running."); + } +} + +void MainWindow::pathMessage(const char *file) { + char message[PATH_MAX]; + snprintf(message, sizeof(message), "File not found: %s", file); + statusMsg(rs_err, message); +} + +void MainWindow::showEditTab(EditorWidget *editWidget) { + if (editWidget) { + _tabGroup->value(editWidget->parent()); + editWidget->take_focus(); + } +} + +/** + * run the give file. returns whether break was hit + */ +bool MainWindow::basicMain(EditorWidget *editWidget, + const char *fullpath, bool toolExec) { + int len = strlen(fullpath); + char path[PATH_MAX]; + bool breakToLine = false; // whether to restore the editor cursor + + if (strcasecmp(fullpath + len - 4, ".htm") == 0 || + strcasecmp(fullpath + len - 5, ".html") == 0) { + // render html edit buffer + snprintf(path, sizeof(path), "file:%s", fullpath); + if (editWidget) { + editWidget->take_focus(); + } + return false; + } + if (access(fullpath, 0) != 0) { + pathMessage(fullpath); + runMode = edit_state; + return false; + } + // start in the directory of the bas program + const char *filename = FileWidget::splitPath(fullpath, path); + + if (editWidget) { + _runEditWidget = editWidget; + if (!toolExec) { + editWidget->readonly(true); + editWidget->runState(rs_run); + breakToLine = editWidget->isBreakToLine(); + opt_ide = editWidget->isHideIDE()? IDE_NONE : IDE_INTERNAL; + } + } + + Fl_Window *fullScreen = NULL; + Fl_Group *oldOutputGroup = _outputGroup; + int old_w = _out->w(); + int old_h = _out->h(); + int interactive = opt_interactive; + + if (!toolExec) { + if (opt_ide == IDE_NONE) { + // run in a separate window with the ide hidden + fullScreen = new BaseWindow(w(), h()); + _profile->restoreAppPosition(fullScreen); + + fullScreen->callback(quit_cb); + fullScreen->add(_out); + fullScreen->resizable(fullScreen); + setTitle(fullScreen, filename); + _outputGroup = fullScreen; + resizeDisplay(w(), h()); + hide(); + } else { + setTitle(this, filename); + } + } else { + opt_interactive = false; + } + + int success; + do { + restart = false; + runMode = run_state; + chdir(path); + success = _system->run(filename); + } + while (restart); + + opt_interactive = interactive; + bool was_break = (runMode == break_state); + + if (fullScreen != NULL) { + _profile->setAppPosition(*fullScreen); + fullScreen->remove(_out); + delete fullScreen; + + _outputGroup = oldOutputGroup; + _outputGroup->add(_out); + resizeDisplay(old_w, old_h); + show(); + } else { + copy_label("SmallBASIC"); + } + + if (runMode == quit_state) { + exit(0); + } + + if (!success || was_break) { + if (!toolExec && editWidget && (!was_break || breakToLine)) { + editWidget->gotoLine(gsb_last_line); + } + if (editWidget) { + showEditTab(editWidget); + editWidget->runState(was_break ? rs_ready : rs_err); + } + } else if (!toolExec && editWidget) { + // normal termination + editWidget->runState(rs_ready); + } + + if (!toolExec && editWidget) { + editWidget->readonly(false); + } + + runMode = edit_state; + _runEditWidget = 0; + return was_break; +} + +//--Menu callbacks-------------------------------------------------------------- + +void MainWindow::close_tab(Fl_Widget *w, void *eventData) { + if (_tabGroup->children() > 1) { + Fl_Group *group = getSelectedTab(); + if (group && group != _outputGroup) { + if (gw_editor == getGroupWidget(group)) { + EditorWidget *editWidget = (EditorWidget *)group->child(0); + if (!editWidget->checkSave(true)) { + return; + } + // check whether the editor is a running program + if (editWidget == _runEditWidget) { + setBreak(); + return; + } + } + _tabGroup->remove(group); + delete group; + } + } +} + +void MainWindow::close_other_tabs(Fl_Widget *w, void *eventData) { + Fl_Group *selected = getSelectedTab(); + int n = _tabGroup->children(); + Fl_Group *items[n]; + for (int c = 0; c < n; c++) { + items[c] = NULL; + Fl_Group *child = (Fl_Group *)_tabGroup->child(c); + if (child != selected && gw_editor == getGroupWidget(child)) { + EditorWidget *editWidget = (EditorWidget *)child->child(0); + if (editWidget != _runEditWidget && editWidget->checkSave(true)) { + items[c] = child; + } + } + } + for (int c = 0; c < n; c++) { + if (items[c] != NULL) { + _tabGroup->remove(items[c]); + delete items[c]; + } + } +} + +void MainWindow::restart_run(Fl_Widget *w, void *eventData) { + if (runMode == run_state) { + setBreak(); + restart = true; + } +} + +void MainWindow::quit(Fl_Widget *w, void *eventData) { + if (runMode == edit_state || runMode == quit_state) { + // auto-save scratchpad + int n = _tabGroup->children(); + for (int c = 0; c < n; c++) { + Fl_Group *group = (Fl_Group *)_tabGroup->child(c); + char path[PATH_MAX]; + if (gw_editor == getGroupWidget(group)) { + EditorWidget *editWidget = (EditorWidget *)group->child(0); + const char *filename = editWidget->getFilename(); + int offs = strlen(filename) - strlen(untitledFile); + if (filename[0] == 0 || (offs > 0 && strcasecmp(filename + offs, untitledFile) == 0)) { + getHomeDir(path, sizeof(path)); + strcat(path, untitledFile); + editWidget->doSaveFile(path); + } else if (!editWidget->checkSave(true)) { + return; + } + } + } + exit(0); + } else { + switch (fl_choice("Terminate running program?", "*Exit", "Break", "Cancel")) { + case 0: + exit(0); + case 1: + setBreak(); + default: + break; + } + } +} + +/** + * opens the smallbasic home page in a browser window + */ +void MainWindow::help_home(Fl_Widget *w, void *eventData) { + browseFile("https://smallbasic.github.io"); +} + +/** + * displays the program help page in a browser window + */ +void MainWindow::help_app(Fl_Widget *w, void *eventData) { + browseFile("https://smallbasic.github.io/pages/fltk.html"); +} + +/** + * handle click from within help window + */ +void MainWindow::help_contents_anchor(Fl_Widget *w, void *eventData) { + String eventName = wnd->getHelp()->getEventName(); + if (eventName.indexOf("http", 0) != -1) { + browseFile(eventName.c_str()); + } else { + char path[PATH_MAX]; + sprintf(path, "https://smallbasic.github.io%s", eventName.c_str()); + browseFile(path); + } +} + +/** + * handle f1 context help + */ +void MainWindow::help_contents(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + int start, end; + char *selection = editWidget->getSelection(&start, &end); + getHelp()->showContextHelp(selection); + free((void *)selection); + } else { + getHelp()->helpIndex(); + } +} + +void MainWindow::help_contents_brief(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + int start, end; + char *selection = editWidget->getSelection(&start, &end); + const char *help = getBriefHelp(selection); + if (help != NULL) { + editWidget->getTty()->print(help); + editWidget->getTty()->print("\n"); + } + free((void *)selection); + } +} + +void MainWindow::help_about(Fl_Widget *w, void *eventData) { + getHelp()->about(); +} + +void MainWindow::export_file(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + static char token[PATH_MAX]; + if (editWidget) { + if (runMode == edit_state) { + int handle = 1; + char buffer[PATH_MAX]; + if (_exportFile.length()) { + strcpy(buffer, _exportFile.c_str()); + } else { + buffer[0] = 0; + } + editWidget->statusMsg("Enter file/address: For mobile SmallBASIC use SOCL::"); + editWidget->getInput(buffer, PATH_MAX); + if (buffer[0]) { + if (strncasecmp(buffer, "SOCL:", 5) == 0) { + editWidget->statusMsg("Enter token:"); + editWidget->getInput(token, PATH_MAX); + } else { + token[0] = 0; + } + _exportFile = buffer; + if (dev_fopen(handle, _exportFile, DEV_FILE_OUTPUT)) { + const char *data = editWidget->data(); + if (token[0]) { + vsncat(buffer, sizeof(buffer), "# ", token, "\n", NULL); + dev_fwrite(handle, (byte *)buffer, strlen(buffer)); + } + if (!dev_fwrite(handle, (byte *)data, editWidget->dataLength())) { + vsncat(buffer, sizeof(buffer), "Failed to write: ", _exportFile.c_str(), NULL); + statusMsg(rs_err, buffer); + } else { + vsncat(buffer, sizeof(buffer), "Exported", editWidget->getFilename(), " to ", + _exportFile.c_str(), NULL); + statusMsg(rs_ready, buffer); + } + } else { + vsncat(buffer, sizeof(buffer), "Failed to open: ", _exportFile.c_str(), NULL); + statusMsg(rs_err, buffer); + } + dev_fclose(handle); + } + // cancel setModal() from editWidget->getInput() + runMode = edit_state; + } else { + busyMessage(); + } + } +} + +void MainWindow::set_options(Fl_Widget *w, void *eventData) { + const char *args = fl_input("Enter program command line", opt_command); + if (args) { + strcpy(opt_command, args); + } +} + +void MainWindow::set_theme(Fl_Widget *w, void *eventData) { + Fl_Group *group = getSelectedTab(); + if (group && group != _outputGroup) { + GroupWidgetEnum gw = getGroupWidget(group); + switch (gw) { + case gw_editor: + // change all editors + _profile->loadEditTheme(((intptr_t) eventData)); + for (int c = 0; c < _tabGroup->children(); c++) { + Fl_Group *child = (Fl_Group *)_tabGroup->child(c); + if (getGroupWidget(child) == gw_editor) { + _profile->setEditTheme((EditorWidget *)child->child(0)); + } + } + break; + case gw_help: + _profile->setHelpTheme((HelpView *)group->child(0), ((intptr_t) eventData)); + break; + default: + break; + } + } + damage(FL_DAMAGE_ALL); +} + +void MainWindow::next_tab(Fl_Widget *w, void *eventData) { + Fl_Group *group = getNextTab(getSelectedTab()); + _tabGroup->value(group); + EditorWidget *editWidget = getEditor(group); + if (editWidget) { + editWidget->take_focus(); + } +} + +void MainWindow::prev_tab(Fl_Widget *w, void *eventData) { + Fl_Group *group = getPrevTab(getSelectedTab()); + _tabGroup->value(group); + EditorWidget *editWidget = getEditor(group); + if (editWidget) { + editWidget->take_focus(); + } +} + +void MainWindow::copy_text(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + editWidget->copyText(); + } else { + handle(EVENT_COPY_TEXT); + } +} + +void MainWindow::font_size_incr(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + int size = editWidget->getFontSize(); + if (size < MAX_FONT_SIZE) { + editWidget->setFontSize(size + 1); + updateConfig(editWidget); + _system->setFontSize(size + 1); + } + } else { + handle(EVENT_INCREASE_FONT); + } +} + +void MainWindow::font_size_decr(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + int size = editWidget->getFontSize(); + if (size > MIN_FONT_SIZE) { + editWidget->setFontSize(size - 1); + updateConfig(editWidget); + _system->setFontSize(size - 1); + } + } else { + handle(EVENT_DECREASE_FONT); + } +} + +void MainWindow::run(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + const char *filename = editWidget->getFilename(); + if (runMode == edit_state) { + // inhibit autosave on run function with environment var + const char *noSave = dev_getenv("NO_RUN_SAVE"); + char path[PATH_MAX]; + if (noSave == 0 || noSave[0] != '1') { + if (filename == 0 || filename[0] == 0) { + getHomeDir(path, sizeof(path)); + strcat(path, untitledFile); + filename = path; + editWidget->doSaveFile(filename); + } else if (access(filename, W_OK) == 0) { + editWidget->doSaveFile(filename); + } + } + basicMain(editWidget, filename, false); + } else { + busyMessage(); + } + } +} + +void MainWindow::run_break(Fl_Widget *w, void *eventData) { + if (runMode == modal_state && !count_tasks()) { + // break from modal edit mode loop + runMode = edit_state; + } else if (runMode == run_state || runMode == modal_state) { + setBreak(); + } +} + +/** + * run the selected text as the main program + */ +void MainWindow::run_selection(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + char path[PATH_MAX]; + getHomeDir(path, sizeof(path)); + strcat(path, "selection.bas"); + editWidget->saveSelection(path); + basicMain(editWidget, path, false); + } +} + +/** + * callback for editor-plug-in plug-ins. we assume the target + * program will be changing the contents of the editor buffer + */ +void MainWindow::editor_plugin(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + Fl_Text_Editor *editor = editWidget->getEditor(); + char filename[PATH_MAX]; + char path[PATH_MAX]; + strcpy(filename, editWidget->getFilename()); + + if (runMode == edit_state) { + if (editWidget->checkSave(false) && filename[0]) { + int pos = editor->insert_position(); + int row, col, s1r, s1c, s2r, s2c; + editWidget->getRowCol(&row, &col); + editWidget->getSelStartRowCol(&s1r, &s1c); + editWidget->getSelEndRowCol(&s2r, &s2c); + snprintf(opt_command, sizeof(opt_command), "%s|%d|%d|%d|%d|%d|%d", + filename, row - 1, col, s1r - 1, s1c, s2r - 1, s2c); + runMode = run_state; + editWidget->runState(rs_run); + snprintf(path, sizeof(path), "%s/%s", packageHome, (const char *)eventData); + int interactive = opt_interactive; + opt_interactive = false; + int success = sbasic_main(path); + opt_interactive = interactive; + editWidget->runState(success ? rs_ready : rs_err); + editWidget->loadFile(filename); + editor->insert_position(pos); + editor->show_insert_position(); + editWidget->setRowCol(row, col + 1); + showEditTab(editWidget); + runMode = edit_state; + opt_command[0] = 0; + } + } else { + busyMessage(); + } + } +} + +/** + * callback for tool-plug-in plug-ins. + */ +void MainWindow::tool_plugin(Fl_Widget *w, void *eventData) { + if (runMode == edit_state) { + char path[PATH_MAX]; + snprintf(opt_command, sizeof(opt_command), "%s/%s", packageHome, pluginHome); + statusMsg(rs_ready, (const char *)eventData); + snprintf(path, sizeof(path), "%s/%s", packageHome, (const char *)eventData); + _tabGroup->value(_outputGroup); + basicMain(0, path, true); + statusMsg(rs_ready, 0); + opt_command[0] = 0; + } else { + busyMessage(); + } +} + +void MainWindow::load_file(Fl_Widget *w, void *eventData) { + int pathIndex = ((intptr_t) eventData) - 1; + const char *path = recentPath[pathIndex].c_str(); + EditorWidget *editWidget = getEditor(path); + if (!editWidget) { + editWidget = getEditor(createEditor(path)); + } + + if (editWidget->checkSave(true)) { + Fl_Text_Editor *editor = editWidget->getEditor(); + // save current position + recentPosition[recentIndex] = editor->insert_position(); + recentIndex = pathIndex; + // load selected file + if (access(path, 0) == 0) { + editWidget->loadFile(path); + // restore previous position + editor->insert_position(recentPosition[recentIndex]); + editWidget->fileChanged(true); + const char *slash = strrchr(path, '/'); + editWidget->parent()->copy_label(slash ? slash + 1 : path); + showEditTab(editWidget); + } else { + pathMessage(path); + } + } +} + +//--Startup functions----------------------------------------------------------- + +/** + *Adds a plug-in to the menu + */ +void MainWindow::addPlugin(Fl_Menu_Bar *menu, const char *label, const char *filename) { + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "%s/%s", pluginHome, filename); + if (access(path, R_OK) == 0) { + menu->add(label, 0, editor_plugin_cb, strdup(path)); + } +} + +/** + * scan for recent files + */ +void MainWindow::scanRecentFiles(Fl_Menu_Bar *menu) { + FILE *fp; + char buffer[PATH_MAX]; + char path[PATH_MAX]; + char label[1024]; + int i = 0; + + getHomeDir(path, sizeof(path)); + strcat(path, historyFile); + fp = fopen(path, "r"); + if (fp) { + while (feof(fp) == 0 && fgets(buffer, sizeof(buffer), fp)) { + buffer[strlen(buffer) - 1] = 0; // trim new-line + if (access(buffer, 0) == 0) { + char *fileLabel = strrchr(buffer, '/'); + if (fileLabel == 0) { + fileLabel = strrchr(buffer, '\\'); + } + fileLabel = fileLabel ? fileLabel + 1 : buffer; + if (fileLabel != 0 && *fileLabel == '_') { + fileLabel++; + } + void *data = (void *)(intptr_t)(i + 1); + snprintf(label, sizeof(label), i == 0 ? recentFile0 : recentFile, fileLabel, i); + recentLabel[i].append(fileLabel); + recentPath[i].append(buffer); + recentMenu[i] = menu->add(label, FL_CTRL + '1' + i, load_file_cb, data); + if (++i == NUM_RECENT_ITEMS) { + break; + } + } + } + fclose(fp); + } + while (i < NUM_RECENT_ITEMS) { + void *data = (void *)(intptr_t)(i + 1); + snprintf(label, sizeof(label), i == 0 ? recentFile0 : recentFile, untitledFile, i); + recentLabel[i].append(untitledFile); + recentPath[i].append(untitledFile); + recentMenu[i] = menu->add(label, FL_CTRL + '1' + i, load_file_cb, data); + i++; + } +} + +/** + * scan for optional plugins + */ +void MainWindow::scanPlugIns(Fl_Menu_Bar *menu) { + char buffer[PATH_MAX]; + char path[PATH_MAX]; + char label[1024]; + + snprintf(path, sizeof(path), "%s/%s", packageHome, pluginHome); + DIR *dp = opendir(path); + while (dp != NULL) { + struct dirent *e = readdir(dp); + if (e == NULL) { + break; + } + const char *filename = e->d_name; + int len = strlen(filename); + + if (strcasecmp(filename + len - 4, ".bas") == 0) { + snprintf(path, sizeof(path), "%s/%s/%s", packageHome, pluginHome, filename); + FILE *file = fopen(path, "r"); + if (!file) { + continue; + } + + if (!fgets(buffer, PATH_MAX, file)) { + fclose(file); + continue; + } + bool editorTool = false; + FileWidget::trimEOL(buffer); + if (strcmp("'tool-plug-in", buffer) == 0) { + editorTool = true; + } else if (strcmp("'app-plug-in", buffer) != 0) { + fclose(file); + continue; + } + + if (fgets(buffer, PATH_MAX, file) && strncmp("'menu", buffer, 5) == 0) { + FileWidget::trimEOL(buffer); + int offs = 6; + while (buffer[offs] && (buffer[offs] == '\t' || buffer[offs] == ' ')) { + offs++; + } + snprintf(label, sizeof(label), (editorTool ? "&Edit/Basic/%s" : "&Basic/%s"), buffer + offs); + // use an absolute path + snprintf(path, sizeof(path), "%s/%s", pluginHome, filename); + menu->add(label, 0, (Fl_Callback *)(editorTool ? editor_plugin_cb : tool_plugin_cb), strdup(path)); + } + fclose(file); + } + } + // cleanup + if (dp) { + closedir(dp); + } +} + +/** + * process the program command line arguments + */ +int arg_cb(int argc, char **argv, int &i) { + const char *s = argv[i]; + int len = strlen(s); + int c; + + if (strcasecmp(s + len - 4, ".bas") == 0 && access(s, 0) == 0) { + runfile = strdup(s); + runMode = run_state; + i += 1; + return 1; + } + + if (argv[i][0] == '-') { + if (!argv[i][2] && argv[i + 1]) { + // commands that take an additional file name argument + switch (argv[i][1]) { + case 'e': + runfile = strdup(argv[i + 1]); + runMode = edit_state; + i += 2; + return 1; + + case 'r': + runfile = strdup(argv[i + 1]); + runMode = run_state; + i += 2; + return 1; + + case 'm': + opt_loadmod = 1; + strcpy(opt_modpath, argv[i + 1]); + i += 2; + return 1; + } + } + + switch (argv[i][1]) { + case 'n': + i += 1; + opt_interactive = true; + return 1; + + case 'v': + i += 1; + opt_verbose = 1; + opt_quiet = 0; + return 1; + + case '-': + // echo foo | sbasic foo.bas -- + while ((c = fgetc(stdin)) != EOF) { + int len = strlen(opt_command); + opt_command[len] = c; + opt_command[len + 1] = 0; + } + i++; + return 1; + } + } + + if (runMode == run_state) { + // remaining text is .bas program argument + if (opt_command[0]) { + strcat(opt_command, " "); + } + strcat(opt_command, s); + i++; + return 1; + } + + return 0; +} + +/** + * start the application in run mode + */ +void run_mode_startup(void *data) { + if (data) { + Fl_Window *w = (Fl_Window *)data; + delete w; + } + + EditorWidget *editWidget = wnd->getEditor(true); + if (editWidget) { + editWidget->setHideIde(true); + editWidget->loadFile(runfile); + + opt_ide = IDE_NONE; + if (!wnd->basicMain(0, runfile, false)) { + exit(0); + } + editWidget->getEditor()->take_focus(); + opt_ide = IDE_INTERNAL; + } +} + +/** + * prepare to start the application + */ +bool initialise(int argc, char **argv) { + opt_graphics = 1; + opt_quiet = 1; + opt_verbose = 0; + opt_nosave = 1; + opt_ide = IDE_INTERNAL; + opt_interactive = true; + opt_file_permitted = 1; + os_graphics = 1; + opt_mute_audio = 1; + + int i = 0; + if (Fl::args(argc, argv, i, arg_cb) < argc) { + fl_message("Options are:\n" + " -e[dit] file.bas\n" + " -r[un] file.bas\n" + " -v[erbose]\n" + " -n[on]-interactive\n" + " -m[odule]-home"); + return false; + } + + // package home contains installed components +#if defined(WIN32) + packageHome = strdup(argv[0]); + char *slash = (char *)FileWidget::forwardSlash(packageHome); + if (slash) { + *slash = 0; + } +#else + packageHome = (char *)PACKAGE_DATA_DIR; +#endif + dev_setenv("PKG_HOME", packageHome); + + // bas_home contains user editable files along with generated help + char path[PATH_MAX]; + getHomeDir(path, sizeof(path), false); + dev_setenv("BAS_HOME", path); + + wnd = new MainWindow(800, 650); + + // load startup editors + wnd->new_file(0, 0); + wnd->_profile->restore(wnd); + + Fl::scheme("gtk+"); + Fl_Window::default_xclass("smallbasic"); + fl_message_title("SmallBASIC"); + wnd->loadIcon(); + Fl::wait(0); + + Fl_Window *run_wnd; + + switch (runMode) { + case run_state: + run_wnd = new Fl_Window(0, 0); + run_wnd->show(); + Fl::add_timeout(0.5f, run_mode_startup, run_wnd); + break; + + case edit_state: + wnd->getEditor(true)->loadFile(runfile); + break; + + default: + runMode = edit_state; + } + + if (runMode != run_state) { + wnd->show(argc, argv); + } + + return true; +} + +/** + * save application state at program exit + */ +void save_profile(void) { + wnd->_profile->save(wnd); +} + +/** + * application entry point + */ +int main(int argc, char **argv) { + int result; + if (initialise(argc, argv)) { + atexit(save_profile); + Fl::run(); + result = 0; + } else { + result = 1; + } + return result; +} + +//--MainWindow methods---------------------------------------------------------- + +MainWindow::MainWindow(int w, int h) : + BaseWindow(w, h) { + _runEditWidget = 0; + _profile = new Profile(); + size_range(250, 250); + + FileWidget::forwardSlash(runfile); + begin(); + Fl_Menu_Bar *m = _menuBar = new Fl_Menu_Bar(0, 0, w, MENU_HEIGHT); + m->add("&File/&New File", FL_CTRL + 'n', new_file_cb); + m->add("&File/&Open File", FL_CTRL + 'o', open_file_cb); + scanRecentFiles(m); + m->add("&File/&Close", FL_CTRL + FL_F+4, close_tab_cb); + m->add("&File/_&Close Others", 0, close_other_tabs_cb); + m->add("&File/&Save File", FL_CTRL + 's', EditorWidget::save_file_cb); + m->add("&File/_Save File &As", FL_CTRL + FL_SHIFT + 'S', save_file_as_cb); + addPlugin(m, "&File/Publish Online", "publish.bas"); + m->add("&File/_Export", FL_CTRL + FL_F+9, export_file_cb); + m->add("&File/E&xit", FL_CTRL + 'q', quit_cb); + m->add("&Edit/_&Undo", FL_CTRL + 'z', EditorWidget::undo_cb); + m->add("&Edit/Cu&t", FL_CTRL + 'x', EditorWidget::cut_text_cb); + m->add("&Edit/&Copy", FL_CTRL + 'c', copy_text_cb); + m->add("&Edit/_&Paste", FL_CTRL + 'v', EditorWidget::paste_text_cb); + m->add("&Edit/_&Select All", FL_CTRL + 'a', EditorWidget::select_all_cb); + m->add("&Edit/&Change Case", FL_ALT + 'c', EditorWidget::change_case_cb); + m->add("&Edit/&Expand Word", FL_ALT + '/', EditorWidget::expand_word_cb); + m->add("&Edit/_&Rename Word", FL_CTRL + FL_SHIFT + 'r', EditorWidget::rename_word_cb); + m->add("&Edit/&Find", FL_CTRL + 'f', EditorWidget::find_cb); + m->add("&Edit/&Replace", FL_CTRL + 'r', EditorWidget::show_replace_cb); + m->add("&Edit/_&Goto Line", FL_CTRL + 'g', EditorWidget::goto_line_cb); + m->add("&View/&Next Tab", FL_F+6, next_tab_cb); + m->add("&View/_&Prev Tab", FL_CTRL + FL_F+6, prev_tab_cb); + m->add("&View/Theme/&Solarized Dark", 0, set_theme_cb, (void *)(intptr_t)0); + m->add("&View/Theme/&Solarized Light", 0, set_theme_cb, (void *)(intptr_t)1); + m->add("&View/Theme/&Shian", 0, set_theme_cb, (void *)(intptr_t)2); + m->add("&View/Theme/&Atom 1", 0, set_theme_cb, (void *)(intptr_t)3); + m->add("&View/Theme/&Atom 2", 0, set_theme_cb, (void *)(intptr_t)4); + m->add("&View/Text Color/_Background", 0, EditorWidget::set_color_cb, (void *)st_background); + m->add("&View/Text Color/Text", 0, EditorWidget::set_color_cb, (void *)st_text); + m->add("&View/Text Color/Comments", 0, EditorWidget::set_color_cb, (void *)st_comments); + m->add("&View/Text Color/_Strings", 0, EditorWidget::set_color_cb, (void *)st_strings); + m->add("&View/Text Color/Keywords", 0, EditorWidget::set_color_cb, (void *)st_keywords); + m->add("&View/Text Color/Funcs", 0, EditorWidget::set_color_cb, (void *)st_funcs); + m->add("&View/Text Color/_Subs", 0, EditorWidget::set_color_cb, (void *)st_subs); + m->add("&View/Text Color/Numbers", 0, EditorWidget::set_color_cb, (void *)st_numbers); + m->add("&View/Text Color/Operators", 0, EditorWidget::set_color_cb, (void *)st_operators); + m->add("&View/Text Color/Find Matches", 0, EditorWidget::set_color_cb, (void *)st_findMatches); + m->add("&View/Text Size/&Increase", FL_CTRL + ']', font_size_incr_cb); + m->add("&View/Text Size/&Decrease", FL_CTRL + '[', font_size_decr_cb); + + scanPlugIns(m); + + m->add("&Program/&Run", FL_F+9, run_cb); + m->add("&Program/_&Run Selection", FL_F+8, run_selection_cb); + m->add("&Program/&Break", FL_CTRL + 'b', run_break_cb); + m->add("&Program/_&Restart", FL_CTRL + 'r', restart_run_cb); + m->add("&Program/&Command", FL_F+10, set_options_cb); + m->add("&Help/&Help Contents", FL_F+1, help_contents_cb); + m->add("&Help/_&Context Help", FL_F+2, help_contents_brief_cb); + m->add("&Help/&Program Help", FL_F+11, help_app_cb); + m->add("&Help/_&Home Page", 0, help_home_cb); + m->add("&Help/&About SmallBASIC", FL_F+12, help_about_cb); + + callback(quit_cb); + + int x1 = 0; + int y1 = MENU_HEIGHT + TAB_BORDER; + int x2 = w; + int y2 = h - MENU_HEIGHT - TAB_BORDER; + + // group for all tabs + _tabGroup = new Fl_Tabs(x1, y1, x2, y2); + + // create the output tab + y1 += MENU_HEIGHT; + y2 -= MENU_HEIGHT; + _outputGroup = new Fl_Group(x1, y1, x2, y2, "Output"); + _outputGroup->labelfont(FL_HELVETICA); + _outputGroup->user_data((void *)gw_output); + _out = new GraphicsWidget(x1, y1 + 1, x2, y2 -1); + _system = new Runtime(x2, y2 - 1, DEF_FONT_SIZE); + _outputGroup->resizable(_out); + _outputGroup->end(); + _tabGroup->resizable(_outputGroup); + _tabGroup->end(); + + end(); + resizable(_tabGroup); +} + +MainWindow::~MainWindow() { + delete _system; +} + +Fl_Group *MainWindow::createTab(GroupWidgetEnum groupWidgetEnum, const char *label) { + _tabGroup->begin(); + Fl_Group * result = new Fl_Group(_out->x(), _out->y(), _out->w(), _out->h(), label); + result->box(FL_NO_BOX); + result->labelfont(FL_HELVETICA); + result->user_data((void *)groupWidgetEnum); + result->begin(); + return result; +} + +/** + * create a new help widget and add it to the tab group + */ +Fl_Group *MainWindow::createEditor(const char *title) { + const char *slash = strrchr(title, '/'); + Fl_Group *editGroup = createTab(gw_editor, slash ? slash + 1 : title); + editGroup->resizable(new EditorWidget(_out, _menuBar)); + editGroup->end(); + _tabGroup->add(editGroup); + _tabGroup->value(editGroup); + _tabGroup->end(); + return editGroup; +} + +void MainWindow::new_file(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = 0; + Fl_Group *untitledEditor = findTab(untitledFile); + char path[PATH_MAX]; + + if (untitledEditor) { + _tabGroup->value(untitledEditor); + editWidget = getEditor(untitledEditor); + } + if (!editWidget) { + editWidget = getEditor(createEditor(untitledFile)); + + // preserve the contents of any existing untitled.bas + getHomeDir(path, sizeof(path)); + strcat(path, untitledFile); + if (access(path, 0) == 0) { + editWidget->loadFile(path); + } + } + + editWidget->selectAll(); +} + +void MainWindow::open_file(Fl_Widget *w, void *eventData) { + FileWidget *fileWidget = NULL; + Fl_Group *openFileGroup = findTab(gw_file); + if (!openFileGroup) { + openFileGroup = createTab(gw_file, fileTabName); + fileWidget = new FileWidget(_out); + openFileGroup->resizable(fileWidget); + openFileGroup->end(); + _tabGroup->end(); + } else { + fileWidget = (FileWidget *)openFileGroup->resizable(); + } + + // change to the directory of the current editor widget + EditorWidget *editWidget = getEditor(false); + char path[PATH_MAX]; + + if (editWidget) { + FileWidget::splitPath(editWidget->getFilename(), path); + } else { + Fl_Group *group = (Fl_Group *)_tabGroup->value(); + GroupWidgetEnum gw = getGroupWidget(group); + switch (gw) { + case gw_output: + strcpy(path, packageHome); + break; + case gw_help: + getHomeDir(path, sizeof(path)); + break; + default: + path[0] = 0; + } + } + + StringList *paths = new StringList(); + for (int i = 0; i < NUM_RECENT_ITEMS; i++) { + char nextPath[PATH_MAX]; + FileWidget::splitPath(recentPath[i].c_str(), nextPath); + if (nextPath[0] && !paths->contains(nextPath)) { + paths->add(nextPath); + } + } + + fileWidget->openPath(path, paths); + _tabGroup->value(openFileGroup); +} + +void MainWindow::save_file_as(Fl_Widget *w, void *eventData) { + EditorWidget *editWidget = getEditor(); + if (editWidget) { + open_file(w, eventData); + FileWidget *fileWidget = (FileWidget *)getSelectedTab()->resizable(); + fileWidget->fileOpen(editWidget); + } +} + +HelpView *MainWindow::getHelp() { + HelpView *help = 0; + Fl_Group *helpGroup = findTab(gw_help); + if (!helpGroup) { + helpGroup = createTab(gw_help, helpTabName); + help = new HelpView(_out); + help->callback(help_contents_anchor_cb); + helpGroup->resizable(help); + _profile->setHelpTheme(help); + _tabGroup->end(); + } else { + help = (HelpView *)helpGroup->resizable(); + } + _tabGroup->value(helpGroup); + return help; +} + +EditorWidget *MainWindow::getEditor(bool select) { + EditorWidget *result = 0; + if (select) { + int n = _tabGroup->children(); + for (int c = 0; c < n; c++) { + Fl_Group *group = (Fl_Group *)_tabGroup->child(c); + if (gw_editor == getGroupWidget(group)) { + result = (EditorWidget *)group->child(0); + _tabGroup->value(group); + break; + } + } + } else { + result = getEditor(getSelectedTab()); + } + return result; +} + +EditorWidget *MainWindow::getEditor(Fl_Group *group) { + EditorWidget *editWidget = 0; + if (group != 0 && gw_editor == getGroupWidget(group)) { + editWidget = (EditorWidget *)group->resizable(); + } + return editWidget; +} + +EditorWidget *MainWindow::getEditor(const char *fullpath) { + if (fullpath != 0 && fullpath[0] != 0) { + int n = _tabGroup->children(); + for (int c = 0; c < n; c++) { + Fl_Group *group = (Fl_Group *)_tabGroup->child(c); + if (gw_editor == getGroupWidget(group)) { + EditorWidget *editWidget = (EditorWidget *)group->child(0); + const char *fileName = editWidget->getFilename(); + if (fileName && strcmp(fullpath, fileName) == 0) { + return editWidget; + } + } + } + } + return NULL; +} + +/** + * called by FileWidget to open the selected file + */ +void MainWindow::editFile(const char *filePath) { + EditorWidget *editWidget = getEditor(filePath); + if (!editWidget) { + editWidget = getEditor(createEditor(filePath)); + editWidget->loadFile(filePath); + } + showEditTab(editWidget); +} + +Fl_Group *MainWindow::getSelectedTab() { + return (Fl_Group *)_tabGroup->value(); +} + +/** + * returns the tab with the given name + */ +Fl_Group *MainWindow::findTab(const char *label) { + int n = _tabGroup->children(); + for (int c = 0; c < n; c++) { + Fl_Group *child = (Fl_Group *)_tabGroup->child(c); + if (strcmp(child->label(), label) == 0) { + return child; + } + } + return 0; +} + +Fl_Group *MainWindow::findTab(GroupWidgetEnum groupWidget) { + int n = _tabGroup->children(); + for (int c = 0; c < n; c++) { + Fl_Group *child = (Fl_Group *)_tabGroup->child(c); + if (groupWidget == getGroupWidget(child)) { + return child; + } + } + return 0; +} + +/** + * find and select the tab with the given tab label + */ +Fl_Group *MainWindow::selectTab(const char *label) { + Fl_Group *tab = findTab(label); + if (tab) { + _tabGroup->value(tab); + } + return tab; +} + +/** + * copies the configuration from the current editor to any remaining editors + */ +void MainWindow::updateConfig(EditorWidget *current) { + int n = _tabGroup->children(); + for (int c = 0; c < n; c++) { + Fl_Group *child = (Fl_Group *)_tabGroup->child(c); + if (getGroupWidget(child) == gw_editor) { + EditorWidget *editWidget = (EditorWidget *)child->child(0); + if (editWidget != current) { + editWidget->updateConfig(current); + _profile->setEditTheme(editWidget); + } + } + } +} + +/** + * updates the names of the editor tabs based on the enclosed editing file + */ +void MainWindow::updateEditTabName(EditorWidget *editWidget) { + int n = _tabGroup->children(); + for (int c = 0; c < n; c++) { + Fl_Group *group = (Fl_Group *)_tabGroup->child(c); + if (gw_editor == getGroupWidget(group) && editWidget == (EditorWidget *)group->child(0)) { + const char *editFileName = editWidget->getFilename(); + if (editFileName && editFileName[0]) { + const char *slash = strrchr(editFileName, '/'); + group->copy_label(slash ? slash + 1 : editFileName); + } + } + } +} + +/** + * returns the tab following the given tab + */ +Fl_Group *MainWindow::getNextTab(Fl_Group *current) { + int n = _tabGroup->children(); + for (int c = 0; c < n - 1; c++) { + Fl_Group *child = (Fl_Group *)_tabGroup->child(c); + if (child == current) { + return (Fl_Group *)_tabGroup->child(c + 1); + } + } + return (Fl_Group *)_tabGroup->child(0); +} + +/** + * returns the tab prior the given tab or null if not found + */ +Fl_Group *MainWindow::getPrevTab(Fl_Group *current) { + int n = _tabGroup->children(); + for (int c = n - 1; c > 0; c--) { + Fl_Group *child = (Fl_Group *)_tabGroup->child(c); + if (child == current) { + return (Fl_Group *)_tabGroup->child(c - 1); + } + } + return (Fl_Group *)_tabGroup->child(n - 1); +} + +/** + * Opens the config file ready for writing + */ +FILE *MainWindow::openConfig(const char *fileName, const char *flags) { + char path[PATH_MAX]; + getHomeDir(path, sizeof(path)); + strcat(path, fileName); + return fopen(path, flags); +} + +bool MainWindow::isBreakExec(void) { + return (runMode == break_state || runMode == quit_state); +} + +bool MainWindow::isRunning(void) { + return (runMode == run_state || runMode == modal_state); +} + +void MainWindow::setModal(bool modal) { + runMode = modal ? modal_state : run_state; +} + +/** + * sets the window title based on the filename + */ +void MainWindow::setTitle(Fl_Window *widget, const char *filename) { + char title[PATH_MAX]; + const char *dot = strrchr(filename, '.'); + int len = (dot ? dot - filename : strlen(filename)); + + strncpy(title, filename, len); + title[len] = 0; + title[0] = toupper(title[0]); + strcat(title, " - SmallBASIC"); + widget->copy_label(title); +} + +void MainWindow::setBreak() { + _system->setExit(false); + runMode = break_state; +} + +bool MainWindow::isModal() { + return (runMode == modal_state); +} + +bool MainWindow::isEdit() { + return (runMode == edit_state); +} + +bool MainWindow::isInteractive() { + return opt_interactive; +} + +bool MainWindow::isIdeHidden() { + return (opt_ide == IDE_NONE); +} + +void MainWindow::resize(int x, int y, int w, int h) { + BaseWindow::resize(x, y, w, h); + _system->resize(_out->w(), _out->h()); +} + +void MainWindow::resizeDisplay(int w, int h) { + _out->resize(_out->x(), _out->y(), w, h - 1); + _system->resize(w, h - 1); +} + +/** + * returns any active tty widget + */ +TtyWidget *MainWindow::tty() { + TtyWidget *result = 0; + EditorWidget *editor = _runEditWidget; + if (!editor) { + editor = getEditor(false); + } + if (editor) { + result = editor->getTty(); + } + return result; +} + +/** + * returns whether printing to the tty widget is active + */ +bool MainWindow::logPrint() { + return (_runEditWidget && _runEditWidget->getTty() && _runEditWidget->isLogPrint()); +} + +int MainWindow::handle(int e) { + int result; + if (getSelectedTab() == _outputGroup && _system->handle(e)) { + result = 1; + } else { + result = BaseWindow::handle(e); + } + return result; +} + +void MainWindow::loadHelp(const char *path) { + if (!getHelp()->loadHelp(path) && getEditor() != NULL) { + getEditor()->statusMsg("Failed to open help file"); + } +} + +/** + * loads the desktop icon + */ +void MainWindow::loadIcon() { + if (!icon()) { +#if defined(WIN32) + HICON ico = (HICON) icon(); + if (!ico) { + ico = (HICON) LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(101), + IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR | LR_SHARED); + if (!ico) { + ico = LoadIcon(NULL, IDI_APPLICATION); + } + } + icon((char *)ico); +#else +#include "icon.h" + Fl_RGB_Image *image = new Fl_PNG_Image(NULL, sb_desktop_128x128_png, sb_desktop_128x128_png_len); + icon(image); +#endif + } +} + +int BaseWindow::handle(int e) { + switch (runMode) { + case run_state: + case modal_state: + switch (e) { + case FL_FOCUS: + // accept key events into handleKeyEvent + return 1; + case FL_PUSH: + if (keymap_invoke(SB_KEY_MK_PUSH)) { + return 1; + } + break; + case FL_DRAG: + if (keymap_invoke(SB_KEY_MK_DRAG)) { + return 1; + } + break; + case FL_MOVE: + if (keymap_invoke(SB_KEY_MK_MOVE)) { + return 1; + } + break; + case FL_RELEASE: + if (keymap_invoke(SB_KEY_MK_RELEASE)) { + return 1; + } + break; + case FL_MOUSEWHEEL: + if (keymap_invoke(SB_KEY_MK_WHEEL)) { + return 1; + } + break; + case FL_SHORTCUT: + case FL_KEYBOARD: + if (handleKeyEvent()) { + // no default processing by Window + return 1; + } + break; + } + break; + + case edit_state: + switch (e) { + case FL_SHORTCUT: + case FL_KEYBOARD: + if (Fl::event_state(FL_CTRL)) { + EditorWidget *editWidget = wnd->getEditor(); + if (editWidget) { + if (Fl::event_key() == FL_F+1) { + // FL_CTRL + F1 key for brief log mode help + wnd->help_contents(0, (void *)true); + return 1; + } + if (editWidget->focusWidget()) { + return 1; + } + } + } + } + break; + + default: + break; + } + + return Fl_Window::handle(e); +} + +bool BaseWindow::handleKeyEvent() { + int k = Fl::event_key(); + bool key_pushed = true; + + switch (k) { + case FL_Tab: + dev_pushkey(SB_KEY_TAB); + break; + case FL_Home: + dev_pushkey(SB_KEY_KP_HOME); + break; + case FL_End: + dev_pushkey(SB_KEY_END); + break; + case FL_Insert: + dev_pushkey(SB_KEY_INSERT); + break; + case FL_Menu: + dev_pushkey(SB_KEY_MENU); + break; + case FL_Multiply: + dev_pushkey(SB_KEY_KP_MUL); + break; + case FL_AddKey: + dev_pushkey(SB_KEY_KP_PLUS); + break; + case FL_SubtractKey: + dev_pushkey(SB_KEY_KP_MINUS); + break; + case FL_DivideKey: + dev_pushkey(SB_KEY_KP_DIV); + break; + case FL_F: + dev_pushkey(SB_KEY_F(0)); + break; + case FL_F+1: + dev_pushkey(SB_KEY_F(1)); + break; + case FL_F+2: + dev_pushkey(SB_KEY_F(2)); + break; + case FL_F+3: + dev_pushkey(SB_KEY_F(3)); + break; + case FL_F+4: + dev_pushkey(SB_KEY_F(4)); + break; + case FL_F+5: + dev_pushkey(SB_KEY_F(5)); + break; + case FL_F+6: + dev_pushkey(SB_KEY_F(6)); + break; + case FL_F+7: + dev_pushkey(SB_KEY_F(7)); + break; + case FL_F+8: + dev_pushkey(SB_KEY_F(8)); + break; + case FL_F+9: + dev_pushkey(SB_KEY_F(9)); + break; + case FL_F+10: + dev_pushkey(SB_KEY_F(10)); + break; + case FL_F+11: + dev_pushkey(SB_KEY_F(11)); + break; + case FL_F+12: + dev_pushkey(SB_KEY_F(12)); + break; + case FL_Page_Up: + dev_pushkey(SB_KEY_PGUP); + break; + case FL_Page_Down: + dev_pushkey(SB_KEY_PGDN); + break; + case FL_Up: + dev_pushkey(SB_KEY_UP); + break; + case FL_Down: + dev_pushkey(SB_KEY_DN); + break; + case FL_Left: + dev_pushkey(SB_KEY_LEFT); + break; + case FL_Right: + dev_pushkey(SB_KEY_RIGHT); + break; + case FL_BackSpace: + dev_pushkey(SB_KEY_BACKSPACE); + break; + case FL_Delete: + dev_pushkey(SB_KEY_DELETE); + break; + case FL_Enter: + case FL_KP_Enter: + dev_pushkey(13); + break; + case 'b': + if (Fl::event_state(FL_CTRL)) { + wnd->run_break(); + key_pushed = false; + break; + } + dev_pushkey(Fl::event_text()[0]); + break; + case 'q': + if (Fl::event_state(FL_CTRL)) { + wnd->quit(); + key_pushed = false; + break; + } + dev_pushkey(Fl::event_text()[0]); + break; + + default: + if (k >= FL_Shift_L && k <= FL_Alt_R) { + // avoid pushing meta-keys + key_pushed = false; + } else if (Fl::event_state(FL_CTRL & FL_ALT)) { + dev_pushkey(SB_KEY_CTRL_ALT(k)); + } else if (Fl::event_state(FL_CTRL)) { + dev_pushkey(SB_KEY_CTRL(k)); + } else if (Fl::event_state(FL_ALT)) { + dev_pushkey(SB_KEY_ALT(k)); + } else { + dev_pushkey(Fl::event_text()[0]); + } + break; + } + return key_pushed; +} + diff --git a/src/platform/fltk/MainWindow.h b/src/platform/fltk/MainWindow.h new file mode 100644 index 00000000..acb4c4b0 --- /dev/null +++ b/src/platform/fltk/MainWindow.h @@ -0,0 +1,166 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef MAIN_WINDOW_H +#define MAIN_WINDOW_H + +#include +#include +#include +#include +#include +#include +#include "platform/fltk/display.h" +#include "platform/fltk/runtime.h" +#include "platform/fltk/EditorWidget.h" +#include "platform/fltk/HelpView.h" +#include "platform/fltk/Profile.h" +#include "platform/fltk/utils.h" + +enum ExecState { + init_state, + edit_state, + run_state, + modal_state, + break_state, + quit_state +}; + +enum GroupWidgetEnum { + gw_editor, + gw_output, + gw_help, + gw_file +}; + +struct MainWindow; +extern MainWindow *wnd; +extern ExecState runMode; + +#ifdef CALLBACK_METHOD +#undef CALLBACK_METHOD +#endif + +#define CALLBACK_METHOD(FN) \ + void FN(Fl_Widget *w=0, void *v=0); \ + static void FN ## _cb(Fl_Widget *w, void *v) { \ + wnd->FN(w, v); \ + } + +struct BaseWindow : public Fl_Double_Window { + BaseWindow(int w, int h) : Fl_Double_Window(w, h, "SmallBASIC") {} + virtual ~BaseWindow() {}; + int handle(int e); + bool handleKeyEvent(); +}; + +struct MainWindow : public BaseWindow { + MainWindow(int w, int h); + virtual ~MainWindow(); + + bool basicMain(EditorWidget *editWidget, const char *filename, bool toolExec); + bool isBreakExec(void); // whether BREAK mode has been entered + bool isRunning(void); // whether a program is running + bool isEdit(); // whether a program is currently being edited + bool isIdeHidden(); // whether to run without the IDE displayed + bool isInteractive(); // whether to run without an interface + bool isModal(); // whether a modal gui loop is active + void addPlugin(Fl_Menu_Bar *menu, const char *label, const char *filename); + void busyMessage(); + int handle(int e); + void loadHelp(const char *path); + void loadIcon(); + void pathMessage(const char *file); + void resize(int x, int y, int w, int h); + void resizeDisplay(int w, int h); + void saveEditConfig(EditorWidget *editWidget); + void scanPlugIns(Fl_Menu_Bar *menu); + void scanRecentFiles(Fl_Menu_Bar *menu); + void setBreak(); + void setModal(bool modal); + void setTitle(Fl_Window *widget, const char *filename); + void showEditTab(EditorWidget *editWidget); + void statusMsg(RunMessage runMessage, const char *filename); + void updateConfig(EditorWidget *current); + void updateEditTabName(EditorWidget *editWidget); + + Fl_Group *createEditor(const char *title); + EditorWidget *getEditor(Fl_Group *group); + EditorWidget *getEditor(const char *fullPath); + EditorWidget *getEditor(bool select = false); + void editFile(const char *filePath); + Fl_Group *getSelectedTab(); + Fl_Group *getNextTab(Fl_Group *current); + Fl_Group *getPrevTab(Fl_Group *current); + Fl_Group *selectTab(const char *label); + Fl_Group *findTab(const char *label); + Fl_Group *findTab(GroupWidgetEnum groupWidget); + GroupWidgetEnum getGroupWidget(Fl_Group *group) { + return (GroupWidgetEnum) (intptr_t) group->user_data(); + } + bool logPrint(); + FILE *openConfig(const char *fileName, const char *flags = "w"); + TtyWidget *tty(); + + CALLBACK_METHOD(close_tab); + CALLBACK_METHOD(close_other_tabs); + CALLBACK_METHOD(copy_text); + CALLBACK_METHOD(cut_text); + CALLBACK_METHOD(editor_plugin); + CALLBACK_METHOD(export_file); + CALLBACK_METHOD(font_size_decr); + CALLBACK_METHOD(font_size_incr); + CALLBACK_METHOD(help_about); + CALLBACK_METHOD(help_app); + CALLBACK_METHOD(help_contents); + CALLBACK_METHOD(help_contents_brief); + CALLBACK_METHOD(help_contents_anchor); + CALLBACK_METHOD(help_home); + CALLBACK_METHOD(hide_ide); + CALLBACK_METHOD(load_file); + CALLBACK_METHOD(new_file); + CALLBACK_METHOD(next_tab); + CALLBACK_METHOD(open_file); + CALLBACK_METHOD(paste_text); + CALLBACK_METHOD(prev_tab); + CALLBACK_METHOD(quit); + CALLBACK_METHOD(restart_run); + CALLBACK_METHOD(run); + CALLBACK_METHOD(run_break); + CALLBACK_METHOD(run_selection); + CALLBACK_METHOD(save_file_as); + CALLBACK_METHOD(set_options); + CALLBACK_METHOD(set_theme); + CALLBACK_METHOD(tool_plugin); + + HelpView *getHelp(); + Fl_Group *createTab(GroupWidgetEnum groupWidgetEnum, const char *label = NULL); + + strlib::String _siteHome; + strlib::String _exportFile; + + // display system + GraphicsWidget *_out; + Runtime *_system; + + // main output + Fl_Group *_outputGroup; + + EditorWidget *_runEditWidget; + + // tab parent + Fl_Tabs *_tabGroup; + + // configuration + Profile *_profile; + + // the system menu + Fl_Menu_Bar *_menuBar; +}; + +#endif diff --git a/src/platform/fltk/Makefile.am b/src/platform/fltk/Makefile.am new file mode 100644 index 00000000..20cff780 --- /dev/null +++ b/src/platform/fltk/Makefile.am @@ -0,0 +1,56 @@ +# SmallBASIC for FLTK +# Copyright(C) 2001-2019 Chris Warren-Smith. +# +# This program is distributed under the terms of the GPL v2.0 or later +# Download the GNU Public License (GPL) from www.gnu.org +# + +AM_CPPFLAGS = \ + -I$(top_builddir)/src -I. \ + -DPACKAGE_PREFIX=\""$(prefix)"\" \ + -DPACKAGE_DATA_DIR=\""$(pkgdatadir)"\" \ + -DPACKAGE_LOCALE_DIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \ + @PACKAGE_CFLAGS@ @FLTK_CXXFLAGS@ + +EXTRA_DIST = \ + $(desktopentry_DATA) + +bin_PROGRAMS = sbasici + +sbasici_SOURCES = \ + ../../ui/ansiwidget.cpp \ + ../../ui/system.cpp \ + ../../ui/window.cpp \ + ../../ui/screen.cpp \ + ../../ui/form.cpp \ + ../../ui/inputs.cpp \ + ../../ui/image.cpp \ + ../../ui/strlib.cpp \ + ../../ui/textedit.cpp \ + display.cxx display.h \ + runtime.cxx runtime.h \ + HelpWidget.cxx HelpWidget.h \ + HelpView.cxx HelpView.h \ + TtyWidget.cxx TtyWidget.h \ + MainWindow.cxx MainWindow.h \ + BasicEditor.cxx BasicEditor.h \ + EditorWidget.cxx EditorWidget.h \ + FileWidget.cxx FileWidget.h \ + Profile.cxx Profile.h \ + utils.cxx utils.h + +sbasici_LDADD = -L$(top_srcdir)/src/common -lsb_common @PACKAGE_LIBS@ + +sbasici_DEPENDENCIES = $(top_srcdir)/src/common/libsb_common.a + +iconsdir = $(datadir)/icons/hicolor/128x128/apps +icons_DATA = ../../../images/sb-desktop-128x128.png +desktopdir = $(datadir)/applications +desktop_DATA = ../sdl/smallbasic.desktop + +if WITH_WIN32 +sbasici_LDADD += win.res +sbasici_DEPENDENCIES += win.res +win.res : win.rc + $(WINDRES) $< -O coff -o $@ +endif diff --git a/src/platform/fltk/Profile.cxx b/src/platform/fltk/Profile.cxx new file mode 100644 index 00000000..48e16a04 --- /dev/null +++ b/src/platform/fltk/Profile.cxx @@ -0,0 +1,394 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include +#include +#include "platform/fltk/Profile.h" +#include "platform/fltk/MainWindow.h" +#include "platform/fltk/utils.h" + +const char *configFile = "fl_config.txt"; +const char *pathKey = "path"; +const char *indentLevelKey = "indentLevel"; +const char *fontNameKey = "fontName"; +const char *fontSizeKey = "fontSize"; +const char *windowPosKey = "windowPos"; +const char *activeTabKey = "activeTab"; +const char *createBackupsKey = "createBackups"; +const char *lineNumbersKey = "lineNumbers"; +const char *appPositionKey = "appPosition"; +const char *themeIdKey = "themeId"; +const char *helpThemeIdKey = "helpThemeId"; + +// in BasicEditor.cxx +extern Fl_Text_Display::Style_Table_Entry styletable[]; + +// +// Profile constructor +// +Profile::Profile() : + _font(FL_COURIER), + _appPosition(0, 0, 640, 480), + _loaded(false), + _createBackups(true), + _lineNumbers(true), + _fontSize(12), + _indentLevel(2), + _helpThemeId(0) { + loadEditTheme(0); + _helpTheme.setId(_helpThemeId); +} + +// +// setup the editor defaults +// +void Profile::loadConfig(EditorWidget *editWidget) { + editWidget->setIndentLevel(_indentLevel); + editWidget->setFont(_font); + editWidget->setFontSize(_fontSize); + editWidget->setTheme(&_theme); + editWidget->getEditor()->linenumber_width(_lineNumbers ? LINE_NUMBER_WIDTH : 1); +} + +// +// select the given theme +// +void Profile::loadEditTheme(int themeId) { + _theme.setId(themeId); + _themeId = themeId; + styletable[0].color = get_color(_theme._color); // A - plain + styletable[1].color = get_color(_theme._syntax_comments); // B - comments + styletable[2].color = get_color(_theme._syntax_text); // C - string + styletable[3].color = get_color(_theme._syntax_statement); // D - keywords + styletable[4].color = get_color(_theme._syntax_command); // E - functions + styletable[5].color = get_color(_theme._syntax_command); // F - procedures + styletable[6].color = get_color(_theme._match_background); // G - find matches + styletable[7].color = get_color(_theme._syntax_comments); // H - comments + styletable[8].color = get_color(_theme._syntax_digit); // I - numbers + styletable[9].color = get_color(_theme._syntax_command); // J - operators + styletable[10].color = get_color(_theme._background); // Background +} + +// +// restore saved settings +// +void Profile::restore(MainWindow *wnd) { + strlib::String buffer; + Properties profile; + + FILE *fp = wnd->openConfig(configFile, "r"); + if (fp) { + fseek(fp, 0, SEEK_END); + long len = ftell(fp); + rewind(fp); + buffer.append(fp, len); + fclose(fp); + profile.load(buffer.c_str(), buffer.length()); + + restoreValue(&profile, indentLevelKey, &_indentLevel); + restoreValue(&profile, createBackupsKey, &_createBackups); + restoreValue(&profile, lineNumbersKey, &_lineNumbers); + restoreValue(&profile, themeIdKey, &_themeId); + restoreValue(&profile, helpThemeIdKey, &_helpThemeId); + restoreStyles(&profile); + + Fl_Rect rc; + rc = restoreRect(&profile, appPositionKey); + if (rc.w() && rc.h()) { + _appPosition = rc; + } + + rc = restoreRect(&profile, windowPosKey); + if (rc.w() && rc.h()) { + restoreWindowPos(wnd, rc); + } + + restoreTabs(wnd, &profile); + } + _loaded = true; +} + +// +// restore the standalone window position +// +void Profile::restoreAppPosition(Fl_Window *wnd) { + if (_appPosition.w() && _appPosition.h()) { + int x = _appPosition.x() != 0 ? _appPosition.x() : wnd->x(); + int y = _appPosition.y() != 0 ? _appPosition.y() : wnd->y(); + wnd->resize(x, y, _appPosition.w(), _appPosition.h()); + } +} + +// +// set editor theme colors +// +void Profile::setEditTheme(EditorWidget *editWidget) { + editWidget->setTheme(&_theme); + editWidget->redraw(); +} + +// +// set help theme colors +// +void Profile::setHelpTheme(HelpWidget *helpWidget, int themeId) { + if (themeId != -1) { + _helpTheme.setId(themeId); + _helpThemeId = themeId; + } + helpWidget->setTheme(&_helpTheme); + helpWidget->damage(FL_DAMAGE_ALL); +} + +// +// persist profile values +// +void Profile::save(MainWindow *wnd) { + if (_loaded) { + // prevent overwriting config when not initially used + FILE *fp = wnd->openConfig(configFile); + if (fp) { + saveValue(fp, indentLevelKey, _indentLevel); + saveValue(fp, createBackupsKey, _createBackups); + saveValue(fp, lineNumbersKey, _lineNumbers); + saveValue(fp, themeIdKey, _themeId); + saveValue(fp, helpThemeIdKey, _helpThemeId); + saveStyles(fp); + saveTabs(fp, wnd); + saveRect(fp, appPositionKey, &_appPosition); + Fl_Rect wndRect(wnd->x(), wnd->y(), wnd->w(), wnd->h()); + saveRect(fp, windowPosKey, &wndRect); + fclose(fp); + } + } +} + +// +// update the theme from the style table +// +void Profile::updateTheme() { + _theme._color = styletable[0].color >> 8; + _theme._syntax_comments = styletable[1].color >> 8; + _theme._syntax_text = styletable[2].color >> 8; + _theme._syntax_statement = styletable[3].color >> 8; + _theme._syntax_command = styletable[4].color >> 8; + _theme._syntax_command = styletable[5].color >> 8; + _theme._match_background = styletable[6].color >> 8; + _theme._syntax_comments = styletable[7].color >> 8; + _theme._syntax_digit = styletable[8].color >> 8; + _theme._syntax_command = styletable[9].color >> 8; + _theme._background = styletable[10].color >> 8; +} + +// +// returns the next integer from the given string +// +int Profile::nextInteger(const char *s, int len, int &index) { + int result = 0; + while (index < len && isdigit(s[index])) { + result = (result * 10) + (s[index] - '0'); + index++; + } + if (s[index] == ';') { + index++; + } + return result; +} + +// +// restore a rectangle value with the given key +// +Fl_Rect Profile::restoreRect(Properties *profile, const char *key) { + Fl_Rect result(0, 0, 0, 0); + String *value = profile->get(key); + if (value != NULL) { + const char *buffer = value->c_str(); + int index = 0; + int len = strlen(buffer); + + result.x(nextInteger(buffer, len, index)); + result.y(nextInteger(buffer, len, index)); + result.w(nextInteger(buffer, len, index)); + result.h(nextInteger(buffer, len, index)); + } + return result; +} + +// +// load any stored font or color settings +// +void Profile::restoreStyles(Properties *profile) { + // restore size and face + loadEditTheme(_themeId); + _helpTheme.setId(_helpThemeId); + + restoreValue(profile, fontSizeKey, &_fontSize); + String *fontName = profile->get(fontNameKey); + if (fontName) { + _font = get_font(fontName->c_str()); + } + + for (int i = 0; i <= st_background; i++) { + char buffer[4]; + sprintf(buffer, "%02d", i); + String *color = profile->get(buffer); + if (color) { + Fl_Color c = get_color(color->c_str(), NO_COLOR); + if (c != (Fl_Color)NO_COLOR) { + styletable[i].color = c; + if (i == st_background) { + _theme._background = c >> 8; + } + } + } + } +} + +// +// restore the editor tabs +// +void Profile::restoreTabs(MainWindow *wnd, Properties *profile) { + bool usedEditor = false; + strlib::List paths; + profile->get(pathKey, &paths); + + List_each(String*, it, paths) { + String *nextString = (*it); + const char *buffer = nextString->c_str(); + int index = 0; + int len = strlen(buffer); + int logPrint = nextInteger(buffer, len, index); + int scrollLock = nextInteger(buffer, len, index); + int hideIde = nextInteger(buffer, len, index); + int gotoLine = nextInteger(buffer, len, index); + int insertPos = nextInteger(buffer, len, index); + int topLineNo = nextInteger(buffer, len, index); + + const char *path = buffer + index; + + EditorWidget *editWidget = 0; + if (usedEditor) { + // constructor will call loadConfig + Fl_Group *group = wnd->createEditor(path); + editWidget = wnd->getEditor(group); + } else { + // load into the initial buffer + editWidget = wnd->getEditor(true); + loadConfig(editWidget); + usedEditor = true; + } + + editWidget->loadFile(path); + editWidget->setHideIde(hideIde); + editWidget->setLogPrint(logPrint); + editWidget->setScrollLock(scrollLock); + editWidget->setBreakToLine(gotoLine); + editWidget->getEditor()->insert_position(insertPos); + editWidget->getEditor()->show_insert_position(); + editWidget->getEditor()->scroll(topLineNo, 0); + } + + // restore the active tab + String *activeTab = profile->get(activeTabKey); + if (activeTab != NULL) { + EditorWidget *editWidget = wnd->getEditor(activeTab->c_str()); + if (editWidget) { + wnd->showEditTab(editWidget); + } + } +} + +// +// restore the int value +// +void Profile::restoreValue(Properties *p, const char *key, int *value) { + String *s = p->get(key); + if (s) { + *value = s->toInteger(); + } +} + +// +// restore the main window position +// +void Profile::restoreWindowPos(MainWindow *wnd, Fl_Rect &rc) { + int x = rc.x(); + int y = rc.y(); + int w = rc.w(); + int h = rc.h(); + if (x > 0 && y > 0 && w > 100 && h > 100) { + if (x < Fl::w() && y < Fl::h()) { + wnd->resize(x, y, w, h); + } + } +} + +// +// save the window position +// +void Profile::saveRect(FILE *fp, const char *key, Fl_Rect *rc) { + fprintf(fp, "%s=%d;%d;%d;%d\n", key, rc->x(), rc->y(), rc->w(), rc->h()); +} + +// +// saves the current font size, face and colour configuration +// +void Profile::saveStyles(FILE *fp) { + uint8_t r, g, b; + + saveValue(fp, fontSizeKey, (int)styletable[0].size); + saveValue(fp, fontNameKey, styletable[0].font); + + for (int i = 0; i <= st_background; i++) { + Fl::get_color(styletable[i].color, r, g, b); + fprintf(fp, "%02d=#%02x%02x%02x\n", i, r, g, b); + } +} + +// +// persist the editor tabs +// +void Profile::saveTabs(FILE *fp, MainWindow *wnd) { + int n = wnd->_tabGroup->children(); + for (int c = 0; c < n; c++) { + Fl_Group *group = (Fl_Group *) wnd->_tabGroup->child(c); + if (gw_editor == ((GroupWidgetEnum) (intptr_t) group->user_data())) { + EditorWidget *editWidget = (EditorWidget *) group->child(0); + + bool logPrint = editWidget->isLogPrint(); + bool scrollLock = editWidget->isScrollLock(); + bool hideIde = editWidget->isHideIDE(); + bool gotoLine = editWidget->isBreakToLine(); + int insertPos = editWidget->getEditor()->insert_position(); + int topLineNo = editWidget->top_line(); + + fprintf(fp, "%s='%d;%d;%d;%d;%d;%d;%s'\n", pathKey, + logPrint, scrollLock, hideIde, gotoLine, insertPos, topLineNo, editWidget->getFilename()); + } + } + + // save the active tab + EditorWidget *editWidget = wnd->getEditor(false); + if (editWidget) { + saveValue(fp, activeTabKey, editWidget->getFilename()); + } +} + +// +// persist a single value +// +void Profile::saveValue(FILE *fp, const char *key, const char *value) { + fprintf(fp, "%s='%s'\n", key, value); +} + +// +// persist a single value +// +void Profile::saveValue(FILE *fp, const char *key, int value) { + fprintf(fp, "%s=%d\n", key, value); +} diff --git a/src/platform/fltk/Profile.h b/src/platform/fltk/Profile.h new file mode 100644 index 00000000..9a57ab95 --- /dev/null +++ b/src/platform/fltk/Profile.h @@ -0,0 +1,65 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef PROFILE_H +#define PROFILE_H + +#include +#include "ui/strlib.h" +#include "ui/textedit.h" + +using namespace strlib; + +struct MainWindow; +struct EditorWidget; +class HelpWidget; + +struct Profile { + Profile(); + + bool createBackups() const { return _createBackups; } + void loadConfig(EditorWidget *editor); + void loadEditTheme(int themeId); + void restore(MainWindow *wnd); + void restoreAppPosition(Fl_Window *wnd); + void setAppPosition(Fl_Rect rect) { _appPosition = rect; } + void setEditTheme(EditorWidget *editor); + void setHelpTheme(HelpWidget *help, int themeId = -1); + void setFont(Fl_Font font) { _font = font; } + void setFontSize(int size) { _fontSize = size; } + void save(MainWindow *wnd); + void updateTheme(); + +private: + Fl_Font _font; + Fl_Rect _appPosition; + EditTheme _theme; + EditTheme _helpTheme; + bool _loaded; + int _createBackups; + int _lineNumbers; + int _fontSize; + int _indentLevel; + int _themeId; + int _helpThemeId; + + int nextInteger(const char *s, int len, int &index); + Fl_Rect restoreRect(Properties *profile, const char *key); + void restoreStyles(Properties *profile); + void restoreTabs(MainWindow *wnd, Properties *profile); + void restoreValue(Properties *profile, const char *key, int *value); + void restoreWindowPos(MainWindow *wnd, Fl_Rect &rc); + void saveRect(FILE *fp, const char *key, Fl_Rect *wnd); + void saveStyles(FILE *fp); + void saveTabs(FILE *fp, MainWindow *wnd); + void saveValue(FILE *fp, const char *key, const char *value); + void saveValue(FILE *fp, const char *key, int value); +}; + +#endif + diff --git a/src/platform/fltk/TtyWidget.cxx b/src/platform/fltk/TtyWidget.cxx new file mode 100644 index 00000000..81c6f367 --- /dev/null +++ b/src/platform/fltk/TtyWidget.cxx @@ -0,0 +1,614 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include +#include "platform/fltk/TtyWidget.h" + +static void scrollbar_callback(Fl_Widget *scrollBar, void *widget) { + ((Fl_Group *)widget)->redraw(); +} + +// +// TtyWidget constructor +// +TtyWidget::TtyWidget(int x, int y, int w, int h, int numRows) : + Fl_Group(x, y, w, h, 0) { + + // initialize the buffer + buffer = new TtyRow[numRows]; + rows = numRows; + cols = width = 0; + head = tail = 0; + markX = markY = pointX = pointY = 0; + + setfont(FL_COURIER, DEF_FONT_SIZE); + scrollLock = false; + + begin(); + // vertical scrollbar scrolls in row units + vscrollbar = new Fl_Scrollbar(w - SCROLL_W, y, x + SCROLL_W, h); + vscrollbar->type(FL_VERTICAL); + vscrollbar->user_data(this); + vscrollbar->callback(scrollbar_callback); + + // horizontal scrollbar scrolls in pixel units + hscrollbar = new Fl_Scrollbar(w - HSCROLL_W - SCROLL_W, 1, HSCROLL_W, SCROLL_H); + hscrollbar->type(FL_HORIZONTAL); + hscrollbar->user_data(this); + hscrollbar->callback(scrollbar_callback); + + end(); + resize(x, y, w, h); +} + +TtyWidget::~TtyWidget() { + delete [] buffer; +} + +// +// draw the text +// +void TtyWidget::draw() { + // get the text drawing rectangle + Fl_Rect rc = Fl_Rect(x(), y(), w(), h()); + if (vscrollbar->visible()) { + rc.w(rc.w() - vscrollbar->w()); + } + // prepare escape state variables + bool bold = false; + bool italic = false; + bool underline = false; + bool invert = false; + + // calculate rows to display + int pageRows = getPageRows(); + int textRows = getTextRows(); + int vscroll = vscrollbar->value(); + int hscroll = hscrollbar->value(); + int numRows = textRows < pageRows ? textRows : pageRows; + int firstRow = tail + vscroll; // from start plus scroll offset + + // setup the background colour + fl_color(color()); + fl_rectf(rc.x(), rc.y(), rc.w(), rc.h()); + fl_push_clip(rc.x(), rc.y(), rc.w(), rc.h()); + fl_color(fl_color_average(labelcolor(), color(), .33f)); + fl_rectf(rc.x(), rc.y(), rc.w(), 1); + fl_color(labelcolor()); + fl_font(labelfont(), labelsize()); + + int pageWidth = 0; + for (int row = firstRow, rows = 0, y = rc.y() + lineHeight; rows < numRows; row++, rows++, y += lineHeight) { + TtyRow *line = getLine(row); // next logical row + TtyTextSeg *seg = line->head; + int x = rc.x() + 2 - hscroll; + while (seg != NULL) { + if (seg->escape(&bold, &italic, &underline, &invert)) { + setfont(bold, italic); + } + drawSelection(seg, NULL, row, x, y); + int width = seg->width(); + if (seg->str) { + if (invert) { + fl_color(labelcolor()); + fl_rectf(x, (y - lineHeight) + fl_descent(), width, lineHeight); + fl_color(color()); + fl_draw(seg->str, x, y); + fl_color(labelcolor()); + } else { + fl_draw(seg->str, x, y); + } + } + if (underline) { + fl_line(x, y + 1, x + width, y + 1); + } + x += width; + seg = seg->next; + } + int rowWidth = line->width(); + if (rowWidth > pageWidth) { + pageWidth = rowWidth; + } + } + + // draw scrollbar controls + if (pageWidth > w()) { + draw_child(*hscrollbar); + } + fl_pop_clip(); + draw_child(*vscrollbar); +} + +// +// draw the background for selected text +// +void TtyWidget::drawSelection(TtyTextSeg *seg, strlib::String *s, int row, int x, int y) { + if (markX != pointX || markY != pointY) { + Fl_Rect rc(0, y - fl_height(), 0, lineHeight); + int r1 = markY; + int r2 = pointY; + int x1 = markX; + int x2 = pointX; + + if (r1 > r2) { + r1 = pointY; + r2 = markY; + x1 = pointX; + x2 = markX; + } + if (r1 == r2 && x1 > x2) { + x1 = pointX; + x2 = markX; + } + if (row > r1 && row < r2) { + // entire row + rc.x(x); + rc.w(seg->width()); + if (s) { + s->append(seg->str); + } + } else if (row == r1 && (r2 > r1 || x < x2)) { + // top selection row + int i = 0; + int len = seg->numChars(); + + // find start of selection + while (x < x1 && i < len) { + x += fl_width(seg->str + (i++), 1); + } + rc.x(x); + + // select rest of line when r2>r1 + while ((r2 > r1 || x < x2) && i < len) { + if (s) { + s->append(seg->str[i]); + } + x += fl_width(seg->str + (i++), 1); + } + rc.w(x - rc.x()); + } else if (row == r2) { + // bottom selection row + rc.x(x); + + // select rest of line when r2>r1 + int i = 0; + int len = seg->numChars(); + while (x < x2 && i < len) { + if (s) { + s->append(seg->str[i]); + } + x += fl_width(seg->str + (i++), 1); + } + rc.w(x - rc.x()); + } + + if (!s && (rc.w() || rc.h())) { + fl_color(selection_color()); + fl_rectf(rc.x(), rc.y(), rc.w(), rc.h()); + fl_color(labelcolor()); + } + } +} + +// +// process mouse messages +// +int TtyWidget::handle(int e) { + static bool leftButtonDown = false; + switch (e) { + case FL_PUSH: + if ((!vscrollbar->visible() || !Fl::event_inside(vscrollbar)) && + (!hscrollbar->visible() || !Fl::event_inside(hscrollbar))) { + bool selected = (markX != pointX || markY != pointY); + if (selected && Fl::event_button() == FL_RIGHT_MOUSE) { + // right click to copy selection + copySelection(); + } + markX = pointX = Fl::event_x(); + markY = pointY = rowEvent(); + if (selected) { + // draw end selection + damage(DAMAGE_HIGHLIGHT); + } + leftButtonDown = true; + return 1; // become belowmouse to receive RELEASE event + } + break; + + case FL_DRAG: + case FL_MOVE: + if (leftButtonDown) { + pointX = Fl::event_x(); + pointY = rowEvent(); + damage(DAMAGE_HIGHLIGHT); + if (vscrollbar->visible()) { + // drag to scroll up or down + int value = vscrollbar->value(); + if (Fl::event_y() < 0 && value > 0) { + vscrollbar->value(value - 1); + } else if ((Fl::event_y() > h()) && (value + getPageRows() < getTextRows())) { + vscrollbar->value(value + 1); + } + } + } + return 1; + + case FL_RELEASE: + leftButtonDown = false; + return 1; + + case FL_MOUSEWHEEL: + if (vscrollbar->visible()) { + return vscrollbar->handle(e); + } + break; + } + + return Fl_Group::handle(e); +} + +// +// update scrollbar positions +// +void TtyWidget::resize(int x, int y, int w, int h) { + Fl_Group::resize(x, y, w, h); + + int pageRows = getPageRows(); + int textRows = getTextRows(); + int hscrollX = w - HSCROLL_W; + int hscrollW = w - 4; + + if (textRows > pageRows && h > SCROLL_W) { + vscrollbar->set_visible(); + int value = vscrollbar->value(); + if (value + pageRows > textRows) { + // prevent value from extending beyond the buffer range + value = textRows - pageRows; + } + vscrollbar->resize(x + w - SCROLL_W, y, SCROLL_W, h); + vscrollbar->value(value, pageRows, 0, textRows); + hscrollX -= SCROLL_W; + hscrollW -= SCROLL_W; + } else { + vscrollbar->clear_visible(); + vscrollbar->value(0); + } + if (width > hscrollW) { + hscrollbar->set_visible(); + hscrollbar->resize(hscrollX, y, HSCROLL_W, SCROLL_H); + hscrollbar->value(hscrollbar->value(), hscrollW, 0, width); + } else { + hscrollbar->clear_visible(); + hscrollbar->value(0); + } +} + +// +// copy selected text to the clipboard +// +bool TtyWidget::copySelection() { + int hscroll = hscrollbar->value(); + bool bold = false; + bool italic = false; + bool underline = false; + bool invert = false; + int r1 = markY; + int r2 = pointY; + + if (r1 > r2) { + r1 = pointY; + r2 = markY; + } + + strlib::String selection; + + for (int row = r1; row <= r2; row++) { + TtyRow *line = getLine(row); // next logical row + TtyTextSeg *seg = line->head; + int x = 2 - hscroll; + strlib::String rowText; + while (seg != NULL) { + if (seg->escape(&bold, &italic, &underline, &invert)) { + setfont(bold, italic); + } + drawSelection(seg, &rowText, row, x, 0); + x += seg->width(); + seg = seg->next; + } + if (rowText.length()) { + selection.append(rowText); + selection.append("\n"); + } + } + + bool result = selection.length() > 0; + if (result) { + const char *copy = selection.c_str(); + Fl::copy(copy, strlen(copy), true); + } + return result; +} + +// +// clear the screen +// +void TtyWidget::clearScreen() { + head = tail = 0; + cols = width = 0; + markX = markY = pointX = pointY = 0; + getLine(0)->clear(); + vscrollbar->value(0); + vscrollbar->hide(); + hscrollbar->value(0); + hscrollbar->hide(); + redraw(); +} + +// +// process incoming text +// +void TtyWidget::print(const char *str) { + int strLength = strlen(str); + TtyRow *line = getLine(head); // pointer to current line + + // need the current font set to calculate text widths + fl_font(labelfont(), labelsize()); + + // scan the text, handle any special characters, and display the rest. + for (int i = 0; i < strLength; i++) { + // check for telnet IAC codes + switch (str[i]) { + case '\r': // return + // move to the start of the line + break; + + case '\a': + // beep! + break; + + case '\n': // new line + // scroll by moving logical last line + if (getTextRows() == rows) { + tail = (tail + 1 >= rows) ? 0 : tail + 1; + } + head = (head + 1 >= rows) ? 0 : head + 1; + + // clear the new line + line = getLine(head); + line->clear(); + break; + + case '\b': // backspace + break; + + case '\t': + line->tab(); + break; + + case '\xC': + clearScreen(); + break; + + default: + i += processLine(line, &str[i]); + } // end case + } + + if (!scrollLock) { + vscrollbar->value(getTextRows() - getPageRows()); + } + + // schedule a layout and redraw + resize(x(), y(), w(), h()); + redraw(); +} + +// +// return a pointer to the specified line of the display. +// +TtyRow *TtyWidget::getLine(int pos) { + if (pos < 0) { + pos += rows; + } + if (pos > rows - 1) { + pos -= rows; + } + + return &buffer[pos]; +} + +// +// interpret ANSI escape codes in linePtr and return number of chars consumed +// +int TtyWidget::processLine(TtyRow *line, const char *linePtr) { + TtyTextSeg *segment = new TtyTextSeg(); + line->append(segment); + + const char *linePtrStart = linePtr; + + // Determine if we are at an end-of-line or an escape + if (*linePtr == '\033') { + linePtr++; + + bool escaped = false; + if (*linePtr == '[') { + escaped = true; + linePtr++; + } + + if (escaped) { + int param = 0; + while (*linePtr != '\0' && escaped) { + // Walk the escape sequence + switch (*linePtr) { + case 'm': + escaped = false; // fall through + + case ';': // Parameter seperator + setGraphicsRendition(segment, param); + param = 0; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // Numeric OK; continue till delimeter or illegal char + param = (param * 10) + (*linePtr - '0'); + break; + + default: // Illegal character - reset + segment->flags = 0; + escaped = false; + break; + } + linePtr += 1; + } // while escaped and not null + } + } + + const char *linePtrNext = linePtr; + + // Walk the line of text until an escape char or end-of-line + // is encountered. + while (*linePtr > 31) { + linePtr++; + } + + // Print the next (possible) line of text + if (*linePtrNext != '\0' && linePtrNext != linePtr) { + segment->setText(linePtrNext, (linePtr - linePtrNext)); + + // save max rows encountered + int lineWidth = line->width(); + if (lineWidth > width) { + width = lineWidth; + } + + int lineChars = line->numChars(); + if (lineChars > cols) { + cols = lineChars; + } + } + // return the number of eaten chars (less 1) + return linePtr == linePtrStart ? 0 : (linePtr - linePtrStart) - 1; +} + +// +// performs the ANSI text SGI function. +// +void TtyWidget::setGraphicsRendition(TtyTextSeg *segment, int c) { + switch (c) { + case 0: + segment->reset(); + break; + + case 1: // Bold on + segment->set(TtyTextSeg::BOLD, true); + break; + + case 2: // Faint on + segment->set(TtyTextSeg::BOLD, false); + break; + + case 3: // Italic on + segment->set(TtyTextSeg::ITALIC, true); + break; + + case 4: // Underscrore + segment->set(TtyTextSeg::UNDERLINE, true); + break; + + case 7: // reverse video on + segment->set(TtyTextSeg::INVERT, true); + break; + + case 21: // set bold off + segment->set(TtyTextSeg::BOLD, false); + break; + + case 23: + segment->set(TtyTextSeg::ITALIC, false); + break; + + case 24: // set underline off + segment->set(TtyTextSeg::UNDERLINE, false); + break; + + case 27: // reverse video off + segment->set(TtyTextSeg::INVERT, false); + break; + + case 30: // Black + segment->color = FL_BLACK; + break; + + case 31: // Red + segment->color = FL_RED; + break; + + case 32: // Green + segment->color = FL_GREEN; + break; + + case 33: // Yellow + segment->color = FL_YELLOW; + break; + + case 34: // Blue + segment->color = FL_BLUE; + break; + + case 35: // Magenta + segment->color = FL_MAGENTA; + break; + + case 36: // Cyan + segment->color = FL_CYAN; + break; + + case 37: // White + segment->color = FL_WHITE; + break; + } +} + +// +// update the current drawing font +// +void TtyWidget::setfont(bool bold, bool italic) { + Fl_Font font = labelfont(); + if (bold) { + font += FL_BOLD; + } + if (italic) { + font += FL_ITALIC; + } + fl_font(font, labelsize()); +} + +// +// update the current drawing font and remember the face/size +// +void TtyWidget::setfont(Fl_Font font, int size) { + if (font) { + labelfont(font); + } + if (size) { + labelsize(size); + } + fl_font(labelfont(), labelsize()); + lineHeight = fl_height() + fl_descent(); +} diff --git a/src/platform/fltk/TtyWidget.h b/src/platform/fltk/TtyWidget.h new file mode 100644 index 00000000..b8a25257 --- /dev/null +++ b/src/platform/fltk/TtyWidget.h @@ -0,0 +1,269 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef Fl_TTY_WIDGET +#define Fl_TTY_WIDGET + +#include +#include +#include +#include +#include +#include +#include "ui/strlib.h" +#include "platform/fltk/utils.h" + +using namespace strlib; + +struct Point { + int x; + int y; +}; + +struct TtyTextSeg { + enum { + BOLD = 0x00000001, + ITALIC = 0x00000002, + UNDERLINE = 0x00000004, + INVERT = 0x00000008, + }; + + // create a new segment + TtyTextSeg() { + this->str = 0; + this->flags = 0; + this->color = 0; + this->next = 0; + } + + ~TtyTextSeg() { + if (str) { + delete[]str; + } + } + + // reset all flags + void reset() { + set(BOLD, false); + set(ITALIC, false); + set(UNDERLINE, false); + set(INVERT, false); + } + + void setText(const char *str, int n) { + if ((!str || !n)) { + this->str = 0; + } else { + this->str = new char[n + 1]; + strncpy(this->str, str, n); + this->str[n] = 0; + } + } + + // create a string of n spaces + void tab(int n) { + this->str = new char[n + 1]; + memset(this->str, ' ', n); + this->str[n] = 0; + } + + // set the flag value + void set(int f, bool value) { + if (value) { + flags |= f; + } else { + flags &= ~f; + } + flags |= (f << 16); + } + + // return whether the flag was set (to true or false) + bool set(int f) { + return (flags & (f << 16)); + } + + // return the flag value if set, otherwise return value + bool get(int f, bool *value) { + bool result = *value; + if (flags & (f << 16)) { + result = (flags & f); + } + return result; + } + + // width of this segment in pixels + int width() { + return !str ? 0 : fl_width(str); + } + + // number of chars in this segment + int numChars() { + return !str ? 0 : strlen(str); + } + + // update font and state variables when set in this segment + bool escape(bool *bold, bool *italic, bool *underline, bool *invert) { + *bold = get(BOLD, bold); + *italic = get(ITALIC, italic); + *underline = get(UNDERLINE, underline); + *invert = get(INVERT, invert); + + if (this->color) { + fl_color(this->color); + } + + return set(BOLD) || set(ITALIC); + } + + char *str; + int flags; + Fl_Color color; + TtyTextSeg *next; +}; + +struct TtyRow { + TtyRow() : head(0) { + } + ~TtyRow() { + clear(); + } + + // append a segment to this row + void append(TtyTextSeg *node) { + if (!head) { + head = node; + } else { + tail(head)->next = node; + } + node->next = 0; + } + + // clear the contents of this row + void clear() { + remove(head); + head = 0; + } + + // number of characters in this row + int numChars() { + return numChars(this->head); + } + + int numChars(TtyTextSeg *next) { + int n = 0; + if (next) { + n = next->numChars() + numChars(next->next); + } + return n; + } + + void remove(TtyTextSeg *next) { + if (next) { + remove(next->next); + delete next; + } + } + + // move to the tab position + void tab() { + int tabSize = 6; + int num = numChars(this->head); + int pos = tabSize - (num % tabSize); + if (pos) { + TtyTextSeg *next = new TtyTextSeg(); + next->tab(pos); + append(next); + } + } + + TtyTextSeg *tail(TtyTextSeg *next) { + return !next->next ? next : tail(next->next); + } + + int width() { + return width(this->head); + } + + int width(TtyTextSeg *next) { + int n = 0; + if (next) { + n = next->width() + width(next->next); + } + return n; + } + + TtyTextSeg *head; +}; + +struct TtyWidget : public Fl_Group { + TtyWidget(int x, int y, int w, int h, int numRows); + virtual ~TtyWidget(); + + // inherited methods + void draw(); + int handle(int e); + void resize(int x, int y, int w, int h); + + // public api + void clearScreen(); + bool copySelection(); + void print(const char *str); + void setFont(Fl_Font font) { + setfont(font, 0); + redraw(); + }; + void setFontSize(int size) { + setfont(0, size); + redraw(); + }; + void setScrollLock(bool b) { + scrollLock = b; + }; + +private: + void drawSelection(TtyTextSeg *seg, strlib::String *s, int row, int x, int y); + TtyRow *getLine(int ndx); + int processLine(TtyRow *line, const char *linePtr); + void setfont(bool bold, bool italic); + void setfont(Fl_Font font, int size); + void setGraphicsRendition(TtyTextSeg *segment, int c); + + // returns the number of display text rows held in the buffer + int getTextRows() { + return 1 + ((head >= tail) ? (head - tail) : head + (rows - tail)); + } + + // returns the number of rows available for display + int getPageRows() { + return (h() - 1) / lineHeight; + } + + // returns the selected row within the circular buffer + int rowEvent() { + return ((Fl::event_y() - y()) / lineHeight) + tail + vscrollbar->value(); + } + + // buffer management + TtyRow *buffer; + int head; // current head of buffer + int tail; // buffer last line + int rows; // total number of rows - size of buffer + int cols; // maximum number of characters in a row + int width; // the maximum width of the buffer text in pixels + + // scrollbars + Fl_Scrollbar *vscrollbar; + Fl_Scrollbar *hscrollbar; + int lineHeight; + bool scrollLock; + + // clipboard handling + int markX, markY, pointX, pointY; +}; + +#endif diff --git a/src/platform/fltk/build_kwp.cxx b/src/platform/fltk/build_kwp.cxx new file mode 100644 index 00000000..b089b866 --- /dev/null +++ b/src/platform/fltk/build_kwp.cxx @@ -0,0 +1,125 @@ +/** + * builds the kwp.cxx (keyword tables) + */ +#include +#include +#include +#include +#include + +char line[256]; +char keyword[256]; + +typedef struct { + char key[64]; +} kw_t; + +kw_t ktable[2048]; +int kcount; + +#define code_t int +#define bid_t int +#define fcode_t int +#define pcode_t int + +struct keyword_s { + char name[16]; + code_t code; +}; + +struct opr_keyword_s { + char name[16]; + code_t code; + code_t opr; +}; + +struct func_keyword_s { + char name[16]; + fcode_t fcode; +}; + +struct proc_keyword_s { + char name[16]; + pcode_t pcode; +}; + +struct spopr_keyword_s { + char name[16]; + code_t code; +}; + +#include "../../common/kw.h" +#include "../../languages/keywords.en.c" + +void add_key(const char *key) { + strcpy(ktable[kcount].key, key); + kcount++; +} + +int key_cmp(const void *a, const void *b) { + return strcasecmp(((kw_t *) a)->key, ((kw_t *) b)->key); +} + +void sort_ktable() { + qsort(ktable, kcount, sizeof(kw_t), key_cmp); +} + +void print_ktable(FILE *fp) { + int i; + for (i = 0; i < kcount; i++) { + fprintf(fp, "\"%s\"", ktable[i].key); + if (i != kcount - 1) { + fprintf(fp, ", "); + } + if (((i + 1) % 8) == 0) { + fprintf(fp, "\n"); + } + } +} + +int main(int argc, char *argv[]) { + FILE *fo = fopen("kwp.h", "wt"); + + kcount = 0; + + int code_keywords_len = 0; + int code_procedures_len = 0; + int code_functions_len = 0; + + fprintf(fo, "const char *code_keywords[] = {\n"); + for (int i = 0; keyword_table[i].name[0] != '\0'; i++) { + if (keyword_table[i].name[0] != '$') { + add_key(keyword_table[i].name); + code_keywords_len++; + } + } + sort_ktable(); + print_ktable(fo); + + kcount = 0; + fprintf(fo, "};\n\nconst char *code_procedures[] = {\n"); + for (int i = 0; proc_table[i].name[0] != '\0'; i++) { + add_key(proc_table[i].name); + code_procedures_len++; + } + sort_ktable(); + print_ktable(fo); + + kcount = 0; + fprintf(fo, "};\n\nconst char *code_functions[] = {\n"); + for (int i = 0; func_table[i].name[0] != '\0'; i++) { + add_key(func_table[i].name); + code_functions_len++; + } + sort_ktable(); + print_ktable(fo); + + fprintf(fo, "};\n\n"); + + fprintf(fo, "const int code_keywords_len = %d;\n", code_keywords_len); + fprintf(fo, "const int code_procedures_len = %d;\n", code_procedures_len); + fprintf(fo, "const int code_functions_len = %d;\n", code_functions_len); + + fclose(fo); + return 0; +} diff --git a/src/platform/fltk/display.cxx b/src/platform/fltk/display.cxx new file mode 100755 index 00000000..45dcda00 --- /dev/null +++ b/src/platform/fltk/display.cxx @@ -0,0 +1,433 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include "config.h" +#include +#include "ui/utils.h" +#include "platform/fltk/display.h" + +GraphicsWidget *graphics; + +#define MAX_CANVAS_SIZE 20 + +// +// Canvas implementation +// +Canvas::Canvas() : + _w(0), + _h(0), + _scale(0), + _offscreen(0), + _clip(NULL), + _drawColor(fl_rgb_color(31, 28, 31)) { +} + +Canvas::~Canvas() { + if (_offscreen) { + fl_delete_offscreen(_offscreen); + _offscreen = 0; + } + delete _clip; + _clip = NULL; +} + +bool Canvas::create(int w, int h) { + logEntered(); + _w = w; + _h = h; + _offscreen = fl_create_offscreen(_w, _h); + _scale = Fl_Graphics_Driver::default_driver().scale(); + return _offscreen != 0; +} + +void Canvas::drawArc(int xc, int yc, double r, double start, double end, double aspect) { + fl_begin_offscreen(_offscreen); + fl_push_clip(x(), y(), w(), h()); + fl_color(_drawColor); + if (r < 1) { + r = 1; + } + while (end < start) { + end += M_PI * 2.0; + } + double th = (end - start) / r; + double xs = xc + r * cos(start); + double ys = yc + r * aspect * sin(start); + double xe = xc + r * cos(end); + double ye = yc + r * aspect * sin(end); + int x = xs; + int y = ys; + for (int i = 1; i < r; i++) { + double ph = start + i * th; + xs = xc + r * cos(ph); + ys = yc + r * aspect * sin(ph); + fl_line(x, y, xs, ys); + x = xs; + y = ys; + } + fl_line(x, y, xe, ye); + fl_pop_clip(); + fl_end_offscreen(); +} + +void Canvas::drawEllipse(int xc, int yc, int rx, int ry, bool fill) { + fl_begin_offscreen(_offscreen); + fl_push_clip(x(), y(), w(), h()); + fl_color(_drawColor); + int x = xc - rx; + int y = yc - ry; + int w = rx * 2; + int h = ry * 2; + if (fill) { + fl_begin_polygon(); + fl_pie(x, y, w, h, 0.0, 360.0); + fl_end_polygon(); + } else { + fl_begin_line(); + fl_arc(x, y, w, h, 0.0, 360.0); + fl_end_line(); + } + fl_pop_clip(); + fl_end_offscreen(); +} + +void Canvas::drawLine(int startX, int startY, int endX, int endY) { + fl_begin_offscreen(_offscreen); + fl_push_clip(x(), y(), w(), h()); + fl_color(_drawColor); + fl_line(startX, startY, endX, endY); + fl_pop_clip(); + fl_end_offscreen(); +} + +void Canvas::drawPixel(int posX, int posY) { + fl_begin_offscreen(_offscreen); + fl_color(_drawColor); + fl_line(posX, posY, posX + 1, posY + 1); + fl_end_offscreen(); +} + +// x, y, w are position and width of scan line in image. copy w +// pixels from scanline y, starting at pixel x to this buffer. +void drawImage(void *data, int x, int y, int w, uchar *out) { + uint8_t *image = (uint8_t *)data; + int scanLine = w * 3; + int offs = y * w * 4; + + for (int sx = 0; sx < scanLine; sx += 3, offs += 4) { + out[sx + 0] = image[offs + 0]; + out[sx + 1] = image[offs + 1]; + out[sx + 2] = image[offs + 2]; + } +} + +void Canvas::drawRGB(const MAPoint2d *dstPoint, const void *src, const MARect *srcRect, int opacity, int bytesPerLine) { + int x = dstPoint->x; + int y = dstPoint->y; + int w = srcRect->width; + int h = srcRect->height; + fl_begin_offscreen(_offscreen); + fl_draw_image(drawImage, (void *)src, x, y, w, h); + fl_end_offscreen(); +} + +void Canvas::drawRegion(Canvas *src, const MARect *srcRect, int destX, int destY) { + int width = MIN(_w, srcRect->width); + int height = MIN(_h, srcRect->height); + fl_begin_offscreen(_offscreen); + fl_copy_offscreen(destX, destY, width, height, src->_offscreen, srcRect->left, srcRect->top); + fl_end_offscreen(); +} + +void Canvas::drawText(Font *font, int left, int top, const char *str, int len) { + fl_begin_offscreen(_offscreen); + if (font) { + font->setCurrent(); + } + fl_push_clip(x(), y(), w(), h()); + fl_color(_drawColor); + fl_draw(str, len, x() + left, y() + top); + fl_pop_clip(); + fl_end_offscreen(); +} + +void Canvas::fillRect(int left, int top, int width, int height, Fl_Color color) { + fl_begin_offscreen(_offscreen); + fl_color(color); + fl_push_clip(x(), y(), w(), h()); + fl_rectf(left, top, width, height); + fl_pop_clip(); + fl_end_offscreen(); +} + +void Canvas::getImageData(uint8_t *image, const MARect *srcRect, int bytesPerLine) { + fl_begin_offscreen(_offscreen); + int x = srcRect->left; + int y = srcRect->top; + int w = srcRect->width; + int h = srcRect->height; + fl_read_image(image, x, y, w, h, 1); + fl_end_offscreen(); + + if (srcRect->width == 1 && srcRect->height == 1) { + // compatibility with PSET/POINT + uchar r = image[0]; + uchar g = image[1]; + uchar b = image[2]; + uchar a = image[3]; + image[0] = b; + image[1] = g; + image[2] = r; + image[3] = a; + } +} + +void Canvas::setClip(int x, int y, int w, int h) { + delete _clip; + if (x != 0 || y != 0 || _w != w || _h != h) { + _clip = new Fl_Rect(x, y, x + w, y + h); + } else { + _clip = NULL; + } +} + +// +// Graphics implementation +// +GraphicsWidget::GraphicsWidget(int xx, int yy, int ww, int hh) : + Fl_Widget(xx, yy, ww, hh), + _screen(NULL), + _drawTarget(NULL), + _font(NULL), + _textOffset(0) { + logEntered(); + _screen = new Canvas(); + _screen->create(ww, hh); + graphics = this; +} + +GraphicsWidget::~GraphicsWidget() { + delete _screen; +} + +void GraphicsWidget::deleteFont(Font *font) { + if (font == _font) { + _font = NULL; + } + delete font; +} + +void GraphicsWidget::draw() { + if (_screen && _screen->_offscreen) { + if (_screen->_scale != Fl_Graphics_Driver::default_driver().scale()) { + fl_rescale_offscreen(_screen->_offscreen); + _screen->_scale = Fl_Graphics_Driver::default_driver().scale(); + } + fl_copy_offscreen(x(), y(), w(), h(), _screen->_offscreen, 0, 0); + } +} + +void GraphicsWidget::drawText(int left, int top, const char *str, int length) { + if (_drawTarget) { + _drawTarget->drawText(_font, left, top + _textOffset, str, length); + } +} + +MAExtent GraphicsWidget::getTextSize(const char *str) { + if (_font) { + _font->setCurrent(); + } + int height = fl_height(); + int width = fl_width(str); + _textOffset = height - fl_descent(); + return (MAExtent)((width << 16) + height); +} + +void GraphicsWidget::resize(int x, int y, int w, int h) { + Fl_Widget::resize(x, y, w, h); + layout(); +} + +void GraphicsWidget::layout() { + if (_screen->_w != w() || _screen->_h != h()) { + logEntered(); + bool drawScreen = (_drawTarget == _screen); + delete _screen; + _screen = new Canvas(); + _screen->create(w(), h()); + _drawTarget = drawScreen ? _screen : NULL; + } +} + +MAHandle GraphicsWidget::setDrawTarget(MAHandle maHandle) { + MAHandle result = (MAHandle) _drawTarget; + if (maHandle == (MAHandle) HANDLE_SCREEN || + maHandle == (MAHandle) HANDLE_SCREEN_BUFFER) { + _drawTarget = _screen; + } else { + _drawTarget = (Canvas *)maHandle; + } + delete _drawTarget->_clip; + _drawTarget->_clip = NULL; + return result; +} + +// +// maapi implementation +// +MAHandle maCreatePlaceholder(void) { + return (MAHandle) new Canvas(); +} + +int maFontDelete(MAHandle maHandle) { + if (maHandle != -1) { + graphics->deleteFont((Font *)maHandle); + } + return RES_FONT_OK; +} + +int maSetColor(int c) { + Canvas *canvas = graphics->getDrawTarget(); + if (canvas) { + // Fl_Color => 0xrrggbbii + canvas->setColor((c << 8) & 0xffffff00); + } + return c; +} + +void maSetClipRect(int left, int top, int width, int height) { + Canvas *canvas = graphics->getDrawTarget(); + if (canvas) { + canvas->setClip(left, top, width, height); + } +} + +void maPlot(int posX, int posY) { + Canvas *canvas = graphics->getDrawTarget(); + if (canvas) { + canvas->drawPixel(posX, posY); + } +} + +void maLine(int startX, int startY, int endX, int endY) { + Canvas *canvas = graphics->getDrawTarget(); + if (canvas) { + canvas->drawLine(startX, startY, endX, endY); + } +} + +void maFillRect(int left, int top, int width, int height) { + Canvas *canvas = graphics->getDrawTarget(); + if (canvas) { + canvas->fillRect(left, top, width, height, canvas->_drawColor); + } +} + +void maArc(int xc, int yc, double r, double start, double end, double aspect) { + Canvas *canvas = graphics->getDrawTarget(); + if (canvas) { + canvas->drawArc(xc, yc, r, start, end, aspect); + } +} + +void maEllipse(int xc, int yc, int rx, int ry, int fill) { + Canvas *canvas = graphics->getDrawTarget(); + if (canvas) { + canvas->drawEllipse(xc, yc, rx, ry, fill); + } +} + +void maDrawText(int left, int top, const char *str, int length) { + if (str && str[0]) { + graphics->drawText(left, top, str, length); + } +} + +void maDrawRGB(const MAPoint2d *dstPoint, const void *src, const MARect *srcRect, int opacity, int bytesPerLine) { + Canvas *canvas = graphics->getDrawTarget(); + if (canvas) { + canvas->drawRGB(dstPoint, src, srcRect, opacity, bytesPerLine); + } +} + +MAExtent maGetTextSize(const char *str) { + MAExtent result; + if (str && str[0]) { + result = graphics->getTextSize(str); + } else { + result = 0; + } + return result; +} + +MAExtent maGetScrSize(void) { + short width = graphics->getWidth(); + short height = graphics->getHeight(); + return (MAExtent)((width << 16) + height); +} + +MAHandle maFontLoadDefault(int type, int style, int size) { + Fl_Font font = FL_COURIER; + if (style & FONT_STYLE_BOLD) { + font += FL_BOLD; + } + if (style & FONT_STYLE_ITALIC) { + font += FL_ITALIC; + } + return (MAHandle)new Font(font, size); +} + +MAHandle maFontSetCurrent(MAHandle maHandle) { + graphics->setFont((Font *)maHandle); + return maHandle; +} + +void maDrawImageRegion(MAHandle maHandle, const MARect *srcRect, const MAPoint2d *dstPoint, int transformMode) { + Canvas *canvas = graphics->getDrawTarget(); + Canvas *src = (Canvas *)maHandle; + if (canvas && canvas != src) { + canvas->drawRegion(src, srcRect, dstPoint->x, dstPoint->y); + } +} + +void maDestroyPlaceholder(MAHandle maHandle) { + Canvas *canvas = (Canvas *)maHandle; + delete canvas; +} + +void maGetImageData(MAHandle maHandle, void *dst, const MARect *srcRect, int bytesPerLine) { + Canvas *canvas = (Canvas *)maHandle; + if (canvas == HANDLE_SCREEN) { + canvas = graphics->getScreen(); + } + canvas->getImageData((uint8_t *)dst, srcRect, bytesPerLine); +} + +MAHandle maSetDrawTarget(MAHandle maHandle) { + return graphics->setDrawTarget(maHandle); +} + +int maCreateDrawableImage(MAHandle maHandle, int width, int height) { + int result = RES_OK; + int maxSize = MAX(graphics->getWidth(), graphics->getHeight()); + if (height > maxSize * MAX_CANVAS_SIZE) { + result -= 1; + } else { + Canvas *drawable = (Canvas *)maHandle; + result = drawable->create(width, height) ? RES_OK : -1; + } + return result; +} + +void maUpdateScreen(void) { + ((::GraphicsWidget *)graphics)->redraw(); +} + +int maShowVirtualKeyboard(void) { + return 0; +} diff --git a/src/platform/fltk/display.h b/src/platform/fltk/display.h new file mode 100755 index 00000000..5dbafd38 --- /dev/null +++ b/src/platform/fltk/display.h @@ -0,0 +1,83 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef DISPLAY_H +#define DISPLAY_H + +#include +#include +#include +#include "lib/maapi.h" + +struct Font { + Font(Fl_Font font, Fl_Fontsize size) : _font(font), _size(size) {} + void setCurrent() { fl_font(_font, _size); } + +private: + Fl_Font _font; + Fl_Fontsize _size; +}; + +struct Canvas { + Canvas(); + virtual ~Canvas(); + + bool create(int w, int h); + void drawArc(int xc, int yc, double r, double start, double end, double aspect); + void drawEllipse(int xc, int yc, int rx, int ry, bool fill); + void drawLine(int startX, int startY, int endX, int endY); + void drawPixel(int posX, int posY); + void drawRGB(const MAPoint2d *dstPoint, const void *src, const MARect *srcRect, int opacity, int bytesPerLine); + void drawRegion(Canvas *src, const MARect *srcRect, int dstx, int dsty); + void drawText(Font *font, int left, int top, const char *str, int len); + void fillRect(int x, int y, int w, int h, Fl_Color color); + Fl_Color getDrawColor() { return _drawColor; } + void getImageData(uint8_t *image, const MARect *srcRect, int bytesPerLine); + void setClip(int x, int y, int w, int h); + void setColor(Fl_Color color) { _drawColor = color; } + + int x() { return _clip ? _clip->x() : 0; } + int y() { return _clip ? _clip->y() : 0; } + int w() { return _clip ? _clip->w() : _w; } + int h() { return _clip ? _clip->h() : _h; } + + int _w; + int _h; + float _scale; + Fl_Offscreen _offscreen; + Fl_Rect *_clip; + Fl_Color _drawColor; +}; + +class GraphicsWidget : public Fl_Widget { +public: + GraphicsWidget(int x, int y, int w, int h); + virtual ~GraphicsWidget(); + + void deleteFont(Font *font); + void drawText(int left, int top, const char *str, int len); + int getHeight() { return _screen->_h; } + int getWidth() { return _screen->_w; } + Canvas *getDrawTarget() { return _drawTarget; } + Canvas *getScreen() { return _screen; } + MAExtent getTextSize(const char *str); + void layout(); + void resize(int x, int y, int w, int h); + MAHandle setDrawTarget(MAHandle maHandle); + void setFont(Font *font) { _font = font; } + +private: + void draw(); + + Canvas *_screen; + Canvas *_drawTarget; + Font *_font; + int _textOffset; +}; + +#endif diff --git a/src/platform/fltk/runtime.cxx b/src/platform/fltk/runtime.cxx new file mode 100644 index 00000000..bb58b390 --- /dev/null +++ b/src/platform/fltk/runtime.cxx @@ -0,0 +1,383 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include "common/osd.h" +#include "lib/maapi.h" +#include "platform/fltk/MainWindow.h" +#include "platform/fltk/runtime.h" +#include +#include + +extern MainWindow *wnd; +extern System *g_system; + +#define OPTIONS_BOX_WIDTH_EXTRA 4 +#define WAIT_INTERVAL_MILLIS 5 +#define WAIT_INTERVAL (WAIT_INTERVAL_MILLIS/1000) + +int event_x() { + return Fl::event_x(); +} + +int event_y() { + return Fl::event_y() - wnd->_out->y() - 2; +} + +void setMotionEvent(MAEvent &event, int type) { + event.type = type; + event.point.x = event_x(); + event.point.y = event_y(); +} + +// +// Runtime implementation +// +Runtime::Runtime(int w, int h, int defsize) : System() { + _output = new AnsiWidget(w, h); + _output->construct(); + _output->setTextColor(DEFAULT_FOREGROUND, DEFAULT_BACKGROUND); + _output->setFontSize(defsize); +} + +Runtime::~Runtime() { + // empty +} + +void Runtime::alert(const char *title, const char *message) { + fl_alert("%s", message); +} + +int Runtime::ask(const char *title, const char *prompt, bool cancel) { + int result; + if (cancel) { + result = fl_choice(prompt, "Yes", "No", "Cancel", NULL); + } else { + result = fl_choice(prompt, "Yes", "No", 0, NULL); + } + return result; +} + +void Runtime::browseFile(const char *url) { + ::browseFile(url); +} + +void Runtime::enableCursor(bool enabled) { + fl_cursor(enabled ? FL_CURSOR_DEFAULT : FL_CURSOR_NONE); +} + +char *Runtime::getClipboardText() { + return NULL; +} + +int Runtime::handle(int e) { + MAEvent event; + + switch (e) { + case FL_PUSH: + setMotionEvent(event, EVENT_TYPE_POINTER_PRESSED); + if (event.point.y >= 0) { + handleEvent(event); + } + break; + + case FL_DRAG: + setMotionEvent(event, EVENT_TYPE_POINTER_DRAGGED); + handleEvent(event); + break; + + case FL_RELEASE: + setMotionEvent(event, EVENT_TYPE_POINTER_RELEASED); + handleEvent(event); + break; + + default: + break; + } + + return 0; +} + +void Runtime::optionsBox(StringList *items) { + Fl_Menu_Item menu[items->size() + 1]; + int index = 0; + int width = 0; + int height = 0; + int charWidth = _output->getCharWidth(); + + List_each(String *, it, *items) { + int w, h; + char *str = (char *)(* it)->c_str(); + menu[index].text = str; + menu[index].shortcut_ = 0; + menu[index].callback_ = 0; + menu[index].user_data_ = (void *)(intptr_t)index; + menu[index].flags = 0; + menu[index].labeltype_ = 0; + menu[index].labelfont_ = FL_HELVETICA; + menu[index].labelsize_ = FL_NORMAL_SIZE; + menu[index].labelcolor_ = FL_FOREGROUND_COLOR; + menu[index].measure(&h, NULL); + + height += h + 1; + w = (strlen(str) * charWidth); + if (w > width) { + width = w; + } + index++; + } + + menu[index].flags = 0; + menu[index].text = NULL; + width += (charWidth * OPTIONS_BOX_WIDTH_EXTRA); + + int menuX = event_x(); + int menuY = event_y(); + int maxWidth = wnd->_out->w() - wnd->_out->x(); + int maxHeight = wnd->h() - height - MENU_HEIGHT; + + if (menuX + width >= maxWidth) { + menuX = maxWidth - width; + } + + if (menuY + height >= maxHeight) { + menuY = maxHeight - height; + } else { + menuY -= wnd->_out->y(); + } + + Fl_Menu_Button popup(menuX, menuY, width, height, "@popup"); + popup.menu(menu); + + const Fl_Menu_Item *result = popup.popup(); + if (result && result->text) { + MAEvent event; + event.type = EVENT_TYPE_OPTIONS_BOX_BUTTON_CLICKED; + event.optionsBoxButtonIndex = (intptr_t)(void *)result->user_data_; + handleEvent(event); + } +} + +MAEvent Runtime::processEvents(int waitFlag) { + switch (waitFlag) { + case 1: + // wait for an event + _output->flush(true); + Fl::wait(); + break; + case 2: + _output->flush(false); + Fl::wait(WAIT_INTERVAL); + break; + default: + Fl::check(); + break; + } + + MAEvent event; + event.type = 0; + event.key = 0; + event.nativeKey = 0; + + if (keymap_kbhit()) { + event.type = EVENT_TYPE_KEY_PRESSED; + event.key = keymap_kbpeek(); + } + return event; +} + +void Runtime::resize(int w, int h) { + if (w != _output->getWidth() || h != _output->getHeight()) { + _output->resize(w, h); + } +} + +void Runtime::setClipboardText(const char *text) { + Fl::copy(text, strlen(text), true); +} + +void Runtime::setFontSize(int size) { + _output->setFontSize(size); +} + +void Runtime::setWindowSize(int width, int height) { + wnd->size(width, height); +} + +void Runtime::setWindowTitle(const char *title) { + wnd->label(title); +} + +void Runtime::showCursor(CursorType cursorType) { + switch (cursorType) { + case kHand: + fl_cursor(FL_CURSOR_HAND); + break; + case kArrow: + fl_cursor(FL_CURSOR_ARROW); + break; + case kIBeam: + fl_cursor(FL_CURSOR_INSERT); + break; + } +} + +// +// System platform methods +// +void System::editSource(strlib::String loadPath, bool restoreOnExit) { + // empty +} + +bool System::getPen3() { + Fl::check(); + bool result = Fl::event_state(FL_BUTTON1); + if (result) { + _touchCurX = event_x(); + _touchCurY = event_y(); + } + return result; +} + +void System::completeKeyword(int index) { + // empty +} + +// +// ma event handling +// +int maGetEvent(MAEvent *event) { + int result = 0; + if (Fl::check()) { + switch (Fl::event()) { + case FL_PUSH: + event->type = EVENT_TYPE_POINTER_PRESSED; + result = 1; + break; + case FL_DRAG: + event->type = EVENT_TYPE_POINTER_DRAGGED; + result = 1; + break; + case FL_RELEASE: + event->type = EVENT_TYPE_POINTER_RELEASED; + result = 1; + break; + } + } + return result; +} + +void maWait(int timeout) { + if (timeout == -1) { + Fl::wait(); + } else { + int slept = 0; + while (1) { + Fl::check(); + if (wnd->isBreakExec()) { + break; + } + usleep(WAIT_INTERVAL_MILLIS * 1000); + slept += WAIT_INTERVAL_MILLIS; + if (timeout > 0 && slept > timeout) { + break; + } + } + } +} + +// +// sbasic implementation +// +int osd_devinit() { + // allow the application to set the preferred width and height + if ((opt_pref_width || opt_pref_height) && wnd->isIdeHidden()) { + int delta_x = wnd->w() - g_system->getOutput()->getWidth(); + int delta_y = wnd->h() - g_system->getOutput()->getHeight(); + if (opt_pref_width < 10) { + opt_pref_width = 10; + } + if (opt_pref_height < 10) { + opt_pref_height = 10; + } + int w = opt_pref_width + delta_x; + int h = opt_pref_height + delta_y; + wnd->_outputGroup->resize(0, 0, w, h); + } + + // show the output-group in case it's the full-screen container. + if (wnd->isInteractive() && !wnd->logPrint()) { + wnd->_outputGroup->show(); + } + + g_system->setRunning(true); + return 1; +} + +void osd_write(const char *str) { + if (wnd->tty() && wnd->logPrint()) { + wnd->tty()->print(str); + } + g_system->getOutput()->print(str); +} + +int osd_devrestore() { + g_system->setRunning(false); + return 1; +} + +void osd_beep() { + fl_beep(); +} + +void osd_sound(int frq, int ms, int vol, int bgplay) { +#if defined(WIN32) + if (!bgplay) { + ::Beep(frq, ms); + } +#endif +} + +// +// utils +// +void appLog(const char *format, ...) { + va_list args; + va_start(args, format); + unsigned size = vsnprintf(NULL, 0, format, args); + va_end(args); + + if (size) { + char *buf = (char *)malloc(size + 3); + buf[0] = '\0'; + va_start(args, format); + vsnprintf(buf, size + 1, format, args); + va_end(args); + buf[size] = '\0'; + + int i = size - 1; + while (i >= 0 && isspace(buf[i])) { + buf[i--] = '\0'; + } + strcat(buf, "\r\n"); + if (wnd && wnd->tty()) { + wnd->tty()->print(buf); + } else { + fprintf(stderr, "%s", buf); + } + free(buf); + } +} + +// +// not implemented +// +void open_audio() {} +void close_audio() {} +void osd_audio(const char *path) {} +void osd_clear_sound_queue() {} diff --git a/src/platform/fltk/runtime.h b/src/platform/fltk/runtime.h new file mode 100755 index 00000000..5eafee9c --- /dev/null +++ b/src/platform/fltk/runtime.h @@ -0,0 +1,40 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef FLTK_RUNTIME_H +#define FLTK_RUNTIME_H + +#include "ui/ansiwidget.h" +#include "ui/system.h" + +struct Runtime : public System { + Runtime(int w, int h, int defSize); + ~Runtime(); + + void addShortcut(const char *) {} + void alert(const char *title, const char *message); + int ask(const char *title, const char *prompt, bool cancel=true); + void browseFile(const char *url); + char *getClipboardText(); + void enableCursor(bool enabled); + int handle(int event); + void optionsBox(StringList *items); + MAEvent processEvents(int waitFlag); + bool run(const char *bas) { return execute(bas); } + void resize(int w, int h); + void setClipboardText(const char *text); + void setFontSize(int size); + void setLoadBreak(const char *url) {} + void setLoadPath(const char *url) {} + void setWindowSize(int width, int height); + void setWindowTitle(const char *title); + void share(const char *path) {} + void showCursor(CursorType cursorType); +}; + +#endif diff --git a/src/platform/fltk/utils.cxx b/src/platform/fltk/utils.cxx new file mode 100755 index 00000000..77edd0bc --- /dev/null +++ b/src/platform/fltk/utils.cxx @@ -0,0 +1,232 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#include +#include +#include +#include "lib/str.h" +#include "utils.h" + +#define RX_BUFFER_SIZE 1024 + +Fl_Color get_color(int argb) { + // Fl_Color => 0xrrggbbii + return (argb << 8) & 0xffffff00; +} + +Fl_Color get_color(const char *name, Fl_Color def) { + Fl_Color result = def; + if (!name || name[0] == '\0') { + result = def; + } else if (name[0] == '#') { + // do hex color lookup + int rgb = strtol(name + 1, NULL, 16); + if (!rgb) { + result = FL_BLACK; + } else { + uchar r = rgb >> 16; + uchar g = (rgb >> 8) & 255; + uchar b = rgb & 255; + result = fl_rgb_color(r, g, b); + } + } else if (strcasecmp(name, "black") == 0) { + result = FL_BLACK; + } else if (strcasecmp(name, "red") == 0) { + result = FL_RED; + } else if (strcasecmp(name, "green") == 0) { + result = fl_rgb_color(0, 0x80, 0); + } else if (strcasecmp(name, "yellow") == 0) { + result = FL_YELLOW; + } else if (strcasecmp(name, "blue") == 0) { + result = FL_BLUE; + } else if (strcasecmp(name, "magenta") == 0 || + strcasecmp(name, "fuchsia") == 0) { + result = FL_MAGENTA; + } else if (strcasecmp(name, "cyan") == 0 || + strcasecmp(name, "aqua") == 0) { + result = FL_CYAN; + } else if (strcasecmp(name, "white") == 0) { + result = FL_WHITE; + } else if (strcasecmp(name, "gray") == 0 || + strcasecmp(name, "grey") == 0) { + result = fl_rgb_color(0x80, 0x80, 0x80); + } else if (strcasecmp(name, "lime") == 0) { + result = FL_GREEN; + } else if (strcasecmp(name, "maroon") == 0) { + result = fl_rgb_color(0x80, 0, 0); + } else if (strcasecmp(name, "navy") == 0) { + result = fl_rgb_color(0, 0, 0x80); + } else if (strcasecmp(name, "olive") == 0) { + result = fl_rgb_color(0x80, 0x80, 0); + } else if (strcasecmp(name, "purple") == 0) { + result = fl_rgb_color(0x80, 0, 0x80); + } else if (strcasecmp(name, "silver") == 0) { + result = fl_rgb_color(0xc0, 0xc0, 0xc0); + } else if (strcasecmp(name, "teal") == 0) { + result = fl_rgb_color(0, 0x80, 0x80); + } + return result; +} + +Fl_Font get_font(const char *name) { + Fl_Font result = FL_COURIER; + if (strcasecmp(name, "helvetica") == 0) { + result = FL_HELVETICA; + } else if (strcasecmp(name, "times") == 0) { + result = FL_TIMES; + } + return result; +} + +void getHomeDir(char *fileName, size_t size, bool appendSlash) { + const int homeIndex = 1; + static const char *envVars[] = { + "APPDATA", "HOME", "TMP", "TEMP", "TMPDIR", "" + }; + + fileName[0] = '\0'; + + for (int i = 0; envVars[i][0] != '\0' && fileName[0] == '\0'; i++) { + const char *home = getenv(envVars[i]); + if (home && access(home, R_OK) == 0) { + strlcpy(fileName, home, size); + if (i == homeIndex) { + strlcat(fileName, "/.config", size); + makedir(fileName); + } + strlcat(fileName, "/SmallBASIC", size); + if (appendSlash) { + strlcat(fileName, "/", size); + } + makedir(fileName); + break; + } + } +} + +// copy the url into the local cache +bool cacheLink(dev_file_t *df, char *localFile, size_t size) { + char rxbuff[RX_BUFFER_SIZE]; + FILE *fp; + const char *url = df->name; + const char *pathBegin = strchr(url + 7, '/'); + const char *pathEnd = strrchr(url + 7, '/'); + const char *pathNext; + bool inHeader = true; + bool httpOK = false; + + getHomeDir(localFile, size, true); + strlcat(localFile, "cache/", size); + makedir(localFile); + + // create host name component + strncat(localFile, url + 7, pathBegin - url - 7); + strlcat(localFile, "/", size); + makedir(localFile); + + if (pathBegin != 0 && pathBegin < pathEnd) { + // re-create the server path in cache + int level = 0; + pathBegin++; + do { + pathNext = strchr(pathBegin, '/'); + strncat(localFile, pathBegin, pathNext - pathBegin + 1); + makedir(localFile); + pathBegin = pathNext + 1; + } + while (pathBegin < pathEnd && ++level < 20); + } + if (pathEnd == 0 || pathEnd[1] == 0 || pathEnd[1] == '?') { + strlcat(localFile, "index.html", size); + } else { + strlcat(localFile, pathEnd + 1, size); + } + + fp = fopen(localFile, "wb"); + if (fp == 0) { + if (df->handle != -1) { + shutdown(df->handle, df->handle); + } + return false; + } + + if (df->handle == -1) { + // pass the cache file modified time to the HTTP server + struct stat st; + if (stat(localFile, &st) == 0) { + df->drv_dw[2] = st.st_mtime; + } + if (http_open(df) == 0) { + fclose(fp); + return false; + } + } + + while (true) { + int bytes = recv(df->handle, (char *)rxbuff, sizeof(rxbuff), 0); + if (bytes == 0) { + break; // no more data + } + // assumes http header < 1024 bytes + if (inHeader) { + int i = 0; + while (true) { + int iattr = i; + while (rxbuff[i] != 0 && rxbuff[i] != '\n') { + i++; + } + if (rxbuff[i] == 0) { + inHeader = false; + break; // no end delimiter + } + if (rxbuff[i + 2] == '\n') { + if (!fwrite(rxbuff + i + 3, bytes - i - 3, 1, fp)) { + break; + } + inHeader = false; + break; // found start of content + } + // null terminate attribute (in \r) + rxbuff[i - 1] = 0; + i++; + if (strstr(rxbuff + iattr, "200 OK") != 0) { + httpOK = true; + } + if (strncmp(rxbuff + iattr, "Location: ", 10) == 0) { + // handle redirection + shutdown(df->handle, df->handle); + strcpy(df->name, rxbuff + iattr + 10); + if (http_open(df) == 0) { + fclose(fp); + return false; + } + break; // scan next header + } + } + } else if (!fwrite(rxbuff, bytes, 1, fp)) { + break; + } + } + + // cleanup + fclose(fp); + shutdown(df->handle, df->handle); + return httpOK; +} + +void vsncat(char *buffer, size_t size, ...) { + va_list args; + va_start(args, size); + strlcpy(buffer, va_arg(args, char *), size); + for (char *next = va_arg(args, char *); + next != NULL; + next = va_arg(args, char *)) { + strlcat(buffer, next, size); + } + va_end(args); +} diff --git a/src/platform/fltk/utils.h b/src/platform/fltk/utils.h new file mode 100755 index 00000000..6bac22cd --- /dev/null +++ b/src/platform/fltk/utils.h @@ -0,0 +1,60 @@ +// This file is part of SmallBASIC +// +// Copyright(C) 2001-2019 Chris Warren-Smith. +// +// This program is distributed under the terms of the GPL v2.0 or later +// Download the GNU Public License (GPL) from www.gnu.org +// + +#ifndef FLTK_UTILS_H +#define FLTK_UTILS_H + +#include +#include "common/pproc.h" +#include "common/fs_socket_client.h" +#include + +#define DAMAGE_HIGHLIGHT FL_DAMAGE_USER1 +#define DAMAGE_PUSHED FL_DAMAGE_USER2 +#define SCROLL_W 15 +#define SCROLL_H 15 +#define SCROLL_X SCROLL_W - 2 +#define HSCROLL_W 80 +#define DEF_FONT_SIZE 12 +#define TAB_BORDER 4 +#define TTY_ROWS 1000 +#define MENU_HEIGHT 24 +#define NUM_RECENT_ITEMS 9 +#define STATUS_HEIGHT (MENU_HEIGHT + 2) +#define LINE_NUMBER_WIDTH 40 + +// currently missing from Enumerations.H +#define FL_Multiply 0xffaa +#define FL_AddKey 0xffab +#define FL_SubtractKey 0xffad +#define FL_DivideKey 0xffaf + +#define C_LINKAGE_BEGIN extern "C" { +#define C_LINKAGE_END } + +#ifndef MAX +#define MAX(a,b) ((ab) ? (b) : (a)) +#endif + +#if defined(__MINGW32__) +#define makedir(f) mkdir(f) +#else +#define makedir(f) mkdir(f, 0700) +#endif + +Fl_Color get_color(const char *name, Fl_Color def); +Fl_Color get_color(int argb); +Fl_Font get_font(const char *name); +void getHomeDir(char *fileName, size_t size, bool appendSlash = true); +bool cacheLink(dev_file_t *df, char *localFile, size_t size); +void vsncat(char *buffer, size_t size, ...); + +#endif diff --git a/src/platform/sdl/editor.cpp b/src/platform/sdl/editor.cpp index 7b61f6bb..e332d7c0 100644 --- a/src/platform/sdl/editor.cpp +++ b/src/platform/sdl/editor.cpp @@ -77,14 +77,18 @@ struct StatusMessage { } out->setStatus(message); _dirty = dirty; - _row = editor->getRow(); - _col = editor->getCol(); + resetCursor(editor); } else { result = false; } return result; } + void resetCursor(TextEditInput *editor) { + _row = editor->getRow(); + _col = editor->getCol(); + } + bool _dirty; int _row; int _col; @@ -151,7 +155,7 @@ void exportBuffer(AnsiWidget *out, const char *text, String &dest, String &token out->setStatus(buffer); } -void System::editSource(String loadPath) { +void System::editSource(String loadPath, bool restoreOnExit) { logEntered(); int w = _output->getWidth(); @@ -309,8 +313,7 @@ void System::editSource(String loadPath) { helpWidget->createMessage(); helpWidget->show(); ((Runtime *)this)->debugStart(editWidget, loadPath.c_str()); - statusMessage._row = editWidget->getRow(); - statusMessage._col = editWidget->getCol(); + statusMessage.resetCursor(editWidget); break; case SB_KEY_F(6): ((Runtime *)this)->debugStep(editWidget, helpWidget, false); @@ -349,12 +352,14 @@ void System::editSource(String loadPath) { widget = helpWidget; helpWidget->createSearch(false); helpWidget->show(); + statusMessage.resetCursor(editWidget); break; case SB_KEY_CTRL('n'): _output->setStatus("Replace string. Esc=Close"); widget = helpWidget; helpWidget->createSearch(true); helpWidget->show(); + statusMessage.resetCursor(editWidget); break; case SB_KEY_ALT('g'): _output->setStatus("Goto line. Esc=Close"); @@ -505,7 +510,7 @@ void System::editSource(String loadPath) { // deletes editWidget unless it has been removed _output->removeInputs(); - if (!isClosing()) { + if (!isClosing() && restoreOnExit) { _output->selectScreen(prevScreenId, false); } logLeaving(); diff --git a/src/platform/sdl/main.cpp b/src/platform/sdl/main.cpp index 936a4eb3..708f38b6 100644 --- a/src/platform/sdl/main.cpp +++ b/src/platform/sdl/main.cpp @@ -276,7 +276,7 @@ void showHelp() { OPTIONS[i].val, OPTIONS[i].name); i++; } - fprintf(stdout, "\nhttps://smallbasic.sourceforge.io\n\n"); + fprintf(stdout, "\nhttps://smallbasic.github.io\n"); } int main(int argc, char* argv[]) { diff --git a/src/platform/web/main.cpp b/src/platform/web/main.cpp index 61d3aed1..5ba1f328 100644 --- a/src/platform/web/main.cpp +++ b/src/platform/web/main.cpp @@ -24,6 +24,7 @@ Canvas g_canvas; uint32_t g_start = 0; uint32_t g_maxTime = 2000; bool g_graphicText = true; +bool g_noExecute = false; struct MHD_Connection *g_connection; StringList g_cookies; @@ -31,6 +32,7 @@ static struct option OPTIONS[] = { {"help", no_argument, NULL, 'h'}, {"verbose", no_argument, NULL, 'v'}, {"file-permitted", no_argument, NULL, 'f'}, + {"no-execute", no_argument, NULL, 'x'}, {"port", optional_argument, NULL, 'p'}, {"run", optional_argument, NULL, 'r'}, {"width", optional_argument, NULL, 'w'}, @@ -72,7 +74,7 @@ void show_help() { OPTIONS[i].val, OPTIONS[i].name); i++; } - fprintf(stdout, "\nhttps://smallbasic.sourceforge.io\n\n"); + fprintf(stdout, "\nhttps://smallbasic.github.io\n\n"); } void log(const char *format, ...) { @@ -177,7 +179,7 @@ MHD_Response *get_response(struct MHD_Connection *connection, const char *path) response = serve_file(path); } else if (strstr(path, "..") == NULL) { const char *dot = strrchr(path, '.'); - if (dot && strncasecmp(dot, ".bas", 4) == 0 && + if (dot && !g_noExecute && strncasecmp(dot, ".bas", 4) == 0 && stat(path, &stbuf) != -1 && S_ISREG(stbuf.st_mode)) { response = execute(connection, path); } else { @@ -238,7 +240,7 @@ int main(int argc, char **argv) { while (1) { int option_index = 0; - int c = getopt_long(argc, argv, "hvfp:t:m::r:w:e:c:g:", OPTIONS, &option_index); + int c = getopt_long(argc, argv, "hvfxp:t:m::r:w:e:c:g:", OPTIONS, &option_index); if (c == -1) { break; } @@ -285,6 +287,9 @@ int main(int argc, char **argv) { strcpy(opt_modpath, optarg); } break; + case 'x': + g_noExecute = true; + break; default: show_help(); exit(1); diff --git a/src/ui/ansiwidget.cpp b/src/ui/ansiwidget.cpp index f2dc5558..f42099c8 100755 --- a/src/ui/ansiwidget.cpp +++ b/src/ui/ansiwidget.cpp @@ -62,6 +62,8 @@ AnsiWidget::AnsiWidget(int width, int height) : _back(NULL), _front(NULL), _focus(NULL), + _activeButton(NULL), + _hoverInput(NULL), _width(width), _height(height), _xTouch(-1), @@ -70,9 +72,7 @@ AnsiWidget::AnsiWidget(int width, int height) : _yMove(-1), _touchTime(0), _swipeExit(false), - _autoflush(true), - _activeButton(NULL), - _hoverInput(NULL) { + _autoflush(true) { for (int i = 0; i < MAX_SCREENS; i++) { _screens[i] = NULL; } @@ -590,7 +590,7 @@ void AnsiWidget::doSwipe(int start, bool moveDown, int distance, int maxScroll) // draws the focus screen's active button void AnsiWidget::drawActiveButton() { -#if defined(_SDL) +#if defined(_SDL) || defined(_FLTK) if (_focus != NULL && !_activeButton->hasHover()) { MAHandle currentHandle = maSetDrawTarget(HANDLE_SCREEN); _focus->drawShape(_activeButton); @@ -620,7 +620,7 @@ void AnsiWidget::drawActiveButton() { } bool AnsiWidget::drawHoverLink(MAEvent &event) { -#if defined(_SDL) +#if defined(_SDL) || defined(_FLTK) if (_front != _screens[MENU_SCREEN]) { int dx = _front->_x; int dy = _front->_y - _front->_scrollY; diff --git a/src/ui/ansiwidget.h b/src/ui/ansiwidget.h index a909b359..19d58e9e 100755 --- a/src/ui/ansiwidget.h +++ b/src/ui/ansiwidget.h @@ -1,6 +1,6 @@ // This file is part of SmallBASIC // -// Copyright(C) 2001-2014 Chris Warren-Smith. +// Copyright(C) 2001-2019 Chris Warren-Smith. // // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org @@ -114,6 +114,8 @@ struct AnsiWidget { Screen *_back; // screen being painted/written Screen *_front; // screen to display Screen *_focus; // screen with the active button + FormInput *_activeButton; + FormInput *_hoverInput; int _width; // device screen width int _height; // device screen height int _fontSize; // font height based on screen size @@ -124,8 +126,6 @@ struct AnsiWidget { int _touchTime; // last move time bool _swipeExit; // last touch-down was swipe exit bool _autoflush; // flush internally - FormInput *_activeButton; - FormInput *_hoverInput; }; #endif // ANSIWIDGET_H diff --git a/src/ui/form.cpp b/src/ui/form.cpp index ee3feac6..37c7cdd2 100644 --- a/src/ui/form.cpp +++ b/src/ui/form.cpp @@ -302,9 +302,9 @@ extern "C" void v_create_form(var_p_t var) { } out->setDirty(); v_zerostr(map_add_var(var, FORM_VALUE, 0)); - create_func(var, "doEvents", cmd_form_do_events); - create_func(var, "close", cmd_form_close); - create_func(var, "refresh", cmd_form_refresh); + v_create_func(var, "doEvents", cmd_form_do_events); + v_create_func(var, "close", cmd_form_close); + v_create_func(var, "refresh", cmd_form_refresh); } else { err_form_input(); } diff --git a/src/ui/graphics.cpp b/src/ui/graphics.cpp index c5425fb6..36eda7ac 100644 --- a/src/ui/graphics.cpp +++ b/src/ui/graphics.cpp @@ -297,11 +297,17 @@ void Graphics::drawRGB(const MAPoint2d *dstPoint, const void *src, dX < _drawTarget->w()) { // get RGBA components uint8_t r,g,b,a; +#if defined(PIXELFORMAT_RGBA8888) + b = image[4 * y * w + 4 * x + 0]; // blue + g = image[4 * y * w + 4 * x + 1]; // green + r = image[4 * y * w + 4 * x + 2]; // red + a = image[4 * y * w + 4 * x + 3]; // alpha +#else r = image[4 * y * w + 4 * x + 0]; // red g = image[4 * y * w + 4 * x + 1]; // green b = image[4 * y * w + 4 * x + 2]; // blue a = image[4 * y * w + 4 * x + 3]; // alpha - +#endif uint8_t dR, dG, dB; GET_RGB(line[dX], dR, dG, dB); if (opacity > 0 && opacity < 100 && a > 64) { @@ -383,7 +389,7 @@ void Graphics::getImageData(Canvas *canvas, uint8_t *image, for (int dy = 0, y = srcRect->top; y < y_end; y += scale, dy++) { if (y >= canvas->y() && y < canvas->h()) { pixel_t *line = canvas->getLine(y); - int yoffs = (dy * bytesPerLine * 4); + int yoffs = (dy * bytesPerLine); for (int dx = 0, x = srcRect->left; x < x_end; x += scale, dx++) { if (x >= canvas->x() && x < canvas->w()) { uint8_t r, g, b; @@ -673,7 +679,7 @@ MAHandle maFontLoadDefault(int type, int style, int size) { MAHandle maFontSetCurrent(MAHandle maHandle) { if (graphics) { - graphics->setFont((Font *) maHandle); + graphics->setFont((Font *)maHandle); } return maHandle; } diff --git a/src/ui/image.cpp b/src/ui/image.cpp index 559eff33..b8f77118 100644 --- a/src/ui/image.cpp +++ b/src/ui/image.cpp @@ -5,7 +5,7 @@ // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org // -// Copyright(C) 2002-2015 Chris Warren-Smith. +// Copyright(C) 2002-2019 Chris Warren-Smith. #include "common/sys.h" #include "common/messages.h" @@ -14,7 +14,7 @@ #include "lib/maapi.h" #include "ui/image.h" #include "ui/system.h" -#include "ui/graphics.h" +#include "ui/rgb.h" #if !defined(LODEPNG_NO_COMPILE_CPP) #define LODEPNG_NO_COMPILE_CPP @@ -129,7 +129,7 @@ uint8_t *get_image_data(int x, int y, int w, int h) { uint8_t *result = (uint8_t *)malloc(size); if (result != NULL) { g_system->getOutput()->redraw(); - maGetImageData(HANDLE_SCREEN, result, &rc, w); + maGetImageData(HANDLE_SCREEN, result, &rc, w * 4); } return result; } @@ -429,9 +429,9 @@ void create_image(var_p_t var, ImageBuffer *image) { map_add_var(var, IMG_WIDTH, image->_width); map_add_var(var, IMG_HEIGHT, image->_height); map_add_var(var, IMG_BID, image->_bid); - create_func(var, "show", cmd_image_show); - create_func(var, "hide", cmd_image_hide); - create_func(var, "save", cmd_image_save); + v_create_func(var, "show", cmd_image_show); + v_create_func(var, "hide", cmd_image_hide); + v_create_func(var, "save", cmd_image_save); } // loads an image for the form image input type @@ -489,7 +489,10 @@ void screen_dump() { } if (access(file, R_OK) != 0) { g_system->systemPrint("Saving screen to %s\n", file); - lodepng_encode32_file(file, image, width, height); + unsigned error = lodepng_encode32_file(file, image, width, height); + if (error) { + g_system->systemPrint("Error: %s\n", lodepng_error_text(error)); + } break; } } diff --git a/src/ui/inputs.cpp b/src/ui/inputs.cpp index 1ec34f40..bdad4095 100644 --- a/src/ui/inputs.cpp +++ b/src/ui/inputs.cpp @@ -1193,8 +1193,8 @@ void FormImage::draw(int x, int y, int w, int h, int chw) { MenuButton::MenuButton(int index, int &selectedIndex, const char *caption, int x, int y, int w, int h) : FormButton(caption, x, y, w, h), - _index(index), - _selectedIndex(selectedIndex) { + _selectedIndex(selectedIndex), + _index(index) { } void MenuButton::clicked(int x, int y, bool pressed) { diff --git a/src/ui/inputs.h b/src/ui/inputs.h index 521173dc..bb62c871 100644 --- a/src/ui/inputs.h +++ b/src/ui/inputs.h @@ -146,6 +146,7 @@ struct FormInput : public Shape { bool _pressed; protected: + bool __padding[3]; int _id; bool _exit; bool _visible; @@ -318,8 +319,8 @@ struct MenuButton : public FormButton { void clicked(int x, int y, bool pressed); void draw(int x, int y, int w, int h, int chw); - int _index; int &_selectedIndex; + int _index; }; FormEditInput *get_focus_edit(); diff --git a/src/ui/rgb.h b/src/ui/rgb.h index c78a5ce2..b3c15e98 100644 --- a/src/ui/rgb.h +++ b/src/ui/rgb.h @@ -1,6 +1,6 @@ // This file is part of SmallBASIC // -// Copyright(C) 2001-2018 Chris Warren-Smith. +// Copyright(C) 2001-2019 Chris Warren-Smith. // // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org @@ -45,7 +45,7 @@ inline void GET_ARGB(pixel_t c, uint8_t &a, uint8_t &r, uint8_t &g, uint8_t &b) #define SET_RGB(r, g, b) ((0xff000000) | (r << 16) | (g << 8) | (b)) #define SET_ARGB(a, r, g, b) (a << 24 | (r << 16) | (g << 8) | (b)) #define GET_RGB RGB888_to_RGB - #define GET_RGB2 RGB888_to_RGB + #define GET_RGB2 RGB888_BE_to_RGB #define GET_FROM_RGB888 RGB888_to_RGBA8888 #else #define SET_RGB(r, g, b) ((r << 16) | (g << 8) | (b)) diff --git a/src/ui/screen.cpp b/src/ui/screen.cpp index 4ff99108..05a9a7c3 100644 --- a/src/ui/screen.cpp +++ b/src/ui/screen.cpp @@ -1,6 +1,6 @@ // This file is part of SmallBASIC // -// Copyright(C) 2001-2014 Chris Warren-Smith. +// Copyright(C) 2001-2019 Chris Warren-Smith. // // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org @@ -203,10 +203,14 @@ void Screen::drawOverlay(bool vscroll) { drawLabel(); +#if defined(_FLTK) + drawMenu(); +#else if ((!_inputs.empty() || !_label.empty()) && isFullScreen()) { // draw the menu widget when in UI mode drawMenu(); } +#endif } void Screen::drawInto(bool background) { diff --git a/src/ui/strlib.cpp b/src/ui/strlib.cpp index 2b6a5723..0b5c6a7b 100644 --- a/src/ui/strlib.cpp +++ b/src/ui/strlib.cpp @@ -347,4 +347,18 @@ template<> void Properties::put(const char *key, const char *value) { } } - +template<> void Properties::get(const char *key, List *arrayValues) { + for (int i = 0; i < _count; i++) { + String *nextKey = (String *)_head[i++]; + if (nextKey == NULL || i == _count) { + break; + } + String *nextValue = (String *)_head[i]; + if (nextValue == NULL) { + break; + } + if (nextKey->equals(key)) { + arrayValues->add(new String(*nextValue)); + } + } +} diff --git a/src/ui/strlib.h b/src/ui/strlib.h index 14984046..d22ff144 100644 --- a/src/ui/strlib.h +++ b/src/ui/strlib.h @@ -168,12 +168,12 @@ struct List { } /** - * Returns whether string exists in the list of strings + * Returns whether list of strings constains the string */ bool contains(const char *s); /** - * Returns whether T exists in the list + * Returns whether the list contains t */ bool contains(T t) { bool result = false; @@ -279,6 +279,7 @@ struct Properties : public List { } // for Properties + void get(const char *key, List *arrayValues); void load(const char *s); void load(const char *s, int len); void put(const char *key, const char *value); @@ -295,7 +296,8 @@ struct Properties : public List { }; // specialisations for String properties -template<> void strlib::Properties::load(const char *); +template<> void Properties::get(const char *key, List *arrayValues); +template<> void Properties::load(const char *); template<> void Properties::load(const char *s, int slen); template<> void Properties::put(const char *key, const char *value); diff --git a/src/ui/system.cpp b/src/ui/system.cpp index 33c79b89..a60d7da7 100644 --- a/src/ui/system.cpp +++ b/src/ui/system.cpp @@ -1,6 +1,6 @@ // This file is part of SmallBASIC // -// Copyright(C) 2001-2017 Chris Warren-Smith. +// Copyright(C) 2001-2019 Chris Warren-Smith. // // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org @@ -74,10 +74,12 @@ void Cache::add(const char *key, const char *value) { } System::System() : + _cache(MAX_CACHE), _output(NULL), - _state(kInitState), _editor(NULL), - _cache(MAX_CACHE), + _systemMenu(NULL), + _programSrc(NULL), + _state(kInitState), _touchX(-1), _touchY(-1), _touchCurX(-1), @@ -85,13 +87,11 @@ System::System() : _initialFontSize(0), _fontScale(100), _userScreenId(-1), - _systemMenu(NULL), + _modifiedTime(0), _mainBas(false), _buttonPressed(false), _srcRendered(false), - _menuActive(false), - _programSrc(NULL), - _modifiedTime(0) { + _menuActive(false) { g_system = this; } @@ -525,7 +525,7 @@ char *System::readSource(const char *fileName) { _activeFile.clear(); char *buffer; if (!_mainBas && _editor != NULL && _loadPath.equals(fileName)) { - buffer = _editor->getTextSelection(); + buffer = _editor->getTextSelection(true); } else { buffer = loadResource(fileName); if (!buffer) { @@ -581,7 +581,7 @@ void System::runEdit(const char *startupBas) { while (true) { if (loadSource(_loadPath)) { setupPath(_loadPath); - editSource(_loadPath); + editSource(_loadPath, false); if (isBack() || isClosing()) { break; } else { @@ -656,7 +656,7 @@ void System::runMain(const char *mainBasPath) { } if (!_mainBas && isEditReady() && loadSource(_loadPath)) { - editSource(_loadPath); + editSource(_loadPath, true); if (isBack()) { _loadPath.clear(); _state = kActiveState; @@ -905,7 +905,7 @@ void System::showMenu() { _systemMenu[index++] = MENU_PASTE; _systemMenu[index++] = MENU_SELECT_ALL; } -#if defined(_SDL) +#if defined(_SDL) || defined(_FLTK) items->add(new String("Back")); _systemMenu[index++] = MENU_BACK; #else @@ -927,7 +927,7 @@ void System::showMenu() { items->add(new String("Restart")); _systemMenu[index++] = MENU_RESTART; } -#if !defined(_SDL) +#if !defined(_SDL) && !defined(_FLTK) items->add(new String("Show keypad")); _systemMenu[index++] = MENU_KEYPAD; #endif @@ -944,7 +944,7 @@ void System::showMenu() { items->add(new String(buffer)); _systemMenu[index++] = MENU_EDITMODE; } -#if !defined(_SDL) +#if !defined(_SDL) && !defined(_FLTK) if (!_mainBas && !_activeFile.empty()) { items->add(new String("Desktop Shortcut")); items->add(new String("Share")); @@ -955,7 +955,7 @@ void System::showMenu() { sprintf(buffer, "Audio [%s]", (opt_mute_audio ? "OFF" : "ON")); items->add(new String(buffer)); _systemMenu[index++] = MENU_AUDIO; -#if defined(_SDL) +#if defined(_SDL) || defined(_FLTK) items->add(new String("Back")); _systemMenu[index++] = MENU_BACK; #endif @@ -1125,7 +1125,7 @@ void System::printSource() { void System::setExit(bool quit) { if (!isClosing()) { bool running = isRunning(); - _state = quit ? kClosingState : kBackState; + _state = (quit && _editor == NULL) ? kClosingState : kBackState; if (running) { brun_break(); } @@ -1258,11 +1258,13 @@ int osd_textwidth(const char *str) { return EXTENT_X(textSize); } +#if !defined(_FLTK) void osd_write(const char *str) { if (!g_system->isClosing()) { g_system->getOutput()->print(str); } } +#endif void lwrite(const char *str) { if (!(str[0] == '\n' && str[1] == '\0') && !g_system->isClosing()) { @@ -1287,12 +1289,6 @@ int maGetMilliSecondCount(void) { return dev_get_millisecond_count(); } -void create_func(var_p_t map, const char *name, method cb) { - var_p_t v_func = map_add_var(map, name, 0); - v_func->type = V_FUNC; - v_func->v.fn.cb = cb; -} - void dev_log_stack(const char *keyword, int type, int line) { return g_system->logStack(keyword, type, line); } diff --git a/src/ui/system.h b/src/ui/system.h index e5537fc8..f4bd370a 100755 --- a/src/ui/system.h +++ b/src/ui/system.h @@ -1,6 +1,6 @@ // This file is part of SmallBASIC // -// Copyright(C) 2001-2017 Chris Warren-Smith. +// Copyright(C) 2001-2019 Chris Warren-Smith. // // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org @@ -14,7 +14,6 @@ #include "ui/ansiwidget.h" #include "ui/textedit.h" -void create_func(var_p_t form, const char *name, method cb); void reset_image_cache(); struct Cache : public strlib::Properties { @@ -71,7 +70,7 @@ struct System { virtual char *getClipboardText() = 0; protected: - void editSource(strlib::String loadPath); + void editSource(strlib::String loadPath, bool restoreOnExit); bool execute(const char *bas); bool fileExists(strlib::String &path); MAEvent getNextEvent() { return processEvents(1); } @@ -101,12 +100,19 @@ struct System { void showSystemScreen(bool showSrc); void waitForBack(); void waitForChange(bool error); - AnsiWidget *_output; // platform static virtual bool getPen3(); void completeKeyword(int index); + strlib::Stack _history; + StackTrace _stackTrace; + Cache _cache; + AnsiWidget *_output; + TextEditInput *_editor; + int *_systemMenu; + char *_programSrc; + enum { kInitState = 0,// thread not active kActiveState, // thread activated @@ -121,12 +127,6 @@ struct System { kDoneState // thread has terminated } _state; - strlib::Stack _history; - strlib::String _loadPath; - strlib::String _activeFile; - StackTrace _stackTrace; - TextEditInput *_editor; - Cache _cache; int _touchX; int _touchY; int _touchCurX; @@ -134,13 +134,13 @@ struct System { int _initialFontSize; int _fontScale; int _userScreenId; - int *_systemMenu; + uint32_t _modifiedTime; bool _mainBas; bool _buttonPressed; bool _srcRendered; bool _menuActive; - char *_programSrc; - uint32_t _modifiedTime; + strlib::String _loadPath; + strlib::String _activeFile; }; #endif diff --git a/src/ui/textedit.cpp b/src/ui/textedit.cpp index 28f11fc5..7a2a907f 100644 --- a/src/ui/textedit.cpp +++ b/src/ui/textedit.cpp @@ -1,6 +1,6 @@ // This file is part of SmallBASIC // -// Copyright(C) 2001-2018 Chris Warren-Smith. +// Copyright(C) 2001-2019 Chris Warren-Smith. // // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org @@ -26,7 +26,42 @@ void safe_memmove(void *dest, const void *src, size_t n) { #define IS_VAR_CHAR(ch) (ch == '_' || ch == '$' || isalpha(ch) || isdigit(ch)) #define STB_TEXTEDIT_memmove safe_memmove #define STB_TEXTEDIT_IMPLEMENTATION + +int is_word_border(EditBuffer *_str, int _idx) { + return _idx > 0 ? ((STB_TEXTEDIT_IS_SPACE(STB_TEXTEDIT_GETCHAR(_str,_idx-1)) || + STB_TEXTEDIT_IS_PUNCT(STB_TEXTEDIT_GETCHAR(_str,_idx-1))) && + !STB_TEXTEDIT_IS_SPACE(STB_TEXTEDIT_GETCHAR(_str, _idx))) : 1; +} + +int textedit_move_to_word_previous(EditBuffer *str, int c) { + --c; // always move at least one character + while (c >= 0 && !is_word_border(str, c)) { + --c; + } + if (c < 0) { + c = 0; + } + return c; +} + +int textedit_move_to_word_next(EditBuffer *str, int c) { + const int len = str->_len; + ++c; // always move at least one character + while (c < len && !is_word_border(str, c)) { + ++c; + } + if (c > len) { + c = len; + } + return c; +} + +#define STB_TEXTEDIT_MOVEWORDLEFT textedit_move_to_word_previous +#define STB_TEXTEDIT_MOVEWORDRIGHT textedit_move_to_word_next + +#pragma GCC diagnostic ignored "-Wunused-function" #include "lib/stb_textedit.h" +#pragma GCC diagnostic pop #define GROW_SIZE 128 #define LINE_BUFFER_SIZE 200 @@ -153,9 +188,11 @@ const char *helpText = "C-end bottom\n" "C-5,6,7 macro\n" "A-c change case\n" + "A-d kill word\n" "A-g goto line\n" "A-n trim line-endings\n" "A-t select theme\n" + "A-w select word\n" "A-. return mode\n" "A- recent file\n" "A-= count chars\n" @@ -191,6 +228,10 @@ int compareIntegers(const void *p1, const void *p2) { return i1 < i2 ? -1 : i1 == i2 ? 0 : 1; } +const char *find_str(bool allUpper, const char *haystack, const char *needle) { + return allUpper ? strstr(haystack, needle) : strcasestr(haystack, needle); +} + // // EditTheme // @@ -221,6 +262,14 @@ EditTheme::EditTheme(int fg, int bg) : _row_marker(fg) { } +void EditTheme::setId(const unsigned themeId) { + if (themeId >= (sizeof(themes) / sizeof(themes[0]))) { + selectTheme(themes[0]); + } else { + selectTheme(themes[themeId]); + } +} + void EditTheme::selectTheme(const int theme[]) { _color = theme[0]; _selection_color = theme[1]; @@ -728,6 +777,12 @@ bool TextEditInput::edit(int key, int screenWidth, int charWidth) { case SB_KEY_ALT('c'): changeCase(); break; + case SB_KEY_ALT('d'): + killWord(); + break; + case SB_KEY_ALT('w'): + selectWord(); + break; case SB_KEY_CTRL('d'): stb_textedit_key(&_buf, &_state, STB_TEXTEDIT_K_DELETE); break; @@ -799,15 +854,24 @@ bool TextEditInput::edit(int key, int screenWidth, int charWidth) { bool TextEditInput::find(const char *word, bool next) { bool result = false; + bool allUpper = true; + int len = strlen(word); + for (int i = 0; i < len; i++) { + if (islower(word[i])) { + allUpper = false; + break; + } + } + if (_buf._buffer != NULL && word != NULL) { - const char *found = strcasestr(_buf._buffer + _state.cursor, word); + const char *found = find_str(allUpper, _buf._buffer + _state.cursor, word); if (next && found != NULL) { // skip to next word - found = strcasestr(found + strlen(word), word); + found = find_str(allUpper, found + strlen(word), word); } if (found == NULL) { // start over - found = strcasestr(_buf._buffer, word); + found = find_str(allUpper, _buf._buffer, word); } if (found != NULL) { result = true; @@ -865,7 +929,7 @@ int TextEditInput::getSelectionRow() { return result; } -char *TextEditInput::getTextSelection() { +char *TextEditInput::getTextSelection(bool selectAll) { char *result; if (_state.select_start != _state.select_end) { int start, end; @@ -877,8 +941,10 @@ char *TextEditInput::getTextSelection() { end = _state.select_end; } result = _buf.textRange(start, end); - } else { + } else if (selectAll) { result = _buf.textRange(0, _buf._len); + } else { + result = NULL; } return result; } @@ -1150,7 +1216,8 @@ void TextEditInput::editDeleteLine() { int start = _state.cursor; int end = linePos(_state.cursor, true, true); if (end > start) { - stb_textedit_delete(&_buf, &_state, start, end - start); + // delete the entire line when the cursor is at the home position + stb_textedit_delete(&_buf, &_state, start, end - start + (_cursorCol == 0 ? 1 : 0)); _state.cursor = start; } else if (start == end) { stb_textedit_delete(&_buf, &_state, start, 1); @@ -1164,7 +1231,24 @@ void TextEditInput::editEnter() { if (prevLineStart || _cursorLine == 1) { char spaces[LINE_BUFFER_SIZE]; - int indent = getIndent(spaces, sizeof(spaces), prevLineStart); + int indent = getIndent(spaces, LINE_BUFFER_SIZE, prevLineStart); + + // check whether the previous line was a comment + char *buf = lineText(prevLineStart); + int length = strlen(buf); + int pos = 0; + while (buf && (buf[pos] == ' ' || buf[pos] == '\t')) { + pos++; + } + if (length > 2 && (buf[pos] == '#' || buf[pos] == '\'') && indent + 2 < LINE_BUFFER_SIZE) { + spaces[indent] = buf[pos]; + spaces[++indent] = ' '; + spaces[++indent] = '\0'; + } else if (length > 4 && strncasecmp(buf + pos, "rem", 3) == 0) { + indent = strlcat(spaces, "rem ", LINE_BUFFER_SIZE); + } + free(buf); + if (indent) { _buf.insertChars(_state.cursor, spaces, indent); stb_text_makeundo_insert(&_state, _state.cursor, indent); @@ -1175,7 +1259,6 @@ void TextEditInput::editEnter() { void TextEditInput::editTab() { char spaces[LINE_BUFFER_SIZE]; - int indent; // get the desired indent based on the previous line int start = lineStart(_state.cursor); @@ -1186,7 +1269,7 @@ void TextEditInput::editTab() { prevLineStart = lineStart(prevLineStart - 1); } // note - spaces not used in this context - indent = (prevLineStart || _cursorLine == 2) ? getIndent(spaces, sizeof(spaces), prevLineStart) : 0; + int indent = (prevLineStart || _cursorLine == 2) ? getIndent(spaces, sizeof(spaces), prevLineStart) : 0; // get the current lines indent char *buf = lineText(start); @@ -1523,6 +1606,23 @@ void TextEditInput::gotoNextMarker() { } } +void TextEditInput::killWord() { + int start = _state.cursor; + int end = wordEnd(); + if (start == end) { + int word = textedit_move_to_word_next(&_buf, _state.cursor); + end = textedit_move_to_word_next(&_buf, word) - 1; + int bound = lineEnd(start); + if (end > bound && bound != start) { + // clip to line end when there are characters prior to the line end + end = bound; + } + } + if (end > start) { + stb_textedit_delete(&_buf, &_state, start, end - start); + } +} + void TextEditInput::lineNavigate(bool arrowDown) { if (arrowDown) { if (!_bottom) { @@ -1649,6 +1749,21 @@ void TextEditInput::removeTrailingSpaces() { setCursorRow(row - 1); } +void TextEditInput::selectWord() { + if (_state.select_start != _state.select_end) { + // advance to next word + _state.cursor = textedit_move_to_word_next(&_buf, _state.cursor); + _state.select_start = _state.select_end = -1; + } + _state.select_start = wordStart(); + _state.select_end = _state.cursor = wordEnd(); + + if (_state.select_start == _state.select_end) { + // move to next word + _state.cursor = textedit_move_to_word_next(&_buf, _state.cursor); + } +} + void TextEditInput::setColor(SyntaxState &state) { switch (state) { case kComment: @@ -1716,8 +1831,8 @@ int TextEditInput::wordEnd() { int TextEditInput::wordStart() { int cursor = _state.cursor == 0 ? 0 : _state.cursor - 1; return ((cursor >= 0 && cursor < _buf._len && _buf._buffer[cursor] == '\n') ? _state.cursor : - is_word_boundary(&_buf, _state.cursor) ? _state.cursor : - stb_textedit_move_to_word_previous(&_buf, _state.cursor)); + is_word_border(&_buf, _state.cursor) ? _state.cursor : + textedit_move_to_word_previous(&_buf, _state.cursor)); } // @@ -2034,11 +2149,20 @@ void TextEditHelpWidget::createOutline() { } void TextEditHelpWidget::createSearch(bool replace) { - if (_mode == kSearch) { - _editor->find(_buf._buffer, true); - } else { + if (_mode != kSearch) { reset(replace ? kSearchReplace : kSearch); } + + char *text = _editor->getTextSelection(false); + if (text != NULL) { + // prime search from selected text + _buf.clear(); + _buf.insertChars(0, text, strlen(text)); + free(text); + + // ensure the selected word is first match + _editor->setCursorPos(_editor->getSelectionStart()); + } } void TextEditHelpWidget::createStackTrace(const char *error, int line, StackTrace &trace) { diff --git a/src/ui/textedit.h b/src/ui/textedit.h index 6410f116..6580b62f 100644 --- a/src/ui/textedit.h +++ b/src/ui/textedit.h @@ -1,6 +1,6 @@ // This file is part of SmallBASIC // -// Copyright(C) 2001-2015 Chris Warren-Smith. +// Copyright(C) 2001-2019 Chris Warren-Smith. // // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org @@ -32,6 +32,7 @@ struct TextEditInput; struct EditTheme { EditTheme(); EditTheme(int fg, int bg); + void setId(const unsigned themeId); void selectTheme(const int theme[]); int _color; @@ -101,11 +102,13 @@ struct TextEditInput : public FormEditInput { int getRow() const { return _cursorRow + 1; } int getPageRows() const { return _height / _charHeight; } int getLines() { return _buf.lineCount(); } + int getMarginWidth() { return _marginWidth; } void getSelectionCounts(int *lines, int *chars); int getSelectionRow(); + int getSelectionStart() { return _state.select_start; } int getScroll() const { return _scroll; } const char *getText() const { return _buf._buffer; } - char *getTextSelection(); + char *getTextSelection(bool selectAll); int getTextLength() const { return _buf._len; } int *getMarkers(); void gotoLine(const char *buffer); @@ -162,6 +165,7 @@ struct TextEditInput : public FormEditInput { int getLineChars(StbTexteditRow *row, int pos); char *getSelection(int *start, int *end); void gotoNextMarker(); + void killWord(); void lineNavigate(bool lineDown); char *lineText(int pos); int lineEnd(int pos) { return linePos(pos, true); } @@ -171,6 +175,7 @@ struct TextEditInput : public FormEditInput { bool matchStatement(uint32_t hash); void pageNavigate(bool pageDown, bool shift); void removeTrailingSpaces(); + void selectWord(); void setColor(SyntaxState &state); void toggleMarker(); void updateScroll(); diff --git a/src/ui/utils.h b/src/ui/utils.h index 0c492a28..c4448afc 100644 --- a/src/ui/utils.h +++ b/src/ui/utils.h @@ -1,6 +1,6 @@ // This file is part of SmallBASIC // -// Copyright(C) 2001-2014 Chris Warren-Smith. +// Copyright(C) 2001-2019 Chris Warren-Smith. // // This program is distributed under the terms of the GPL v2.0 or later // Download the GNU Public License (GPL) from www.gnu.org @@ -28,7 +28,7 @@ #include #define deviceLog(...) __android_log_print(ANDROID_LOG_INFO, \ "smallbasic", __VA_ARGS__) -#elif defined(_SDL) +#elif defined(_SDL) || defined(_FLTK) void appLog(const char *format, ...); #define deviceLog(...) appLog(__VA_ARGS__) #endif diff --git a/src/ui/window.cpp b/src/ui/window.cpp index e21bdf96..1335096d 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -147,17 +147,17 @@ void cmd_window_message(var_s *self) { extern "C" void v_create_window(var_p_t var) { map_init(var); - create_func(var, WINDOW_SCREEN1, cmd_window_select_screen1); - create_func(var, WINDOW_SCREEN2, cmd_window_select_screen2); - create_func(var, WINDOW_SCREEN3, cmd_window_select_screen3); - create_func(var, WINDOW_ALERT, cmd_window_alert); - create_func(var, WINDOW_ASK, cmd_window_ask); - create_func(var, WINDOW_MESSAGE, cmd_window_message); - create_func(var, WINDOW_MENU, cmd_window_menu); - create_func(var, WINDOW_VKEYPAD, cmd_window_show_keypad); - create_func(var, WINDOW_INSET, cmd_window_inset); - create_func(var, WINDOW_SETFONT, cmd_window_set_font); - create_func(var, WINDOW_SETSIZE, cmd_window_set_size); + v_create_func(var, WINDOW_SCREEN1, cmd_window_select_screen1); + v_create_func(var, WINDOW_SCREEN2, cmd_window_select_screen2); + v_create_func(var, WINDOW_SCREEN3, cmd_window_select_screen3); + v_create_func(var, WINDOW_ALERT, cmd_window_alert); + v_create_func(var, WINDOW_ASK, cmd_window_ask); + v_create_func(var, WINDOW_MESSAGE, cmd_window_message); + v_create_func(var, WINDOW_MENU, cmd_window_menu); + v_create_func(var, WINDOW_VKEYPAD, cmd_window_show_keypad); + v_create_func(var, WINDOW_INSET, cmd_window_inset); + v_create_func(var, WINDOW_SETFONT, cmd_window_set_font); + v_create_func(var, WINDOW_SETSIZE, cmd_window_set_size); } extern "C" void dev_show_page() {