Compare commits

..

4 Commits

Author SHA1 Message Date
Michael R Sweet
53967552df
Add some documentation on the code128 example.
Clean up the code128 code a bit and use the Helvetica font for the text.

Add support for writing to a PDF file on the command-line vs. just doing
redirection.
2024-12-15 22:52:03 -05:00
Michael R Sweet
f8639fbd64
Add "need_bottom" argument to keep the following block on the same page. 2024-12-15 18:17:31 -05:00
Michael R Sweet
9020e92928
Make sure we have room for at least two lines in a paragraph or code block at the bottom of the page. 2024-12-15 17:46:03 -05:00
Michael R Sweet
48e6597337
Make block quote bar a thick orange so it stands out. 2024-12-15 17:35:16 -05:00
3 changed files with 243 additions and 40 deletions

View File

@ -33,7 +33,7 @@ PDFio requires the following to build the software:
IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided. IDE files for Xcode (macOS/iOS) and Visual Studio (Windows) are also provided.
Installing pdfio Installing PDFio
---------------- ----------------
PDFio comes with a configure script that creates a portable makefile that will PDFio comes with a configure script that creates a portable makefile that will
@ -315,8 +315,9 @@ Reading PDF Files
You open an existing PDF file using the [`pdfioFileOpen`](@@) function: You open an existing PDF file using the [`pdfioFileOpen`](@@) function:
```c ```c
pdfio_file_t *pdf = pdfioFileOpen("myinputfile.pdf", password_cb, password_data, pdfio_file_t *pdf =
error_cb, error_data); pdfioFileOpen("myinputfile.pdf", password_cb, password_data,
error_cb, error_data);
``` ```
@ -763,6 +764,9 @@ pdfio_obj_t *img =
/*interpolate*/true); /*interpolate*/true);
``` ```
> Note: Currently `pdfioFileCreateImageObjFromFile` does not support 12 bit JPEG
> files or PNG files with an alpha channel.
### Page Dictionary Functions ### Page Dictionary Functions
@ -862,6 +866,7 @@ escaping, as needed:
Examples Examples
======== ========
Read PDF Metadata Read PDF Metadata
----------------- -----------------
@ -992,3 +997,169 @@ create_pdf_image_file(const char *pdfname, const char *imagename,
pdfioFileClose(pdf); pdfioFileClose(pdf);
} }
``` ```
Generate a Code 128 Barcode
---------------------------
One-dimensional barcodes are often rendered using special fonts that map ASCII
characters to sequences of bars that can be read. The "examples" directory
contains such a font to create "Code 128" barcodes, with an accompanying bit of
example code.
The first thing you need to do is prepare the barcode string to use with the
font. Each barcode begins with a start pattern followed by the characters or
digits you want to encode, a weighted sum digit, and a stop pattern. The
`make_code128` function creates this string:
```c
static char * // O - Output string
make_code128(char *dst, // I - Destination buffer
const char *src, // I - Source string
size_t dstsize) // I - Size of destination buffer
{
char *dstptr, // Pointer into destination buffer
*dstend; // End of destination buffer
int sum; // Weighted sum
static const char *code128_chars = // Code 128 characters
" !\"#$%&'()*+,-./0123456789:;<=>?"
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
"`abcdefghijklmnopqrstuvwxyz{|}~\303"
"\304\305\306\307\310\311\312";
static const char code128_start_code_b = '\314';
// Start code B
static const char code128_stop = '\316';
// Stop pattern
// Start a Code B barcode...
dstptr = dst;
dstend = dst + dstsize - 3;
*dstptr++ = code128_start_code_b;
sum = code128_start_code_b - 100;
while (*src && dstptr < dstend)
{
if (*src >= ' ' && *src < 0x7f)
{
sum += (dstptr - dst) * (*src - ' ');
*dstptr++ = *src;
}
src ++;
}
// Add the weighted sum modulo 103
*dstptr++ = code128_chars[sum % 103];
// Add the stop pattern and return...
*dstptr++ = code128_stop;
*dstptr = '\0';
return (dst);
}
```
The `main` function does the rest of the work. The barcode font is imported
using the [`pdfioFileCreateFontObjFromFile`](@@) function. We pass `false`
for the "unicode" argument since we just want the (default) ASCII encoding:
```c
barcode_font = pdfioFileCreateFontObjFromFile(pdf, "code128.ttf",
/*unicode*/false);
```
Since barcodes usually have the number or text represented by the barcode
printed underneath it, we also need a regular text font, for which we can choose
one of the standard 14 PostScript base fonts using the
[`pdfioFIleCreateFontObjFromBase`](@@) function:
```c
text_font = pdfioFileCreateFontObjFromBase(pdf, "Helvetica");
```
Once we have these fonts we can measure the barcode and regular text labels
using the [`pdfioContentTextMeasure`](@@) function to determine how large the
PDF page needs to be to hold the barcode and text:
```c
// Compute sizes of the text...
const char *barcode = argv[1];
char barcode_temp[256];
if (!(barcode[0] & 0x80))
barcode = make_code128(barcode_temp, barcode, sizeof(barcode_temp));
double barcode_height = 36.0;
double barcode_width =
pdfioContentTextMeasure(barcode_font, barcode, barcode_height);
const char *text = argv[2];
double text_height = 0.0;
double text_width = 0.0;
if (text && text_font)
{
text_height = 9.0;
text_width = pdfioContentTextMeasure(text_font, text,
text_height);
}
// Compute the size of the PDF page...
pdfio_rect_t media_box;
media_box.x1 = 0.0;
media_box.y1 = 0.0;
media_box.x2 = (barcode_width > text_width ?
barcode_width : text_width) + 18.0;
media_box.y2 = barcode_height + text_height + 18.0;
```
Finally, we just need to create a page of the specified size that references the
two fonts:
```c
// Start a page for the barcode...
page_dict = pdfioDictCreate(pdf);
pdfioDictSetRect(page_dict, "MediaBox", &media_box);
pdfioDictSetRect(page_dict, "CropBox", &media_box);
pdfioPageDictAddFont(page_dict, "B128", barcode_font);
if (text_font)
pdfioPageDictAddFont(page_dict, "TEXT", text_font);
page_st = pdfioFileCreatePage(pdf, page_dict);
```
With the barcode font called "B128" and the text font called "TEXT", we can
use them to draw two strings:
```c
// Draw the page...
pdfioContentSetFillColorGray(page_st, 0.0);
pdfioContentSetTextFont(page_st, "B128", barcode_height);
pdfioContentTextBegin(page_st);
pdfioContentTextMoveTo(page_st, 0.5 * (media_box.x2 - barcode_width),
9.0 + text_height);
pdfioContentTextShow(page_st, /*unicode*/false, barcode);
pdfioContentTextEnd(page_st);
if (text && text_font)
{
pdfioContentSetTextFont(page_st, "TEXT", text_height);
pdfioContentTextBegin(page_st);
pdfioContentTextMoveTo(page_st, 0.5 * (media_box.x2 - text_width), 9.0);
pdfioContentTextShow(page_st, /*unicode*/false, text);
pdfioContentTextEnd(page_st);
}
pdfioStreamClose(page_st);
```
Convert Markdown to PDF
-----------------------

View File

@ -23,7 +23,6 @@
// extended characters are ignored in the source string. // extended characters are ignored in the source string.
// //
static char * // O - Output string static char * // O - Output string
make_code128(char *dst, // I - Destination buffer make_code128(char *dst, // I - Destination buffer
const char *src, // I - Source string const char *src, // I - Source string
@ -54,9 +53,9 @@ make_code128(char *dst, // I - Destination buffer
static const char code128_start_code_a = '\313'; static const char code128_start_code_a = '\313';
// Start code A // Start code A
static const char code128_start_code_b = '\314'; static const char code128_start_code_b = '\314';
// Start code A // Start code B
static const char code128_start_code_c = '\315'; static const char code128_start_code_c = '\315';
// Start code A // Start code C
static const char code128_stop = '\316'; static const char code128_stop = '\316';
// Stop pattern // Stop pattern
@ -149,7 +148,7 @@ main(int argc, // I - Number of command-line arguments
// Load fonts... // Load fonts...
barcode_font = pdfioFileCreateFontObjFromFile(pdf, "code128.ttf", /*unicode*/false); barcode_font = pdfioFileCreateFontObjFromFile(pdf, "code128.ttf", /*unicode*/false);
if (text) if (text)
text_font = pdfioFileCreateFontObjFromFile(pdf, "../testfiles/OpenSans-Regular.ttf", /*unicode*/true); text_font = pdfioFileCreateFontObjFromBase(pdf, "Helvetica");
// Generate Code128 characters for the desired barcode... // Generate Code128 characters for the desired barcode...
if (!(barcode[0] & 0x80)) if (!(barcode[0] & 0x80))
@ -182,7 +181,7 @@ main(int argc, // I - Number of command-line arguments
page_st = pdfioFileCreatePage(pdf, page_dict); page_st = pdfioFileCreatePage(pdf, page_dict);
// Draw the page... // Draw the page...
pdfioContentSetStrokeColorGray(page_st, 0.0); pdfioContentSetFillColorGray(page_st, 0.0);
pdfioContentSetTextFont(page_st, "B128", barcode_height); pdfioContentSetTextFont(page_st, "B128", barcode_height);
pdfioContentTextBegin(page_st); pdfioContentTextBegin(page_st);
@ -195,7 +194,7 @@ main(int argc, // I - Number of command-line arguments
pdfioContentSetTextFont(page_st, "TEXT", text_height); pdfioContentSetTextFont(page_st, "TEXT", text_height);
pdfioContentTextBegin(page_st); pdfioContentTextBegin(page_st);
pdfioContentTextMoveTo(page_st, 0.5 * (media_box.x2 - text_width), 9.0); pdfioContentTextMoveTo(page_st, 0.5 * (media_box.x2 - text_width), 9.0);
pdfioContentTextShow(page_st, /*unicode*/true, text); pdfioContentTextShow(page_st, /*unicode*/false, text);
pdfioContentTextEnd(page_st); pdfioContentTextEnd(page_st);
} }

View File

@ -8,6 +8,7 @@
// //
// Usage: // Usage:
// //
// ./md2pdf FILENAME.md FILENAME.pdf
// ./md2pdf FILENAME.md >FILENAME.pdf // ./md2pdf FILENAME.md >FILENAME.pdf
// //
// The generated PDF file is formatted for a "universal" paper size (8.27x11", // The generated PDF file is formatted for a "universal" paper size (8.27x11",
@ -40,7 +41,7 @@ typedef enum doccolor_e // Document color enumeration
{ {
DOCCOLOR_BLACK, // #000 DOCCOLOR_BLACK, // #000
DOCCOLOR_RED, // #900 DOCCOLOR_RED, // #900
DOCCOLOR_GREEN, // #090 DOCCOLOR_ORANGE, // #CC0
DOCCOLOR_BLUE, // #00C DOCCOLOR_BLUE, // #00C
DOCCOLOR_LTGRAY, // #EEE DOCCOLOR_LTGRAY, // #EEE
DOCCOLOR_GRAY // #555 DOCCOLOR_GRAY // #555
@ -238,7 +239,7 @@ 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 mmd_t *mmd_walk_next(mmd_t *top, mmd_t *node);
static void new_page(docdata_t *dd); static void new_page(docdata_t *dd);
static ssize_t output_cb(void *output_cbdata, const void *buffer, size_t bytes); 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_line(docdata_t *dd, double margin_left, double margin_top, double need_bottom, 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 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_color(docdata_t *dd, doccolor_t color);
static void set_font(docdata_t *dd, docfont_t font, double fsize); static void set_font(docdata_t *dd, docfont_t font, double fsize);
@ -262,9 +263,10 @@ main(int argc, // I - Number of command-line arguments
setbuf(stderr, NULL); 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 || argc > 3)
{ {
fputs("Usage: md2pdf FILENANE.md >FILENAME.pdf\n", stderr); fputs("Usage: md2pdf FILENANE.md [FILENAME.pdf]\n", stderr);
fputs(" md2pdf FILENANE.md >FILENAME.pdf\n", stderr);
return (1); return (1);
} }
@ -289,13 +291,20 @@ main(int argc, // I - Number of command-line arguments
dd.title = mmdGetMetadata(doc, "title"); dd.title = mmdGetMetadata(doc, "title");
// Output a PDF file to the standard output... if (argc == 2)
{
// Output a PDF file to the standard output...
#ifdef _WIN32 #ifdef _WIN32
setmode(1, O_BINARY); // Force binary output on Windows setmode(1, O_BINARY); // Force binary output on Windows
#endif // _WIN32 #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) 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);
}
else if ((dd.pdf = pdfioFileCreate(argv[2], /*version*/NULL, /*media_box*/NULL, /*crop_box*/NULL, /*error_cb*/NULL, /*error_data*/NULL)) == NULL)
{
return (1); return (1);
}
if ((value = mmdGetMetadata(doc, "author")) != NULL) if ((value = mmdGetMetadata(doc, "author")) != NULL)
pdfioFileSetAuthor(dd.pdf, value); pdfioFileSetAuthor(dd.pdf, value);
@ -540,6 +549,7 @@ format_block(docdata_t *dd, // I - Document data
wswidth, // Width of whitespace wswidth, // Width of whitespace
margin_left, // Left margin margin_left, // Left margin
margin_top, // Top margin margin_top, // Top margin
need_bottom, // Space needed after this block
height, // Height of current fragment height, // Height of current fragment
lineheight; // Height of current line lineheight; // Height of current line
@ -551,6 +561,11 @@ format_block(docdata_t *dd, // I - Document data
else else
margin_top = fsize * LINE_HEIGHT; margin_top = fsize * LINE_HEIGHT;
if (mmdGetNextSibling(block))
need_bottom = 3.0 * SIZE_BODY * LINE_HEIGHT;
else
need_bottom = 0.0;
if (leader) if (leader)
{ {
// Add leader text on first line... // Add leader text on first line...
@ -620,22 +635,26 @@ format_block(docdata_t *dd, // I - Document data
else else
margin_left = 0.0; margin_left = 0.0;
render_line(dd, margin_left, margin_top, lineheight, num_frags, frags); render_line(dd, margin_left, margin_top, need_bottom, lineheight, num_frags, frags);
if (deffont == DOCFONT_ITALIC) if (deffont == DOCFONT_ITALIC)
{ {
// Add a gray bar to the left of block quotes... // Add an orange bar to the left of block quotes...
set_color(dd, DOCCOLOR_GREEN); set_color(dd, DOCCOLOR_ORANGE);
pdfioContentSave(dd->st);
pdfioContentSetLineWidth(dd->st, 3.0);
pdfioContentPathMoveTo(dd->st, left - 6.0, dd->y - (LINE_HEIGHT - 1.0) * fsize); pdfioContentPathMoveTo(dd->st, left - 6.0, dd->y - (LINE_HEIGHT - 1.0) * fsize);
pdfioContentPathLineTo(dd->st, left - 6.0, dd->y + fsize); pdfioContentPathLineTo(dd->st, left - 6.0, dd->y + fsize);
pdfioContentStroke(dd->st); pdfioContentStroke(dd->st);
pdfioContentRestore(dd->st);
} }
num_frags = 0; num_frags = 0;
frag = frags; frag = frags;
x = left; x = left;
lineheight = 0.0; lineheight = 0.0;
margin_top = 0.0; margin_top = 0.0;
need_bottom = 0.0;
continue; continue;
} }
@ -685,22 +704,26 @@ format_block(docdata_t *dd, // I - Document data
else else
margin_left = 0.0; margin_left = 0.0;
render_line(dd, margin_left, margin_top, lineheight, num_frags, frags); render_line(dd, margin_left, margin_top, need_bottom, lineheight, num_frags, frags);
if (deffont == DOCFONT_ITALIC) if (deffont == DOCFONT_ITALIC)
{ {
// Add a gray bar to the left of block quotes... // Add an orange bar to the left of block quotes...
set_color(dd, DOCCOLOR_GREEN); set_color(dd, DOCCOLOR_ORANGE);
pdfioContentSave(dd->st);
pdfioContentSetLineWidth(dd->st, 3.0);
pdfioContentPathMoveTo(dd->st, left - 6.0, dd->y - (LINE_HEIGHT - 1.0) * fsize); pdfioContentPathMoveTo(dd->st, left - 6.0, dd->y - (LINE_HEIGHT - 1.0) * fsize);
pdfioContentPathLineTo(dd->st, left - 6.0, dd->y + fsize); pdfioContentPathLineTo(dd->st, left - 6.0, dd->y + fsize);
pdfioContentStroke(dd->st); pdfioContentStroke(dd->st);
pdfioContentRestore(dd->st);
} }
num_frags = 0; num_frags = 0;
frag = frags; frag = frags;
x = left; x = left;
lineheight = 0.0; lineheight = 0.0;
margin_top = 0.0; margin_top = 0.0;
need_bottom = 0.0;
} }
// Add the current node to the fragment list // Add the current node to the fragment list
@ -737,15 +760,18 @@ format_block(docdata_t *dd, // I - Document data
else else
margin_left = 0.0; margin_left = 0.0;
render_line(dd, margin_left, margin_top, lineheight, num_frags, frags); render_line(dd, margin_left, margin_top, need_bottom, lineheight, num_frags, frags);
if (deffont == DOCFONT_ITALIC) if (deffont == DOCFONT_ITALIC)
{ {
// Add a gray bar to the left of block quotes... // Add an orange bar to the left of block quotes...
set_color(dd, DOCCOLOR_GREEN); set_color(dd, DOCCOLOR_ORANGE);
pdfioContentSave(dd->st);
pdfioContentSetLineWidth(dd->st, 3.0);
pdfioContentPathMoveTo(dd->st, left - 6.0, dd->y - (LINE_HEIGHT - 1.0) * fsize); pdfioContentPathMoveTo(dd->st, left - 6.0, dd->y - (LINE_HEIGHT - 1.0) * fsize);
pdfioContentPathLineTo(dd->st, left - 6.0, dd->y + fsize); pdfioContentPathLineTo(dd->st, left - 6.0, dd->y + fsize);
pdfioContentStroke(dd->st); pdfioContentStroke(dd->st);
pdfioContentRestore(dd->st);
} }
} }
} }
@ -771,7 +797,7 @@ format_code(docdata_t *dd, // I - Document data
lineheight = SIZE_CODEBLOCK * LINE_HEIGHT; lineheight = SIZE_CODEBLOCK * LINE_HEIGHT;
dd->y -= 2.0 * lineheight; dd->y -= 2.0 * lineheight;
if (dd->y < dd->art_box.y1) if ((dd->y - lineheight) < dd->art_box.y1)
{ {
new_page(dd); new_page(dd);
@ -811,6 +837,7 @@ format_code(docdata_t *dd, // I - Document data
// End the current text block... // End the current text block...
pdfioContentTextEnd(dd->st); pdfioContentTextEnd(dd->st);
dd->y += lineheight;
} }
@ -850,6 +877,11 @@ format_doc(docdata_t *dd, // I - Document data
default : default :
break; break;
case MMD_TYPE_THEMATIC_BREAK :
// Force a page break
dd->y = dd->art_box.y1;
break;
case MMD_TYPE_BLOCK_QUOTE : case MMD_TYPE_BLOCK_QUOTE :
format_doc(dd, current, DOCFONT_ITALIC, left + 36.0, right - 36.0); format_doc(dd, current, DOCFONT_ITALIC, left + 36.0, right - 36.0);
break; break;
@ -1372,6 +1404,7 @@ static void
render_line(docdata_t *dd, // I - Document data render_line(docdata_t *dd, // I - Document data
double margin_left, // I - Left margin double margin_left, // I - Left margin
double margin_top, // I - Top margin double margin_top, // I - Top margin
double need_bottom, // I - How much space is needed after
double lineheight, // I - Height of line double lineheight, // I - Height of line
size_t num_frags, // I - Number of line fragments size_t num_frags, // I - Number of line fragments
linefrag_t *frags) // I - Line fragments linefrag_t *frags) // I - Line fragments
@ -1388,7 +1421,7 @@ render_line(docdata_t *dd, // I - Document data
} }
dd->y -= margin_top + lineheight; dd->y -= margin_top + lineheight;
if (dd->y < dd->art_box.y1) if ((dd->y - need_bottom) < dd->art_box.y1)
{ {
new_page(dd); new_page(dd);
@ -1573,9 +1606,9 @@ set_color(docdata_t *dd, // I - Document data
pdfioContentSetFillColorDeviceRGB(dd->st, 0.6, 0.0, 0.0); pdfioContentSetFillColorDeviceRGB(dd->st, 0.6, 0.0, 0.0);
pdfioContentSetStrokeColorDeviceRGB(dd->st, 0.6, 0.0, 0.0); pdfioContentSetStrokeColorDeviceRGB(dd->st, 0.6, 0.0, 0.0);
break; break;
case DOCCOLOR_GREEN : case DOCCOLOR_ORANGE :
pdfioContentSetFillColorDeviceRGB(dd->st, 0.0, 0.6, 0.0); pdfioContentSetFillColorDeviceRGB(dd->st, 1.0, 0.5, 0.0);
pdfioContentSetStrokeColorDeviceRGB(dd->st, 0.0, 0.6, 0.0); pdfioContentSetStrokeColorDeviceRGB(dd->st, 1.0, 0.5, 0.0);
break; break;
case DOCCOLOR_BLUE : case DOCCOLOR_BLUE :
pdfioContentSetFillColorDeviceRGB(dd->st, 0.0, 0.0, 0.8); pdfioContentSetFillColorDeviceRGB(dd->st, 0.0, 0.0, 0.8);