/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks. Copyright (C) 2002-2019 by Jin-Hwan Cho and Shunsaku Hirata, the dvipdfmx project team. Copyright (C) 1998, 1999 by Mark A. Wicks This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include "dpx-pdfdev.h" #include #include #include #include #include #include "tectonic_bridge_core.h" #include "dpx-cff.h" #include "dpx-cff_types.h" #include "dpx-cmap.h" #include "dpx-dpxconf.h" #include "dpx-error.h" #include "dpx-fontmap.h" #include "dpx-mem.h" #include "dpx-mfileio.h" #include "dpx-numbers.h" #include "dpx-pdfcolor.h" #include "dpx-pdfdoc.h" #include "dpx-pdfdraw.h" #include "dpx-pdffont.h" #include "dpx-pdfobj.h" #include "dpx-pdfximage.h" #include "dpx-type0.h" struct dev_param { /* Text composition (direction) mode is ignored (always same * as font's writing mode) if autorotate is unset (value zero). */ int autorotate; /* * Ignore color migrated to here. This is device's capacity. * colormode 0 for ignore colors */ int colormode; }; /* * Text handling routines. */ /* Motion state: * GRAPHICS_MODE Initial state (not within BT/ET block nor in string) * TEXT_MODE Text section is started via BT operator but not * in string. * STRING_MODE In string. A string or array of strings are currently * in process. May started '[', '<' or '('. */ #define GRAPHICS_MODE 1 #define TEXT_MODE 2 #define STRING_MODE 3 /* * In PDF, vertical text positioning is always applied when current font * is vertical font. While ASCII pTeX manages current writing direction * and font's WMode separately. * * 000/101 WMODE_HH/VV h(v) font, h(v) direction. * 001 WMODE_HV -90 deg. rotated * 100 WMODE_VH +90 deg. rotated * 011 WMODE_HD +90 deg. rotated * 111 WMODE_VD 180 deg. rotated * In MetaPost PostScript file processing (mp_mode = 1), only HH/VV mode * is applied. */ #define TEXT_WMODE_HH 0 #define TEXT_WMODE_HV 1 #define TEXT_WMODE_VH 4 #define TEXT_WMODE_VV 5 #define TEXT_WMODE_HD 3 #define TEXT_WMODE_VD 7 #define ANGLE_CHANGES(m1,m2) ((abs((m1)-(m2)) % 5) == 0 ? 0 : 1) #define ROTATE_TEXT(m) ((m) != TEXT_WMODE_HH && (m) != TEXT_WMODE_VV) struct text_state { /* Current font. * This is index within dev_fonts. */ int font_id; /* Dvipdfmx does compression of text by doing text positioning * in relative motion and uses string array [(foo) -250 (bar)] * with kerning (negative kern is used for white space as suited * for TeX). This is offset within current string. */ spt_t offset; /* This is reference point of strings. * It may include error correction induced by rounding. */ spt_t ref_x; spt_t ref_y; /* Using text raise and leading is highly recommended for * text extraction to work properly. But not implemented yet. * We can't do consice output for \TeX without this. */ spt_t raise; /* unused */ spt_t leading; /* unused */ /* This is not always text matrix but rather font matrix. * We do not use horizontal scaling in PDF text state parameter * since they always apply scaling in fixed direction regardless * of writing mode. */ struct { double slant; double extend; int rotate; /* TEXT_WMODE_XX */ } matrix; /* Fake bold parameter: * If bold_param is positive, use text rendering mode * fill-then-stroke with stroking line width specified * by bold_param. */ double bold_param; /* Text composition (direction) mode. */ int dir_mode; /* internal */ /* Flag indicating text matrix to be forcibly reset. * Enabled if synthetic font features (slant, extend, etc) * are used for current font or when text rotation mode * changes. */ int force_reset; /* This information is duplicated from dev[font_id].format. * Set to 1 if font is composite (Type0) font. */ int is_mb; }; #define PDF_FONTTYPE_SIMPLE 1 #define PDF_FONTTYPE_BITMAP 2 #define PDF_FONTTYPE_COMPOSITE 3 struct dev_font { /* Needs to be big enough to hold name "Fxxx" * where xxx is number of largest font */ char short_name[16]; /* Resource name */ int used_on_this_page; char *tex_name; /* String identifier of this font */ spt_t sptsize; /* Point size */ /* Returned values from font/encoding layer: * * The font_id and enc_id is font and encoding (CMap) identifier * used in pdf_font or encoding/cmap layer. * The PDF object "resource" is an indirect reference object * pointing font resource of this font. The used_chars is somewhat * misleading, this is actually used_glyphs in CIDFont for Type0 * and is 65536/8 bytes binary data with each bits representing * whether the glyph is in-use or not. It is 256 char array for * simple font. */ int font_id; int enc_id; pdf_obj *resource; char *used_chars; /* Font format: * simple, composite or bitmap. */ int format; /* Writing mode: * Non-zero for vertical. Duplicated from CMap. */ int wmode; /* Syntetic Font: * * We use text matrix for creating extended or slanted font, * but not with font's FontMatrix since TrueType and Type0 * font don't support them. */ double extend; double slant; double bold; /* Boldness prameter */ }; /* * Unit conversion, formatting and others. */ #define TEX_ONE_HUNDRED_BP 6578176 struct dev_unit { double dvi2pts; int min_bp_val; /* Shortest resolvable distance in the output PDF. */ int precision; /* Number of decimal digits (in fractional part) kept. */ }; #define FORMAT_BUF_SIZE 4096 struct pdf_dev { int motion_state; struct dev_param param; struct dev_unit unit; struct text_state text_state; struct dev_font *fonts; int num_dev_fonts; int max_dev_fonts; char format_buffer[FORMAT_BUF_SIZE+1]; }; /* * For a moment declare as static variable here * 2020/07/23 Most of the static variables put into single struct pdev */ static pdf_dev pdev; static pdf_dev * current_device (void) { return &pdev; } static void dev_out (pdf_dev *p, const char *str, size_t len) { pdf_doc_add_page_content(str, len); } static double pdf_dev_unit_dviunit (pdf_dev *p) { assert(p); return (1.0 / p->unit.dvi2pts); } #define DEV_PRECISION_MAX 8 static uint32_t ten_pow[10] = { 1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u }; static double ten_pow_inv[10] = { 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, 0.00000001, 0.000000001 }; #define bpt2spt(p, b) ( (spt_t) round( (b) / (p)->unit.dvi2pts ) ) #define spt2bpt(p, s) ( (s) * (p)->unit.dvi2pts ) #define dround_at(v,p) (ROUND( (v), ten_pow_inv[(p)] )) static unsigned int p_itoa (int value, char *buf) { unsigned int sign, ndigits; char *p = buf; if (value < 0) { *p++ = '-'; value = -value; sign = 1; } else { sign = 0; } ndigits = 0; /* Generate at least one digit in reverse order */ do { p[ndigits++] = (value % 10) + '0'; value /= 10; } while (value != 0); /* Reverse the digits */ { unsigned int i; for (i = 0; i < ndigits / 2 ; i++) { char tmp = p[i]; p[i] = p[ndigits-i-1]; p[ndigits-i-1] = tmp; } } p[ndigits] = '\0'; return (sign ? ndigits + 1 : ndigits); } /* NOTE: Acrobat 5 and prior uses 16.16 fixed point representation for * real numbers. */ static int p_dtoa (double value, int prec, char *buf) { const int32_t p[10] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; double i, f; int32_t g; char *c = buf; int n; if (value < 0) { value = -value; *c++ = '-'; n = 1; } else { n = 0; } f = modf(value, &i); g = (int32_t) (f * p[prec] + 0.5); if (g == p[prec]) { g = 0; i += 1; } if (i) { int m = sprintf(c, "%.0f", i); c += m; n += m; } else if (g == 0) { *(c = buf) = '0'; n = 1; } if (g) { int j = prec; *c++ = '.'; while (j--) { c[j] = (g % 10) + '0'; g /= 10; } c += prec - 1; n += 1 + prec; while (*c == '0') { c--; n--; } } *(++c) = 0; return n; } static int dev_sprint_bp (pdf_dev *p, char *buf, spt_t value, spt_t *error) { double value_in_bp; double error_in_bp; int prec; assert(p); prec = p->unit.precision; value_in_bp = spt2bpt(p, value); if (error) { error_in_bp = value_in_bp - dround_at(value_in_bp, prec); *error = bpt2spt(p, error_in_bp); } return p_dtoa(value_in_bp, prec, buf); } /* They are affected by precision (set at device initialization). */ static size_t pdf_dev_sprint_matrix (pdf_dev *p, char *buf, const pdf_tmatrix *M) { size_t len; int prec0, prec2; assert(p); prec2 = MIN(p->unit.precision + 2, DEV_PRECISION_MAX); prec0 = MAX(p->unit.precision, 2); len = p_dtoa(M->a, prec2, buf); buf[len++] = ' '; len += p_dtoa(M->b, prec2, buf+len); buf[len++] = ' '; len += p_dtoa(M->c, prec2, buf+len); buf[len++] = ' '; len += p_dtoa(M->d, prec2, buf+len); buf[len++] = ' '; len += p_dtoa(M->e, prec0, buf+len); buf[len++] = ' '; len += p_dtoa(M->f, prec0, buf+len); buf[len] = '\0'; /* xxx_sprint_xxx NULL terminates strings. */ return len; } static size_t pdf_dev_sprint_rect (pdf_dev *p, char *buf, const pdf_rect *rect) { size_t len; assert(p); len = p_dtoa(rect->llx, p->unit.precision, buf); buf[len++] = ' '; len += p_dtoa(rect->lly, p->unit.precision, buf+len); buf[len++] = ' '; len += p_dtoa(rect->urx, p->unit.precision, buf+len); buf[len++] = ' '; len += p_dtoa(rect->ury, p->unit.precision, buf+len); buf[len] = '\0'; /* xxx_sprint_xxx NULL terminates strings. */ return len; } static size_t pdf_dev_sprint_coord (pdf_dev *p, char *buf, const pdf_coord *c) { size_t len; assert(p); len = p_dtoa(c->x, p->unit.precision, buf); buf[len++] = ' '; len += p_dtoa(c->y, p->unit.precision, buf+len); buf[len] = '\0'; /* xxx_sprint_xxx NULL terminates strings. */ return len; } static size_t pdf_dev_sprint_length (pdf_dev *p, char *buf, double value) { size_t len; assert(p); len = p_dtoa(value, p->unit.precision, buf); buf[len] = '\0'; /* xxx_sprint_xxx NULL terminates strings. */ return len; } static size_t pdf_dev_sprint_number (pdf_dev *p, char *buf, double value) { size_t len; assert(p); len = p_dtoa(value, DEV_PRECISION_MAX, buf); buf[len] = '\0'; /* xxx_sprint_xxx NULL terminates strings. */ return len; } /* * Text handling routines. */ #define CURRENTFONT(p) (((p)->text_state.font_id < 0) ? \ NULL : &((p)->fonts[(p)->text_state.font_id])) #define GET_FONT(p, n) (&((p)->fonts[(n)])) static void dev_set_text_matrix (pdf_dev *p, spt_t xpos, spt_t ypos, double slant, double extend, int rotate) { pdf_tmatrix tm; int len = 0; assert(p); /* slant is negated for vertical font so that right-side * is always lower. */ switch (rotate) { case TEXT_WMODE_VH: /* Vertical font */ tm.a = slant ; tm.b = 1.0; tm.c = -extend; tm.d = 0.0 ; break; case TEXT_WMODE_HV: /* Horizontal font */ tm.a = 0.0; tm.b = -extend; tm.c = 1.0; tm.d = -slant ; break; case TEXT_WMODE_HH: /* Horizontal font */ tm.a = extend; tm.b = 0.0; tm.c = slant ; tm.d = 1.0; break; case TEXT_WMODE_VV: /* Vertical font */ tm.a = 1.0; tm.b = -slant; tm.c = 0.0; tm.d = extend; break; case TEXT_WMODE_HD: /* Horizontal font */ tm.a = 0.0; tm.b = extend; tm.c = -1.0; tm.d = slant ; break; case TEXT_WMODE_VD: /* Vertical font */ tm.a = -1.0; tm.b = slant; tm.c = 0.0; tm.d = -extend; break; } tm.e = xpos * p->unit.dvi2pts; tm.f = ypos * p->unit.dvi2pts; p->format_buffer[len++] = ' '; len += pdf_dev_sprint_matrix(p, p->format_buffer+len, &tm); p->format_buffer[len++] = ' '; p->format_buffer[len++] = 'T'; p->format_buffer[len++] = 'm'; dev_out(p, p->format_buffer, len); /* op: Tm */ p->text_state.ref_x = xpos; p->text_state.ref_y = ypos; p->text_state.matrix.slant = slant; p->text_state.matrix.extend = extend; p->text_state.matrix.rotate = rotate; } /* * reset_text_state(p) outputs a BT and does any necessary coordinate * transformations to get ready to ship out text. */ static void reset_text_state (pdf_dev *p) { assert(p); /* * We need to reset the line matrix to handle slanted fonts. */ dev_out(p, " BT", 3); /* op: BT */ /* * p->text_state.matrix is identity at top of page. * This sometimes write unnecessary "Tm"s when transition from * GRAPHICS_MODE to TEXT_MODE occurs. */ if (p->text_state.force_reset || p->text_state.matrix.slant != 0.0 || p->text_state.matrix.extend != 1.0 || ROTATE_TEXT(p->text_state.matrix.rotate)) { dev_set_text_matrix(p, 0, 0, p->text_state.matrix.slant, p->text_state.matrix.extend, p->text_state.matrix.rotate); } p->text_state.ref_x = 0; p->text_state.ref_y = 0; p->text_state.offset = 0; p->text_state.force_reset = 0; } static void pdf_dev_text_mode (pdf_dev *p) { assert(p); switch (p->motion_state) { case TEXT_MODE: break; case STRING_MODE: dev_out(p, p->text_state.is_mb ? ">]TJ" : ")]TJ", 4); /* op: TJ */ break; case GRAPHICS_MODE: reset_text_state(p); break; } p->motion_state = TEXT_MODE; p->text_state.offset = 0; } static void pdf_dev_graphics_mode (pdf_dev *p) { switch (p->motion_state) { case GRAPHICS_MODE: break; case STRING_MODE: dev_out(p, p->text_state.is_mb ? ">]TJ" : ")]TJ", 4); /* op: TJ */ /* continue */ case TEXT_MODE: if (p->text_state.bold_param != 0.0) { /* fake-bold "2 Tr" is still active */ dev_out(p, " 0 Tr", 5); /* op: Tr */ p->text_state.bold_param = 0.0; } dev_out(p, " ET", 3); /* op: ET */ p->text_state.force_reset = 0; p->text_state.font_id = -1; break; } p->motion_state = GRAPHICS_MODE; } static void start_string (pdf_dev *p, spt_t xpos, spt_t ypos, double slant, double extend, int rotate) { spt_t delx, dely, error_delx = 0, error_dely = 0; spt_t desired_delx, desired_dely; int len = 0; assert(p); delx = xpos - p->text_state.ref_x; dely = ypos - p->text_state.ref_y; /* * Precompensating for line transformation matrix. * * Line transformation matrix L for horizontal font in horizontal * mode and it's inverse I is * * | e 0| | 1/e 0| * L_hh = | | , I_hh = | | * | s 1| |-s/e 1| * * For vertical font in vertical mode, * * | 1 -s| | 1 s/e| * L_vv = | | , I_vv = | | * | 0 e| | 0 1/e| * * For vertical font in horizontal mode, * * | s 1| | 0 1| * L_vh = | | = L_vv x | | * |-e 0| |-1 0| * * | 0 -1| * I_vh = | | x I_vv * | 1 0| * * For horizontal font in vertical mode, * * | 0 -e| | 0 -1| * L_hv = | | = L_hh x | | * | 1 -s| | 1 0| * * | 0 1| * I_hv = | | x I_hh * |-1 0| * */ switch (rotate) { case TEXT_WMODE_VH: /* Vertical font in horizontal mode: rot = +90 * | 0 -1/e| * d_user = d x I_vh = d x | | * | 1 s/e| */ desired_delx = dely; desired_dely = (spt_t) (-(delx - dely*slant)/extend); /* error_del is in device space * * | 0 1| * e = e_user x | | = (-e_user_y, e_user_x) * |-1 0| * * We must care about rotation here but not extend/slant... * The extend and slant actually is font matrix. */ p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_delx, &error_dely); p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_dely, &error_delx); error_delx = -error_delx; break; case TEXT_WMODE_HV: /* Horizontal font in vertical mode: rot = -90 * * |-s/e 1| * d_user = d x I_hv = d x | | * |-1/e 0| */ desired_delx = (spt_t)(-(dely + delx*slant)/extend); desired_dely = delx; /* * e = (e_user_y, -e_user_x) */ p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_delx, &error_dely); p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_dely, &error_delx); error_dely = -error_dely; break; case TEXT_WMODE_HH: /* Horizontal font in horizontal mode: * | 1/e 0| * d_user = d x I_hh = d x | | * |-s/e 1| */ desired_delx = (spt_t)((delx - dely*slant)/extend); desired_dely = dely; p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_delx, &error_delx); p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_dely, &error_dely); break; case TEXT_WMODE_VV: /* Vertical font in vertical mode: * | 1 s/e| * d_user = d x I_vv = d x | | * | 0 1/e| */ desired_delx = delx; desired_dely = (spt_t)((dely + delx*slant)/extend); p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_delx, &error_delx); p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_dely, &error_dely); break; case TEXT_WMODE_HD: /* Horizontal font in down-to-up mode: rot = +90 * * | s/e -1| * d_user = d x -I_hv = d x | | * | 1/e 0| */ desired_delx = -(spt_t)(-(dely + delx*slant)/extend); desired_dely = -delx; p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_delx, &error_dely); p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_dely, &error_delx); error_delx = -error_delx; error_dely = -error_dely; break; case TEXT_WMODE_VD: /* Vertical font in down-to-up mode: rot = 180 * |-1 -s/e| * d_user = d x -I_vv = d x | | * | 0 -1/e| */ desired_delx = -delx; desired_dely = -(spt_t)((dely + delx*slant)/extend); p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_delx, &error_delx); p->format_buffer[len++] = ' '; len += dev_sprint_bp(p, p->format_buffer+len, desired_dely, &error_dely); error_delx = -error_delx; error_dely = -error_dely; break; } dev_out(p, p->format_buffer, len); /* op: */ /* * dvipdfm wrongly using "TD" in place of "Td". * The TD operator set leading, but we are not using T* etc. */ dev_out(p, p->text_state.is_mb ? " Td[<" : " Td[(", 5); /* op: Td */ /* Error correction */ p->text_state.ref_x = xpos - error_delx; p->text_state.ref_y = ypos - error_dely; p->text_state.offset = 0; } static void pdf_dev_string_mode (pdf_dev *p, spt_t xpos, spt_t ypos, double slant, double extend, int rotate) { assert(p); switch (p->motion_state) { case STRING_MODE: break; case GRAPHICS_MODE: reset_text_state(p); /* continue */ case TEXT_MODE: if (p->text_state.force_reset) { dev_set_text_matrix(p, xpos, ypos, slant, extend, rotate); dev_out(p, p->text_state.is_mb ? "[<" : "[(", 2); /* op: */ p->text_state.force_reset = 0; } else { start_string(p, xpos, ypos, slant, extend, rotate); } break; } p->motion_state = STRING_MODE; } /* * The purpose of the following routine is to force a Tf only * when it's actually necessary. This became a problem when the * VF code was added. The VF spec says to instantiate the * first font contained in the VF file before drawing a virtual * character. However, that font may not be used for * many characters (e.g. small caps fonts). For this reason, * dev_select_font() should not force a "physical" font selection. * This routine prevents a PDF Tf font selection until there's * really a character in that font. */ static int pdf_dev_set_font (pdf_dev *p, int font_id) { struct dev_font *font; int text_rotate; double font_scale; int len; int vert_dir, vert_font; assert(p); /* pdf_dev_text_mode(p) must come before p->text_state.is_mb is changed. */ pdf_dev_text_mode(p); font = GET_FONT(p, font_id); assert(font); /* Caller should check font_id. */ p->text_state.is_mb = (font->format == PDF_FONTTYPE_COMPOSITE) ? 1 : 0; vert_font = font->wmode ? 1 : 0; if (p->param.autorotate) { vert_dir = p->text_state.dir_mode; } else { vert_dir = vert_font; } text_rotate = (vert_font << 2)|vert_dir; if (font->slant != p->text_state.matrix.slant || font->extend != p->text_state.matrix.extend || ANGLE_CHANGES(text_rotate, p->text_state.matrix.rotate)) { p->text_state.force_reset = 1; } p->text_state.matrix.slant = font->slant; p->text_state.matrix.extend = font->extend; p->text_state.matrix.rotate = text_rotate; if (!font->resource) { font->resource = pdf_get_font_reference(font->font_id); font->used_chars = pdf_get_font_usedchars(font->font_id); } if (!font->used_on_this_page) { pdf_doc_add_page_resource("Font", font->short_name, pdf_link_obj(font->resource)); font->used_on_this_page = 1; } font_scale = (double) font->sptsize * p->unit.dvi2pts; len = sprintf(p->format_buffer, " /%s", font->short_name); /* space not necessary. */ p->format_buffer[len++] = ' '; len += p_dtoa(font_scale, MIN(p->unit.precision+1, DEV_PRECISION_MAX), p->format_buffer+len); p->format_buffer[len++] = ' '; p->format_buffer[len++] = 'T'; p->format_buffer[len++] = 'f'; dev_out(p, p->format_buffer, len); /* op: Tf */ if (font->bold > 0.0 || font->bold != p->text_state.bold_param) { if (font->bold <= 0.0) len = sprintf(p->format_buffer, " 0 Tr"); else len = sprintf(p->format_buffer, " 2 Tr %.6f w", font->bold); /* _FIXME_ */ dev_out(p, p->format_buffer, len); /* op: Tr w */ } p->text_state.bold_param = font->bold; p->text_state.font_id = font_id; return 0; } /* These tmp buffers can't be removed since the pointer to this can be * used as the return value of handle_multibyte_string(): str_ptr can point these. */ static unsigned char sbuf0[FORMAT_BUF_SIZE]; static int handle_multibyte_string (struct dev_font *font, const unsigned char **str_ptr, size_t *str_len) { const unsigned char *p; size_t length; p = *str_ptr; length = *str_len; /* * Font is double-byte font. Output is assumed to be 16-bit fixed length * encoding. * TODO: A character decomposed to multiple characters. */ if (font->enc_id >= 0) { const unsigned char *inbuf; unsigned char *outbuf; size_t inbytesleft, outbytesleft; CMap *cmap; cmap = CMap_cache_get(font->enc_id); inbuf = p; outbuf = sbuf0; inbytesleft = length; outbytesleft = FORMAT_BUF_SIZE; CMap_decode(cmap, &inbuf, &inbytesleft, &outbuf, &outbytesleft); if (inbytesleft != 0) { dpx_warning("CMap conversion failed. (%"PRIuZ" bytes remains)", inbytesleft); return -1; } length = FORMAT_BUF_SIZE - outbytesleft; p = sbuf0; } *str_ptr = p; *str_len = length; return 0; } void pdf_dev_set_string (spt_t xpos, spt_t ypos, const void *instr_ptr, size_t instr_len, spt_t width, int font_id) { pdf_dev *p = current_device(); struct dev_font *font; const unsigned char *str_ptr; /* Pointer to the reencoded string. */ size_t length, i, len = 0; spt_t kern, delh, delv; spt_t text_xorigin; spt_t text_yorigin; if (font_id < 0 || font_id >= p->num_dev_fonts) { _tt_abort("Invalid font: %d (%d)", font_id, p->num_dev_fonts); return; } if (font_id != p->text_state.font_id) { pdf_dev_set_font(p, font_id); } font = CURRENTFONT(p); if (!font) { _tt_abort("Currentfont not set."); } text_xorigin = p->text_state.ref_x; text_yorigin = p->text_state.ref_y; str_ptr = instr_ptr; length = instr_len; if (font->format == PDF_FONTTYPE_COMPOSITE) { if (handle_multibyte_string(font, &str_ptr, &length) < 0) { _tt_abort("Error in converting input string..."); } if (font->used_chars != NULL) { for (i = 0; i < length; i += 2) { unsigned short cid = (str_ptr[i] << 8) | str_ptr[i + 1]; add_to_used_chars2(font->used_chars, cid); } } } else { if (font->used_chars != NULL) { for (i = 0; i < length; i++) font->used_chars[str_ptr[i]] = 1; } } /* * Kern is in units of character units, i.e., 1000 = 1 em. * * Positive kern means kerning (reduce excess white space). * * The following formula is of the form a*x/b where a, x, and b are signed long * integers. Since in integer arithmetic (a*x) could overflow and a*(x/b) would * not be accurate, we use floating point arithmetic rather than trying to do * this all with integer arithmetic. * * 1000.0 / (font->extend * font->sptsize) is caluculated each times... * Is accuracy really a matter? Character widths are always rounded to integer * (in 1000 units per em) but dvipdfmx does not take into account of this... */ if (p->text_state.dir_mode==0) { /* Left-to-right */ delh = text_xorigin + p->text_state.offset - xpos; delv = ypos - text_yorigin; } else if (p->text_state.dir_mode==1) { /* Top-to-bottom */ delh = ypos - text_yorigin + p->text_state.offset; delv = xpos - text_xorigin; } else { /* Bottom-to-top */ delh = ypos + text_yorigin + p->text_state.offset; delv = xpos + text_xorigin; } /* White-space more than 3em is not considered as a part of single text. * So we will break string mode in that case. * Dvipdfmx spend most of time processing strings with kern = 0 (but far * more times in font handling). * You may want to use pre-calculated value for WORD_SPACE_MAX. * More text compression may be possible by replacing kern with space char * when -kern is equal to space char width. */ #define WORD_SPACE_MAX(f) (spt_t) (3.0 * (f)->extend * (f)->sptsize) if (p->text_state.force_reset || labs(delv) > p->unit.min_bp_val || labs(delh) > WORD_SPACE_MAX(font)) { pdf_dev_text_mode(p); kern = 0; } else { kern = (spt_t) (1000.0 / font->extend * delh / font->sptsize); } /* Inaccucary introduced by rounding of character width appears within * single text block. There are point_size/1000 rounding error per character. * If you really care about accuracy, you should compensate this here too. */ if (p->motion_state != STRING_MODE) pdf_dev_string_mode(p, xpos, ypos, font->slant, font->extend, p->text_state.matrix.rotate); else if (kern != 0) { /* * Same issues as earlier. Use floating point for simplicity. * This routine needs to be fast, so we don't call sprintf() or strcpy(). */ p->text_state.offset -= (spt_t) (kern * font->extend * (font->sptsize / 1000.0)); p->format_buffer[len++] = p->text_state.is_mb ? '>' : ')'; if (font->wmode) len += p_itoa(-kern, p->format_buffer + len); else { len += p_itoa( kern, p->format_buffer + len); } p->format_buffer[len++] = p->text_state.is_mb ? '<' : '('; dev_out(p, p->format_buffer, len); /* op: */ len = 0; } if (p->text_state.is_mb) { if (FORMAT_BUF_SIZE - len < 2 * length) _tt_abort("Buffer overflow..."); for (i = 0; i < length; i++) { int first, second; first = (str_ptr[i] >> 4) & 0x0f; second = str_ptr[i] & 0x0f; p->format_buffer[len++] = ((first >= 10) ? first + 'W' : first + '0'); p->format_buffer[len++] = ((second >= 10) ? second + 'W' : second + '0'); } } else { len += pdfobj_escape_str(p->format_buffer + len, FORMAT_BUF_SIZE - len, str_ptr, length); } /* I think if you really care about speed, you should avoid memcopy here. */ dev_out(p, p->format_buffer, len); /* op: */ p->text_state.offset += width; } void pdf_init_device (double dvi2pts, int precision, int black_and_white) { pdf_dev *p = current_device(); p->motion_state = GRAPHICS_MODE; p->unit.dvi2pts = 0.0; p->unit.min_bp_val = 658; p->unit.precision = 2; p->param.autorotate = 1; p->param.colormode = 1; p->text_state.font_id = -1; p->text_state.offset = 0; p->text_state.matrix.slant = 0; p->text_state.matrix.extend = 0; p->text_state.matrix.rotate = 0; p->text_state.bold_param = 0; p->text_state.dir_mode = 0; p->text_state.force_reset = 0; p->text_state.is_mb = 0; if (precision < 0 || precision > DEV_PRECISION_MAX) dpx_warning("Number of decimal digits out of range [0-%d].", DEV_PRECISION_MAX); if (precision < 0) { p->unit.precision = 0; } else if (precision > DEV_PRECISION_MAX) { p->unit.precision = DEV_PRECISION_MAX; } else { p->unit.precision = precision; } p->unit.dvi2pts = dvi2pts; p->unit.min_bp_val = (int) ROUND(1.0/(ten_pow[p->unit.precision]*dvi2pts), 1); if (p->unit.min_bp_val < 0) p->unit.min_bp_val = -p->unit.min_bp_val; p->param.colormode = (black_and_white ? 0 : 1); pdf_dev_graphics_mode(p); pdf_color_clear_stack(); pdf_dev_init_gstates(); p->num_dev_fonts = p->max_dev_fonts = 0; p->fonts = NULL; } void pdf_close_device (void) { pdf_dev *p = current_device(); if (p->fonts) { int i; for (i = 0; i < p->num_dev_fonts; i++) { free(p->fonts[i].tex_name); pdf_release_obj(p->fonts[i].resource); p->fonts[i].tex_name = NULL; p->fonts[i].resource = NULL; } free(p->fonts); p->fonts = NULL; } pdf_dev_clear_gstates(); } /* * BOP, EOP, and FONT section. * BOP and EOP manipulate some of the same data structures * as the font stuff. */ void pdf_dev_reset_fonts (int newpage) { pdf_dev *p = current_device(); int i; for (i = 0; i < p->num_dev_fonts; i++) { p->fonts[i].used_on_this_page = 0; } p->text_state.font_id = -1; p->text_state.matrix.slant = 0.0; p->text_state.matrix.extend = 1.0; p->text_state.matrix.rotate = TEXT_WMODE_HH; if (newpage) p->text_state.bold_param = 0.0; p->text_state.is_mb = 0; } void pdf_dev_reset_color (int force) { pdf_color *sc, *fc; pdf_color_get_current(&sc, &fc); pdf_dev_set_color(sc, 0, force); pdf_dev_set_color(fc, 0x20, force); } void pdf_dev_bop (const pdf_tmatrix *M) { pdf_dev *p = current_device(); pdf_dev_graphics_mode(p); p->text_state.force_reset = 0; pdf_dev_gsave(); pdf_dev_concat(M); pdf_dev_reset_fonts(1); pdf_dev_reset_color(0); pdf_dev_reset_xgstate(0); } void pdf_dev_eop (void) { pdf_dev *p = current_device(); int depth; pdf_dev_graphics_mode(p); depth = pdf_dev_current_depth(); if (depth != 1) { dpx_warning("Unbalenced q/Q nesting...: %d", depth); pdf_dev_grestore_to(0); } else { pdf_dev_grestore(); } } static void print_fontmap (const char *font_name, fontmap_rec *mrec) { if (!mrec) return; dpx_message("\n"); dpx_message("fontmap: %s -> %s", font_name, mrec->font_name); if (mrec->enc_name) dpx_message("(%s)", mrec->enc_name); if (mrec->opt.extend != 1.0) dpx_message("[extend:%g]", mrec->opt.extend); if (mrec->opt.slant != 0.0) dpx_message("[slant:%g]", mrec->opt.slant); if (mrec->opt.bold != 0.0) dpx_message("[bold:%g]", mrec->opt.bold); if (mrec->opt.flags & FONTMAP_OPT_NOEMBED) dpx_message("[noemb]"); if (mrec->opt.mapc >= 0) dpx_message("[map:<%02x>]", mrec->opt.mapc); if (mrec->opt.charcoll) dpx_message("[csi:%s]", mrec->opt.charcoll); if (mrec->opt.index) dpx_message("[index:%d]", mrec->opt.index); switch (mrec->opt.style) { case FONTMAP_STYLE_BOLD: dpx_message("[style:bold]"); break; case FONTMAP_STYLE_ITALIC: dpx_message("[style:italic]"); break; case FONTMAP_STYLE_BOLDITALIC: dpx_message("[style:bolditalic]"); break; } dpx_message("\n"); } /* _FIXME_ * Font is identified with font_name and point_size as in DVI here. * However, except for PDF_FONTTYPE_BITMAP, we can share the * short_name, resource and used_chars between multiple instances * of the same font at different sizes. */ int pdf_dev_locate_font (const char *font_name, spt_t ptsize) { pdf_dev *p = current_device(); int i; fontmap_rec *mrec; struct dev_font *font; if (!font_name) return -1; if (ptsize == 0) { _tt_abort("pdf_dev_locate_font() called with the zero ptsize."); } for (i = 0; i < p->num_dev_fonts; i++) { if (streq_ptr(font_name, p->fonts[i].tex_name) && ptsize == p->fonts[i].sptsize) return i; /* found a dev_font that matches the request */ } /* * Make sure we have room for a new one, even though we may not * actually create one. */ if (p->num_dev_fonts >= p->max_dev_fonts) { p->max_dev_fonts += 16; p->fonts = RENEW(p->fonts, p->max_dev_fonts, struct dev_font); } font = &p->fonts[p->num_dev_fonts]; /* New font */ mrec = pdf_lookup_fontmap_record(font_name); if (dpx_conf.verbose_level > 1) print_fontmap(font_name, mrec); font->font_id = pdf_font_findresource(font_name, ptsize * p->unit.dvi2pts); if (font->font_id < 0) { font->font_id = pdf_font_load_font(font_name, ptsize * p->unit.dvi2pts, mrec); if (font->font_id < 0) return -1; } pdf_font_resource_name(font->font_id, font->short_name); font->used_on_this_page = 0; font->tex_name = NEW(strlen(font_name) + 1, char); strcpy(font->tex_name, font_name); font->sptsize = ptsize; switch (pdf_get_font_subtype(font->font_id)) { case PDF_FONT_FONTTYPE_TYPE3: font->format = PDF_FONTTYPE_BITMAP; break; case PDF_FONT_FONTTYPE_TYPE0: font->format = PDF_FONTTYPE_COMPOSITE; break; default: font->format = PDF_FONTTYPE_SIMPLE; break; } font->wmode = pdf_get_font_wmode (font->font_id); font->enc_id = pdf_get_font_encoding(font->font_id); font->resource = NULL; /* Don't ref obj until font is actually used. */ font->used_chars = NULL; font->extend = 1.0; font->slant = 0.0; font->bold = 0.0; if (mrec) { font->extend = mrec->opt.extend; font->slant = mrec->opt.slant; font->bold = mrec->opt.bold; } return p->num_dev_fonts++; } /* This does not remember current stroking width. */ static int dev_sprint_line (pdf_dev *p, char *buf, spt_t width, spt_t p0_x, spt_t p0_y, spt_t p1_x, spt_t p1_y) { int len = 0; double w; w = width * p->unit.dvi2pts; len += p_dtoa(w, MIN(p->unit.precision+1, DEV_PRECISION_MAX), buf+len); buf[len++] = ' '; buf[len++] = 'w'; buf[len++] = ' '; len += dev_sprint_bp(p, buf+len, p0_x, NULL); buf[len++] = ' '; len += dev_sprint_bp(p, buf+len, p0_y, NULL); buf[len++] = ' '; buf[len++] = 'm'; buf[len++] = ' '; len += dev_sprint_bp(p, buf+len, p1_x, NULL); buf[len++] = ' '; len += dev_sprint_bp(p, buf+len, p1_y, NULL); buf[len++] = ' '; buf[len++] = 'l'; buf[len++] = ' '; buf[len++] = 'S'; return len; } /* Not optimized. */ #define PDF_LINE_THICKNESS_MAX 5.0 void pdf_dev_set_rule (spt_t xpos, spt_t ypos, spt_t width, spt_t height) { pdf_dev *p = current_device(); int len = 0; double width_in_bp; pdf_dev_graphics_mode(p); p->format_buffer[len++] = ' '; p->format_buffer[len++] = 'q'; p->format_buffer[len++] = ' '; /* Don't use too thick line. */ width_in_bp = ((width < height) ? width : height) * p->unit.dvi2pts; if (width_in_bp < 0.0 || /* Shouldn't happen */ width_in_bp > PDF_LINE_THICKNESS_MAX) { pdf_rect rect; rect.llx = p->unit.dvi2pts * xpos; rect.lly = p->unit.dvi2pts * ypos; rect.urx = p->unit.dvi2pts * width; rect.ury = p->unit.dvi2pts * height; len += pdf_sprint_rect(p->format_buffer+len, &rect); p->format_buffer[len++] = ' '; p->format_buffer[len++] = 'r'; p->format_buffer[len++] = 'e'; p->format_buffer[len++] = ' '; p->format_buffer[len++] = 'f'; } else { if (width > height) { /* NOTE: * A line width of 0 denotes the thinnest line that can be rendered at * device resolution. See, PDF Reference Manual 4th ed., sec. 4.3.2, * "Details of Graphics State Parameters", p. 185. */ if (height < p->unit.min_bp_val) { dpx_warning("Too thin line: height=%d (%g bp)", height, width_in_bp); dpx_warning("Please consider using \"-d\" option."); } len += dev_sprint_line(p, p->format_buffer+len, height, xpos, ypos + height/2, xpos + width, ypos + height/2); } else { if (width < p->unit.min_bp_val) { dpx_warning("Too thin line: width=%d (%g bp)", width, width_in_bp); dpx_warning("Please consider using \"-d\" option."); } len += dev_sprint_line(p, p->format_buffer+len, width, xpos + width/2, ypos, xpos + width/2, ypos + height); } } p->format_buffer[len++] = ' '; p->format_buffer[len++] = 'Q'; dev_out(p, p->format_buffer, len); /* op: q re f Q */ } /* Rectangle in device space coordinate. */ void pdf_dev_set_rect (pdf_rect *rect, spt_t x_user, spt_t y_user, spt_t width, spt_t height, spt_t depth) { pdf_dev *p = current_device(); double dev_x, dev_y; pdf_coord p0, p1, p2, p3; double min_x, min_y, max_x, max_y; dev_x = x_user * p->unit.dvi2pts; dev_y = y_user * p->unit.dvi2pts; if (p->text_state.dir_mode) { p0.x = dev_x - p->unit.dvi2pts * depth; p0.y = dev_y - p->unit.dvi2pts * width; p1.x = dev_x + p->unit.dvi2pts * height; p1.y = p0.y; p2.x = p1.x; p2.y = dev_y; p3.x = p0.x; p3.y = p2.y; } else { p0.x = dev_x; p0.y = dev_y - p->unit.dvi2pts * depth; p1.x = dev_x + p->unit.dvi2pts * width; p1.y = p0.y; p2.x = p1.x; p2.y = dev_y + p->unit.dvi2pts * height; p3.x = p0.x; p3.y = p2.y; } pdf_dev_transform(&p0, NULL); /* currentmatrix */ pdf_dev_transform(&p1, NULL); pdf_dev_transform(&p2, NULL); pdf_dev_transform(&p3, NULL); min_x = MIN(p0.x , p1.x); min_x = MIN(min_x, p2.x); min_x = MIN(min_x, p3.x); max_x = MAX(p0.x , p1.x); max_x = MAX(max_x, p2.x); max_x = MAX(max_x, p3.x); min_y = MIN(p0.y , p1.y); min_y = MIN(min_y, p2.y); min_y = MIN(min_y, p3.y); max_y = MAX(p0.y , p1.y); max_y = MAX(max_y, p2.y); max_y = MAX(max_y, p3.y); rect->llx = min_x; rect->lly = min_y; rect->urx = max_x; rect->ury = max_y; return; } int pdf_dev_get_dirmode (void) { pdf_dev *p = current_device(); return p->text_state.dir_mode; } void pdf_dev_set_dirmode (int text_dir) { pdf_dev *p = current_device(); struct dev_font *font; int text_rotate; int vert_dir, vert_font; font = CURRENTFONT(p); vert_font = (font && font->wmode) ? 1 : 0; if (p->param.autorotate) { vert_dir = text_dir; } else { vert_dir = vert_font; } text_rotate = (vert_font << 2)|vert_dir; if (font && ANGLE_CHANGES(text_rotate, p->text_state.matrix.rotate)) { p->text_state.force_reset = 1; } p->text_state.matrix.rotate = text_rotate; p->text_state.dir_mode = text_dir; } static void dev_set_param_autorotate (pdf_dev *p, int auto_rotate) { struct dev_font *font; int text_rotate, vert_font, vert_dir; assert(p); font = CURRENTFONT(p); vert_font = (font && font->wmode) ? 1 : 0; if (auto_rotate) { vert_dir = p->text_state.dir_mode; } else { vert_dir = vert_font; } text_rotate = (vert_font << 2)|vert_dir; if (ANGLE_CHANGES(text_rotate, p->text_state.matrix.rotate)) { p->text_state.force_reset = 1; } p->text_state.matrix.rotate = text_rotate; p->param.autorotate = auto_rotate; } int pdf_dev_get_param (int param_type) { pdf_dev *p = current_device(); int value = 0; switch (param_type) { case PDF_DEV_PARAM_AUTOROTATE: value = p->param.autorotate; break; case PDF_DEV_PARAM_COLORMODE: value = p->param.colormode; break; default: _tt_abort("Unknown device parameter: %d", param_type); } return value; } void pdf_dev_set_param (int param_type, int value) { pdf_dev *p = current_device(); switch (param_type) { case PDF_DEV_PARAM_AUTOROTATE: dev_set_param_autorotate(p, value); break; case PDF_DEV_PARAM_COLORMODE: p->param.colormode = value; /* 0 for B&W */ break; default: _tt_abort("Unknown device parameter: %d", param_type); } return; } int pdf_dev_put_image (int id, transform_info *ti, double ref_x, double ref_y, pdf_rect *rect) { pdf_dev *p = current_device(); char *res_name; pdf_tmatrix M, M1; pdf_rect r; int len = 0; pdf_copymatrix(&M, &(ti->matrix)); M.e += ref_x; M.f += ref_y; /* Just rotate by -90, but not tested yet. Any problem if M has scaling? */ if (p->param.autorotate && p->text_state.dir_mode) { double tmp; tmp = -M.a; M.a = M.b; M.b = tmp; tmp = -M.c; M.c = M.d; M.d = tmp; } pdf_dev_graphics_mode(p); pdf_dev_gsave(); pdf_ximage_scale_image(id, &M1, &r, ti); pdf_concatmatrix(&M, &M1); pdf_dev_concat(&M); /* Clip */ if (ti->flags & INFO_DO_CLIP) { pdf_dev_rectclip(r.llx, r.lly, r.urx - r.llx, r.ury - r.lly); } res_name = pdf_ximage_get_resname(id); { char *buf; buf = NEW(strlen(res_name)+6, char); len = sprintf(buf, " /%s Do", res_name); dev_out(p, buf, len); /* op: Do */ free(buf); } if (rect) { pdf_rect r1; /* Sorry for ugly code. */ pdf_dev_set_rect(&r1, bpt2spt(p, r.llx), bpt2spt(p, r.lly), bpt2spt(p, r.urx - r.llx), bpt2spt(p, r.ury - r.lly), 0); rect->llx = r1.llx; rect->lly = r1.lly; rect->urx = r1.urx; rect->ury = r1.ury; } pdf_dev_grestore(); pdf_doc_add_page_resource("XObject", res_name, pdf_ximage_get_reference(id)); return 0; } void transform_info_clear (transform_info *info) { /* Physical dimensions */ info->width = 0.0; info->height = 0.0; info->depth = 0.0; info->bbox.llx = 0.0; info->bbox.lly = 0.0; info->bbox.urx = 0.0; info->bbox.ury = 0.0; /* Transformation matrix */ pdf_setmatrix(&(info->matrix), 1.0, 0.0, 0.0, 1.0, 0.0, 0.0); info->flags = 0; } void pdf_dev_begin_actualtext (uint16_t *unicodes, int count) { pdf_dev *p = current_device(); int len, i, pdf_doc_enc = 1; /* check whether we can use PDFDocEncoding for this string (we punt on the 0x80..0xA0 range that does not directly correspond to unicode) */ for (i = 0; i < count; i++) { if (unicodes[i] > 0xff || (unicodes[i] > 0x7f && unicodes[i] < 0xa1)) { pdf_doc_enc = 0; break; } } pdf_dev_graphics_mode(p); dev_out(p, "\n/Span << /ActualText (", 23); if (!pdf_doc_enc) dev_out(p, "\xFE\xFF", 2); while (count-- > 0) { unsigned char s[2] = { *unicodes >> 8, *unicodes & 0xff }; char buf[32]; i = pdf_doc_enc; /* if using PDFDocEncoding, we only care about the low 8 bits, so start with the second byte of our pair */ len = 0; for (; i < 2; i++) { unsigned char c = s[i]; if (c == '(' || c == ')' || c == '\\') { len += sprintf(buf + len, "\\%c", c); } else if (c < ' ') { len += sprintf(buf + len, "\\%03o", c); } else { len += sprintf(buf + len, "%c", c); } } dev_out(p, work_buffer, len); ++unicodes; } dev_out(p, ") >> BDC", 8); } void pdf_dev_end_actualtext (void) { pdf_dev *p = current_device(); pdf_dev_graphics_mode(p); dev_out(p, " EMC", 4); } /* Compatibility functions.... For other files still using old interface */ void graphics_mode (void) { pdf_dev *p = current_device(); pdf_dev_graphics_mode(p); } double dev_unit_dviunit (void) { pdf_dev *p = current_device(); return pdf_dev_unit_dviunit(p); } int pdf_sprint_matrix (char *buf, const pdf_tmatrix *M) { pdf_dev *p = current_device(); return (int) pdf_dev_sprint_matrix(p, buf, M); } int pdf_sprint_rect (char *buf, const pdf_rect *rect) { pdf_dev *p = current_device(); return (int) pdf_dev_sprint_rect(p, buf, rect); } int pdf_sprint_coord (char *buf, const pdf_coord *c) { pdf_dev *p = current_device(); return (int) pdf_dev_sprint_coord(p, buf, c); } int pdf_sprint_length (char *buf, double value) { pdf_dev *p = current_device(); return (int) pdf_dev_sprint_length(p, buf, value); } int pdf_sprint_number (char *buf, double value) { pdf_dev *p = current_device(); return (int) pdf_dev_sprint_number(p, buf, value); } /* Access text state parameters. */ int pdf_dev_get_font_wmode (int font_id) { pdf_dev *p = current_device(); struct dev_font *font; font = GET_FONT(p, font_id); if (font) { return font->wmode; } return 0; } int pdf_dev_font_minbytes (int font_id) { pdf_dev *p = current_device(); struct dev_font *font; int minbytes = 1; font = GET_FONT(p, font_id); if (font && font->format == PDF_FONTTYPE_COMPOSITE) { CMap *cmap; cmap = CMap_cache_get(font->enc_id); minbytes = CMap_get_profile(cmap, CMAP_PROF_TYPE_INBYTES_MIN); } return minbytes; } /* Extra Tectonic API: */ void pdf_dev_reset_global_state(void) { pdf_dev *p = current_device(); p->fonts = NULL; p->num_dev_fonts = 0; p->max_dev_fonts = 0; }