Fossil

Changes On Branch pikchrshow-wasm
Login

Changes On Branch pikchrshow-wasm

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch pikchrshow-wasm Excluding Merge-Ins

This is equivalent to a diff from e6554457 to 98dd2626

2022-06-08
23:27
Merge in pikchrshow-wasm branch: reimplement /pikchrshow using a client-side WASM build of pikchr.c, plus related feature-adjacent tweaks in mimetype handling. ... (check-in: 7fcb4626 user: stephan tags: trunk)
12:19
Refactored the configure script's emsdk detection in the hopes that this reformulation will work on systems where emcc is installed via a package manager. ... (Closed-Leaf check-in: 98dd2626 user: stephan tags: pikchrshow-wasm)
10:09
build.wiki: added notes about emsdk requirements and keeping it up to date. ... (check-in: b256c5bc user: stephan tags: pikchrshow-wasm)
2022-06-07
06:22
Corrected a bit of misinformation in search.c's comments, per a report in the forum. ... (check-in: 6d6880c8 user: stephan tags: trunk)
2022-06-06
18:02
Replace /pikchrshow with a WASM-based version and rename the prior version to /pikchrshowcs (cs=client/server). There are still a couple layout/style quirks to resolve, and a feature or two to port from the legacy app, but it more or less works. ... (check-in: 321f01a8 user: stephan tags: pikchrshow-wasm)
15:51
Remove the '; charset=utf-8' suffix from response Content-Type headers. That modifier is technically incorrect for many mimetypes, but wasm loaders are extra picky about it and refuse to load wasm files with the charset set. An attempt at porting over althttpd's solution for this same problem leads down a much deeper and far more invasive rabbit hole because how fossil handles/sets the response content type is more involved than in althttpd. ... (Closed-Leaf check-in: 15e7b49e user: stephan tags: content-type-no-charset)
00:41
Fix the new linear bisect so that it always goes from good to bad and stops at the first bad check-in found. ... (check-in: e6554457 user: drh tags: trunk)
2022-06-05
19:43
Add the "fossil bisect option linear on" command that allows the "fossil bisect run" command to invoke a test script on every check-in along a path between two boundary check-ins. The "linear" option resets automatically opon "fossil bisect reset". ... (check-in: 42f61b67 user: drh tags: trunk)

Changes to Makefile.in.

70
71
72
73
74
75
76











77
78
79
80
81
82
83
USE_LINENOISE = @USE_LINENOISE@
USE_MMAN_H = @USE_MMAN_H@
USE_SEE = @USE_SEE@
APPNAME = fossil
#
# APPNAME = fossil-fuzz
# may be more appropriate for fuzzing.












.PHONY: all tags

include $(SRCDIR)/main.mk

distclean: clean
	-rm -f autoconfig.h config.log Makefile







>
>
>
>
>
>
>
>
>
>
>







70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
USE_LINENOISE = @USE_LINENOISE@
USE_MMAN_H = @USE_MMAN_H@
USE_SEE = @USE_SEE@
APPNAME = fossil
#
# APPNAME = fossil-fuzz
# may be more appropriate for fuzzing.

#### Emscripten stuff for optionally doing in-tree builds
# of any WASM components. We store precompiled WASM in the the SCM, so
# this is only useful for people who actively work on WASM
# components. EMSDK_ENV refers to the "environment" script which comes
# with the Emscripten SDK package:
# https://emscripten.org/docs/getting_started/downloads.html
EMSDK_HOME = @EMSDK_HOME@
EMSDK_ENV = @EMSDK_ENV@
EMCC_OPT = @EMCC_OPT@
EMCC_WRAPPER = $(SRCDIR_tools)/emcc.sh

.PHONY: all tags

include $(SRCDIR)/main.mk

distclean: clean
	-rm -f autoconfig.h config.log Makefile

Changes to auto.def.

26
27
28
29
30
31
32

33
34
35
36
37
38
39
                         => {print the minimum SQLite version number required, and exit}
    internal-sqlite=1    => {Don't use the internal SQLite, use the system one}
    static=0             => {Link a static executable}
    fusefs=1             => {Disable the Fuse Filesystem}
    fossil-debug=0       => {Build with fossil debugging enabled}
    no-opt=0             => {Build without optimization}
    json=0               => {Build with fossil JSON API enabled}

}

# Update the minimum required SQLite version number here, and also
# in src/main.c near the sqlite3_libversion_number() call.  Take care
# that both places agree!
define MINIMUM_SQLITE_VERSION "3.38.0"








>







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
                         => {print the minimum SQLite version number required, and exit}
    internal-sqlite=1    => {Don't use the internal SQLite, use the system one}
    static=0             => {Link a static executable}
    fusefs=1             => {Disable the Fuse Filesystem}
    fossil-debug=0       => {Build with fossil debugging enabled}
    no-opt=0             => {Build without optimization}
    json=0               => {Build with fossil JSON API enabled}
    with-emsdk:path      => {Directory containing the Emscripten SDK}
}

# Update the minimum required SQLite version number here, and also
# in src/main.c near the sqlite3_libversion_number() call.  Take care
# that both places agree!
define MINIMUM_SQLITE_VERSION "3.38.0"

630
631
632
633
634
635
636



























637
638
639
640
641
642
643
    #       be checked for near the bottom of this file.
    #
    set tclconfig(TCL_LD_FLAGS) [string map [list -ldl ""] \
        $tclconfig(TCL_LD_FLAGS)]
    define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS)
    define FOSSIL_ENABLE_TCL
}




























# Network functions require libraries on some systems
cc-check-function-in-lib gethostbyname nsl
if {![cc-check-function-in-lib socket {socket network}]} {
    # Last resort, may be Windows
    if {[is_mingw]} {
        define-append LIBS -lwsock32







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
    #       be checked for near the bottom of this file.
    #
    set tclconfig(TCL_LD_FLAGS) [string map [list -ldl ""] \
        $tclconfig(TCL_LD_FLAGS)]
    define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS)
    define FOSSIL_ENABLE_TCL
}

# Emscripten is a purely optional component used only for doing
# in-tree builds of WASM stuff, as opposed to WASM binaries we import
# from other places. This is only set up for Unix-style OSes and is
# untested anywhere but Linux.
set emsdkHome [opt-val with-emsdk]
define EMSDK_HOME ""
define EMSDK_ENV ""
define EMCC_OPT "-Oz"
if {$emsdkHome eq "" && [info exists ::env(EMSDK)]} {
  # Fall back to checking the environment. $EMSDK gets set
  # by sourcing emsdk_env.sh.
  set emsdkHome $::env(EMSDK)
}
if {$emsdkHome ne ""} {
  define EMSDK_HOME $emsdkHome
  set emsdkEnv "$emsdkHome/emsdk_env.sh"
  if {[file exists $emsdkEnv]} {
    puts "Using Emscripten SDK environment from $emsdkEnv."
    define EMSDK_ENV $emsdkEnv
    if {[info exists ::env(EMCC_OPT)]} {
      define EMCC_OPT $::env(EMCC_OPT)
    }
  } else {
    puts "emsdk_env.sh not found. Assuming emcc is in the PATH."
  }
}

# Network functions require libraries on some systems
cc-check-function-in-lib gethostbyname nsl
if {![cc-check-function-in-lib socket {socket network}]} {
    # Last resort, may be Windows
    if {[is_mingw]} {
        define-append LIBS -lwsock32
724
725
726
727
728
729
730








731
732
733
}
if {[opt-bool static]} {
    # Linux can only infer the dependency on pthread from OpenSSL when
    # doing dynamic linkage.
    define-append LIBS -lpthread
}










make-template Makefile.in
make-config-header autoconfig.h -auto {USE_* FOSSIL_*}







>
>
>
>
>
>
>
>



752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
}
if {[opt-bool static]} {
    # Linux can only infer the dependency on pthread from OpenSSL when
    # doing dynamic linkage.
    define-append LIBS -lpthread
}

if {[get-define EMSDK_HOME] ne ""} {
  define EMCC_WRAPPER $::autosetup(dir)/../tools/emcc.sh
  make-template tools/emcc.sh.in
  catch {exec chmod u+x tools/emcc.sh}
} else {
  define EMCC_WRAPPER ""
  catch {exec rm -f tools/emcc.sh}
}

make-template Makefile.in
make-config-header autoconfig.h -auto {USE_* FOSSIL_*}

Added extsrc/pikchr-worker.js.



























































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/*
  2022-05-20

  The author disclaims copyright to this source code.  In place of a
  legal notice, here is a blessing:

  *   May you do good and not evil.
  *   May you find forgiveness for yourself and forgive others.
  *   May you share freely, never taking more than you give.

  ***********************************************************************

  This is a JS Worker file for use with the pikchr wasm build. It
  loads the pikchr wasm module and offers access to it via the Worker
  message-passing interface.

  Because we can have only a single message handler, as opposed to an
  arbitrary number of discrete event listeners like with DOM elements,
  we have to define a lower-level message API. Messages abstractly
  look like:

  { type: string, data: type-specific value }

  Where 'type' is used for dispatching and 'data' is a
  'type'-dependent value.

  The 'type' values expected by each side of the main/worker
  connection vary. The types are described below but subject to
  change at any time as this experiment evolves.

  Main-to-Worker message types:

  - pikchr: data=pikchr-format text to render or an object:

  {
    pikchr: source code for the pikchr,
    darkMode: boolean true to adjust colors for a dark color scheme,
    cssClass: CSS class name to add to the SVG
  }

  Workers-to-Main types

  - stdout, stderr: indicate stdout/stderr output from the wasm
  layer. The data property is the string of the output, noting
  that the emscripten binding emits these one line at a time. Thus,
  if a C-side puts() emits multiple lines in a single call, the JS
  side will see that as multiple calls. Example:

  {type:'stdout', data: 'Hi, world.'}

  - module: Status text. This is intended to alert the main thread
  about module loading status so that, e.g., the main thread can
  update a progress widget and DTRT when the module is finished
  loading and available for work. Status messages come in the form
  
  {type:'module', data:{
  type:'status',
  data: {text:string|null, step:1-based-integer}
  }

  with an incrementing step value for each subsequent message. When
  the module loading is complete, a message with a text value of
  null is posted.

  - pikchr: 

  {type: 'pikchr',
    data:{
      pikchr: input text,
      result: rendered result (SVG on success, HTML on error),
      isError: bool, true if .result holds an error report,
      flags: integer: flags used to configure the pikchr rendering,
      width: if !isError, width (integer pixels) of the SVG,
      height: if !isError, height (integer pixels) of the SVG
    }
  }

*/

"use strict";
(function(){
  /**
     Posts a message in the form {type,data} unless passed more than
     2 args, in which case it posts {type, data:[arg1...argN]}.
  */
  const wMsg = function(type,data){
    postMessage({
      type,
      data: arguments.length<3
        ? data
        : Array.prototype.slice.call(arguments,1)
    });
  };

  const stderr = function(){wMsg('stderr', Array.prototype.slice.call(arguments));};

  self.onerror = function(/*message, source, lineno, colno, error*/) {
    const err = arguments[4];
    if(err && 'ExitStatus'==err.name){
      /* This "cannot happen" for this wasm binding, but just in
         case... */
      pikchrModule.isDead = true;
      stderr("FATAL ERROR:", err.message);
      stderr("Restarting the app requires reloading the page.");
      wMsg('error', err);
    }
    pikchrModule.setStatus('Exception thrown, see JavaScript console: '+err);
  };

  self.onmessage = function f(ev){
    ev = ev.data;
    switch(ev.type){
          /**
             Runs the given text through pikchr and emits a 'pikchr'
             message result (output format documented above).

             Fires a working/start event before it starts and
             working/end event when it finishes.
          */
        case 'pikchr':
          if(pikchrModule.isDead){
            stderr("wasm module has exit()ed. Cannot pikchr.");
            return;
          }
          if(!f._){
            f._ = pikchrModule.cwrap('pikchr', 'string', [
              'string'/*script*/, 'string'/*CSS class*/, 'number'/*flags*/,
              'number'/*output: SVG width*/, 'number'/*output: SVG height*/
            ]);
          }
          wMsg('working','start');
          const stack = pikchrModule.stackSave();
          try {
            const pnWidth = pikchrModule.stackAlloc(4),
                  pnHeight = pikchrModule.stackAlloc(4);
            let script = '', flags = 0, cssClass = null;
            if('string'===typeof ev.data){
              script = ev.data;
            }else if(ev.data && 'object'===typeof ev.data){
              script = ev.data.pikchr;
              flags = ev.data.darkMode ? 0x02 : 0;
              if(ev.data.cssClass) cssClass = ev.data.cssClass;
            }
            pikchrModule.setValue(pnWidth, 0, "i32");
            pikchrModule.setValue(pnHeight, 0, "i32");
            const msg = {
              pikchr: script,
              result: (f._(script, cssClass, flags, pnWidth, pnHeight) || "").trim(),
              flags: flags
            };
            msg.isError = !!(msg.result && msg.result.startsWith('<div'));
            if(msg.isError){
              msg.width = msg.height = null;
            }else{
              msg.width = pikchrModule.getValue(pnWidth, "i32");
              msg.height = pikchrModule.getValue(pnHeight, "i32");
            }
            wMsg('pikchr', msg);
          } finally {
            pikchrModule.stackRestore(stack);
            wMsg('working','end');
          }
          return;
    };
    console.warn("Unknown pikchr-worker message type:",ev);
  };
  
  /**
     emscripten module for use with build mode -sMODULARIZE.
  */
  const pikchrModule = {
    print: function(){wMsg('stdout', Array.prototype.slice.call(arguments));},
    printErr: stderr,
    /**
       Intercepts status updates from the emscripting module init
       and fires worker events with a type of 'status' and a
       payload of:

       {
       text: string | null, // null at end of load process
       step: integer // starts at 1, increments 1 per call
       }

       We have no way of knowing in advance how many steps will
       be processed/posted, so creating a "percentage done" view is
       not really practical. One can be approximated by giving it a
       current value of message.step and max value of message.step+1,
       though.

       When work is finished, a message with a text value of null is
       submitted.

       After a message with text==null is posted, the module may later
       post messages about fatal problems, e.g. an exit() being
       triggered, so it is recommended that UI elements for posting
       status messages not be outright removed from the DOM when
       text==null, and that they instead be hidden until/unless
       text!=null.
    */
    setStatus: function f(text){
      if(!f.last) f.last = { step: 0, text: '' };
      else if(text === f.last.text) return;
      f.last.text = text;
      wMsg('module',{
        type:'status',
        data:{step: ++f.last.step, text: text||null}
      });
    }
  };

  importScripts('pikchr.js');
  /**
     initPikchrModule() is installed via pikchr.js due to
     building with:

     emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initPikchrModule
  */
  initPikchrModule(pikchrModule).then(function(thisModule){
    wMsg('pikchrshow-ready');
  });
})();

Added extsrc/pikchr.js.



































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721

var initPikchrModule = (() => {
  var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
  
  return (
function(initPikchrModule) {
  initPikchrModule = initPikchrModule || {};

var Module = typeof initPikchrModule != "undefined" ? initPikchrModule : {};

var readyPromiseResolve, readyPromiseReject;

Module["ready"] = new Promise(function(resolve, reject) {
 readyPromiseResolve = resolve;
 readyPromiseReject = reject;
});

var moduleOverrides = Object.assign({}, Module);

var arguments_ = [];

var thisProgram = "./this.program";

var quit_ = (status, toThrow) => {
 throw toThrow;
};

var ENVIRONMENT_IS_WEB = true;

var ENVIRONMENT_IS_WORKER = false;

var scriptDirectory = "";

function locateFile(path) {
 if (Module["locateFile"]) {
  return Module["locateFile"](path, scriptDirectory);
 }
 return scriptDirectory + path;
}

var read_, readAsync, readBinary, setWindowTitle;

if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
 if (ENVIRONMENT_IS_WORKER) {
  scriptDirectory = self.location.href;
 } else if (typeof document != "undefined" && document.currentScript) {
  scriptDirectory = document.currentScript.src;
 }
 if (_scriptDir) {
  scriptDirectory = _scriptDir;
 }
 if (scriptDirectory.indexOf("blob:") !== 0) {
  scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1);
 } else {
  scriptDirectory = "";
 }
 {
  read_ = url => {
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, false);
   xhr.send(null);
   return xhr.responseText;
  };
  if (ENVIRONMENT_IS_WORKER) {
   readBinary = url => {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, false);
    xhr.responseType = "arraybuffer";
    xhr.send(null);
    return new Uint8Array(xhr.response);
   };
  }
  readAsync = (url, onload, onerror) => {
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.responseType = "arraybuffer";
   xhr.onload = () => {
    if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
     onload(xhr.response);
     return;
    }
    onerror();
   };
   xhr.onerror = onerror;
   xhr.send(null);
  };
 }
 setWindowTitle = title => document.title = title;
} else {}

var out = Module["print"] || console.log.bind(console);

var err = Module["printErr"] || console.warn.bind(console);

Object.assign(Module, moduleOverrides);

moduleOverrides = null;

if (Module["arguments"]) arguments_ = Module["arguments"];

if (Module["thisProgram"]) thisProgram = Module["thisProgram"];

if (Module["quit"]) quit_ = Module["quit"];

var wasmBinary;

if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"];

var noExitRuntime = Module["noExitRuntime"] || true;

if (typeof WebAssembly != "object") {
 abort("no native wasm support detected");
}

var wasmMemory;

var ABORT = false;

