From 2d175fdf70a51091543711d51ea926d95cb6fce0 Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Sat, 14 Dec 2024 10:18:01 -0500 Subject: [PATCH] Drop Apache badge (not needed), rearrange functions in alphabetical order, and reset top margin for list items to 0. --- examples/apache-badge.png | Bin 15926 -> 0 bytes examples/md2pdf.c | 1237 +++++++++++++++++++------------------ 2 files changed, 630 insertions(+), 607 deletions(-) delete mode 100644 examples/apache-badge.png diff --git a/examples/apache-badge.png b/examples/apache-badge.png deleted file mode 100644 index 798b9f8085153e1ca209fc996653745f2330ce7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15926 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 diff --git a/examples/md2pdf.c b/examples/md2pdf.c index 49ca5e4..72d37f7 100644 --- a/examples/md2pdf.c +++ b/examples/md2pdf.c @@ -199,34 +199,116 @@ static const char * const docfont_names[] = // -// 'mmd_walk_next()' - Find the next markdown node. +// Functions... // -static mmd_t * // O - Next node or `NULL` at end -mmd_walk_next(mmd_t *top, // I - Top node - mmd_t *node) // I - Current node +static void add_images(docdata_t *dd, mmd_t *doc); +static pdfio_obj_t *find_image(docdata_t *dd, const char *url, size_t *imagenum); +static void format_block(docdata_t *dd, mmd_t *block, docfont_t deffont, double fsize, double left, double right, const char *leader); +static void format_code(docdata_t *dd, mmd_t *block, double left, double right); +static void format_doc(docdata_t *dd, mmd_t *doc, double left, double right); +static void format_table(docdata_t *dd, mmd_t *table, double left, double right); +static double measure_cell(docdata_t *dd, mmd_t *cell, tablecol_t *col); +static mmd_t *mmd_walk_next(mmd_t *top, mmd_t *node); +static void new_page(docdata_t *dd); +static ssize_t output_cb(void *output_cbdata, const void *buffer, size_t bytes); +static void render_line(docdata_t *dd, double margin_left, double margin_top, double lineheight, size_t num_frags, linefrag_t *frags); +static void render_row(docdata_t *dd, size_t num_cols, tablecol_t *cols, tablerow_t *row); +static void set_color(docdata_t *dd, doccolor_t color); +static void set_font(docdata_t *dd, docfont_t font, double fsize); + + +// +// 'main()' - Convert markdown to PDF. +// + +int // O - Exit status +main(int argc, // I - Number of command-line arguments + char *argv[]) // I - Command-line arguments { - mmd_t *next, // Next node - *parent; // Parent node + docdata_t dd; // Document data + docfont_t fontface; // Current font + mmd_t *doc; // Markdown document + const char *value; // Metadata value - // Figure out the next node under "top"... - if ((next = mmdGetFirstChild(node)) == NULL) + setbuf(stderr, NULL); + + // Get the markdown file from the command-line... + if (argc != 2) { - if ((next = mmdGetNextSibling(node)) == NULL) - { - if ((parent = mmdGetParent(node)) != top) - { - while ((next = mmdGetNextSibling(parent)) == NULL) - { - if ((parent = mmdGetParent(parent)) == top) - break; - } - } - } + fputs("Usage: md2pdf FILENANE.md >FILENAME.pdf\n", stderr); + return (1); } - return (next); + if ((doc = mmdLoad(/*root*/NULL, argv[1])) == NULL) + return (1); + + // Initialize the document data + memset(&dd, 0, sizeof(dd)); + + dd.media_box.x2 = PAGE_WIDTH; + dd.media_box.y2 = PAGE_LENGTH; + + dd.crop_box.x1 = PAGE_LEFT; + dd.crop_box.y1 = PAGE_FOOTER; + dd.crop_box.x2 = PAGE_RIGHT; + dd.crop_box.y2 = PAGE_HEADER; + + dd.art_box.x1 = PAGE_LEFT; + dd.art_box.y1 = PAGE_BOTTOM; + dd.art_box.x2 = PAGE_RIGHT; + dd.art_box.y2 = PAGE_TOP; + + dd.title = mmdGetMetadata(doc, "title"); + + // Output a PDF file to the standard output... +#ifdef _WIN32 + setmode(1, O_BINARY); // Force binary output on Windows +#endif // _WIN32 + + if ((dd.pdf = pdfioFileCreateOutput(output_cb, /*output_cbdata*/NULL, /*version*/NULL, /*media_box*/NULL, /*crop_box*/NULL, /*error_cb*/NULL, /*error_data*/NULL)) == NULL) + return (1); + + if ((value = mmdGetMetadata(doc, "author")) != NULL) + pdfioFileSetAuthor(dd.pdf, value); + + if ((value = mmdGetMetadata(doc, "keywords")) != NULL) + pdfioFileSetKeywords(dd.pdf, value); + + if ((value = mmdGetMetadata(doc, "subject")) != NULL) + pdfioFileSetSubject(dd.pdf, value); + else if ((value = mmdGetMetadata(doc, "copyright")) != NULL) + pdfioFileSetSubject(dd.pdf, value); + + if (dd.title) + pdfioFileSetTitle(dd.pdf, dd.title); + + // Add fonts... + for (fontface = DOCFONT_REGULAR; fontface < DOCFONT_MAX; fontface ++) + { +#if USE_TRUETYPE + if ((dd.fonts[fontface] = pdfioFileCreateFontObjFromFile(dd.pdf, docfont_filenames[fontface], UNICODE_VALUE)) == NULL) + return (1); +#else + if ((dd.fonts[fontface] = pdfioFileCreateFontObjFromBase(dd.pdf, docfont_filenames[fontface])) == NULL) + return (1); +#endif // USE_TRUETYPE + } + + // Add images... + add_images(&dd, doc); + + // Parse the markdown document... + format_doc(&dd, doc, dd.art_box.x1, dd.art_box.x2); + + // Close the PDF and return... + pdfioStreamClose(dd.st); + pdfioFileClose(dd.pdf); + + mmdFree(doc); + + return (0); } @@ -312,268 +394,6 @@ find_image(docdata_t *dd, // I - Document data } -// -// 'set_color()' - Set the stroke and fill color as needed. -// - -static void -set_color(docdata_t *dd, // I - Document data - doccolor_t color) // I - Document color -{ - if (color == dd->color) - return; - - switch (color) - { - case DOCCOLOR_BLACK : - pdfioContentSetFillColorDeviceGray(dd->st, 0.0); - pdfioContentSetStrokeColorDeviceGray(dd->st, 0.0); - break; - case DOCCOLOR_RED : - pdfioContentSetFillColorDeviceRGB(dd->st, 0.6, 0.0, 0.0); - pdfioContentSetStrokeColorDeviceRGB(dd->st, 0.6, 0.0, 0.0); - break; - case DOCCOLOR_GREEN : - pdfioContentSetFillColorDeviceRGB(dd->st, 0.0, 0.6, 0.0); - pdfioContentSetStrokeColorDeviceRGB(dd->st, 0.0, 0.6, 0.0); - break; - case DOCCOLOR_BLUE : - pdfioContentSetFillColorDeviceRGB(dd->st, 0.0, 0.0, 0.8); - pdfioContentSetStrokeColorDeviceRGB(dd->st, 0.0, 0.0, 0.8); - break; - case DOCCOLOR_LTGRAY : - pdfioContentSetFillColorDeviceGray(dd->st, 0.933); - pdfioContentSetStrokeColorDeviceGray(dd->st, 0.933); - break; - case DOCCOLOR_GRAY : - pdfioContentSetFillColorDeviceGray(dd->st, 0.333); - pdfioContentSetStrokeColorDeviceGray(dd->st, 0.333); - break; - } - - dd->color = color; -} - - -// -// '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); - - if (fabs(fsize - dd->fsize) >= 0.1) - pdfioContentSetTextLeading(dd->st, fsize * LINE_HEIGHT); - - dd->font = font; - dd->fsize = fsize; -} - - -// -// 'new_page()' - Start a new page. -// - -static void -new_page(docdata_t *dd) // I - Document data -{ - pdfio_dict_t *page_dict; // Page dictionary - docfont_t fontface; // Current font face - size_t i; // Looping var - char temp[32]; // Temporary string - double width; // Width of fragment - - - // Close the current page... - pdfioStreamClose(dd->st); - - // Prep the new page... - page_dict = pdfioDictCreate(dd->pdf); - - pdfioDictSetRect(page_dict, "MediaBox", &dd->media_box); -// pdfioDictSetRect(page_dict, "CropBox", &dd->crop_box); - pdfioDictSetRect(page_dict, "ArtBox", &dd->art_box); - - for (fontface = DOCFONT_REGULAR; fontface < DOCFONT_MAX; fontface ++) - pdfioPageDictAddFont(page_dict, docfont_names[fontface], dd->fonts[fontface]); - - for (i = 0; i < dd->num_images; i ++) - pdfioPageDictAddImage(page_dict, pdfioStringCreatef(dd->pdf, "I%u", (unsigned)i), dd->images[i].obj); - - 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 - set_color(dd, DOCCOLOR_GRAY); - set_font(dd, DOCFONT_REGULAR, SIZE_HEADFOOT); - - if (pdfioFileGetNumPages(dd->pdf) > 1 && dd->title) - { - // Show title in header... - width = pdfioContentTextMeasure(dd->fonts[DOCFONT_REGULAR], dd->title, SIZE_HEADFOOT); - - pdfioContentTextBegin(dd->st); - pdfioContentTextMoveTo(dd->st, dd->crop_box.x1 + 0.5 * (dd->crop_box.x2 - dd->crop_box.x1 - width), dd->crop_box.y2 - SIZE_HEADFOOT); - pdfioContentTextShow(dd->st, UNICODE_VALUE, dd->title); - pdfioContentTextEnd(dd->st); - - pdfioContentPathMoveTo(dd->st, dd->crop_box.x1, dd->crop_box.y2 - 2 * SIZE_HEADFOOT * LINE_HEIGHT + SIZE_HEADFOOT); - pdfioContentPathLineTo(dd->st, dd->crop_box.x2, dd->crop_box.y2 - 2 * SIZE_HEADFOOT * LINE_HEIGHT + SIZE_HEADFOOT); - pdfioContentStroke(dd->st); - } - - // Show page number and current heading... - pdfioContentPathMoveTo(dd->st, dd->crop_box.x1, dd->crop_box.y1 + SIZE_HEADFOOT * LINE_HEIGHT); - pdfioContentPathLineTo(dd->st, dd->crop_box.x2, dd->crop_box.y1 + SIZE_HEADFOOT * LINE_HEIGHT); - pdfioContentStroke(dd->st); - - pdfioContentTextBegin(dd->st); - snprintf(temp, sizeof(temp), "%u", (unsigned)pdfioFileGetNumPages(dd->pdf)); - if (pdfioFileGetNumPages(dd->pdf) & 1) - { - // Page number on right... - width = pdfioContentTextMeasure(dd->fonts[DOCFONT_REGULAR], temp, SIZE_HEADFOOT); - pdfioContentTextMoveTo(dd->st, dd->crop_box.x2 - width, dd->crop_box.y1); - } - else - { - // Page number on left... - pdfioContentTextMoveTo(dd->st, dd->crop_box.x1, dd->crop_box.y1); - } - - pdfioContentTextShow(dd->st, UNICODE_VALUE, temp); - pdfioContentTextEnd(dd->st); - - if (dd->heading) - { - pdfioContentTextBegin(dd->st); - - if (pdfioFileGetNumPages(dd->pdf) & 1) - { - // Current heading on left... - pdfioContentTextMoveTo(dd->st, dd->crop_box.x1, dd->crop_box.y1); - } - else - { - width = pdfioContentTextMeasure(dd->fonts[DOCFONT_REGULAR], dd->heading, SIZE_HEADFOOT); - pdfioContentTextMoveTo(dd->st, dd->crop_box.x2 - width, dd->crop_box.y1); - } - - pdfioContentTextShow(dd->st, UNICODE_VALUE, dd->heading); - pdfioContentTextEnd(dd->st); - } -} - - -// -// 'render_line()' - Render a line of text/graphics. -// - -static void -render_line(docdata_t *dd, // I - Document data - double margin_left, // I - Left margin - 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; - } - - for (i = 0, frag = frags; i < num_frags; i ++, frag ++) - { - if (frag->type == MMD_TYPE_CHECKBOX) - { - // Draw checkbox... - set_color(dd, frag->color); - - if (in_text) - { - pdfioContentTextEnd(dd->st); - in_text = false; - } - - // Add box - pdfioContentPathRect(dd->st, frag->x + 1.0 + margin_left, dd->y, frag->width - 3.0, frag->height - 3.0); - - if (frag->text) - { - // Add check - pdfioContentPathMoveTo(dd->st, frag->x + 3.0 + margin_left, dd->y + 2.0); - pdfioContentPathLineTo(dd->st, frag->x + frag->width - 4.0 + margin_left, dd->y + frag->height - 5.0); - - pdfioContentPathMoveTo(dd->st, frag->x + 3.0 + margin_left, dd->y + frag->height - 5.0); - pdfioContentPathLineTo(dd->st, frag->x + frag->width - 4.0 + margin_left, dd->y + 2.0); - } - - pdfioContentStroke(dd->st); - } - else if (frag->text) - { - // Draw text - set_color(dd, frag->color); - set_font(dd, frag->font, frag->height); - - if (!in_text) - { - pdfioContentTextBegin(dd->st); - pdfioContentTextMoveTo(dd->st, frag->x + margin_left, 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 - - if (in_text) - { - pdfioContentTextEnd(dd->st); - in_text = false; - } - - snprintf(imagename, sizeof(imagename), "I%u", (unsigned)frag->imagenum); - pdfioContentDrawImage(dd->st, imagename, frag->x + margin_left, dd->y, frag->width, frag->height); - } - } - - if (in_text) - pdfioContentTextEnd(dd->st); -} - - // // 'format_block()' - Format a block of text // @@ -612,7 +432,7 @@ format_block(docdata_t *dd, // I - Document data blocktype = mmdGetType(block); - if (blocktype >= MMD_TYPE_TABLE_HEADER_CELL && blocktype <= MMD_TYPE_TABLE_BODY_CELL_RIGHT) + if ((blocktype >= MMD_TYPE_TABLE_HEADER_CELL && blocktype <= MMD_TYPE_TABLE_BODY_CELL_RIGHT) || blocktype == MMD_TYPE_LIST_ITEM) margin_top = 0.0; else margin_top = fsize * LINE_HEIGHT; @@ -853,191 +673,91 @@ format_code(docdata_t *dd, // I - Document data // -// 'measure_cell()' - Measure the dimensions of a table cell. -// - -static double // O - Formatted height -measure_cell(docdata_t *dd, // I - Document data - mmd_t *cell, // I - Cell node - tablecol_t *col) // O - Column data -{ - mmd_t *current, // Current node - *next; // Next node - mmd_type_t type; // Node type - const char *text, // Current text - *url; // Current URL, if any - bool ws; // Current whitespace - docfont_t font; // Current font - double x = 0.0, // Current X position - width, // Width of node - wswidth, // Width of whitespace - height, // Height of node - lineheight = 0.0, // Height of line - cellheight = 0.0; // Height of cell - - - for (current = mmdGetFirstChild(cell); current; current = next) - { - next = mmd_walk_next(cell, current); - type = mmdGetType(current); - text = mmdGetText(current); - url = mmdGetURL(current); - ws = mmdGetWhitespace(current); - wswidth = 0.0; - - if (type == MMD_TYPE_IMAGE && url) - { - // Embed an image - pdfio_obj_t *image = find_image(dd, url, /*imagenum*/NULL); - // Image object - - if (!image) - continue; - - // Image - treat as 100dpi - width = 72.0 * pdfioImageGetWidth(image) / IMAGE_PPI; - height = 72.0 * pdfioImageGetHeight(image) / IMAGE_PPI; - - if (col->width > 0.0 && width > col->width) - { - // Too wide, scale to width... - width = col->width; - height = width * pdfioImageGetHeight(image) / pdfioImageGetWidth(image); - } - else if (height > (dd->art_box.y2 - dd->art_box.y1)) - { - // Too tall, scale to height... - height = dd->art_box.y2 - dd->art_box.y1; - width = height * pdfioImageGetWidth(image) / pdfioImageGetHeight(image); - } - } - else if (type == MMD_TYPE_HARD_BREAK && x > 0.0) - { - // Hard break... - if (x > col->max_width) - col->max_width = x; - - cellheight += lineheight; - x = 0.0; - lineheight = 0.0; - continue; - } - else if (type == MMD_TYPE_CHECKBOX) - { - // Checkbox... - width = height = SIZE_TABLE; - } - else if (!text) - { - continue; - } - else - { - // Text fragment... - if (type == MMD_TYPE_EMPHASIZED_TEXT) - font = DOCFONT_ITALIC; - else if (type == MMD_TYPE_STRONG_TEXT) - font = DOCFONT_BOLD; - else if (type == MMD_TYPE_CODE_TEXT) - font = DOCFONT_MONOSPACE; - else if (mmdGetType(cell) == MMD_TYPE_TABLE_HEADER_CELL) - font = DOCFONT_BOLD; - else - font = DOCFONT_REGULAR; - - width = pdfioContentTextMeasure(dd->fonts[font], text, SIZE_TABLE); - height = SIZE_TABLE * LINE_HEIGHT; - - if (ws && x > 0.0) - wswidth = pdfioContentTextMeasure(dd->fonts[font], " ", SIZE_TABLE); - } - - if (width > col->min_width) - col->min_width = width; - - // See if this node will fit on the current line - if (col->width > 0.0 && (x + width + wswidth) >= col->width) - { - // No, record the new line... - if (x > col->max_width) - col->max_width = x; - - cellheight += lineheight; - x = 0.0; - lineheight = 0.0; - wswidth = 0.0; - } - - x += width + wswidth; - - if (height > lineheight) - lineheight = height; - } - - // Capture the last line's measurements... - if (x > col->max_width) - col->max_width = x; - - if (x > 0.0) - cellheight += lineheight; - - // Return the total height... - return (cellheight); -} - - -// -// 'render_row()' - Render a table row... +// 'format_doc()' - Format a document. // static void -render_row(docdata_t *dd, // I - Document data - size_t num_cols, // I - Number of columns - tablecol_t *cols, // I - Column information - tablerow_t *row) // I - Row +format_doc(docdata_t *dd, // I - Document data + mmd_t *doc, // I - Document node to format + double left, // I - Left margin + double right) // I - Right margin { - size_t col; // Current column - double row_y; // Start of row - docfont_t deffont; // Default font - - - // Start a new page as needed... - if (!dd->st) - new_page(dd); - - if ((dd->y - row->height) < dd->art_box.y1) - new_page(dd); - - if (mmdGetType(row->cells[0]) == MMD_TYPE_TABLE_HEADER_CELL) + int i; // Child number + mmd_type_t doctype; // Document node type + mmd_t *current; // Current node + mmd_type_t curtype; // Current node type + char leader[32]; // Leader + static const double heading_sizes[] = // Heading font sizes { - // Header row, no border... - deffont = DOCFONT_BOLD; - } - else - { - // Regular body row, add borders... - deffont = DOCFONT_REGULAR; + SIZE_HEADING_1, + SIZE_HEADING_2, + SIZE_HEADING_3, + SIZE_HEADING_4, + SIZE_HEADING_5, + SIZE_HEADING_6 + }; - set_color(dd, DOCCOLOR_GRAY); - pdfioContentPathRect(dd->st, cols[0].left - TABLE_PADDING, dd->y - row->height, cols[num_cols - 1].right - cols[0].left + 2.0 * TABLE_PADDING, row->height); - for (col = 1; col < num_cols; col ++) + + doctype = mmdGetType(doc); + + for (i = 1, current = mmdGetFirstChild(doc); current; i ++, current = mmdGetNextSibling(current)) + { + switch (curtype = mmdGetType(current)) { - pdfioContentPathMoveTo(dd->st, cols[col].left - TABLE_PADDING, dd->y); - pdfioContentPathLineTo(dd->st, cols[col].left - TABLE_PADDING, dd->y - row->height); + default : + break; + + case MMD_TYPE_BLOCK_QUOTE : + format_doc(dd, current, left + 36.0, right - 36.0); + break; + + case MMD_TYPE_ORDERED_LIST : + case MMD_TYPE_UNORDERED_LIST : + if (dd->st) + dd->y -= SIZE_BODY * LINE_HEIGHT; + + format_doc(dd, current, left + 36.0, right); + break; + + case MMD_TYPE_LIST_ITEM : + if (doctype == MMD_TYPE_ORDERED_LIST) + { + snprintf(leader, sizeof(leader), "%d. ", i); + format_block(dd, current, DOCFONT_REGULAR, SIZE_BODY, left, right, leader); + } + else + { + format_block(dd, current, DOCFONT_REGULAR, SIZE_BODY, left, right, /*leader*/"• "); + } + break; + + case MMD_TYPE_HEADING_1 : + case MMD_TYPE_HEADING_2 : + case MMD_TYPE_HEADING_3 : + case MMD_TYPE_HEADING_4 : + case MMD_TYPE_HEADING_5 : + case MMD_TYPE_HEADING_6 : + if (dd->heading) + free(dd->heading); + + dd->heading = mmdCopyAllText(current); + + format_block(dd, current, DOCFONT_BOLD, heading_sizes[curtype - MMD_TYPE_HEADING_1], left, right, /*leader*/NULL); + break; + + case MMD_TYPE_PARAGRAPH : + format_block(dd, current, DOCFONT_REGULAR, SIZE_BODY, left, right, /*leader*/NULL); + break; + + case MMD_TYPE_TABLE : + format_table(dd, current, left, right); + break; + + case MMD_TYPE_CODE_BLOCK : + format_code(dd, current, left + 36.0, right - 36.0); + break; } - pdfioContentStroke(dd->st); } - - row_y = dd->y; - - for (col = 0; col < num_cols; col ++) - { - dd->y = row_y; - - format_block(dd, row->cells[col], deffont, SIZE_TABLE, cols[col].left, cols[col].right, /*leader*/NULL); - } - - dd->y = row_y - row->height; } @@ -1176,87 +896,265 @@ format_table(docdata_t *dd, // I - Document data // -// 'format_doc()' - Format a document. +// 'measure_cell()' - Measure the dimensions of a table cell. +// + +static double // O - Formatted height +measure_cell(docdata_t *dd, // I - Document data + mmd_t *cell, // I - Cell node + tablecol_t *col) // O - Column data +{ + mmd_t *current, // Current node + *next; // Next node + mmd_type_t type; // Node type + const char *text, // Current text + *url; // Current URL, if any + bool ws; // Current whitespace + docfont_t font; // Current font + double x = 0.0, // Current X position + width, // Width of node + wswidth, // Width of whitespace + height, // Height of node + lineheight = 0.0, // Height of line + cellheight = 0.0; // Height of cell + + + for (current = mmdGetFirstChild(cell); current; current = next) + { + next = mmd_walk_next(cell, current); + type = mmdGetType(current); + text = mmdGetText(current); + url = mmdGetURL(current); + ws = mmdGetWhitespace(current); + wswidth = 0.0; + + if (type == MMD_TYPE_IMAGE && url) + { + // Embed an image + pdfio_obj_t *image = find_image(dd, url, /*imagenum*/NULL); + // Image object + + if (!image) + continue; + + // Image - treat as 100dpi + width = 72.0 * pdfioImageGetWidth(image) / IMAGE_PPI; + height = 72.0 * pdfioImageGetHeight(image) / IMAGE_PPI; + + if (col->width > 0.0 && width > col->width) + { + // Too wide, scale to width... + width = col->width; + height = width * pdfioImageGetHeight(image) / pdfioImageGetWidth(image); + } + else if (height > (dd->art_box.y2 - dd->art_box.y1)) + { + // Too tall, scale to height... + height = dd->art_box.y2 - dd->art_box.y1; + width = height * pdfioImageGetWidth(image) / pdfioImageGetHeight(image); + } + } + else if (type == MMD_TYPE_HARD_BREAK && x > 0.0) + { + // Hard break... + if (x > col->max_width) + col->max_width = x; + + cellheight += lineheight; + x = 0.0; + lineheight = 0.0; + continue; + } + else if (type == MMD_TYPE_CHECKBOX) + { + // Checkbox... + width = height = SIZE_TABLE; + } + else if (!text) + { + continue; + } + else + { + // Text fragment... + if (type == MMD_TYPE_EMPHASIZED_TEXT) + font = DOCFONT_ITALIC; + else if (type == MMD_TYPE_STRONG_TEXT) + font = DOCFONT_BOLD; + else if (type == MMD_TYPE_CODE_TEXT) + font = DOCFONT_MONOSPACE; + else if (mmdGetType(cell) == MMD_TYPE_TABLE_HEADER_CELL) + font = DOCFONT_BOLD; + else + font = DOCFONT_REGULAR; + + width = pdfioContentTextMeasure(dd->fonts[font], text, SIZE_TABLE); + height = SIZE_TABLE * LINE_HEIGHT; + + if (ws && x > 0.0) + wswidth = pdfioContentTextMeasure(dd->fonts[font], " ", SIZE_TABLE); + } + + if (width > col->min_width) + col->min_width = width; + + // See if this node will fit on the current line + if (col->width > 0.0 && (x + width + wswidth) >= col->width) + { + // No, record the new line... + if (x > col->max_width) + col->max_width = x; + + cellheight += lineheight; + x = 0.0; + lineheight = 0.0; + wswidth = 0.0; + } + + x += width + wswidth; + + if (height > lineheight) + lineheight = height; + } + + // Capture the last line's measurements... + if (x > col->max_width) + col->max_width = x; + + if (x > 0.0) + cellheight += lineheight; + + // Return the total height... + return (cellheight); +} + + +// +// 'mmd_walk_next()' - Find the next markdown node. +// + +static mmd_t * // O - Next node or `NULL` at end +mmd_walk_next(mmd_t *top, // I - Top node + mmd_t *node) // I - Current node +{ + mmd_t *next, // Next node + *parent; // Parent node + + + // Figure out the next node under "top"... + if ((next = mmdGetFirstChild(node)) == NULL) + { + if ((next = mmdGetNextSibling(node)) == NULL) + { + if ((parent = mmdGetParent(node)) != top) + { + while ((next = mmdGetNextSibling(parent)) == NULL) + { + if ((parent = mmdGetParent(parent)) == top) + break; + } + } + } + } + + return (next); +} + + +// +// 'new_page()' - Start a new page. // static void -format_doc(docdata_t *dd, // I - Document data - mmd_t *doc, // I - Document node to format - double left, // I - Left margin - double right) // I - Right margin +new_page(docdata_t *dd) // I - Document data { - int i; // Child number - mmd_type_t doctype; // Document node type - mmd_t *current; // Current node - mmd_type_t curtype; // Current node type - char leader[32]; // Leader - static const double heading_sizes[] = // Heading font sizes + pdfio_dict_t *page_dict; // Page dictionary + docfont_t fontface; // Current font face + size_t i; // Looping var + char temp[32]; // Temporary string + double width; // Width of fragment + + + // Close the current page... + pdfioStreamClose(dd->st); + + // Prep the new page... + page_dict = pdfioDictCreate(dd->pdf); + + pdfioDictSetRect(page_dict, "MediaBox", &dd->media_box); +// pdfioDictSetRect(page_dict, "CropBox", &dd->crop_box); + pdfioDictSetRect(page_dict, "ArtBox", &dd->art_box); + + for (fontface = DOCFONT_REGULAR; fontface < DOCFONT_MAX; fontface ++) + pdfioPageDictAddFont(page_dict, docfont_names[fontface], dd->fonts[fontface]); + + for (i = 0; i < dd->num_images; i ++) + pdfioPageDictAddImage(page_dict, pdfioStringCreatef(dd->pdf, "I%u", (unsigned)i), dd->images[i].obj); + + 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 + set_color(dd, DOCCOLOR_GRAY); + set_font(dd, DOCFONT_REGULAR, SIZE_HEADFOOT); + + if (pdfioFileGetNumPages(dd->pdf) > 1 && dd->title) { - SIZE_HEADING_1, - SIZE_HEADING_2, - SIZE_HEADING_3, - SIZE_HEADING_4, - SIZE_HEADING_5, - SIZE_HEADING_6 - }; + // Show title in header... + width = pdfioContentTextMeasure(dd->fonts[DOCFONT_REGULAR], dd->title, SIZE_HEADFOOT); + pdfioContentTextBegin(dd->st); + pdfioContentTextMoveTo(dd->st, dd->crop_box.x1 + 0.5 * (dd->crop_box.x2 - dd->crop_box.x1 - width), dd->crop_box.y2 - SIZE_HEADFOOT); + pdfioContentTextShow(dd->st, UNICODE_VALUE, dd->title); + pdfioContentTextEnd(dd->st); - doctype = mmdGetType(doc); + pdfioContentPathMoveTo(dd->st, dd->crop_box.x1, dd->crop_box.y2 - 2 * SIZE_HEADFOOT * LINE_HEIGHT + SIZE_HEADFOOT); + pdfioContentPathLineTo(dd->st, dd->crop_box.x2, dd->crop_box.y2 - 2 * SIZE_HEADFOOT * LINE_HEIGHT + SIZE_HEADFOOT); + pdfioContentStroke(dd->st); + } - for (i = 1, current = mmdGetFirstChild(doc); current; i ++, current = mmdGetNextSibling(current)) + // Show page number and current heading... + pdfioContentPathMoveTo(dd->st, dd->crop_box.x1, dd->crop_box.y1 + SIZE_HEADFOOT * LINE_HEIGHT); + pdfioContentPathLineTo(dd->st, dd->crop_box.x2, dd->crop_box.y1 + SIZE_HEADFOOT * LINE_HEIGHT); + pdfioContentStroke(dd->st); + + pdfioContentTextBegin(dd->st); + snprintf(temp, sizeof(temp), "%u", (unsigned)pdfioFileGetNumPages(dd->pdf)); + if (pdfioFileGetNumPages(dd->pdf) & 1) { - switch (curtype = mmdGetType(current)) + // Page number on right... + width = pdfioContentTextMeasure(dd->fonts[DOCFONT_REGULAR], temp, SIZE_HEADFOOT); + pdfioContentTextMoveTo(dd->st, dd->crop_box.x2 - width, dd->crop_box.y1); + } + else + { + // Page number on left... + pdfioContentTextMoveTo(dd->st, dd->crop_box.x1, dd->crop_box.y1); + } + + pdfioContentTextShow(dd->st, UNICODE_VALUE, temp); + pdfioContentTextEnd(dd->st); + + if (dd->heading) + { + pdfioContentTextBegin(dd->st); + + if (pdfioFileGetNumPages(dd->pdf) & 1) { - default : - break; - - case MMD_TYPE_BLOCK_QUOTE : - format_doc(dd, current, left + 36.0, right - 36.0); - break; - - case MMD_TYPE_ORDERED_LIST : - case MMD_TYPE_UNORDERED_LIST : - format_doc(dd, current, left + 36.0, right); - break; - - case MMD_TYPE_LIST_ITEM : - if (doctype == MMD_TYPE_ORDERED_LIST) - { - snprintf(leader, sizeof(leader), "%d. ", i); - format_block(dd, current, DOCFONT_REGULAR, SIZE_BODY, left, right, leader); - } - else - { - format_block(dd, current, DOCFONT_REGULAR, SIZE_BODY, left, right, /*leader*/"• "); - } - break; - - case MMD_TYPE_HEADING_1 : - case MMD_TYPE_HEADING_2 : - case MMD_TYPE_HEADING_3 : - case MMD_TYPE_HEADING_4 : - case MMD_TYPE_HEADING_5 : - case MMD_TYPE_HEADING_6 : - if (dd->heading) - free(dd->heading); - - dd->heading = mmdCopyAllText(current); - - format_block(dd, current, DOCFONT_BOLD, heading_sizes[curtype - MMD_TYPE_HEADING_1], left, right, /*leader*/NULL); - break; - - case MMD_TYPE_PARAGRAPH : - format_block(dd, current, DOCFONT_REGULAR, SIZE_BODY, left, right, /*leader*/NULL); - break; - - case MMD_TYPE_TABLE : - format_table(dd, current, left, right); - break; - - case MMD_TYPE_CODE_BLOCK : - format_code(dd, current, left + 36.0, right - 36.0); - break; + // Current heading on left... + pdfioContentTextMoveTo(dd->st, dd->crop_box.x1, dd->crop_box.y1); } + else + { + width = pdfioContentTextMeasure(dd->fonts[DOCFONT_REGULAR], dd->heading, SIZE_HEADFOOT); + pdfioContentTextMoveTo(dd->st, dd->crop_box.x2 - width, dd->crop_box.y1); + } + + pdfioContentTextShow(dd->st, UNICODE_VALUE, dd->heading); + pdfioContentTextEnd(dd->st); } } @@ -1277,94 +1175,219 @@ output_cb(void *output_cbdata, // I - Callback data (not used) // -// 'main()' - Convert markdown to PDF. +// 'render_line()' - Render a line of text/graphics. // -int // O - Exit status -main(int argc, // I - Number of command-line arguments - char *argv[]) // I - Command-line arguments +static void +render_line(docdata_t *dd, // I - Document data + double margin_left, // I - Left margin + 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 { - docdata_t dd; // Document data - docfont_t fontface; // Current font - mmd_t *doc; // Markdown document - const char *value; // Metadata value + size_t i; // Looping var + linefrag_t *frag; // Current line fragment + bool in_text = false; // Are we in a text block? - setbuf(stderr, NULL); + if (!dd->st) + new_page(dd); - // Get the markdown file from the command-line... - if (argc != 2) + dd->y -= margin_top + lineheight; + if (dd->y < dd->art_box.y1) { - fputs("Usage: md2pdf FILENANE.md >FILENAME.pdf\n", stderr); - return (1); + new_page(dd); + + dd->y -= lineheight; } - if ((doc = mmdLoad(/*root*/NULL, argv[1])) == NULL) - return (1); - - // Initialize the document data - memset(&dd, 0, sizeof(dd)); - - dd.media_box.x2 = PAGE_WIDTH; - dd.media_box.y2 = PAGE_LENGTH; - - dd.crop_box.x1 = PAGE_LEFT; - dd.crop_box.y1 = PAGE_FOOTER; - dd.crop_box.x2 = PAGE_RIGHT; - dd.crop_box.y2 = PAGE_HEADER; - - dd.art_box.x1 = PAGE_LEFT; - dd.art_box.y1 = PAGE_BOTTOM; - dd.art_box.x2 = PAGE_RIGHT; - dd.art_box.y2 = PAGE_TOP; - - dd.title = mmdGetMetadata(doc, "title"); - - // Output a PDF file to the standard output... -#ifdef _WIN32 - setmode(1, O_BINARY); // Force binary output on Windows -#endif // _WIN32 - - if ((dd.pdf = pdfioFileCreateOutput(output_cb, /*output_cbdata*/NULL, /*version*/NULL, /*media_box*/NULL, /*crop_box*/NULL, /*error_cb*/NULL, /*error_data*/NULL)) == NULL) - return (1); - - if ((value = mmdGetMetadata(doc, "author")) != NULL) - pdfioFileSetAuthor(dd.pdf, value); - - if ((value = mmdGetMetadata(doc, "keywords")) != NULL) - pdfioFileSetKeywords(dd.pdf, value); - - if ((value = mmdGetMetadata(doc, "subject")) != NULL) - pdfioFileSetSubject(dd.pdf, value); - else if ((value = mmdGetMetadata(doc, "copyright")) != NULL) - pdfioFileSetSubject(dd.pdf, value); - - if (dd.title) - pdfioFileSetTitle(dd.pdf, dd.title); - - // Add fonts... - for (fontface = DOCFONT_REGULAR; fontface < DOCFONT_MAX; fontface ++) + for (i = 0, frag = frags; i < num_frags; i ++, frag ++) { -#if USE_TRUETYPE - if ((dd.fonts[fontface] = pdfioFileCreateFontObjFromFile(dd.pdf, docfont_filenames[fontface], UNICODE_VALUE)) == NULL) - return (1); -#else - if ((dd.fonts[fontface] = pdfioFileCreateFontObjFromBase(dd.pdf, docfont_filenames[fontface])) == NULL) - return (1); -#endif // USE_TRUETYPE + if (frag->type == MMD_TYPE_CHECKBOX) + { + // Draw checkbox... + set_color(dd, frag->color); + + if (in_text) + { + pdfioContentTextEnd(dd->st); + in_text = false; + } + + // Add box + pdfioContentPathRect(dd->st, frag->x + 1.0 + margin_left, dd->y, frag->width - 3.0, frag->height - 3.0); + + if (frag->text) + { + // Add check + pdfioContentPathMoveTo(dd->st, frag->x + 3.0 + margin_left, dd->y + 2.0); + pdfioContentPathLineTo(dd->st, frag->x + frag->width - 4.0 + margin_left, dd->y + frag->height - 5.0); + + pdfioContentPathMoveTo(dd->st, frag->x + 3.0 + margin_left, dd->y + frag->height - 5.0); + pdfioContentPathLineTo(dd->st, frag->x + frag->width - 4.0 + margin_left, dd->y + 2.0); + } + + pdfioContentStroke(dd->st); + } + else if (frag->text) + { + // Draw text + set_color(dd, frag->color); + set_font(dd, frag->font, frag->height); + + if (!in_text) + { + pdfioContentTextBegin(dd->st); + pdfioContentTextMoveTo(dd->st, frag->x + margin_left, 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 + + if (in_text) + { + pdfioContentTextEnd(dd->st); + in_text = false; + } + + snprintf(imagename, sizeof(imagename), "I%u", (unsigned)frag->imagenum); + pdfioContentDrawImage(dd->st, imagename, frag->x + margin_left, dd->y, frag->width, frag->height); + } } - // Add images... - add_images(&dd, doc); - - // Parse the markdown document... - format_doc(&dd, doc, dd.art_box.x1, dd.art_box.x2); - - // Close the PDF and return... - pdfioStreamClose(dd.st); - pdfioFileClose(dd.pdf); - - mmdFree(doc); - - return (0); + if (in_text) + pdfioContentTextEnd(dd->st); +} + + +// +// 'render_row()' - Render a table row... +// + +static void +render_row(docdata_t *dd, // I - Document data + size_t num_cols, // I - Number of columns + tablecol_t *cols, // I - Column information + tablerow_t *row) // I - Row +{ + size_t col; // Current column + double row_y; // Start of row + docfont_t deffont; // Default font + + + // Start a new page as needed... + if (!dd->st) + new_page(dd); + + if ((dd->y - row->height) < dd->art_box.y1) + new_page(dd); + + if (mmdGetType(row->cells[0]) == MMD_TYPE_TABLE_HEADER_CELL) + { + // Header row, no border... + deffont = DOCFONT_BOLD; + } + else + { + // Regular body row, add borders... + deffont = DOCFONT_REGULAR; + + set_color(dd, DOCCOLOR_GRAY); + pdfioContentPathRect(dd->st, cols[0].left - TABLE_PADDING, dd->y - row->height, cols[num_cols - 1].right - cols[0].left + 2.0 * TABLE_PADDING, row->height); + for (col = 1; col < num_cols; col ++) + { + pdfioContentPathMoveTo(dd->st, cols[col].left - TABLE_PADDING, dd->y); + pdfioContentPathLineTo(dd->st, cols[col].left - TABLE_PADDING, dd->y - row->height); + } + pdfioContentStroke(dd->st); + } + + row_y = dd->y; + + for (col = 0; col < num_cols; col ++) + { + dd->y = row_y; + + format_block(dd, row->cells[col], deffont, SIZE_TABLE, cols[col].left, cols[col].right, /*leader*/NULL); + } + + dd->y = row_y - row->height; +} + + +// +// 'set_color()' - Set the stroke and fill color as needed. +// + +static void +set_color(docdata_t *dd, // I - Document data + doccolor_t color) // I - Document color +{ + if (color == dd->color) + return; + + switch (color) + { + case DOCCOLOR_BLACK : + pdfioContentSetFillColorDeviceGray(dd->st, 0.0); + pdfioContentSetStrokeColorDeviceGray(dd->st, 0.0); + break; + case DOCCOLOR_RED : + pdfioContentSetFillColorDeviceRGB(dd->st, 0.6, 0.0, 0.0); + pdfioContentSetStrokeColorDeviceRGB(dd->st, 0.6, 0.0, 0.0); + break; + case DOCCOLOR_GREEN : + pdfioContentSetFillColorDeviceRGB(dd->st, 0.0, 0.6, 0.0); + pdfioContentSetStrokeColorDeviceRGB(dd->st, 0.0, 0.6, 0.0); + break; + case DOCCOLOR_BLUE : + pdfioContentSetFillColorDeviceRGB(dd->st, 0.0, 0.0, 0.8); + pdfioContentSetStrokeColorDeviceRGB(dd->st, 0.0, 0.0, 0.8); + break; + case DOCCOLOR_LTGRAY : + pdfioContentSetFillColorDeviceGray(dd->st, 0.933); + pdfioContentSetStrokeColorDeviceGray(dd->st, 0.933); + break; + case DOCCOLOR_GRAY : + pdfioContentSetFillColorDeviceGray(dd->st, 0.333); + pdfioContentSetStrokeColorDeviceGray(dd->st, 0.333); + break; + } + + dd->color = color; +} + + +// +// '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); + + if (fabs(fsize - dd->fsize) >= 0.1) + pdfioContentSetTextLeading(dd->st, fsize * LINE_HEIGHT); + + dd->font = font; + dd->fsize = fsize; }