/* Copyright (C) 1996, 2000, 2001 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: gdevpdfs.c,v 1.7.2.3.2.1 2003/01/17 00:49:01 giles Exp $ */
/* Text string processing for PDF-writing driver. */
#include "math_.h"
#include "memory_.h"
#include "string_.h"
#include "gx.h"
#include "gserrors.h"
#include "gsmatrix.h"
#include "gsutil.h"		/* for bytes_compare */
#include "gxfixed.h"		/* for gxfcache.h */
#include "gxfont.h"
#include "gxfont0.h"
#include "gxfont1.h"
#include "gxfont42.h"
#include "gxfcache.h"		/* for orig_fonts list */
#include "gxfcid.h"
#include "gxfcmap.h"
#include "gxpath.h"		/* for getting current point */
#include "gdevpsf.h"
#include "gdevpdfx.h"
#include "gdevpdff.h"
#include "gdevpdfg.h"
#include "gdevpdfo.h"		/* only to mark CMap as written */
#include "scommon.h"

/*
 * Continue processing text.  Per the check in pdf_text_begin, we know the
 * operation is not a charpath, but it could be anything else.
 */

/*
 * Define quantities derived from the current font and CTM, used within
 * the text processing loop.
 */
typedef struct pdf_text_process_state_s {
    float chars;		/* scaled character spacing (Tc) */
    float words;		/* scaled word spacing (Tw) */
    float size;			/* font size for Tf */
    gs_matrix text_matrix;	/* normalized FontMatrix * CTM for Tm */
    int mode;			/* render mode (Tr) */
    gs_font *font;
    pdf_font_t *pdfont;
} pdf_text_process_state_t;

/*
 * Declare the procedures for processing different species of text.
 * These procedures may, but need not, copy pte->text into buf
 * (a caller-supplied buffer large enough to hold the string).
 */
#define PROCESS_TEXT_PROC(proc)\
  int proc(P4(gs_text_enum_t *pte, const void *vdata, void *vbuf, uint size))
private PROCESS_TEXT_PROC(process_plain_text);
private PROCESS_TEXT_PROC(process_composite_text);
private PROCESS_TEXT_PROC(process_cmap_text);
private PROCESS_TEXT_PROC(process_cid_text);

/* Other forward declarations */
private int encoding_find_glyph(P3(gs_font_base *bfont, gs_glyph font_glyph,
				   gs_encoding_index_t index));
private int pdf_process_string(P6(pdf_text_enum_t *penum, gs_string *pstr,
				  const gs_matrix *pfmat, bool encoded,
				  pdf_text_process_state_t *pts, int *pindex));
private int pdf_update_text_state(P3(pdf_text_process_state_t *ppts,
				     const pdf_text_enum_t *penum,
				     const gs_matrix *pfmat));
private int pdf_encode_char(P4(gx_device_pdf *pdev, int chr,
			       gs_font_base *bfont, pdf_font_t *ppf));
private int pdf_encode_glyph(P5(gx_device_pdf *pdev, int chr, gs_glyph glyph,
				gs_font_base *bfont, pdf_font_t *ppf));
private int pdf_write_text_process_state(P4(gx_device_pdf *pdev,
			const gs_text_enum_t *pte,	/* for pdcolor, pis */
			const pdf_text_process_state_t *ppts,
			const gs_const_string *pstr));
private int process_text_return_width(P7(const gs_text_enum_t *pte,
					 gs_font *font, pdf_font_t *pdfont,
					 const gs_matrix *pfmat,
					 const gs_const_string *pstr,
					 int *pindex, gs_point *pdpt));
private int process_text_add_width(P7(gs_text_enum_t *pte,
				      gs_font *font, const gs_matrix *pfmat,
				      const pdf_text_process_state_t *ppts,
				      const gs_const_string *pstr,
				      int *pindex, gs_point *pdpt));

/*
 * Continue processing text.  This is the 'process' procedure in the text
 * enumerator.
 */
private int
pdf_default_text_begin(gs_text_enum_t *pte, const gs_text_params_t *text,
		       gs_text_enum_t **ppte)
{
    return gx_default_text_begin(pte->dev, pte->pis, text, pte->current_font,
				 pte->path, pte->pdcolor, pte->pcpath,
				 pte->memory, ppte);
}
int
pdf_text_process(gs_text_enum_t *pte)
{
    pdf_text_enum_t *const penum = (pdf_text_enum_t *)pte;
    uint operation = pte->text.operation;
    const void *vdata;
    uint size = pte->text.size - pte->index;
    gs_text_enum_t *pte_default;
    PROCESS_TEXT_PROC((*process));
    int code = -1;		/* to force default implementation */
#define BUF_SIZE 100		/* arbitrary > 0 */

    /*
     * If we fell back to the default implementation, continue using it.
     */
 top:
    pte_default = penum->pte_default;
    if (pte_default) {
	code = gs_text_process(pte_default);
	gs_text_enum_copy_dynamic(pte, pte_default, true);
	if (code)
	    return code;
	gs_text_release(pte_default, "pdf_text_process");
	penum->pte_default = 0;
	return 0;
    }
    {
	gs_font *font = pte->current_font;

	switch (font->FontType) {
	case ft_CID_encrypted:
	case ft_CID_TrueType:
	    process = process_cid_text;
	    break;
	case ft_encrypted:
	case ft_encrypted2:
	case ft_TrueType:
	    /* The data may be either glyphs or characters. */
	    process = process_plain_text;
	    break;
	case ft_composite:
	    process =
		(((gs_font_type0 *)font)->data.FMapType == fmap_CMap ?
		 process_cmap_text :
		 process_composite_text);
	    break;
	default:
	    goto skip;
	}
    }

    /*
     * We want to process the entire string in a single call, but we may
     * need to modify it.  Copy it to a buffer.  Note that it may consist
     * of bytes, gs_chars, or gs_glyphs.
     */

    if (operation & (TEXT_FROM_STRING | TEXT_FROM_BYTES))
	vdata = pte->text.data.bytes;
    else if (operation & TEXT_FROM_CHARS)
	vdata = pte->text.data.chars, size *= sizeof(gs_char);
    else if (operation & TEXT_FROM_SINGLE_CHAR)
	vdata = &pte->text.data.d_char, size = sizeof(gs_char);
    else if (operation & TEXT_FROM_GLYPHS)
	vdata = pte->text.data.glyphs, size *= sizeof(gs_glyph);
    else if (operation & TEXT_FROM_SINGLE_GLYPH)
	vdata = &pte->text.data.d_glyph, size = sizeof(gs_glyph);
    else
	goto skip;

    if (size <= BUF_SIZE) {
	/* Use a union to ensure alignment. */
	union bu_ {
	    byte bytes[BUF_SIZE];
	    gs_char chars[BUF_SIZE / sizeof(gs_char)];
	    gs_glyph glyphs[BUF_SIZE / sizeof(gs_glyph)];
	} buf;

	code = process(pte, vdata, buf.bytes, size);
    } else {
	byte *buf = gs_alloc_string(pte->memory, size, "pdf_text_process");

	if (buf == 0)
	    return_error(gs_error_VMerror);
	code = process(pte, vdata, buf, size);
	gs_free_string(pte->memory, buf, size, "pdf_text_process");
    }

 skip:
    if (code < 0) {
	/* Fall back to the default implementation. */
	code = pdf_default_text_begin(pte, &pte->text, &pte_default);
	if (code < 0)
	    return code;
	penum->pte_default = pte_default;
	gs_text_enum_copy_dynamic(pte_default, pte, false);
    }
    /* The 'process' procedure might also have set pte_default itself. */
    if (penum->pte_default && !code)
	goto top;
    return code;
}

