diff options
author | Thomas Deutschmann <whissi@gentoo.org> | 2019-10-15 12:24:12 +0200 |
---|---|---|
committer | Thomas Deutschmann <whissi@gentoo.org> | 2020-08-13 11:26:55 +0200 |
commit | e088156d5b620e5e639580dacf85c6dc13823c74 (patch) | |
tree | 57f5c025e203279944da512166c20bc0521d8ccd /devices/vector/gdevtxtw.c | |
download | ghostscript-gpl-patches-e088156d5b620e5e639580dacf85c6dc13823c74.tar.gz ghostscript-gpl-patches-e088156d5b620e5e639580dacf85c6dc13823c74.tar.bz2 ghostscript-gpl-patches-e088156d5b620e5e639580dacf85c6dc13823c74.zip |
Import Ghostscript 9.50ghostscript-9.50
Signed-off-by: Thomas Deutschmann <whissi@gentoo.org>
Diffstat (limited to 'devices/vector/gdevtxtw.c')
-rw-r--r-- | devices/vector/gdevtxtw.c | 2396 |
1 files changed, 2396 insertions, 0 deletions
diff --git a/devices/vector/gdevtxtw.c b/devices/vector/gdevtxtw.c new file mode 100644 index 00000000..aed535bc --- /dev/null +++ b/devices/vector/gdevtxtw.c @@ -0,0 +1,2396 @@ +/* Copyright (C) 2001-2019 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, + modified or distributed except as expressly authorized under the terms + of the license contained in the file LICENSE in this distribution. + + Refer to licensing information at http://www.artifex.com or contact + Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, + CA 94945, U.S.A., +1(415)492-9861, for further information. +*/ + +/*$Id: gdevtxtw.c 7795 2007-03-23 13:56:11Z tim $ */ +/* Device for Unicode (UTF-8 or UCS2) text extraction */ +#include "memory_.h" +#include "string_.h" +#include "gp.h" /* for gp_file_name_sizeof */ +#include "gx.h" +#include "gserrors.h" +#include "gsparam.h" +#include "gsutil.h" +#include "gxdevice.h" +#include "gsdevice.h" /* requires gsmatrix.h */ +#include "gxfont.h" +#include "gxfont0.h" +#include "gstext.h" +#include "gxfcid.h" +#include "gxgstate.h" +#include "gxpath.h" +#include "gdevagl.h" +#include "gxdevsop.h" +#include "gzpath.h" +#include "gdevkrnlsclass.h" /* 'standard' built in subclasses, currently First/Last Page and obejct filter */ + +/* #define TRACE_TXTWRITE 1 */ + +extern single_glyph_list_t *SingleGlyphList; +extern double_glyph_list_t *DoubleGlyphList; +extern treble_glyph_list_t *TrebleGlyphList; +extern quad_glyph_list_t *QuadGlyphList; +/* + * Define the structure used to return glyph width information. Note that + * there are two different sets of width information: real-number (x,y) + * values, which give the true advance width, and an integer value, which + * gives an X advance width for WMode = 0 or a Y advance width for WMode = 1. + * The return value from txt_glyph_width() indicates which of these is/are + * valid. + */ +typedef struct txt_glyph_width_s { + double w; + gs_point xy; + gs_point v; /* glyph origin shift */ +} txt_glyph_width_t; +typedef struct txt_glyph_widths_s { + txt_glyph_width_t Width; /* unmodified, for Widths */ + txt_glyph_width_t real_width; /* possibly modified, for rendering */ + bool replaced_v; +} txt_glyph_widths_t; + +/* Structure to record the Unicode characters, the total width of the text + * recorded, and various useful attributes such as the font, size, colour + * rendering mode, writing mode etc. These are stored as a series of x-ordered + * entries in a list, using the starting x co-ordinate. + */ +typedef struct text_list_entry_s { + struct text_list_entry_s *previous; + struct text_list_entry_s *next; + + gs_point start; + gs_point end; + gs_point FontBBox_bottomleft, FontBBox_topright; + float *Widths; + unsigned short *Unicode_Text; + int Unicode_Text_Size; + int render_mode; + + gs_matrix matrix; /* Tm et al */ + + gs_font *font; + char *FontName; + int wmode; /* WMode of font */ + double PaintType0Width; + double size; +} text_list_entry_t; + +/* Structure to maintain a list of text fragments, ordered by X co-ordinate. + * These structures are themselves maintained in a Y-ordered list. + */ +typedef struct page_text_list_s { + struct page_text_list_s *previous; + struct page_text_list_s *next; + gs_point start; + float MinY, MaxY; + text_list_entry_t *x_ordered_list; +} page_text_list_t; + +/* A simple structure to maintain the lists of text fragments, it is also + * a convenient place to record the page number and anything else we may + * want to record that is relevant to the page rather than the text. + */ +typedef struct page_text_s { + int PageNum; + page_text_list_t *y_ordered_list; + text_list_entry_t *unsorted_text_list; +} page_text_t; + +/* The custom sub-classed device structure */ +typedef struct gx_device_txtwrite_s { + gx_device_common; + page_text_t PageData; + char fname[gp_file_name_sizeof]; /* OutputFile */ + gp_file *file; + int TextFormat; +#ifdef TRACE_TXTWRITE + gp_file *DebugFile; +#endif +} gx_device_txtwrite_t; + +/* Device procedures */ +static dev_proc_open_device(txtwrite_open_device); +static dev_proc_close_device(txtwrite_close_device); +static dev_proc_output_page(txtwrite_output_page); +static dev_proc_fill_rectangle(txtwrite_fill_rectangle); +static dev_proc_get_params(txtwrite_get_params); +static dev_proc_put_params(txtwrite_put_params); +static dev_proc_fill_path(txtwrite_fill_path); +static dev_proc_stroke_path(txtwrite_stroke_path); +static dev_proc_text_begin(txtwrite_text_begin); +static dev_proc_strip_copy_rop(txtwrite_strip_copy_rop); +static dev_proc_dev_spec_op(txtwrite_dev_spec_op); + + +/* The device prototype */ +#define X_DPI 72 +#define Y_DPI 72 + +/* Define the text enumerator. */ +typedef struct textw_text_enum_s { + gs_text_enum_common; + bool charproc_accum; + bool cdevproc_callout; + double cdevproc_result[10]; + float *Widths; + unsigned short *TextBuffer; + int TextBufferIndex; + text_list_entry_t *text_state; +} textw_text_enum_t; +#define private_st_textw_text_enum()\ + extern_st(st_gs_text_enum);\ + gs_private_st_suffix_add0(st_textw_text_enum, textw_text_enum_t,\ + "textw_text_enum_t", textw_text_enum_enum_ptrs, textw_text_enum_reloc_ptrs,\ + st_gs_text_enum) + +private_st_textw_text_enum(); + +const gx_device_txtwrite_t gs_txtwrite_device = +{ + /* Define the device as 8-bit gray scale to avoid computing halftones. */ + std_device_dci_body(gx_device_txtwrite_t, 0, "txtwrite", + DEFAULT_WIDTH_10THS * X_DPI / 10, + DEFAULT_HEIGHT_10THS * Y_DPI / 10, + X_DPI, Y_DPI, + 1, 8, 255, 0, 256, 1), + {txtwrite_open_device, + NULL, /*gx_upright_get_initial_matrix,*/ + NULL, /*gx_default_sync_output,*/ + txtwrite_output_page, + txtwrite_close_device, + NULL, /*gx_default_gray_map_rgb_color,*/ + NULL, /*gx_default_gray_map_color_rgb,*/ + txtwrite_fill_rectangle, /* Can't be NULL and there is no gx_default_fill_rectangle! */ + NULL, /*gx_default_tile_rectangle,*/ + NULL, /*gx_default_copy_mono,*/ + NULL, /*gx_default_copy_color,*/ + NULL, /*gx_default_draw_line,*/ + NULL, /*gx_default_get_bits,*/ + txtwrite_get_params, + txtwrite_put_params, + NULL, /*gx_default_map_cmyk_color,*/ + NULL, /*gx_default_get_xfont_procs,*/ + NULL, /*gx_default_get_xfont_device,*/ + NULL, /*gx_default_map_rgb_alpha_color,*/ + gx_page_device_get_page_device, /*gx_page_device_get_page_device,*/ + NULL, /* get_alpha_bits */ + NULL, /*gx_default_copy_alpha,*/ + NULL, /* get_band */ + NULL, /* copy_rop */ + txtwrite_fill_path, + txtwrite_stroke_path, + NULL, /*gx_default_fill_mask,*/ + NULL, /*gx_default_fill_trapezoid,*/ + NULL, /*gx_default_fill_parallelogram,*/ + NULL, /*gx_default_fill_triangle,*/ + NULL, /*gx_default_draw_thin_line,*/ + NULL, /* begin image */ + NULL, /* image_data */ + NULL, /* end_image */ + NULL, /*gx_default_strip_tile_rectangle,*/ + txtwrite_strip_copy_rop, + NULL, /* get_clipping_box */ + NULL, /* txtwrite_begin_typed_image */ + NULL, /* get_bits_rectangle */ + NULL, /*gx_default_map_color_rgb_alpha,*/ + gx_null_create_compositor, + NULL, /* get_hardware_params */ + txtwrite_text_begin, + NULL, /* finish_copydevice */ + NULL, /* begin_transparency_group */ + NULL, /* end_transparency_group */ + NULL, /* begin_transparency_mask */ + NULL, /* end_transparency_mask */ + NULL, /* discard_transparency_layer */ + NULL, /* get_color_mapping_procs */ + NULL, /* get_color_comp_index */ + NULL, /* encode_color */ + NULL, /* decode_color */ + NULL, /* pattern manager */ + NULL, /* fill_rectangle_hl_color */ + NULL, /* include_color_space */ + NULL, /* fill_linear_color_scanline */ + NULL, /* fill_linear_color_trapezoid */ + NULL, /* fill_linear_color_triangle */ + NULL, /* update_spot_equivalent_colors */ + NULL, /* ret_devn_params */ + NULL, /* fillpage */ + NULL, /* push_transparency_state */ + NULL, /* pop_transparency_state */ + NULL, /* put_image */ + txtwrite_dev_spec_op, /* dev_spec_op */ + NULL, /* copy_planes */ + NULL, /* get_profile */ + NULL, /* set_graphics_type_tag */ + NULL, /* strip_copy_rop2 */ + NULL /* strip_tile_rect_devn */ + }, + { 0 }, /* Page Data */ + { 0 }, /* Output Filename */ + 0, /* Output FILE * */ + 3 /* TextFormat */ +}; + +typedef struct gx_device_textwrite_s gx_device_textw; + +static const gs_param_item_t txt_param_items[] = { +#define pi(key, type, memb) { key, type, offset_of(gx_device_txtwrite_t, memb) } + pi("TextFormat", gs_param_type_int, TextFormat), +#undef pi + gs_param_item_end +}; + +/* ---------------- Open/close/page ---------------- */ + +static int +txtwrite_open_device(gx_device * dev) +{ + gx_device_txtwrite_t *const tdev = (gx_device_txtwrite_t *) dev; + int code = 0; + + gx_device_fill_in_procs(dev); + if (tdev->fname[0] == 0) + return_error(gs_error_undefinedfilename); + + tdev->PageData.PageNum = 0; + tdev->PageData.y_ordered_list = NULL; + tdev->file = NULL; +#ifdef TRACE_TXTWRITE + tdev->DebugFile = gp_fopen(dev->memory,"/temp/txtw_dbg.txt", "wb+"); +#endif + dev->color_info.separable_and_linear = GX_CINFO_SEP_LIN; + set_linear_color_bits_mask_shift(dev); + dev->interpolate_control = 0; + + code = install_internal_subclass_devices((gx_device **)&dev, NULL); + return code; +} + +static int +txtwrite_close_device(gx_device * dev) +{ + int code = 0; + gx_device_txtwrite_t *const tdev = (gx_device_txtwrite_t *) dev; + + if (tdev->file) { + code = gx_device_close_output_file(dev, tdev->fname, tdev->file); + tdev->file = 0; + } + +#ifdef TRACE_TXTWRITE + fclose(tdev->DebugFile); +#endif + return code; +} + +/* Routine inspects horizontal lines of text to see if they can be collapsed + * into a single line. This essentially detects superscripts and subscripts + * as well as lines which are slightly mis-aligned. + */ +static int merge_vertically(gx_device_txtwrite_t *tdev) +{ +#ifdef TRACE_TXTWRITE + text_list_entry_t *debug_x; +#endif + page_text_list_t *y_list = tdev->PageData.y_ordered_list; + + while (y_list && y_list->next) { + page_text_list_t *next = y_list->next; + bool collision = false; + float overlap = (y_list->start.y + y_list->MaxY) - (next->start.y + next->MinY); + + if (overlap >= (y_list->MaxY - y_list->MinY) / 4) { + /* At least a 25% overlap, lets test for x collisions */ + text_list_entry_t *upper = y_list->x_ordered_list, *lower; + while (upper && !collision) { + lower = next->x_ordered_list; + while (lower && !collision) { + if (upper->start.x >= lower->start.x) { + if (upper->start.x <= lower->end.x) { + /* Collision */ + collision = true; + break; + } + } else { + if (upper->end.x > lower->start.x) { + /* Collision */ + collision = true; + break; + } + } + lower = lower->next; + } + upper = upper->next; + } + if (!collision) { + text_list_entry_t *from, *to, *new_order, *current; + /* Consolidate y lists */ + to = y_list->x_ordered_list; + from = next->x_ordered_list; +#ifdef TRACE_TXTWRITE + fprintf(tdev->DebugFile, "\nConsolidating two horizontal lines, line 1:"); + debug_x = from; + while (debug_x) { + gp_fprintf(tdev->DebugFile, "\n\t"); + gp_fwrite(debug_x->Unicode_Text, sizeof(unsigned short), debug_x->Unicode_Text_Size, tdev->DebugFile); + debug_x = debug_x->next; + } + fprintf(tdev->DebugFile, "\nConsolidating two horizontal lines, line 2"); + debug_x = to; + while (debug_x) { + gp_fprintf(tdev->DebugFile, "\n\t"); + gp_fwrite(debug_x->Unicode_Text, sizeof(unsigned short), debug_x->Unicode_Text_Size, tdev->DebugFile); + debug_x = debug_x->next; + } +#endif + if (from->start.x < to->start.x) { + current = new_order = from; + from = from->next; + } else { + current = new_order = to; + to = to->next; + } + while (to && from) { + if (to->start.x < from->start.x) { + current->next = to; + to->previous = current; + to = to->next; + } else { + current->next = from; + from->previous = current; + from = from->next; + } + current = current->next; + } + if (to) { + to->previous = current; + current->next = to; + } else { + if (from) { + from->previous = current; + current->next = from; + } + } + y_list->x_ordered_list = new_order; +#ifdef TRACE_TXTWRITE + fprintf(tdev->DebugFile, "\nAfter:"); + debug_x = new_order; + while (debug_x) { + gp_fprintf(tdev->DebugFile, "\n\t"); + gp_fwrite(debug_x->Unicode_Text, sizeof(unsigned short), debug_x->Unicode_Text_Size, tdev->DebugFile); + debug_x = debug_x->next; + } + fprintf(tdev->DebugFile, "\n"); +#endif + y_list->next = next->next; + if (next->next) + next->next->previous = y_list; + gs_free(tdev->memory, next, 1, sizeof(page_text_list_entry_t), "txtwrite free text list"); + } else + y_list = next; + } else + y_list = next; + } + return 0; +} + +/* Routine to merge horizontally adjacent text fragments. If the distance + * between two horizontal fragments is small, then they are treated as one + * frament of text, if its larger then we insert a space (and set the Width + * entry appropriately). Otherwise we leave them as separate. + */ +static int merge_horizontally(gx_device_txtwrite_t *tdev) +{ +#ifdef TRACE_TXTWRITE + text_list_entry_t *debug_x; +#endif + unsigned short UnicodeSpace = 0x20; + page_text_list_t *y_list = tdev->PageData.y_ordered_list; + + while (y_list) { + float average_width; + text_list_entry_t *from, *to; + from = y_list->x_ordered_list; + to = from->next; + + while (from && to) { + average_width = (from->end.x - from->start.x) / from->Unicode_Text_Size; + + if (to->start.x - from->end.x < average_width / 2) { + /* consolidate fragments */ + unsigned short *NewText; + float *NewWidths; + + NewText = (unsigned short *)gs_malloc(tdev->memory->stable_memory, + (from->Unicode_Text_Size + to->Unicode_Text_Size), sizeof(unsigned short), "txtwrite alloc working text buffer"); + NewWidths = (float *)gs_malloc(tdev->memory->stable_memory, + (from->Unicode_Text_Size + to->Unicode_Text_Size), sizeof(float), "txtwrite alloc Widths array"); + if (!NewText || !NewWidths) { + if (NewText) + gs_free(tdev->memory, NewText, from->Unicode_Text_Size + to->Unicode_Text_Size, sizeof (unsigned short), "free working text fragment"); + /* ran out of memory, don't consolidate */ + from = from->next; + to = to->next; + } else { +#ifdef TRACE_TXTWRITE + gp_fprintf(tdev->DebugFile, "Consolidating two horizontal fragments in one line, before:\n\t"); + gp_fwrite(from->Unicode_Text, sizeof(unsigned short), from->Unicode_Text_Size, tdev->DebugFile); + gp_fprintf(tdev->DebugFile, "\n\t"); + gp_fwrite(to->Unicode_Text, sizeof(unsigned short), to->Unicode_Text_Size, tdev->DebugFile); +#endif + memcpy(NewText, from->Unicode_Text, from->Unicode_Text_Size * sizeof(unsigned short)); + memcpy(&NewText[from->Unicode_Text_Size], to->Unicode_Text, to->Unicode_Text_Size * sizeof(unsigned short)); + memcpy(NewWidths, from->Widths, from->Unicode_Text_Size * sizeof(float)); + memcpy(&NewWidths[from->Unicode_Text_Size], to->Widths, to->Unicode_Text_Size * sizeof(float)); + gs_free(tdev->memory, from->Unicode_Text, from->Unicode_Text_Size, sizeof (unsigned short), "free consolidated text fragment"); + gs_free(tdev->memory, to->Unicode_Text, to->Unicode_Text_Size, sizeof (unsigned short), "free consolidated text fragment"); + gs_free(tdev->memory, from->Widths, from->Unicode_Text_Size, sizeof (float), "free consolidated Widths array"); + gs_free(tdev->memory, to->Widths, to->Unicode_Text_Size, sizeof (float), "free consolidated Widths array"); + gs_free(tdev->memory, to->FontName, 1, strlen(from->FontName) + 1, "free FontName"); + from->Unicode_Text = NewText; + from->Unicode_Text_Size += to->Unicode_Text_Size; + from->Widths = NewWidths; +#ifdef TRACE_TXTWRITE + gp_fprintf(tdev->DebugFile, "After:\n\t"); + gp_fwrite(from->Unicode_Text, sizeof(unsigned short), from->Unicode_Text_Size, tdev->DebugFile); +#endif + from->end = to->end; + from->next = to->next; + if (from->next) + from->next->previous = from; + gs_free(tdev->memory, to, 1, sizeof(text_list_entry_t), "free consolidated fragment"); + to = from->next; + } + } else { + if (to->start.x - from->end.x < average_width *2){ + unsigned short *NewText; + float *NewWidths; + + NewText = (unsigned short *)gs_malloc(tdev->memory->stable_memory, + (from->Unicode_Text_Size + to->Unicode_Text_Size + 1), sizeof(unsigned short), "txtwrite alloc text state"); + NewWidths = (float *)gs_malloc(tdev->memory->stable_memory, + (from->Unicode_Text_Size + to->Unicode_Text_Size + 1), sizeof(float), "txtwrite alloc Widths array"); + if (!NewText || !NewWidths) { + if (NewText) + gs_free(tdev->memory, NewText, from->Unicode_Text_Size + to->Unicode_Text_Size, sizeof (unsigned short), "free working text fragment"); + /* ran out of memory, don't consolidate */ + from = from->next; + to = to->next; + } else { + memcpy(NewText, from->Unicode_Text, from->Unicode_Text_Size * sizeof(unsigned short)); + memcpy(&NewText[from->Unicode_Text_Size], &UnicodeSpace, sizeof(unsigned short)); + memcpy(&NewText[from->Unicode_Text_Size + 1], to->Unicode_Text, to->Unicode_Text_Size * sizeof(unsigned short)); + memcpy(NewWidths, from->Widths, from->Unicode_Text_Size * sizeof(float)); + NewWidths[from->Unicode_Text_Size] = to->start.x - from->end.x; + memcpy(&NewWidths[from->Unicode_Text_Size + 1], to->Widths, to->Unicode_Text_Size * sizeof(float)); + gs_free(tdev->memory, from->Unicode_Text, from->Unicode_Text_Size, sizeof (unsigned short), "free consolidated text fragment"); + gs_free(tdev->memory, to->Unicode_Text, to->Unicode_Text_Size, sizeof (unsigned short), "free consolidated text fragment"); + gs_free(tdev->memory, from->Widths, from->Unicode_Text_Size, sizeof (float), "free consolidated Widths array"); + gs_free(tdev->memory, to->Widths, to->Unicode_Text_Size, sizeof (float), "free consolidated Widths array"); + gs_free(tdev->memory, to->FontName, 1, strlen(from->FontName) + 1, "free FontName"); + from->Unicode_Text = NewText; + from->Unicode_Text_Size += to->Unicode_Text_Size + 1; + from->Widths = NewWidths; + from->end = to->end; + from->next = to->next; + if (from->next) + from->next->previous = from; + gs_free(tdev->memory, to, 1, sizeof(text_list_entry_t), "free consolidated fragment"); + to = from->next; + } + } else { + from = from->next; + to = to->next; + } + } + } + y_list = y_list->next; + } + return 0; +} + +static int write_simple_text(unsigned short *text, int count, gx_device_txtwrite_t *tdev) +{ + switch(tdev->TextFormat) { + case 2: + gp_fwrite(text, sizeof (unsigned short), count, tdev->file); + break; + case 3: + { + int i; + unsigned short *UTF16 = (unsigned short *)text; + unsigned char UTF8[3]; + + for (i=0;i<count;i++) { + if (*UTF16 < 0x80) { + UTF8[0] = *UTF16 & 0xff; + gp_fwrite (UTF8, sizeof(unsigned char), 1, tdev->file); + } else { + if (*UTF16 < 0x800) { + UTF8[0] = (*UTF16 >> 6) + 0xC0; + UTF8[1] = (*UTF16 & 0x3F) + 0x80; + gp_fwrite (UTF8, sizeof(unsigned char), 2, tdev->file); + } else { + UTF8[0] = (*UTF16 >> 12) + 0xE0; + UTF8[1] = ((*UTF16 >> 6) & 0x3F) + 0x80; + UTF8[2] = (*UTF16 & 0x3F) + 0x80; + gp_fwrite (UTF8, sizeof(unsigned char), 3, tdev->file); + } + } + UTF16++; + } + } + break; + default: + return gs_note_error(gs_error_rangecheck); + break; + } + return 0; +} + +static int simple_text_output(gx_device_txtwrite_t *tdev) +{ + int chars_wide; + float char_size, min_size, min_width_size; +#ifdef TRACE_TXTWRITE + text_list_entry_t *debug_x; +#endif + text_list_entry_t * x_entry; + page_text_list_t *y_list; + unsigned short UnicodeSpace = 0x20, UnicodeEOL[2] = {0x00D, 0x0a}; + + merge_vertically(tdev); + + merge_horizontally(tdev); + + min_size = (float)tdev->width; + /* Estimate maximum text density */ + y_list = tdev->PageData.y_ordered_list; + while (y_list) { + x_entry = y_list->x_ordered_list; + while (x_entry) { + if (x_entry->size < min_size) + min_size = x_entry->size; + x_entry = x_entry->next; + } + y_list = y_list->next; + } + + min_width_size = min_size; + y_list = tdev->PageData.y_ordered_list; + while (y_list) { + float width; + + x_entry = y_list->x_ordered_list; + while (x_entry) { + width = (x_entry->end.x - x_entry->start.x) / x_entry->Unicode_Text_Size; + if (x_entry->next) { + /* If we have following text, check to see if *not* using the newly calculated size would result in + * the end of the text going past the beginning of the following text. If it does then we must + * use the new minimum, regardless of how small it is! The foregoing comment isn't quite true... + * we never used a width of 0 because that would result in an endless loop, we need to allow a little + * slop in case rounding errors mean that the x difference from start to end of the text is almost, + * but not quite, zero. + */ + if (x_entry->start.x + ((x_entry->Unicode_Text_Size + 1) * min_width_size) > x_entry->next->start.x && width > 0.001) + min_width_size = width; + } else { + if (width < min_width_size && width >= (float)min_size * 0.75) + min_width_size = width; + } + x_entry = x_entry->next; + } + y_list = y_list->next; + } + + min_size = min_width_size; + chars_wide = (int)ceil(tdev->width / min_size); + char_size = (float)tdev->width / (float)chars_wide; + + y_list = tdev->PageData.y_ordered_list; + while (y_list) { + float xpos = 0; + x_entry = y_list->x_ordered_list; + while (x_entry) { + while (xpos < x_entry->start.x) { + write_simple_text(&UnicodeSpace, 1, tdev); + xpos += char_size; + } + write_simple_text(x_entry->Unicode_Text, x_entry->Unicode_Text_Size, tdev); + xpos += x_entry->Unicode_Text_Size * char_size; + if (x_entry->next) { + x_entry = x_entry->next; + } else { + x_entry = NULL; + } + } + write_simple_text((unsigned short *)&UnicodeEOL, 2, tdev); + if (y_list->next) { + y_list = y_list->next; + } else { + y_list = NULL; + } + } + return 0; +} + +static int escaped_Unicode (unsigned short Unicode, char *Buf) +{ + switch (Unicode) + { + case 0x3C: gs_sprintf(Buf, "<"); break; + case 0x3E: gs_sprintf(Buf, ">"); break; + case 0x26: gs_sprintf(Buf, "&"); break; + case 0x22: gs_sprintf(Buf, """); break; + case 0x27: gs_sprintf(Buf, "'"); break; + default: + if (Unicode >= 32 && Unicode <= 127) + gs_sprintf(Buf, "%c", Unicode); + else + gs_sprintf(Buf, "&#x%x;", Unicode); + break; + } + + return 0; +} + +static int decorated_text_output(gx_device_txtwrite_t *tdev) +{ + int i; + text_list_entry_t * x_entry, *next_x; + char TextBuffer[512], Escaped[32]; + float xpos; + page_text_list_t *y_list; +#ifdef TRACE_TXTWRITE + text_list_entry_t *debug_x; +#endif + + if (tdev->TextFormat == 0) { + gp_fwrite("<page>\n", sizeof(unsigned char), 7, tdev->file); + x_entry = tdev->PageData.unsorted_text_list; + while (x_entry) { + next_x = x_entry->next; + gs_sprintf(TextBuffer, "<span bbox=\"%0.0f %0.0f %0.0f %0.0f\" font=\"%s\" size=\"%0.4f\">\n", x_entry->start.x, x_entry->start.y, + x_entry->end.x, x_entry->end.y, x_entry->FontName,x_entry->size); + gp_fwrite(TextBuffer, 1, strlen(TextBuffer), tdev->file); + xpos = x_entry->start.x; + for (i=0;i<x_entry->Unicode_Text_Size;i++) { + escaped_Unicode(x_entry->Unicode_Text[i], (char *)&Escaped); + gs_sprintf(TextBuffer, "<char bbox=\"%0.0f %0.0f %0.0f %0.0f\" c=\"%s\"/>\n", xpos, + x_entry->start.y, xpos + x_entry->Widths[i], x_entry->end.y, Escaped); + gp_fwrite(TextBuffer, 1, strlen(TextBuffer), tdev->file); + xpos += x_entry->Widths[i]; + } + gp_fwrite("</span>\n", sizeof(unsigned char), 8, tdev->file); + + x_entry = next_x; + } + gp_fwrite("</page>\n", sizeof(unsigned char), 8, tdev->file); + } else { + + merge_vertically(tdev); + merge_horizontally(tdev); + + y_list = tdev->PageData.y_ordered_list; + gp_fwrite("<page>\n", sizeof(unsigned char), 7, tdev->file); + /* Walk the list looking for 'blocks' */ + do { + page_text_list_t *temp; + page_text_t block; + page_text_list_t *block_line; + float BBox[4]; + + memset(&block, 0x00, sizeof(page_text_t)); + memset(BBox, 0x00, sizeof(float) * 4); + + while (y_list) { + if (block.y_ordered_list) { + text_list_entry_t *x_entry = y_list->x_ordered_list; + + block_line = block.y_ordered_list; + while (x_entry) { + if (x_entry->start.x > BBox[2] || x_entry->end.x < BBox[0] || + x_entry->start.y > (BBox[1] + (BBox[3] - BBox[1]))) { + ; + } else { + block_line->next = (page_text_list_t *)gs_malloc(tdev->memory->stable_memory, 1, + sizeof(page_text_list_t), "txtwrite alloc Y-list"); + memset(block_line->next, 0x00, sizeof(page_text_list_t)); + block_line = block_line->next; + block_line->x_ordered_list = x_entry; + if(x_entry->next) + x_entry->next->previous = x_entry->previous; + if (x_entry->previous) + x_entry->previous->next = x_entry->next; + else { + if (x_entry->next == 0x00) { + /* remove Y entry */ + temp = y_list->next; + if (y_list->previous) + y_list->previous->next = y_list->next; + if (y_list->next) + y_list->next->previous = y_list->previous; + else { + if (y_list->previous == 0x00) { + tdev->PageData.y_ordered_list = 0x00; + } + } + gs_free(tdev->memory, y_list, 1, sizeof(page_text_list_t), "txtwrite free text list"); + if (tdev->PageData.y_ordered_list == y_list) + tdev->PageData.y_ordered_list = temp; + y_list = temp; + x_entry = x_entry->next; + continue; + } + } + if (block_line->x_ordered_list->start.x < BBox[0]) + BBox[0] = block_line->x_ordered_list->start.x; + if (block_line->x_ordered_list->start.y < BBox[1]) + BBox[1] = block_line->x_ordered_list->start.y; + if (block_line->x_ordered_list->end.x < BBox[2]) + BBox[2] = block_line->x_ordered_list->end.x; + if (block_line->x_ordered_list->end.y + block_line->x_ordered_list->FontBBox_topright.y < BBox[3]) + BBox[3] = block_line->x_ordered_list->end.y + block_line->x_ordered_list->FontBBox_topright.y; + } + x_entry = x_entry->next; + } + } else { + block.y_ordered_list = block_line = (page_text_list_t *)gs_malloc(tdev->memory->stable_memory, 1, + sizeof(page_text_list_t), "txtwrite alloc Y-list"); + memset(block_line, 0x00, sizeof(page_text_list_t)); + block_line->x_ordered_list = y_list->x_ordered_list; + y_list->x_ordered_list = y_list->x_ordered_list->next; + if (y_list->x_ordered_list == 0x00) { + temp = y_list->next; + if (y_list->previous) + y_list->previous->next = y_list->next; + if (y_list->next) + y_list->next->previous = y_list->previous; + else { + if (y_list->previous == 0x00) { + tdev->PageData.y_ordered_list = 0x00; + } + } + gs_free(tdev->memory, y_list, 1, sizeof(page_text_list_t), "txtwrite free text list"); + if (tdev->PageData.y_ordered_list == y_list) + tdev->PageData.y_ordered_list = temp; + y_list = temp; + continue; + } + block_line->x_ordered_list->next = block_line->x_ordered_list->previous = 0x00; + BBox[0] = block_line->x_ordered_list->start.x; + BBox[1] = block_line->x_ordered_list->start.y; + BBox[2] = block_line->x_ordered_list->end.x; + BBox[3] = block_line->x_ordered_list->end.y + block_line->x_ordered_list->FontBBox_topright.y; + } + if (y_list) + y_list = y_list->next; + } + /* FIXME - need to free the used memory in here */ + gp_fwrite("<block>\n", sizeof(unsigned char), 8, tdev->file); + block_line = block.y_ordered_list; + while (block_line) { + gp_fwrite("<line>\n", sizeof(unsigned char), 7, tdev->file); + x_entry = block_line->x_ordered_list; + while(x_entry) { + gs_sprintf(TextBuffer, "<span bbox=\"%0.0f %0.0f %0.0f %0.0f\" font=\"%s\" size=\"%0.4f\">\n", x_entry->start.x, x_entry->start.y, + x_entry->end.x, x_entry->end.y, x_entry->FontName,x_entry->size); + gp_fwrite(TextBuffer, 1, strlen(TextBuffer), tdev->file); + xpos = x_entry->start.x; + for (i=0;i<x_entry->Unicode_Text_Size;i++) { + escaped_Unicode(x_entry->Unicode_Text[i], (char *)&Escaped); + gs_sprintf(TextBuffer, "<char bbox=\"%0.0f %0.0f %0.0f %0.0f\" c=\"%s\"/>\n", xpos, + x_entry->start.y, xpos + x_entry->Widths[i], x_entry->end.y, Escaped); + gp_fwrite(TextBuffer, 1, strlen(TextBuffer), tdev->file); + xpos += x_entry->Widths[i]; + } + gp_fwrite("</span>\n", sizeof(unsigned char), 8, tdev->file); + x_entry = x_entry->next; + } + gp_fwrite("</line>\n", sizeof(unsigned char), 8, tdev->file); + block_line = block_line->next; + } + gp_fwrite("</block>\n", sizeof(unsigned char), 9, tdev->file); + y_list = tdev->PageData.y_ordered_list; + } while (y_list); + + gp_fwrite("</page>\n", sizeof(unsigned char), 8, tdev->file); + } + return 0; +} + +static int +txtwrite_output_page(gx_device * dev, int num_copies, int flush) +{ + int code; + gx_device_txtwrite_t *const tdev = (gx_device_txtwrite_t *) dev; + text_list_entry_t * x_entry, *next_x; + page_text_list_t *y_list; + gs_parsed_file_name_t parsed; + const char *fmt; + const short BOM = 0xFEFF; + + if (!tdev->file) { + /* Either this is the first page, or we're doing one file per page */ + code = gx_device_open_output_file(dev, tdev->fname, + true, false, &tdev->file); /* binary, sequential */ + if (code < 0) + return code; + } + + switch(tdev->TextFormat) { + case 0: + case 1: + code = decorated_text_output(tdev); + if (code < 0) + return code; + break; + + case 2: + gp_fwrite (&BOM, sizeof(unsigned short), 1, tdev->file); + /* fall through */ + case 3: + code = simple_text_output(tdev); + if (code < 0) + return code; + break; + + default: + return gs_note_error(gs_error_rangecheck); + break; + } + + code = gx_default_output_page(dev, num_copies, flush); + if (code < 0) + return code; + + /* free the sorted fragment list! */ + y_list = tdev->PageData.y_ordered_list; + while (y_list) { + x_entry = y_list->x_ordered_list; + while (x_entry) { + gs_free(tdev->memory, x_entry->Unicode_Text, x_entry->Unicode_Text_Size, sizeof (usnigned short), "txtwrite free text fragment text buffer"); + gs_free(tdev->memory, x_entry->Widths, x_entry->Unicode_Text_Size, sizeof (float), "txtwrite free widths array"); + gs_free(tdev->memory, x_entry->FontName, 1, strlen(x_entry->FontName) + 1, "txtwrite free Font Name"); + if (x_entry->next) { + x_entry = x_entry->next; + gs_free(tdev->memory, x_entry->previous, 1, sizeof(text_list_entry_t), "txtwrite free text fragment"); + } else { + gs_free(tdev->memory, x_entry, 1, sizeof(text_list_entry_t), "txtwrite free text fragment"); + x_entry = NULL; + } + } + if (y_list->next) { + y_list = y_list->next; + gs_free(tdev->memory, y_list->previous, 1, sizeof(page_text_list_t), "txtwrite free text list"); + } else { + gs_free(tdev->memory, y_list, 1, sizeof(page_text_list_t), "txtwrite free text list"); + y_list = NULL; + } + } + tdev->PageData.y_ordered_list = NULL; + + /* free the unsorted fragment list */ + x_entry = tdev->PageData.unsorted_text_list; + while (x_entry) { + next_x = x_entry->next; + gs_free(tdev->memory, x_entry->Unicode_Text, x_entry->Unicode_Text_Size, sizeof (usnigned short), "txtwrite free unsorted text fragment text buffer"); + gs_free(tdev->memory, x_entry->Widths, x_entry->Unicode_Text_Size, sizeof (float), "txtwrite free widths array"); + gs_free(tdev->memory, x_entry->FontName, 1, strlen(x_entry->FontName) + 1, "txtwrite free Font Name"); + gs_free(tdev->memory, x_entry, 1, sizeof(text_list_entry_t), "txtwrite free unsorted text fragment"); + x_entry = next_x; + } + tdev->PageData.unsorted_text_list = NULL; + + code = gx_parse_output_file_name(&parsed, &fmt, tdev->fname, + strlen(tdev->fname), tdev->memory); + + if (code >= 0 && fmt) { /* file per page */ + code = gx_device_close_output_file(dev, tdev->fname, tdev->file); + tdev->file = NULL; + } + return code; +} + +/* ---------------- Low-level drawing ---------------- */ + +static int +txtwrite_fill_rectangle(gx_device * dev, int x, int y, int w, int h, + gx_color_index color) +{ + return 0; +} + +/*static int +txtwrite_copy_alpha(gx_device * dev, const byte * data, int data_x, + int raster, gx_bitmap_id id, int x, int y, int w, int h, + gx_color_index color, int depth) +{ + return 0; +} + +static int +txtwrite_copy_mono(gx_device * dev, const byte * data, int dx, int raster, + gx_bitmap_id id, int x, int y, int w, int h, + gx_color_index zero, gx_color_index one) +{ + return 0; +} +static int +txtwrite_copy_color(gx_device * dev, const byte * data, + int data_x, int raster, gx_bitmap_id id, + int x, int y, int width, int height) +{ + return 0; +} + +static int +txtwrite_strip_tile_rectangle(gx_device * dev, const gx_strip_bitmap * tiles, + int x, int y, int w, int h, gx_color_index color0, gx_color_index color1, + int px, int py) +{ + return 0; +} + +static int +txtwrite_strip_copy_rop(gx_device * dev, + const byte * sdata, int sourcex, uint sraster, + gx_bitmap_id id, + const gx_color_index * scolors, + const gx_strip_bitmap * textures, + const gx_color_index * tcolors, + int x, int y, int w, int h, + int phase_x, int phase_y, gs_logical_operation_t lop) +{ + return 0; +}*/ + +/* ---------------- Parameters ---------------- */ + +static int txtwrite_get_param(gx_device *dev, char *Param, void *list) +{ + gx_device_txtwrite_t *const tdev = (gx_device_txtwrite_t *) dev; + gs_param_list * plist = (gs_param_list *)list; + bool bool_T = true; + + if (strcmp(Param, "OutputFile") == 0) { + gs_param_string ofns; + + ofns.data = (const byte *)tdev->fname, + ofns.size = strlen(tdev->fname), + ofns.persistent = false; + return param_write_string(plist, "OutputFile", &ofns); + } + if (strcmp(Param, "WantsToUnicode") == 0) { + return param_write_bool(plist, "WantsToUnicode", &bool_T); + } + if (strcmp(Param, "PreserveTrMode") == 0) { + return param_write_bool(plist, "PreserveTrMode", &bool_T); + } + if (strcmp(Param, "HighLevelDevice") == 0) { + return param_write_bool(plist, "HighLevelDevice", &bool_T); + } + return_error(gs_error_undefined); +} + +static int +txtwrite_get_params(gx_device * dev, gs_param_list * plist) +{ + int code; + bool bool_T = true; + gs_param_string ofns; + gx_device_txtwrite_t *const tdev = (gx_device_txtwrite_t *) dev; + + code = gx_default_get_params(dev, plist); + if (code < 0) + return code; + + ofns.data = (const byte *)tdev->fname, + ofns.size = strlen(tdev->fname), + ofns.persistent = false; + code = param_write_string(plist, "OutputFile", &ofns); + if (code < 0) + return code; + + code = param_write_bool(plist, "WantsToUnicode", &bool_T); + if (code < 0) + return code; + + code = param_write_bool(plist, "PreserveTrMode", &bool_T); + if (code < 0) + return code; + + code = param_write_bool(plist, "HighLevelDevice", &bool_T); + if (code < 0) + return code; + + code = gs_param_write_items(plist, tdev, NULL, txt_param_items); + return code; +} + +/* We implement put_params to ensure that we keep the important */ +/* device parameters up to date, and to prevent an /undefined error */ +static int +txtwrite_put_params(gx_device * dev, gs_param_list * plist) +{ + gx_device_txtwrite_t *tdev = (gx_device_txtwrite_t *) dev; + int ecode = 0; + int code; + const char *param_name; + gs_param_string ofs; + bool dummy; + + switch (code = param_read_string(plist, (param_name = "OutputFile"), &ofs)) { + case 0: + if (dev->LockSafetyParams && + bytes_compare(ofs.data, ofs.size, + (const byte *)tdev->fname, strlen(tdev->fname))) { + ecode = gs_note_error(gs_error_invalidaccess); + goto ofe; + } + if (ofs.size >= gp_file_name_sizeof) + ecode = gs_error_limitcheck; + else + break; + goto ofe; + default: + ecode = code; + ofe:param_signal_error(plist, param_name, ecode); + /* fall through */ + case 1: + ofs.data = 0; + break; + } + + if (ecode < 0) + return ecode; + + code = param_read_int(plist, "TextFormat", &tdev->TextFormat); + if (code < 0) + return code; + + code = param_read_bool(plist, "WantsToUnicode", &dummy); + if (code < 0) + return code; + + code = param_read_bool(plist, "HighLevelDevice", &dummy); + if (code < 0) + return code; + + code = param_read_bool(plist, "PreserveTrMode", &dummy); + if (code < 0) + return code; + + code = gx_default_put_params(dev, plist); + if (code < 0) + return code; + + dev->interpolate_control = 0; + + if (ofs.data != 0) { /* Close the file if it's open. */ + if (tdev->file != 0) { + gp_fclose(tdev->file); + tdev->file = 0; + } + memcpy(tdev->fname, ofs.data, ofs.size); + tdev->fname[ofs.size] = 0; + } + return 0; +} + +/* ---------------- Polygon drawing ---------------- */ + +/*static int +txtwrite_fill_trapezoid(gx_device * dev, + const gs_fixed_edge * left, const gs_fixed_edge * right, + fixed ybot, fixed ytop, bool swap_axes, + const gx_device_color * pdevc, gs_logical_operation_t lop) +{ + return 0; +} + +static int +txtwrite_fill_parallelogram(gx_device * dev, + fixed px, fixed py, fixed ax, fixed ay, + fixed bx, fixed by, const gx_device_color * pdevc, + gs_logical_operation_t lop) +{ + return 0; +} + +static int +txtwrite_fill_triangle(gx_device * dev, + fixed px, fixed py, fixed ax, fixed ay, fixed bx, fixed by, + const gx_device_color * pdevc, gs_logical_operation_t lop) +{ + return 0; +} + +static int +txtwrite_draw_thin_line(gx_device * dev, + fixed fx0, fixed fy0, fixed fx1, fixed fy1, + const gx_device_color * pdevc, gs_logical_operation_t lop, + fixed adjustx, fixed adjusty) +{ + return 0; +}*/ + +/* ---------------- High-level drawing ---------------- */ + +static int +txtwrite_fill_path(gx_device * dev, const gs_gstate * pgs, gx_path * ppath, + const gx_fill_params * params, const gx_device_color * pdevc, + const gx_clip_path * pcpath) +{ + return 0; +} + +static int +txtwrite_stroke_path(gx_device * dev, const gs_gstate * pgs, gx_path * ppath, + const gx_stroke_params * params, + const gx_drawing_color * pdevc, const gx_clip_path * pcpath) +{ + return 0; +} + +/* ------ Text imaging ------ */ + +static int +txtwrite_font_orig_matrix(const gs_font *font, gs_glyph cid, gs_matrix *pmat) +{ + int code; + + switch (font->FontType) { + case ft_composite: /* subfonts have their own FontMatrix */ + case ft_TrueType: + case ft_CID_TrueType: + /* The TrueType FontMatrix is 1 unit per em, which is what we want. */ + gs_make_identity(pmat); + return 0; + case ft_encrypted: + case ft_encrypted2: + case ft_CID_encrypted: + case ft_user_defined: + case ft_PCL_user_defined: + case ft_GL2_stick_user_defined: + case ft_GL2_531: + /* + * Type 1 fonts are supposed to use a standard FontMatrix of + * [0.001 0 0 0.001 0 0], with a 1000-unit cell. However, + * Windows NT 4.0 creates Type 1 fonts, apparently derived from + * TrueType fonts, that use a 2048-unit cell and corresponding + * FontMatrix. Also, some PS programs perform font scaling by + * replacing FontMatrix like this : + * + * /f12 /Times-Roman findfont + * copyfont % (remove FID) + * dup /FontMatrix [0.012 0 0 0.012 0 0] put + * definefont + * /f12 1 selectfont + * + * Such fonts are their own "base font", but the orig_matrix + * must still be set to 0.001, not 0.012 . + * + * The old code used a heuristic to detect and correct for this here. + * Unfortunately it doesn't work properly when it meets a font + * with FontMatrix like this : + * + * /FontMatrix [1 2288 div 0 0 1 2288 div 0 0 ] def + * + * (the bug 686970). Also comparefiles\455690.pdf appears to + * have similar problem. Therefore we added a support to lib/gs_fonts.ps, + * src/zbfont.c, src/gsfont.c that provides an acces to the original + * font via a special key OrigFont added to the font dictionary while definefont. + * Now we work through this access with PS interpreter, + * but keep the old heuristic for other clients. + */ + { + const gs_font *base_font = font; + + while (base_font->base != base_font) + base_font = base_font->base; + if (font->FontType == ft_user_defined || + font->FontType == ft_PCL_user_defined || + font->FontType == ft_GL2_stick_user_defined || + font->FontType == ft_GL2_531) + *pmat = base_font->FontMatrix; + else if (base_font->orig_FontMatrix.xx != 0 || base_font->orig_FontMatrix.xy != 0 || + base_font->orig_FontMatrix.yx != 0 || base_font->orig_FontMatrix.yy != 0) + *pmat = base_font->orig_FontMatrix; + else { + /* Must not happen with PS interpreter. + Provide a hewuristic for other clients. + */ + if (base_font->FontMatrix.xx == 1.0/2048 && + base_font->FontMatrix.xy == 0 && + base_font->FontMatrix.yx == 0 && + any_abs(base_font->FontMatrix.yy) == 1.0/2048 + ) + *pmat = base_font->FontMatrix; + else + gs_make_scaling(0.001, 0.001, pmat); + } + } + if (font->FontType == ft_CID_encrypted && cid != -1) { + int fidx; + + if (cid < GS_MIN_CID_GLYPH) + cid = GS_MIN_CID_GLYPH; + code = ((gs_font_cid0 *)font)->cidata.glyph_data((gs_font_base *)font, + cid, NULL, &fidx); + if (code < 0) { + code = ((gs_font_cid0 *)font)->cidata.glyph_data((gs_font_base *)font, + (gs_glyph)GS_MIN_CID_GLYPH, NULL, &fidx); + } + if (code >= 0) { + gs_matrix_multiply(&(gs_cid0_indexed_font(font, fidx)->FontMatrix), + pmat, pmat); + } + } + return 0; + default: + return_error(gs_error_rangecheck); + } +} + +/* + * Special version of txtwrite_font_orig_matrix(), that considers FDArray font's FontMatrix too. + * Called only by txt_glyph_width(). + * 'cid' is only consulted if 'font' is a CIDFontType 0 CID font. + */ +static int +glyph_orig_matrix(const gs_font *font, gs_glyph cid, gs_matrix *pmat) +{ + int code = txtwrite_font_orig_matrix(font, cid, pmat); + if (code >= 0) { + if (font->FontType == ft_CID_encrypted) { + int fidx; + + if (cid < GS_MIN_CID_GLYPH) + cid = GS_MIN_CID_GLYPH; + code = ((gs_font_cid0 *)font)->cidata.glyph_data((gs_font_base *)font, + cid, NULL, &fidx); + if (code < 0) { + code = ((gs_font_cid0 *)font)->cidata.glyph_data((gs_font_base *)font, + (gs_glyph)GS_MIN_CID_GLYPH, NULL, &fidx); + } + if (code >= 0) { + gs_matrix_multiply(&(gs_cid0_indexed_font(font, fidx)->FontMatrix), + pmat, pmat); + } + } + } + return code; +} + +/* + * Compute the cached values in the text processing state from the text + * parameters, current_font, and pgs->ctm. Return either an error code (< + * 0) or a mask of operation attributes that the caller must emulate. + * Currently the only such attributes are TEXT_ADD_TO_ALL_WIDTHS and + * TEXT_ADD_TO_SPACE_WIDTH. Note that this procedure fills in all the + * values in ppts->values, not just the ones that need to be set now. + */ +static int +transform_delta_inverse(const gs_point *pdelta, const gs_matrix *pmat, + gs_point *ppt) +{ + int code = gs_distance_transform_inverse(pdelta->x, pdelta->y, pmat, ppt); + gs_point delta; + + if (code < 0) + return code; + if (ppt->y == 0) + return 0; + /* Check for numerical fuzz. */ + code = gs_distance_transform(ppt->x, 0.0, pmat, &delta); + if (code < 0) + return 0; /* punt */ + if (fabs(delta.x - pdelta->x) < 0.01 && fabs(delta.y - pdelta->y) < 0.01) { + /* Close enough to y == 0: device space error < 0.01 pixel. */ + ppt->y = 0; + } + return 0; +} + +static +float txt_calculate_text_size(gs_gstate *pgs, gs_font *ofont, + const gs_matrix *pfmat, gs_matrix *smat, gs_matrix *tmat, + gs_font *font, gx_device *pdev) +{ + gs_matrix orig_matrix; + double + sx = pdev->HWResolution[0] / 72.0, + sy = pdev->HWResolution[1] / 72.0; + float size; + + /* Get the original matrix of the base font. */ + + txtwrite_font_orig_matrix(ofont, -1, &orig_matrix); + /* Compute the scaling matrix and combined matrix. */ + + if (gs_matrix_invert(&orig_matrix, smat) < 0) { + gs_make_identity(smat); + return 1; /* Arbitrary */ + } + gs_matrix_multiply(smat, pfmat, smat); + *tmat = ctm_only(pgs); + tmat->tx = tmat->ty = 0; + gs_matrix_multiply(smat, tmat, tmat); + + /* Try to find a reasonable size value. */ + + size = hypot(tmat->yx, tmat->yy) / sy; + if (size < 0.01) + size = hypot(tmat->xx, tmat->xy) / sx; + if (size < 0.01) + size = 1; + + return(size); +} + +static int +txt_update_text_state(text_list_entry_t *ppts, + const textw_text_enum_t *penum, + gs_font *ofont, const gs_matrix *pfmat) +{ + gx_device *const pdev = penum->dev; + gs_font *font = penum->current_font; + gs_fixed_point cpt; + gs_matrix smat, tmat; + float size; + int mask = 0; + int code = gx_path_current_point(penum->path, &cpt); + + if (code < 0) + return code; + + size = txt_calculate_text_size(penum->pgs, ofont, pfmat, &smat, &tmat, penum->current_font, pdev); + /* Check for spacing parameters we can handle, and transform them. */ + + if (penum->text.operation & TEXT_ADD_TO_ALL_WIDTHS) { + if (penum->current_font->WMode == 0) { + gs_point pt; + + code = transform_delta_inverse(&penum->text.delta_all, &smat, &pt); + if (code < 0 || pt.y != 0) + mask |= TEXT_ADD_TO_ALL_WIDTHS; + } + else + mask |= TEXT_ADD_TO_ALL_WIDTHS; + } + + if (penum->text.operation & TEXT_ADD_TO_SPACE_WIDTH) { + gs_point pt; + + code = transform_delta_inverse(&penum->text.delta_space, &smat, &pt); + if (code < 0 || pt.y != 0 || penum->text.space.s_char != 32) + mask |= TEXT_ADD_TO_SPACE_WIDTH; + } + /* Store the updated values. */ + + tmat.xx /= size; + tmat.xy /= size; + tmat.yx /= size; + tmat.yy /= size; + tmat.tx += fixed2float(cpt.x); + tmat.ty += fixed2float(cpt.y); + + ppts->size = size; + ppts->matrix = tmat; + ppts->render_mode = penum->pgs->text_rendering_mode; + ppts->FontName = (char *)gs_malloc(pdev->memory->stable_memory, 1, + font->font_name.size + 1, "txtwrite alloc font name"); + if (!ppts->FontName) + return gs_note_error(gs_error_VMerror); + memcpy(ppts->FontName, font->font_name.chars, font->font_name.size); + ppts->FontName[font->font_name.size] = 0x00; + ppts->render_mode = font->WMode; + + if (font->PaintType == 2 && penum->pgs->text_rendering_mode == 0) + { + gs_gstate *pgs = penum->pgs; + gs_font *font = penum->current_font; + double scaled_width = font->StrokeWidth != 0 ? font->StrokeWidth : 0.001; + double saved_width = pgs->line_params.half_width; + /* + * See stream_to_text in gdevpdfu.c re the computation of + * the scaling value. + */ + double scale = 72.0 / pdev->HWResolution[1]; + + if (font->FontMatrix.yy != 0) + scaled_width *= fabs(font->orig_FontMatrix.yy) * size * tmat.yy * scale; + else + scaled_width *= fabs(font->orig_FontMatrix.xy) * size * tmat.xy * scale; + + ppts->render_mode = 1; + ppts->PaintType0Width = scaled_width; + + pgs->line_params.half_width = scaled_width / 2; + if (code < 0) + return code; + + pgs->line_params.half_width = saved_width; + } + return (code < 0 ? code : mask); +} + +static int +store_glyph_width(txt_glyph_width_t *pwidth, int wmode, const gs_matrix *scale, + const gs_glyph_info_t *pinfo) +{ + double w, v; + + gs_distance_transform(pinfo->width[wmode].x, pinfo->width[wmode].y, scale, &pwidth->xy); + if (wmode) + w = pwidth->xy.y, v = pwidth->xy.x; + else + w = pwidth->xy.x, v = pwidth->xy.y; + if (v != 0) + return 1; + pwidth->w = w; + gs_distance_transform(pinfo->v.x, pinfo->v.y, scale, &pwidth->v); + return 0; +} + +static int +get_missing_width(gs_font *font, int wmode, const gs_matrix *scale_c, + txt_glyph_widths_t *pwidths) +{ + gs_font_info_t finfo; + int code; + + code = font->procs.font_info((gs_font *)font, NULL, + FONT_INFO_MISSING_WIDTH, &finfo); + if (code < 0) + return code; + if (wmode) { + gs_distance_transform(0.0, -finfo.MissingWidth, scale_c, &pwidths->real_width.xy); + pwidths->Width.xy.x = 0; + pwidths->Width.xy.y = pwidths->real_width.xy.y; + pwidths->Width.w = pwidths->real_width.w = + pwidths->Width.xy.y; + pwidths->Width.v.x = - pwidths->Width.xy.y / 2; + pwidths->Width.v.y = - pwidths->Width.xy.y; + } else { + gs_distance_transform(finfo.MissingWidth, 0.0, scale_c, &pwidths->real_width.xy); + pwidths->Width.xy.x = pwidths->real_width.xy.x; + pwidths->Width.xy.y = 0; + pwidths->Width.w = pwidths->real_width.w = + pwidths->Width.xy.x; + pwidths->Width.v.x = pwidths->Width.v.y = 0; + } + /* + * Don't mark the width as known, just in case this is an + * incrementally defined font. + */ + return 1; +} + +/* + * Get the widths (unmodified from the copied font, + * and possibly modified from the original font) of a given glyph. + * Return 1 if the width was defaulted to MissingWidth. + * Return TEXT_PROCESS_CDEVPROC if a CDevProc callout is needed. + * cdevproc_result != NULL if we restart after a CDevProc callout. + */ +static int +txt_glyph_widths(gs_font *font, int wmode, gs_glyph glyph, + gs_font *orig_font, txt_glyph_widths_t *pwidths, + const double cdevproc_result[10]) +{ + gs_font *ofont = orig_font; + gs_glyph_info_t info; + gs_matrix scale_c, scale_o; + int code, rcode = 0; + gs_point v; + int allow_cdevproc_callout = (orig_font->FontType == ft_CID_TrueType + || orig_font->FontType == ft_CID_encrypted + ? GLYPH_INFO_CDEVPROC : 0); /* fixme : allow more font types. */ + + if (ofont->FontType == ft_composite) + return_error(gs_error_unregistered); /* Must not happen. */ + code = glyph_orig_matrix((const gs_font *)font, glyph, &scale_c); + if (code < 0) + return code; + code = glyph_orig_matrix(ofont, glyph, &scale_o); + if (code < 0) + return code; + gs_matrix_scale(&scale_c, 1000.0, 1000.0, &scale_c); + gs_matrix_scale(&scale_o, 1000.0, 1000.0, &scale_o); + pwidths->Width.v.x = pwidths->Width.v.y = 0; + pwidths->real_width.v.x = pwidths->real_width.v.y = 0; + pwidths->replaced_v = false; + if (glyph == GS_NO_GLYPH) + return get_missing_width(font, wmode, &scale_c, pwidths); + code = font->procs.glyph_info((gs_font *)font, glyph, NULL, + GLYPH_INFO_WIDTH0 | + (GLYPH_INFO_WIDTH0 << wmode) | + GLYPH_INFO_OUTLINE_WIDTHS | + (GLYPH_INFO_VVECTOR0 << wmode), + &info); + /* For CID fonts the PDF spec requires the x-component of v-vector + to be equal to half glyph width, and AR5 takes it from W, DW. + So make a compatibe data here. + */ + if (font->FontType != ft_PCL_user_defined && font->FontType != ft_GL2_stick_user_defined && + font->FontType != ft_GL2_531 + && (code == gs_error_undefined || !(info.members & (GLYPH_INFO_WIDTH0 << wmode)))) { + code = get_missing_width(font, wmode, &scale_c, pwidths); + if (code < 0) + v.y = 0; + else + v.y = pwidths->Width.v.y; + if (wmode && (ofont->FontType == ft_CID_encrypted || + ofont->FontType == ft_CID_TrueType)) { + txt_glyph_widths_t widths1; + + if (get_missing_width(font, 0, &scale_c, &widths1) < 0) + v.x = 0; + else + v.x = widths1.Width.w / 2; + } else + v.x = pwidths->Width.v.x; + } else if (code < 0) + return code; + else { + code = store_glyph_width(&pwidths->Width, wmode, &scale_c, &info); + if (code < 0) + return code; + rcode |= code; + if (info.members & (GLYPH_INFO_VVECTOR0 << wmode)) + gs_distance_transform(info.v.x, info.v.y, &scale_c, &v); + else + v.x = v.y = 0; + if (wmode && (ofont->FontType == ft_CID_encrypted || + ofont->FontType == ft_CID_TrueType)) { + if (info.members & (GLYPH_INFO_WIDTH0 << wmode)) { + gs_point xy; + + gs_distance_transform(info.width[0].x, info.width[0].y, &scale_c, &xy); + v.x = xy.x / 2; + } else { + txt_glyph_widths_t widths1; + + if (get_missing_width(font, 0, &scale_c, &widths1) < 0) + v.x = 0; + else + v.x = widths1.Width.w / 2; + } + } + } + pwidths->Width.v = v; + /* Skip only if not paralel to the axis. */ + if (code > 0 && ofont->FontType != ft_CID_encrypted && + ofont->FontType != ft_CID_TrueType) + pwidths->Width.xy.x = pwidths->Width.xy.y = pwidths->Width.w = 0; + if (cdevproc_result == NULL) { + info.members = 0; + code = ofont->procs.glyph_info(ofont, glyph, NULL, + (GLYPH_INFO_WIDTH0 << wmode) | + (GLYPH_INFO_VVECTOR0 << wmode) | + allow_cdevproc_callout, + &info); + /* fixme : Move this call before cfont->procs.glyph_info. */ + if (info.members & GLYPH_INFO_CDEVPROC) { + if (allow_cdevproc_callout) + return TEXT_PROCESS_CDEVPROC; + else + return_error(gs_error_rangecheck); + } + } else { + info.width[0].x = cdevproc_result[0]; + info.width[0].y = cdevproc_result[1]; + info.width[1].x = cdevproc_result[6]; + info.width[1].y = cdevproc_result[7]; + info.v.x = (wmode ? cdevproc_result[8] : 0); + info.v.y = (wmode ? cdevproc_result[9] : 0); + info.members = (GLYPH_INFO_WIDTH0 << wmode) | + (wmode ? GLYPH_INFO_VVECTOR1 : 0); + code = 0; + } + if (code == gs_error_undefined || !(info.members & (GLYPH_INFO_WIDTH0 << wmode))) + pwidths->real_width = pwidths->Width; + else if (code < 0) + return code; + else { + if ((info.members & (GLYPH_INFO_VVECTOR0 | GLYPH_INFO_VVECTOR1)) != 0) + pwidths->replaced_v = true; + else + info.v.x = info.v.y = 0; + code = store_glyph_width(&pwidths->real_width, wmode, &scale_o, &info); + if (code < 0) + return code; + rcode |= code; + gs_distance_transform(info.v.x, info.v.y, &scale_o, &pwidths->real_width.v); + } + return rcode; +} + +static void +txt_char_widths_to_uts(gs_font *font /* may be NULL for non-Type3 */, + txt_glyph_widths_t *pwidths) +{ + if (font && (font->FontType == ft_user_defined || + font->FontType == ft_PCL_user_defined || + font->FontType == ft_GL2_stick_user_defined || + font->FontType == ft_GL2_531)) { + gs_matrix *pmat = &font->orig_FontMatrix; + + pwidths->Width.xy.x *= pmat->xx; /* formula simplified based on wy in glyph space == 0 */ + pwidths->Width.xy.y = 0.0; /* WMode == 0 for PDF Type 3 fonts */ + gs_distance_transform(pwidths->real_width.xy.x, pwidths->real_width.xy.y, pmat, &pwidths->real_width.xy); + } else { + /* + * For other font types: + * - PDF design->text space is a simple scaling by 0.001. + * - The Width.xy.x/y that should be zeroed-out per 5.3.3 "Text Space Details" is already 0. + */ + pwidths->Width.xy.x /= 1000.0; + pwidths->Width.xy.y /= 1000.0; + pwidths->real_width.xy.x /= 1000.0; + pwidths->real_width.xy.y /= 1000.0; + } +} + +/* Simple routine to update the current point by the accumulated width of the + * text. + */ +static int +txt_shift_text_currentpoint(textw_text_enum_t *penum, gs_point *wpt) +{ + return gs_moveto_aux(penum->pgs, gx_current_path(penum->pgs), + fixed2float(penum->origin.x) + wpt->x, + fixed2float(penum->origin.y) + wpt->y); +} + +/* Try to convert glyph names/character codes to Unicode. We first try to see + * if we have any Unicode information either from a ToUnicode CMap or GlyphNames2Unicode + * table. If that fails we look at the glyph name to see if it starts 'uni' + * in which case we assume the remainder of the name is the Unicode value. If + * its not a glyph of that form then we search a bunch of tables whcih map standard + * glyph names to Unicode code points. If that fails we finally just return the character code. + */ +static int get_unicode(textw_text_enum_t *penum, gs_font *font, gs_glyph glyph, gs_char ch, unsigned short *Buffer) +{ + int code; + gs_const_string gnstr; + unsigned short fallback = ch; + ushort *unicode = NULL; + int length; + + length = font->procs.decode_glyph((gs_font *)font, glyph, ch, NULL, 0); + if (length == 0) { + code = font->procs.glyph_name(font, glyph, &gnstr); + if (code >= 0 && gnstr.size == 7) { + if (!memcmp(gnstr.data, "uni", 3)) { + static const char *hexdigits = "0123456789ABCDEF"; + char *d0 = strchr(hexdigits, gnstr.data[3]); + char *d1 = strchr(hexdigits, gnstr.data[4]); + char *d2 = strchr(hexdigits, gnstr.data[5]); + char *d3 = strchr(hexdigits, gnstr.data[6]); + + if (d0 != NULL && d1 != NULL && d2 != NULL && d3 != NULL) { + *Buffer++ = ((d0 - hexdigits) << 12) + ((d1 - hexdigits) << 8) + ((d2 - hexdigits) << 4) + (d3 - hexdigits); + return 1; + } + } + } + if (length == 0) { + single_glyph_list_t *sentry = (single_glyph_list_t *)&SingleGlyphList; + double_glyph_list_t *dentry = (double_glyph_list_t *)&DoubleGlyphList; + treble_glyph_list_t *tentry = (treble_glyph_list_t *)&TrebleGlyphList; + quad_glyph_list_t *qentry = (quad_glyph_list_t *)&QuadGlyphList; + + /* Search glyph to single Unicode value table */ + while (sentry->Glyph != 0) { + if (sentry->Glyph[0] < gnstr.data[0]) { + sentry++; + continue; + } + if (sentry->Glyph[0] > gnstr.data[0]){ + break; + } + if (strlen(sentry->Glyph) == gnstr.size) { + if(memcmp(gnstr.data, sentry->Glyph, gnstr.size) == 0) { + *Buffer = sentry->Unicode; + return 1; + } + } + sentry++; + } + + /* Search glyph to double Unicode value table */ + while (dentry->Glyph != 0) { + if (dentry->Glyph[0] < gnstr.data[0]) { + dentry++; + continue; + } + if (dentry->Glyph[0] > gnstr.data[0]){ + break; + } + if (strlen(dentry->Glyph) == gnstr.size) { + if(memcmp(gnstr.data, dentry->Glyph, gnstr.size) == 0) { + memcpy(Buffer, dentry->Unicode, 2); + return 2; + } + } + dentry++; + } + + /* Search glyph to triple Unicode value table */ + while (tentry->Glyph != 0) { + if (tentry->Glyph[0] < gnstr.data[0]) { + tentry++; + continue; + } + if (tentry->Glyph[0] > gnstr.data[0]){ + break; + } + if (strlen(tentry->Glyph) == gnstr.size) { + if(memcmp(gnstr.data, tentry->Glyph, gnstr.size) == 0) { + memcpy(Buffer, tentry->Unicode, 3); + return 3; + } + } + tentry++; + } + + /* Search glyph to quadruple Unicode value table */ + while (qentry->Glyph != 0) { + if (qentry->Glyph[0] < gnstr.data[0]) { + qentry++; + continue; + } + if (qentry->Glyph[0] > gnstr.data[0]){ + break; + } + if (strlen(qentry->Glyph) == gnstr.size) { + if(memcmp(gnstr.data, qentry->Glyph, gnstr.size) == 0) { + memcpy(Buffer, qentry->Unicode, 4); + return 4; + } + } + qentry++; + } + } + *Buffer = fallback; + return 1; + } else { + char *b, *u; + int l = length - 1; + + unicode = (ushort *)gs_alloc_bytes(penum->dev->memory, length, "temporary Unicode array"); + length = font->procs.decode_glyph((gs_font *)font, glyph, ch, unicode, length); +#if ARCH_IS_BIG_ENDIAN + memcpy(Buffer, unicode, length); +#else + b = (char *)Buffer; + u = (char *)unicode; + while (l >= 0) { + *b++ = *(u + l); + l--; + } + +#endif + gs_free_object(penum->dev->memory, unicode, "free temporary unicode buffer"); + return length / sizeof(short); + } +} + +/* Routines to enumerate each glyph/character code in turn, find its width + * so that we can update the current point and find the end of the text, convert + * to Unicode if at all possible, and store some state such as the font, colour + * text rendering mode, writing mode, etc. + */ + +static int +txtwrite_process_cmap_text(gs_text_enum_t *pte) +{ + textw_text_enum_t *const penum = (textw_text_enum_t *)pte; + unsigned int rcode = 0; + gs_text_enum_t scan = *(gs_text_enum_t *)pte; + + /* Composite font using a CMap */ + for ( ; ; ) { + gs_glyph glyph; + int font_code, code; + gs_font *subfont; + gs_char chr; + txt_glyph_widths_t widths; + gs_matrix m3; + gs_point wanted; /* user space */ + gs_point dpt = {0,0}; + + font_code = scan.orig_font->procs.next_char_glyph + (&scan, &chr, &glyph); + + subfont = scan.fstack.items[scan.fstack.depth].font; + + switch (font_code) { + case 0: /* no font change */ + case 1: /* font change */ + code = txt_glyph_widths(subfont, scan.orig_font->WMode, glyph, (gs_font *)subfont, &widths, + penum->cdevproc_callout ? penum->cdevproc_result : NULL); + if (code == TEXT_PROCESS_CDEVPROC) { + penum->cdevproc_callout = true; + pte->returned.current_glyph = glyph; + scan.returned.current_glyph = glyph; + pte->current_font = subfont; + scan.current_font = subfont; + rcode = TEXT_PROCESS_CDEVPROC; + break; + } + else { + penum->cdevproc_callout = false; + pte->index = scan.index; + } + code = gs_matrix_multiply(&subfont->FontMatrix, &pte->orig_font->FontMatrix, &m3); + if (code < 0) + return code; + code = txt_update_text_state(penum->text_state, (textw_text_enum_t *)pte, pte->orig_font, &m3); + if (code < 0) + return code; + txt_char_widths_to_uts(pte->orig_font, &widths); /* convert design->text space */ + gs_distance_transform(widths.real_width.xy.x * penum->text_state->size, + widths.real_width.xy.y * penum->text_state->size, + &penum->text_state->matrix, &wanted); + pte->returned.total_width.x += wanted.x; + pte->returned.total_width.y += wanted.y; + penum->Widths[penum->TextBufferIndex] = wanted.x; + + if (pte->text.operation & TEXT_ADD_TO_ALL_WIDTHS) { + gs_point tpt; + + gs_distance_transform(pte->text.delta_all.x, pte->text.delta_all.y, + &ctm_only(pte->pgs), &tpt); + dpt.x += tpt.x; + dpt.y += tpt.y; + } + if (pte->text.operation & TEXT_ADD_TO_SPACE_WIDTH && chr == pte->text.space.s_char) { + gs_point tpt; + + gs_distance_transform(pte->text.delta_space.x, pte->text.delta_space.y, + &ctm_only(pte->pgs), &tpt); + dpt.x += tpt.x; + dpt.y += tpt.y; + } + pte->returned.total_width.x += dpt.x; + pte->returned.total_width.y += dpt.y; + + penum->TextBufferIndex += get_unicode(penum, (gs_font *)pte->orig_font, glyph, chr, &penum->TextBuffer[penum->TextBufferIndex]); + penum->Widths[penum->TextBufferIndex] += dpt.x; + break; + case 2: /* end of string */ + return 0; + default: /* error */ + return font_code; + } + if (rcode || pte->index >= pte->text.size) + break; + } + return rcode; +} + +static int +txtwrite_process_plain_text(gs_text_enum_t *pte) +{ + /* one byte regular font */ + textw_text_enum_t *const penum = (textw_text_enum_t *)pte; + gs_font *font = pte->orig_font; + const gs_glyph *gdata = NULL; + gs_glyph glyph; + gs_char ch = 0; + int i, code; + uint operation = pte->text.operation; + txt_glyph_widths_t widths; + gs_point wanted; /* user space */ + gs_point dpt = {0,0}; + + for (i=0;i<pte->text.size;i++) { + if (operation & (TEXT_FROM_STRING | TEXT_FROM_BYTES)) { + ch = pte->text.data.bytes[pte->index++]; + } else if (operation & (TEXT_FROM_CHARS | TEXT_FROM_SINGLE_CHAR)) { + ch = pte->text.data.chars[pte->index++]; + } else if (operation & (TEXT_FROM_GLYPHS | TEXT_FROM_SINGLE_GLYPH)) { + if (operation & TEXT_FROM_GLYPHS) { + gdata = pte->text.data.glyphs + (pte->index++ * sizeof (gs_glyph)); + } else { + gdata = &pte->text.data.d_glyph; + pte->index++; + } + } + glyph = (gdata == NULL ? pte->orig_font->procs.encode_char(pte->orig_font, ch, GLYPH_SPACE_NAME) + : *gdata); + + code = txt_glyph_widths(font, font->WMode, glyph, (gs_font *)font, &widths, NULL); + if (code < 0) + return code; + + penum->cdevproc_callout = false; + code = txt_update_text_state(penum->text_state, (textw_text_enum_t *)pte, pte->orig_font, &font->FontMatrix); + if (code < 0) + return code; + + txt_char_widths_to_uts(pte->orig_font, &widths); /* convert design->text space */ + gs_distance_transform(widths.real_width.xy.x * penum->text_state->size, + widths.real_width.xy.y * penum->text_state->size, + &penum->text_state->matrix, &wanted); + pte->returned.total_width.x += wanted.x; + pte->returned.total_width.y += wanted.y; + penum->Widths[pte->index - 1] = wanted.x; + + if (pte->text.operation & TEXT_ADD_TO_ALL_WIDTHS) { + gs_point tpt; + + gs_distance_transform(pte->text.delta_all.x, pte->text.delta_all.y, + &ctm_only(pte->pgs), &tpt); + dpt.x += tpt.x; + dpt.y += tpt.y; + } + if (pte->text.operation & TEXT_ADD_TO_SPACE_WIDTH && ch == pte->text.space.s_char) { + gs_point tpt; + + gs_distance_transform(pte->text.delta_space.x, pte->text.delta_space.y, + &ctm_only(pte->pgs), &tpt); + dpt.x += tpt.x; + dpt.y += tpt.y; + } + pte->returned.total_width.x += dpt.x; + pte->returned.total_width.y += dpt.y; + + penum->TextBufferIndex += get_unicode(penum, (gs_font *)pte->orig_font, glyph, ch, &penum->TextBuffer[penum->TextBufferIndex]); + penum->Widths[pte->index - 1] += dpt.x; + } + return 0; +} + +/* Routine to add the accumulated text, and its recorded properties to our + * lists. We maintain a list of text on a per-page basis. Each fragment is + * sorted by Y co-ordinate, then by X co-ordinate, and stored that way. + * Eventually we will want to merge 'adjacent' fragments with the same + * properties, at least when outputting a simple representation. We won't + * do this for languages which don't read left/right or right/left though. + */ +static int +txt_add_sorted_fragment(gx_device_txtwrite_t *tdev, textw_text_enum_t *penum) +{ + if (!tdev->PageData.y_ordered_list) { + /* first entry, no need to sort, just store it */ + tdev->PageData.y_ordered_list = (page_text_list_t *)gs_malloc(tdev->memory->stable_memory, 1, + sizeof(page_text_list_t), "txtwrite alloc Y list entry"); + if (!tdev->PageData.y_ordered_list) + return gs_note_error(gs_error_VMerror); + memset(tdev->PageData.y_ordered_list, 0x00, sizeof(page_text_list_t)); + tdev->PageData.y_ordered_list->x_ordered_list = penum->text_state; + tdev->PageData.y_ordered_list->next = tdev->PageData.y_ordered_list->previous = NULL; + tdev->PageData.y_ordered_list->start = penum->text_state->start; + } else { + page_text_list_t *Y_List = tdev->PageData.y_ordered_list; + + while (Y_List->next && Y_List->start.y < penum->text_state->start.y) + Y_List = Y_List->next; + + if (Y_List->start.y == penum->text_state->start.y) { + /* Already have text at this y-position */ + text_list_entry_t *X_List = Y_List->x_ordered_list; + + while (X_List->next && X_List->start.x < penum->text_state->start.x) + X_List = X_List->next; + + if (X_List->start.x > penum->text_state->start.x) { + /* Insert before */ + penum->text_state->next = X_List; + penum->text_state->previous = X_List->previous; + X_List->previous = penum->text_state; + if (!penum->text_state->previous) + /* New head of list */ + Y_List->x_ordered_list = penum->text_state; + else + penum->text_state->previous->next = penum->text_state; + } else { + /* Insert after */ + penum->text_state->previous = X_List; + penum->text_state->next = X_List->next; + X_List->next = penum->text_state; + if (penum->text_state->next) + penum->text_state->next->previous = penum->text_state; + } + if (penum->text_state->FontBBox_bottomleft.y < Y_List->MinY) + Y_List->MinY = penum->text_state->FontBBox_bottomleft.y; + if (penum->text_state->FontBBox_bottomleft.y > Y_List->MaxY) + Y_List->MaxY = penum->text_state->FontBBox_bottomleft.y; + if (penum->text_state->FontBBox_topright.y < Y_List->MinY) + Y_List->MinY = penum->text_state->FontBBox_topright.y; + if (penum->text_state->FontBBox_topright.y > Y_List->MaxY) + Y_List->MaxY = penum->text_state->FontBBox_topright.y; + } else { + /* New y-position, make a Y list new record */ + page_text_list_t *Y_Entry = (page_text_list_t *)gs_malloc(tdev->memory->stable_memory, 1, + sizeof(page_text_list_t), "txtwrite alloc Y-list"); + if (!Y_Entry) + return gs_note_error(gs_error_VMerror); + + Y_Entry->x_ordered_list = penum->text_state; + Y_Entry->start = penum->text_state->start; + if (penum->text_state->FontBBox_bottomleft.y > penum->text_state->FontBBox_topright.y) { + Y_Entry->MinY = penum->text_state->FontBBox_topright.y; + Y_Entry->MaxY = penum->text_state->FontBBox_bottomleft.y; + } else { + Y_Entry->MaxY = penum->text_state->FontBBox_topright.y; + Y_Entry->MinY = penum->text_state->FontBBox_bottomleft.y; + } + + if (Y_List->start.y > penum->text_state->start.y) { + /* Insert before */ + Y_Entry->next = Y_List; + Y_Entry->previous = Y_List->previous; + Y_List->previous = Y_Entry; + if (!Y_Entry->previous) + /* New head of list */ + tdev->PageData.y_ordered_list = Y_Entry; + else + ((page_text_list_t *)Y_Entry->previous)->next = Y_Entry; + } else { + /* Insert after */ + Y_Entry->next = Y_List->next; + Y_Entry->previous = Y_List; + Y_List->next = Y_Entry; + if (Y_Entry->next) + ((page_text_list_t *)(Y_Entry->next))->previous = Y_Entry; + } + } + } + penum->text_state = NULL; + return 0; +} + +static int +txt_add_fragment(gx_device_txtwrite_t *tdev, textw_text_enum_t *penum) +{ + text_list_entry_t *unsorted_entry, *t; + + /* Create a duplicate entry for the unsorted list */ + unsorted_entry = (text_list_entry_t *)gs_malloc(tdev->memory->stable_memory, 1, + sizeof(text_list_entry_t), "txtwrite alloc sorted text state"); + if (!unsorted_entry) + return gs_note_error(gs_error_VMerror); + + /* Calculate the start and end points of the text */ + penum->text_state->start.x = fixed2float(penum->origin.x); + penum->text_state->start.y = fixed2float(penum->origin.y); + penum->text_state->end.x = penum->text_state->start.x + penum->returned.total_width.x; + penum->text_state->end.y = penum->text_state->start.y + penum->returned.total_width.y; + penum->text_state->Unicode_Text_Size = penum->TextBufferIndex; + + *unsorted_entry = *(penum->text_state); + + /* Update the saved text state with the acccumulated Unicode data */ + /* The working buffer (penum->TextBuffer) is freed in the text_release method */ + penum->text_state->Unicode_Text = (unsigned short *)gs_malloc(tdev->memory->stable_memory, + penum->TextBufferIndex, sizeof(unsigned short), "txtwrite alloc text buffer"); + if (!penum->text_state->Unicode_Text) + return gs_note_error(gs_error_VMerror); + memcpy(penum->text_state->Unicode_Text, penum->TextBuffer, penum->TextBufferIndex * sizeof(unsigned short)); + + penum->text_state->Widths = (float *)gs_malloc(tdev->memory->stable_memory, + penum->TextBufferIndex, sizeof(float), "txtwrite alloc widths array"); + if (!penum->text_state->Widths) + return gs_note_error(gs_error_VMerror); + memcpy(penum->text_state->Widths, penum->Widths, penum->TextBufferIndex * sizeof(float)); + + unsorted_entry->Unicode_Text = (unsigned short *)gs_malloc(tdev->memory->stable_memory, + penum->TextBufferIndex, sizeof(unsigned short), "txtwrite alloc sorted text buffer"); + if (!unsorted_entry->Unicode_Text) + return gs_note_error(gs_error_VMerror); + memcpy(unsorted_entry->Unicode_Text, penum->TextBuffer, penum->TextBufferIndex * sizeof(unsigned short)); + + unsorted_entry->Widths = (float *)gs_malloc(tdev->memory->stable_memory, + penum->TextBufferIndex, sizeof(float), "txtwrite alloc widths array"); + if (!unsorted_entry->Widths) + return gs_note_error(gs_error_VMerror); + memcpy(unsorted_entry->Widths, penum->Widths, penum->TextBufferIndex * sizeof(float)); + + unsorted_entry->FontName = (char *)gs_malloc(tdev->memory->stable_memory, + (strlen(penum->text_state->FontName) + 1), sizeof(unsigned char), "txtwrite alloc sorted text buffer"); + if (!unsorted_entry->FontName) + return gs_note_error(gs_error_VMerror); + memcpy(unsorted_entry->FontName, penum->text_state->FontName, strlen(penum->text_state->FontName) * sizeof(unsigned char)); + unsorted_entry->FontName[strlen(penum->text_state->FontName)] = 0x00; + + /* First add one entry to the unsorted list */ + if (!tdev->PageData.unsorted_text_list) { + tdev->PageData.unsorted_text_list = unsorted_entry; + unsorted_entry->next = unsorted_entry->previous = NULL; + } else { + t = tdev->PageData.unsorted_text_list; + while (t->next) + t = t->next; + t->next = unsorted_entry; + unsorted_entry->next = NULL; + unsorted_entry->previous = t; + } + + /* Then add the other entry to the sorted list */ + return txt_add_sorted_fragment(tdev, penum); +} + +/* This routine selects whether the text needs to be handled as regular glyphs + * and character codes, or as CIDs, depending on the font type. This is required + * because there are ways that regular text can be handled that aren't possible + * with CIDFonts. + */ +static int +textw_text_process(gs_text_enum_t *pte) +{ + textw_text_enum_t *const penum = (textw_text_enum_t *)pte; + gx_device_txtwrite_t *const tdev = (gx_device_txtwrite_t *) pte->dev; + gs_font *font = pte->orig_font; + gs_font_base *font_base = (gs_font_base *)pte->current_font; + int code = 0; + + if (pte->text.size == 0) + return 0; + + if (!penum->TextBuffer) { + /* We can get up to 4 Unicode points per glyph, and a glyph can be + * be represented by as little as one byte. So we make a very large + * temporary buffer to hold the Unicode string as we assemble it. When + * we copy it to the text fragment we will allocate only as many bytes + * as are required to hold the actual nukmber of Unicode values we + * decoded, and this temporary buffer will be discarded. + */ + penum->TextBuffer = (unsigned short *)gs_malloc(tdev->memory->stable_memory, + pte->text.size * 4, sizeof(unsigned short), "txtwrite temporary text buffer"); + if (!penum->TextBuffer) + return gs_note_error(gs_error_VMerror); + penum->Widths = (float *)gs_malloc(tdev->memory->stable_memory, + pte->text.size, sizeof(float), "txtwrite temporary widths array"); + if (!penum->Widths) + return gs_note_error(gs_error_VMerror); + } + { + switch (font->FontType) { + case ft_CID_encrypted: + case ft_CID_TrueType: + case ft_composite: + code = txtwrite_process_cmap_text(pte); + break; + case ft_encrypted: + case ft_encrypted2: + case ft_TrueType: + case ft_user_defined: + case ft_PCL_user_defined: + case ft_GL2_stick_user_defined: + case ft_GL2_531: + code = txtwrite_process_plain_text(pte); + break; + default: + return_error(gs_error_rangecheck); + break; + } + if (code == 0) { + if (font_base->FontBBox.p.x != font_base->FontBBox.q.x || + font_base->FontBBox.p.y != font_base->FontBBox.q.y) { + gs_point p0, p1, p2, p3; + gs_matrix m; + + m = ctm_only(pte->pgs); + m.tx = m.ty = fixed2float(0); + gs_matrix_multiply(&font_base->FontMatrix, &m, &m); + gs_point_transform(font_base->FontBBox.p.x, font_base->FontBBox.p.y, &m, &p0); + gs_point_transform(font_base->FontBBox.p.x, font_base->FontBBox.q.y, &m, &p1); + gs_point_transform(font_base->FontBBox.q.x, font_base->FontBBox.p.y, &m, &p2); + gs_point_transform(font_base->FontBBox.q.x, font_base->FontBBox.q.y, &m, &p3); + penum->text_state->FontBBox_bottomleft.x = min(min(p0.x, p1.x), min(p1.x, p2.x)); + penum->text_state->FontBBox_topright.x = max(max(p0.x, p1.x), max(p1.x, p2.x)); + penum->text_state->FontBBox_bottomleft.y = min(min(p0.y, p1.y), min(p1.y, p2.y)); + penum->text_state->FontBBox_topright.y = max(max(p0.y, p1.y), max(p1.y, p2.y)); + } + code = txt_shift_text_currentpoint(penum, &penum->returned.total_width); + if (code != 0) + return code; + + code = txt_add_fragment(tdev, penum); + } + } + return code; +} + +/* Begin processing text. */ + +/* Define the auxiliary procedures for text processing. */ +static int +textw_text_resync(gs_text_enum_t *pte, const gs_text_enum_t *pfrom) +{ + return gs_text_resync(pte, pfrom); +} +static bool +textw_text_is_width_only(const gs_text_enum_t *pte) +{ + return false; +} +static int +textw_text_current_width(const gs_text_enum_t *pte, gs_point *pwidth) +{ + return gs_text_current_width(pte, pwidth); +} +static int +textw_text_set_cache(gs_text_enum_t *pte, const double *pw, + gs_text_cache_control_t control) +{ + textw_text_enum_t *const penum = (textw_text_enum_t *)pte; + + switch (control) { + case TEXT_SET_CHAR_WIDTH: + case TEXT_SET_CACHE_DEVICE: + return gs_text_set_cache(pte, pw, control); + case TEXT_SET_CACHE_DEVICE2: + if (penum->cdevproc_callout) { + memcpy(penum->cdevproc_result, pw, sizeof(penum->cdevproc_result)); + return 0; + } + return gs_text_set_cache(pte, pw, control); + default: + return_error(gs_error_rangecheck); + } + return 0; +} + +static int +textw_text_retry(gs_text_enum_t *pte) +{ + return gs_text_retry(pte); +} +static void +textw_text_release(gs_text_enum_t *pte, client_name_t cname) +{ + textw_text_enum_t *const penum = (textw_text_enum_t *)pte; + gx_device_txtwrite_t *const tdev = (gx_device_txtwrite_t *) pte->dev; + + /* Free the working buffer where the Unicode was assembled from the enumerated text */ + if (penum->TextBuffer) + gs_free(tdev->memory, penum->TextBuffer, 1, penum->TextBufferIndex, "txtwrite free temporary text buffer"); + if (penum->Widths) + gs_free(tdev->memory, penum->Widths, sizeof(float), pte->text.size, "txtwrite free temporary widths array"); + /* If this is copied away when we complete the text enumeration succesfully, then + * we set the pointer to NULL, if we get here with it non-NULL , then there was + * an error. + */ + if (penum->text_state) + gs_free(tdev->memory, penum->text_state, 1, sizeof(penum->text_state), "txtwrite free text state"); + + gs_text_release(pte, cname); +} + +/* This is the list of methods for the text enumerator */ +static const gs_text_enum_procs_t textw_text_procs = { + textw_text_resync, textw_text_process, + textw_text_is_width_only, textw_text_current_width, + textw_text_set_cache, textw_text_retry, + textw_text_release +}; + +/* This device method gets called by the interpreter to deal with text. It + * must create a text enumerator, which contains the methods for dealing with + * the text itself. The interpreter will use the enumerator methods to deal with + * the text, it won't refer to the device methods again for this text. + */ +static int +txtwrite_text_begin(gx_device * dev, gs_gstate * pgs, + const gs_text_params_t * text, gs_font * font, + gx_path * path, const gx_device_color * pdcolor, + const gx_clip_path * pcpath, + gs_memory_t * mem, gs_text_enum_t ** ppenum) +{ + gx_device_txtwrite_t *const tdev = (gx_device_txtwrite_t *) dev; + textw_text_enum_t *penum; + int code; + + /* If this is a stringwidth, we must let the default graphics library code handle it + * in case there is no current point (this can happen if this is the first operation + * we get, the current font is a CIDFont, and its descendant is a substitute type 1 + * font). If there is no current point we throw an error in text_process and that + * messes up all the font handling. The test below is copied from pdfwrite + * (gdev_pdf_text_begin) and seems to do the job. + */ + if ((!(text->operation & TEXT_DO_DRAW) && pgs->text_rendering_mode != 3) + || path == 0 || !path_position_valid(path)) + return gx_default_text_begin(dev, pgs, text, font, path, pdcolor, + pcpath, mem, ppenum); + /* Allocate and initialize one of our text enumerators. */ + rc_alloc_struct_1(penum, textw_text_enum_t, &st_textw_text_enum, mem, + return_error(gs_error_VMerror), "gdev_textw_text_begin"); + penum->rc.free = rc_free_text_enum; + penum->charproc_accum = false; + penum->cdevproc_callout = false; + penum->returned.total_width.x = penum->returned.total_width.y = 0; + penum->TextBuffer = NULL; + penum->TextBufferIndex = 0; + penum->Widths = NULL; + /* The enumerator's text_release method frees this memory */ + penum->text_state = (text_list_entry_t *)gs_malloc(tdev->memory->stable_memory, 1, + sizeof(text_list_entry_t), "txtwrite alloc text state"); + if (!penum->text_state) + return gs_note_error(gs_error_VMerror); + memset(penum->text_state, 0x00, sizeof(text_list_entry_t)); + + code = gs_text_enum_init((gs_text_enum_t *)penum, &textw_text_procs, + dev, pgs, text, font, path, pdcolor, pcpath, mem); + if (code < 0) { + /* Belt and braces; I'm not certain this is required, but its safe */ + gs_free(tdev->memory, penum->text_state, 1, sizeof(text_list_entry_t), "txtwrite free text state"); + penum->text_state = NULL; + gs_free_object(mem, penum, "textwrite_text_begin"); + return code; + } + + code = gx_path_current_point(penum->path, &penum->origin); + if (code != 0) + return code; + + *ppenum = (gs_text_enum_t *)penum; + + return 0; +} + +static int +txtwrite_strip_copy_rop(gx_device * dev, + const byte * sdata, int sourcex, uint sraster, + gx_bitmap_id id, + const gx_color_index * scolors, + const gx_strip_bitmap * textures, + const gx_color_index * tcolors, + int x, int y, int w, int h, + int phase_x, int phase_y, gs_logical_operation_t lop) +{ + return 0; +} + +int +txtwrite_dev_spec_op(gx_device *pdev, int dev_spec_op, void *data, int size) +{ + switch (dev_spec_op) { + case gxdso_get_dev_param: + { + int code; + dev_param_req_t *request = (dev_param_req_t *)data; + code = txtwrite_get_param(pdev, request->Param, request->list); + if (code != gs_error_undefined) + return code; + } + } + return gx_default_dev_spec_op(pdev, dev_spec_op, data, size); +} |