Refactor block formatter to split content into lines and render the lines.

Also cache the current font for the whole page.
This commit is contained in:
Michael R Sweet 2024-12-11 20:14:32 -05:00
parent dc65eb8d2f
commit 72e55b5bd1
No known key found for this signature in database
GPG Key ID: BE67C75EC81F3244

View File

@ -20,6 +20,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <math.h>
#ifdef _WIN32 #ifdef _WIN32
# include <io.h> # include <io.h>
#else #else
@ -81,12 +82,28 @@ typedef struct docdata_s // Document formatting data
char *heading; // Current document heading char *heading; // Current document heading
pdfio_stream_t *st; // Current page stream pdfio_stream_t *st; // Current page stream
double y; // Current position on page double y; // Current position on page
docfont_t font; // Current font
double fsize; // Current font size
doccolor_t color; // Current color doccolor_t color; // Current color
pdfio_obj_t *annots; // Annotations object (for links) pdfio_obj_t *annots; // Annotations object (for links)
size_t num_links; // Number of links for this page size_t num_links; // Number of links for this page
doclink_t links[DOCLINK_MAX]; // Links for this page doclink_t links[DOCLINK_MAX]; // Links for this page
} docdata_t; } docdata_t;
typedef struct linefrag_s // Line fragment
{
double x, // X position of item
width, // Width of item
height; // Height of item
size_t imagenum; // Image number
const char *text; // Text string
bool ws; // Whitespace before text?
docfont_t font; // Text font
doccolor_t color; // Text color
} linefrag_t;
#define LINEFRAG_MAX 200 // Maximum number of fragments on a line
// //
// Macros... // Macros...
@ -229,13 +246,16 @@ add_images(docdata_t *dd, // I - Document data
// //
// 'set_color()' - Set the stroke and fill color. // 'set_color()' - Set the stroke and fill color as needed.
// //
static void static void
set_color(docdata_t *dd, // I - Document data set_color(docdata_t *dd, // I - Document data
doccolor_t color) // I - Document color doccolor_t color) // I - Document color
{ {
if (color == dd->color)
return;
switch (color) switch (color)
{ {
case DOCCOLOR_BLACK : case DOCCOLOR_BLACK :
@ -264,6 +284,28 @@ set_color(docdata_t *dd, // I - Document data
} }
//
// 'set_font()' - Set the font typeface and size as needed.
//
static void
set_font(docdata_t *dd, // I - Document data
docfont_t font, // I - Font
double fsize) // I - Font size
{
if (font == dd->font && fabs(fsize - dd->fsize) < 0.1)
return;
if (font == DOCFONT_MAX)
return;
pdfioContentSetTextFont(dd->st, docfont_names[font], fsize);
dd->font = font;
dd->fsize = fsize;
}
// //
// 'new_page()' - Start a new page. // 'new_page()' - Start a new page.
// //
@ -297,11 +339,15 @@ new_page(docdata_t *dd) // I - Document data
pdfioPageDictAddImage(page_dict, temp, dd->images[i].obj); pdfioPageDictAddImage(page_dict, temp, dd->images[i].obj);
} }
dd->st = pdfioFileCreatePage(dd->pdf, page_dict); dd->st = pdfioFileCreatePage(dd->pdf, page_dict);
dd->color = DOCCOLOR_BLACK;
dd->font = DOCFONT_MAX;
dd->fsize = 0.0;
dd->y = dd->art_box.y2;
// Add header/footer text // Add header/footer text
set_color(dd, DOCCOLOR_GRAY); set_color(dd, DOCCOLOR_GRAY);
pdfioContentSetTextFont(dd->st, docfont_names[DOCFONT_REGULAR], SIZE_HEADFOOT); set_font(dd, DOCFONT_REGULAR, SIZE_HEADFOOT);
if (pdfioFileGetNumPages(dd->pdf) > 1 && dd->title) if (pdfioFileGetNumPages(dd->pdf) > 1 && dd->title)
{ {
@ -358,11 +404,81 @@ new_page(docdata_t *dd) // I - Document data
pdfioContentTextShow(dd->st, UNICODE_VALUE, dd->heading); pdfioContentTextShow(dd->st, UNICODE_VALUE, dd->heading);
pdfioContentTextEnd(dd->st); pdfioContentTextEnd(dd->st);
} }
}
// The rest of the text will be full black...
set_color(dd, DOCCOLOR_BLACK);
dd->y = dd->art_box.y2; //
// 'render_line()' - Render a line of text/graphics.
//
static void
render_line(docdata_t *dd, // I - Document data
double margin_top, // I - Top margin
double lineheight, // I - Height of line
size_t num_frags, // I - Number of line fragments
linefrag_t *frags) // I - Line fragments
{
size_t i; // Looping var
linefrag_t *frag; // Current line fragment
bool in_text = false; // Are we in a text block?
if (!dd->st)
new_page(dd);
dd->y -= margin_top + lineheight;
if (dd->y < dd->art_box.y1)
{
new_page(dd);
dd->y -= lineheight;
}
fprintf(stderr, "num_frags=%u, y=%g\n", (unsigned)num_frags, dd->y);
for (i = 0, frag = frags; i < num_frags; i ++, frag ++)
{
if (frag->text)
{
// Draw text
fprintf(stderr, " text=\"%s\", font=%d, color=%d, x=%g\n", frag->text, frag->font, frag->color, frag->x);
set_color(dd, frag->color);
set_font(dd, frag->font, frag->height);
if (!in_text)
{
pdfioContentTextBegin(dd->st);
pdfioContentTextMoveTo(dd->st, frag->x, dd->y);
in_text = true;
}
if (frag->ws)
pdfioContentTextShowf(dd->st, UNICODE_VALUE, " %s", frag->text);
else
pdfioContentTextShow(dd->st, UNICODE_VALUE, frag->text);
}
else
{
// Draw image
char imagename[32]; // Current image name
fprintf(stderr, " imagenum=%u, x=%g, width=%g, height=%g\n", (unsigned)frag->imagenum, frag->x, frag->width, frag->height);
if (in_text)
{
pdfioContentTextEnd(dd->st);
in_text = false;
}
snprintf(imagename, sizeof(imagename), "I%u", (unsigned)frag->imagenum);
pdfioContentDrawImage(dd->st, imagename, frag->x, dd->y, frag->width, frag->height);
}
}
if (in_text)
pdfioContentTextEnd(dd->st);
} }
@ -373,8 +489,8 @@ new_page(docdata_t *dd) // I - Document data
static void static void
format_block(docdata_t *dd, // I - Document data format_block(docdata_t *dd, // I - Document data
mmd_t *block, // I - Block to format mmd_t *block, // I - Block to format
docfont_t fontface, // I - Default font docfont_t deffont, // I - Default font
double fontsize, // I - Size of font double fsize, // I - Size of font
double left, // I - Left margin double left, // I - Left margin
double right, // I - Right margin double right, // I - Right margin
const char *leader) // I - Leader text on the first line const char *leader) // I - Leader text on the first line
@ -382,253 +498,165 @@ format_block(docdata_t *dd, // I - Document data
mmd_type_t blocktype; // Block type mmd_type_t blocktype; // Block type
mmd_t *current, // Current node mmd_t *current, // Current node
*next; // Next node *next; // Next node
mmd_type_t curtype; // Current node type size_t num_frags; // Number of line fragments
const char *curtext, // Current text linefrag_t frags[LINEFRAG_MAX], // Line fragments
*cururl; // Current URL, if any *frag; // Current fragment
bool curws; // Current whitespace mmd_type_t type; // Current node type
pdfio_obj_t *curimage; // Current image, if any const char *text, // Current text
char curimagename[32]; // Current image name *url; // Current URL, if any
docfont_t curface, // Current font face bool ws; // Current whitespace
prevface; // Previous font face pdfio_obj_t *image; // Current image, if any
double x, y; // Current position size_t imagenum; // Current image number
double width, // Width of current fragment doccolor_t color = DOCCOLOR_BLACK; // Current text color
docfont_t font = deffont; // Current text font
double x, // Current position
width, // Width of current fragment
wswidth, // Width of whitespace
margin_top, // Top margin
height, // Height of current fragment height, // Height of current fragment
lwidth, // Leader width lineheight; // Height of current line
wswidth; // Width of whitespace
doccolor_t color; // Color of text
blocktype = mmdGetType(block); blocktype = mmdGetType(block);
margin_top = fsize * LINE_HEIGHT;
if (!dd->st)
new_page(dd);
if ((y = dd->y - 2.0 * fontsize * LINE_HEIGHT) < dd->art_box.y1)
{
new_page(dd);
y = dd->y - fontsize;
}
if (leader) if (leader)
{ {
// Add leader text on first line... // Add leader text on first line...
pdfioContentSetTextFont(dd->st, docfont_names[prevface = fontface], fontsize); frags[0].width = pdfioContentTextMeasure(dd->fonts[deffont], leader, fsize);
frags[0].height = fsize;
frags[0].x = left - frags[0].width;
frags[0].text = leader;
frags[0].font = deffont;
frags[0].color = DOCCOLOR_BLACK;
lwidth = pdfioContentTextMeasure(dd->fonts[fontface], leader, fontsize); num_frags = 1;
lineheight = fsize * LINE_HEIGHT;
pdfioContentTextBegin(dd->st);
pdfioContentTextMoveTo(dd->st, left - lwidth, y);
pdfioContentTextShow(dd->st, UNICODE_VALUE, leader);
} }
else else
{ {
// No leader text... // No leader text...
prevface = DOCFONT_MAX; num_frags = 0;
lwidth = 0.0; lineheight = 0.0;
} }
frag = frags + num_frags;
// Loop through the block and render lines...
for (current = mmdGetFirstChild(block), x = left; current; current = next) for (current = mmdGetFirstChild(block), x = left; current; current = next)
{ {
// Get information about the current node... // Get information about the current node...
curtype = mmdGetType(current); type = mmdGetType(current);
curtext = mmdGetText(current); text = mmdGetText(current);
curimage = NULL; image = NULL;
curimagename[0] = '\0'; imagenum = 0;
cururl = mmdGetURL(current); url = mmdGetURL(current);
curws = mmdGetWhitespace(current); ws = mmdGetWhitespace(current);
next = mmd_walk_next(block, current); wswidth = 0.0;
next = mmd_walk_next(block, current);
// Process the node... // Process the node...
if (curtype == MMD_TYPE_IMAGE && cururl) if (type == MMD_TYPE_IMAGE && url)
{ {
// Embed an image // Embed an image
size_t i; // Looping var size_t i; // Looping var
for (i = 0; i < dd->num_images; i ++) for (i = 0; i < dd->num_images; i ++)
{ {
if (!strcmp(dd->images[i].url, cururl)) if (!strcmp(dd->images[i].url, url))
{ {
curimage = dd->images[i].obj; image = dd->images[i].obj;
snprintf(curimagename, sizeof(curimagename), "I%u", (unsigned)i); imagenum = i;
break; break;
} }
} }
if (!curimage) if (!image)
continue; continue;
}
else if (!curtext)
{
continue;
}
if (curtype == MMD_TYPE_EMPHASIZED_TEXT)
curface = DOCFONT_ITALIC;
else if (curtype == MMD_TYPE_STRONG_TEXT)
curface = DOCFONT_BOLD;
else if (curtype == MMD_TYPE_CODE_TEXT)
curface = DOCFONT_MONOSPACE;
else
curface = fontface;
if (curtype == MMD_TYPE_CODE_TEXT)
color = DOCCOLOR_RED;
else if (curtype == MMD_TYPE_LINKED_TEXT)
color = DOCCOLOR_BLUE;
else
color = DOCCOLOR_BLACK;
if (curimage)
{
// Image - treat as 100dpi // Image - treat as 100dpi
width = 72.0 * pdfioImageGetWidth(curimage) / 100.0; width = 72.0 * pdfioImageGetWidth(image) / 100.0;
height = 72.0 * pdfioImageGetHeight(curimage) / 100.0; height = 72.0 * pdfioImageGetHeight(image) / 100.0;
text = NULL;
if (width > (right - left)) if (width > (right - left))
{ {
// Too wide, scale to width... // Too wide, scale to width...
width = right - left; width = right - left;
height = width * pdfioImageGetHeight(curimage) / pdfioImageGetWidth(curimage); height = width * pdfioImageGetHeight(image) / pdfioImageGetWidth(image);
} }
else if (height > (dd->art_box.y2 - dd->art_box.y1)) else if (height > (dd->art_box.y2 - dd->art_box.y1))
{ {
// Too tall, scale to height... // Too tall, scale to height...
height = dd->art_box.y2 - dd->art_box.y1; height = dd->art_box.y2 - dd->art_box.y1;
width = height * pdfioImageGetWidth(curimage) / pdfioImageGetHeight(curimage); width = height * pdfioImageGetWidth(image) / pdfioImageGetHeight(image);
}
if (x <= left)
{
y -= height - fontsize * LINE_HEIGHT;
if (prevface != DOCFONT_MAX)
{
pdfioContentTextEnd(dd->st);
prevface = DOCFONT_MAX;
}
if (y < dd->art_box.y1)
{
// New page...
new_page(dd);
x = left;
y = dd->y - height;
}
} }
} }
else if (!text)
{
continue;
}
else else
{ {
// Text fragment... // Text fragment...
width = pdfioContentTextMeasure(dd->fonts[curface], curtext, fontsize); if (type == MMD_TYPE_EMPHASIZED_TEXT)
height = fontsize * LINE_HEIGHT; font = DOCFONT_ITALIC;
else if (type == MMD_TYPE_STRONG_TEXT)
font = DOCFONT_BOLD;
else if (type == MMD_TYPE_CODE_TEXT)
font = DOCFONT_MONOSPACE;
else
font = deffont;
if (type == MMD_TYPE_CODE_TEXT)
color = DOCCOLOR_RED;
else if (type == MMD_TYPE_LINKED_TEXT)
color = DOCCOLOR_BLUE;
else
color = DOCCOLOR_BLACK;
width = pdfioContentTextMeasure(dd->fonts[font], text, fsize);
height = fsize * LINE_HEIGHT;
if (ws)
wswidth = pdfioContentTextMeasure(dd->fonts[font], " ", fsize);
} }
if (curws) // See if this node will fit on the current line...
wswidth = pdfioContentTextMeasure(dd->fonts[curface], " ", fontsize); if ((num_frags > 0 && (x + width + wswidth) >= right) || num_frags == LINEFRAG_MAX)
else {
// No, render this line and start over...
render_line(dd, margin_top, lineheight, num_frags, frags);
num_frags = 0;
frag = frags;
x = left;
lineheight = 0.0;
margin_top = 0.0;
}
// Add the current node to the fragment list
if (num_frags == 0)
wswidth = 0.0; wswidth = 0.0;
if (x > left && (x + width + wswidth) >= right) frag->x = x;
{ frag->width = width + wswidth;
// New line... frag->height = text ? fsize : height;
x = left; frag->imagenum = imagenum;
y -= height; frag->text = text;
frag->ws = ws;
frag->font = font;
frag->color = color;
if (y < dd->art_box.y1) num_frags ++;
{ frag ++;
// New page... x += width + wswidth;
if (prevface != DOCFONT_MAX) if (height > lineheight)
{ lineheight = height;
pdfioContentTextEnd(dd->st);
prevface = DOCFONT_MAX;
}
new_page(dd);
y = dd->y - height;
}
else
{
pdfioContentTextMoveTo(dd->st, lwidth, -fontsize * LINE_HEIGHT);
lwidth = 0.0;
}
}
fprintf(stderr, "curtext=\"%s\", curimage=\"%s\", x=%g, y=%g, width=%g, height=%g\n", curtext, curimagename, x, y, width, height);
if (curimage)
{
// Image
if (prevface != DOCFONT_MAX)
{
pdfioContentTextEnd(dd->st);
prevface = DOCFONT_MAX;
}
pdfioContentDrawImage(dd->st, curimagename, x, y, width, height);
}
else
{
// Text
if (curface != prevface)
{
if (prevface == DOCFONT_MAX)
{
pdfioContentTextBegin(dd->st);
pdfioContentTextMoveTo(dd->st, x, y);
}
pdfioContentSetTextFont(dd->st, docfont_names[prevface = curface], fontsize);
}
if (color != dd->color)
set_color(dd, color);
if (x > left && curws)
{
pdfioContentTextShowf(dd->st, UNICODE_VALUE, " %s", curtext);
x += width + wswidth;
}
else
{
pdfioContentTextShow(dd->st, UNICODE_VALUE, curtext);
x += width;
}
}
if (blocktype == MMD_TYPE_CODE_BLOCK)
{
// Force a new line...
x = left;
y -= fontsize * LINE_HEIGHT;
if (y < dd->art_box.y1)
{
// New page...
if (prevface != DOCFONT_MAX)
{
pdfioContentTextEnd(dd->st);
prevface = DOCFONT_MAX;
}
new_page(dd);
y = dd->y - fontsize * LINE_HEIGHT;
}
else
{
pdfioContentTextMoveTo(dd->st, lwidth, -fontsize * LINE_HEIGHT);
lwidth = 0.0;
}
}
} }
// End the current text block and save out position on the page... if (num_frags > 0)
if (prevface != DOCFONT_MAX) render_line(dd, margin_top, lineheight, num_frags, frags);
pdfioContentTextEnd(dd->st);
dd->y = y;
} }
@ -707,7 +735,29 @@ format_doc(docdata_t *dd, // I - Document data
break; break;
case MMD_TYPE_CODE_BLOCK : case MMD_TYPE_CODE_BLOCK :
format_block(dd, current, DOCFONT_MONOSPACE, SIZE_BODY, left + 36.0, right, /*leader*/NULL); {
mmd_t *code; // Current code block
linefrag_t frag; // Line fragment
double margin_top; // Top margin
frag.x = left + 36.0;
frag.width = 0.0;
frag.height = SIZE_CODEBLOCK;
frag.imagenum = 0;
frag.ws = false;
frag.font = DOCFONT_MONOSPACE;
frag.color = DOCCOLOR_RED;
margin_top = SIZE_CODEBLOCK * LINE_HEIGHT;
for (code = mmdGetFirstChild(current); code; code = mmdGetNextSibling(code))
{
frag.text = mmdGetText(code);
render_line(dd, margin_top, SIZE_CODEBLOCK * LINE_HEIGHT, 1, &frag);
margin_top = 0.0;
}
}
break; break;
} }
} }
@ -743,6 +793,8 @@ main(int argc, // I - Number of command-line arguments
const char *value; // Metadata value const char *value; // Metadata value
setbuf(stderr, NULL);
// Get the markdown file from the command-line... // Get the markdown file from the command-line...
if (argc != 2) if (argc != 2)
{ {