/*
 * Process a text string in an ordinary font.
 */
private int
process_plain_text(gs_text_enum_t *pte, const void *vdata, void *vbuf,
		   uint size)
{
    byte *const buf = vbuf;
    uint operation = pte->text.operation;
    pdf_text_enum_t *const penum = (pdf_text_enum_t *)pte;
    int index = 0, code;
    gs_string str;
    bool encoded;
    pdf_text_process_state_t text_state;

    if (operation & (TEXT_FROM_STRING | TEXT_FROM_BYTES)) {
	memcpy(buf, (const byte *)vdata + pte->index, size);
	encoded = false;
    } else if (operation & (TEXT_FROM_CHARS | TEXT_FROM_SINGLE_CHAR)) {
	/* Check that all chars fit in a single byte. */
	const gs_char *const cdata = vdata;
	int i;

	size /= sizeof(gs_char);
	for (i = 0; i < size; ++i) {
	    gs_char chr = cdata[pte->index + i];

	    if (chr & ~0xff)
		return_error(gs_error_rangecheck);
	    buf[i] = (byte)chr;
	}
	encoded = false;
    } else if (operation & (TEXT_FROM_GLYPHS | TEXT_FROM_SINGLE_GLYPH)) {
	/*
	 * Reverse-map the glyphs into the encoding.  Eventually, assign
	 * them to empty slots if missing.
	 */
	const gs_glyph *const gdata = vdata;
	gs_font *font = pte->current_font;
	int i;

	size /= sizeof(gs_glyph);
	code = pdf_update_text_state(&text_state, penum, &font->FontMatrix);
	if (code < 0)
	    return code;
	for (i = 0; i < size; ++i) {
	    gs_glyph glyph = gdata[pte->index + i];
	    int chr = pdf_encode_glyph((gx_device_pdf *)pte->dev, -1, glyph,
				       (gs_font_base *)font,
				       text_state.pdfont);

	    if (chr < 0)
		return_error(gs_error_rangecheck);
	    buf[i] = (byte)chr;
	}
	encoded = true;
    } else
	return_error(gs_error_rangecheck);
    str.data = buf;
    if (size > 1 && (pte->text.operation & TEXT_INTERVENE)) {
	/* Just do one character. */
	str.size = 1;
	code = pdf_process_string(penum, &str, NULL, encoded, &text_state,
				  &index);
	if (code >= 0) {
	    pte->returned.current_char = buf[0];
	    code = TEXT_PROCESS_INTERVENE;
	}
    } else {
	str.size = size;
	code = pdf_process_string(penum, &str, NULL, encoded, &text_state,
				  &index);
    }
    pte->index += index;
    return code;
}

/*
 * Process a text string in a composite font with FMapType != 9 (CMap).
 */
private int
process_composite_text(gs_text_enum_t *pte, const void *vdata, void *vbuf,
		       uint size)
{
    byte *const buf = vbuf;
    pdf_text_enum_t *const penum = (pdf_text_enum_t *)pte;
    int index = 0, code;
    gs_string str;
    pdf_text_process_state_t text_state;
    pdf_text_enum_t curr, prev;
    gs_point total_width;

    str.data = buf;
    if (pte->text.operation &
	(TEXT_FROM_ANY - (TEXT_FROM_STRING | TEXT_FROM_BYTES))
	)
	return_error(gs_error_rangecheck);
    if (pte->text.operation & TEXT_INTERVENE) {
	/* Not implemented. (PostScript doesn't even allow this case.) */
	return_error(gs_error_rangecheck);
    }
    total_width.x = total_width.y = 0;
    curr = *penum;
    prev = curr;
    prev.current_font = 0;
    /* Scan runs of characters in the same leaf font. */
    for ( ; ; ) {
	int font_code, buf_index;
	gs_font *new_font;

	for (buf_index = 0; ; ++buf_index) {
	    gs_char chr;
	    gs_glyph glyph;

	    gs_text_enum_copy_dynamic((gs_text_enum_t *)&prev,
				      (gs_text_enum_t *)&curr, false);
	    font_code = pte->orig_font->procs.next_char_glyph
		((gs_text_enum_t *)&curr, &chr, &glyph);
	    /*
	     * We check for a font change by comparing the current
	     * font, rather than testing the return code, because
	     * it makes the control structure a little simpler.
	     */
	    switch (font_code) {
	    case 0:		/* no font change */
	    case 1:		/* font change */
		new_font = curr.fstack.items[curr.fstack.depth].font;
		if (new_font != prev.current_font)
		    break;
		if (chr != (byte)chr)	/* probably can't happen */
		    return_error(gs_error_rangecheck);
		buf[buf_index] = (byte)chr;
		continue;
	    case 2:		/* end of string */
		break;
	    default:	/* error */
		return font_code;
	    }
	    break;
	}
	str.size = buf_index;
	gs_text_enum_copy_dynamic((gs_text_enum_t *)&curr,
				  (gs_text_enum_t *)&prev, false);
	if (buf_index) {
	    /* buf_index == 0 is only possible the very first time. */
	    /*
	     * The FontMatrix of leaf descendant fonts is not updated
	     * by scalefont.  Compute the effective FontMatrix now.
	     */
	    const gs_matrix *psmat =
		&curr.fstack.items[curr.fstack.depth - 1].font->FontMatrix;
	    gs_matrix fmat;

	    gs_matrix_multiply(&curr.current_font->FontMatrix, psmat,
			       &fmat);
	    code = pdf_process_string(&curr, &str, &fmat, false, &text_state,
				      &index);
	    if (code < 0)
		return code;
	    gs_text_enum_copy_dynamic(pte, (gs_text_enum_t *)&curr, true);
	    if (pte->text.operation & TEXT_RETURN_WIDTH) {
		pte->returned.total_width.x = total_width.x +=
		    curr.returned.total_width.x;
		pte->returned.total_width.y = total_width.y +=
		    curr.returned.total_width.y;
	    }
	}
	if (font_code == 2)
	    break;
	curr.current_font = new_font;
    }
    return code;
}

/*
 * Process a text string in a composite font with FMapType == 9 (CMap).
 */
