Fossil

Check-in [4f697731]
Login

Check-in [4f697731]

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

Overview
Comment:Implemented most of the feedback from https://pikchr.org/home/forumpost/f538d8e7f7, with the exceptions being documented in that thread. /pikchrshow now longer uses the mode-swap feature (seems superfluous there) and tapping anywhere in the body, outside of the source/svg container element, now turns off those buttons on all pikchrs where they are currently visible. Consolidated pikchr-rendering impl for markdown/fossil/pikchrshow/pikchr command.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 4f697731832a44b4dc5bd129152324323e17b14a6498284e88cd089b8b9d5d75
User & Date: stephan 2020-09-17 16:31:21
Context
2020-09-17
16:52
pikchr mode swap: tapping on an SVG switches modes and enables the mode-select button. Leaving source view mode requires activating that button (which works like before, but is only visible in source mode). ... (check-in: 2a2be013 user: stephan tags: trunk)
16:31
Implemented most of the feedback from https://pikchr.org/home/forumpost/f538d8e7f7, with the exceptions being documented in that thread. /pikchrshow now longer uses the mode-swap feature (seems superfluous there) and tapping anywhere in the body, outside of the source/svg container element, now turns off those buttons on all pikchrs where they are currently visible. Consolidated pikchr-rendering impl for markdown/fossil/pikchrshow/pikchr command. ... (check-in: 4f697731 user: stephan tags: trunk)
2020-09-16
22:25
Changelog update per forum request. ... (check-in: b816bae3 user: stephan tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/default.css.
1383
1384
1385
1386
1387
1388
1389






1390
  border-style: outset;
}

noscript > .error {
  /* Part of the style_emit_noscript_for_js_page() interface. */
  padding: 1em;
  font-size: 150%;






}







>
>
>
>
>
>

1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
  border-style: outset;
}

