diff options
Diffstat (limited to 'libs/anr/anr_pdf.h')
| -rw-r--r-- | libs/anr/anr_pdf.h | 1527 |
1 files changed, 1527 insertions, 0 deletions
diff --git a/libs/anr/anr_pdf.h b/libs/anr/anr_pdf.h new file mode 100644 index 0000000..4758c1b --- /dev/null +++ b/libs/anr/anr_pdf.h @@ -0,0 +1,1527 @@ +/* +anr_pdf.h - v0.1 - public domain pdf writer + +This is a single-header-file library for writing pdf files. + +Do this: + #ifdef ANR_PDF_IMPLEMENTATION +before you include this file in *one* C file to create the implementation. + +QUICK NOTES: + Primarily of interest to developers making word processors. + This libray does not do any layout calculations / text wrapping for you. + +LICENSE + See end of file for license information. + +*/ +#ifndef INCLUDE_ANR_PDF_H +#define INCLUDE_ANR_PDF_H + +// DOCUMENTATION +// This library follows the pdf 1.7 ISO 32000-1 standard +// https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf +// +// Coordinates & size are denoted in user space units. (inch / 72). +// When positioning objects, xy (0, 0) is on the bottomleft of the page. +// +// Dates follow ASN.1 format. see chapter 7.9.4. (YYYYMMDDHHmmSSOHH'mm) +// +// ASCII text only. +// +// IMPLEMENTED +// Text (fonts/sizes/colors/spacing/rotation) +// Primitives (lines/polygons/cubic beziers/rectangles) +// Annotations (text/link/markup) + annotation threads +// Page & Document labeling +// Encoding (ASCIIHex) +// Images (rgb) +// TTF embedding +// Bookmarks +// +// UNIMPLEMENTED +// Password encryption (See chapter 7.6.1) +// Links + +#include <inttypes.h> + +#ifndef ANRPDF_ASSERT +#include <assert.h> +#define ANRPDF_ASSERT(x) assert(x) +#endif + +#ifndef ANR_PDF_BUFFER_RESERVE +#define ANR_PDF_BUFFER_RESERVE 1000000 +#endif + +#ifndef ANR_PDF_MAX_PAGES +#define ANR_PDF_MAX_PAGES 2000 +#endif + + +#ifndef ANR_PDF_MAX_CUSTOM_FONTS +#define ANR_PDF_MAX_CUSTOM_FONTS 50 +#endif + + +#ifndef ANR_PDF_MAX_BOOKMARKS +#define ANR_PDF_MAX_BOOKMARKS 2000 +#endif + +#ifndef ANR_PDF_MAX_OBJECTS_PER_PAGE +#define ANR_PDF_MAX_OBJECTS_PER_PAGE 10000 +#endif + +#ifndef ANR_PDF_MAX_ANNOTATIONS_PER_PAGE +#define ANR_PDF_MAX_ANNOTATIONS_PER_PAGE 200 +#endif + + +#ifndef ANRPDFDEF +#ifdef ANR_PDF_STATIC +#define ANRPDFDEF static +#else +#define ANRPDFDEF extern +#endif +#endif + +#define ANR_PDF_PLACEHOLDER_REF "00000000" + +typedef uint64_t anr_pdf_id; + +typedef struct +{ + float x,y; +} anr_pdf_vecf; + +typedef struct +{ + float x,y,w,h; +} anr_pdf_recf; + +#define ANR_PDF_REC(__x,__y,__w,__h) (anr_pdf_recf){.x = __x, .y = __y,.w = __w, .h = __h} + +typedef struct +{ + anr_pdf_id id; + uint64_t offset_in_body; +} anr_pdf_ref; + + +typedef struct +{ + float r,g,b; // colors between 0.0 and 1.0 +} anr_pdf_color; + +#define ANR_PDF_RGB(_r,_g,_b) (anr_pdf_color){_r, _g, _b} + +// Convert inches to user space units. +#define ANR_INCH_TO_USU(_inches) _inches*72 + +typedef enum { + ANR_PDF_PAGE_SIZE_LETTER, + ANR_PDF_PAGE_SIZE_A0, + ANR_PDF_PAGE_SIZE_A1, + ANR_PDF_PAGE_SIZE_A2, + ANR_PDF_PAGE_SIZE_A3, + ANR_PDF_PAGE_SIZE_A4, + ANR_PDF_PAGE_SIZE_A5, + ANR_PDF_PAGE_SIZE_A6, + + ANR_PDF_PAGE_COUNT, +} anr_pdf_page_size; + +typedef struct +{ + anr_pdf_ref ref; + anr_pdf_recf rec; +} anr_pdf_obj; + +typedef struct +{ + anr_pdf_ref ref; + anr_pdf_page_size size; + uint64_t parentoffset; // offset to parent reference. + uint64_t annotoffset; // offset to annot array reference. +} anr_pdf_page; + +typedef struct +{ + anr_pdf_ref ref; + char id[7]; // ImXXXX + uint32_t width; + uint32_t height; +} anr_pdf_img; + +typedef enum +{ + ANR_PDF_ANNOTATION_TEXT, + ANR_PDF_ANNOTATION_LINK, +} anr_pdf_annot_type; + +typedef struct +{ + anr_pdf_ref ref; + anr_pdf_page parent; + anr_pdf_annot_type type; +} anr_pdf_annot; + +typedef struct +{ + uint64_t parent_index; // if no parent = -1 + uint32_t children_count; + uint64_t index; + const char* text; // needs to be valid untill end of document. + anr_pdf_obj item_on_page; + anr_pdf_page page; + uint64_t prev_index; // prev item on same level. first item = -1 + uint64_t next_index; // next item on same level. last item = -1 + uint64_t first_child_index; // if no children = -1 + uint64_t last_child_index; // if no children = -1 + uint32_t depth; +} anr_pdf_bookmark; + +typedef enum +{ + ANR_PDF_ALIGN_LEFT, + ANR_PDF_ALIGN_CENTER, + ANR_PDF_ALIGN_RIGHT, +} anr_pdf_align; + +// Size = inches * 72 +anr_pdf_vecf __anr_pdf_page_sizes[ANR_PDF_PAGE_COUNT] = +{ + {.x = ANR_INCH_TO_USU(08.5), .y = ANR_INCH_TO_USU(11.0)}, // Letter + {.x = ANR_INCH_TO_USU(33.1), .y = ANR_INCH_TO_USU(46.8)}, // A0 + {.x = ANR_INCH_TO_USU(23.4), .y = ANR_INCH_TO_USU(33.1)}, // A1 + {.x = ANR_INCH_TO_USU(16.5), .y = ANR_INCH_TO_USU(23.4)}, // A2 + + {.x = ANR_INCH_TO_USU(11.7), .y = ANR_INCH_TO_USU(16.5)}, // A3 + {.x = ANR_INCH_TO_USU(8.3), .y = ANR_INCH_TO_USU(11.7)}, // A4 + {.x = ANR_INCH_TO_USU(5.8), .y = ANR_INCH_TO_USU(8.3)}, // A5 + {.x = ANR_INCH_TO_USU(4.1), .y = ANR_INCH_TO_USU(5.8)}, // A6 +}; + +typedef enum +{ + ANR_PDF_TEXT_RENDERING_FILL = 0, + ANR_PDF_TEXT_RENDERING_STROKE = 1, + ANR_PDF_TEXT_RENDERING_STROKETHENFILL = 2, +} anr_pdf_text_rendering_mode; + +// See Table 105, 74. +// Parameters for text state and color. +typedef struct +{ + float char_space; // default 0 + float word_space; // default 0 + float horizontal_scale; // default 100 + float leading; // default 0 + uint16_t font_size; // default to 12 + anr_pdf_ref font; // default to document font + anr_pdf_text_rendering_mode render_mode; // default 0 (see table 106) + float rise; // default 0, can be negative + anr_pdf_color color; // default black + float angle; // default 0 +} anr_pdf_txt_conf; + +typedef enum +{ + ANR_PDF_LINECAP_BUTT = 0, + ANR_PDF_LINECAP_ROUNDED = 1, + ANR_PDF_LINECAP_SQUARE = 2, +} anr_pdf_linecap_style; + +typedef enum +{ + ANR_PDF_LINEJOIN_MITER = 0, + ANR_PDF_LINEJOIN_ROUND = 1, + ANR_PDF_LINEJOIN_BEVEL = 2, +} anr_pdf_linejoin_style; + +typedef enum +{ + ANR_PDF_ANNOTATION_MARKUP_HIGHLIGHT, + ANR_PDF_ANNOTATION_MARKUP_UNDERLINE, + ANR_PDF_ANNOTATION_MARKUP_SQUIGGLY, + ANR_PDF_ANNOTATION_MARKUP_STRIKEOUT, +} anr_pdf_annotation_markup_type; + +// See Table 52 +// Parameters for all graphics. +typedef struct +{ + anr_pdf_linecap_style line_cap; // default 0 + int line_width; // default 0 = smallest possible line depending on device. + anr_pdf_linejoin_style line_join; // default 0 + float miter_limit; // default 10, only applicable when line_join = ANR_PDF_LINEJOIN_MITER, must be > 0 + int dash_pattern[2]; // default = empty = solid line + anr_pdf_color color; // default black + // @Unimplemented: automatic stroke adjustment, op SA, see table 58 + char fill; +} anr_pdf_gfx_conf; + +// Parameters for annotations. all optional. +typedef struct +{ + char* posted_by; // default NULL + char* post_date; // default NULL + anr_pdf_color color; // default yellow + anr_pdf_annot parent; // default none +} anr_pdf_annot_cnf; + +#define ANR_PDF_TXT_CONF_DEFAULT anr_pdf_txt_conf_default() +#define ANR_PDF_GFX_CONF_DEFAULT anr_pdf_gfx_conf_conf_default() +#define ANR_PDF_ANNOT_CONF_DEFAULT anr_pdf_annot_conf_default() + +typedef enum +{ + ANR_PDF_STREAM_ENCODE_NONE, + ANR_PDF_STREAM_ENCODE_ASCIIHEX, + // @Unimplemented: Base85 encoding +} anr_pdf_stream_encoding; + +typedef struct +{ + // Main data buffer + char* body_buffer; + uint64_t body_write_cursor; + uint32_t buf_size; + uint64_t next_obj_id; + + // Stream encoding state + anr_pdf_stream_encoding stream_encoding; + char writing_to_stream; + + // Xref data + struct { + char* buffer; + uint64_t write_cursor; + uint32_t buf_size; + } xref; + + // Current page data + struct { + char is_written; + anr_pdf_page_size size; + anr_pdf_ref objects[ANR_PDF_MAX_OBJECTS_PER_PAGE]; + uint64_t objects_count; + anr_pdf_img images[ANR_PDF_MAX_OBJECTS_PER_PAGE]; + uint32_t images_count; + } page; + + // list of pages + anr_pdf_page pages[ANR_PDF_MAX_PAGES]; + uint64_t page_count; + + anr_pdf_annot all_annotations[ANR_PDF_MAX_ANNOTATIONS_PER_PAGE*ANR_PDF_MAX_PAGES]; + uint64_t all_annotations_count; + + // Standard objects + anr_pdf_ref pagetree_ref; + anr_pdf_ref catalog_ref; + anr_pdf_ref doc_info_dic_ref; // Can be empty + + // Bookmark list + anr_pdf_bookmark bookmarks[ANR_PDF_MAX_BOOKMARKS]; + uint64_t bookmark_count; + + // Fonts + anr_pdf_ref default_font_ref; + anr_pdf_ref default_font_italic_ref; + anr_pdf_ref default_font_bold_ref; + anr_pdf_ref default_font_italic_bold_ref; + anr_pdf_ref custom_fonts[ANR_PDF_MAX_CUSTOM_FONTS]; + uint32_t custom_fonts_count; +} anr_pdf; + + +// === DOCUMENT OPERATIONS === +ANRPDFDEF anr_pdf* anr_pdf_document_begin(); +ANRPDFDEF void anr_pdf_document_end(anr_pdf* pdf); +ANRPDFDEF void anr_pdf_document_free(anr_pdf* pdf); +ANRPDFDEF void anr_pdf_write_to_file(anr_pdf* pdf, const char* path); +// item_on_page optional, parent optional. +ANRPDFDEF anr_pdf_bookmark anr_pdf_document_add_bookmark(anr_pdf* pdf, anr_pdf_page page, anr_pdf_obj* item_on_page, + anr_pdf_bookmark* parent, const char* text); +// Add document information to the pdf-> See chapter 14.3.3 +// Dates follow ASN.1 format. see chapter 7.9.4. (YYYYMMDDHHmmSSOHH'mm) @Unimplemented: create helper function for this +ANRPDFDEF void anr_pdf_document_add_information_dictionary(anr_pdf* pdf, char* title, + char* author, char* subject, char* keywords, char* creator, + char* producer, char* creation_date, char* mod_date); + +// === PAGE OPERATIONS === +ANRPDFDEF void anr_pdf_page_begin(anr_pdf* pdf, anr_pdf_page_size size); +ANRPDFDEF anr_pdf_page anr_pdf_page_end(anr_pdf* pdf); +ANRPDFDEF anr_pdf_vecf anr_pdf_page_get_size(anr_pdf_page_size size); // Returns the size of the page in user space units. (inches * 72) + +// === ANNOTATION OPERATIONS === +ANRPDFDEF anr_pdf_annot anr_pdf_add_annotation_markup(anr_pdf* pdf, anr_pdf_page page, anr_pdf_obj obj, char* text, anr_pdf_annotation_markup_type type, anr_pdf_annot_cnf data); +ANRPDFDEF anr_pdf_annot anr_pdf_add_annotation_text(anr_pdf* pdf, anr_pdf_page page, anr_pdf_obj obj, char* text, anr_pdf_annot_cnf data); +ANRPDFDEF anr_pdf_annot anr_pdf_add_annotation_link(anr_pdf* pdf, anr_pdf_page src_page, anr_pdf_obj src_obj, anr_pdf_page dest_page, anr_pdf_obj* dest_obj, anr_pdf_annot_cnf data); + +// === OBJECT OPERATIONS (pdf native) === +ANRPDFDEF anr_pdf_obj anr_pdf_add_text(anr_pdf* pdf, const char* text, float x, float y, anr_pdf_txt_conf info); +ANRPDFDEF anr_pdf_obj anr_pdf_add_line(anr_pdf* pdf, anr_pdf_vecf p1, anr_pdf_vecf p2, anr_pdf_gfx_conf gfx); +ANRPDFDEF anr_pdf_obj anr_pdf_add_polygon(anr_pdf* pdf, anr_pdf_vecf* data, uint32_t data_length, anr_pdf_gfx_conf gfx); +ANRPDFDEF anr_pdf_obj anr_pdf_add_cubic_bezier(anr_pdf* pdf, anr_pdf_vecf* data, uint32_t data_length, anr_pdf_gfx_conf gfx); +ANRPDFDEF anr_pdf_obj anr_pdf_add_image(anr_pdf* pdf, anr_pdf_img img, float x, float y, float w, float h); + +// === OBJECT OPERATIONS (wrappers) === +ANRPDFDEF anr_pdf_obj anr_pdf_add_page_label(anr_pdf* pdf, const char* text, anr_pdf_align align); +ANRPDFDEF anr_pdf_obj anr_pdf_add_table(anr_pdf* pdf, float* rows, uint32_t row_count, float* cols, uint32_t col_count, anr_pdf_color color); +ANRPDFDEF anr_pdf_obj anr_pdf_add_rectangle(anr_pdf* pdf, anr_pdf_vecf tl, anr_pdf_vecf br, char fill, anr_pdf_color color); + +// === FILE EMBEDDING === + // Image data is assumed to be in rgb color space. 3 bytes per pixel. +ANRPDFDEF anr_pdf_img anr_pdf_embed_image(anr_pdf* pdf, unsigned char* data, uint32_t length, uint32_t width, uint32_t height, uint8_t bits_per_sample); +ANRPDFDEF anr_pdf_ref anr_pdf_embed_ttf(anr_pdf* pdf, unsigned char* data, uint32_t length); + +// === DEFAULT CONFIGS === +ANRPDFDEF anr_pdf_txt_conf anr_pdf_txt_conf_default(); +ANRPDFDEF anr_pdf_gfx_conf anr_pdf_gfx_conf_conf_default(); +ANRPDFDEF anr_pdf_annot_cnf anr_pdf_annot_conf_default(); + +//// end header file ///////////////////////////////////////////////////// +#endif // End of INCLUDE_ANR_PDF_H + +#ifdef ANR_PDF_IMPLEMENTATION + +static FILE* anr__pdf_fopen(const char* filename, const char* mode) +{ + FILE* f; + f = fopen(filename, mode); + return f; +} + +static void anr__pdf_fclose(FILE* file) +{ + fflush(file); + fclose(file); +} + +static anr_pdf_obj anr__pdf_emptyobj() +{ + return (anr_pdf_obj){0}; +} + +static anr_pdf_ref anr__pdf_emptyref() +{ + return (anr_pdf_ref){0}; +} + +static char anr__pdf_ref_valid(anr_pdf_ref ref) +{ + return ref.id > 0; +} + +static char* anr__pdf_encode_asciihex(const char* src, char* dest, uint64_t src_size, uint64_t dest_size) +{ + for (uint64_t i = 0; i < src_size; i++) { + dest[i*2] = "0123456789ABCDEF"[src[i] >> 4]; + dest[i*2+1] = "0123456789ABCDEF"[src[i] & 0x0F]; + } + return dest; +} + +static uint64_t anr__pdf_append_bytes(anr_pdf* pdf, const char* bytes, uint64_t size) +{ + char* encoded_data = malloc(size*2 + 1); // asciihex uses at most size*2 + char* ptr = (char*)encoded_data; + + // Encode data + if (pdf->writing_to_stream) { + switch(pdf->stream_encoding) { + case ANR_PDF_STREAM_ENCODE_NONE: ptr = (char*)bytes; break; + case ANR_PDF_STREAM_ENCODE_ASCIIHEX: + ptr = anr__pdf_encode_asciihex(bytes, ptr, size, sizeof(encoded_data)); + size = size*2; + break; + } + } + else { + ptr = (char*)bytes; + } + + // check buffer bounds + while (pdf->body_write_cursor + size >= pdf->buf_size) + { + pdf->buf_size += ANR_PDF_BUFFER_RESERVE; + pdf->body_buffer = realloc(pdf->body_buffer, pdf->buf_size); + } + + memcpy(pdf->body_buffer + pdf->body_write_cursor, ptr, size); + uint64_t result = pdf->body_write_cursor; + pdf->body_write_cursor += size; + free(encoded_data); + return result; +} + +static uint64_t anr__pdf_append_str(anr_pdf* pdf, const char* bytes) +{ + uint64_t length = strlen(bytes); + return anr__pdf_append_bytes(pdf, bytes, length); +} + +static uint64_t anr__pdf_append_str_idref(anr_pdf* pdf, const char* bytes, anr_pdf_ref ref) +{ + char formatted_str[300]; + sprintf(formatted_str, bytes, ref.id); + + uint64_t length = strlen(formatted_str); + uint64_t result = anr__pdf_append_bytes(pdf, formatted_str, length); + return result; +} + +static uint64_t anr__pdf_append_printf(anr_pdf* pdf, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + char formatted_str[300]; + vsnprintf(formatted_str, 300, fmt, ap); + + uint64_t length = strlen(formatted_str); + uint64_t result = anr__pdf_append_bytes(pdf, formatted_str, length); + + va_end(ap); + return result; +} + +static anr_pdf_id anr__peak_next_id(anr_pdf* pdf) +{ + return pdf->next_obj_id; +} + +static anr_pdf_ref anr__pdf_begin_obj(anr_pdf* pdf) +{ + #define XREF_ENTRY_SIZE 20 + anr_pdf_id id = pdf->next_obj_id++; + char xref_entry[XREF_ENTRY_SIZE]; + memcpy(xref_entry, "0000000000 00000 n \n", XREF_ENTRY_SIZE); + + char offset_str[11]; + sprintf(offset_str, "%" PRId64 , pdf->body_write_cursor); + memcpy(xref_entry + 10 - strlen(offset_str), offset_str, strlen(offset_str)); + + // check buffer bounds + while (pdf->xref.write_cursor + XREF_ENTRY_SIZE >= pdf->xref.buf_size) + { + pdf->xref.buf_size += ANR_PDF_BUFFER_RESERVE; + pdf->xref.buffer = realloc(pdf->xref.buffer, pdf->xref.buf_size); + } + + memcpy(pdf->xref.buffer + pdf->xref.write_cursor, xref_entry, XREF_ENTRY_SIZE); + pdf->xref.write_cursor += XREF_ENTRY_SIZE; + + char newobj_entry[30]; + sprintf(newobj_entry, "\n%" PRId64 " 0 obj", id); + anr__pdf_append_str(pdf, newobj_entry); + + return (anr_pdf_ref){.id = id, .offset_in_body = pdf->body_write_cursor}; +} + +static uint64_t anr__pdf_end_content_obj(anr_pdf* pdf, uint64_t write_start) +{ + pdf->writing_to_stream = 0; + + // Some encodings have EOD sign + switch(pdf->stream_encoding) + { + case ANR_PDF_STREAM_ENCODE_NONE: break; + case ANR_PDF_STREAM_ENCODE_ASCIIHEX: anr__pdf_append_str(pdf, ">"); break; + } + + uint64_t write_end = anr__pdf_append_str(pdf, "\nendstream"); + anr__pdf_append_str(pdf, "\nendobj"); + + uint64_t stream_length = write_end - write_start; + + // Object containing stream length. + anr__pdf_begin_obj(pdf); + anr__pdf_append_printf(pdf, "\n%d", stream_length); + anr__pdf_append_str(pdf, "\nendobj"); + return write_end; +} + +static anr_pdf_obj anr__pdf_begin_content_obj(anr_pdf* pdf, anr_pdf_recf rec) +{ + ANRPDF_ASSERT(!pdf->page.is_written); + ANRPDF_ASSERT(pdf->page.objects_count < ANR_PDF_MAX_OBJECTS_PER_PAGE); + anr_pdf_ref ref = anr__pdf_begin_obj(pdf); + anr_pdf_id streamlen_id = anr__peak_next_id(pdf); // next object is stream length + anr__pdf_append_str_idref(pdf, "\n<< /Length %d 0 R", (anr_pdf_ref){.id=streamlen_id}); + switch(pdf->stream_encoding) + { + case ANR_PDF_STREAM_ENCODE_NONE: break; + case ANR_PDF_STREAM_ENCODE_ASCIIHEX: anr__pdf_append_str(pdf, "\n/Filter /ASCIIHexDecode"); break; + } + + anr__pdf_append_str(pdf, ">>\nstream\n"); + pdf->writing_to_stream = 1; + anr_pdf_obj objref = {.ref = ref, .rec = rec}; + pdf->page.objects[pdf->page.objects_count++] = ref; + return objref; +} + +static void anr__append_xref_table(anr_pdf* pdf) +{ + // Cross reference table + uint64_t refxref = anr__pdf_append_str(pdf, "\nxref"); + anr__pdf_append_printf(pdf, "\n0 %d", pdf->next_obj_id); + anr__pdf_append_str(pdf, "\n0000000000 65535 f \n"); + anr__pdf_append_bytes(pdf, pdf->xref.buffer, pdf->xref.write_cursor); + + // Trailer + anr__pdf_append_str(pdf, "trailer\n<<"); + anr__pdf_append_printf(pdf, "/Size %d\n", pdf->next_obj_id); + anr__pdf_append_str_idref(pdf, "/Root %d 0 R", pdf->catalog_ref); + if (anr__pdf_ref_valid(pdf->doc_info_dic_ref)) { + anr__pdf_append_str_idref(pdf, "/Info %d 0 R", pdf->doc_info_dic_ref); + } + anr__pdf_append_str(pdf, ">>\nstartxref"); + anr__pdf_append_printf(pdf, "\n%d", refxref+1); +} + +static anr_pdf_ref anr__pdf_append_outlines(anr_pdf* pdf) +{ + anr_pdf_id id_offset = anr__peak_next_id(pdf); // ids for bookmarks will be index+id_offset + + anr_pdf_ref first_bookmark = anr__pdf_emptyref(); + anr_pdf_ref last_bookmark = anr__pdf_emptyref(); + for (int i = 0; i < pdf->bookmark_count; i++) + { + anr_pdf_bookmark current_bookmark = pdf->bookmarks[i]; + + anr_pdf_ref ref = anr__pdf_begin_obj(pdf); + if (i == 0) first_bookmark = ref; + last_bookmark = ref; + anr__pdf_append_printf(pdf, "\n<<\n/Title (%s)", current_bookmark.text); + if (current_bookmark.parent_index != -1) + anr__pdf_append_printf(pdf, "\n/Parent %d 0 R", id_offset + current_bookmark.parent_index); + if (current_bookmark.prev_index != -1) + anr__pdf_append_printf(pdf, "\n/Prev %d 0 R", id_offset + current_bookmark.prev_index); + if (current_bookmark.next_index != -1) + anr__pdf_append_printf(pdf, "\n/Next %d 0 R", id_offset + current_bookmark.next_index); + if (current_bookmark.first_child_index != -1) + anr__pdf_append_printf(pdf, "\n/First %d 0 R", id_offset + current_bookmark.first_child_index); + if (current_bookmark.last_child_index != -1) + anr__pdf_append_printf(pdf, "\n/Last %d 0 R", id_offset + current_bookmark.last_child_index); + if (current_bookmark.children_count != -1) + anr__pdf_append_printf(pdf, "\n/Count %d", current_bookmark.children_count); + + float offset_bottom = anr_pdf_page_get_size(current_bookmark.page.size).y + 2; + if (anr__pdf_ref_valid(current_bookmark.item_on_page.ref)) { + offset_bottom = current_bookmark.item_on_page.rec.y; + } + anr__pdf_append_printf(pdf, "\n/Dest [%d 0 R /XYZ 0 %.2f 0]", current_bookmark.page.ref.id, offset_bottom); + + // @Unimplemented "C" color key (see Table 153) + // @Unimplemented "F" font flag (see Table 153) + + anr__pdf_append_str(pdf, "\n>>"); + anr__pdf_append_str(pdf, "\nendobj"); + } + + anr_pdf_ref outlineref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n<</Type /Outlines"); + anr__pdf_append_printf(pdf, "\n/Count %d", pdf->bookmark_count); + if (anr__pdf_ref_valid(first_bookmark)) { + anr__pdf_append_str_idref(pdf, "\n/First %d 0 R", first_bookmark); + anr__pdf_append_str_idref(pdf, "\n/Last %d 0 R", last_bookmark); + } + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nendobj"); + + return outlineref; +} + +static void anr__pdf_replace_placeholder_id(anr_pdf* pdf, uint64_t offset, anr_pdf_ref ref) +{ + // We write the pdf in one go but some objects need to reference eachother.. :( + char idbuf[20]; + sprintf(idbuf, "%" PRId64, ref.id); + int len = strlen(idbuf); + memcpy(pdf->body_buffer + offset + (sizeof(ANR_PDF_PLACEHOLDER_REF)-1-len), idbuf, len); +} + +// Document catalog, see 7.7.2 +static void anr__pdf_append_document_catalog(anr_pdf* pdf) +{ + // Outlines + anr_pdf_ref outlineref = anr__pdf_append_outlines(pdf); + + // Page tree + anr_pdf_ref treeref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n<</Type /Pages"); + + anr__pdf_append_str(pdf, "\n/Kids ["); + for (uint64_t i = 0; i < pdf->page_count; i++) + anr__pdf_append_str_idref(pdf, "%d 0 R\n", pdf->pages[i].ref); + anr__pdf_append_str(pdf, "]"); + + anr__pdf_append_printf(pdf, "\n/Count %d", pdf->page_count); + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nendobj"); + + pdf->pagetree_ref = treeref; + + // Catalog + anr_pdf_ref docref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n<</Type /Catalog"); + anr__pdf_append_str_idref(pdf, "\n/Outlines %d 0 R", outlineref); + // @Unimplemented: pagemode (see Table 28) + anr__pdf_append_str_idref(pdf, "\n/Pages %d 0 R", treeref); + anr__pdf_append_str(pdf, "\n/PageLabels << /Nums [ 0 << /S /D >> ] >>"); // arabic numerals for page numbering. + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nendobj"); + + // Pages need reference to page tree. + for (uint64_t i = 0; i < pdf->page_count; i++) + { + anr__pdf_replace_placeholder_id(pdf, pdf->pages[i].parentoffset, treeref); + } + + // Pages need reference to annotation array. + for (uint64_t p = 0; p < pdf->page_count; p++) + { + anr_pdf_ref ref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n["); + for (uint64_t i = 0; i < pdf->all_annotations_count; i++) { + if (pdf->all_annotations[i].parent.ref.id != pdf->pages[p].ref.id) continue; + anr__pdf_append_str_idref(pdf, "\n%d 0 R", pdf->all_annotations[i].ref); + } + anr__pdf_append_str(pdf, "\n]"); + anr__pdf_append_str(pdf, "\nendobj"); + + anr__pdf_replace_placeholder_id(pdf, pdf->pages[p].annotoffset, ref); + } + + pdf->catalog_ref = docref; +} + +static void anr__create_default_font(anr_pdf* pdf) { + #define LOAD_FONT(_name, _container) { \ + anr_pdf_ref fontref = anr__pdf_begin_obj(pdf); \ + anr__pdf_append_str(pdf, "\n<< /Type /Font"); \ + anr__pdf_append_str(pdf, "\n/Subtype /Type1"); \ + anr__pdf_append_str_idref(pdf, "\n/Name /F%d", fontref); \ + anr__pdf_append_str(pdf, "\n/BaseFont /"_name); \ + anr__pdf_append_str(pdf, "\n/Encoding /WinAnsiEncoding"); \ + anr__pdf_append_str(pdf, ">>"); \ + anr__pdf_append_str(pdf, "\nendobj"); \ + _container = fontref; \ + } + + LOAD_FONT("Times-Roman", pdf->default_font_ref); + LOAD_FONT("Times-Italic", pdf->default_font_italic_ref); + LOAD_FONT("Times-Bold", pdf->default_font_bold_ref); + LOAD_FONT("Times-BoldItalic", pdf->default_font_italic_bold_ref); +} + +anr_pdf* anr_pdf_document_begin() +{ + anr_pdf* pdf = malloc(sizeof(anr_pdf)); + memset(pdf, 0, sizeof(anr_pdf)); + pdf->body_buffer = malloc(ANR_PDF_BUFFER_RESERVE); + pdf->buf_size = ANR_PDF_BUFFER_RESERVE; + pdf->body_write_cursor = 0; + pdf->next_obj_id = 1; + pdf->doc_info_dic_ref = anr__pdf_emptyref(); + pdf->stream_encoding = ANR_PDF_STREAM_ENCODE_NONE; + pdf->writing_to_stream = 0; + + pdf->xref.buffer = malloc(ANR_PDF_BUFFER_RESERVE); + pdf->xref.write_cursor = 0; + pdf->xref.buf_size = ANR_PDF_BUFFER_RESERVE; + + pdf->page.is_written = 1; + + anr__pdf_append_str(pdf, "%PDF-1.7"); + anr__create_default_font(pdf); + + return pdf; +} + +void anr_pdf_document_free(anr_pdf* pdf) +{ + free(pdf->body_buffer); + free(pdf->xref.buffer); + free(pdf); +} + +void anr_pdf_document_end(anr_pdf* pdf) +{ + anr__pdf_append_document_catalog(pdf); + anr__append_xref_table(pdf); + anr__pdf_append_str(pdf, "\n%%EOF\n"); +} + +void anr_pdf_write_to_file(anr_pdf* pdf, const char* path) +{ + FILE* file = anr__pdf_fopen(path, "wb"); + fwrite(pdf->body_buffer, 1, pdf->body_write_cursor, file); + anr__pdf_fclose(file); +} + +void anr_pdf_page_begin(anr_pdf* pdf, anr_pdf_page_size size) +{ + ANRPDF_ASSERT(pdf->page.is_written); + ANRPDF_ASSERT(pdf->page_count < ANR_PDF_MAX_PAGES); + pdf->page.is_written = 0; + pdf->page.objects_count = 0; + pdf->page.images_count = 0; + pdf->page.size = size; + // @Unimplemented: page rotation +} + +anr_pdf_page anr_pdf_page_end(anr_pdf* pdf) +{ + ANRPDF_ASSERT(!pdf->page.is_written); + + anr_pdf_ref procsetref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n[/PDF /Text /ImageB /ImageC /ImageI]"); + anr__pdf_append_str(pdf, "\nendobj"); + + anr_pdf_ref extgstateref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n<< /Type /ExtGState /SA true >>"); + anr__pdf_append_str(pdf, "\nendobj"); + + + anr_pdf_ref pageref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n<</Type /Page"); + uint64_t offset = anr__pdf_append_str(pdf, "\n/Parent "ANR_PDF_PLACEHOLDER_REF" 0 R"); // ref is set when pagetree is apended.. + anr__pdf_append_str_idref(pdf, "\n/Resources <</ProcSet %d 0 R", procsetref); + anr__pdf_append_str_idref(pdf, "\n/ExtGState <</GS1 %d 0 R >>", extgstateref); + + // Import all default fonts in page. + anr__pdf_append_str(pdf, "\n/Font <<"); + anr__pdf_append_printf(pdf, "\n/F%d %d 0 R", pdf->default_font_ref.id, pdf->default_font_ref.id); + anr__pdf_append_printf(pdf, "\n/F%d %d 0 R", pdf->default_font_italic_ref.id, pdf->default_font_italic_ref.id); + anr__pdf_append_printf(pdf, "\n/F%d %d 0 R", pdf->default_font_bold_ref.id, pdf->default_font_bold_ref.id); + anr__pdf_append_printf(pdf, "\n/F%d %d 0 R", pdf->default_font_italic_bold_ref.id, pdf->default_font_italic_bold_ref.id); + for (uint32_t i = 0; i < pdf->custom_fonts_count; i++) + anr__pdf_append_printf(pdf, "\n/F%d %d 0 R", pdf->custom_fonts[i].id, pdf->custom_fonts[i].id); + anr__pdf_append_str(pdf, "\n>>"); + + // Add all images to xobject array. + if (pdf->page.images_count) { + anr__pdf_append_str(pdf, "\n/XObject <<\n"); + for (uint64_t i = 0; i < pdf->page.images_count; i++) + anr__pdf_append_printf(pdf, "/%s %" PRId64 " 0 R\n", pdf->page.images[i].id, pdf->page.images[i].ref.id); + anr__pdf_append_str(pdf, "\n>>"); + } + + anr__pdf_append_str(pdf, "\n>>"); + + anr_pdf_vecf page_size = anr_pdf_page_get_size(pdf->page.size); + anr__pdf_append_printf(pdf, "\n/MediaBox [0 0 %.3f %.3f]", page_size.x, page_size.y); + uint64_t annotoffset = anr__pdf_append_str(pdf, "\n/Annots "ANR_PDF_PLACEHOLDER_REF" 0 R"); // ref is set when pagetree is appended.. + + // Add all objects to content array. + anr__pdf_append_str(pdf, "\n/Contents [\n"); + for (uint64_t i = 0; i < pdf->page.objects_count; i++) + anr__pdf_append_str_idref(pdf, "%d 0 R\n", pdf->page.objects[i]); + anr__pdf_append_str(pdf, "]>>"); + + anr__pdf_append_str(pdf, "\nendobj"); + + + anr_pdf_page page = (anr_pdf_page){.ref = pageref, .size = pdf->page.size, .parentoffset = offset+9, .annotoffset = annotoffset + 9}; + pdf->pages[pdf->page_count++] = page; + pdf->page.is_written = 1; + + return page; +} + +anr_pdf_txt_conf anr_pdf_txt_conf_default() +{ + return (anr_pdf_txt_conf){0.0f,0.0f,100.0f,0.0f,12,anr__pdf_emptyref(),ANR_PDF_TEXT_RENDERING_FILL, 0.0f, (anr_pdf_color){0.0f,0.0f,0.0f}, 0.0f}; +} + +anr_pdf_gfx_conf anr_pdf_gfx_conf_conf_default() +{ + return (anr_pdf_gfx_conf){.line_cap = ANR_PDF_LINECAP_BUTT, .line_width = 0, .line_join = ANR_PDF_LINEJOIN_MITER, + .miter_limit = 10, .dash_pattern = {0}, .color = ANR_PDF_RGB(0.0f,0.0f,0.0f), 0}; +} + +anr_pdf_annot_cnf anr_pdf_annot_conf_default() +{ + return (anr_pdf_annot_cnf){.color=ANR_PDF_RGB(1.0f, 1.0f, 0.0f), .parent = {.ref.id = 0}, .post_date = NULL, .posted_by = NULL}; +} + +anr_pdf_obj anr_pdf_add_cubic_bezier(anr_pdf* pdf, anr_pdf_vecf* data, uint32_t data_length, anr_pdf_gfx_conf gfx) +{ + ANRPDF_ASSERT(data_length >= 3); + ANRPDF_ASSERT((data_length - 3) % 2 == 0); + ANRPDF_ASSERT(gfx.miter_limit > 0); // no warning given & gfx will not be displayed if 0 + + // Calculate bounds + anr_pdf_recf rec = {0}; + for (uint32_t i = 0; i < data_length; i++) + { + anr_pdf_vecf point = data[i]; + if (point.x < rec.x) rec.x = point.x; + if (point.y > rec.y) rec.y = point.y; + if (point.x > rec.w) rec.w = point.x; + if (point.y < rec.h) rec.h = point.y; + } + rec.w = rec.w - rec.x; + rec.h = rec.y - rec.h; + + anr_pdf_obj obj_ref = anr__pdf_begin_content_obj(pdf, rec); + + uint64_t write_start = anr__pdf_append_printf(pdf, "\n%d J", gfx.line_cap); + anr__pdf_append_printf(pdf, "\n%d w", gfx.line_width); + anr__pdf_append_printf(pdf, "\n%d j", gfx.line_join); + anr__pdf_append_printf(pdf, "\n%.3f M", gfx.miter_limit); + if (gfx.dash_pattern[0] > 0 && gfx.dash_pattern[1] > 0) { + anr__pdf_append_printf(pdf, "\n[%d %d] 0 d", gfx.dash_pattern[0], gfx.dash_pattern[1]); + } + anr__pdf_append_printf(pdf, "\n%.1f %.1f %.1f %s", gfx.color.r, gfx.color.g, gfx.color.b, gfx.fill ? "rg" : "RG"); + + anr__pdf_append_printf(pdf, "\n%.3f %.3f m", data[0].x, data[0].y); + anr__pdf_append_printf(pdf, "\n%.3f %.3f %.3f %.3f %.3f %.3f c", + data[0].x, data[0].y, data[1].x, data[1].y, data[2].x, data[2].y); + + for (uint32_t i = 3; i < data_length; i+=2) + { + anr_pdf_vecf point1 = data[i]; + anr_pdf_vecf point2 = data[i+1]; + anr__pdf_append_printf(pdf, "\n%.3f %.3f %.3f %.3f v", point1.x, point1.y, point2.x, point2.y); + } + + anr__pdf_append_printf(pdf, gfx.fill ? "\nf" : "\nS"); + anr__pdf_append_printf(pdf, "\nn"); + + anr__pdf_end_content_obj(pdf, write_start); + return obj_ref; +} + +anr_pdf_obj anr_pdf_add_line(anr_pdf* pdf, anr_pdf_vecf p1, anr_pdf_vecf p2, anr_pdf_gfx_conf gfx) +{ + anr_pdf_vecf data[2] = {p1, p2}; + return anr_pdf_add_polygon(pdf, data, 2, gfx); +} + +anr_pdf_obj anr_pdf_add_polygon(anr_pdf* pdf, anr_pdf_vecf* data, uint32_t data_length, anr_pdf_gfx_conf gfx) +{ + ANRPDF_ASSERT(data_length > 0); + ANRPDF_ASSERT(gfx.miter_limit > 0); + + // Calculate bounds + anr_pdf_recf rec = {0}; + for (uint32_t i = 0; i < data_length; i++) + { + anr_pdf_vecf point = data[i]; + if (point.x < rec.x) rec.x = point.x; + if (point.y > rec.y) rec.y = point.y; + if (point.x > rec.w) rec.w = point.x; + if (point.y < rec.h) rec.h = point.y; + } + rec.w = rec.w - rec.x; + rec.h = rec.y - rec.h; + + anr_pdf_obj obj_ref = anr__pdf_begin_content_obj(pdf, rec); + + uint64_t write_start = anr__pdf_append_printf(pdf, "\n%.3f %.3f m", data[0].x, data[0].y); + anr__pdf_append_printf(pdf, "\n%d J", gfx.line_cap); + anr__pdf_append_printf(pdf, "\n%d w", gfx.line_width); + anr__pdf_append_printf(pdf, "\n%d j", gfx.line_join); + anr__pdf_append_printf(pdf, "\n%.3f M", gfx.miter_limit); + if (gfx.dash_pattern[0] > 0 && gfx.dash_pattern[1] > 0) { + anr__pdf_append_printf(pdf, "\n[%d %d] 0 d", gfx.dash_pattern[0], gfx.dash_pattern[1]); + } + anr__pdf_append_printf(pdf, "\n%.1f %.1f %.1f %s", gfx.color.r, gfx.color.g, gfx.color.b, gfx.fill ? "rg" : "RG"); + + for (uint32_t i = 1; i < data_length; i++) + { + anr_pdf_vecf point = data[i]; + anr__pdf_append_printf(pdf, "\n%.3f %.3f l", point.x, point.y); + } + + anr__pdf_append_printf(pdf, gfx.fill ? "\nf" : "\nS"); + anr__pdf_append_printf(pdf, "\nn"); + + anr__pdf_end_content_obj(pdf, write_start); + return obj_ref; +} + +anr_pdf_obj anr_pdf_add_rectangle(anr_pdf* pdf, anr_pdf_vecf tl, anr_pdf_vecf br, char fill, anr_pdf_color color) +{ + anr_pdf_vecf header[] = { tl, (anr_pdf_vecf){br.x, tl.y}, br, (anr_pdf_vecf){tl.x, br.y} }; + anr_pdf_gfx_conf conf = ANR_PDF_GFX_CONF_DEFAULT; + conf.fill = fill; + conf.color = color; + return anr_pdf_add_polygon(pdf, header, 4, conf); +} + +anr_pdf_obj anr_pdf_add_table(anr_pdf* pdf, float* rows, uint32_t row_count, float* cols, uint32_t col_count, anr_pdf_color color) +{ + ANRPDF_ASSERT(row_count > 1); + ANRPDF_ASSERT(col_count > 1); + + float ystart = rows[0]; + float xstart = cols[0]; + float yend = rows[row_count-1]; + float xend = cols[col_count-1]; + + // Header row + anr_pdf_obj header = anr_pdf_add_rectangle(pdf, (anr_pdf_vecf){cols[0], rows[0]}, (anr_pdf_vecf){xend, rows[1]}, 1, color); + + for (int i = 0; i < row_count; i++) + { + anr_pdf_vecf v1 = {xstart, rows[i]}; + anr_pdf_vecf v2 = {xend, rows[i]}; + anr_pdf_add_line(pdf, v1, v2, ANR_PDF_GFX_CONF_DEFAULT); + } + + for (int i = 0; i < col_count; i++) + { + anr_pdf_vecf v1 = {cols[i], ystart}; + anr_pdf_vecf v2 = {cols[i], yend}; + anr_pdf_add_line(pdf, v1, v2, ANR_PDF_GFX_CONF_DEFAULT); + } + + return header; +} + +anr_pdf_obj anr_pdf_add_page_label(anr_pdf* pdf, const char* text, anr_pdf_align align) +{ + anr_pdf_vecf page_size = anr_pdf_page_get_size(pdf->page.size); + float x = ANR_INCH_TO_USU(0.8), y = ANR_INCH_TO_USU(0.5); + + switch (align) + { + case ANR_PDF_ALIGN_LEFT: break; + case ANR_PDF_ALIGN_CENTER: x = page_size.x/2; + break; + case ANR_PDF_ALIGN_RIGHT: x = page_size.x - ANR_INCH_TO_USU(0.8); + break; + } + + return anr_pdf_add_text(pdf, text, x, y, ANR_PDF_TXT_CONF_DEFAULT); +} + +anr_pdf_obj anr_pdf_add_text(anr_pdf* pdf, const char* text, float x, float y, anr_pdf_txt_conf info) +{ + uint64_t str_len = strlen(text); // @Unimplemented: we need to calculate string width somehow + anr_pdf_obj obj_ref = anr__pdf_begin_content_obj(pdf, ANR_PDF_REC(x, y + info.font_size, str_len*5, (float)info.font_size)); + uint64_t write_start = anr__pdf_append_str(pdf, "\nBT"); + + // Use default regular font if no font given. + if (!anr__pdf_ref_valid(info.font)) { + info.font = pdf->default_font_ref; + } + + anr__pdf_append_printf(pdf, "\n/F%d %d Tf", info.font.id, info.font_size); + anr__pdf_append_printf(pdf, "\n%.1f %.1f %.1f rg", info.color.r, info.color.g, info.color.b); + anr__pdf_append_printf(pdf, "\n%.2f Tc", info.char_space); + anr__pdf_append_printf(pdf, "\n%.2f Tw", info.word_space); + anr__pdf_append_printf(pdf, "\n%.2f Tz", info.horizontal_scale); + anr__pdf_append_printf(pdf, "\n%.2f TL", info.leading); + anr__pdf_append_printf(pdf, "\n%.2f Ts", info.rise); + anr__pdf_append_printf(pdf, "\n%d Tr", info.render_mode); + anr__pdf_append_printf(pdf, "\n%f %f %f %f %f %f Tm", cosf(info.angle), sinf(info.angle), -sinf(info.angle), cosf(info.angle), x, y); + anr__pdf_append_printf(pdf, "\nT* (%s) Tj", text); + anr__pdf_append_str(pdf, "\nET"); + + anr__pdf_end_content_obj(pdf, write_start); + return obj_ref; +} + + void anr_pdf_document_add_information_dictionary(anr_pdf* pdf, char* title, + char* author, char* subject, char* keywords, char* creator, + char* producer, char* creation_date, char* mod_date) +{ + // @Unimplemented parameter: Trapped (see Table 317) + + #define ADD_ENTRY(__entry, __tag) \ + if (__entry) {\ + anr__pdf_append_str(pdf, "/"__tag" ("); \ + anr__pdf_append_str(pdf, __entry); \ + anr__pdf_append_str(pdf, ")\n"); } + + pdf->doc_info_dic_ref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "<<"); + ADD_ENTRY(title, "Title"); + ADD_ENTRY(author, "Author"); + ADD_ENTRY(subject, "Subject"); + ADD_ENTRY(keywords, "Keywords"); + ADD_ENTRY(creator, "Creator"); + ADD_ENTRY(producer, "Producer"); + ADD_ENTRY(creation_date, "CreationDate"); + ADD_ENTRY(mod_date, "ModDate"); + + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nendobj"); +} + +anr_pdf_vecf anr_pdf_page_get_size(anr_pdf_page_size size) { + return __anr_pdf_page_sizes[size]; +} + +static void anr__pdf_add_optional_annotation_data(anr_pdf* pdf, anr_pdf_annot_cnf data) +{ + if (data.posted_by) { + anr__pdf_append_printf(pdf, "\n/T (%s)", data.posted_by); + } + if (anr__pdf_ref_valid(data.parent.ref)) { + anr__pdf_append_str(pdf, "\n/RT /R"); + anr__pdf_append_str_idref(pdf, "\n/IRT %d 0 R", data.parent.ref); + } + if (data.post_date) { + anr__pdf_append_printf(pdf, "\n/M (D:%s)", data.post_date); + } + anr__pdf_append_printf(pdf, "\n/C [%.2f %.2f %.2f]", data.color.r, data.color.g, data.color.b); +} + +anr_pdf_annot anr_pdf_add_annotation_link(anr_pdf* pdf, anr_pdf_page src_page, anr_pdf_obj src_obj, anr_pdf_page dest_page, anr_pdf_obj* dest_obj, anr_pdf_annot_cnf data) +{ + ANRPDF_ASSERT(pdf->all_annotations_count < ANR_PDF_MAX_ANNOTATIONS_PER_PAGE); + anr_pdf_ref ref = anr__pdf_begin_obj(pdf); + + anr__pdf_append_str(pdf, "\n<</Type /Annot"); + anr__pdf_append_str(pdf, "\n/Subtype /Link"); + anr__pdf_append_printf(pdf, "\n/Rect [%.2f %.2f %.2f %.2f]", + src_obj.rec.x, src_obj.rec.y, src_obj.rec.x + src_obj.rec.w, src_obj.rec.y - src_obj.rec.h); + if (dest_obj) { + anr__pdf_append_printf(pdf, "\n/Dest [%d 0 R /XYZ %f %f null]", dest_page.ref.id, dest_obj->rec.x, dest_obj->rec.y); + } + else { + anr_pdf_vecf psize = anr_pdf_page_get_size(dest_page.size); + anr__pdf_append_printf(pdf, "\n/Dest [%d 0 R /XYZ %f %f null]", dest_page.ref.id, 0, psize.y); + } + anr__pdf_append_str(pdf, "\n/Border [0 0 0 0]"); + anr__pdf_add_optional_annotation_data(pdf, data); + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nendobj"); + + anr_pdf_annot annot = {.ref = ref, .parent = src_page}; + pdf->all_annotations[pdf->all_annotations_count++] = annot; + + return annot; +} + +anr_pdf_annot anr_pdf_add_annotation_markup(anr_pdf* pdf, anr_pdf_page page, anr_pdf_obj obj, char* text, anr_pdf_annotation_markup_type type, anr_pdf_annot_cnf data) +{ + ANRPDF_ASSERT(pdf->all_annotations_count < ANR_PDF_MAX_ANNOTATIONS_PER_PAGE); + anr_pdf_ref ref = anr__pdf_begin_obj(pdf); + + anr__pdf_append_str(pdf, "\n<</Type /Annot"); + switch(type) { + case ANR_PDF_ANNOTATION_MARKUP_HIGHLIGHT: anr__pdf_append_str(pdf, "\n/Subtype /Highlight"); break; + case ANR_PDF_ANNOTATION_MARKUP_UNDERLINE: anr__pdf_append_str(pdf, "\n/Subtype /Underline"); break; + case ANR_PDF_ANNOTATION_MARKUP_SQUIGGLY: anr__pdf_append_str(pdf, "\n/Subtype /Squiggly"); break; + case ANR_PDF_ANNOTATION_MARKUP_STRIKEOUT: anr__pdf_append_str(pdf, "\n/Subtype /StrikeOut"); break; + } + anr__pdf_append_printf(pdf, "\n/Rect [%.2f %.2f %.2f %.2f]", obj.rec.x, obj.rec.y, obj.rec.x + obj.rec.w, obj.rec.y - obj.rec.h); + obj.rec.y -= 2; + anr__pdf_append_printf(pdf, "\n/QuadPoints [%.2f %.2f %.2f %.2f %.2f %.2f %.2f %.2f]", + obj.rec.x, + obj.rec.y - obj.rec.h, + obj.rec.x + obj.rec.w, + obj.rec.y - obj.rec.h, + obj.rec.x, + obj.rec.y, + obj.rec.x + obj.rec.w, + obj.rec.y); + anr__pdf_add_optional_annotation_data(pdf, data); + anr__pdf_append_printf(pdf, "\n/Contents(%s)", text); + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nendobj"); + + anr_pdf_annot annot = {.ref = ref, .parent = page}; + pdf->all_annotations[pdf->all_annotations_count++] = annot; + + return annot; +} + +anr_pdf_annot anr_pdf_add_annotation_text(anr_pdf* pdf, anr_pdf_page page, anr_pdf_obj obj, char* text, anr_pdf_annot_cnf data) +{ + ANRPDF_ASSERT(pdf->all_annotations_count < ANR_PDF_MAX_ANNOTATIONS_PER_PAGE); + anr_pdf_ref ref = anr__pdf_begin_obj(pdf); + + anr__pdf_append_str(pdf, "\n<</Type /Annot"); + anr__pdf_append_str(pdf, "\n/Subtype /Text"); + anr__pdf_append_printf(pdf, "\n/Rect [%.2f %.2f %.2f %.2f]", obj.rec.x, obj.rec.y, obj.rec.x + obj.rec.w, obj.rec.y - obj.rec.h); + anr__pdf_append_printf(pdf, "\n/Contents (%s)", text); + anr__pdf_add_optional_annotation_data(pdf, data); + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nendobj"); + + anr_pdf_annot annot = {.ref = ref, .parent = page}; + pdf->all_annotations[pdf->all_annotations_count++] = annot; + + return annot; +} + +anr_pdf_bookmark anr_pdf_document_add_bookmark(anr_pdf* pdf, anr_pdf_page page, anr_pdf_obj* item_on_page, + anr_pdf_bookmark* parent, const char* text) +{ + ANRPDF_ASSERT(pdf->bookmark_count < ANR_PDF_MAX_BOOKMARKS); + + uint64_t new_index = pdf->bookmark_count; + uint32_t depth = 0; + + uint64_t prev_last_child = -1; + + // update parent refs. + if (parent) { + if (pdf->bookmarks[parent->index].children_count == 0) { + pdf->bookmarks[parent->index].first_child_index = new_index; + pdf->bookmarks[parent->index].last_child_index = new_index; + } + else { + prev_last_child = pdf->bookmarks[parent->index].last_child_index; + pdf->bookmarks[prev_last_child].next_index = new_index; + pdf->bookmarks[parent->index].last_child_index = new_index; + } + pdf->bookmarks[parent->index].children_count++; + + depth = parent->depth+1; + } + else + { + // update prev and next refs for topmost items. + for (int i = pdf->bookmark_count-1; i >= 0; i--) + { + anr_pdf_bookmark existing_bookmark = pdf->bookmarks[i]; + if (existing_bookmark.depth != depth) continue; + + prev_last_child = pdf->bookmarks[i].index; + pdf->bookmarks[i].next_index = new_index; + break; + } + } + + + anr_pdf_bookmark bookmark = { + .item_on_page = item_on_page == NULL ? anr__pdf_emptyobj() : *item_on_page, + .index = new_index, + .text = text, + .parent_index = parent == NULL ? -1 : parent->index, + .first_child_index = -1, + .last_child_index = -1, + .prev_index = prev_last_child, + .next_index = -1, + .children_count = 0, + .depth = depth, + .page = page, + }; + pdf->bookmarks[pdf->bookmark_count++] = bookmark; + + return bookmark; +} + +anr_pdf_img anr_pdf_embed_image(anr_pdf* pdf, unsigned char* data, uint32_t length, uint32_t width, uint32_t height, uint8_t bits_per_sample) +{ + ANRPDF_ASSERT(length == width*height*3); + + anr_pdf_ref ref = anr__pdf_begin_obj(pdf); + + anr__pdf_append_str(pdf, "\n<</Type /XObject"); + anr__pdf_append_str(pdf, "\n/Subtype /Image"); + anr__pdf_append_printf(pdf, "\n/Width %d", width); + anr__pdf_append_printf(pdf, "\n/Height %d", height); + anr__pdf_append_str(pdf, "\n/ColorSpace /DeviceRGB"); + anr__pdf_append_printf(pdf, "\n/BitsPerComponent %d", bits_per_sample); + anr__pdf_append_str(pdf, "\n/Interpolate true"); + anr__pdf_append_printf(pdf, "\n/Length %d", length); + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nstream\n"); + anr__pdf_append_bytes(pdf, (char*)data, length); + anr__pdf_append_str(pdf, "\nendstream"); + anr__pdf_append_str(pdf, "\nendobj"); + + return (anr_pdf_img){.ref = ref, .width = width, .height = height}; +} + +anr_pdf_obj anr_pdf_add_image(anr_pdf* pdf, anr_pdf_img img, float x, float y, float w, float h) +{ + // add image to page resources. + char id[10]; + snprintf(id, 10, "Im%d", pdf->page.images_count); + memcpy(img.id, id, sizeof(img.id)); + pdf->page.images[pdf->page.images_count++] = img; + + anr_pdf_obj obj_ref = anr__pdf_begin_content_obj(pdf, ANR_PDF_REC(x, y, w, h)); + + uint64_t write_start = anr__pdf_append_str(pdf, "q"); + anr__pdf_append_printf(pdf, "\n%f 0 0 %f %f %f cm", w, h, x, y); // Translate & scale + anr__pdf_append_printf(pdf, "\n/%s Do", id); + anr__pdf_append_str(pdf, "\nQ"); + + anr__pdf_end_content_obj(pdf, write_start); + return obj_ref; +} + +//////////////////////////////////////////////////////////// +// code stripped from stb_truetype.h by Sean Barrett +//////////////////////////////////////////////////////////// + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) +typedef unsigned char stbtt_uint8; +typedef signed char stbtt_int8; +typedef unsigned short stbtt_uint16; +typedef signed short stbtt_int16; +typedef unsigned int stbtt_uint32; +typedef signed int stbtt_int32; +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +int stbtt_FindGlyphIndex(unsigned char* data, uint32_t index_map, int unicode_codepoint) +{ + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + return 0; +} +static stbtt_uint32 stbtt__find_table(unsigned char *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} +void stbtt_GetGlyphHMetrics(unsigned char* data, uint32_t hmtx, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + uint32_t hhea = stbtt__find_table(data, 0, "hhea"); + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(data+hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(data + hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(data + hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(data + hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(data + hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} +void stbtt_GetCodepointHMetrics(unsigned char* data, uint32_t hmtx, uint32_t index_map, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(data, hmtx, stbtt_FindGlyphIndex(data,index_map,codepoint), advanceWidth, leftSideBearing); +} +uint32_t anr__pdf_get_ttf_codepoint_width(unsigned char* data, uint32_t codepoint) +{ + uint32_t hmtx = stbtt__find_table(data, 0, "hmtx"); + uint32_t cmap = stbtt__find_table(data, 0, "cmap"); + + uint32_t numTables = ttUSHORT(data + cmap + 2); + uint32_t index_map = 0; + for (uint32_t i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case 3: + switch (ttUSHORT(data+encoding_record+2)) { + case 1: + case 10: + index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case 0: + index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + + int32_t advance; + int32_t lsb; + stbtt_GetCodepointHMetrics(data, hmtx, index_map, codepoint, &advance, &lsb); + return advance; +} + +//////////////////////////////////////////////////////////// +// end of code from stb_truetype.h +//////////////////////////////////////////////////////////// + +anr_pdf_ref anr_pdf_embed_ttf(anr_pdf* pdf, unsigned char* data, uint32_t length) +{ + ANRPDF_ASSERT(pdf->all_annotations_count < ANR_PDF_MAX_CUSTOM_FONTS); + + anr_pdf_ref ttf_ref = anr__pdf_begin_obj(pdf); + anr__pdf_append_printf(pdf, "\n<</Length %d>>", length); + anr__pdf_append_str(pdf, "\nstream\n"); + anr__pdf_append_bytes(pdf, (char*)data, length); + anr__pdf_append_str(pdf, "\nendstream"); + anr__pdf_append_str(pdf, "\nendobj"); + + anr_pdf_ref widths_ref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n[ "); + { + for (uint32_t i = 0; i < 0xFFFF; i++) + { + int advance = anr__pdf_get_ttf_codepoint_width(data, i); + anr__pdf_append_printf(pdf, "%d ", advance); + } + } + anr__pdf_append_str(pdf, "]\nendobj"); + + // These values have been pulled from my behind. + anr_pdf_ref descriptor_ref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n<</Type /FontDescriptor"); + anr__pdf_append_str_idref(pdf, "\n/FontName /F%d", descriptor_ref); + anr__pdf_append_printf(pdf, "\n/Flags %d", 1 << 5); // nonsymbolic + anr__pdf_append_str(pdf, "\n/FontBBox [-92.773438 -312.01172 1186.52344 1102.05078]"); + anr__pdf_append_str(pdf, "\n/MissingWidth 350"); + anr__pdf_append_str(pdf, "\n/ItalicAngle 0"); + anr__pdf_append_str(pdf, "\n/Ascent 1102.05078"); + anr__pdf_append_str(pdf, "\n/Descent -291.50391"); + anr__pdf_append_str(pdf, "\n/CapHeight 389.16016"); + anr__pdf_append_str(pdf, "\n/StemV 61.035156"); + anr__pdf_append_str_idref(pdf, "\n/FontFile2 %d 0 R", ttf_ref); + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nendobj"); + + anr_pdf_ref ref = anr__pdf_begin_obj(pdf); + anr__pdf_append_str(pdf, "\n<</Type /Font"); + anr__pdf_append_str(pdf, "\n/Subtype /TrueType"); + anr__pdf_append_str_idref(pdf, "\n/Name /F%d", ref); + anr__pdf_append_str_idref(pdf, "\n/BaseFont /F%d", descriptor_ref); + anr__pdf_append_str(pdf, "\n/FirstChar 0"); + anr__pdf_append_str(pdf, "\n/LastChar 255"); + anr__pdf_append_str_idref(pdf, "\n/Widths %d 0 R", widths_ref); + anr__pdf_append_str(pdf, "\n/Encoding /WinAsciEncoding"); + anr__pdf_append_str_idref(pdf, "\n/FontDescriptor %d 0 R", descriptor_ref); + anr__pdf_append_str(pdf, ">>"); + anr__pdf_append_str(pdf, "\nendobj"); + + pdf->custom_fonts[pdf->custom_fonts_count++] = ref; + + return ref; +} + +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2024 Aldrik Ramaekers +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/
\ No newline at end of file |