private const char *const standard_cmap_names[] = {
    "Identity-H", "Identity-V",

    "GB-EUC-H", "GB-EUC-V",
    "GBpc-EUC-H", "GBpc-EUC-V",
    "GBK-EUC-H", "GBK-EUC-V",
    "UniGB-UCS2-H", "UniGB-UCS2-V",

    "B5pc-H", "B5pc-V",
    "ETen-B5-H", "ETen-B5-V",
    "ETenms-B5-H", "ETenms-B5-V",
    "CNS-EUC-H", "CNS-EUC-V",
    "UniCNS-UCS2-H", "UniCNS-UCS2-V",

    "83pv-RKSJ-H",
    "90ms-RKSJ-H", "90ms-RKSJ-V",
    "90msp-RKSJ-H", "90msp-RKSJ-V",
    "90pv-RKSJ-H",
    "Add-RKSJ-H", "Add-RKSJ-V",
    "EUC-H", "EUC-V",
    "Ext-RKSJ-H", "Ext-RKSJ-V",
    "H", "V",
    "UniJIS-UCS2-H", "UniJIS-UCS2-V",
    "UniJIS-UCS2-HW-H", "UniJIS-UCS2-HW-V",

    "KSC-EUC-H", "KSC-EUC-V",
    "KSCms-UHC-H", "KSCms-UHC-V",
    "KSCms-UHC-HW-H", "KSCms-UHC-HW-V",
    "KSCpc-EUC-H",
    "UniKS-UCS2-H", "UniKS-UCS2-V",

    0
};
/* Mark a glyph and all its pieces as used. */
private int
mark_glyphs_used(gs_font *font, gs_glyph orig_glyph,
		 const pdf_font_descriptor_t *pfd)
{
#define MAX_GLYPH_PIECES 10	/* arbitrary */
    gs_glyph glyphs[MAX_GLYPH_PIECES];
    int count = 1;

    glyphs[0] = orig_glyph;
    while (count > 0) {
	gs_glyph glyph = glyphs[--count];

	if (glyph >= gs_min_cid_glyph &&
	    glyph - gs_min_cid_glyph < pfd->glyphs_count
	    ) {
	    int gid = glyph - gs_min_cid_glyph;
	    int index = gid >> 3, mask = 0x80 >> (gid & 7);

	    if (!(pfd->glyphs_used.data[index] & mask)) {
		uint piece_count = 1;
		uint max_pieces = MAX_GLYPH_PIECES - 1 - count;
		psf_add_subset_pieces(&glyphs[count], &piece_count,
				      max_pieces, max_pieces, font);

		count += piece_count - 1;
		if (count > MAX_GLYPH_PIECES)
		    return_error(gs_error_limitcheck);
		pfd->glyphs_used.data[index] |= mask;
	    }
	}
    }
#undef MAX_GLYPH_PIECES
    return 0;
}
/* Record widths and CID => GID mappings. */
private int
scan_cmap_text(gs_text_enum_t *pte, gs_font *font, pdf_font_t *psubf,
	       gs_font *subfont)
{
    pdf_font_descriptor_t *pfd = psubf->FontDescriptor;

    for ( ; ; ) {
	gs_char chr;
	gs_glyph glyph;
	int code = font->procs.next_char_glyph(pte, &chr, &glyph);

	if (code == 2)		/* end of string */
	    break;
	if (code < 0)
	    return code;
	if (glyph >= gs_min_cid_glyph) {
	    uint cid = glyph - gs_min_cid_glyph;

	    if (cid < pfd->chars_count) {
		int index = cid >> 3, mask = 0x80 >> (cid & 7);

		if (!(pfd->chars_used.data[index] & mask)) {
		    pfd->chars_used.data[index] |= mask;
		    if (psubf->CIDToGIDMap) {
			gs_font_cid2 *const subfont2 =
			    (gs_font_cid2 *)subfont;
			int gid =
			    subfont2->cidata.CIDMap_proc(subfont2, glyph);

			if (gid >= 0) {
			    psubf->CIDToGIDMap[cid] = gid;
			    mark_glyphs_used(subfont, gid + gs_min_cid_glyph,
					     pfd);
			}
		    }
		}
		if (!(psubf->widths_known[index] & mask)) {
		    int width;

		    code = pdf_glyph_width(psubf, glyph, subfont, &width);
		    if (code == 0) {
			psubf->Widths[cid] = width;
			psubf->widths_known[index] |= mask;
		    }
		}
	    }
	}
    }
    return 0;
}
private int
process_cmap_text(gs_text_enum_t *pte, const void *vdata, void *vbuf,
		  uint size)
{
    gx_device_pdf *const pdev = (gx_device_pdf *)pte->dev;
    pdf_text_enum_t *const penum = (pdf_text_enum_t *)pte;
    gs_font *font = pte->current_font;
    gs_font_type0 *const pfont = (gs_font_type0 *)font;
    gs_font *subfont = pfont->data.FDepVector[0];
    const gs_cmap_t *pcmap = pfont->data.CMap;
    const char *const *pcmn = standard_cmap_names;
    pdf_resource_t *pcmres = 0;
    pdf_font_t *ppf;
    pdf_font_t *psubf;
    pdf_text_process_state_t text_state;
    int code;

    if (pte->text.operation &
	(TEXT_FROM_ANY - (TEXT_FROM_STRING | TEXT_FROM_BYTES))
	)
	return_error(gs_error_rangecheck);
    if (pte->text.operation & TEXT_INTERVENE) {
	/* Not implemented.  (PostScript doesn't allow TEXT_INTERVENE.) */
	return_error(gs_error_rangecheck);
    }
    /* Require a single CID-keyed DescendantFont. */
    if (pfont->data.fdep_size != 1)
	return_error(gs_error_rangecheck);
    switch (subfont->FontType) {
    case ft_CID_encrypted:
    case ft_CID_TrueType:
	break;
    default:
	return_error(gs_error_rangecheck);
    }

    /*
     * If the CMap isn't standard, write it out if necessary.
     */

    for (; *pcmn != 0; ++pcmn)
	if (!bytes_compare((const byte *)*pcmn, strlen(*pcmn),
			   pcmap->CMapName.data, pcmap->CMapName.size))
	    break;
    if (*pcmn == 0) {		/* not standard */
	pcmres = pdf_find_resource_by_gs_id(pdev, resourceCMap, pcmap->id);
	if (pcmres == 0) {
	    /*
	     * Create the CMap object, and write the stream.
	     */
	    pdf_data_writer_t writer;
	    stream *s;

	    code = pdf_begin_resource(pdev, resourceCMap, pcmap->id, &pcmres);
	    if (code < 0)
		return code;
	    s = pdev->strm;
	    pprintd1(s, "/WMode %d/CMapName", pcmap->WMode);
	    pdf_put_name(pdev, pcmap->CMapName.data, pcmap->CMapName.size);
	    stream_puts(s, "/CIDSystemInfo");
	    code = pdf_write_CMap_system_info(pdev, pcmap);
	    if (code < 0)
		return code;
	    code = pdf_begin_data_binary(pdev, &writer, false);
	    if (code < 0)
		return code;
	    code = psf_write_cmap(writer.binary.strm, pcmap,
				  pdf_put_name_chars_proc(pdev), NULL);
	    if (code < 0)
		return code;
	    code = pdf_end_data(&writer);
	    if (code < 0)
		return code;
	    pcmres->object->written = true; /* don't try to write at end */
	}
    }

    /* Create pdf_font_t objects for the composite font and the CIDFont. */

    pte->current_font = subfont; /* CIDFont */
    {
	/* See process_composite_text re the following matrix computation. */
	gs_matrix fmat;

	gs_matrix_multiply(&subfont->FontMatrix, &font->FontMatrix, &fmat);
	code = pdf_update_text_state(&text_state, penum, &fmat);
    }
    pte->current_font = font;	/* restore composite font */
    if (code < 0)
	return code;
    psubf = text_state.pdfont;
    /* Make sure we always write glyph 0. */
    psubf->FontDescriptor->chars_used.data[0] |= 0x80;
    if (psubf->FontDescriptor->glyphs_used.data)
	psubf->FontDescriptor->glyphs_used.data[0] |= 0x80;
    code = pdf_update_text_state(&text_state, penum, &font->FontMatrix);
    if (code < 0)
	return code;
    if (code > 0)
	return_error(gs_error_rangecheck);
    ppf = text_state.pdfont;
    ppf->DescendantFont = psubf;
    /* Copy the subfont name and type to the composite font. */
    ppf->fname = psubf->FontDescriptor->FontName;
    ppf->sub_font_type = subfont->FontType;
    if (pcmres) {
	sprintf(ppf->cmapname, "%ld 0 R", pdf_resource_id(pcmres));
    } else {
	strcpy(ppf->cmapname, "/");
	strcat(ppf->cmapname, *pcmn);
    }

    /*
     * Scan the string to mark CIDs as used, record widths, and
     * (for CIDFontType 2 fonts) record CID => GID mappings and mark
     * glyphs as used.
     */

    {
	gs_text_enum_t orig;

	gs_text_enum_copy_dynamic(&orig, pte, false);
	code = scan_cmap_text(pte, font, psubf, subfont);
	gs_text_enum_copy_dynamic(pte, &orig, false);
    }

    /*
     * Let the default implementation do the rest of the work, but don't
     * allow it to draw anything in the output; instead, append the
     * characters to the output now.
     */

    {
	gs_text_params_t text;
	gs_text_enum_t *pte_default;
	gs_const_string str;
	int acode;

	text = pte->text;
	if (text.operation & TEXT_DO_DRAW)
	    text.operation =
		(text.operation - TEXT_DO_DRAW) | TEXT_DO_CHARWIDTH;
	code = pdf_default_text_begin(pte, &text, &pte_default);
	if (code < 0)
	    return code;
	str.data = vdata;
	str.size = size;
	if ((acode = pdf_write_text_process_state(pdev, pte, &text_state, &str)) < 0 ||
	    (acode = pdf_append_chars(pdev, str.data, str.size)) < 0
	    ) {
	    gs_text_release(pte_default, "process_cmap_text");
	    return acode;
	}
	/* Let the caller (pdf_text_process) handle pte_default. */
	gs_text_enum_copy_dynamic(pte_default, pte, false);
	penum->pte_default = pte_default;
	return code;
    }	
}