var EXITSTATUS;

function getCFunc(ident) {
 var func = Module["_" + ident];
 return func;
}

function ccall(ident, returnType, argTypes, args, opts) {
 var toC = {
  "string": function(str) {
   var ret = 0;
   if (str !== null && str !== undefined && str !== 0) {
    var len = (str.length << 2) + 1;
    ret = stackAlloc(len);
    stringToUTF8(str, ret, len);
   }
   return ret;
  },
  "array": function(arr) {
   var ret = stackAlloc(arr.length);
   writeArrayToMemory(arr, ret);
   return ret;
  }
 };
 function convertReturnValue(ret) {
  if (returnType === "string") {
   return UTF8ToString(ret);
  }
  if (returnType === "boolean") return Boolean(ret);
  return ret;
 }
 var func = getCFunc(ident);
 var cArgs = [];
 var stack = 0;
 if (args) {
  for (var i = 0; i < args.length; i++) {
   var converter = toC[argTypes[i]];
   if (converter) {
    if (stack === 0) stack = stackSave();
    cArgs[i] = converter(args[i]);
   } else {
    cArgs[i] = args[i];
   }
  }
 }
 var ret = func.apply(null, cArgs);
 function onDone(ret) {
  if (stack !== 0) stackRestore(stack);
  return convertReturnValue(ret);
 }
 ret = onDone(ret);
 return ret;
}

function cwrap(ident, returnType, argTypes, opts) {
 argTypes = argTypes || [];
 var numericArgs = argTypes.every(function(type) {
  return type === "number";
 });
 var numericRet = returnType !== "string";
 if (numericRet && numericArgs && !opts) {
  return getCFunc(ident);
 }
 return function() {
  return ccall(ident, returnType, argTypes, arguments, opts);
 };
}

var UTF8Decoder = typeof TextDecoder != "undefined" ? new TextDecoder("utf8") : undefined;

function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead) {
 var endIdx = idx + maxBytesToRead;
 var endPtr = idx;
 while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr;
 if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
  return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
 } else {
  var str = "";
  while (idx < endPtr) {
   var u0 = heapOrArray[idx++];
   if (!(u0 & 128)) {
    str += String.fromCharCode(u0);
    continue;
   }
   var u1 = heapOrArray[idx++] & 63;
   if ((u0 & 224) == 192) {
    str += String.fromCharCode((u0 & 31) << 6 | u1);
    continue;
   }
   var u2 = heapOrArray[idx++] & 63;
   if ((u0 & 240) == 224) {
    u0 = (u0 & 15) << 12 | u1 << 6 | u2;
   } else {
    u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | heapOrArray[idx++] & 63;
   }
   if (u0 < 65536) {
    str += String.fromCharCode(u0);
   } else {
    var ch = u0 - 65536;
    str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023);
   }
  }
 }
 return str;
}

function UTF8ToString(ptr, maxBytesToRead) {
 return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";
}

function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) {
 if (!(maxBytesToWrite > 0)) return 0;
 var startIdx = outIdx;
 var endIdx = outIdx + maxBytesToWrite - 1;
 for (var i = 0; i < str.length; ++i) {
  var u = str.charCodeAt(i);
  if (u >= 55296 && u <= 57343) {
   var u1 = str.charCodeAt(++i);
   u = 65536 + ((u & 1023) << 10) | u1 & 1023;
  }
  if (u <= 127) {
   if (outIdx >= endIdx) break;
   heap[outIdx++] = u;
  } else if (u <= 2047) {
   if (outIdx + 1 >= endIdx) break;
   heap[outIdx++] = 192 | u >> 6;
   heap[outIdx++] = 128 | u & 63;
  } else if (u <= 65535) {
   if (outIdx + 2 >= endIdx) break;
   heap[outIdx++] = 224 | u >> 12;
   heap[outIdx++] = 128 | u >> 6 & 63;
   heap[outIdx++] = 128 | u & 63;
  } else {
   if (outIdx + 3 >= endIdx) break;
   heap[outIdx++] = 240 | u >> 18;
   heap[outIdx++] = 128 | u >> 12 & 63;
   heap[outIdx++] = 128 | u >> 6 & 63;
   heap[outIdx++] = 128 | u & 63;
  }
 }
 heap[outIdx] = 0;
 return outIdx - startIdx;
}

function stringToUTF8(str, outPtr, maxBytesToWrite) {
 return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
}

function writeArrayToMemory(array, buffer) {
 HEAP8.set(array, buffer);
}

var buffer, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64;

function updateGlobalBufferAndViews(buf) {
 buffer = buf;
 Module["HEAP8"] = HEAP8 = new Int8Array(buf);
 Module["HEAP16"] = HEAP16 = new Int16Array(buf);
 Module["HEAP32"] = HEAP32 = new Int32Array(buf);
 Module["HEAPU8"] = HEAPU8 = new Uint8Array(buf);
 Module["HEAPU16"] = HEAPU16 = new Uint16Array(buf);
 Module["HEAPU32"] = HEAPU32 = new Uint32Array(buf);
 Module["HEAPF32"] = HEAPF32 = new Float32Array(buf);
 Module["HEAPF64"] = HEAPF64 = new Float64Array(buf);
}

var INITIAL_MEMORY = Module["INITIAL_MEMORY"] || 16777216;

var wasmTable;

var __ATPRERUN__ = [];

var __ATINIT__ = [];

var __ATPOSTRUN__ = [];

var runtimeInitialized = false;

function keepRuntimeAlive() {
 return noExitRuntime;
}

function preRun() {
 if (Module["preRun"]) {
  if (typeof Module["preRun"] == "function") Module["preRun"] = [ Module["preRun"] ];
  while (Module["preRun"].length) {
   addOnPreRun(Module["preRun"].shift());
  }
 }
 callRuntimeCallbacks(__ATPRERUN__);
}

function initRuntime() {
 runtimeInitialized = true;
 callRuntimeCallbacks(__ATINIT__);
}

function postRun() {
 if (Module["postRun"]) {
  if (typeof Module["postRun"] == "function") Module["postRun"] = [ Module["postRun"] ];
  while (Module["postRun"].length) {
   addOnPostRun(Module["postRun"].shift());
  }
 }
 callRuntimeCallbacks(__ATPOSTRUN__);
}

function addOnPreRun(cb) {
 __ATPRERUN__.unshift(cb);
}

function addOnInit(cb) {
 __ATINIT__.unshift(cb);
}

function addOnPostRun(cb) {
 __ATPOSTRUN__.unshift(cb);
}

var runDependencies = 0;

var runDependencyWatcher = null;

var dependenciesFulfilled = null;

function addRunDependency(id) {
 runDependencies++;
 if (Module["monitorRunDependencies"]) {
  Module["monitorRunDependencies"](runDependencies);
 }
}

function removeRunDependency(id) {
 runDependencies--;
 if (Module["monitorRunDependencies"]) {
  Module["monitorRunDependencies"](runDependencies);
 }
 if (runDependencies == 0) {
  if (runDependencyWatcher !== null) {
   clearInterval(runDependencyWatcher);
   runDependencyWatcher = null;
  }
  if (dependenciesFulfilled) {
   var callback = dependenciesFulfilled;
   dependenciesFulfilled = null;
   callback();
  }
 }
}

function abort(what) {
 {
  if (Module["onAbort"]) {
   Module["onAbort"](what);
  }
 }
 what = "Aborted(" + what + ")";
 err(what);
 ABORT = true;
 EXITSTATUS = 1;
 what += ". Build with -sASSERTIONS for more info.";
 var e = new WebAssembly.RuntimeError(what);
 readyPromiseReject(e);
 throw e;
}

var dataURIPrefix = "data:application/octet-stream;base64,";

function isDataURI(filename) {
 return filename.startsWith(dataURIPrefix);
}

var wasmBinaryFile;

wasmBinaryFile = "pikchr.wasm";

if (!isDataURI(wasmBinaryFile)) {
 wasmBinaryFile = locateFile(wasmBinaryFile);
}

function getBinary(file) {
 try {
  if (file == wasmBinaryFile && wasmBinary) {
   return new Uint8Array(wasmBinary);
  }
  if (readBinary) {
   return readBinary(file);
  } else {
   throw "both async and sync fetching of the wasm failed";
  }
 } catch (err) {
  abort(err);
 }
}

function getBinaryPromise() {
 if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) {
  if (typeof fetch == "function") {
   return fetch(wasmBinaryFile, {
    credentials: "same-origin"
   }).then(function(response) {
    if (!response["ok"]) {
     throw "failed to load wasm binary file at '" + wasmBinaryFile + "'";
    }
    return response["arrayBuffer"]();
   }).catch(function() {
    return getBinary(wasmBinaryFile);
   });
  }
 }
 return Promise.resolve().then(function() {
  return getBinary(wasmBinaryFile);
 });
}

function createWasm() {
 var info = {
  "a": asmLibraryArg
 };
 function receiveInstance(instance, module) {
  var exports = instance.exports;
  Module["asm"] = exports;
  wasmMemory = Module["asm"]["d"];
  updateGlobalBufferAndViews(wasmMemory.buffer);
  wasmTable = Module["asm"]["g"];
  addOnInit(Module["asm"]["e"]);
  removeRunDependency("wasm-instantiate");
 }
 addRunDependency("wasm-instantiate");
 function receiveInstantiationResult(result) {
  receiveInstance(result["instance"]);
 }
 function instantiateArrayBuffer(receiver) {
  return getBinaryPromise().then(function(binary) {
   return WebAssembly.instantiate(binary, info);
  }).then(function(instance) {
   return instance;
  }).then(receiver, function(reason) {
   err("failed to asynchronously prepare wasm: " + reason);
   abort(reason);
  });
 }
 function instantiateAsync() {
  if (!wasmBinary && typeof WebAssembly.instantiateStreaming == "function" && !isDataURI(wasmBinaryFile) && typeof fetch == "function") {
   return fetch(wasmBinaryFile, {
    credentials: "same-origin"
   }).then(function(response) {
    var result = WebAssembly.instantiateStreaming(response, info);
    return result.then(receiveInstantiationResult, function(reason) {
     err("wasm streaming compile failed: " + reason);
     err("falling back to ArrayBuffer instantiation");
     return instantiateArrayBuffer(receiveInstantiationResult);
    });
   });
  } else {
   return instantiateArrayBuffer(receiveInstantiationResult);
  }
 }
 if (Module["instantiateWasm"]) {
  try {
   var exports = Module["instantiateWasm"](info, receiveInstance);
   return exports;
  } catch (e) {
   err("Module.instantiateWasm callback failed with error: " + e);
   return false;
  }
 }
 instantiateAsync().catch(readyPromiseReject);
 return {};
}

var tempDouble;

var tempI64;

function callRuntimeCallbacks(callbacks) {
 while (callbacks.length > 0) {
  var callback = callbacks.shift();
  if (typeof callback == "function") {
   callback(Module);
   continue;
  }
  var func = callback.func;
  if (typeof func == "number") {
   if (callback.arg === undefined) {
    getWasmTableEntry(func)();
   } else {
    getWasmTableEntry(func)(callback.arg);
   }
  } else {
   func(callback.arg === undefined ? null : callback.arg);
  }
 }
}

function getValue(ptr, type = "i8") {
 if (type.endsWith("*")) type = "i32";
 switch (type) {
 case "i1":
  return HEAP8[ptr >> 0];

 case "i8":
  return HEAP8[ptr >> 0];

 case "i16":
  return HEAP16[ptr >> 1];

 case "i32":
  return HEAP32[ptr >> 2];

 case "i64":
  return HEAP32[ptr >> 2];

 case "float":
  return HEAPF32[ptr >> 2];

 case "double":
  return Number(HEAPF64[ptr >> 3]);

 default:
  abort("invalid type for getValue: " + type);
 }
 return null;
}

function getWasmTableEntry(funcPtr) {
 return wasmTable.get(funcPtr);
}

function setValue(ptr, value, type = "i8") {
 if (type.endsWith("*")) type = "i32";
 switch (type) {
 case "i1":
  HEAP8[ptr >> 0] = value;
  break;

 case "i8":
  HEAP8[ptr >> 0] = value;
  break;

 case "i16":
  HEAP16[ptr >> 1] = value;
  break;

 case "i32":
  HEAP32[ptr >> 2] = value;
  break;

 case "i64":
  tempI64 = [ value >>> 0, (tempDouble = value, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0) ], 
  HEAP32[ptr >> 2] = tempI64[0], HEAP32[ptr + 4 >> 2] = tempI64[1];
  break;

 case "float":
  HEAPF32[ptr >> 2] = value;
  break;

 case "double":
  HEAPF64[ptr >> 3] = value;
  break;

 default:
  abort("invalid type for setValue: " + type);
 }
}

function ___assert_fail(condition, filename, line, func) {
 abort("Assertion failed: " + UTF8ToString(condition) + ", at: " + [ filename ? UTF8ToString(filename) : "unknown filename", line, func ? UTF8ToString(func) : "unknown function" ]);
}

function abortOnCannotGrowMemory(requestedSize) {
 abort("OOM");
}

function _emscripten_resize_heap(requestedSize) {
 var oldSize = HEAPU8.length;
 requestedSize = requestedSize >>> 0;
 abortOnCannotGrowMemory(requestedSize);
}

function _exit(status) {
 exit(status);
}

var asmLibraryArg = {
 "a": ___assert_fail,
 "b": _emscripten_resize_heap,
 "c": _exit
};

var asm = createWasm();

var ___wasm_call_ctors = Module["___wasm_call_ctors"] = function() {
 return (___wasm_call_ctors = Module["___wasm_call_ctors"] = Module["asm"]["e"]).apply(null, arguments);
};

var _pikchr = Module["_pikchr"] = function() {
 return (_pikchr = Module["_pikchr"] = Module["asm"]["f"]).apply(null, arguments);
};

var stackSave = Module["stackSave"] = function() {
 return (stackSave = Module["stackSave"] = Module["asm"]["h"]).apply(null, arguments);
};

var stackRestore = Module["stackRestore"] = function() {
 return (stackRestore = Module["stackRestore"] = Module["asm"]["i"]).apply(null, arguments);
};

var stackAlloc = Module["stackAlloc"] = function() {
 return (stackAlloc = Module["stackAlloc"] = Module["asm"]["j"]).apply(null, arguments);
};

Module["cwrap"] = cwrap;

Module["stackSave"] = stackSave;

Module["stackRestore"] = stackRestore;

Module["setValue"] = setValue;

Module["getValue"] = getValue;

var calledRun;

function ExitStatus(status) {
 this.name = "ExitStatus";
 this.message = "Program terminated with exit(" + status + ")";
 this.status = status;
}

dependenciesFulfilled = function runCaller() {
 if (!calledRun) run();
 if (!calledRun) dependenciesFulfilled = runCaller;
};

function run(args) {
 args = args || arguments_;
 if (runDependencies > 0) {
  return;
 }
 preRun();
 if (runDependencies > 0) {
  return;
 }
 function doRun() {
  if (calledRun) return;
  calledRun = true;
  Module["calledRun"] = true;
  if (ABORT) return;
  initRuntime();
  readyPromiseResolve(Module);
  if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"]();
  postRun();
 }
 if (Module["setStatus"]) {
  Module["setStatus"]("Running...");
  setTimeout(function() {
   setTimeout(function() {
    Module["setStatus"]("");
   }, 1);
   doRun();
  }, 1);
 } else {
  doRun();
 }
}

Module["run"] = run;

function exit(status, implicit) {
 EXITSTATUS = status;
 procExit(status);
}

function procExit(code) {
 EXITSTATUS = code;
 if (!keepRuntimeAlive()) {
  if (Module["onExit"]) Module["onExit"](code);
  ABORT = true;
 }
 quit_(code, new ExitStatus(code));
}

if (Module["preInit"]) {
 if (typeof Module["preInit"] == "function") Module["preInit"] = [ Module["preInit"] ];
 while (Module["preInit"].length > 0) {
  Module["preInit"].pop()();
 }
}

run();


  return initPikchrModule.ready
}
);
})();
if (typeof exports === 'object' && typeof module === 'object')
  module.exports = initPikchrModule;
else if (typeof define === 'function' && define['amd'])
  define([], function() { return initPikchrModule; });
else if (typeof exports === 'object')
  exports["initPikchrModule"] = initPikchrModule;

Added extsrc/pikchr.wasm.

cannot compute difference between binary files

Changes to skins/darkmode/css.txt.

129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
  outline: 0
}
.button:focus,
button:focus,
input[type=button]:focus,
input[type=reset]:focus,
input[type=submit]:focus {
  color: #333;
  border-color: #888;
  outline: 0
}

/* All page content from the bottom of the menu or submenu down to
** the footer */
div.content {
  padding: 0ex 1ex 1ex 1ex;
}







|

<







129
130
131
132
133
134
135
136
137

138
139
140
141
142
143
144
  outline: 0
}
.button:focus,
button:focus,
input[type=button]:focus,
input[type=reset]:focus,
input[type=submit]:focus {
  outline: 2px outset #333;
  border-color: #888;

}

/* All page content from the bottom of the menu or submenu down to
** the footer */
div.content {
  padding: 0ex 1ex 1ex 1ex;
}

Changes to src/builtin.c.

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
  blob_init(&x, (const char*)pData, nByte);
  blob_write_to_file(&x, g.argc==4 ? g.argv[3] : "-");
  blob_reset(&x);
}