noscript > .error {
  /* Part of the style_emit_noscript_for_js_page() interface. */
  padding: 1em;
  font-size: 150%;
}
.pikchr-src { /* source code view for a pikchr (see fossil.pikchr.js) */
  box-sizing: border-box/*reduces UI shift*/;
  border-width: 1px;
  border-style: dotted;
  overflow: auto;
}
Changes to src/fossil.page.pikchrshow.js.
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
      label = "SVG";
      f.showMarkupAlignment(false);
      D.parseHtml(D.clearElement(preTgt), P.response.raw);
      svg = f.getSvgNode(this.response.raw);
      if(svg){ /*for copy button*/
        this.e.taPreviewText.value = svg.outerHTML;
      }
      if(F.pikchr){
        F.pikchr.addSrcView(preTgt.querySelector('svg'));
      }
      break;
    case 1:
      label = "Markdown";
      f.showMarkupAlignment(true);
      this.e.taPreviewText.value = [
        '```pikchr'+f.getMarkupAlignmentClass(),
        this.response.inputText.trim(), '```'







<
<
<







324
325
326
327
328
329
330



331
332
333
334
335
336
337
      label = "SVG";
      f.showMarkupAlignment(false);
      D.parseHtml(D.clearElement(preTgt), P.response.raw);
      svg = f.getSvgNode(this.response.raw);
      if(svg){ /*for copy button*/
        this.e.taPreviewText.value = svg.outerHTML;
      }



      break;
    case 1:
      label = "Markdown";
      f.showMarkupAlignment(true);
      this.e.taPreviewText.value = [
        '```pikchr'+f.getMarkupAlignmentClass(),
        this.response.inputText.trim(), '```'
Changes to src/fossil.pikchr.js.
10
11
12
13
14
15
16








17
18
19
20
21
22
23
24
25
26
27
28
29
30
  // Install an app-specific stylesheet, just for development, after which
  // it will be moved into default.css
  (function(){
    const head = document.head || document.querySelector('head'),
          styleTag = document.createElement('style'),
          wh = '1cm' /* fixed width/height of buttons */,
          styleCSS = `








.pikchr-src-button {
  min-height: ${wh}; max-height: ${wh};
  min-width: ${wh}; max-width: ${wh};
  font-size: ${wh};
  position: absolute;
  top: 0;
  left: 0;
  border: 1px solid black;
  background-color: rgba(255,255,0,0.7);
  border-radius: 0.25cm;
  z-index: 50;
  cursor: pointer;
  text-align: center;
  display: inline-flex;







>
>
>
>
>
>
>
>




<
<
<







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
  // Install an app-specific stylesheet, just for development, after which
  // it will be moved into default.css
  (function(){
    const head = document.head || document.querySelector('head'),
          styleTag = document.createElement('style'),
          wh = '1cm' /* fixed width/height of buttons */,
          styleCSS = `
.pikchr-button-bar {
  position: absolute;
  position: absolute;
  top: 0;
  left: 0;
  display: inline-flex;
  flex-direction: column;
}
.pikchr-src-button {
  min-height: ${wh}; max-height: ${wh};
  min-width: ${wh}; max-width: ${wh};
  font-size: ${wh};



  border: 1px solid black;
  background-color: rgba(255,255,0,0.7);
  border-radius: 0.25cm;
  z-index: 50;
  cursor: pointer;
  text-align: center;
  display: inline-flex;
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

     Each element will only be processed once by this routine, even if
     it is passed to this function multiple times. Each processed
     element gets a "data" attribute set to it to indicate that it was
     already dealt with.
  */
  P.addSrcView = function f(svg,opt){






    if(!svg) svg = 'svg.pikchr';
    if('string' === typeof svg){
      document.querySelectorAll(svg).forEach(
        (e)=>f.call(this, e, opt)
      );
      return this;
    }else if(svg.forEach){
      svg.forEach((e)=>f.call(this, e, opt));
      return this;
    }
    if(svg.dataset.pikchrProcessed){
      return this;
    }
    svg.dataset.pikchrProcessed = 1;


    const src = svg.querySelector('pikchr\\:src');
    if(!src){
      console.warn("No pikchr:src node found in",svg);
      return this;
    }
    opt = F.mergeLastWins({
    },opt);
    const parent = svg.parentNode;
    parent.style.position = 'relative' /* REQUIRED for btn placement */;
    const srcView = D.addClass(D.textarea(0,0,true), 'pikchr-src-text');
    srcView.value = src.textContent;
    const btnFlip = D.append(
      D.addClass(D.span(), 'pikchr-src-button'),
    );
    const btnCopy = F.copyButton(
      D.span(), {
        cssClass: ['copy-button', 'pikchr-copy-button'],
        extractText: function(){
          return (srcView.classList.contains('hidden')
                  ? svg.outerHTML
                  : srcView.value);
        }
      }
    );
    const buttons = [btnFlip, btnCopy];
    D.addClass(buttons, 'hidden');
    D.append(parent, D.addClass(srcView, 'hidden'), buttons);

    /**
       Toggle the buttons on only when the mouse is in the parent
       widget's area or the user taps on that area. This seems much
       less "busy" than having them always visible and slightly in the way.
       It also means that we can make them a bit larger.
    */
    if(0){ /* Mouse enter/leave triggers currently disabled by request */
      parent.addEventListener('mouseenter', function(ev){
        if(ev.target === parent) D.removeClass(buttons, 'hidden');
      }, true);
      parent.addEventListener('mouseleave', function(ev){
        if(ev.target === parent) D.addClass(buttons, 'hidden');
      }, true);
      /* mouseenter/leave work well... but only if there's a mouse. */
    }
    parent.addEventListener('click', function(ev){
      ev.preventDefault();
      ev.stopPropagation();
      D.toggleClass(buttons, 'hidden');
    }, false);


    btnFlip.addEventListener('click', function f(ev){
      ev.preventDefault();
      ev.stopPropagation();
      if(!f.hasOwnProperty('origMaxWidth')){
        f.origMaxWidth = parent.style.maxWidth;
      }
      const svgStyle = window.getComputedStyle(svg);







>
>
>
>
>
>














>
>
|
|
|


<
<
<
<
|
<



|
<
<
|
<
<
<
<
<
|
<
|
|

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|


|


>







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

     Each element will only be processed once by this routine, even if
     it is passed to this function multiple times. Each processed
     element gets a "data" attribute set to it to indicate that it was
     already dealt with.
  */
  P.addSrcView = function f(svg,opt){
    if(!f.hasOwnProperty('bodyClick')){
      f.bodyClick = function(){
        D.addClass(document.querySelectorAll('.pikchr-button-bar'), 'hidden');
      };
      document.body.addEventListener('click', f.bodyClick, false);
    }
    if(!svg) svg = 'svg.pikchr';
    if('string' === typeof svg){
      document.querySelectorAll(svg).forEach(
        (e)=>f.call(this, e, opt)
      );
      return this;
    }else if(svg.forEach){
      svg.forEach((e)=>f.call(this, e, opt));
      return this;
    }
    if(svg.dataset.pikchrProcessed){
      return this;
    }
    svg.dataset.pikchrProcessed = 1;
    const parent = svg.parentNode;
    parent.style.position = 'relative' /* REQUIRED for btn placement */;
    const srcView = parent.querySelector('.pikchr-src');
    if(!srcView){
      console.warn("No pikchr source node found in",parent);
      return this;
    }




    const buttonBar = D.addClass(D.span(), 'pikchr-button-bar');

    const btnFlip = D.append(
      D.addClass(D.span(), 'pikchr-src-button'),
    );
    D.append(buttonBar, btnFlip);


    // not yet sure which options we can/should support:





    // opt = F.mergeLastWins({},opt);

    D.addClass(srcView, 'hidden')/*should already be so, but just in case*/;
    D.append(parent, D.addClass(buttonBar, 'hidden'));
















    parent.addEventListener('click', function f(ev){
      ev.preventDefault();
      ev.stopPropagation();
      D.toggleClass(buttonBar, 'hidden');
    }, false);

    /** Toggle the source/SVG view on click. */
    btnFlip.addEventListener('click', function f(ev){
      ev.preventDefault();
      ev.stopPropagation();
      if(!f.hasOwnProperty('origMaxWidth')){
        f.origMaxWidth = parent.style.maxWidth;
      }
      const svgStyle = window.getComputedStyle(svg);
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
        if(srcView.classList.contains('hidden')){/*initial state*/
          parent.style.width = f.origMaxWidth;
          parent.style.maxWidth = 'unset';
        }else{/*srcView is active*/
          parent.style.maxWidth = f.origMaxWidth;
          parent.style.width = 'unset';
        }
      }else{
        /* Option #2: gives us good results for non-centered items but
           not for centered. We apparently have no(?) reliable way of
           distinguishing centered from left/indented pikchrs here
           unless we add a CSS class to mark them as such in the
           pikchr-to-wiki-image code. */
        if(srcView.classList.contains('hidden')){/*initial state*/
          parent.style.width = 'unset';
          parent.style.maxWidth = 'unset';
        }else{/*srcView is active*/
          parent.style.maxWidth = f.origMaxWidth;
          parent.style.width = 'unset';
        }

      }
      btnFlip.classList.toggle('src-active');
      D.toggleClass([svg, srcView], 'hidden');
    }, false);
  };
  
})(window.fossil);







|












<







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
        if(srcView.classList.contains('hidden')){/*initial state*/
          parent.style.width = f.origMaxWidth;
          parent.style.maxWidth = 'unset';
        }else{/*srcView is active*/
          parent.style.maxWidth = f.origMaxWidth;
          parent.style.width = 'unset';
        }
      }else if(1){
        /* Option #2: gives us good results for non-centered items but
           not for centered. We apparently have no(?) reliable way of
           distinguishing centered from left/indented pikchrs here
           unless we add a CSS class to mark them as such in the
           pikchr-to-wiki-image code. */
        if(srcView.classList.contains('hidden')){/*initial state*/
          parent.style.width = 'unset';
          parent.style.maxWidth = 'unset';
        }else{/*srcView is active*/
          parent.style.maxWidth = f.origMaxWidth;
          parent.style.width = 'unset';
        }

      }
      btnFlip.classList.toggle('src-active');
      D.toggleClass([svg, srcView], 'hidden');
    }, false);
  };
  
})(window.fossil);
Changes to src/markdown_html.c.
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
** text and insert the result in place of the original.
*/
void pikchr_to_html(
  Blob *ob,                     /* Write the generated SVG here */
  const char *zSrc, int nSrc,   /* The Pikchr source text */
  const char *zArg, int nArg    /* Addition arguments */
){
  int w = 0, h = 0;
  char *zIn = fossil_strndup(zSrc, nSrc);
  char *zOut = pikchr(zIn, "pikchr", PIKCHR_INCLUDE_SOURCE, &w, &h);
  fossil_free(zIn);
  if( w>0 && h>0 ){
    const char *zNonce = safe_html_nonce(1);
    Blob css;
    blob_init(&css,0,0);
    blob_appendf(&css,"max-width:%dpx;",w);
    blob_append(ob, zNonce, -1);
    blob_append_char(ob, '\n');
    while( nArg>0 ){
      int i;
      for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
      if( i==6 && strncmp(zArg, "center", 6)==0 ){
        blob_appendf(&css, "display:block;margin:auto;");

        break;
      }else if( i==6 && strncmp(zArg, "indent", 6)==0 ){
        blob_appendf(&css, "margin-left:4em;");

        break;
      }else if( i==10 && strncmp(zArg, "float-left", 10)==0 ){
        blob_appendf(&css, "float:left;padding=4em;");

        break;
      }else if( i==11 && strncmp(zArg, "float-right", 11)==0 ){
        blob_appendf(&css, "float:right;padding=4em;");

        break;
      }
      while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
      zArg += i;
      nArg -= i;
    }
    blob_appendf(ob, "<div style='%s'>\n", blob_str(&css));
    blob_append(ob, zOut, -1);

    blob_appendf(ob, "</div>\n");
    blob_reset(&css);
    blob_appendf(ob, "%s\n", zNonce);
  }else{
    blob_appendf(ob, "<pre>\n%s\n</pre>\n", zOut);
  }
  free(zOut);
}


