]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Rewrite the table layout in QRF. Easier to maintain now. Still a few
authordrh <>
Sat, 8 Nov 2025 03:20:47 +0000 (03:20 +0000)
committerdrh <>
Sat, 8 Nov 2025 03:20:47 +0000 (03:20 +0000)
shell test failures, but much better after this rewrite.

FossilOrigin-Name: 25838d282c5f7a922988245f373edfe8adbe28e929abce83afcda2b08472e685

ext/qrf/qrf.c
manifest
manifest.uuid
test/qrf01.test

index 3650cfa78ec6aeb0bb68859144439507184778db..efb5af87e0545ececd3cd50aa81c559aaa9aa578 100644 (file)
@@ -532,6 +532,53 @@ static int qrfDisplayLength(const char *zIn){
   return n;
 }
 
+/*
+** Return the display width of the longest line of text
+** in the (possibly) multi-line input string zIn[0..nByte].
+** zIn[] is not necessarily zero-terminated.  Take
+** into account tab characters, zero- and double-width
+** characters, CR and NL, and VT100 escape codes.
+**
+** Write the number of newlines into *pnNL.  So, *pnNL will
+** return 0 if everything fits on one line, or positive it
+** it will need to be split.
+*/
+static int qrfDisplayWidth(const char *zIn, sqlite3_int64 nByte, int *pnNL){
+  const unsigned char *z = (const unsigned char*)zIn;
+  const unsigned char *zEnd = &z[nByte];
+  int mx = 0;
+  int n = 0;
+  int nNL = 0;
+  while( z<zEnd ){
+    if( z[0]<' ' ){
+      int k;
+      if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){
+        z += k;
+      }else{
+        if( z[0]=='\t' ){
+          n = (n+8)&~7;
+        }else if( z[0]=='\n' || z[0]=='\r' ){
+          nNL++;
+          if( n>mx ) mx = n;
+          n = 0;
+        }
+        z++;
+      }
+    }else if( (0x80&z[0])==0 ){
+      n++;
+      z++;
+    }else{
+      int u = 0;
+      int len = sqlite3_qrf_decode_utf8(z, &u);
+      z += len;
+      n += sqlite3_qrf_wcwidth(u);
+    }
+  }
+  if( mx>n ) n = mx;
+  if( pnNL ) *pnNL = nNL;
+  return n;
+}
+
 /*
 ** Escape the input string if it is needed and in accordance with
 ** eEsc, which is either QRF_ESC_Ascii or QRF_ESC_Symbol.
@@ -883,137 +930,6 @@ static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){
   }
 }
 
-/*
-** z[] is a line of text that is to be displayed the box or table or
-** similar tabular formats.  z[] might contain control characters such
-** as \n, \t, \f, or \r.
-**
-** Compute characters to display on the first line of z[].  Stop at the
-** first \r, \n, or \f.  Expand \t into spaces.  Return a copy (obtained
-** from sqlite3_malloc()) of that first line.  The caller is responsible
-** for eventually freeing that line of text.
-**
-** Write anything to display on the next line into *pzTail.  If this is
-** the last line, write a NULL into *pzTail. (*pzTail is not allocated.)
-*/
-static char *qrfTableCell(
-  Qrf *p,                        /* To access display settings */
-  const unsigned char *z,        /* Input text to be transformed */
-  const unsigned char **pzTail,  /* OUT: Tail of the input for next line */
-  int mxWidth,                   /* Max width.  0 means no limit */
-  int bWordWrap                  /* If true, avoid breaking mid-word */
-){
-  int i;                 /* Input bytes consumed */
-  int j;                 /* Output bytes generated */
-  int k;                 /* Input bytes to be displayed */
-  int n;                 /* Output column number */
-  unsigned char *zOut;   /* Output text */
-
-  if( z==0 ){
-    *pzTail = 0;
-    return 0;
-  }
-  if( mxWidth<0 ) mxWidth = -mxWidth;
-  if( mxWidth==0 ) mxWidth = QRF_MX_WIDTH;
-  i = j = n = 0;
-  while( n<=mxWidth ){
-    unsigned char c = z[i];
-    if( c>=0xc0 ){
-      int u;
-      int len = sqlite3_qrf_decode_utf8(&z[i], &u);
-      i += len;
-      j += len;
-      n += sqlite3_qrf_wcwidth(u);
-      continue;
-    }
-    if( c>=' ' ){
-      n++;
-      i++;
-      j++;
-      continue;
-    }
-    if( c==0 || c=='\n' || (c=='\r' && z[i+1]=='\n') ) break;
-    if( c=='\t' ){
-      do{
-        n++;
-        j++;
-      }while( (n&7)!=0 && n<mxWidth );
-      i++;
-      continue;
-    }
-    if( c==0x1b && (k = qrfIsVt100(&z[i]))>0 ){
-      i += k;
-      j += k;
-    }else{
-      n++;
-      j += 3;
-      i++;
-    }
-  }
-  if( n>mxWidth && bWordWrap  ){
-    /* Perhaps try to back up to a better place to break the line */
-    for(k=i; k>i/2; k--){
-      if( isspace(z[k-1]) ) break;
-    }
-    if( k<=i/2 ){
-      for(k=i; k>i/2; k--){
-        if( isalnum(z[k-1])!=isalnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
-      }
-    }
-    if( k<=i/2 ){
-      k = i;
-    }else{
-      i = k;
-      while( z[i]==' ' ) i++;
-    }
-  }else{
-    k = i;
-  }
-  if( n>mxWidth && z[i]>=' ' ){
-   *pzTail = &z[i];
-  }else if( z[i]=='\r' && z[i+1]=='\n' ){
-    *pzTail = z[i+2] ? &z[i+2] : 0;
-  }else if( z[i]==0 || z[i+1]==0 ){
-    *pzTail = 0;
-  }else{
-    *pzTail = &z[i+1];
-  }
-  if( n>=mxWidth && k>0 && z[k-1]==' ' ) k--;
-  zOut = sqlite3_malloc64( j+1 );
-  if( zOut==0 ){
-    qrfOom(p);
-    return 0;
-  }
-  i = j = n = 0;
-  while( i<k ){
-    unsigned char c = z[i];
-    if( c>=0xc0 ){
-      int u;
-      int len = sqlite3_qrf_decode_utf8(&z[i], &u);
-      do{ zOut[j++] = z[i++]; }while( (--len)>0 );
-      n += sqlite3_qrf_wcwidth(u);
-      continue;
-    }
-    if( c>=' ' || c=='\033' ){
-      n++;
-      zOut[j++] = z[i++];
-      continue;
-    }
-    if( c==0 ) break;
-    if( z[i]=='\t' ){
-      do{
-        n++;
-        zOut[j++] = ' ';
-      }while( (n&7)!=0 && n<mxWidth );
-      i++;
-      continue;
-    }
-    i++;
-  }
-  zOut[j] = 0;
-  return (char*)zOut;
-}
-
 /*
 ** Store string zUtf to pOut as w characters.  If w is negative,
 ** then right-justify the text.  W is the width in display characters, not
@@ -1065,25 +981,175 @@ static void qrfWidthPrint(Qrf *p, sqlite3_str *pOut, int w, const char *zUtf){
   }
 }
 
+/*
+** Data for columnar layout, collected into a single object so
+** that it can be more easily passed into subroutines.
+*/
+typedef struct qrfColData qrfColData;
+struct qrfColData {
+  Qrf *p;                  /* The QRF instance */
+  int nCol;                /* Number of columns in the table */
+  unsigned char bMultiRow; /* One or more cells will span multiple lines */
+  sqlite3_int64 nRow;      /* Number of rows */
+  sqlite3_int64 nAlloc;    /* Number of cells allocated */
+  sqlite3_int64 n;         /* Number of cells.  nCol*nRow */
+  char **azThis;           /* Cache of pointers to current row */
+  char **az;               /* Content of all cells */
+  int *aiWth;              /* Width of each cell */
+  int *aiCol;              /* Width of each column */
+};
+
+/*
+** Free all the memory allocates in the qrfColData object
+*/
+static void qrfColDataFree(qrfColData *p){
+  sqlite3_int64 i;
+  for(i=0; i<p->n; i++) sqlite3_free(p->az[i]);
+  sqlite3_free(p->az);
+  sqlite3_free(p->aiWth);
+  sqlite3_free(p->azThis);
+  memset(p, 0, sizeof(*p));
+}
+
+/*
+** Allocate space for more cells in the qrfColData object.
+** Return non-zero if a memory allocation fails.
+*/
+static int qrfColDataEnlarge(qrfColData *p){
+  char **azData;
+  int *aiWth;
+  p->nAlloc = 2*p->nAlloc + 10*p->nCol;
+  azData = sqlite3_realloc64(p->az, p->nAlloc*sizeof(char*));
+  if( azData==0 ){
+    qrfOom(p->p);
+    qrfColDataFree(p);
+    return 1;
+  }
+  p->az = azData;
+  aiWth = sqlite3_realloc64(p->aiWth, p->nAlloc*sizeof(int));
+  if( aiWth==0 ){
+    qrfOom(p->p);
+    qrfColDataFree(p);
+    return 1;
+  }
+  p->aiWth = aiWth;
+  return 0;
+}
+
+/*
+** (*pz)[] is a line of text that is to be displayed the box or table or
+** similar tabular formats.  z[] contain newlines or might be too wide
+** to fit in the columns so will need to be split into multiple line.
+**
+** This routine determines:
+**
+**    *  How many bytes of z[] should be shown on the current line.
+**    *  How many character positions those bytes will cover.
+**    *  The byte offset to the start of the next line.
+*/
+static void qrfWrapLine(
+  const char *zIn,   /* Input text to be displayed */
+  int w,             /* Column width in characters (not bytes) */
+  int bWrap,         /* True if we should do word-wrapping */
+  int *pnThis,       /* OUT: How many bytes of z[] for the current line */
+  int *pnWide,       /* OUT: How wide is the text of this line */
+  int *piNext        /* OUT: Offset into z[] to start of the next line */
+){
+  int i;                 /* Input bytes consumed */
+  int k;                 /* Bytes in a VT100 code */
+  int n;                 /* Output column number */
+  const unsigned char *z = (const unsigned char*)zIn;
+  unsigned char c;
+
+  if( zIn[0]==0 ){
+    *pnThis = 0;
+    *pnWide = 0;
+    *piNext = 0;
+    return;
+  }
+  n = 0;
+  for(i=0; n<w; i++){
+    c = zIn[i];
+    if( c>=0xc0 ){
+      int u;
+      int len = sqlite3_qrf_decode_utf8(&z[i], &u);
+      int wcw = sqlite3_qrf_wcwidth(u);
+      if( wcw+n>w ) break;
+      i += len-1;
+      n += wcw;
+      continue;
+    }
+    if( c>=' ' ){
+      n++;
+      continue;
+    }
+    if( c==0 || c=='\n' ) break;
+    if( c=='\r' && zIn[i+1]=='\n' ){ c = zIn[++i]; break; }
+    if( c=='\t' ){
+      int wcw = 8 - (n&7);
+      if( n+wcw>w ) break;
+      n += wcw;
+      continue;
+    }
+    if( c==0x1b && (k = qrfIsVt100(&z[i]))>0 ){
+      i += k-1;
+    }
+  }
+  if( c==0 ){
+    *pnThis = i;
+    *pnWide = n;
+    *piNext = i;
+    return;
+  }
+  if( c=='\n' ){
+    *pnThis = i;
+    *pnWide = n;
+    *piNext = i+1;
+    return;
+  }
+
+  /* If we get this far, that means the current line will end at some
+  ** point that is neither a "\n" or a 0x00.  Figure out where that
+  ** split should occur
+  */
+  if( bWrap && z[i]!=0 && !isspace(z[i]) && isalnum(c)==isalnum(z[i]) ){
+    /* Perhaps try to back up to a better place to break the line */
+    for(k=i-1; k>=i/2; k--){
+      if( isspace(z[k]) ) break;
+    }
+    if( k<i/2 ){
+      for(k=i; k>i/2; k--){
+        if( isalnum(z[k-1])!=isalnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
+      }
+    }
+    if( k>=i/2 ){
+      i = k;
+      n = qrfDisplayWidth((const char*)z, k, 0);
+    }
+  }
+  *pnThis = i;
+  *pnWide = n;
+  while( zIn[i]==' ' || zIn[i]=='\t' || zIn[i]=='\r' ){ i++; }
+  *piNext = i;
+}
+
 /*
 ** Print a markdown or table-style row separator using ascii-art
 */
-static void qrfRowSeparator(Qrf *p, const char *zSep){
+static void qrfRowSeparator(sqlite3_str *pOut, qrfColData *p, char cSep){
   int i;
   if( p->nCol>0 ){
-    int nSep = (int)(strlen(zSep)&0x3fffffff);
-    sqlite3_str_append(p->pOut, zSep, nSep);
-    sqlite3_str_appendchar(p->pOut, p->actualWidth[0]+2, '-');
+    sqlite3_str_append(pOut, &cSep, 1);
+    sqlite3_str_appendchar(pOut, p->aiCol[0]+2, '-');
     for(i=1; i<p->nCol; i++){
-      sqlite3_str_append(p->pOut, zSep, nSep);
-      sqlite3_str_appendchar(p->pOut, p->actualWidth[i]+2, '-');
+      sqlite3_str_append(pOut, &cSep, 1);
+      sqlite3_str_appendchar(pOut, p->aiCol[i]+2, '-');
     }
-    sqlite3_str_append(p->pOut, zSep, nSep);
+    sqlite3_str_append(pOut, &cSep, 1);
   }
-  sqlite3_str_append(p->pOut, "\n", 1);
+  sqlite3_str_append(pOut, "\n", 1);
 }
 
-
 /*
 ** UTF8 box-drawing characters.  Imagine box lines like this:
 **
@@ -1129,301 +1195,261 @@ static void qrfBoxLine(sqlite3_str *pOut, int N){
 ** Draw a horizontal separator for a QRF_STYLE_Box table.
 */
 static void qrfBoxSeparator(
-  Qrf *p,
+  sqlite3_str *pOut,
+  qrfColData *p,
   const char *zSep1,
   const char *zSep2,
   const char *zSep3
 ){
   int i;
   if( p->nCol>0 ){
-    sqlite3_str_appendall(p->pOut, zSep1);
-    qrfBoxLine(p->pOut, p->actualWidth[0]+2);
+    sqlite3_str_appendall(pOut, zSep1);
+    qrfBoxLine(pOut, p->aiCol[0]+2);
     for(i=1; i<p->nCol; i++){
-      sqlite3_str_appendall(p->pOut, zSep2);
-      qrfBoxLine(p->pOut, p->actualWidth[i]+2);
+      sqlite3_str_appendall(pOut, zSep2);
+      qrfBoxLine(pOut, p->aiCol[i]+2);
     }
-    sqlite3_str_appendall(p->pOut, zSep3);
+    sqlite3_str_appendall(pOut, zSep3);
   }
-  sqlite3_str_append(p->pOut, "\n", 1);
+  sqlite3_str_append(pOut, "\n", 1);
 }
 
-
 /*
 ** Columnar modes require that the entire query be evaluated first, with
 ** results written into memory, so that we can compute appropriate column
 ** widths.
 */
 static void qrfColumnar(Qrf *p){
-  sqlite3_int64 nRow = 0;        /* Rows displayed.  nRow>=p->nRow */
-  char **azData = 0;             /* Text for all cells */
-  int *aiDspyWth = 0;            /* Display width of each cell */
-  sqlite3_int64 nAlloc = 0;      /* Slots allocated for azData[], aiDspyWth[] */
-  char *abRowDiv = 0;            /* Row is a continuation or is complete */
-  const unsigned char *uz;
-  const char *z;
-  sqlite3_str **aCol = 0;        /* Temporary storage for column data */
-  sqlite3_int64 i, j, k, nData;
-  int rc, nTotal, w, n;
+  sqlite3_int64 i, j;
   const char *colSep = 0;
   const char *rowSep = 0;
-  const unsigned char **azNextLine = 0;
-  int bNextLine = 0;
-  int bMultiLineRowExists = 0;
-  int bw = p->spec.bWordWrap==QRF_SW_On;
+  const char *rowStart = 0;
+  int szColSep, szRowSep, szRowStart;
+  int rc;
   int nColumn = p->nCol;
+  int bWW;
+  int bCenter;
+  sqlite3_str *pStr;
+  qrfColData data;
 
   rc = sqlite3_step(p->pStmt);
   if( rc!=SQLITE_ROW || nColumn==0 ){
     return;   /* No output */
   }
 
-  /* Initialize data structures */
-  nAlloc = nColumn*4;
-  azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
-  if( azData==0 ){ qrfOom(p); goto qrf_column_end; }
-  aiDspyWth = sqlite3_malloc64( nAlloc*sizeof(int) );
-  if( aiDspyWth==0 ){ qrfOom(p); goto qrf_column_end; }
-  azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
-  if( azNextLine==0 ){ qrfOom(p); goto qrf_column_end; }
-  memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
-  aCol = sqlite3_malloc64( nColumn*sizeof(aCol[0]) );
-  if( aCol==0 ){ qrfOom(p); goto qrf_column_end; }
-  for(i=0; i<nColumn; i++){
-    aCol[i] = sqlite3_str_new(p->db);
-    if( aCol[i]==0 ){ qrfOom(p); goto qrf_column_end; }
-  }
-  abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
-  if( abRowDiv==0 ){ qrfOom(p); goto qrf_column_end; }
-  p->actualWidth = sqlite3_malloc64( sizeof(int)*nColumn );
-  if( p->actualWidth==0 ){ qrfOom(p); goto qrf_column_end; }
-  for(i=0; i<p->spec.nWidth && i<nColumn; i++){
-    w = i<p->spec.nWidth ? p->spec.aWidth[i] : 0;
-    if( w<0 ){
-      if( w==QRF_MINUS_ZERO ){ w = 0; }
-      else{ w = -w; }
-    }
-    p->actualWidth[i] = w;
+  /* Initialize the data container */
+  memset(&data, 0, sizeof(data));
+  data.nCol = p->nCol;
+  data.azThis = sqlite3_malloc64( nColumn*(sizeof(char*) + sizeof(int)) );
+  if( data.azThis==0 ){
+    qrfOom(p);
+    return;
   }
-  while( i<nColumn ){
-    p->actualWidth[i++] = 0;
+  data.aiCol = (int*)&data.azThis[nColumn];
+  qrfColDataEnlarge(&data);
+  if( p->iErr ){
+    qrfColDataFree(&data);
+    return;
   }
 
-  /* Capture the column names as the first row of data */
-  if( p->spec.bColumnNames ){
-    int saved_eText = p->spec.eText;
+  /* Load the column header names and all cell content into data */
+  if( p->spec.bColumnNames==QRF_SW_On ){
+    unsigned char saved_eText = p->spec.eText;
     p->spec.eText = QRF_TEXT_Off;
     for(i=0; i<nColumn; i++){
-      const unsigned char *zNotUsed;
-      int wx = p->actualWidth[i];
-      if( wx==0 ){
-        wx = p->spec.mxColWidth;
-      }
-      if( wx<0 ) wx = -wx;
-      uz = (const unsigned char*)sqlite3_column_name(p->pStmt,i);
-      if( uz==0 ) uz = (unsigned char*)"";
-      qrfEncodeText(p, aCol[i], (const char*)uz);
-      uz = (unsigned char*)sqlite3_str_value(aCol[i]);
-      azData[i] = qrfTableCell(p, uz, &zNotUsed, wx, bw);
-      if( p->iErr ) goto qrf_column_end;
-      aiDspyWth[i] = qrfDisplayLength(azData[i]);
-      if( aiDspyWth[i]>p->actualWidth[i] ){
-        p->actualWidth[i] = aiDspyWth[i];
-      }
-      sqlite3_str_reset(aCol[i]);
+      const char *z = (const char*)sqlite3_column_name(p->pStmt,i);
+      int nNL = 0;
+      int n;
+      pStr = sqlite3_str_new(p->db);
+      qrfEncodeText(p, pStr, z ? z : "");
+      n = sqlite3_str_length(pStr);
+      z = data.az[data.n] = sqlite3_str_finish(pStr);
+      data.aiWth[data.n] = qrfDisplayWidth(z, n, &nNL);
+      data.n++;
+      if( nNL ) data.bMultiRow = 1;
     }
     p->spec.eText = saved_eText;
+    p->nRow++;
+    bCenter = 1;
+  }else{
+    bCenter = 0;
   }
-
-  /* Capture column content for all rows */
-  k = nColumn;
   do{
+    if( data.n+nColumn > data.nAlloc ){
+      qrfColDataEnlarge(&data);
+      if( p->iErr ) return;
+    }
+    for(i=0; i<nColumn; i++){
+      char *z;
+      int nNL = 0;
+      int n;
+      pStr = sqlite3_str_new(p->db);
+      qrfRenderValue(p, pStr, i);
+      n = sqlite3_str_length(pStr);
+      z = data.az[data.n] = sqlite3_str_finish(pStr);
+      data.aiWth[data.n] = qrfDisplayWidth(z, n, &nNL);
+      data.n++;
+      if( nNL ) data.bMultiRow = 1;
+    }
     p->nRow++;
-    do{
-      int useNextLine = bNextLine;
-      bNextLine = 0;
-      if( (nRow+2)*nColumn >= nAlloc ){
-        char **azNewData;
-        char *abNewDiv;
-        int *aiNewWidth;
-        
-        nAlloc *= 2;
-        azNewData = sqlite3_realloc64(azData, nAlloc*sizeof(char*));
-        if( azNewData==0 ){ qrfOom(p); goto qrf_column_end; }
-        azData = azNewData;
-        abNewDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn);
-        if( abNewDiv==0 ){ qrfOom(p); goto qrf_column_end; }
-        abRowDiv = abNewDiv;
-        aiNewWidth = sqlite3_realloc64(aiDspyWth, nAlloc*sizeof(int));
-        if( aiNewWidth==0 ){ qrfOom(p); goto qrf_column_end; }
-        aiDspyWth = aiNewWidth;
+  }while( sqlite3_step(p->pStmt)==SQLITE_ROW && p->iErr==SQLITE_OK );
+  if( p->iErr ){
+    qrfColDataFree(&data);
+    return;
+  }
+
+  /* Compute the width of every column */
+  for(i=0; i<nColumn; i++){
+    int w = 0;
+    if( i<p->spec.nWidth ){
+      w = p->spec.aWidth[i];
+      if( w==QRF_MINUS_ZERO ){
+        w = 0;
+      }else if( w<0 ){
+        w = -w;
       }
-      abRowDiv[nRow] = 1;
-      nRow++;
-      for(i=0; i<nColumn; i++){
-        int wx = i<p->spec.nWidth ? p->spec.aWidth[i] : 0;
-        if( wx==0 || wx==QRF_MINUS_ZERO ){
-          wx = p->spec.mxColWidth;
-        }
-        if( wx<0 ) wx = -wx;
-        if( useNextLine ){
-          uz = azNextLine[i];
-          if( uz==0 ) uz = (unsigned char*)"";
-        }else{
-          sqlite3_str_reset(aCol[i]);
-          qrfRenderValue(p, aCol[i], i);
-          uz = (unsigned char*)sqlite3_str_value(aCol[i]);
-          if( uz==0 ) uz = (unsigned char*)"";
-        }
-        azData[k] = qrfTableCell(p, uz, &azNextLine[i], wx, bw);
-        if( p->iErr ) goto qrf_column_end;
-        aiDspyWth[k] = qrfDisplayLength(azData[k]);
-        if( aiDspyWth[k]>p->actualWidth[i] ){
-          p->actualWidth[i] = aiDspyWth[k];
+    }
+    if( w==0 ){
+      for(j=i; j<data.n; j+=nColumn){
+        if( data.aiWth[j] > w ){
+          w = data.aiWth[j];
+          if( p->spec.mxColWidth>0 && w>p->spec.mxColWidth ){
+            w = p->spec.mxColWidth;
+            data.bMultiRow = 1;
+            break;
+          }
         }
-        k++;
-        if( azNextLine[i] ){
-          bNextLine = 1;
-          abRowDiv[nRow-1] = 0;
-          bMultiLineRowExists = 1;
+      }
+    }else if( data.bMultiRow==0 ){
+      for(j=i; j<data.n; j+=nColumn){
+        if( data.aiWth[j] > w ){
+          data.bMultiRow = 1;
+          break;
         }
       }
-    }while( bNextLine );
-  }while( sqlite3_step(p->pStmt)==SQLITE_ROW );
-  if( sqlite3_is_interrupted(p->db) ) goto qrf_column_end;
+    }
+    data.aiCol[i] = w;
+  }
 
-  /* Generate the column titles */
+  /* TBD: Narrow columns so that the total is less the p->spec.mxTotalWidth */
+
+  /* Draw the line across the top of the table */
   switch( p->spec.eStyle ){
-    case QRF_STYLE_Column: {
-      colSep = "  ";
-      rowSep = "\n";
-      if( p->spec.bColumnNames==QRF_SW_On ){
-        for(i=0; i<nColumn; i++){
-          w = p->actualWidth[i];
-          if( i<p->spec.nWidth && p->spec.aWidth[i]<0 ) w = -w;
-          qrfWidthPrint(p, p->pOut, w, azData[i]);
-          sqlite3_str_appendall(p->pOut, i==nColumn-1?"\n":"  ");
-        }
-        for(i=0; i<nColumn; i++){
-          sqlite3_str_appendchar(p->pOut, p->actualWidth[i], '-');
-          sqlite3_str_appendall(p->pOut, i==nColumn-1?"\n":"  ");
-        }
-      }
+    case QRF_STYLE_Box:
+      rowStart = BOX_13 " ";
+      colSep = " " BOX_13 " ";
+      rowSep = " " BOX_13 "\n";
+      qrfBoxSeparator(p->pOut, &data, BOX_23, BOX_234, BOX_34);
       break;
-    }
-    case QRF_STYLE_Table: {
+    case QRF_STYLE_Table:
+      rowStart = "| ";
       colSep = " | ";
       rowSep = " |\n";
-      if( p->spec.bColumnNames==QRF_SW_On ){
-        qrfRowSeparator(p, "+");
-        sqlite3_str_append(p->pOut, "| ", 2);
-        for(i=0; i<nColumn; i++){
-          w = p->actualWidth[i];
-          n = aiDspyWth[i];
-          sqlite3_str_appendchar(p->pOut, (w-n)/2, ' ');
-          sqlite3_str_appendall(p->pOut, azData[i]);
-          sqlite3_str_appendchar(p->pOut, (w-n+1)/2, ' ');
-          sqlite3_str_append(p->pOut, i==nColumn-1?" |\n":" | ", 3);
-        }
-      }
-      qrfRowSeparator(p, "+");
+      qrfRowSeparator(p->pOut, &data, '+');
       break;
-    }
-    case QRF_STYLE_Markdown: {
+    case QRF_STYLE_Column:
+      rowStart = "";
+      colSep = "  ";
+      rowSep = "\n";
+      break;
+    case QRF_STYLE_Markdown:
+      rowStart = "| ";
       colSep = " | ";
       rowSep = " |\n";
-      if( p->spec.bColumnNames==QRF_SW_On ){
-        sqlite3_str_append(p->pOut, "| ", 2);
-        for(i=0; i<nColumn; i++){
-          w = p->actualWidth[i];
-          n = aiDspyWth[i];
-          sqlite3_str_appendchar(p->pOut, (w-n)/2, ' ');
-          sqlite3_str_appendall(p->pOut, azData[i]);
-          sqlite3_str_appendchar(p->pOut, (w-n+1)/2, ' ');
-          sqlite3_str_append(p->pOut, i==nColumn-1 ? " |\n" : " | ", 3);
-        }
-        qrfRowSeparator(p, "|");
-      }
       break;
-    }
-    case QRF_STYLE_Box: {
-      colSep = " " BOX_13 " ";
-      rowSep = " " BOX_13 "\n";
-      qrfBoxSeparator(p, BOX_23, BOX_234, BOX_34);
-      if( p->spec.bColumnNames==QRF_SW_On ){
-        sqlite3_str_appendall(p->pOut, BOX_13 " ");
-        for(i=0; i<nColumn; i++){
-          w = p->actualWidth[i];
-          n = aiDspyWth[i];
-          sqlite3_str_appendchar(p->pOut, (w-n)/2, ' ');
-          sqlite3_str_appendall(p->pOut, azData[i]);
-          sqlite3_str_appendchar(p->pOut, (w-n+1)/2, ' ');
-          sqlite3_str_appendall(p->pOut,
-                          i==nColumn-1 ? (" "BOX_13"\n"):(" "BOX_13" "));
-        }
-        qrfBoxSeparator(p, BOX_123, BOX_1234, BOX_134);
-      }
-      break;
-    }
   }
+  szRowStart = (int)strlen(rowStart);
+  szRowSep = (int)strlen(rowSep);
+  szColSep = (int)strlen(colSep);
 
-  /* Render the body of the table */
-  nTotal = nColumn*(nRow+1);
-  for(i=nColumn, j=0; i<nTotal; i++, j++){
-    if( j==0 && p->spec.eStyle!=QRF_STYLE_Column ){
-      sqlite3_str_appendall(p->pOut,
-             p->spec.eStyle==QRF_STYLE_Box ? BOX_13" " : "| ");
-    }
-    z = azData[i];
-    if( z==0 ) z = "";
-    w = p->actualWidth[j];
-    if( j<p->spec.nWidth && p->spec.aWidth[j]<0 ) w = -w;
-    qrfWidthPrint(p, p->pOut, w, z);
-    if( j==nColumn-1 ){
-      sqlite3_str_appendall(p->pOut, rowSep);
-      if( bMultiLineRowExists && abRowDiv[i/nColumn-1] && i+1<nTotal ){
-        switch( p->spec.eStyle ){
-          case QRF_STYLE_Table:
-            qrfRowSeparator(p, "+");
-            break;
-          case QRF_STYLE_Box:
-            qrfBoxSeparator(p, BOX_123, BOX_1234, BOX_134);
-            break;
-          case QRF_STYLE_Column:
+  bWW = (p->spec.bWordWrap==QRF_SW_On && data.bMultiRow);
+  for(i=0; i<data.n; i+=nColumn){
+    int bMore;
+    for(j=0; j<nColumn; j++){ data.azThis[j] = data.az[i+j]; }
+    do{
+      sqlite3_str_append(p->pOut, rowStart, szRowStart);
+      bMore = 0;
+      for(j=0; j<nColumn; j++){
+        int nThis = 0;
+        int nWide = 0;
+        int iNext = 0;
+        int nWS;
+        qrfWrapLine(data.azThis[j], data.aiCol[j], bWW, &nThis, &nWide, &iNext);
+        nWS = data.aiCol[j] - nWide;
+        if( bCenter ){
+          /* Center the text */
+          sqlite3_str_appendchar(p->pOut, nWS/2, ' ');
+          sqlite3_str_append(p->pOut, data.azThis[j], nThis);
+          sqlite3_str_appendchar(p->pOut, nWS - nWS/2, ' ');
+        }else if( j<p->spec.nWidth && p->spec.aWidth[j]<0 ){
+          /* Right justify the text */
+          sqlite3_str_appendchar(p->pOut, nWS, ' ');
+          sqlite3_str_append(p->pOut, data.azThis[j], nThis);
+        }else{
+          /* Left justify the next */
+          sqlite3_str_append(p->pOut, data.azThis[j], nThis);
+          sqlite3_str_appendchar(p->pOut, nWS, ' ');
+        }
+        data.azThis[j] += iNext;
+        if( data.azThis[j][0]!=0 ) bMore = 1;
+        if( j<nColumn-1 ){
+          sqlite3_str_append(p->pOut, colSep, szColSep);
+        }else{
+          sqlite3_str_append(p->pOut, rowSep, szRowSep);
+        }
+      }
+    }while( bMore );
+    bCenter = 0;
+    if( i+nColumn<data.n ){
+      switch( p->spec.eStyle ){
+        case QRF_STYLE_Table: {
+          if( (i==0 && p->spec.bColumnNames==QRF_SW_On) || data.bMultiRow ){
+            qrfRowSeparator(p->pOut, &data, '+');
+          }
+          break;
+        }
+        case QRF_STYLE_Box: {
+          if( (i==0 && p->spec.bColumnNames==QRF_SW_On) || data.bMultiRow ){
+            qrfBoxSeparator(p->pOut, &data, BOX_123, BOX_1234, BOX_134);
+          }
+          break;
+        }
+        case QRF_STYLE_Markdown: {
+          if( i==0 && p->spec.bColumnNames==QRF_SW_On ){
+            qrfRowSeparator(p->pOut, &data, '|');
+          }
+          break;
+        }
+        case QRF_STYLE_Column: {
+          if( i==0 && p->spec.bColumnNames==QRF_SW_On ){
+            for(j=0; j<nColumn; j++){
+              sqlite3_str_appendchar(p->pOut, data.aiCol[j], '-');
+              if( j<nColumn-1 ){
+                sqlite3_str_append(p->pOut, colSep, szColSep);
+              }else{
+                sqlite3_str_append(p->pOut, rowSep, szRowSep);
+              }
+            }
+          }else if( data.bMultiRow ){
             sqlite3_str_append(p->pOut, "\n", 1);
-            break;
+          }
+          break;
         }
       }
-      j = -1;
-      qrfWrite(p);
-      if( sqlite3_is_interrupted(p->db) ) break;
-    }else{
-      sqlite3_str_appendall(p->pOut, colSep);
     }
   }
-  if( p->spec.eStyle==QRF_STYLE_Table ){
-    qrfRowSeparator(p, "+");
-  }else if( p->spec.eStyle==QRF_STYLE_Box ){
-    qrfBoxSeparator(p, BOX_12, BOX_124, BOX_14);
-  }
-  qrfWrite(p);
 
-qrf_column_end:
-  /* Free allocated memory */
-  if( aCol ){
-    for(i=0; i<nColumn; i++){
-      sqlite3_free(sqlite3_str_finish(aCol[i]));
-    }
-    sqlite3_free(aCol);
-  }
-  sqlite3_free(azNextLine);
-  sqlite3_free(aiDspyWth);
-  nData = (nRow+1)*nColumn;
-  for(i=0; i<nData; i++){
-    sqlite3_free(azData[i]);
+  /* Draw the line across the bottom of the table */
+  switch( p->spec.eStyle ){
+    case QRF_STYLE_Box:
+      qrfBoxSeparator(p->pOut, &data, BOX_12, BOX_124, BOX_14);
+      break;
+    case QRF_STYLE_Table:
+      qrfRowSeparator(p->pOut, &data, '+');
+      break;
   }
-  sqlite3_free(azData);
-  sqlite3_free(abRowDiv);
-  
+
+  qrfColDataFree(&data);
   return;
 }
 
index d0078130b04bca44aa0c03a42a855eee7755ae93..c4a6e4d043a20467229ea11e09e1c9ac363a5a13 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\ssome\sproblems.\s\sBut\sthere\sis\sa\sneed\sto\sdo\smajor\ssurgery\son\sthe\ntable\slayout\salgorithm,\sso\sit\sseems\sgood\sto\ssnapshot\sbefore\sproceeding.
-D 2025-11-07T19:20:49.895
+C Rewrite\sthe\stable\slayout\sin\sQRF.\s\sEasier\sto\smaintain\snow.\s\sStill\sa\sfew\nshell\stest\sfailures,\sbut\smuch\sbetter\safter\sthis\srewrite.
+D 2025-11-08T03:20:47.075
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -417,7 +417,7 @@ F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6
 F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c
 F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee
 F ext/qrf/README.md 550c99ea5f9db2580ec66c12260b28feddc1c8a9b22396bb01bd9c3faaf720b6
-F ext/qrf/qrf.c ac6efea5dcd21c30a6182d772b517245a2b8a11a5a37a2da4cab31e9e1464652
+F ext/qrf/qrf.c e187f25116b1e33a1ccba1d25a03e60e7bf4bddfab72de48cb82da623f9fed7f
 F ext/qrf/qrf.h 642c717aa5a88074f17712a59763f80c9d83e356c3c165953c3f9ba648511087
 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
@@ -1506,7 +1506,7 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c
 F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb
 F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
 F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd
-F test/qrf01.test 193a3ad8101975d3e574f0855eb7285928a554420fc354916a199600234e3d62
+F test/qrf01.test 68481b70feab9dee97481daf23e3b63c3822542d4f8bc7ea805db93db3b0feaa
 F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92
 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
 F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
@@ -2173,8 +2173,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P de7fc9afc43d4bd5f5995832cd698633e0e7213c190fa044856c9f82512161f9
-R d7dd914866de963dc2cf233d6a5493f5
+P 9052991a897ad12ac54476d9a370d1ad18937abc3c3fc11efcb678f21cb5ef69
+R bfd19efc8813f884de7c5af24dd2aa88
 U drh
-Z feff0772021bea128322542ff15b0626
+Z a1cd8bbd8fc7f8460e105f73287d0a96
 # Remove this line to create a well-formed Fossil manifest.
index 60a0b4ee73e577fb18c7e6a48f2068e7afeb98fc..df00c694a63de7073ce47ad32183437592f46648 100644 (file)
@@ -1 +1 @@
-9052991a897ad12ac54476d9a370d1ad18937abc3c3fc11efcb678f21cb5ef69
+25838d282c5f7a922988245f373edfe8adbe28e929abce83afcda2b08472e685
index cf970f8844a429626b9f4364a71d87e857378699..49dee2e521634e243e0eb569ad0c6d48370208d4 100644 (file)
@@ -367,17 +367,29 @@ do_test 2.4 {
 └───────┴───────┴────────────────────┘
 }
 do_test 2.5 {
-  set result "\n[db format -widths {5 -5 18} -wordwrap off \
+  set result "\n[db format -widths {5 -5 19} -wordwrap off \
                  {SELECT * FROM t1}]"
 } {
 ┌───────┬───────┬─────────────────────┐
 │   a   │   b   │          c          │
 ├───────┼───────┼─────────────────────┤
 │ 1     │     2 │ The quick fox jumps │
-│       │       │  over the lazy brow │
-│       │       │ n dog.              │
+│       │       │ over the lazy brown │
+│       │       │ dog.                │
 └───────┴───────┴─────────────────────┘
 }
+do_test 2.6 {
+  set result "\n[db format -widths {5 -5 18} -wordwrap off \
+                 {SELECT * FROM t1}]"
+} {
+┌───────┬───────┬────────────────────┐
+│   a   │   b   │         c          │
+├───────┼───────┼────────────────────┤
+│ 1     │     2 │ The quick fox jump │
+│       │       │ s over the lazy br │
+│       │       │ own dog.           │
+└───────┴───────┴────────────────────┘
+}
 
 do_execsql_test 2.10 {
   UPDATE t1 SET c='Η γρήγορη αλεπού πηδάει πάνω από το τεμπέλικο καφέ σκυλί';
@@ -466,7 +478,7 @@ do_test 4.2 {
 └─────────────────┴─────────────────┴────┘
 }
 do_test 4.3 {
-  set result "\n[db format -text off -textjsonb on -maxcolwidth 10 \
+  set result "\n[db format -text off -textjsonb on -maxcolwidth 11 \
               {SELECT a AS json, b AS jsonb, c AS num FROM t1}]"
 } {
 ┌─────────────┬─────────────┬─────┐