/*
 * Process a text string in a CIDFont.  (Only glyphshow is supported.)
 */
private int
process_cid_text(gs_text_enum_t *pte, const void *vdata, void *vbuf,
		 uint size)
{
    uint operation = pte->text.operation;
    gs_text_enum_t save;
    gs_font *scaled_font = pte->current_font;
    gs_font *font;		/* unscaled font */
    gx_device_pdf *pdev = (gx_device_pdf *)pte->dev;
    const gs_glyph *glyphs = (const gs_glyph *)vdata;
    gs_matrix scale_matrix;
    pdf_font_t *ppf;
    int code;

    if (!(operation & (TEXT_FROM_GLYPHS | TEXT_FROM_SINGLE_GLYPH)))
	return_error(gs_error_rangecheck);

    /*
     * PDF doesn't support glyphshow directly: we need to create a Type 0
     * font with an Identity CMap.  Make sure all the glyph numbers fit
     * into 16 bits.  (Eventually we should support wider glyphs too.)
     */
    {
	int i;
	byte *pchars = vbuf;

	for (i = 0; i * sizeof(gs_glyph) < size; ++i) {
	    ulong gnum = glyphs[i] - gs_min_cid_glyph;

	    if (gnum & ~0xffffL)
		return_error(gs_error_rangecheck);
	    *pchars++ = (byte)(gnum >> 8);
	    *pchars++ = (byte)gnum;
	}
    }

    /* Find the original (unscaled) version of this font. */

    for (font = scaled_font; font->base != font; )
	font = font->base;
    /* Compute the scaling matrix. */
    gs_matrix_invert(&font->FontMatrix, &scale_matrix);
    gs_matrix_multiply(&scale_matrix, &scaled_font->FontMatrix, &scale_matrix);

    /* Find or create the CIDFont resource. */

    ppf = (pdf_font_t *)
	pdf_find_resource_by_gs_id(pdev, resourceCIDFont, font->id);
    /* Check for the possibility that the base font has been freed. */
    if (ppf && ppf->FontDescriptor->written)
	ppf = 0;
    if (ppf == 0 || ppf->font == 0) {
	code = pdf_create_pdf_font(pdev, font, &font->FontMatrix, &ppf);
	if (code < 0)
	    return code;
    }

    /* Create the CMap and Type 0 font if they don't exist already. */

    if (!ppf->glyphshow_font) {
	gs_memory_t *mem = font->memory;
	gs_font_type0 *font0 = (gs_font_type0 *)
	    gs_font_alloc(mem, &st_gs_font_type0,
			  &gs_font_procs_default, NULL, "process_cid_text");
	/* We allocate Encoding dynamically only for the sake of the GC. */
	uint *encoding = (uint *)
	    gs_alloc_bytes(mem, sizeof(uint), "process_cid_text");
	gs_font **fdep = gs_alloc_struct_array(mem, 1, gs_font *,
					       &st_gs_font_ptr_element,
					       "process_cid_text");
	gs_cmap_t *pcmap;
	pdf_font_t *pgsf;

	if (font0 == 0 || encoding == 0 || fdep == 0)
	    return_error(gs_error_VMerror);
	code = gs_cmap_create_identity(&pcmap, 2, font->WMode, mem);
	if (code < 0)
	    return code;

	font0->FontMatrix = scale_matrix;
	font0->FontType = ft_composite;
	font0->procs.init_fstack = gs_type0_init_fstack;
	font0->procs.define_font = 0; /* not called */
	font0->procs.make_font = 0; /* not called */
	font0->procs.next_char_glyph = gs_type0_next_char_glyph;
	font0->key_name = font->key_name;
	font0->font_name = font->font_name;
	font0->data.FMapType = fmap_CMap;
	encoding[0] = 0;
	font0->data.Encoding = encoding;
	font0->data.encoding_size = 1;
	fdep[0] = font;
	font0->data.FDepVector = fdep;
	font0->data.fdep_size = 1;
	font0->data.CMap = pcmap;

	code = pdf_create_pdf_font(pdev, (gs_font *)font0, &font0->FontMatrix,
				   &pgsf);
	if (code < 0)
	    return code;
	pgsf->DescendantFont = ppf;
	ppf->glyphshow_font = pgsf;
    }

    /* Now handle the glyphshow as a show in the Type 0 font. */

    save = *pte;
    pte->current_font = ppf->glyphshow_font->font;
    /* Patch the operation temporarily for init_fstack. */
    pte->text.operation = (operation & ~TEXT_FROM_ANY) | TEXT_FROM_BYTES;
    /* Patch the data for process_cmap_text. */
    pte->text.data.bytes = vbuf;
    pte->text.size = size / sizeof(gs_glyph) * 2;
    pte->index = 0;
    gs_type0_init_fstack(pte, pte->current_font);
    code = process_cmap_text(pte, vbuf, vbuf, pte->text.size);
    pte->current_font = font;
    pte->text = save.text;
    pte->index = save.index + pte->index / 2;
    pte->fstack = save.fstack;
    return code;
}