/* Invoked for `...` blocks where there are nSep grave accents in a
** row that serve as the delimiter.  According to CommonMark:
**
**   *  https://spec.commonmark.org/0.29/#fenced-code-blocks







|
|
|
<
<
<
|
|
<
<
<
|
|
|
|
<
>
|
|
<
>
|
|
<
>
|
|
<
>
|
|
|
|
|
|
<
|
>
|
|
<
<
<
<
<







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
** text and insert the result in place of the original.
*/
void pikchr_to_html(
  Blob *ob,                     /* Write the generated SVG here */
  const char *zSrc, int nSrc,   /* The Pikchr source text */
  const char *zArg, int nArg    /* Addition arguments */
){
  int pikFlags = PIKCHR_PROCESS_NONCE
    | PIKCHR_PROCESS_DIV
    | PIKCHR_PROCESS_SRC_HIDDEN;



  Blob bSrc = empty_blob;




  while( nArg>0 ){
    int i;
    for(i=0; i<nArg && !fossil_isspace(zArg[i]); i++){}
    if( i==6 && strncmp(zArg, "center", 6)==0 ){

      pikFlags |= PIKCHR_PROCESS_DIV_CENTER;
      break;
    }else if( i==6 && strncmp(zArg, "indent", 6)==0 ){

      pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
      break;
    }else if( i==10 && strncmp(zArg, "float-left", 10)==0 ){

      pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_LEFT;
      break;
    }else if( i==11 && strncmp(zArg, "float-right", 11)==0 ){

      pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_RIGHT;
      break;
    }
    while( i<nArg && fossil_isspace(zArg[i]) ){ i++; }
    zArg += i;
    nArg -= i;
  }

  blob_append(&bSrc, zSrc, nSrc)
    /*have to dupe input to ensure a NUL-terminated source string */;
  pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
  blob_reset(&bSrc);





}


