/* Copyright (C) 2000 artofcode LLC. All rights reserved. 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. */ /*$Id: gdevpsfx.c,v 1.9.2.1.2.1 2003/01/17 00:49:01 giles Exp $ */ /* Convert Type 1 Charstrings to Type 2 */ #include "math_.h" #include "memory_.h" #include "gx.h" #include "gserrors.h" #include "gxfixed.h" #include "gxmatrix.h" /* for gsfont.h */ #include "gxfont.h" #include "gxfont1.h" #include "gxtype1.h" #include "stream.h" #include "gdevpsf.h" /* ------ Type 1 Charstring parsing ------ */ /* * The parsing code handles numbers on its own; it reports callsubr and * return operators to the caller, but also executes them. * * Only the following elements of the Type 1 state are used: * ostack, os_count, ipstack, ips_count */ #define CE_OFFSET 32 /* offset for extended opcodes */ /* Skip over the initial bytes in a Charstring, if any. */ private void skip_iv(gs_type1_state *pcis) { int skip = pcis->pfont->data.lenIV; ip_state_t *ipsp = &pcis->ipstack[pcis->ips_count - 1]; const byte *cip = ipsp->char_string.data; crypt_state state = crypt_charstring_seed; for (; skip > 0; ++cip, --skip) decrypt_skip_next(*cip, state); ipsp->ip = cip; ipsp->dstate = state; } /* * Set up for parsing a Type 1 Charstring. * * Only uses the following elements of *pfont: * data.lenIV */ private void type1_next_init(gs_type1_state *pcis, const gs_const_string *pstr, gs_font_type1 *pfont) { static const gs_log2_scale_point no_scale = {0, 0}; gs_type1_interp_init(pcis, NULL, NULL, &no_scale, false, 0, pfont); pcis->flex_count = flex_max; pcis->dotsection_flag = dotsection_out; pcis->ipstack[0].char_string = *pstr; pcis->ipstack[0].free_char_string = 0; skip_iv(pcis); } /* Clear the Type 1 operand stack. */ inline private void type1_clear(gs_type1_state *pcis) { pcis->os_count = 0; } /* Execute a callsubr. */ private int type1_callsubr(gs_type1_state *pcis, int index) { gs_font_type1 *pfont = pcis->pfont; ip_state_t *ipsp1 = &pcis->ipstack[pcis->ips_count]; int code = pfont->data.procs.subr_data(pfont, index, false, &ipsp1->char_string); if (code < 0) return_error(code); ipsp1->free_char_string = code; pcis->ips_count++; skip_iv(pcis); return code; } /* Add 1 or 3 stem hints. */ private int type1_stem1(gs_type1_state *pcis, stem_hint_table *psht, const fixed *pv, byte *active_hints) { fixed v0 = pv[0], v1 = v0 + pv[1]; stem_hint *bot = &psht->data[0]; stem_hint *orig_top = bot + psht->count; stem_hint *top = orig_top; if (psht->count >= max_stems) return_error(gs_error_limitcheck); while (top > bot && (v0 < top[-1].v0 || (v0 == top[-1].v0 && v1 < top[-1].v1)) ) { *top = top[-1]; top--; } if (top > bot && v0 == top[-1].v0 && v1 == top[-1].v1) { /* Duplicate hint, don't add it. */ memmove(top, top + 1, (char *)orig_top - (char *)top); if (active_hints) { uint index = top[-1].index; active_hints[index >> 3] |= 0x80 >> (index & 7); } return 0; } top->v0 = v0; top->v1 = v1; psht->count++; return 0; } private void type1_stem3(gs_type1_state *pcis, stem_hint_table *psht, const fixed *pv3, byte *active_hints) { type1_stem1(pcis, psht, pv3, active_hints); type1_stem1(pcis, psht, pv3 + 2, active_hints); type1_stem1(pcis, psht, pv3 + 4, active_hints); } /* * Get the next operator from a Type 1 Charstring. This procedure handles * numbers, div, blend, pop, and callsubr/return. */ private int type1_next(gs_type1_state *pcis) { ip_state_t *ipsp = &pcis->ipstack[pcis->ips_count - 1]; const byte *cip; crypt_state state; #define CLEAR (csp = pcis->ostack - 1) fixed *csp = &pcis->ostack[pcis->os_count - 1]; const bool encrypted = pcis->pfont->data.lenIV >= 0; int c, code, num_results; load: cip = ipsp->ip; state = ipsp->dstate; for (;;) { uint c0 = *cip++; charstring_next(c0, state, c, encrypted); if (c >= c_num1) { /* This is a number, decode it and push it on the stack. */ if (c < c_pos2_0) { /* 1-byte number */ decode_push_num1(csp, c); } else if (c < cx_num4) { /* 2-byte number */ decode_push_num2(csp, c, cip, state, encrypted); } else if (c == cx_num4) { /* 4-byte number */ long lw; decode_num4(lw, cip, state, encrypted); *++csp = int2fixed(lw); } else /* not possible */ return_error(gs_error_invalidfont); continue; } #ifdef DEBUG if (gs_debug_c('1')) { const fixed *p; for (p = pcis->ostack; p <= csp; ++p) dprintf1(" %g", fixed2float(*p)); if (c == cx_escape) { crypt_state cstate = state; int cn; charstring_next(*cip, cstate, cn, encrypted); dprintf1(" [*%d]\n", cn); } else dprintf1(" [%d]\n", c); } #endif switch ((char_command) c) { default: break; case c_undef0: case c_undef2: case c_undef17: return_error(gs_error_invalidfont); case c_callsubr: code = type1_callsubr(pcis, fixed2int_var(*csp)); if (code < 0) return_error(code); ipsp->ip = cip, ipsp->dstate = state; --csp; ++ipsp; goto load; case c_return: if (ipsp->free_char_string > 0) gs_free_const_string(pcis->pfont->memory, ipsp->char_string.data, ipsp->char_string.size, "type1_next"); pcis->ips_count--; --ipsp; goto load; case cx_escape: charstring_next(*cip, state, c, encrypted); ++cip; switch ((char1_extended_command) c) { default: c += CE_OFFSET; break; case ce1_div: csp[-1] = float2fixed((double)csp[-1] / (double)*csp); --csp; continue; case ce1_undoc15: /* see gstype1.h */ CLEAR; continue; case ce1_callothersubr: switch (fixed2int_var(*csp)) { case 0: pcis->ignore_pops = 2; break; /* pass to caller */ case 3: pcis->ignore_pops = 1; break; /* pass to caller */ case 14: num_results = 1; goto blend; case 15: num_results = 2; goto blend; case 16: num_results = 3; goto blend; case 17: num_results = 4; goto blend; case 18: num_results = 6; blend: code = gs_type1_blend(pcis, csp, num_results); if (code < 0) return code; csp -= code; continue; default: break; /* pass to caller */ } break; case ce1_pop: if (pcis->ignore_pops != 0) { pcis->ignore_pops--; continue; } return_error(gs_error_rangecheck); } break; } break; } ipsp->ip = cip, ipsp->dstate = state; pcis->ips_count = ipsp + 1 - &pcis->ipstack[0]; pcis->os_count = csp + 1 - &pcis->ostack[0]; return c; } /* ------ Output ------ */ /* Put 2 or 4 bytes on a stream (big-endian). */ private void sputc2(stream *s, int i) { sputc(s, (byte)(i >> 8)); sputc(s, (byte)i); } private void sputc4(stream *s, int i) { sputc2(s, i >> 16); sputc2(s, i); } /* Put a Type 2 operator on a stream. */ private void type2_put_op(stream *s, int op) { if (op >= CE_OFFSET) { spputc(s, cx_escape); spputc(s, op - CE_OFFSET); } else sputc(s, op); } /* Put a Type 2 number on a stream. */ private void type2_put_int(stream *s, int i) { if (i >= -107 && i <= 107) sputc(s, (byte)(i + 139)); else if (i <= 1131 && i >= 0) sputc2(s, (c_pos2_0 << 8) + i - 108); else if (i >= -1131 && i < 0) sputc2(s, (c_neg2_0 << 8) - i - 108); else if (i >= -32768 && i <= 32767) { spputc(s, c2_shortint); sputc2(s, i); } else { /* * We can't represent this number directly: compute it. * (This can be done much more efficiently in particular cases; * we'll do this if it ever seems worthwhile.) */ type2_put_int(s, i >> 10); type2_put_int(s, 1024); type2_put_op(s, CE_OFFSET + ce2_mul); type2_put_int(s, i & 1023); type2_put_op(s, CE_OFFSET + ce2_add); } } /* Put a fixed value on a stream. */ private void type2_put_fixed(stream *s, fixed v) { if (fixed_is_int(v)) type2_put_int(s, fixed2int_var(v)); else if (v >= int2fixed(-32768) && v < int2fixed(32768)) { /* We can represent this as a 16:16 number. */ spputc(s, cx_num4); sputc4(s, v << (16 - _fixed_shift)); } else { type2_put_int(s, fixed2int_var(v)); type2_put_fixed(s, fixed_fraction(v)); type2_put_op(s, CE_OFFSET + ce2_add); } } /* Put a stem hint table on a stream. */ private void type2_put_stems(stream *s, const stem_hint_table *psht, int op) { fixed prev = 0; int pushed = 0; int i; for (i = 0; i < psht->count; ++i, pushed += 2) { fixed v0 = psht->data[i].v0; fixed v1 = psht->data[i].v1; if (pushed > ostack_size - 2) { type2_put_op(s, op); pushed = 0; } type2_put_fixed(s, v0 - prev); type2_put_fixed(s, v1 - v0); prev = v1; } type2_put_op(s, op); } /* Put out a hintmask command. */ private void type2_put_hintmask(stream *s, const byte *mask, uint size) { uint ignore; type2_put_op(s, c2_hintmask); sputs(s, mask, size, &ignore); } /* ------ Main program ------ */ /* * Convert a Type 1 Charstring to (unencrypted) Type 2. * For simplicity, we expand all Subrs in-line. * We still need to optimize the output using these patterns: * (vhcurveto hvcurveto)* (vhcurveto hrcurveto | vrcurveto) => * vhcurveto * (hvcurveto vhcurveto)* (hvcurveto vrcurveto | hrcurveto) => * hvcurveto */ #define MAX_STACK ostack_size int psf_convert_type1_to_type2(stream *s, const gs_const_string *pstr, gs_font_type1 *pfont) { gs_type1_state cis; bool first = true; bool replace_hints = false; bool hints_changed = false; byte active_hints[(max_total_stem_hints + 7) / 8]; byte dot_save_hints[(max_total_stem_hints + 7) / 8]; uint hintmask_size; #define HINTS_CHANGED()\ BEGIN\ hints_changed = replace_hints;\ if (hints_changed)\ CHECK_OP(); /* see below */\ END #define CHECK_HINTS_CHANGED()\ BEGIN\ if (hints_changed) {\ type2_put_hintmask(s, active_hints, hintmask_size);\ hints_changed = false;\ }\ END /* * In order to combine Type 1 operators, we usually delay writing * out operators (but not their operands). We must keep track of * the stack depth so we don't exceed it when combining operators. */ int depth; /* of operands on stack */ int prev_op; /* operator to write, -1 if none */ #define CLEAR_OP()\ (depth = 0, prev_op = -1) #define CHECK_OP()\ BEGIN\ if (prev_op >= 0) {\ type2_put_op(s, prev_op);\ CLEAR_OP();\ }\ END /* Do a first pass to collect hints. */ reset_stem_hints(&cis); type1_next_init(&cis, pstr, pfont); for (;;) { int c = type1_next(&cis); fixed *csp = &cis.ostack[cis.os_count - 1]; switch (c) { default: if (c < 0) return c; type1_clear(&cis); continue; case cx_hstem: type1_stem1(&cis, &cis.hstem_hints, csp - 1, NULL); goto clear; case cx_vstem: type1_stem1(&cis, &cis.vstem_hints, csp - 1, NULL); goto clear; case CE_OFFSET + ce1_vstem3: type1_stem3(&cis, &cis.vstem_hints, csp - 5, NULL); goto clear; case CE_OFFSET + ce1_hstem3: type1_stem3(&cis, &cis.hstem_hints, csp - 5, NULL); clear: type1_clear(&cis); continue; case ce1_callothersubr: if (*csp == int2fixed(3)) replace_hints = true; cis.os_count -= 2; continue; case CE_OFFSET + ce1_dotsection: replace_hints = true; continue; case CE_OFFSET + ce1_seac: case cx_endchar: break; } break; } /* * Number the hints for hintmask. We must do this even if we never * replace hints, because type1_stem# uses the index to set bits in * active_hints. */ { int i; for (i = 0; i < cis.hstem_hints.count; ++i) cis.hstem_hints.data[i].index = i; for (i = 0; i < cis.vstem_hints.count; ++i) cis.vstem_hints.data[i].index = i + cis.hstem_hints.count; } if (replace_hints) { hintmask_size = (cis.hstem_hints.count + cis.vstem_hints.count + 7) / 8; memset(active_hints, 0, hintmask_size); } else hintmask_size = 0; /* Do a second pass to write the result. */ type1_next_init(&cis, pstr, pfont); CLEAR_OP(); for (;;) { int c = type1_next(&cis); fixed *csp = &cis.ostack[cis.os_count - 1]; #define POP(n)\ (csp -= (n), cis.os_count -= (n)) int i; fixed mx, my; switch (c) { default: if (c < 0) return c; if (c >= CE_OFFSET) return_error(gs_error_rangecheck); /* The Type 1 use of all other operators is the same in Type 2. */ copy: CHECK_OP(); CHECK_HINTS_CHANGED(); put: for (i = 0; i < cis.os_count; ++i) type2_put_fixed(s, cis.ostack[i]); depth += cis.os_count; prev_op = c; type1_clear(&cis); continue; case cx_hstem: type1_stem1(&cis, &cis.hstem_hints, csp - 1, active_hints); hint: HINTS_CHANGED(); type1_clear(&cis); continue; case cx_vstem: type1_stem1(&cis, &cis.vstem_hints, csp - 1, active_hints); goto hint; case CE_OFFSET + ce1_vstem3: type1_stem3(&cis, &cis.vstem_hints, csp - 5, active_hints); goto hint; case CE_OFFSET + ce1_hstem3: type1_stem3(&cis, &cis.hstem_hints, csp - 5, active_hints); goto hint; case CE_OFFSET + ce1_dotsection: if (cis.dotsection_flag == dotsection_out) { memcpy(dot_save_hints, active_hints, hintmask_size); memset(active_hints, 0, hintmask_size); cis.dotsection_flag = dotsection_in; } else { memcpy(active_hints, dot_save_hints, hintmask_size); cis.dotsection_flag = dotsection_out; } HINTS_CHANGED(); continue; case c1_closepath: case CE_OFFSET + ce1_setcurrentpoint: continue; case cx_vmoveto: mx = 0, my = *csp; POP(1); goto move; case cx_hmoveto: mx = *csp, my = 0; POP(1); goto move; case cx_rmoveto: mx = csp[-1], my = *csp; POP(2); move: CHECK_OP(); if (first) { if (cis.os_count) type2_put_fixed(s, *csp); /* width */ mx += cis.lsb.x, my += cis.lsb.y; first = false; } if (cis.flex_count != flex_max) { /* We're accumulating points for a flex. */ if (type1_next(&cis) != ce1_callothersubr) return_error(gs_error_rangecheck); csp = &cis.ostack[cis.os_count - 1]; if (*csp != int2fixed(2) || csp[-1] != fixed_0) return_error(gs_error_rangecheck); cis.flex_count++; csp[-1] = mx, *csp = my; continue; } CHECK_HINTS_CHANGED(); if (mx == 0) { type2_put_fixed(s, my); depth = 1, prev_op = cx_vmoveto; } else if (my == 0) { type2_put_fixed(s, mx); depth = 1, prev_op = cx_hmoveto; } else { type2_put_fixed(s, mx); type2_put_fixed(s, my); depth = 2, prev_op = cx_rmoveto; } type1_clear(&cis); continue; case c1_hsbw: gs_type1_sbw(&cis, cis.ostack[0], fixed_0, cis.ostack[1], fixed_0); /* * Leave the l.s.b. on the operand stack for the initial hint, * moveto, or endchar command. */ cis.ostack[0] = cis.ostack[1]; sbw: if (cis.ostack[0] == pfont->data.defaultWidthX) cis.os_count = 0; else { cis.ostack[0] -= pfont->data.nominalWidthX; cis.os_count = 1; } if (cis.hstem_hints.count) { if (cis.os_count) type2_put_fixed(s, cis.ostack[0]); cis.os_count = 0; type2_put_stems(s, &cis.hstem_hints, (replace_hints ? c2_hstemhm : cx_hstem)); } if (cis.vstem_hints.count) { if (cis.os_count) type2_put_fixed(s, cis.ostack[0]); cis.os_count = 0; type2_put_stems(s, &cis.vstem_hints, (replace_hints ? c2_vstemhm : cx_vstem)); } continue; case CE_OFFSET + ce1_seac: /* * It is an undocumented feature of the Type 2 CharString * format that endchar + 4 or 5 operands is equivalent to * seac with an implicit asb operand + endchar with 0 or 1 * operands. Remove the asb argument from the stack, but * adjust the adx argument to compensate for the fact that * Type 2 CharStrings don't have any concept of l.s.b. */ csp[-3] += cis.lsb.x - csp[-4]; memmove(csp - 4, csp - 3, sizeof(*csp) * 4); POP(1); /* (falls through) */ case cx_endchar: CHECK_OP(); for (i = 0; i < cis.os_count; ++i) type2_put_fixed(s, cis.ostack[i]); type2_put_op(s, cx_endchar); return 0; case CE_OFFSET + ce1_sbw: gs_type1_sbw(&cis, cis.ostack[0], cis.ostack[1], cis.ostack[2], cis.ostack[3]); cis.ostack[0] = cis.ostack[2]; goto sbw; case ce1_callothersubr: CHECK_OP(); switch (fixed2int_var(*csp)) { default: return_error(gs_error_rangecheck); case 0: /* * The operand stack contains: delta to reference point, * 6 deltas for the two curves, fd, final point, 3, 0. */ csp[-18] += csp[-16], csp[-17] += csp[-15]; memmove(csp - 16, csp - 14, sizeof(*csp) * 11); cis.os_count -= 6, csp -= 6; /* * We could optimize by using [h]flex[1], * but it isn't worth the trouble. */ c = CE_OFFSET + ce2_flex; cis.flex_count = flex_max; /* not inside flex */ cis.ignore_pops = 2; goto copy; case 1: cis.flex_count = 0; cis.os_count -= 2; continue; /*case 2:*/ /* detected in *moveto */ case 3: memset(active_hints, 0, hintmask_size); HINTS_CHANGED(); cis.ignore_pops = 1; cis.os_count -= 2; continue; case 12: case 13: /* Counter control is not implemented. */ cis.os_count -= 2 + fixed2int(csp[-1]); continue; } /* * The remaining cases are strictly for optimization. */ case cx_rlineto: if (depth > MAX_STACK - 2) goto copy; switch (prev_op) { case cx_rlineto: /* rlineto+ => rlineto */ goto put; case cx_rrcurveto: /* rrcurveto+ rlineto => rcurveline */ c = c2_rcurveline; goto put; default: goto copy; } case cx_hlineto: /* hlineto (vlineto hlineto)* [vlineto] => hlineto */ if (depth > MAX_STACK - 1 || prev_op != (depth & 1 ? cx_vlineto : cx_hlineto)) goto copy; c = prev_op; goto put; case cx_vlineto: /* vlineto (hlineto vlineto)* [hlineto] => vlineto */ if (depth > MAX_STACK - 1 || prev_op != (depth & 1 ? cx_hlineto : cx_vlineto)) goto copy; c = prev_op; goto put; case cx_hvcurveto: /* hvcurveto (vhcurveto hvcurveto)* => hvcurveto */ /* (vhcurveto hvcurveto)+ => vhcurveto */ /* * We have to check (depth & 1) because the last curve might * have 5 parameters rather than 4 (see rrcurveto below). */ if ((depth & 1) || depth > MAX_STACK - 4 || prev_op != (depth & 4 ? cx_vhcurveto : cx_hvcurveto)) goto copy; c = prev_op; goto put; case cx_vhcurveto: /* vhcurveto (hvcurveto vhcurveto)* => vhcurveto */ /* (hvcurveto vhcurveto)+ => hvcurveto */ /* See above re the (depth & 1) check. */ if ((depth & 1) || depth > MAX_STACK - 4 || prev_op != (depth & 4 ? cx_hvcurveto : cx_vhcurveto)) goto copy; c = prev_op; goto put; case cx_rrcurveto: if (depth == 0) { if (csp[-1] == 0) { /* A|0 B C D 0 F rrcurveto => [A] B C D F vvcurveto */ c = c2_vvcurveto; csp[-1] = csp[0]; if (csp[-5] == 0) { memcpy(csp - 5, csp - 4, sizeof(*csp) * 4); POP(2); } else POP(1); } else if (*csp == 0) { /* A B|0 C D E 0 rrcurveto => [B] A C D E hhcurveto */ c = c2_hhcurveto; if (csp[-4] == 0) { memcpy(csp - 4, csp - 3, sizeof(*csp) * 3); POP(2); } else { *csp = csp[-5], csp[-5] = csp[-4], csp[-4] = *csp; POP(1); } } /* * We could also optimize: * 0 B C D E F|0 rrcurveto => B C D E [F] vhcurveto * A 0 C D E|0 F rrcurveto => A C D F [E] hvcurveto * but this gets in the way of subsequent optimization * of multiple rrcurvetos, so we don't do it. */ goto copy; } if (depth > MAX_STACK - 6) goto copy; switch (prev_op) { case c2_hhcurveto: /* hrcurveto (x1 0 x2 y2 x3 0 rrcurveto)* => */ /* hhcurveto */ if (csp[-4] == 0 && *csp == 0) { memcpy(csp - 4, csp - 3, sizeof(*csp) * 3); c = prev_op; POP(2); goto put; } goto copy; case c2_vvcurveto: /* rvcurveto (0 y1 x2 y2 0 y3 rrcurveto)* => */ /* vvcurveto */ if (csp[-5] == 0 && csp[-1] == 0) { memcpy(csp - 5, csp - 4, sizeof(*csp) * 3); csp[-2] = *csp; c = prev_op; POP(2); goto put; } goto copy; case cx_hvcurveto: if (depth & 1) goto copy; if (!(depth & 4)) goto hrc; vrc: /* (vhcurveto hvcurveto)+ vrcurveto => vhcurveto */ /* hvcurveto (vhcurveto hvcurveto)* vrcurveto => hvcurveto */ if (csp[-5] != 0) goto copy; memcpy(csp - 5, csp - 4, sizeof(*csp) * 5); c = prev_op; POP(1); goto put; case cx_vhcurveto: if (depth & 1) goto copy; if (!(depth & 4)) goto vrc; hrc: /* (hvcurveto vhcurveto)+ hrcurveto => hvcurveto */ /* vhcurveto (hvcurveto vhcurveto)* hrcurveto => vhcurveto */ if (csp[-4] != 0) goto copy; /* A 0 C D E F => A C D F E */ memcpy(csp - 4, csp - 3, sizeof(*csp) * 2); csp[-2] = *csp; c = prev_op; POP(1); goto put; case cx_rlineto: /* rlineto+ rrcurveto => rlinecurve */ c = c2_rlinecurve; goto put; case cx_rrcurveto: /* rrcurveto+ => rrcurveto */ goto put; default: goto copy; } } } }