/*
 * Internal procedure to process a string in a non-composite font.
 * Doesn't use or set pte->{data,size,index}; may use/set pte->xy_index;
 * may set pte->returned.total_width.
 */
private int
pdf_process_string(pdf_text_enum_t *penum, gs_string *pstr,
		   const gs_matrix *pfmat, bool encoded,
		   pdf_text_process_state_t *pts, int *pindex)
{
    gs_text_enum_t *const pte = (gs_text_enum_t *)penum;
    gx_device_pdf *const pdev = (gx_device_pdf *)penum->dev;
    gs_font *font;
    const gs_text_params_t *text = &pte->text;
    int i;
    int code = 0, mask;

    font = penum->current_font;
    if (pfmat == 0)
	pfmat = &font->FontMatrix;
    if (text->operation & TEXT_RETURN_WIDTH)
	gx_path_current_point(pte->path, &penum->origin);

    switch (font->FontType) {
    case ft_TrueType:
    case ft_encrypted:
    case ft_encrypted2:
	break;
    default:
	return_error(gs_error_rangecheck);
    }

    code = pdf_update_text_state(pts, penum, pfmat);
    if (code > 0) {
	/* Try not to emulate ADD_TO_WIDTH if we don't have to. */
	if (code & TEXT_ADD_TO_SPACE_WIDTH) {
	    if (!memchr(pstr->data, pte->text.space.s_char, pstr->size))
		code &= ~TEXT_ADD_TO_SPACE_WIDTH;
	}
    }
    if (code < 0)
	return code;
    mask = code;

    /* Check that all characters can be encoded. */

    for (i = 0; i < pstr->size; ++i) {
	int chr = pstr->data[i];
	pdf_font_t *pdfont = pts->pdfont;
	int code =
	    (encoded ? chr :
	     pdf_encode_char(pdev, chr, (gs_font_base *)font, pdfont));

	if (code < 0)
	    return code;
	/*
	 * For incrementally loaded fonts, expand FirstChar..LastChar
	 * if needed.
	 */
	if (code < pdfont->FirstChar)
	    pdfont->FirstChar = code;
	if (code > pdfont->LastChar)
	    pdfont->LastChar = code;
	pstr->data[i] = (byte)code;
    }

    /* Bring the text-related parameters in the output up to date. */
    code = pdf_write_text_process_state(pdev, pte, pts,
					(gs_const_string *)pstr);
    if (code < 0)
	return code;

    if (text->operation & TEXT_REPLACE_WIDTHS) {
	gs_point w;
	gs_matrix tmat;

	w.x = w.y = 0;
	tmat = pts->text_matrix;
	for (i = 0; i < pstr->size; *pindex = ++i, pte->xy_index++) {
	    gs_point d, dpt;

	    if (text->operation & TEXT_DO_DRAW) {
		code = pdf_append_chars(pdev, pstr->data + i, 1);
		if (code < 0)
		    return code;
	    }
	    gs_text_replaced_width(&pte->text, pte->xy_index, &d);
	    w.x += d.x, w.y += d.y;
	    gs_distance_transform(d.x, d.y, &ctm_only(pte->pis), &dpt);
	    tmat.tx += dpt.x;
	    tmat.ty += dpt.y;
	    if (i + 1 < pstr->size) {
		code = pdf_set_text_matrix(pdev, &tmat);
		if (code < 0)
		    return code;
	    }
	}
	pte->returned.total_width = w;
	if (text->operation & TEXT_RETURN_WIDTH)
	    code = gx_path_add_point(pte->path, float2fixed(tmat.tx),
				     float2fixed(tmat.ty));
	return code;
    }

    /* Write out the characters, unless we have to emulate ADD_TO_WIDTH. */
    if (mask == 0 && (text->operation & TEXT_DO_DRAW)) {
	code = pdf_append_chars(pdev, pstr->data, pstr->size);
	if (code < 0)
	    return code;
    }

    /*
     * If we don't need the total width, and don't have to emulate
     * ADD_TO_WIDTH, return now.  If the widths are available directly from
     * the font, place the characters and/or compute and return the total
     * width now.  Otherwise, call the default implementation.
     */
    {
	gs_point dpt;

	if (mask) {
	    /*
	     * Cancel the word and character spacing, since we're going
	     * to emulate them.
	     */
	    pts->words = pts->chars = 0;
	    code = pdf_write_text_process_state(pdev, pte, pts,
						(gs_const_string *)pstr);
	    if (code < 0)
		return code;
	    code = process_text_add_width(pte, font, pfmat, pts,
					  (gs_const_string *)pstr,
					  pindex, &dpt);
	    if (code < 0)
		return code;
	    if (!(text->operation & TEXT_RETURN_WIDTH))
		return 0;
	} else if (!(text->operation & TEXT_RETURN_WIDTH))
	    return 0;
	else {
	    code = process_text_return_width(pte, font, pts->pdfont, pfmat,
					     (gs_const_string *)pstr,
					     pindex, &dpt);
	    if (code < 0)
		return code;
	}
	pte->returned.total_width = dpt;
	gs_distance_transform(dpt.x, dpt.y, &ctm_only(pte->pis), &dpt);
	return gx_path_add_point(pte->path,
				 penum->origin.x + float2fixed(dpt.x),
				 penum->origin.y + float2fixed(dpt.y));
    }
}

/*
 * Compute the cached values in the text processing state from the text
 * parameters, current_font, and pis->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.
 */