/* Invoked for `...` blocks where there are nSep grave accents in a
** row that serve as the delimiter.  According to CommonMark:
**
**   *  https://spec.commonmark.org/0.29/#fenced-code-blocks
Changes to src/pikchrshow.c.
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32





33
34
35
36
37
38
39
** This file contains fossil-specific code related to pikchr.
*/
#include "config.h"
#include <assert.h>
#include <ctype.h>
#include "pikchrshow.h"

#ifdef INTERFACE
/* These are described in pikchr_process()'s docs. */
#define PIKCHR_PROCESS_TH1        0x01
#define PIKCHR_PROCESS_TH1_NOSVG  0x02
#define PIKCHR_PROCESS_DIV        0x04
#define PIKCHR_PROCESS_NONCE      0x08
#define PIKCHR_PROCESS_ERR_PRE    0x10
#define PIKCHR_PROCESS_NO_SRC     0x20





#endif

/*
** Processes a pikchr script, optionally with embedded TH1. zIn is the
** input script. pikFlags may be a bitmask of any of the
** PIKCHR_PROCESS_xxx flags (see below). thFlags may be a bitmask of
** any of the TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to







|



|
|
|
|
>
>
>
>
>







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
** This file contains fossil-specific code related to pikchr.
*/
#include "config.h"
#include <assert.h>
#include <ctype.h>
#include "pikchrshow.h"