/*
** Input zList is a list of numeric identifiers for files in
** aBuiltinFiles[].  Return the concatenation of all of those
** files using mimetype zType, or as application/javascript if
** zType is 0.
*/
static void builtin_deliver_multiple_js_files(
  const char *zList,   /* List of numeric identifiers */
  const char *zType    /* Override mimetype */
){
  Blob *pOut;
  if( zType==0 ) zType = "application/javascript";
  cgi_set_content_type(zType);
  pOut = cgi_output_blob();
  while( zList[0] ){
    int i = atoi(zList);
    if( i>0 && i<=count(aBuiltinFiles) ){
      blob_appendf(pOut, "/* %s */\n", aBuiltinFiles[i-1].zName);
      blob_append(pOut, (const char*)aBuiltinFiles[i-1].pData,







|
|
<






|







128
129
130
131
132
133
134
135
136

137
138
139
140
141
142
143
144
145
146
147
148
149
150
  blob_init(&x, (const char*)pData, nByte);
  blob_write_to_file(&x, g.argc==4 ? g.argv[3] : "-");
  blob_reset(&x);
}

/*
** Input zList is a list of numeric identifiers for files in
** aBuiltinFiles[].  Return the concatenation of all of those files
** using mimetype zType, or as text/javascript if zType is 0.

*/
static void builtin_deliver_multiple_js_files(
  const char *zList,   /* List of numeric identifiers */
  const char *zType    /* Override mimetype */
){
  Blob *pOut;
  if( zType==0 ) zType = "text/javascript";
  cgi_set_content_type(zType);
  pOut = cgi_output_blob();
  while( zList[0] ){
    int i = atoi(zList);
    if( i>0 && i<=count(aBuiltinFiles) ){
      blob_appendf(pOut, "/* %s */\n", aBuiltinFiles[i-1].zName);
      blob_append(pOut, (const char*)aBuiltinFiles[i-1].pData,
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
    }
    cgi_set_status(404, "Not Found");
    @ File "%h(zName)" not found
    return;
  }
  if( zType==0 ){
    if( sqlite3_strglob("*.js", zName)==0 ){
      zType = "application/javascript";
    }else{
      zType = mimetype_from_name(zName);
    }
  }
  cgi_set_content_type(zType);
  if( zId
   && (nId = (int)strlen(zId))>=8







|







200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
    }
    cgi_set_status(404, "Not Found");
    @ File "%h(zName)" not found
    return;
  }
  if( zType==0 ){
    if( sqlite3_strglob("*.js", zName)==0 ){
      zType = "text/javascript";
    }else{
      zType = mimetype_from_name(zName);
    }
  }
  cgi_set_content_type(zType);
  if( zId
   && (nId = (int)strlen(zId))>=8

Changes to src/cgi.c.

305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
){
  char const *zSecure = "";
  if(!g.isHTTP) return /* e.g. JSON CLI mode, where g.zTop is not set */;
  else if( zPath==0 ){
    zPath = g.zTop;
    if( zPath[0]==0 ) zPath = "/";
  }
  if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){
    zSecure = " secure;";
  }
  if( lifetime!=0 ){
    blob_appendf(&extraHeader,
       "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; "
       "%s Version=1\r\n",
       zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure);







|







305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
){
  char const *zSecure = "";
  if(!g.isHTTP) return /* e.g. JSON CLI mode, where g.zTop is not set */;
  else if( zPath==0 ){
    zPath = g.zTop;
    if( zPath[0]==0 ) zPath = "/";
  }
  if( g.zBaseURL!=0 && fossil_strncmp(g.zBaseURL, "https:", 6)==0 ){
    zSecure = " secure;";
  }
  if( lifetime!=0 ){
    blob_appendf(&extraHeader,
       "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; "
       "%s Version=1\r\n",
       zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure);
328
329
330
331
332
333
334



335


336









337










338
339
340
341
342
343
344

/*
** Return true if the response should be sent with Content-Encoding: gzip.
*/
static int is_gzippable(void){
  if( g.fNoHttpCompress ) return 0;
  if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0;



  return strncmp(zContentType, "text/", 5)==0


    || sqlite3_strglob("application/*xml", zContentType)==0









    || sqlite3_strglob("application/*javascript", zContentType)==0;










}


/*
** The following routines read or write content from/to the wire for
** an HTTP request.  Depending on settings the content might be coming
** from or going to a socket, or a file, or it might come from or go







>
>
>
|
>
>
|
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>







328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368

/*
** Return true if the response should be sent with Content-Encoding: gzip.
*/
static int is_gzippable(void){
  if( g.fNoHttpCompress ) return 0;
  if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0;
  /* Maintenance note: this oddball structure is intended to make
  ** adding new mimetypes to this list less of a performance hit than
  ** doing a strcmp/glob over a growing set of compressible types. */
  switch(zContentType ? *zContentType : 0){
    case (int)'a':
      if(0==fossil_strncmp("application/",zContentType,12)){
        const char * z = &zContentType[12];
        switch(*z){
          case (int)'j':
            return fossil_strcmp("javascript", z)==0
                || fossil_strcmp("json", z)==0;
          case (int)'w': return fossil_strcmp("wasm", z)==0;
          case (int)'x':
            return fossil_strcmp("x-tcl", z)==0
                || fossil_strcmp("x-tar", z)==0;
          default:
            return sqlite3_strglob("*xml", z)==0;
        }
      }
      break;
    case (int)'i':
      return fossil_strcmp(zContentType, "image/svg+xml")==0
        || fossil_strcmp(zContentType, "image/vnd.microsoft.icon")==0;
    case (int)'t':
      return fossil_strncmp(zContentType, "text/", 5)==0;
  }
  return 0;
}


/*
** The following routines read or write content from/to the wire for
** an HTTP request.  Depending on settings the content might be coming
** from or going to a socket, or a file, or it might come from or go
417
418
419
420
421
422
423



















424
425
426
427
428
429
430
*/
static void cgi_fflush(void){
  if( !g.httpUseSSL ){
    fflush(g.httpOut);
  }
}





















/*
** Generate the reply to a web request.  The output might be an
** full HTTP response, or a CGI response, depending on how things have
** be set up.
**
** The reply consists of a response header (an HTTP or CGI response header)







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
*/
static void cgi_fflush(void){
  if( !g.httpUseSSL ){
    fflush(g.httpOut);
  }
}

/*
** Given a Content-Type value, returns a string suitable for appending
** to the Content-Type header for adding (or not) the "; charset=..."
** part. It returns an empty string for most types or if zContentType
** is NULL.
**
** See forum post f60dece061c364d1 for the discussions which lead to
** this. Previously we always appended the charset, but WASM loaders
** are pedantic and refuse to load any responses which have a
** charset. Also, adding a charset is not strictly appropriate for
** most types (and not required for many others which may ostensibly
** benefit from one, as detailed in that forum post).
*/
static const char * content_type_charset(const char *zContentType){
  if(0==fossil_strncmp(zContentType,"text/",5)){
    return "; charset=utf-8";
  }
  return "";
}

/*
** Generate the reply to a web request.  The output might be an
** full HTTP response, or a CGI response, depending on how things have
** be set up.
**
** The reply consists of a response header (an HTTP or CGI response header)
491
492
493
494
495
496
497
498

499
500
501
502
503
504
505
  ** a CGI script.
  */

  /* Content intended for logged in users should only be cached in
  ** the browser, not some shared location.
  */
  if( iReplyStatus!=304 ) {
    blob_appendf(&hdr, "Content-Type: %s; charset=utf-8\r\n", zContentType);

    if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
      cgi_combine_header_and_body();
      blob_compress(&cgiContent[0], &cgiContent[0]);
    }

    if( is_gzippable() && iReplyStatus!=206 ){
      int i;







|
>







534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
  ** a CGI script.
  */

  /* Content intended for logged in users should only be cached in
  ** the browser, not some shared location.
  */
  if( iReplyStatus!=304 ) {
    blob_appendf(&hdr, "Content-Type: %s%s\r\n", zContentType,
                 content_type_charset(zContentType));
    if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
      cgi_combine_header_and_body();
      blob_compress(&cgiContent[0], &cgiContent[0]);
    }

    if( is_gzippable() && iReplyStatus!=206 ){
      int i;
567
568
569
570
571
572
573

574
575
576
577
578
579
580
581
NORETURN void cgi_redirect_with_status(
  const char *zURL,
  int iStat,
  const char *zStat
){
  char *zLocation;
  CGIDEBUG(("redirect to %s\n", zURL));

  if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 ){
    zLocation = mprintf("Location: %s\r\n", zURL);
  }else if( *zURL=='/' ){
    int n1 = (int)strlen(g.zBaseURL);
    int n2 = (int)strlen(g.zTop);
    if( g.zBaseURL[n1-1]=='/' ) zURL++;
    zLocation = mprintf("Location: %.*s%s\r\n", n1-n2, g.zBaseURL, zURL);
  }else{







>
|







611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
NORETURN void cgi_redirect_with_status(
  const char *zURL,
  int iStat,
  const char *zStat
){
  char *zLocation;
  CGIDEBUG(("redirect to %s\n", zURL));
  if( fossil_strncmp(zURL,"http:",5)==0
      || fossil_strncmp(zURL,"https:",6)==0 ){
    zLocation = mprintf("Location: %s\r\n", zURL);
  }else if( *zURL=='/' ){
    int n1 = (int)strlen(g.zBaseURL);
    int n2 = (int)strlen(g.zTop);
    if( g.zBaseURL[n1-1]=='/' ) zURL++;
    zLocation = mprintf("Location: %.*s%s\r\n", n1-n2, g.zBaseURL, zURL);
  }else{
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
  if( zRef==0 ) return 0;
  if( requirePost ){
    const char *zMethod = P("REQUEST_METHOD");
    if( zMethod==0 ) return 0;
    if( strcmp(zMethod,"POST")!=0 ) return 0;
  }
  nBase = (int)strlen(g.zBaseURL);
  if( strncmp(g.zBaseURL,zRef,nBase)!=0 ) return 0;
  if( zRef[nBase]!=0 && zRef[nBase]!='/' ) return 0;
  return 1;
}

/*
** Information about all query parameters, post parameter, cookies and
** CGI environment variables are stored in a hash table as follows:







|







696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
  if( zRef==0 ) return 0;
  if( requirePost ){
    const char *zMethod = P("REQUEST_METHOD");
    if( zMethod==0 ) return 0;
    if( strcmp(zMethod,"POST")!=0 ) return 0;
  }
  nBase = (int)strlen(g.zBaseURL);
  if( fossil_strncmp(g.zBaseURL,zRef,nBase)!=0 ) return 0;
  if( zRef[nBase]!=0 && zRef[nBase]!='/' ) return 0;
  return 1;
}

/*
** Information about all query parameters, post parameter, cookies and
** CGI environment variables are stored in a hash table as follows:
918
919
920
921
922
923
924
925

926
927
928
929
930
931
932
){
  char *z = *pz;
  int len = *pLen;
  int i;
  int nBoundary = strlen(zBoundary);
  *pnContent = len;
  for(i=0; i<len; i++){
    if( z[i]=='\n' && strncmp(zBoundary, &z[i+1], nBoundary)==0 ){

      if( i>0 && z[i-1]=='\r' ) i--;
      z[i] = 0;
      *pnContent = i;
      i += nBoundary;
      break;
    }
  }







|
>







963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
){
  char *z = *pz;
  int len = *pLen;
  int i;
  int nBoundary = strlen(zBoundary);
  *pnContent = len;
  for(i=0; i<len; i++){
    if( z[i]=='\n' && fossil_strncmp(zBoundary, &z[i+1],
                                     nBoundary)==0 ){
      if( i>0 && z[i-1]=='\r' ) i--;
      z[i] = 0;
      *pnContent = i;
      i += nBoundary;
      break;
    }
  }
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
/*
** Decode POST parameter information in the cgiIn content, if any.
*/
void cgi_decode_post_parameters(void){
  int len = blob_size(&g.cgiIn);
  if( len==0 ) return;
  if( fossil_strcmp(g.zContentType,"application/x-www-form-urlencoded")==0
   || strncmp(g.zContentType,"multipart/form-data",19)==0
  ){
    char *z = blob_str(&g.cgiIn);
    cgi_trace(z);
    if( g.zContentType[0]=='a' ){
      add_param_list(z, '&');
    }else{
      process_multipart_form_data(z, len);







|







1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
/*
** Decode POST parameter information in the cgiIn content, if any.
*/
void cgi_decode_post_parameters(void){
  int len = blob_size(&g.cgiIn);
  if( len==0 ) return;
  if( fossil_strcmp(g.zContentType,"application/x-www-form-urlencoded")==0
   || fossil_strncmp(g.zContentType,"multipart/form-data",19)==0
  ){
    char *z = blob_str(&g.cgiIn);
    cgi_trace(z);
    if( g.zContentType[0]=='a' ){
      add_param_list(z, '&');
    }else{
      process_multipart_form_data(z, len);
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
        g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP",  iPort);
  fflush(stdout);
  if( zBrowser ){
    assert( strstr(zBrowser,"%d")!=0 );
    zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
#if defined(__CYGWIN__)
    /* On Cygwin, we can do better than "echo" */
    if( strncmp(zBrowser, "echo ", 5)==0 ){
      wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
      wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
      if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
        fossil_warning("cannot start browser\n");
      }
    }else
#endif







|







2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
        g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP",  iPort);
  fflush(stdout);
  if( zBrowser ){
    assert( strstr(zBrowser,"%d")!=0 );
    zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
#if defined(__CYGWIN__)
    /* On Cygwin, we can do better than "echo" */
    if( fossil_strncmp(zBrowser, "echo ", 5)==0 ){
      wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
      wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
      if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
        fossil_warning("cannot start browser\n");
      }
    }else
#endif
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
  static const char *const azMonths[] =
    {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
  if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
                       &mday, zMonth, &year, &hour, &min, &sec)){
    if( year > 1900 ) year -= 1900;
    for(mon=0; azMonths[mon]; mon++){
      if( !strncmp( azMonths[mon], zMonth, 3 )){
        int nDay;
        int isLeapYr;
        static int priorDays[] =
         {  0, 31, 59, 90,120,151,181,212,243,273,304,334 };
        if( mon<0 ){
          int nYear = (11 - mon)/12;
          year -= nYear;







|







2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
  static const char *const azMonths[] =
    {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0};
  if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore,
                       &mday, zMonth, &year, &hour, &min, &sec)){
    if( year > 1900 ) year -= 1900;
    for(mon=0; azMonths[mon]; mon++){
      if( !fossil_strncmp( azMonths[mon], zMonth, 3 )){
        int nDay;
        int isLeapYr;
        static int priorDays[] =
         {  0, 31, 59, 90,120,151,181,212,243,273,304,334 };
        if( mon<0 ){
          int nYear = (11 - mon)/12;
          year -= nYear;

Changes to src/default.css.

1005
1006
1007
1008
1009
1010
1011



1012
1013
1014
1015
1016
1017
1018
}
.capsumWrite {
  background-color: #ffb;
}
label {
  white-space: nowrap;
}



.copy-button {
  display: inline-block;
  width: 14px;
  height: 14px;
/*Note: .24em is slightly smaller than the average width of a normal space.*/
  margin: -2px .24em 0 0;
  padding: 0;







>
>
>







1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
}
.capsumWrite {
  background-color: #ffb;
}
label {
  white-space: nowrap;
}
label[for] {
  cursor: pointer;
}
.copy-button {
  display: inline-block;
  width: 14px;
  height: 14px;
/*Note: .24em is slightly smaller than the average width of a normal space.*/
  margin: -2px .24em 0 0;
  padding: 0;
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084





1085
1086
1087
1088
1089
1090
1091
1092
  color: darkred;
  background: yellow;
}
.warning {
  color: black;
  background: yellow;
}
.hidden {
  /* The framework-wide way of hiding elements is to assign them this
     CSS class. To make them visible again, remove it. The !important
     qualifiers are unfortunate but sometimes necessary when hidden
     element has other classes which specify visibility-related





     options. */
  position: absolute !important;
  opacity: 0 !important;
  pointer-events: none !important;
  display: none !important;
}
input {
  max-width: 95%;







|
|
|
|
|
>
>
>
>
>
|







1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
  color: darkred;
  background: yellow;
}
.warning {
  color: black;
  background: yellow;
}
.hidden, .initially-hidden {
  /* The framework-wide way of hiding elements is to assign them th
     .hidden class. To make them visible again, remove it. The
     !important qualifiers are unfortunate but sometimes necessary
     when hidden element has other classes which specify
     visibility-related options. The .initially-hidden class is for
     pages which need to show, e.g., a progress widget while a large
     WASM blob loads. Elements aside from that load-time widget can be
     made .initially-hidden and then have that class removed once the
     long-running startup process is done. See /pikchrshow for an
     example. */
  position: absolute !important;
  opacity: 0 !important;
  pointer-events: none !important;
  display: none !important;
}
input {
  max-width: 95%;

Changes to src/doc.c.

154
155
156
157
158
159
160
161





162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177

178
179
180
181
182
183
184
  { "ips",        3, "application/x-ipscript"            },
  { "ipx",        3, "application/x-ipix"                },
  { "jad",        3, "text/vnd.sun.j2me.app-descriptor"  },
  { "jar",        3, "application/java-archive"          },
  { "jpe",        3, "image/jpeg"                        },
  { "jpeg",       4, "image/jpeg"                        },
  { "jpg",        3, "image/jpeg"                        },
  { "js",         2, "application/javascript"            },





  { "kar",        3, "audio/midi"                        },
  { "latex",      5, "application/x-latex"               },
  { "lha",        3, "application/octet-stream"          },
  { "lsp",        3, "application/x-lisp"                },
  { "lzh",        3, "application/octet-stream"          },
  { "m",          1, "text/plain"                        },
  { "m3u",        3, "audio/x-mpegurl"                   },
  { "man",        3, "text/plain"                        },
  { "markdown",   8, "text/x-markdown"                   },
  { "md",         2, "text/x-markdown"                   },
  { "me",         2, "application/x-troff-me"            },
  { "mesh",       4, "model/mesh"                        },
  { "mid",        3, "audio/midi"                        },
  { "midi",       4, "audio/midi"                        },
  { "mif",        3, "application/x-mif"                 },
  { "mime",       4, "www/mime"                          },

  { "mkd",        3, "text/x-markdown"                   },
  { "mov",        3, "video/quicktime"                   },
  { "movie",      5, "video/x-sgi-movie"                 },
  { "mp2",        3, "audio/mpeg"                        },
  { "mp3",        3, "audio/mpeg"                        },
  { "mp4",        3, "video/mp4"                         },
  { "mpe",        3, "video/mpeg"                        },







|
>
>
>
>
>
















>







154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
  { "ips",        3, "application/x-ipscript"            },
  { "ipx",        3, "application/x-ipix"                },
  { "jad",        3, "text/vnd.sun.j2me.app-descriptor"  },
  { "jar",        3, "application/java-archive"          },
  { "jpe",        3, "image/jpeg"                        },
  { "jpeg",       4, "image/jpeg"                        },
  { "jpg",        3, "image/jpeg"                        },
  { "js",         2, "text/javascript"                   },
  /* application/javascript is commonly used for JS, but the
  ** spec says text/javascript is correct:
  ** https://html.spec.whatwg.org/multipage/scripting.html
  ** #scriptingLanguages:javascript-mime-type */
  { "json",       4, "application/json"                  },
  { "kar",        3, "audio/midi"                        },
  { "latex",      5, "application/x-latex"               },
  { "lha",        3, "application/octet-stream"          },
  { "lsp",        3, "application/x-lisp"                },
  { "lzh",        3, "application/octet-stream"          },
  { "m",          1, "text/plain"                        },
  { "m3u",        3, "audio/x-mpegurl"                   },
  { "man",        3, "text/plain"                        },
  { "markdown",   8, "text/x-markdown"                   },
  { "md",         2, "text/x-markdown"                   },
  { "me",         2, "application/x-troff-me"            },
  { "mesh",       4, "model/mesh"                        },
  { "mid",        3, "audio/midi"                        },
  { "midi",       4, "audio/midi"                        },
  { "mif",        3, "application/x-mif"                 },
  { "mime",       4, "www/mime"                          },
  { "mjs",        3, "text/javascript" /*EM6 modules*/   },
  { "mkd",        3, "text/x-markdown"                   },
  { "mov",        3, "video/quicktime"                   },
  { "movie",      5, "video/x-sgi-movie"                 },
  { "mp2",        3, "audio/mpeg"                        },
  { "mp3",        3, "audio/mpeg"                        },
  { "mp4",        3, "video/mp4"                         },
  { "mpe",        3, "video/mpeg"                        },

Changes to src/fossil.page.pikchrshow.js.

1
2
3
4
5
6
7








8
9
10
11
12
13
14
(function(F/*the fossil object*/){
  "use strict";
  /**
     Client-side implementation of the /pikchrshow app. Requires that
     the fossil JS bootstrapping is complete and that these fossil JS
     APIs have been installed: fossil.fetch, fossil.dom,
     fossil.copybutton, fossil.popupwidget, fossil.storage








  */
  const E = (s)=>document.querySelector(s),
        D = F.dom,
        P = F.page;

  P.previewMode = 0 /*0==rendered SVG, 1==pikchr text markdown,
                      2==pikchr text fossil, 3==raw SVG. */



|



>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function(F/*the fossil object*/){
  "use strict";
  /**
     Client-side implementation of the /pikchrshowcs app. Requires that
     the fossil JS bootstrapping is complete and that these fossil JS
     APIs have been installed: fossil.fetch, fossil.dom,
     fossil.copybutton, fossil.popupwidget, fossil.storage

     Maintenance funkiness note: this file is for the legacy
     /pikchrshowcs app, which was formerly named /pikchrshow.  This
     file and its replacement were not renamed because the replacement
     impl would end up getting this file's name and cause confusion in
     the file history. Whether that confusion would be less than this
     file's name matching the _other_ /pikchrshow impl will cause more
     or less confusion than that remains to be seen.
  */
  const E = (s)=>document.querySelector(s),
        D = F.dom,
        P = F.page;

  P.previewMode = 0 /*0==rendered SVG, 1==pikchr text markdown,
                      2==pikchr text fossil, 3==raw SVG. */

Added src/fossil.page.pikchrshowasm.js.





























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
/*
  2022-05-20

  The author disclaims copyright to this source code.  In place of a
  legal notice, here is a blessing:

  *   May you do good and not evil.
  *   May you find forgiveness for yourself and forgive others.
  *   May you share freely, never taking more than you give.

  ***********************************************************************

  This is the main entry point for the WASM rendition of fossil's
  /pikchrshow app. It sets up the various UI bits, loads a Worker for
  the pikchr process, and manages the communication between the UI and
  worker.

  API dependencies: fossil.dom, fossil.copybutton, fossil.storage
*/
(function(F/*fossil object*/){
  'use strict';

  /* Recall that the 'self' symbol, except where locally
     overwritten, refers to the global window or worker object. */

  const D = F.dom;
  /** Name of the stored copy of this app's config. */
  const configStorageKey = 'pikchrshow-config';

  /* querySelectorAll() proxy */
  const EAll = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelectorAll(arguments[arguments.length-1]);
  };
  /* querySelector() proxy */
  const E = function(/*[element=document,] cssSelector*/){
    return (arguments.length>1 ? arguments[0] : document)
      .querySelector(arguments[arguments.length-1]);
  };

  /** The main application object. */
  const PS = {
    /* Config options. */
    config: {
      /* If true, display input/output areas side-by-side, else stack
         them vertically. */
      sideBySide: true,
      /* If true, swap positions of the input/output areas. */
      swapInOut: false,
      /* If true, the SVG is allowed to resize to fit the parent
         content area, else the parent is resized to fit the rendered
         SVG (as sized by pikchr). */
      renderAutofit: false,
      /* If true, automatically render while the user is typing. */
      renderWhileTyping: false
    },
    /* Various DOM elements. */
    e: {
      previewCopyButton: E('#preview-copy-button'),
      previewModeLabel: E('label[for=preview-copy-button]'),
      zoneInputButtons: E('.zone-wrapper.input > legend > .button-bar'),
      zoneOutputButtons: E('.zone-wrapper.output > legend > .button-bar'),
      outText: E('#pikchr-output-text'),
      pikOutWrapper: E('#pikchr-output-wrapper'),
      pikOut: E('#pikchr-output'),
      btnRender: E('#btn-render')      
    },
    renderModes: ['svg'/*SVG must be at index 0*/,'markdown', 'wiki', 'text'],
    renderModeLabels: {
      svg: 'SVG', markdown: 'Markdown', wiki: 'Fossil Wiki', text: 'Text'
    },
    _msgMap: {},
    /** Adds a worker message handler for messages of the given
        type. */
    addMsgHandler: function f(type,callback){
      if(Array.isArray(type)){
        type.forEach((t)=>this.addMsgHandler(t, callback));
        return this;
      }
      (this._msgMap.hasOwnProperty(type)
       ? this._msgMap[type]
       : (this._msgMap[type] = [])).push(callback);
      return this;
    },
    /** Given a worker message, runs all handlers for msg.type. */
    runMsgHandlers: function(msg){
      const list = (this._msgMap.hasOwnProperty(msg.type)
                    ? this._msgMap[msg.type] : false);
      if(!list){
        console.warn("No handlers found for message type:",msg);
        return false;
      }
      list.forEach((f)=>f(msg));
      return true;
    },
    /** Removes all message handlers for the given message type. */
    clearMsgHandlers: function(type){
      delete this._msgMap[type];
      return this;
    },
    /* Posts a message in the form {type, data} to the db worker. Returns this. */
    wMsg: function(type,data){
      this.worker.postMessage({type, data});
      return this;
    },
    /** Stores this object's config in the browser's storage. */
    storeConfig: function(){
      F.storage.setJSON(configStorageKey,this.config);
    }
  };
  PS.renderModes.selectedIndex = 0;
  PS._config = F.storage.getJSON(configStorageKey);
  if(PS._config){
    /* Copy all properties to PS.config which are currently in
       PS._config. We don't bother copying any other properties: those
       would be stale/removed config entries. */
    Object.keys(PS.config).forEach(function(k){
      if(PS._config.hasOwnProperty(k)){
        PS.config[k] = PS._config[k];
      }
    });
    delete PS._config;
  }

  PS.worker = new Worker('builtin/extsrc/pikchr-worker.js');
  PS.worker.onmessage = (ev)=>PS.runMsgHandlers(ev.data);
  PS.addMsgHandler('stdout', console.log.bind(console));
  PS.addMsgHandler('stderr', console.error.bind(console));

  /** Handles status updates from the Module object. */
  PS.addMsgHandler('module', function f(ev){
    ev = ev.data;
    if('status'!==ev.type){
      console.warn("Unexpected module-type message:",ev);
      return;
    }
    if(!f.ui){
      f.ui = {
        status: E('#module-status'),
        progress: E('#module-progress'),
        spinner: E('#module-spinner')
      };
    }
    const msg = ev.data;
    if(f.ui.progres){
      progress.value = msg.step;
      progress.max = msg.step + 1/*we don't know how many steps to expect*/;
    }
    if(1==msg.step){
      f.ui.progress.classList.remove('hidden');
      f.ui.spinner.classList.remove('hidden');
    }
    if(msg.text){
      f.ui.status.classList.remove('hidden');
      f.ui.status.innerText = msg.text;
    }else{
      if(f.ui.progress){
        f.ui.progress.remove();
        f.ui.spinner.remove();
        delete f.ui.progress;
        delete f.ui.spinner;
      }
      f.ui.status.classList.add('hidden');
      /* The module can post messages about fatal problems,
         e.g. an exit() being triggered or assertion failure,
         after the last "load" message has arrived, so
         leave f.ui.status and message listener intact. */
    }
  });

  PS.e.previewModeLabel.innerText =
    PS.renderModeLabels[PS.renderModes[PS.renderModes.selectedIndex]];

  /**
     The 'pikchrshow-ready' event is fired (with no payload) when the
     wasm module has finished loading. */
  PS.addMsgHandler('pikchrshow-ready', function(){
    PS.clearMsgHandlers('pikchrshow-ready');
    F.page.onPikchrshowLoaded();
  });

  /**
     Performs all app initialization which must wait until after the
     worker module is loaded. This function removes itself when it's
     called.
  */
  F.page.onPikchrshowLoaded = function(){
    delete this.onPikchrshowLoaded;
    // Unhide all elements which start out hidden
    EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden'));
    const taInput = E('#input');
    const btnClearIn = E('#btn-clear');
    btnClearIn.addEventListener('click',function(){
      taInput.value = '';
    },false);
    const getCurrentText = function(){
      let text;
      if(taInput.selectionStart<taInput.selectionEnd){
        text = taInput.value.substring(taInput.selectionStart,taInput.selectionEnd).trim();
      }else{
        text = taInput.value.trim();
      }
      return text;;
    };
    const renderCurrentText = function(){
      const text = getCurrentText();
      if(text) PS.render(text);
    };
    const setCurrentText = function(txt){
      taInput.value = txt;
      renderCurrentText();        
    };
    PS.e.btnRender.addEventListener('click',function(ev){
      ev.preventDefault();
      renderCurrentText();
    },false);

    /** To be called immediately before work is sent to the
        worker. Updates some UI elements. The 'working'/'end'
        event will apply the inverse, undoing the bits this
        function does. This impl is not in the 'working'/'start'
        event handler because that event is given to us
        asynchronously _after_ we need to have performed this
        work.
    */
    const preStartWork = function f(){
      if(!f._){
        const title = E('title');
        f._ = {
          pageTitle: title,
          pageTitleOrig: title.innerText
        };
      }
      //f._.pageTitle.innerText = "[working...] "+f._.pageTitleOrig;
      PS.e.btnRender.setAttribute('disabled','disabled');
    };

    /**
       Submits the current input text to pikchr and renders the
       result. */
    PS.render = function f(txt){
      preStartWork();
      this.wMsg('pikchr',{
        pikchr: txt,
        darkMode: !!window.fossil.config.skin.isDark
      });
    };

    /**
       Event handler for 'pikchr' messages from the Worker thread.
    */
    PS.addMsgHandler('pikchr', function(ev){
      const m = ev.data, pikOut = this.e.pikOut;
      pikOut.classList[m.isError ? 'add' : 'remove']('error');
      pikOut.dataset.pikchr = m.pikchr;
      const mode = this.renderModes[this.renderModes.selectedIndex];
      switch(mode){
          case 'text': case 'markdown': case 'wiki': {
            let body;
            switch(mode){
                case 'markdown':
                  body = ['```pikchr', m.pikchr, '```'].join('\n');
                  break;
                case 'wiki':
                  body = ['<verbatim type="pikchr">', m.pikchr, '</verbatim>'].join('');
                  break;
                default:
                  body = m.result;
            }
            this.e.outText.value = body;
            this.e.outText.classList.remove('hidden');
            pikOut.classList.add('hidden');
            this.e.pikOutWrapper.classList.add('text');
            break;
          }
          case 'svg':
            this.e.outText.classList.add('hidden');
            pikOut.classList.remove('hidden');
            this.e.pikOutWrapper.classList.remove('text');
            pikOut.innerHTML = m.result;
            this.e.outText.value = m.result/*for clipboard copy*/;
            break;
          default: throw new Error("Unhandled render mode: "+mode);
      }
      let vw = null, vh = null;
      if('svg'===mode){
        if(m.isError){
          vw = vh = '100%';
        }else if(this.config.renderAutofit){
          /* FIXME: current behavior doesn't work as desired when width>height
             (e.g. non-side-by-side mode).*/
          vw = vh = '98%';
        }else{
          vw = m.width+1+'px'; vh = m.height+1+'px';
          /* +1 is b/c the SVG uses floating point sizes but pikchr()
             returns truncated integers. */
        }
        pikOut.style.width = vw;
        pikOut.style.height = vh;
      }
    }.bind(PS))/*'pikchr' msg handler*/;

    E('#btn-render-mode').addEventListener('click',function(){
      const modes = this.renderModes;
      modes.selectedIndex = (modes.selectedIndex + 1) % modes.length;
      this.e.previewModeLabel.innerText = this.renderModeLabels[modes[modes.selectedIndex]];
      if(this.e.pikOut.dataset.pikchr){
        this.render(this.e.pikOut.dataset.pikchr);
      }
    }.bind(PS));
    F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText});
    PS.e.previewModeLabel.addEventListener('click', ()=>PS.e.previewCopyButton.click(), false);

    PS.addMsgHandler('working',function f(ev){
      switch(ev.data){
          case 'start': /* See notes in preStartWork(). */; return;
          case 'end':
            //preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
            this.e.btnRender.removeAttribute('disabled');
            this.e.pikOutWrapper.classList[this.config.renderAutofit ? 'add' : 'remove']('autofit');
            return;
      }
      console.warn("Unhandled 'working' event:",ev.data);
    }.bind(PS));

    /* For each checkbox with data-csstgt, set up a handler which
       toggles the given CSS class on the element matching
       E(data-csstgt). */
    EAll('input[type=checkbox][data-csstgt]')
      .forEach(function(e){
        const tgt = E(e.dataset.csstgt);
        const cssClass = e.dataset.cssclass || 'error';
        e.checked = tgt.classList.contains(cssClass);
        e.addEventListener('change', function(){
          tgt.classList[
            this.checked ? 'add' : 'remove'
          ](cssClass)
        }, false);
      });
    /* For each checkbox with data-config=X, set up a binding to
       PS.config[X]. These must be set up AFTER data-csstgt
       checkboxes so that those two states can be synced properly. */
    EAll('input[type=checkbox][data-config]')
      .forEach(function(e){
        const confVal = !!PS.config[e.dataset.config];
        if(e.checked !== confVal){
          /* Ensure that data-csstgt mappings (if any) get
             synced properly. */
          e.checked = confVal;
          e.dispatchEvent(new Event('change'));
        }
        e.addEventListener('change', function(){
          PS.config[this.dataset.config] = this.checked;
          PS.storeConfig();
        }, false);
      });
    E('#opt-cb-autofit').addEventListener('change',function(){
      /* PS.config.renderAutofit was set by the data-config
         event handler. */
      if(0==PS.renderModes.selectedIndex && PS.e.pikOut.dataset.pikchr){
        PS.render(PS.e.pikOut.dataset.pikchr);
      }
    });
    /* For each button with data-cmd=X, map a click handler which
       calls PS.render(X). */
    const cmdClick = function(){PS.render(this.dataset.cmd);};
    EAll('button[data-cmd]').forEach(
      e => e.addEventListener('click', cmdClick, false)
    );


    ////////////////////////////////////////////////////////////
    // Set up selection list of predefined scripts...
    if(true){
      const selectScript = PS.e.selectScript = D.select();
      D.append(PS.e.zoneInputButtons, selectScript);
      PS.predefinedPiks.forEach(function(script,ndx){
        const opt = D.option(script.code ? script.code.trim() :'', script.name);
        D.append(selectScript, opt);
        if(!ndx) selectScript.selectedIndex = 0 /*timing/ordering workaround*/;
        if(ndx && !script.code){
          /* Treat entries w/ no code as separators EXCEPT for the
             first one, which we want to keep selectable solely for
             cosmetic reasons. */
          D.disable(opt);
        }
      });
      delete PS.predefinedPiks;
      selectScript.addEventListener('change', function(ev){
        const val = ev.target.value;
        if(!val) return;
        setCurrentText(val);
      }, false);
    }/*Examples*/

    /**
       TODO: Handle load/import of an external pikchr file.
    */
    if(0) E('#load-pikchr').addEventListener('change',function(){
      const f = this.files[0];
      const r = new FileReader();
      const status = {loaded: 0, total: 0};
      this.setAttribute('disabled','disabled');
      const that = this;
      r.addEventListener('load', function(){
        that.removeAttribute('disabled');
        stdout("Loaded",f.name+". Opening pikchr...");
        PS.wMsg('open',{
          filename: f.name,
          buffer: this.result
        });
      });
      r.addEventListener('error',function(){
        that.removeAttribute('disabled');
        stderr("Loading",f.name,"failed for unknown reasons.");
      });
      r.addEventListener('abort',function(){
        that.removeAttribute('disabled');
        stdout("Cancelled loading of",f.name+".");
      });
      r.readAsArrayBuffer(f);
    });

    EAll('fieldset.collapsible').forEach(function(fs){
      const btnToggle = E(fs,'legend > #btn-options-toggle'),
            content = EAll(fs,':scope > div');
      btnToggle.addEventListener('click', function(){
        fs.classList.toggle('collapsed');
        content.forEach((d)=>d.classList.toggle('hidden'));
      }, false);
    });

    PS.e.btnRender.click();
    
    /** Debounce handler for auto-rendering while typing. */
    const debounceAutoRender = F.debounce(function f(){
      if(!PS._isDirty) return;
      const text = getCurrentText();
      if(f._ === text){
        PS._isDirty = false;
        return;
      }
      f._ = text;
      PS._isDirty = false;
      PS.render(text || '');
    }, 800, false);

    taInput.addEventListener('keydown',function f(ev){
      if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){
        // Ctrl-enter and shift-enter both run the current input
        PS._isDirty = false/*prevent a pending debounce from re-rendering*/;
        ev.preventDefault();
        ev.stopPropagation();
        renderCurrentText();
        return;
      }
      if(!PS.config.renderWhileTyping) return;
      /* Auto-render while typing... */
      switch(ev.keyCode){
          case (ev.keyCode<32): /*any ctrl char*/
            /* ^^^ w/o that, simply tapping ctrl is enough to
               force a re-render. Similarly, TAB-ing focus away
               should not re-render. */
          case 33: case 34: /* page up/down */
          case 35: case 36: /* home/end */
          case 37: case 38: case 39: case 40: /* arrows */
            return;
      }
      PS._isDirty = true;
      debounceAutoRender();
    }, false);

    const ForceResizeKludge = (function(){
      /* Workaround for Safari mayhem regarding use of vh CSS
         units....  We cannot use vh units to set the main view
         size because Safari chokes on that, so we calculate
         that height here. Larger than ~95% is too big for
         Firefox on Android, causing the input area to move
         off-screen. */
      const appViews = EAll('.app-view');
      const elemsToCount = [
        /* Elements which we need to always count in the
           visible body size. */
        E('body > div.header'),
        E('body > div.mainmenu'),
        E('body > div.footer')
      ];
      const resized = function f(){
        if(f.$disabled) return;
        const wh = window.innerHeight;
        var ht;
        var extra = 0;
        elemsToCount.forEach((e)=>e ? extra += F.dom.effectiveHeight(e) : false);
        ht = wh - extra;
        appViews.forEach(function(e){
          e.style.height =
            e.style.maxHeight = [
              "calc(", (ht>=100 ? ht : 100), "px",
              " - 2em"/*fudge value*/,")"
              /* ^^^^ hypothetically not needed, but both
                 Chrome/FF on Linux will force scrollbars on the
                 body if this value is too small. */
            ].join('');
        });
      };
      resized.$disabled = true/*gets deleted when setup is finished*/;
      window.addEventListener('resize', F.debounce(resized, 250), false);
      return resized;
    })()/*ForceResizeKludge*/;

    delete ForceResizeKludge.$disabled;
    ForceResizeKludge();
  }/*onPikchrshowLoaded()*/;


  /**
     Predefined scripts. Each entry is an object:

     {
     name: required string,
     code: optional code string. An entry with a falsy code is treated
           like a separator in the resulting SELECT element (a
           disabled OPTION).
     }
  */
  PS.predefinedPiks = [
    {name: "-- Example Scripts --", code: false},
/*
  The following were imported from the pikchr test scripts:

  https://fossil-scm.org/pikchr/dir/examples
*/
{name:"Cardinal headings",code:`   linerad = 5px
C: circle "Center" rad 150%
   circle "N"  at 1.0 n  of C; arrow from C to last chop ->
   circle "NE" at 1.0 ne of C; arrow from C to last chop <-
   circle "E"  at 1.0 e  of C; arrow from C to last chop <->
   circle "SE" at 1.0 se of C; arrow from C to last chop ->
   circle "S"  at 1.0 s  of C; arrow from C to last chop <-
   circle "SW" at 1.0 sw of C; arrow from C to last chop <->
   circle "W"  at 1.0 w  of C; arrow from C to last chop ->
   circle "NW" at 1.0 nw of C; arrow from C to last chop <-
   arrow from 2nd circle to 3rd circle chop
   arrow from 4th circle to 3rd circle chop
   arrow from SW to S chop <->
   circle "ESE" at 2.0 heading 112.5 from Center \
      thickness 150% fill lightblue radius 75%
   arrow from Center to ESE thickness 150% <-> chop
   arrow from ESE up 1.35 then to NE chop
   line dashed <- from E.e to (ESE.x,E.y)
   line dotted <-> thickness 50% from N to NW chop
`},{name:"Core object types",code:`AllObjects: [

# First row of objects
box "box"
box rad 10px "box (with" "rounded" "corners)" at 1in right of previous
circle "circle" at 1in right of previous
ellipse "ellipse" at 1in right of previous

# second row of objects
OVAL1: oval "oval" at 1in below first box
oval "(tall &" "thin)" "oval" width OVAL1.height height OVAL1.width \
    at 1in right of previous
cylinder "cylinder" at 1in right of previous
file "file" at 1in right of previous

# third row shows line-type objects
dot "dot" above at 1in below first oval
line right from 1.8cm right of previous "lines" above
arrow right from 1.8cm right of previous "arrows" above
spline from 1.8cm right of previous \
   go right .15 then .3 heading 30 then .5 heading 160 then .4 heading 20 \
   then right .15
"splines" at 3rd vertex of previous

# The third vertex of the spline is not actually on the drawn
# curve.  The third vertex is a control point.  To see its actual
# position, uncomment the following line:
#dot color red at 3rd vertex of previous spline

# Draw various lines below the first line
line dashed right from 0.3cm below start of previous line
line dotted right from 0.3cm below start of previous
line thin   right from 0.3cm below start of previous
line thick  right from 0.3cm below start of previous


# Draw arrows with different arrowhead configurations below
# the first arrow
arrow <-  right from 0.4cm below start of previous arrow
arrow <-> right from 0.4cm below start of previous

# Draw splines with different arrowhead configurations below
# the first spline
spline same from .4cm below start of first spline ->
spline same from .4cm below start of previous <-
spline same from .4cm below start of previous <->

] # end of AllObjects

# Label the whole diagram
text "Examples Of Pikchr Objects" big bold  at .8cm above north of AllObjects
`},{name:"Swimlanes",code:`    $laneh = 0.75

    # Draw the lanes
    down
    box width 3.5in height $laneh fill 0xacc9e3
    box same fill 0xc5d8ef
    box same as first box
    box same as 2nd box
    line from 1st box.sw+(0.2,0) up until even with 1st box.n \
      "Alan" above aligned
    line from 2nd box.sw+(0.2,0) up until even with 2nd box.n \
      "Betty" above aligned
    line from 3rd box.sw+(0.2,0) up until even with 3rd box.n \
      "Charlie" above aligned
    line from 4th box.sw+(0.2,0) up until even with 4th box.n \
       "Darlene" above aligned

    # fill in content for the Alice lane
    right
A1: circle rad 0.1in at end of first line + (0.2,-0.2) \
       fill white thickness 1.5px "1" 
    arrow right 50%
    circle same "2"
    arrow right until even with first box.e - (0.65,0.0)
    ellipse "future" fit fill white height 0.2 width 0.5 thickness 1.5px
A3: circle same at A1+(0.8,-0.3) "3" fill 0xc0c0c0
    arrow from A1 to last circle chop "fork!" below aligned

    # content for the Betty lane
B1: circle same as A1 at A1-(0,$laneh) "1"
    arrow right 50%
    circle same "2"
    arrow right until even with first ellipse.w
    ellipse same "future"
B3: circle same at A3-(0,$laneh) "3"
    arrow right 50%
    circle same as A3 "4"
    arrow from B1 to 2nd last circle chop

    # content for the Charlie lane
C1: circle same as A1 at B1-(0,$laneh) "1"
    arrow 50%
    circle same "2"
    arrow right 0.8in "goes" "offline"
C5: circle same as A3 "5"
    arrow right until even with first ellipse.w \
      "back online" above "pushes 5" below "pulls 3 & 4" below
    ellipse same "future"

    # content for the Darlene lane
D1: circle same as A1 at C1-(0,$laneh) "1"
    arrow 50%
    circle same "2"
    arrow right until even with C5.w
    circle same "5"
    arrow 50%
    circle same as A3 "6"
    arrow right until even with first ellipse.w
    ellipse same "future"
D3: circle same as B3 at B3-(0,2*$laneh) "3"
    arrow 50%
    circle same "4"
    arrow from D1 to D3 chop
`},{
  name: "The Stuff of Dreams",
  code:`
O: text "DREAMS" color grey
circle rad 0.9 at 0.6 above O thick color red
text "INEXPENSIVE" big bold at 0.9 above O color red

circle rad 0.9   at 0.6 heading  120 from O thick color green
text "FAST" big bold at 0.9 heading  120 from O  color green

circle rad 0.9 at 0.6 heading -120 from O thick color blue
text "HIGH" big bold "QUALITY" big bold at 0.9 heading  -120 from O  color blue

text "EXPENSIVE" at 0.55 below O  color cyan
text "SLOW" at 0.55 heading  -60 from O  color magenta
text "POOR" "QUALITY" at 0.55 heading   60 from O  color gold
`}
  ];


})(window.fossil);

Changes to src/json.c.

556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
}

/*
** Guesses a RESPONSE Content-Type value based (primarily) on the
** HTTP_ACCEPT header.
**
** It will try to figure out if the client can support
** application/json or application/javascript, and will fall back to
** text/plain if it cannot figure out anything more specific.
**
** Returned memory is static and immutable, but if the environment
** changes after calling this then subsequent calls to this function
** might return different (also static/immutable) values.
*/
char const * json_guess_content_type(){
  char const * cset;
  char doUtf8;
  cset = PD("HTTP_ACCEPT_CHARSET",NULL);
  doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset)))
    ? 1 : 0;
  if( g.json.jsonp ){
    return doUtf8
      ? "application/javascript; charset=utf-8"
      : "application/javascript";
  }else{
    /*
      Content-type

      If the browser does not sent an ACCEPT for application/json
      then we fall back to text/plain.
    */







|














|
|







556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
}

/*
** Guesses a RESPONSE Content-Type value based (primarily) on the
** HTTP_ACCEPT header.
**
** It will try to figure out if the client can support
** application/json, text/javascript, and will fall back to
** text/plain if it cannot figure out anything more specific.
**
** Returned memory is static and immutable, but if the environment
** changes after calling this then subsequent calls to this function
** might return different (also static/immutable) values.
*/
char const * json_guess_content_type(){
  char const * cset;
  char doUtf8;
  cset = PD("HTTP_ACCEPT_CHARSET",NULL);
  doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset)))
    ? 1 : 0;
  if( g.json.jsonp ){
    return doUtf8
      ? "text/javascript; charset=utf-8"
      : "text/javascript";
  }else{
    /*
      Content-type

      If the browser does not sent an ACCEPT for application/json
      then we fall back to text/plain.
    */
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617

618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639

640
641
642
643
644
645
646
  }
}

/*
 ** Given a request CONTENT_TYPE value, this function returns true
 ** if it is of a type which the JSON API can ostensibly read.
 **
 ** It accepts any of application/json, text/plain, or
 ** application/javascript. The former is preferred, but was not
 ** widespread when this API was initially built, so the latter forms
 ** are permitted as fallbacks.
 */
int json_can_consume_content_type(const char * zType){
  return fossil_strcmp(zType, "application/json")==0
    || fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/

    || fossil_strcmp(zType,"application/javascript")==0;
}

/*
** Sends pResponse to the output stream as the response object.  This
** function does no validation of pResponse except to assert() that it
** is not NULL. The caller is responsible for ensuring that it meets
** API response envelope conventions.
**
** In CLI mode pResponse is sent to stdout immediately. In HTTP
** mode pResponse replaces any current CGI content but cgi_reply()
** is not called to flush the output.
**
** If g.json.jsonp is not NULL then the content type is set to
** application/javascript and the output is wrapped in a jsonp
** wrapper.
*/
void json_send_response( cson_value const * pResponse ){
  assert( NULL != pResponse );
  if( g.isHTTP ){
    cgi_reset_content();
    if( g.json.jsonp ){

      cgi_printf("%s(",g.json.jsonp);
    }
    cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt );
    if( g.json.jsonp ){
      cgi_append_content(")",1);
    }
  }else{/*CLI mode*/







|
|
|
|




>














|







>







603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
  }
}

/*
 ** Given a request CONTENT_TYPE value, this function returns true
 ** if it is of a type which the JSON API can ostensibly read.
 **
 ** It accepts any of application/json, text/plain,
 ** application/javascript, or text/javascript. The former is
 ** preferred, but was not widespread when this API was initially
 ** built, so the latter forms are permitted as fallbacks.
 */
int json_can_consume_content_type(const char * zType){
  return fossil_strcmp(zType, "application/json")==0
    || fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/
    || fossil_strcmp(zType,"text/javascript")==0
    || fossil_strcmp(zType,"application/javascript")==0;
}

/*
** Sends pResponse to the output stream as the response object.  This
** function does no validation of pResponse except to assert() that it
** is not NULL. The caller is responsible for ensuring that it meets
** API response envelope conventions.
**
** In CLI mode pResponse is sent to stdout immediately. In HTTP
** mode pResponse replaces any current CGI content but cgi_reply()
** is not called to flush the output.
**
** If g.json.jsonp is not NULL then the content type is set to
** text/javascript and the output is wrapped in a jsonp
** wrapper.
*/
void json_send_response( cson_value const * pResponse ){
  assert( NULL != pResponse );
  if( g.isHTTP ){
    cgi_reset_content();
    if( g.json.jsonp ){
      cgi_set_content_type("text/javascript");
      cgi_printf("%s(",g.json.jsonp);
    }
    cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt );
    if( g.json.jsonp ){
      cgi_append_content(")",1);
    }
  }else{/*CLI mode*/

Changes to src/main.mk.

161
162
163
164
165
166
167



168
169
170
171
172
173
174
  $(SRCDIR)/winfile.c \
  $(SRCDIR)/winhttp.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/xfersetup.c \
  $(SRCDIR)/zip.c

EXTRA_FILES = \



  $(SRCDIR)/../skins/ardoise/css.txt \
  $(SRCDIR)/../skins/ardoise/details.txt \
  $(SRCDIR)/../skins/ardoise/footer.txt \
  $(SRCDIR)/../skins/ardoise/header.txt \
  $(SRCDIR)/../skins/black_and_white/css.txt \
  $(SRCDIR)/../skins/black_and_white/details.txt \
  $(SRCDIR)/../skins/black_and_white/footer.txt \







>
>
>







161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
  $(SRCDIR)/winfile.c \
  $(SRCDIR)/winhttp.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/xfersetup.c \
  $(SRCDIR)/zip.c

EXTRA_FILES = \
  $(SRCDIR)/../extsrc/pikchr-worker.js \
  $(SRCDIR)/../extsrc/pikchr.js \
  $(SRCDIR)/../extsrc/pikchr.wasm \
  $(SRCDIR)/../skins/ardoise/css.txt \
  $(SRCDIR)/../skins/ardoise/details.txt \
  $(SRCDIR)/../skins/ardoise/footer.txt \
  $(SRCDIR)/../skins/ardoise/header.txt \
  $(SRCDIR)/../skins/black_and_white/css.txt \
  $(SRCDIR)/../skins/black_and_white/details.txt \
  $(SRCDIR)/../skins/black_and_white/footer.txt \
229
230
231
232
233
234
235

236
237
238
239
240
241
242
  $(SRCDIR)/fossil.fetch.js \
  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \
  $(SRCDIR)/fossil.page.chat.js \
  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \

  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \
  $(SRCDIR)/fossil.storage.js \
  $(SRCDIR)/fossil.tabs.js \
  $(SRCDIR)/fossil.wikiedit-wysiwyg.js \







>







232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
  $(SRCDIR)/fossil.fetch.js \
  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \
  $(SRCDIR)/fossil.page.chat.js \
  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \
  $(SRCDIR)/fossil.page.pikchrshowasm.js \
  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \
  $(SRCDIR)/fossil.storage.js \
  $(SRCDIR)/fossil.tabs.js \
  $(SRCDIR)/fossil.wikiedit-wysiwyg.js \
264
265
266
267
268
269
270

271
272
273
274
275
276
277
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \
  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.chat.css \
  $(SRCDIR)/style.fileedit.css \

  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \







>







268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \
  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.chat.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.pikchrshow.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \
2106
2107
2108
2109
2110
2111
2112





2113
2114
2115
2116
2117
2118
2119
2120


$(OBJDIR)/pikchr.o:	$(SRCDIR_extsrc)/pikchr.c
	$(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@

$(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c
	$(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@






#
# The list of all the targets that do not correspond to real files. This stops
# 'make' from getting confused when someone makes an error in a rule.
#

.PHONY: all install test clean








>
>
>
>
>








2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130


$(OBJDIR)/pikchr.o:	$(SRCDIR_extsrc)/pikchr.c
	$(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@

$(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c
	$(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@

$(SRCDIR_extsrc)/pikchr.js: $(SRCDIR_extsrc)/pikchr.c
	$(EMCC_WRAPPER) -o $@ $(EMCC_OPT) --no-entry  -sEXPORTED_RUNTIME_METHODS=cwrap,setValue,getValue,stackSave,stackRestore  -sEXPORTED_FUNCTIONS=_pikchr $(SRCDIR_extsrc)/pikchr.c  -sENVIRONMENT=web  -sMODULARIZE  -sEXPORT_NAME=initPikchrModule  --minify 0
	@chmod -x $(SRCDIR_extsrc)/pikchr.wasm
wasm: $(SRCDIR_extsrc)/pikchr.js

#
# The list of all the targets that do not correspond to real files. This stops
# 'make' from getting confused when someone makes an error in a rule.
#

.PHONY: all install test clean

Changes to src/pikchrshow.c.

228
229
230
231
232
233
234
235

236
237
238
239



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254




255
256
257
258
259
260
261
    blob_appendf(pOut, "%s\n", zNonce);
  }
  blob_reset(&bIn);
  return isErr;
}

/*
** WEBPAGE: pikchrshow

**
** A pikchr code editor and previewer, allowing users to experiment
** with pikchr code or prototype it for use in copy/pasting into forum
** posts, wiki pages, or embedded docs.



**
** It optionally accepts a p=pikchr-script-code URL parameter or POST
** value to pre-populate the editor with that code.
*/
void pikchrshow_page(void){
  const char *zContent = 0;
  int isDark;              /* true if the current skin is "dark" */
  int pikFlags =
    PIKCHR_PROCESS_DIV
    | PIKCHR_PROCESS_SRC
    | PIKCHR_PROCESS_ERR_PRE;

  login_check_credentials();
  if( !g.perm.RdWiki && !g.perm.Read && !g.perm.RdForum ){
    cgi_redirectf("%R/login?g=pikchrshow");




  }
  zContent = PD("content",P("p"));
  if(P("ajax")!=0){
    /* Called from the JS-side preview updater.
       TODO: respond with JSON instead.*/
    cgi_set_content_type("text/html");
    if(zContent && *zContent){







|
>



|
>
>
>
|
<
<

|









|
>
>
>
>







228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244


245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
    blob_appendf(pOut, "%s\n", zNonce);
  }
  blob_reset(&bIn);
  return isErr;
}

/*
** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
** this one if the "legacy" or "ajax" request arguments are set.
**
** A pikchr code editor and previewer, allowing users to experiment
** with pikchr code or prototype it for use in copy/pasting into forum
** posts, wiki pages, or embedded docs. This version of pikchrshow
** uses JavaScript to send pikchr code to the server for
** processing. The newer /pikchrshow applications runs pikchr on the
** client machine, without the need for back-and-forth network
** traffic.


*/
void pikchrshowcs_page(void){
  const char *zContent = 0;
  int isDark;              /* true if the current skin is "dark" */
  int pikFlags =
    PIKCHR_PROCESS_DIV
    | PIKCHR_PROCESS_SRC
    | PIKCHR_PROCESS_ERR_PRE;

  login_check_credentials();
  if( !g.perm.RdWiki && !g.perm.Read && !g.perm.RdForum ){
    cgi_redirectf("%R/login?g=pikchrshowcs");
  }
  if(P("wasm")){
    pikchrshow_page();
    return;
  }
  zContent = PD("content",P("p"));
  if(P("ajax")!=0){
    /* Called from the JS-side preview updater.
       TODO: respond with JSON instead.*/
    cgi_set_content_type("text/html");
    if(zContent && *zContent){
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
  if(!zContent){
    zContent = "arrow right 200% \"Markdown\" \"Source\"\n"
      "box rad 10px \"Markdown\" \"Formatter\" \"(markdown.c)\" fit\n"
      "arrow right 200% \"HTML+SVG\" \"Output\"\n"
      "arrow <-> down from last box.s\n"
      "box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n";
  }
  style_header("PikchrShow");
  CX("<style>"); {
    CX("div.content { padding-top: 0.5em }\n");
    CX("#sbs-wrapper {"
       "display: flex; flex-direction: column;"
       "}\n");
    CX("#sbs-wrapper > * {"
       "margin: 0 0.25em 0.5em 0; flex: 1 10 auto;"







|







283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
  if(!zContent){
    zContent = "arrow right 200% \"Markdown\" \"Source\"\n"
      "box rad 10px \"Markdown\" \"Formatter\" \"(markdown.c)\" fit\n"
      "arrow right 200% \"HTML+SVG\" \"Output\"\n"
      "arrow <-> down from last box.s\n"
      "box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n";
  }
  style_header("PikchrShow Client/Server");
  CX("<style>"); {
    CX("div.content { padding-top: 0.5em }\n");
    CX("#sbs-wrapper {"
       "display: flex; flex-direction: column;"
       "}\n");
    CX("#sbs-wrapper > * {"
       "margin: 0 0.25em 0.5em 0; flex: 1 10 auto;"
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
       "}\n");
    CX("body.pikchrshow .v-align-middle{"
       "vertical-align: middle"
       "}\n");
    CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n");
  } CX("</style>");
  CX("<div>Input pikchr code and tap Preview (or Shift-Enter) to render "
     "it:</div>");
  CX("<div id='sbs-wrapper'>"); {
    CX("<div id='pikchrshow-form'>"); {
      CX("<textarea id='content' name='content' rows='15'>"
         "%s</textarea>",zContent/*safe-for-%s*/);
      CX("<div id='pikchrshow-controls'>"); {
        CX("<button id='pikchr-submit-preview'>Preview</button>");
        CX("<div class='input-with-label'>"); {







|







340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
       "}\n");
    CX("body.pikchrshow .v-align-middle{"
       "vertical-align: middle"
       "}\n");
    CX(".dragover {border: 3px dotted rgba(0,255,0,0.6)}\n");
  } CX("</style>");
  CX("<div>Input pikchr code and tap Preview (or Shift-Enter) to render "
     "it. <a href='?wasm'>Switch to WASM mode</a>.</div>");
  CX("<div id='sbs-wrapper'>"); {
    CX("<div id='pikchrshow-form'>"); {
      CX("<textarea id='content' name='content' rows='15'>"
         "%s</textarea>",zContent/*safe-for-%s*/);
      CX("<div id='pikchrshow-controls'>"); {
        CX("<button id='pikchr-submit-preview'>Preview</button>");
        CX("<div class='input-with-label'>"); {
374
375
376
377
378
379
380




































































































































381
382
383
384
385
386
387
  } CX("</div>"/*sbs-wrapper*/);
  builtin_fossil_js_bundle_or("fetch", "copybutton", "popupwidget",
                              "storage", "pikchr", NULL);
  builtin_request_js("fossil.page.pikchrshow.js");
  builtin_fulfill_js_requests();
  style_finish_page();
}





































































































































/*
** COMMAND: pikchr*
**
** Usage: %fossil pikchr [options] ?INFILE? ?OUTFILE?
**
** Accepts a pikchr script as input and outputs the rendered script as







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
  } CX("</div>"/*sbs-wrapper*/);
  builtin_fossil_js_bundle_or("fetch", "copybutton", "popupwidget",
                              "storage", "pikchr", NULL);
  builtin_request_js("fossil.page.pikchrshow.js");
  builtin_fulfill_js_requests();
  style_finish_page();
}

/*
** WEBPAGE: pikchrshow
**
** A pikchr code editor and previewer, allowing users to experiment
** with pikchr code or prototype it for use in copy/pasting into forum
** posts, wiki pages, or embedded docs. This version of pikchrshow
** uses WebAssembly to run entirely in the client browser, without a
** need for back-and-forth client/server traffic to perform the
** rendering. The "legacy" version of this application, which sends
** all input to the server for rendering, can be accessed by adding
** the "legacy" URL argument.
**
** It optionally accepts a p=pikchr-script-code URL parameter or POST
** value to pre-populate the editor with that code.
*/
void pikchrshow_page(void){
  const char *zContent = 0;

  if(P("legacy") || P("ajax")){
    pikchrshowcs_page();
    return;
  }
  login_check_credentials();
  if( !g.perm.RdWiki && !g.perm.Read && !g.perm.RdForum ){
    cgi_redirectf("%R/login?g=pikchrshow");
  }
  style_emit_noscript_for_js_page();
  style_header("PikchrShow");
  zContent = PD("content",P("p"));
  if(!zContent){
    zContent = "arrow right 200% \"Markdown\" \"Source\"\n"
      "box rad 10px \"Markdown\" \"Formatter\" \"(markdown.c)\" fit\n"
      "arrow right 200% \"HTML+SVG\" \"Output\"\n"
      "arrow <-> down from last box.s\n"
      "box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n";
  }
  /* Wasm load/init progress widget... */
  CX("<div class='emscripten'>"); {
    CX("<figure id='module-spinner'>");
      CX("<div class='spinner'></div>");
      CX("<div class='center'><strong>Initializing app...</strong></div>");
      CX("<div class='center'>");
        CX("On a slow internet connection this may take a moment.  If this ");
        CX("message displays for \"a long time\", intialization may have ");
        CX("failed and the JavaScript console may contain clues as to why. ");
      CX("</div>");
      CX("<div><a href='?legacy'>Switch to legacy mode</a></div>");
    CX("</figure>");
    CX("<div class='emscripten' id='module-status'>Downloading...</div>");
    CX("<progress value='0' max='100' id='module-progress' hidden='1'>"
       "</progress>");
  } CX("</div><!-- .emscripten -->");
  /* Main view... */
  CX("<div id='view-split' class='app-view initially-hidden'>"); {
    CX("<fieldset class='options collapsible'>"); {
      CX("<legend><button id='btn-options-toggle'>Options</button></legend>");
      CX("<div>");
      CX("<span class='labeled-input'>");
        CX("<input type='checkbox' id='opt-cb-sbs' ");
        CX("data-csstgt='#main-wrapper' ");
        CX("data-cssclass='side-by-side' ");
        CX("data-config='sideBySide'>");
        CX("<label for='opt-cb-sbs'>Side-by-side</label>");
      CX("</span>");
      CX("<span class='labeled-input'>");
        CX("<input type='checkbox' id='opt-cb-swapio' ");
        CX("data-csstgt='#main-wrapper' ");
        CX("data-cssclass='swapio' ");
        CX("data-config='swapInOut'>");
        CX("<label for='opt-cb-swapio'>Swap in/out</label>");
      CX("</span>");
      CX("<span class='labeled-input'>");
        CX("<input type='checkbox' id='opt-cb-autofit' ");
        CX("data-config='renderAutofit'>");
        CX("<label for='opt-cb-autofit' "
           "title='Attempt to scale SVG to fit viewport. "
           "Whether it will work depends in part on the size "
           "and shape of the image and the viewport.'"
           ">Auto-fit SVG</label>");
      CX("</span>");
      CX("<span class='labeled-input'>");
        CX("<input type='checkbox' id='opt-cb-autorender' ");
        CX("data-csstgt='#main-wrapper' ");
        CX("data-cssclass='auto-render' ");
        CX("data-config='renderWhileTyping'>");
        CX("<label for='opt-cb-autorender'>Render while typing</label>");
      CX("</span>");
      CX("<span class='labeled-input'>");
        CX("<a href='?legacy'>Legacy mode</a>");
      CX("</span>");
      CX("</div><!-- options wrapper -->");
    } CX("</fieldset>");
    CX("<div id='main-wrapper' class=''>"); {
      CX("<fieldset class='zone-wrapper input'>"); {
        CX("<legend><div class='button-bar'>");
          CX("<button id='btn-render' "
             "title='Ctrl-Enter/Shift-Enter'>Render</button>");
          CX("<button id='btn-clear'>Clear Input</button>");
        CX("</div></legend>");
        CX("<div><textarea id='input'");
          CX("placeholder='Pikchr input. Ctrl-enter/shift-enter runs it.'>");
          CX("/**\n");
          CX("  Use ctrl-enter or shift-enter to execute\n");
          CX("  pikchr code. If only a subset is currently\n");
          CX("  selected, only that part is evaluated.\n*/\n");
        CX("%s</textarea></div>",zContent/*safe-for-%s*/);
      } CX("</fieldset><!-- .zone-wrapper.input -->");
      /*CX("<div class='splitter-handle hidden'></div>");*/
      CX("<fieldset class='zone-wrapper output'>"); {
        CX("<legend><div class='button-bar'>");
          CX("<button id='btn-render-mode'>Render Mode</button> ");
          CX("<span style='white-space:nowrap'>"
             "<span id='preview-copy-button' "
             "title='Tap to copy to clipboard.'></span>"
             "<label for='preview-copy-button' "
             "title='Tap to copy to clipboard.'></label>"
             "</span>");
        CX("</div></legend>");
        CX("<div id='pikchr-output-wrapper'>");
          CX("<div id='pikchr-output'></div>");
          CX("<textarea class='hidden' id='pikchr-output-text'></textarea>");
        CX("</div>");
      } CX("</fieldset> <!-- .zone-wrapper.output -->");
    } CX("</div><!-- #main-wrapper -->");
  } CX("</div><!-- #view-split -->");
  builtin_fossil_js_bundle_or("dom", "storage", "copybutton", NULL);
  builtin_request_js("fossil.page.pikchrshowasm.js");
  builtin_fulfill_js_requests();
  style_finish_page();
}


/*
** COMMAND: pikchr*
**
** Usage: %fossil pikchr [options] ?INFILE? ?OUTFILE?
**
** Accepts a pikchr script as input and outputs the rendered script as

Changes to src/style.c.

899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
** Generate code to load all required javascript files.
*/
static void style_load_all_js_files(void){
  if( needHrefJs && g.perm.Hyperlink ){
    int nDelay = db_get_int("auto-hyperlink-delay",0);
    int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0)
                   && sqlite3_strglob("*Android*",PD("HTTP_USER_AGENT",""));
    @ <script id='href-data' type='application/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
  }
  @ <script nonce="%h(style_nonce())">/* style.c:%d(__LINE__) */
  @ function debugMsg(msg){
  @ var n = document.getElementById("debugMsg");
  @ if(n){n.textContent=msg;}
  @ }







|







899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
** Generate code to load all required javascript files.
*/
static void style_load_all_js_files(void){
  if( needHrefJs && g.perm.Hyperlink ){
    int nDelay = db_get_int("auto-hyperlink-delay",0);
    int bMouseover = db_get_boolean("auto-hyperlink-mouseover",0)
                   && sqlite3_strglob("*Android*",PD("HTTP_USER_AGENT",""));
    @ <script id='href-data' type='text/json'>\
    @ {"delay":%d(nDelay),"mouseover":%d(bMouseover)}</script>
  }
  @ <script nonce="%h(style_nonce())">/* style.c:%d(__LINE__) */
  @ function debugMsg(msg){
  @ var n = document.getElementById("debugMsg");
  @ if(n){n.textContent=msg;}
  @ }
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
  const char *zScript = skin_get("js");
  if( P("test") ){
    /* Render the script as plain-text for testing purposes, if the "test"
    ** query parameter is present */
    cgi_set_content_type("text/plain");
  }else{
    /* Default behavior is to return javascript */
    cgi_set_content_type("application/javascript");
  }
  style_init_th1_vars(0);
  Th_Render(zScript?zScript:"");
}

/*
** Check for "name" or "page" query parameters on an /style.css







|







1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
  const char *zScript = skin_get("js");
  if( P("test") ){
    /* Render the script as plain-text for testing purposes, if the "test"
    ** query parameter is present */
    cgi_set_content_type("text/plain");
  }else{
    /* Default behavior is to return javascript */
    cgi_set_content_type("text/javascript");
  }
  style_init_th1_vars(0);
  Th_Render(zScript?zScript:"");
}

/*
** Check for "name" or "page" query parameters on an /style.css

Added src/style.pikchrshow.css.





































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/* CSS for the WASM /pikchrshow app. */
/* emcscript-related styling, used during the module load/intialization processes... */
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
#module-spinner { overflow: visible; }
#module-spinner > * {
    margin-top: 1em;
}
.spinner {
    height: 50px;
    width: 50px;
    margin: 0px auto;
    animation: rotation 0.8s linear infinite;
    border-left: 10px solid rgb(0,150,240);
    border-right: 10px solid rgb(0,150,240);
    border-bottom: 10px solid rgb(0,150,240);
    border-top: 10px solid rgb(100,0,200);
    border-radius: 100%;
    background-color: rgb(200,100,250);
}
@keyframes rotation {
    from {transform: rotate(0deg);}
    to {transform: rotate(360deg);}
}

/* The following styles are for app-level use. */
/* TODO: consolidate the WASM- and legacy-mode CSS into this file.
   Since they share a URI, they both load this file. */
textarea {
  font-family: monospace;
  flex: 1 1 auto;
}
#main-wrapper {
  display: flex;
  flex-direction: column-reverse;
  flex: 1 1 auto;
  margin: 0.5em 0;
  overflow: hidden;
}
#main-wrapper.side-by-side {
  flex-direction: row;
}
#main-wrapper.side-by-side > fieldset {
  margin-left: 0.25em;
  margin-right: 0.25em;
}
#main-wrapper:not(.side-by-side) > fieldset {
  margin-bottom: 0.25em;
}
#main-wrapper.swapio {
  flex-direction: column;
}
#main-wrapper.side-by-side.swapio {
  flex-direction: row-reverse;
}
#main-wrapper .splitter-handle {
  border: 1px outset;
  border-radius: 0.25em;
}
#main-wrapper.side-by-side .splitter-handle {
  width: 2px;
}
#main-wrapper:not(.side-by-side) .splitter-handle {
  width: 100%;
  height: 2px;
}
.zone-wrapper{
  display: flex;
  margin: 0;
  flex: 1 1 0%;
  border-radius: 0.5em;
  min-width: inherit/*important: resolves inability to scroll fieldset child element!*/;
  padding: 0.35em 0 0 0;
}
.zone-wrapper > div {
  display:flex;
  flex-direction: column;
  align-items: stretch;
  flex: 1 1 0%;
}
.zone-wrapper.output {
}
#main-wrapper.side-by-side .zone-wrapper {
  /* Keep the layout from "flopping around" when
     render-while-typing mode is on. */
  /*max-width: 50%;*/
}
#pikchr-output {
  padding: 0;
  margin: 0 auto/*auto resolves a weird left-shift truncation of the SVG*/;
}
#pikchr-output-wrapper {
  flex: 1 1 auto;
  overflow: auto;
}
#pikchr-output-wrapper:not(.autofit) {
  align-items: center;
}
#pikchr-output-wrapper.text {
  display: flex;
  align-items: stretch;
}
#pikchr-output-wrapper.text > #pikchr-output {
  display: flex;
  align-items: stretch;
  flex: 1 1 auto;
}
#pikchr-output-wrapper.text > #pikchr-output > textarea {
  flex: 1 1 auto;
}
fieldset > legend {
    font-size: 85%;
}
.zone-wrapper textarea {
  border-radius: 0.5em;
  flex: 1 1 auto;
  /*min/max width resolve an inexplicable margin on the RHS.  The -1em
   is for the padding, else we overlap the parent boundaries.*/
  min-width: calc(100% - 1em);
  max-width: calc(100% - 1em);
  padding: 0 0.5em;
}
.zone-wrapper.input {
  min-height: 10em;
  /*width: 50%;*/
  min-width: 20em;
}
.zone-wrapper textarea {
  border: 0;
  outline: 0;
}
.zone-wrapper.output {
  overflow: auto;
  justify-content: space-between;
}
.button-bar {
  /*display: flex;
  flex-wrap: wrap;
  align-items: center;*/
}
.button-bar > * {
  margin-top: 0.1em/*avoid crowding if the controls wrap*/;
  vertical-align: middle;
}
.button-bar > * + * /*all children except the first*/ {
  margin-left: 0.65em;
}
fieldset.options {
  border-radius: 0.5em;
  border: 1px inset;
  display: flex;
  flex-direction: column;
  padding: 0.25em;
}
fieldset.options > div {
  display: flex;
  flex-wrap: wrap;
  font-size: 85%;
}
fieldset.options > legend > #btn-options-toggle::after {
  content: " [hide]";
  position: relative;
}
fieldset.options.collapsed > legend > #btn-options-toggle::after {
  content: " [show]";
  position: relative;
}

span.labeled-input {
  padding: 0.25em;
  margin: 0.25em 0.5em;
  border-radius: 0.25em;
  white-space: nowrap;
  background: #0002;
  display: flex;
  align-items: center;
}
span.labeled-input > *:nth-child(2) {
  margin-left: 0.3em;
}
.center { text-align: center; }
.app-view {
  flex: 20 1 auto;
}
#titlebar {
  display: flex;
  justify-content: space-between;
  margin-bottom: 0.5em;
}
#view-split {
  display: flex;
  flex-direction: column-reverse;
}

Added tools/emcc.sh.in.

























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/usr/bin/bash
########################################################################
# WARNING: emcc.sh is generated from emcc.sh.in by the configure
# process. Do not edit emcc.sh directly, as it may be deleted or
# overwritten by the configure script.
#
# A wrapper around the emcc compiler which uses configure-time state
# to locate the Emscripten SDK and import the SDK's environment
# script, if needed.
########################################################################
# EMSDK_HOME comes from the configure --with-emsdk=/dir flag.
# EMSDK_ENV is ${thatDir}/emsdk_env.sh and is also set by the
# configure process.
EMSDK_HOME="@EMSDK_HOME@"
EMSDK_ENV="@EMSDK_ENV@"

emcc=$(which emcc 2>/dev/null)

if [ x = "x${emcc}" ]; then
  # If emcc is not found in the path, try to find it via an emsdk
  # installation. The SDK variant is the official installation
  # style supported by the Emscripten folks, but emcc is also
  # available via package managers on some OSes.
  if [ x = "x${EMSDK_HOME}" ]; then
    echo "EMSDK_HOME is not set. Pass --with-emsdk=/path/to/emsdk" \
         "to the configure script." 1>&2
    exit 1
  fi

  if [ x = "x${EMSDK_ENV}" ]; then
    if [ -f "${EMSDK_HOME}/emsdk_env.sh" ]; then
      EMSDK_ENV="${EMSDK_HOME}/emsdk_env.sh"
    else
      echo "EMSDK_ENV is not set. Expecting configure script to set it." 1>&2
      exit 2
    fi
  fi

  if [ ! -f "${EMSDK_ENV}" ]; then
    echo "emsdk_env script not found: $EMSDK_ENV" 1>&2
    exit 3
  fi

  # $EMSDK is part of the state set by emsdk_env.sh.
  if [ x = "x${EMSDK}" ]; then
    source "${EMSDK_ENV}" >/dev/null 2>&1 || {
      # ^^^ unfortunately outputs lots of noise to stderr
      rc=$?
      echo "Error sourcing ${EMSDK_ENV}"
      exit $rc
    }
  fi
  emcc=$(which emcc 2>/dev/null)
  if [ x = "x${emcc}" ]; then
    echo "emcc not found in PATH. Normally that's set up by EMSDK_ENV." 1>&2
    exit 4
  fi
fi

exec emcc "$@"

Changes to tools/makemake.tcl.

215
216
217
218
219
220
221


222
223
224
225
226
227
228
  wiki.wiki
  *.js
  default.css
  style.*.css
  ../skins/*/*.txt
  sounds/*.wav
  alerts/*.wav


}

# Options used to compile the included SQLite library.
#
set SQLITE_OPTIONS {
  -DNDEBUG=1
  -DSQLITE_DQS=0







>
>







215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
  wiki.wiki
  *.js
  default.css
  style.*.css
  ../skins/*/*.txt
  sounds/*.wav
  alerts/*.wav
  ../extsrc/pikchr.wasm
  ../extsrc/pikchr*.js
}

# Options used to compile the included SQLite library.
#
set SQLITE_OPTIONS {
  -DNDEBUG=1
  -DSQLITE_DQS=0
557
558
559
560
561
562
563











564
565
566
567
568
569
570
writeln {
$(OBJDIR)/pikchr.o:	$(SRCDIR_extsrc)/pikchr.c
	$(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@

$(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c
	$(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@












#
# The list of all the targets that do not correspond to real files. This stops
# 'make' from getting confused when someone makes an error in a rule.
#

.PHONY: all install test clean
}







>
>
>
>
>
>
>
>
>
>
>







559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
writeln {
$(OBJDIR)/pikchr.o:	$(SRCDIR_extsrc)/pikchr.c
	$(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@

$(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c
	$(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@

$(SRCDIR_extsrc)/pikchr.js: $(SRCDIR_extsrc)/pikchr.c
	$(EMCC_WRAPPER) -o $@ $(EMCC_OPT) --no-entry \
        -sEXPORTED_RUNTIME_METHODS=cwrap,setValue,getValue,stackSave,stackRestore \
        -sEXPORTED_FUNCTIONS=_pikchr $(SRCDIR_extsrc)/pikchr.c \
        -sENVIRONMENT=web \
        -sMODULARIZE \
        -sEXPORT_NAME=initPikchrModule \
        --minify 0
	@chmod -x $(SRCDIR_extsrc)/pikchr.wasm
wasm: $(SRCDIR_extsrc)/pikchr.js

#
# The list of all the targets that do not correspond to real files. This stops
# 'make' from getting confused when someone makes an error in a rule.
#

.PHONY: all install test clean
}
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
#
BCC = $(BCCEXE)

#### Enable compiling with debug symbols (much larger binary)
#
# FOSSIL_ENABLE_SYMBOLS = 1

#### Enable JSON (http://www.json.org) support using "cson"
#
# FOSSIL_ENABLE_JSON = 1

#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto)
#
# FOSSIL_ENABLE_SSL = 1








|







648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
#
BCC = $(BCCEXE)

#### Enable compiling with debug symbols (much larger binary)
#
# FOSSIL_ENABLE_SYMBOLS = 1

#### Enable JSON (https://www.json.org) support using "cson"
#
# FOSSIL_ENABLE_JSON = 1

#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto)
#
# FOSSIL_ENABLE_SSL = 1

Changes to win/Makefile.mingw.

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#
BCC = $(BCCEXE)

#### Enable compiling with debug symbols (much larger binary)
#
# FOSSIL_ENABLE_SYMBOLS = 1

#### Enable JSON (http://www.json.org) support using "cson"
#
# FOSSIL_ENABLE_JSON = 1

#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto)
#
# FOSSIL_ENABLE_SSL = 1








|







52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#
BCC = $(BCCEXE)

#### Enable compiling with debug symbols (much larger binary)
#
# FOSSIL_ENABLE_SYMBOLS = 1

#### Enable JSON (https://www.json.org) support using "cson"
#
# FOSSIL_ENABLE_JSON = 1

#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto)
#
# FOSSIL_ENABLE_SSL = 1

547
548
549
550
551
552
553



554
555
556
557
558
559
560
  $(SRCDIR)/winfile.c \
  $(SRCDIR)/winhttp.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/xfersetup.c \
  $(SRCDIR)/zip.c

EXTRA_FILES = \



  $(SRCDIR)/../skins/ardoise/css.txt \
  $(SRCDIR)/../skins/ardoise/details.txt \
  $(SRCDIR)/../skins/ardoise/footer.txt \
  $(SRCDIR)/../skins/ardoise/header.txt \
  $(SRCDIR)/../skins/black_and_white/css.txt \
  $(SRCDIR)/../skins/black_and_white/details.txt \
  $(SRCDIR)/../skins/black_and_white/footer.txt \







>
>
>







547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
  $(SRCDIR)/winfile.c \
  $(SRCDIR)/winhttp.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/xfersetup.c \
  $(SRCDIR)/zip.c

EXTRA_FILES = \
  $(SRCDIR)/../extsrc/pikchr-worker.js \
  $(SRCDIR)/../extsrc/pikchr.js \
  $(SRCDIR)/../extsrc/pikchr.wasm \
  $(SRCDIR)/../skins/ardoise/css.txt \
  $(SRCDIR)/../skins/ardoise/details.txt \
  $(SRCDIR)/../skins/ardoise/footer.txt \
  $(SRCDIR)/../skins/ardoise/header.txt \
  $(SRCDIR)/../skins/black_and_white/css.txt \
  $(SRCDIR)/../skins/black_and_white/details.txt \
  $(SRCDIR)/../skins/black_and_white/footer.txt \
615
616
617
618
619
620
621

622
623
624
625
626
627
628
  $(SRCDIR)/fossil.fetch.js \
  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \
  $(SRCDIR)/fossil.page.chat.js \
  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \

  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \
  $(SRCDIR)/fossil.storage.js \
  $(SRCDIR)/fossil.tabs.js \
  $(SRCDIR)/fossil.wikiedit-wysiwyg.js \







>







618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
  $(SRCDIR)/fossil.fetch.js \
  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \
  $(SRCDIR)/fossil.page.chat.js \
  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \
  $(SRCDIR)/fossil.page.pikchrshowasm.js \
  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \
  $(SRCDIR)/fossil.storage.js \
  $(SRCDIR)/fossil.tabs.js \
  $(SRCDIR)/fossil.wikiedit-wysiwyg.js \
650
651
652
653
654
655
656

657
658
659
660
661
662
663
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \
  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.chat.css \
  $(SRCDIR)/style.fileedit.css \

  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \







>







654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
  $(SRCDIR)/sounds/c.wav \
  $(SRCDIR)/sounds/d.wav \
  $(SRCDIR)/sounds/e.wav \
  $(SRCDIR)/sounds/f.wav \
  $(SRCDIR)/style.admin_log.css \
  $(SRCDIR)/style.chat.css \
  $(SRCDIR)/style.fileedit.css \
  $(SRCDIR)/style.pikchrshow.css \
  $(SRCDIR)/style.wikiedit.css \
  $(SRCDIR)/tree.js \
  $(SRCDIR)/useredit.js \
  $(SRCDIR)/wiki.wiki

TRANS_SRC = \
  $(OBJDIR)/add_.c \

Changes to win/Makefile.msc.

503
504
505
506
507
508
509



510
511
512
513
514
515
516
517
        "$(OX)\winfile_.c" \
        "$(OX)\winhttp_.c" \
        "$(OX)\xfer_.c" \
        "$(OX)\xfersetup_.c" \
        "$(OX)\zip_.c" \
        "$(SRCDIR_extsrc)\pikchr.c"




EXTRA_FILES   = "$(SRCDIR)\..\skins\ardoise\css.txt" \
        "$(SRCDIR)\..\skins\ardoise\details.txt" \
        "$(SRCDIR)\..\skins\ardoise\footer.txt" \
        "$(SRCDIR)\..\skins\ardoise\header.txt" \
        "$(SRCDIR)\..\skins\black_and_white\css.txt" \
        "$(SRCDIR)\..\skins\black_and_white\details.txt" \
        "$(SRCDIR)\..\skins\black_and_white\footer.txt" \
        "$(SRCDIR)\..\skins\black_and_white\header.txt" \







>
>
>
|







503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
        "$(OX)\winfile_.c" \
        "$(OX)\winhttp_.c" \
        "$(OX)\xfer_.c" \
        "$(OX)\xfersetup_.c" \
        "$(OX)\zip_.c" \
        "$(SRCDIR_extsrc)\pikchr.c"

EXTRA_FILES   = "$(SRCDIR)\..\extsrc\pikchr-worker.js" \
        "$(SRCDIR)\..\extsrc\pikchr.js" \
        "$(SRCDIR)\..\extsrc\pikchr.wasm" \
        "$(SRCDIR)\..\skins\ardoise\css.txt" \
        "$(SRCDIR)\..\skins\ardoise\details.txt" \
        "$(SRCDIR)\..\skins\ardoise\footer.txt" \
        "$(SRCDIR)\..\skins\ardoise\header.txt" \
        "$(SRCDIR)\..\skins\black_and_white\css.txt" \
        "$(SRCDIR)\..\skins\black_and_white\details.txt" \
        "$(SRCDIR)\..\skins\black_and_white\footer.txt" \
        "$(SRCDIR)\..\skins\black_and_white\header.txt" \
571
572
573
574
575
576
577

578
579
580
581
582
583
584
        "$(SRCDIR)\fossil.fetch.js" \
        "$(SRCDIR)\fossil.numbered-lines.js" \
        "$(SRCDIR)\fossil.page.brlist.js" \
        "$(SRCDIR)\fossil.page.chat.js" \
        "$(SRCDIR)\fossil.page.fileedit.js" \
        "$(SRCDIR)\fossil.page.forumpost.js" \
        "$(SRCDIR)\fossil.page.pikchrshow.js" \

        "$(SRCDIR)\fossil.page.whistory.js" \
        "$(SRCDIR)\fossil.page.wikiedit.js" \
        "$(SRCDIR)\fossil.pikchr.js" \
        "$(SRCDIR)\fossil.popupwidget.js" \
        "$(SRCDIR)\fossil.storage.js" \
        "$(SRCDIR)\fossil.tabs.js" \
        "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" \







>







574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
        "$(SRCDIR)\fossil.fetch.js" \
        "$(SRCDIR)\fossil.numbered-lines.js" \
        "$(SRCDIR)\fossil.page.brlist.js" \
        "$(SRCDIR)\fossil.page.chat.js" \
        "$(SRCDIR)\fossil.page.fileedit.js" \
        "$(SRCDIR)\fossil.page.forumpost.js" \
        "$(SRCDIR)\fossil.page.pikchrshow.js" \
        "$(SRCDIR)\fossil.page.pikchrshowasm.js" \
        "$(SRCDIR)\fossil.page.whistory.js" \
        "$(SRCDIR)\fossil.page.wikiedit.js" \
        "$(SRCDIR)\fossil.pikchr.js" \
        "$(SRCDIR)\fossil.popupwidget.js" \
        "$(SRCDIR)\fossil.storage.js" \
        "$(SRCDIR)\fossil.tabs.js" \
        "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" \
606
607
608
609
610
611
612

613
614
615
616
617
618
619
        "$(SRCDIR)\sounds\c.wav" \
        "$(SRCDIR)\sounds\d.wav" \
        "$(SRCDIR)\sounds\e.wav" \
        "$(SRCDIR)\sounds\f.wav" \
        "$(SRCDIR)\style.admin_log.css" \
        "$(SRCDIR)\style.chat.css" \
        "$(SRCDIR)\style.fileedit.css" \

        "$(SRCDIR)\style.wikiedit.css" \
        "$(SRCDIR)\tree.js" \
        "$(SRCDIR)\useredit.js" \
        "$(SRCDIR)\wiki.wiki"

OBJ   = "$(OX)\add$O" \
        "$(OX)\ajax$O" \







>







610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
        "$(SRCDIR)\sounds\c.wav" \
        "$(SRCDIR)\sounds\d.wav" \
        "$(SRCDIR)\sounds\e.wav" \
        "$(SRCDIR)\sounds\f.wav" \
        "$(SRCDIR)\style.admin_log.css" \
        "$(SRCDIR)\style.chat.css" \
        "$(SRCDIR)\style.fileedit.css" \
        "$(SRCDIR)\style.pikchrshow.css" \
        "$(SRCDIR)\style.wikiedit.css" \
        "$(SRCDIR)\tree.js" \
        "$(SRCDIR)\useredit.js" \
        "$(SRCDIR)\wiki.wiki"

OBJ   = "$(OX)\add$O" \
        "$(OX)\ajax$O" \
1127
1128
1129
1130
1131
1132
1133



1134
1135
1136
1137
1138
1139
1140
1141
"$(OBJDIR)\json_status$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_tag$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_timeline$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_user$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_wiki$O" : "$(SRCDIR)\json_detail.h"

"$(OX)\builtin_data.reslist": $(EXTRA_FILES) "$(B)\win\Makefile.msc"



	echo "$(SRCDIR)\../skins/ardoise/css.txt" > $@
	echo "$(SRCDIR)\../skins/ardoise/details.txt" >> $@
	echo "$(SRCDIR)\../skins/ardoise/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/ardoise/header.txt" >> $@
	echo "$(SRCDIR)\../skins/black_and_white/css.txt" >> $@
	echo "$(SRCDIR)\../skins/black_and_white/details.txt" >> $@
	echo "$(SRCDIR)\../skins/black_and_white/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/black_and_white/header.txt" >> $@







>
>
>
|







1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
"$(OBJDIR)\json_status$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_tag$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_timeline$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_user$O" : "$(SRCDIR)\json_detail.h"
"$(OBJDIR)\json_wiki$O" : "$(SRCDIR)\json_detail.h"

"$(OX)\builtin_data.reslist": $(EXTRA_FILES) "$(B)\win\Makefile.msc"
	echo "$(SRCDIR)\../extsrc/pikchr-worker.js" > $@
	echo "$(SRCDIR)\../extsrc/pikchr.js" >> $@
	echo "$(SRCDIR)\../extsrc/pikchr.wasm" >> $@
	echo "$(SRCDIR)\../skins/ardoise/css.txt" >> $@
	echo "$(SRCDIR)\../skins/ardoise/details.txt" >> $@
	echo "$(SRCDIR)\../skins/ardoise/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/ardoise/header.txt" >> $@
	echo "$(SRCDIR)\../skins/black_and_white/css.txt" >> $@
	echo "$(SRCDIR)\../skins/black_and_white/details.txt" >> $@
	echo "$(SRCDIR)\../skins/black_and_white/footer.txt" >> $@
	echo "$(SRCDIR)\../skins/black_and_white/header.txt" >> $@
1195
1196
1197
1198
1199
1200
1201

1202
1203
1204
1205
1206
1207
1208
	echo "$(SRCDIR)\fossil.fetch.js" >> $@
	echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@
	echo "$(SRCDIR)\fossil.page.brlist.js" >> $@
	echo "$(SRCDIR)\fossil.page.chat.js" >> $@
	echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@
	echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@
	echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@

	echo "$(SRCDIR)\fossil.page.whistory.js" >> $@
	echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@
	echo "$(SRCDIR)\fossil.pikchr.js" >> $@
	echo "$(SRCDIR)\fossil.popupwidget.js" >> $@
	echo "$(SRCDIR)\fossil.storage.js" >> $@
	echo "$(SRCDIR)\fossil.tabs.js" >> $@
	echo "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" >> $@







>







1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
	echo "$(SRCDIR)\fossil.fetch.js" >> $@
	echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@
	echo "$(SRCDIR)\fossil.page.brlist.js" >> $@
	echo "$(SRCDIR)\fossil.page.chat.js" >> $@
	echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@
	echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@
	echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@
	echo "$(SRCDIR)\fossil.page.pikchrshowasm.js" >> $@
	echo "$(SRCDIR)\fossil.page.whistory.js" >> $@
	echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@
	echo "$(SRCDIR)\fossil.pikchr.js" >> $@
	echo "$(SRCDIR)\fossil.popupwidget.js" >> $@
	echo "$(SRCDIR)\fossil.storage.js" >> $@
	echo "$(SRCDIR)\fossil.tabs.js" >> $@
	echo "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" >> $@
1230
1231
1232
1233
1234
1235
1236

1237
1238
1239
1240
1241
1242
1243
	echo "$(SRCDIR)\sounds/c.wav" >> $@
	echo "$(SRCDIR)\sounds/d.wav" >> $@
	echo "$(SRCDIR)\sounds/e.wav" >> $@
	echo "$(SRCDIR)\sounds/f.wav" >> $@
	echo "$(SRCDIR)\style.admin_log.css" >> $@
	echo "$(SRCDIR)\style.chat.css" >> $@
	echo "$(SRCDIR)\style.fileedit.css" >> $@

	echo "$(SRCDIR)\style.wikiedit.css" >> $@
	echo "$(SRCDIR)\tree.js" >> $@
	echo "$(SRCDIR)\useredit.js" >> $@
	echo "$(SRCDIR)\wiki.wiki" >> $@

"$(OX)\add$O" : "$(OX)\add_.c" "$(OX)\add.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\add_.c"







>







1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
	echo "$(SRCDIR)\sounds/c.wav" >> $@
	echo "$(SRCDIR)\sounds/d.wav" >> $@
	echo "$(SRCDIR)\sounds/e.wav" >> $@
	echo "$(SRCDIR)\sounds/f.wav" >> $@
	echo "$(SRCDIR)\style.admin_log.css" >> $@
	echo "$(SRCDIR)\style.chat.css" >> $@
	echo "$(SRCDIR)\style.fileedit.css" >> $@
	echo "$(SRCDIR)\style.pikchrshow.css" >> $@
	echo "$(SRCDIR)\style.wikiedit.css" >> $@
	echo "$(SRCDIR)\tree.js" >> $@
	echo "$(SRCDIR)\useredit.js" >> $@
	echo "$(SRCDIR)\wiki.wiki" >> $@

"$(OX)\add$O" : "$(OX)\add_.c" "$(OX)\add.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\add_.c"

Changes to www/build.wiki.

330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
<pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre>


<h2>6.0 Building on/for Android</h2>

<h3>6.1 Cross-compiling from Linux</h3>

The following instructions for building Fossil for Android,
without requiring a rooted OS, are adapted from
[https://fossil-scm.org/forum/forumpost/e0e9de4a7e | forumpost/e0e9de4a7e].

On the development machine, from the fossil source tree:

<pre><code>export CC=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang
./configure --with-openssl=none







|







330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
<pre><code>docker image rm THE_FOSSIL_ID THE_ALPINE_ID</code></pre>


<h2>6.0 Building on/for Android</h2>

<h3>6.1 Cross-compiling from Linux</h3>

The following instructions for building Fossil for Android via Linux,
without requiring a rooted OS, are adapted from
[https://fossil-scm.org/forum/forumpost/e0e9de4a7e | forumpost/e0e9de4a7e].

On the development machine, from the fossil source tree:

<pre><code>export CC=$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi21-clang
./configure --with-openssl=none
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395

The source of such warnings is not 100% certain.
Some information about these (reportedly harmless) warnings can
be found
[https://stackoverflow.com/a/41900551 | on this StackOverflow post].



<a id='fuzzer'></a>
<h2>Building for Fuzz Testing</h2>

This feature is primarily intended for fossil's developers and may
change at any time. It is only known to work on Linux systems and has
been seen to work on x86/64 and ARM.

Fossil has builtin support for processing specific features using
<tt>libfuzzer</tt>. The features which can be tested this way are







<

|







379
380
381
382
383
384
385

386
387
388
389
390
391
392
393
394

The source of such warnings is not 100% certain.
Some information about these (reportedly harmless) warnings can
be found
[https://stackoverflow.com/a/41900551 | on this StackOverflow post].



<a id='fuzzer'></a>
<h2>7.0 Building for Fuzz Testing</h2>

This feature is primarily intended for fossil's developers and may
change at any time. It is only known to work on Linux systems and has
been seen to work on x86/64 and ARM.

Fossil has builtin support for processing specific features using
<tt>libfuzzer</tt>. The features which can be tested this way are
458
459
460
461
462
463
464

























































































































 
Flags for the fuzzer can be passed directly to fossil,
e.g. <tt>-jobs=4</tt> to start four fuzzer jobs in parallel, but doing
so may cause the fuzzer to <em>strip the --fuzztype flag</em>, leading
to it testing the wrong thing. When passing on fuzzer-specific flags
along with <tt>--fuzztype</tt>, be sure to check your system's process
list to ensure that your <tt>--fuzztype</tt> flag is there.
































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
 
Flags for the fuzzer can be passed directly to fossil,
e.g. <tt>-jobs=4</tt> to start four fuzzer jobs in parallel, but doing
so may cause the fuzzer to <em>strip the --fuzztype flag</em>, leading
to it testing the wrong thing. When passing on fuzzer-specific flags
along with <tt>--fuzztype</tt>, be sure to check your system's process
list to ensure that your <tt>--fuzztype</tt> flag is there.


<a id='wasm'></a>
<h2>8.0 Building WebAssembly Components</h2>

As of version 2.19, fossil uses one component built as
[https://developer.mozilla.org/en-US/docs/WebAssembly | WebAssembly]
a.k.a. WASM. Because compiling WASM code requires non-trivial
client-side tooling, the repository includes compiled copies of these
pieces. Most Fossil hackers should never need to concern themselves
with the WASM parts, but this section describes how to for those who
want or need to do so.

<strong>The bits described in this section are necessary when updating
<tt>extsrc/pikchr.c</tt></strong> from the upstream source, or the
fossil binary will use a different version of pikchr than
[/pikchrshow] does (as the latter runs the WASM build of pikchr).

These instructions have only ever been tested on Linux systems. They
"should" work on any Unix-like system supported by Emscripten. The
fossil makefiles for Windows builds <em>do not</em> include any of the
WASM-related components (patches to add that would be welcomed, of
course).

The first step is to configure the tree with support for
[https://emscripten.org/|Emscripten]. This requires that the system
has the Emscripten SDK (a.k.a. emsdk) installed, as documented at:

[https://emscripten.org/docs/getting_started/downloads.html]

For instructions on keeping the SDK up to date, see:

[https://emscripten.org/docs/tools_reference/emsdk.html]

    Sidebar: getting Emscripten up and running is trivial and
    painless, at least on Linux systems, but the installer downloads
    many hundreds of megabytes of tools and dependencies, all of which
    will be installed under the single SDK directory (as opposed to
    being installed at the system level). It does, however, require
    that python3 be installed at the system level and it can
    optionally make use of a system-level cmake for certain tasks
    unrelated to how fossil uses the SDK.

After installing the SDK, configure the fossil tree with emsdk
support:

<pre><code>$ ./configure --with-emsdk=/path/to/emsdk ...other options...
</code></pre>

If the <tt>--with-emsdk</tt> flag is not provided, the configure
script will check for the environment variable <tt>EMSDK</tt>, which
is one of the standard variables the SDK environment uses. If that
variable is found, its value will implicitly be used in place of the
missing <tt>--with-emsdk</tt> flag. Thus, if the <tt>emsdk_env.sh</tt>
script is sourced into the shell before running the configure script,
the SDK will be detected even without the config flag.

The configure script installs some makefile variables which tell the
build where to find the SDK and it generates a script named
<tt>tools/emcc.sh</tt> (from the template file
<tt>[/file/tools/emcc.sh.in|/tools/emcc.sh.in]</tt>), which is a
wrapper around the Emscripten C compiler (<tt>emcc</tt>). The wrapper
script uses the configure-time state to attempt to set up the various
environment variables which are required by <tt>emcc</tt> and will
fail if it cannot do so. Once it's set up the environment, it passes
on all of its arguments to <tt>emcc</tt>.

The WASM-related build parts are set up such that none of them should
ever trigger implicity (e.g. via dependencies resolution) in a normal
build cycle. They are instead explicitly built as described below.

From the top of the source tree, all WASM-related components can be
built with:

<pre><code>$ make wasm</code></pre>

As of this writing, those parts include:

   *  <tt>extsrc/pikchr.wasm</tt> is a WASM-compiled form of
      <tt>extsrc/pikchr.c</tt>.
   *  <tt>extsrc/pikchr.js</tt> is JS/WASM glue code generated by Emscripten
      to give JS code access to the API exported by the WASM file.

    Sidebar: The file
    <tt>[/file/extsrc/pikcher-worker.js|extsrc/pikcher-worker.js]</tt>
    is hand-coded and intended to be loaded as a "Worker" in
    JavaScript. That file loads the main module and provides an
    interface via which a main JavaScript thread can communicate with
    pikchr running in a Worker thread. The file
    <tt>[/file/src/fossil.page.pikchrshowasm.js|src/fossil.page.pikchrshowasm.js]</tt>
    implements the [/pikchrshow] app and demonstrates how
    <tt>pikchr-worker.js</tt> is used.

When a new version of <tt>extsrc/pikchr.c</tt> is installed, the
files <tt>pikchr.{js,wasm}</tt> will need to be recompiled to account
for that. Running <tt>make wasm</tt> will, if the build is set up for
the emsdk, recompile those:

<pre><code>$ make wasm
./tools/emcc.sh -o extsrc/pikchr.js ...
$ ls -la extsrc/pikchr.{js,wasm}
-rw-rw-r-- 1 stephan stephan 17263 Jun  8 03:59 extsrc/pikchr.js
-rw-rw-r-- 1 stephan stephan 97578 Jun  8 03:59 extsrc/pikchr.wasm
</code></pre>

Then run the normal build so that those files can be compiled in to
the fossil binary, accessible via the [/help?cmd=/builtin|/builtin page]:

<pre><code>$ make</code></pre>

Before checking in those newly-built files, they need to be tested by
running the [/pikchrshow] page. If that page loads, the compilation
process fundamentally worked (a load failure will be made obvious to
the viewer). If it fails to load then the browser's dev tools console
likely provides at least a small hint (and <em>sometimes</em> a useful
hint) about the nature of the problem. Don't check those files in
until [/pikchrshow] runs, though!

Should pikchr's C interface ever changes, <tt>pikchr-worker.js</tt>
will need to be updated to accommodate it, but such modification is
typically trivial.

Changes to www/changes.wiki.

23
24
25
26
27
28
29
30



31
32
33
34
35
36
37
  *  Add the new special name "start:BRANCH" to refer to the first check-in of
     the branch.
  *  Support [/wiki?name=branch/generated-tkt-mimetype&p|generated "mimetype"]
     columns in the <var>TICKET</var> and <var>TICKETCHNG</var> tables.
  *  Fix [/timeline?r=fix_remote_url_overwrite_with_proxy|remote-url-overwrite]
     bug where remote-url is overwritten by the proxy setting during sync
     operation. Also require explicit "system" proxy setting to use
     "http_proxy" environment variable. 




<h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2>
  *  Added support for [./ssl-server.md|SSL/TLS server mode] for commands
     like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]"
  *  The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for
     [/help?cmd=merge|merge --cherrypick].
  *  Add new setting "[/help?cmd=large-file-size|large-file-size]".  If the size







|
>
>
>







23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  *  Add the new special name "start:BRANCH" to refer to the first check-in of
     the branch.
  *  Support [/wiki?name=branch/generated-tkt-mimetype&p|generated "mimetype"]
     columns in the <var>TICKET</var> and <var>TICKETCHNG</var> tables.
  *  Fix [/timeline?r=fix_remote_url_overwrite_with_proxy|remote-url-overwrite]
     bug where remote-url is overwritten by the proxy setting during sync
     operation. Also require explicit "system" proxy setting to use
     "http_proxy" environment variable.
  *  Reimplemented the [/pikchrshow] app to use a WebAssembly build of
     pikchr so that it can render pikchrs on the client instead of requiring
     a server round-trip.

<h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2>
  *  Added support for [./ssl-server.md|SSL/TLS server mode] for commands
     like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]"
  *  The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for
     [/help?cmd=merge|merge --cherrypick].
  *  Add new setting "[/help?cmd=large-file-size|large-file-size]".  If the size