private 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;
}
private int
pdf_update_text_state(pdf_text_process_state_t *ppts,
		      const pdf_text_enum_t *penum, const gs_matrix *pfmat)
{
    gx_device_pdf *const pdev = (gx_device_pdf *)penum->dev;
    gs_font *font = penum->current_font;
    pdf_font_t *ppf;
    gs_fixed_point cpt;
    gs_matrix orig_matrix, smat, tmat;
    double
	sx = pdev->HWResolution[0] / 72.0,
	sy = pdev->HWResolution[1] / 72.0;
    float chars = 0, words = 0, size;
    int mask = 0;
    int code = gx_path_current_point(penum->path, &cpt);

    if (code < 0)
	return code;

    pdf_font_orig_matrix(font, &orig_matrix);
    DISCARD(pdf_find_orig_font(pdev, font, &orig_matrix));

    /* Compute the scaling matrix and combined matrix. */

    gs_matrix_invert(&orig_matrix, &smat);
    gs_matrix_multiply(&smat, pfmat, &smat);
    tmat = ctm_only(penum->pis);
    tmat.tx = tmat.ty = 0;
    gs_matrix_multiply(&smat, &tmat, &tmat);

    /* Try to find a reasonable size value.  This isn't necessary, */
    /* but it's worth a little effort. */

    size = fabs(tmat.yy) / sy;
    if (size < 0.01)
	size = fabs(tmat.xx) / sx;
    if (size < 0.01)
	size = 1;

    /* Check for spacing parameters we can handle, and transform them. */

    if (penum->text.operation & TEXT_ADD_TO_ALL_WIDTHS) {
	gs_point pt;

	code = transform_delta_inverse(&penum->text.delta_all, &smat, &pt);
	if (code >= 0 && pt.y == 0)
	    chars = pt.x * size;
	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)
	    words = pt.x * size;
	else
	    mask |= TEXT_ADD_TO_SPACE_WIDTH;
    }

    /* Find or create the font resource. */

    ppf = (pdf_font_t *)
	pdf_find_resource_by_gs_id(pdev, pdf_font_resource_type(font),
				   font->id);
    /* Check for the possibility that the base font has been freed. */
    if (ppf && ppf->FontDescriptor && ppf->FontDescriptor->written)
	ppf = 0;
    if (ppf == 0 || ppf->font == 0) {
	code = pdf_create_pdf_font(pdev, font, &orig_matrix, &ppf);
	if (code < 0)
	    return code;
    }

    /* 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->chars = chars;
    ppts->words = words;
    ppts->size = size;
    ppts->text_matrix = tmat;
    ppts->mode = (font->PaintType == 0 ? 0 : 1);
    ppts->font = font;
    ppts->pdfont = ppf;

    return mask;
}

/*
 * For a given character, check whether the encoding of bfont (the current
 * font) is compatible with that of the underlying unscaled, possibly
 * standard, base font, and if not, whether we can re-encode the character
 * using the base font's encoding.  Return the (possibly re-encoded)
 * character if successful.
 *
 * We have to handle re-encoded Type 1 fonts, because TeX output makes
 * constant use of them.  We have several alternatives for how to handle
 * characters whose encoding doesn't match their encoding in the base font's
 * built-in encoding.  If a character's glyph doesn't match the character's
 * glyph in the encoding built up so far, we check if the font has that
 * glyph at all; if not, we fall back to a bitmap.  Otherwise, we use one or
 * both of the following algorithms:
 *
 *      1. If this is the first time a character at this position has been
 *      seen, assign its glyph to that position in the encoding.
 *      We do this step if the device parameter ReAssignCharacters is true.
 *      (This is the default.)
 *
 *      2. If the glyph is present in the encoding at some other position,
 *      substitute that position for the character; otherwise, assign the
 *      glyph to an unoccupied (.notdef) position.
 *      We do this step if the device parameter ReEncodeCharacters is true.
 *      (This is the default.)
 *
 *      3. Finally, fall back to using a bitmap.
 *
 * If it is essential that all strings in the output contain exactly the
 * same character codes as the input, set ReEncodeCharacters to false.  If
 * it is important that strings be searchable, but some non-searchable
 * strings can be tolerated, the defaults are appropriate.  If searchability
 * is not important, set ReAssignCharacters to false.
 * 
 * Since the chars_used bit vector keeps track of characters by their
 * position in the Encoding, not by their glyph identifier, it can't record
 * use of unencoded characters.  Therefore, if we ever use an unencoded
 * character in an embedded font, we force writing the entire font rather
 * than a subset.  This is inelegant but (for the moment) too awkward to
 * fix.
 *
 * Acrobat Reader 3's Print function has a bug that make re-encoded
 * characters print as blank if the font is substituted (not embedded or one
 * of the base 14).  To work around this bug, when CompatibilityLevel <= 1.2,
 * for non-embedded non-base fonts, no substitutions or re-encodings are
 * allowed.
 */
inline private void
record_used(pdf_font_descriptor_t *pfd, int c)
{
    pfd->chars_used.data[c >> 3] |= 0x80 >> (c & 7);
}
inline private gs_encoding_index_t
base_encoding_index(const pdf_font_t *ppf)
{
    return
	(ppf->BaseEncoding != ENCODING_INDEX_UNKNOWN ? ppf->BaseEncoding :
	 ppf->index >= 0 ? pdf_standard_fonts[ppf->index].base_encoding :
	 ENCODING_INDEX_UNKNOWN);
}

private int
pdf_encode_char(gx_device_pdf *pdev, int chr, gs_font_base *bfont,
		pdf_font_t *ppf)
{
    pdf_font_descriptor_t *const pfd = ppf->FontDescriptor;
    /*
     * bfont is the current font in which the text is being shown.
     * ei is its encoding_index.
     */
    gs_encoding_index_t ei = bfont->encoding_index;
    /*
     * base_font is the font that underlies this PDF font (i.e., this PDF
     * font is base_font plus some possible Encoding and Widths differences,
     * and possibly a different FontMatrix).  base_font is 0 iff this PDF
     * font is one of the standard 14 (i.e., ppf->index >= 0).  bei is the
     * index of the BaseEncoding (explicit or, for the standard fonts,
     * implicit) that will be written in the PDF file: it is not necessarily
     * the same as base_font->encoding_index, or even
     * base_font->nearest_encoding_index.
     */
    gs_font *base_font = pfd->base_font;
    bool have_font = base_font != 0 && base_font->FontType != ft_composite;
    bool is_standard = ppf->index >= 0;
    gs_encoding_index_t bei = base_encoding_index(ppf);
    pdf_encoding_element_t *pdiff = ppf->Differences;
    /*
     * If set, font_glyph is the glyph currently associated with chr in
     * base_font + bei + diffs; glyph is the glyph corresponding to chr in
     * bfont.
     */
    gs_glyph font_glyph, glyph;
#define IS_USED(c)\
  (((pfd)->chars_used.data[(c) >> 3] & (0x80 >> ((c) & 7))) != 0)

    if (ei == bei && ei != ENCODING_INDEX_UNKNOWN && pdiff == 0) {
	/*
	 * Just note that the character has been used with its original
	 * encoding.
	 */
	record_used(pfd, chr);
	return chr;
    }
    if (!is_standard && !have_font)
	return_error(gs_error_undefined); /* can't encode */

#define ENCODE_NO_DIFF(ch)\
   (bei != ENCODING_INDEX_UNKNOWN ?\
    bfont->procs.callbacks.known_encode((gs_char)(ch), bei) :\
    /* have_font */ bfont->procs.encode_char(base_font, ch, GLYPH_SPACE_NAME))
#define HAS_DIFF(ch) (pdiff != 0 && pdiff[ch].str.data != 0)
#define ENCODE_DIFF(ch) (pdiff[ch].glyph)
#define ENCODE(ch)\
  (HAS_DIFF(ch) ? ENCODE_DIFF(ch) : ENCODE_NO_DIFF(ch))

    font_glyph = ENCODE(chr);
    glyph =
	(ei == ENCODING_INDEX_UNKNOWN ?
	 bfont->procs.encode_char((gs_font *)bfont, chr, GLYPH_SPACE_NAME) :
	 bfont->procs.callbacks.known_encode(chr, ei));
    if (glyph == font_glyph) {
	record_used(pfd, chr);
	return chr;
    }

    return pdf_encode_glyph(pdev, chr, glyph, bfont, ppf);
}
/*
 * Encode a glyph that doesn't appear in the default place in an encoding.
 * chr will be -1 if we are encoding a glyph for glyphshow.
 */