#if INTERFACE
/* These are described in pikchr_process()'s docs. */
#define PIKCHR_PROCESS_TH1        0x01
#define PIKCHR_PROCESS_TH1_NOSVG  0x02
#define PIKCHR_PROCESS_NONCE      0x04
#define PIKCHR_PROCESS_ERR_PRE    0x08
#define PIKCHR_PROCESS_SRC        0x10
#define PIKCHR_PROCESS_SRC_HIDDEN 0x20
#define PIKCHR_PROCESS_DIV        0x40
#define PIKCHR_PROCESS_DIV_INDENT      0x0100
#define PIKCHR_PROCESS_DIV_CENTER      0x0200
#define PIKCHR_PROCESS_DIV_FLOAT_LEFT  0x0400
#define PIKCHR_PROCESS_DIV_FLOAT_RIGHT 0x0800
#endif

/*
** Processes a pikchr script, optionally with embedded TH1. zIn is the
** input script. pikFlags may be a bitmask of any of the
** PIKCHR_PROCESS_xxx flags (see below). thFlags may be a bitmask of
** any of the TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to
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
**
** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
** init flags specified in the 3rd argument. If thFlags is non-0 then
** this flag is assumed even if it is not specified.
**
** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
** TH1 step, thus the output will be (presumably) a
** TH1-generated/processed pikchr script, and not an SVG. If this flag
** is set, PIKCHR_PROCESS_TH1 is assumed even if it is not specified.

**
** The remaining flags listed below are ignored if
** PIKCHR_PROCESS_TH1_NOSVG is specified:
**
** - PIKCHR_PROCESS_NO_SRC: by default the contents of zIn are stored
** in the resulting SVG content, as part of the image metadata. That
** is suppressed if this flag is set.
**
** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
** element which specifies a max-width style value based on the SVG's
** calculated size.
**








** - PIKCHR_PROCESS_NONCE: if set, the resulting SVG/DEV are wrapped
** in "safe nonce" comments, which are a fossil-internal mechanism
** which prevents the wiki/markdown processors from processing this
** output.
**








** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
** error report is wrapped in PRE element.

*/
int pikchr_process(const char * zIn, int pikFlags, int thFlags,
                   Blob * pOut){
  Blob bIn = empty_blob;
  int isErr = 0;










  if(!(PIKCHR_PROCESS_TH1 & pikFlags)

     && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
    pikFlags |= PIKCHR_PROCESS_TH1;
  }  
  if(PIKCHR_PROCESS_TH1 & pikFlags){
    Blob out = empty_blob;
    isErr = Th_RenderToBlob(zIn, &out, thFlags)
      ? 1 : 0;







|
|
>




<
<
<
<


|

>
>
>
>
>
>
>
>
|

|


>
>
>
>
>
>
>
>

|
>


|



>
>
>
>
>
>
>
>
>

>







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
**
** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
** init flags specified in the 3rd argument. If thFlags is non-0 then
** this flag is assumed even if it is not specified.
**
** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
** TH1 step, thus the output will be (presumably) a
** TH1-generated/processed pikchr script (or whatever else the TH1
** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
** if it is not specified.
**
** The remaining flags listed below are ignored if
** PIKCHR_PROCESS_TH1_NOSVG is specified:
**




** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
** element which specifies a max-width style value based on the SVG's
** calculated size. This flag has multiple mutually exclusive forms:
**
**  - PIKCHR_PROCESS_DIV uses default element alignment.
**  - PIKCHR_PROCESS_DIV_INDENT indents the div.
**  - PIKCHR_PROCESS_DIV_CENTER centers the div.
**  - PIKCHR_PROCESS_DIV_FLOAT_LEFT floats the div left.
**  - PIKCHR_PROCESS_DIV_FLOAT_RIGHT floats the div right.
**
** If more than one is specified, which one is used is undefined.
**
** - PIKCHR_PROCESS_NONCE: if set, the resulting SVG/DIV are wrapped
** in "safe nonce" comments, which are a fossil-internal mechanism
** which prevents the wiki/markdown processors from re-processing this
** output.
**
** - PIKCHR_PROCESS_SRC: if set, a new TEXTAREA.pikchr-src element is injected
** adjacet to the SVG element which contains the HTML-escaped content of
** the input script.
**
** - PIKCHR_PROCESS_SRC_HIDDEN: exactly like PIKCHR_PROCESS_SRC but
** the .pikchr-src tag also gets the CSS class 'hidden' (which, in
** fossil's default CSS, will hide that element).
**
** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
** error report is wrapped in a PRE element, else it is retained
** as-is (intended for console output).
*/
int pikchr_process(const char * zIn, int pikFlags, int thFlags,
                    Blob * pOut){
  Blob bIn = empty_blob;
  int isErr = 0;

  if(!(PIKCHR_PROCESS_DIV & pikFlags)
     /* If any DIV_xxx flags are set, set DIV */
     && (PIKCHR_PROCESS_DIV_INDENT
         | PIKCHR_PROCESS_DIV_CENTER
         | PIKCHR_PROCESS_DIV_FLOAT_RIGHT
         | PIKCHR_PROCESS_DIV_FLOAT_LEFT
         ) & pikFlags){
    pikFlags |= PIKCHR_PROCESS_DIV;
  }
  if(!(PIKCHR_PROCESS_TH1 & pikFlags)
     /* If any TH1_xxx flags are set, set TH1 */
     && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
    pikFlags |= PIKCHR_PROCESS_TH1;
  }  
  if(PIKCHR_PROCESS_TH1 & pikFlags){
    Blob out = empty_blob;
    isErr = Th_RenderToBlob(zIn, &out, thFlags)
      ? 1 : 0;
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
  if(!isErr){
    if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
      blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
    }else{
      int w = 0, h = 0;
      const char * zContent = blob_str(&bIn);
      char *zOut;
      const unsigned int pikFlags2 = (PIKCHR_PROCESS_NO_SRC & pikFlags)
        ? 0 : PIKCHR_INCLUDE_SOURCE;
      
      zOut = pikchr(zContent, "pikchr", pikFlags2, &w, &h);
      if( w>0 && h>0 ){
        const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
          ? safe_html_nonce(1) : 0;
        if(zNonce){
          blob_append(pOut, zNonce, -1);
        }
        if(PIKCHR_PROCESS_DIV & pikFlags){











          blob_appendf(pOut,"<div style='max-width:%dpx;'>\n", w);

        }
        blob_append(pOut, zOut, -1);









        if(PIKCHR_PROCESS_DIV & pikFlags){
          blob_append(pOut,"</div>\n", 7);
        }
        if(zNonce){
          blob_append(pOut, zNonce, -1);
        }
      }else{
        isErr = 2;
        if(PIKCHR_PROCESS_ERR_PRE & pikFlags){







<
<
|
|







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


>
>
>
>
>
>
>
>
>

|







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
  if(!isErr){
    if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
      blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
    }else{
      int w = 0, h = 0;
      const char * zContent = blob_str(&bIn);
      char *zOut;



      zOut = pikchr(zContent, "pikchr", 0, &w, &h);
      if( w>0 && h>0 ){
        const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
          ? safe_html_nonce(1) : 0;
        if(zNonce){
          blob_append(pOut, zNonce, -1);
        }
        if(PIKCHR_PROCESS_DIV & pikFlags){
          Blob css = empty_blob;
          blob_appendf(&css, "max-width:%dpx;", w);
          if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
            blob_append(&css, "display:block;margin-auto;", -1);
          }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
            blob_append(&css, "margin-left:4em", -1);
          }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
            blob_append(&css, "float:left;padding=4em;", -1);
          }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
            blob_append(&css, "float:right;padding=4em;", -1);
          }
          blob_appendf(pOut,"<div class=\"pikchr\" style=\"%b\">\n", &css);
          blob_reset(&css);
        }
        blob_append(pOut, zOut, -1);
        if((PIKCHR_PROCESS_SRC & pikFlags)
           || (PIKCHR_PROCESS_SRC_HIDDEN & pikFlags)){
          blob_appendf(pOut, "<textarea rows='10' readonly "
                       "class='pikchr-src%s'>"
                       "%h</textarea>\n",
                       (PIKCHR_PROCESS_SRC_HIDDEN & pikFlags)
                       ? " hidden" : "",
                       blob_str(&bIn));
        }
        if(PIKCHR_PROCESS_DIV & pikFlags){
          blob_append(pOut, "</div>\n", 7);
        }
        if(zNonce){
          blob_append(pOut, zNonce, -1);
        }
      }else{
        isErr = 2;
        if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
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
**
** 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" */




  login_check_credentials();
  if( !g.perm.RdWiki && !g.perm.Read && !g.perm.RdForum ){
    cgi_redirectf("%s/login?g=%s/pikchrshow", g.zTop, g.zTop);
  }
  zContent = PD("content",P("p"));
  if(P("ajax")!=0){
    /* Called from the JS-side preview updater. */
    cgi_set_content_type("text/html");
    if(zContent && *zContent){
      Blob out = empty_blob;
      const int isErr =
        pikchr_process(zContent,
                       PIKCHR_PROCESS_DIV | PIKCHR_PROCESS_ERR_PRE,
                       0, &out);
      if(isErr){
        cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
      }
      CX("%b", &out);
      blob_reset(&out);
    }else{
      CX("<pre>No content! Nothing to render</pre>");







>
>
>












|
<
<







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
**
** 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_HIDDEN
    | PIKCHR_PROCESS_ERR_PRE;

  login_check_credentials();
  if( !g.perm.RdWiki && !g.perm.Read && !g.perm.RdForum ){
    cgi_redirectf("%s/login?g=%s/pikchrshow", g.zTop, g.zTop);
  }
  zContent = PD("content",P("p"));
  if(P("ajax")!=0){
    /* Called from the JS-side preview updater. */
    cgi_set_content_type("text/html");
    if(zContent && *zContent){
      Blob out = empty_blob;
      const int isErr =
        pikchr_process(zContent, pikFlags, 0, &out);


      if(isErr){
        cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
      }
      CX("%b", &out);
      blob_reset(&out);
    }else{
      CX("<pre>No content! Nothing to render</pre>");
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
    CX("<fieldset id='pikchrshow-output-wrapper'>"); {
      CX("<legend></legend>"
         /* Reminder: Firefox does not properly flexbox a LEGEND
            element, always flowing it in column mode. */);
      CX("<div id='pikchrshow-output'>");
      if(*zContent){
        Blob out = empty_blob;
        pikchr_process(zContent, PIKCHR_PROCESS_ERR_PRE
                       | PIKCHR_PROCESS_DIV, 0, &out);
        CX("%b", &out);
        blob_reset(&out);
      } CX("</div>"/*#pikchrshow-output*/);
    } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
  } CX("</div>"/*sbs-wrapper*/);
  if(!builtin_bundle_all_fossil_js_apis()){
    builtin_emit_fossil_js_apis("dom", "fetch", "copybutton",







|
<







315
316
317
318
319
320
321
322

323
324
325
326
327
328
329
    CX("<fieldset id='pikchrshow-output-wrapper'>"); {
      CX("<legend></legend>"
         /* Reminder: Firefox does not properly flexbox a LEGEND
            element, always flowing it in column mode. */);
      CX("<div id='pikchrshow-output'>");
      if(*zContent){
        Blob out = empty_blob;
        pikchr_process(zContent, pikFlags, 0, &out);

        CX("%b", &out);
        blob_reset(&out);
      } CX("</div>"/*#pikchrshow-output*/);
    } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
  } CX("</div>"/*sbs-wrapper*/);
  if(!builtin_bundle_all_fossil_js_apis()){
    builtin_emit_fossil_js_apis("dom", "fetch", "copybutton",
297
298
299
300
301
302
303
304

305
306






307
308

309
310
311
312
313
314
315
** resp. stdout, and the names "-" can be used as aliases for those
** streams.
**
** Options:
**
**    -div       On success, adds a DIV wrapper around the
**               resulting SVG output which limits its max-width to
**               its computed maximum ideal size, in order to mimic

**               how fossil's web-based components work.
**






**    -svg-src   Stores the input pikchr's source code in the SVG's
**               metadata.

**
**    -th        Process the input using TH1 before passing it to pikchr.
**
**    -th-novar  Disable $var and $<var> TH1 processing. Use this if the
**               pikchr script uses '$' for its own purposes and that
**               causes issues. This only affects parsing of '$' outside
**               of TH1 script blocks. Code in such blocks is unaffected.







|
>
|

>
>
>
>
>
>
|
|
>







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
** resp. stdout, and the names "-" can be used as aliases for those
** streams.
**
** Options:
**
**    -div       On success, adds a DIV wrapper around the
**               resulting SVG output which limits its max-width to
**               its computed maximum ideal size.
**
**    -div-indent Like -div but indents the div.
**
**    -div-center Like -div but centers the div.
**
**    -div-left   Like -div but floats the div left.
**
**    -div-right  Like -div but floats the div right.
**
**    -svg-src   Stores the input pikchr's source code in the output as
**               a separate element adjacent to the SVG one. The
**               source element initially has the "hidden" CSS class.
**
**    -th        Process the input using TH1 before passing it to pikchr.
**
**    -th-novar  Disable $var and $<var> TH1 processing. Use this if the
**               pikchr script uses '$' for its own purposes and that
**               causes issues. This only affects parsing of '$' outside
**               of TH1 script blocks. Code in such blocks is unaffected.
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
** sense for pikchr scripts.
*/
void pikchr_cmd(void){
  Blob bIn = empty_blob;
  Blob bOut = empty_blob;
  const char * zInfile = "-";
  const char * zOutfile = "-";
  const int fWithDiv = find_option("div",0,0)!=0;
  const int fTh1 = find_option("th",0,0)!=0;
  const int fNosvg = find_option("th-nosvg",0,0)!=0;
  int isErr = 0;
  int pikFlags = find_option("svg-src",0,0)!=0
    ? 0 : PIKCHR_PROCESS_NO_SRC;
  u32 fThFlags = TH_INIT_NO_ENCODE
    | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);

  Th_InitTraceLog()/*processes -th-trace flag*/;













  verify_all_options();
  if(g.argc>4){
    usage("?INFILE? ?OUTFILE?");
  }
  if(g.argc>2){
    zInfile = g.argv[2];
  }
  if(g.argc>3){
    zOutfile = g.argv[3];
  }
  blob_read_from_file(&bIn, zInfile, ExtFILE);
  if(fTh1){
    db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
      /* ^^^ needed for certain TH1 functions to work */;;
    pikFlags |= PIKCHR_PROCESS_TH1;
    if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
  }
  if(fWithDiv){
    pikFlags |= PIKCHR_PROCESS_DIV;
  }
  isErr = pikchr_process(blob_str(&bIn), pikFlags,
                         fTh1 ? fThFlags : 0, &bOut);
  if(isErr){
    /*fossil_print("ERROR: raw input:\n%b\n", &bIn);*/
    fossil_fatal("%s ERROR: %b", 1==isErr ? "TH1" : "pikchr",
                 &bOut);
  }else{
    blob_write_to_file(&bOut, zOutfile);
  }
  Th_PrintTraceLog();
  blob_reset(&bIn);
  blob_reset(&bOut);
}







<




|




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

















<
<
<













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
** sense for pikchr scripts.
*/
void pikchr_cmd(void){
  Blob bIn = empty_blob;
  Blob bOut = empty_blob;
  const char * zInfile = "-";
  const char * zOutfile = "-";

  const int fTh1 = find_option("th",0,0)!=0;
  const int fNosvg = find_option("th-nosvg",0,0)!=0;
  int isErr = 0;
  int pikFlags = find_option("svg-src",0,0)!=0
    ? PIKCHR_PROCESS_SRC_HIDDEN : 0;
  u32 fThFlags = TH_INIT_NO_ENCODE
    | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);

  Th_InitTraceLog()/*processes -th-trace flag*/;

  if(find_option("div",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV;
  }else if(find_option("div-indent",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
  }else if(find_option("div-center",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_CENTER;
  }else if(find_option("div-float-left",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_LEFT;
  }else if(find_option("div-float-right",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_RIGHT;
  }

  verify_all_options();
  if(g.argc>4){
    usage("?INFILE? ?OUTFILE?");
  }
  if(g.argc>2){
    zInfile = g.argv[2];
  }
  if(g.argc>3){
    zOutfile = g.argv[3];
  }
  blob_read_from_file(&bIn, zInfile, ExtFILE);
  if(fTh1){
    db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
      /* ^^^ needed for certain TH1 functions to work */;;
    pikFlags |= PIKCHR_PROCESS_TH1;
    if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
  }



  isErr = pikchr_process(blob_str(&bIn), pikFlags,
                         fTh1 ? fThFlags : 0, &bOut);
  if(isErr){
    /*fossil_print("ERROR: raw input:\n%b\n", &bIn);*/
    fossil_fatal("%s ERROR: %b", 1==isErr ? "TH1" : "pikchr",
                 &bOut);
  }else{
    blob_write_to_file(&bOut, zOutfile);
  }
  Th_PrintTraceLog();
  blob_reset(&bIn);
  blob_reset(&bOut);
}