private int
pdf_encode_glyph(gx_device_pdf *pdev, int chr, gs_glyph glyph,
		 gs_font_base *bfont, pdf_font_t *ppf)
{
    pdf_font_descriptor_t *const pfd = ppf->FontDescriptor;
    pdf_encoding_element_t *pdiff = ppf->Differences;
    gs_font *base_font = pfd->base_font;
    gs_encoding_index_t bei = base_encoding_index(ppf);

    if (ppf->index < 0 && pfd->FontFile_id == 0 &&
	pdev->CompatibilityLevel <= 1.2
	) {
	/*
	 * Work around the bug in Acrobat Reader 3's Print function
	 * that makes re-encoded characters in substituted fonts
	 * print as blank.
	 */
	return_error(gs_error_undefined);
    }

    /*
     * TrueType fonts don't allow Differences in the encoding, but we might
     * be able to re-encode the character if it appears elsewhere in the
     * encoding.
     */
    if (bfont->FontType == ft_TrueType) {
	if (pdev->ReEncodeCharacters) {
	    int c;

	    for (c = 0; c < 256; ++c)
		if (ENCODE_NO_DIFF(c) == glyph) {
		    record_used(pfd, c);
		    return c;
		}
	}
	return_error(gs_error_undefined);
    }

    /*
     * If the font isn't going to be embedded, check whether this glyph is
     * available in the base font's glyph set at all.
     */
    if (pfd->FontFile_id == 0) {
	switch (bei) {
	case ENCODING_INDEX_STANDARD:
	case ENCODING_INDEX_ISOLATIN1:
	case ENCODING_INDEX_WINANSI:
	case ENCODING_INDEX_MACROMAN:
	    if (encoding_find_glyph(bfont, glyph, ENCODING_INDEX_ALOGLYPH) < 0
	    /*
	     * One would expect that the standard Latin character set in PDF
	     * 1.3, which was released after PostScript 3, would be the full
	     * 315-character PostScript 3 set.  However, the PDF 1.3
	     * reference manual clearly specifies that the PDF 1.3 Latin
	     * character set is the smaller PostScript Level 2 set,
	     * and we have verified that this is the case in Acrobat 4.
	     * Therefore, we have commented out the second part of the
	     * conditional below.
	     */
#if 0
		&&
		(pdev->CompatibilityLevel < 1.3 ||
		 encoding_find_glyph(bfont, glyph, ENCODING_INDEX_ALXGLYPH) < 0)
#endif
		)
		return_error(gs_error_undefined);
	default:
	    break;
	}
    }

    if (pdev->ReAssignCharacters && chr >= 0) {
	/*
	 * If this is the first time we've seen this character,
	 * assign the glyph to its position in the encoding.
	 */
	if (!HAS_DIFF(chr) && !IS_USED(chr)) {
	    int code =
		pdf_add_encoding_difference(pdev, ppf, chr, bfont, glyph);

	    if (code >= 0) {
		/*
		 * As noted in the comments at the beginning of this file,
		 * we don't have any way to record, for the purpose of
		 * subsetting, the fact that a particular unencoded glyph
		 * was used.  If this glyph doesn't appear anywhere else in
		 * the base encoding, fall back to writing the entire font.
		 */
		int c;

		for (c = 0; c < 256; ++c)
		    if (ENCODE_NO_DIFF(c) == glyph)
			break;
		if (c < 256)	/* found */
		    record_used(pfd, c);
		else {		/* not found */
		    if (pfd->do_subset == FONT_SUBSET_YES)
			return_error(gs_error_undefined);
		    pfd->do_subset = FONT_SUBSET_NO;
		}
		return chr;
	    }
	}
    }

    if (pdev->ReEncodeCharacters || chr < 0) {
	/*
	 * Look for the glyph at some other position in the encoding.
	 */
	int c, code;

	for (c = 0; c < 256; ++c) {
	    if (HAS_DIFF(c)) {
		if (ENCODE_DIFF(c) == glyph)
		    return c;
	    } else if (ENCODE_NO_DIFF(c) == glyph) {
		record_used(pfd, c);
		return c;
	    }
	}
	/*
	 * The glyph isn't encoded anywhere.  Look for a
	 * never-referenced .notdef position where we can put it.
	 */
	for (c = 0; c < 256; ++c) {
	    gs_glyph font_glyph;

	    if (HAS_DIFF(c) || IS_USED(c))
		continue; /* slot already referenced */
	    font_glyph = ENCODE_NO_DIFF(c);
	    if (font_glyph == gs_no_glyph ||
		gs_font_glyph_is_notdef(bfont, font_glyph)
		)
		break;
	}
	if (c == 256)	/* no .notdef positions left */
	    return_error(gs_error_undefined);
	code = pdf_add_encoding_difference(pdev, ppf, c, bfont, glyph);
	if (code < 0)
	    return code;
	/* See under ReAssignCharacters above regarding the following: */
	if (pfd->do_subset == FONT_SUBSET_YES)
	    return_error(gs_error_undefined);
	pfd->do_subset = FONT_SUBSET_NO;
	return c;
    }

    return_error(gs_error_undefined);

#undef IS_USED
#undef ENCODE_NO_DIFF
#undef HAS_DIFF
#undef ENCODE_DIFF
#undef ENCODE
}

/*
 * Write out commands to make the output state match the processing state.
 */
private double
font_matrix_scaling(const gs_font *font)
{
    return fabs((font->FontMatrix.yy != 0 ? font->FontMatrix.yy :
		 font->FontMatrix.yx));
}
private int
pdf_write_text_process_state(gx_device_pdf *pdev,
			     const gs_text_enum_t *pte,	/* for pdcolor, pis */
			     const pdf_text_process_state_t *ppts,
			     const gs_const_string *pstr)
{
    int code;

    pdf_set_font_and_size(pdev, ppts->pdfont, ppts->size);
    code = pdf_set_text_matrix(pdev, &ppts->text_matrix);
    if (code < 0)
	return code;

    if (pdev->text.character_spacing != ppts->chars &&
	pstr->size + pdev->text.buffer_count > 1
	) {
	code = pdf_open_page(pdev, PDF_IN_TEXT);
	if (code < 0)
	    return code;
	pprintg1(pdev->strm, "%g Tc\n", ppts->chars);
	pdev->text.character_spacing = ppts->chars;
    }

    if (pdev->text.word_spacing != ppts->words &&
	(memchr(pstr->data, 32, pstr->size) ||
	 memchr(pdev->text.buffer, 32, pdev->text.buffer_count))
	) {
	code = pdf_open_page(pdev, PDF_IN_TEXT);
	if (code < 0)
	    return code;
	pprintg1(pdev->strm, "%g Tw\n", ppts->words);
	pdev->text.word_spacing = ppts->words;
    }

    if (pdev->text.render_mode != ppts->mode) {
	code = pdf_open_page(pdev, PDF_IN_TEXT);
	if (code < 0)
	    return code;
	pprintd1(pdev->strm, "%d Tr\n", ppts->mode);
	if (ppts->mode) {
	    /* Also write all the parameters for stroking. */
	    gs_imager_state *pis = pte->pis;
	    float save_width = pis->line_params.half_width;
	    const gs_font *font = ppts->font;
	    double scaled_width = font->StrokeWidth;

	    /* Note that we compute pis->line_params.half_width in device space,
	     * even though it logically represents a value in user space.  
	     * The 'scale' value compensates for this.
	     */
	    scaled_width *= font_matrix_scaling(font);
	    scaled_width *= min(hypot(pte->pis->ctm.xx, pte->pis->ctm.yx) / 
                                    pdev->HWResolution[0] * pdev->HWResolution[1],
                                hypot(pte->pis->ctm.xy, pte->pis->ctm.yy));
	    pis->line_params.half_width = scaled_width / 2;
	    code = pdf_prepare_stroke(pdev, pis);
	    if (code >= 0) {
                /*
                 * See stream_to_text in gdevpdfu.c re the computation of
                 * the scaling value.
                 */
		double scale = 72.0 / pdev->HWResolution[1];

		code = gdev_vector_prepare_stroke((gx_device_vector *)pdev,
						  pis, NULL, NULL, scale);
	    }
	    pis->line_params.half_width = save_width;
	    if (code < 0)
		return code;
	}
	pdev->text.render_mode = ppts->mode;
    }

    return 0;
}

/* Compute the total text width (in user space). */
private int
process_text_return_width(const gs_text_enum_t *pte, gs_font *font,
			  pdf_font_t *pdfont, const gs_matrix *pfmat,
			  const gs_const_string *pstr,
			  int *pindex, gs_point *pdpt)
{
    int i, w;
    gs_point dpt;
    gs_matrix smat;
    int num_spaces = 0;
    int space_char =
	(pte->text.operation & TEXT_ADD_TO_SPACE_WIDTH ?
	 pte->text.space.s_char : -1);

    for (i = *pindex, w = 0; i < pstr->size; ++i) {
	int cw;
	int code = pdf_char_width(pdfont, pstr->data[i], font, &cw);

	if (code < 0)
	    return code;
	w += cw;
	if (pstr->data[i] == space_char)
	    ++num_spaces;
    }
    pdf_font_orig_matrix(font, &smat);
    gs_distance_transform(w / 1000.0 / smat.xx, 0.0, pfmat, &dpt);
    if (pte->text.operation & TEXT_ADD_TO_ALL_WIDTHS) {
	int num_chars = pstr->size - pte->index;

	dpt.x += pte->text.delta_all.x * num_chars;
	dpt.y += pte->text.delta_all.y * num_chars;
    }
    if (pte->text.operation & TEXT_ADD_TO_SPACE_WIDTH) {
	dpt.x += pte->text.delta_space.x * num_spaces;
	dpt.y += pte->text.delta_space.y * num_spaces;
    }
    *pindex = i;
    *pdpt = dpt;
    return 0;
}
/*
 * Emulate TEXT_ADD_TO_ALL_WIDTHS and/or TEXT_ADD_TO_SPACE_WIDTH.
 * We know that the Tw and Tc values are zero.
 */
private int
process_text_add_width(gs_text_enum_t *pte, gs_font *font,
		       const gs_matrix *pfmat,
		       const pdf_text_process_state_t *ppts,
		       const gs_const_string *pstr,
		       int *pindex, gs_point *pdpt)
{
    gx_device_pdf *const pdev = (gx_device_pdf *)pte->dev;
    int i, w;
    double scale = (font->FontType == ft_TrueType ? 0.001 : 1.0);
    gs_point dpt;
	gs_matrix tmat;
    int space_char =
	(pte->text.operation & TEXT_ADD_TO_SPACE_WIDTH ?
	 pte->text.space.s_char : -1);
    int code = 0;
    bool move = false;

    dpt.x = dpt.y = 0;
    tmat = ppts->text_matrix;
    for (i = *pindex, w = 0; i < pstr->size; ++i) {
	int cw;
	int code = pdf_char_width(ppts->pdfont, pstr->data[i], font, &cw);
	gs_point wpt;

	if (code < 0)
	    break;
	if (move) {
	    gs_point mpt;

	    gs_distance_transform(dpt.x, dpt.y, &ctm_only(pte->pis), &mpt);
	    tmat.tx = ppts->text_matrix.tx + mpt.x;
	    tmat.ty = ppts->text_matrix.ty + mpt.y;
	    code = pdf_set_text_matrix(pdev, &tmat);
	    if (code < 0)
		break;
	    move = false;
	}
	if (pte->text.operation & TEXT_DO_DRAW) {
	    code = pdf_append_chars(pdev, &pstr->data[i], 1);
	    if (code < 0)
		break;
	}
	gs_distance_transform(cw * scale, 0.0, pfmat, &wpt);
	dpt.x += wpt.x, dpt.y += wpt.y;
	if (pte->text.operation & TEXT_ADD_TO_ALL_WIDTHS) {
	    dpt.x += pte->text.delta_all.x;
	    dpt.y += pte->text.delta_all.y;
	    move = true;
	}
	if (pstr->data[i] == space_char) {
	    dpt.x += pte->text.delta_space.x;
	    dpt.y += pte->text.delta_space.y;
	    move = true;
	}
    }
    *pindex = i;		/* only do the part we haven't done yet */
    *pdpt = dpt;
    return code;
}

/* ---------------- Text and font utilities ---------------- */

/*
 * Try to find a glyph in a (pseudo-)encoding.  If present, return the
 * index (character code); if absent, return -1.
 */
private int
encoding_find_glyph(gs_font_base *bfont, gs_glyph font_glyph,
		    gs_encoding_index_t index)
{
    int ch;
    gs_glyph glyph;

    for (ch = 0;
	 (glyph = bfont->procs.callbacks.known_encode((gs_char)ch, index)) !=
	     gs_no_glyph;
	 ++ch)
	if (glyph == font_glyph)
	    return ch;
    return -1;
}
